Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
content_addressed_append_only_tree.hpp
Go to the documentation of this file.
1// === AUDIT STATUS ===
2// internal: { status: Complete, auditors: [Nishat], commit: 22d6fc368da0fbe5412f4f7b2890a052aa48d803 }
3// external_1: { status: not started, auditors: [], commit: }
4// external_2: { status: not started, auditors: [], commit: }
5// =====================
6
7#pragma once
8
9#include <cstddef>
10#include <cstdint>
11#include <exception>
12#include <functional>
13#include <iostream>
14#include <memory>
15#include <optional>
16#include <ostream>
17#include <random>
18#include <stdexcept>
19#include <string>
20#include <utility>
21#include <vector>
22
32
34
35using namespace bb;
36
45template <typename Store, typename HashingPolicy> class ContentAddressedAppendOnlyTree {
46 public:
48
49 // Asynchronous methods accept these callback function types as arguments
50 using EmptyResponseCallback = std::function<void(Response&)>;
56 using GetLeafCallback = std::function<void(TypedResponse<GetLeafResponse>&)>;
57 using CommitCallback = std::function<void(TypedResponse<CommitResponse>&)>;
66
67 // Only construct from provided store and thread pool, no copies or moves
68 ContentAddressedAppendOnlyTree(std::unique_ptr<Store> store,
70 const std::vector<fr>& initial_values = {},
71 bool commit_genesis_state = true);
77
83 virtual void add_value(const fr& value, const AppendCompletionCallback& on_completion);
84
90 virtual void add_values(const std::vector<fr>& values, const AppendCompletionCallback& on_completion);
91
98 void get_sibling_path(const index_t& index, const HashPathCallback& on_completion, bool includeUncommitted) const;
99
107 void get_sibling_path(const index_t& index,
108 const block_number_t& blockNumber,
109 const HashPathCallback& on_completion,
110 bool includeUncommitted) const;
111
119 void get_subtree_sibling_path(uint32_t subtree_depth,
120 const HashPathCallback& on_completion,
121 bool includeUncommitted) const;
122
131 void get_subtree_sibling_path(const index_t& leaf_index,
132 uint32_t subtree_depth,
133 const HashPathCallback& on_completion,
134 bool includeUncommitted) const;
135
141 void get_meta_data(bool includeUncommitted, const MetaDataCallback& on_completion) const;
142
149 void get_meta_data(const block_number_t& blockNumber,
150 bool includeUncommitted,
151 const MetaDataCallback& on_completion) const;
152
159 void get_leaf(const index_t& index, bool includeUncommitted, const GetLeafCallback& completion) const;
160
168 void get_leaf(const index_t& index,
169 const block_number_t& blockNumber,
170 bool includeUncommitted,
171 const GetLeafCallback& completion) const;
172
177 bool includeUncommitted,
178 const FindLeafCallback& on_completion) const;
179
184 const block_number_t& blockNumber,
185 bool includeUncommitted,
186 const FindLeafCallback& on_completion) const;
187
192 bool includeUncommitted,
193 const FindSiblingPathCallback& on_completion) const;
194
199 const block_number_t& block_number,
200 bool includeUncommitted,
201 const FindSiblingPathCallback& on_completion) const;
202
207 const index_t& start_index,
208 bool includeUncommitted,
209 const FindLeafCallback& on_completion) const;
210
215 const index_t& start_index,
216 const block_number_t& blockNumber,
217 bool includeUncommitted,
218 const FindLeafCallback& on_completion) const;
219
223 void find_block_numbers(const std::vector<index_t>& indices, const GetBlockForIndexCallback& on_completion) const;
224
229 void find_block_numbers(const std::vector<index_t>& indices,
230 const block_number_t& blockNumber,
231 const GetBlockForIndexCallback& on_completion) const;
232
236 void commit(const CommitCallback& on_completion);
237
241 void rollback(const RollbackCallback& on_completion);
242
246 uint32_t depth() const { return depth_; }
247
248 void remove_historic_block(const block_number_t& blockNumber, const RemoveHistoricBlockCallback& on_completion);
249
250 void unwind_block(const block_number_t& blockNumber, const UnwindBlockCallback& on_completion);
251
252 void finalize_block(const block_number_t& blockNumber, const FinalizeBlockCallback& on_completion);
253
254 void checkpoint(const CheckpointCallback& on_completion);
255 void commit_checkpoint(const CheckpointCommitCallback& on_completion);
256 void revert_checkpoint(const CheckpointRevertCallback& on_completion);
257 void commit_all_checkpoints_to(const CheckpointCommitCallback& on_completion);
258 void revert_all_checkpoints_to(const CheckpointRevertCallback& on_completion);
259 void commit_to_depth(uint32_t target_depth, const CheckpointCommitCallback& on_completion);
260 void revert_to_depth(uint32_t target_depth, const CheckpointRevertCallback& on_completion);
261 uint32_t checkpoint_depth() const;
262
263 protected:
264 using ReadTransaction = typename Store::ReadTransaction;
265 using ReadTransactionPtr = typename Store::ReadTransactionPtr;
266
268
270
271 void add_values_internal(std::shared_ptr<std::vector<fr>> values,
272 fr& new_root,
273 index_t& new_size,
274 bool update_index);
275
276 void add_values_internal(const std::vector<fr>& values,
277 const AppendCompletionCallback& on_completion,
278 bool update_index);
279
281 uint32_t subtree_depth,
282 const RequestContext& requestContext,
283 ReadTransaction& tx) const;
284
285 std::optional<fr> find_leaf_hash(const index_t& leaf_index,
286 const RequestContext& requestContext,
287 ReadTransaction& tx,
288 bool updateNodesByIndexCache = false) const;
289
290 index_t get_batch_insertion_size(const index_t& treeSize, const index_t& remainingAppendSize);
291
293 std::vector<fr>& values, fr& new_root, index_t& new_size, bool update_index, ReadTransaction& tx);
294
295 std::unique_ptr<Store> store_;
296 uint32_t depth_;
297 uint64_t max_size_;
298 std::vector<fr> zero_hashes_;
300};
301
302template <typename Store, typename HashingPolicy>
304 std::unique_ptr<Store> store,
306 const std::vector<fr>& initial_values,
307 bool commit_genesis_state)
308 : store_(std::move(store))
309 , workers_(workers)
310{
311 TreeMeta meta;
312 // start by reading the meta data from the backing store
313 store_->get_meta(meta);
314 depth_ = meta.depth;
315 zero_hashes_.resize(depth_ + 1);
316
317 // Create the zero hashes for the tree
318 auto current = HashingPolicy::zero_hash();
319 for (size_t i = depth_; i > 0; --i) {
320 // std::cout << "Zero hash at " << i << " : " << current << std::endl;
321 zero_hashes_[i] = current;
322 current = HashingPolicy::hash_pair(current, current);
323 }
324 zero_hashes_[0] = current;
325 // std::cout << "Zero root: " << current << std::endl;
326
328 // if root is non-zero it means the tree has already been initialized
329 if (meta.root != fr::zero()) {
330 return;
331 }
332
333 if (initial_values.empty()) {
334 meta.initialRoot = meta.root = current;
335 meta.initialSize = meta.size = 0;
336 } else {
337 Signal signal(1);
339 add_values(initial_values, [&](const TypedResponse<AddDataResponse>& resp) {
340 result = resp;
341 signal.signal_level(0);
342 });
343
344 signal.wait_for_level(0);
345 if (!result.success) {
346 throw std::runtime_error(format("Failed to initialize tree: ", result.message));
347 }
348
349 store_->get_meta(meta);
350
351 meta.initialRoot = meta.root = result.inner.root;
352 meta.initialSize = meta.size = result.inner.size;
353 }
354 store_->put_meta(meta);
355
356 if (commit_genesis_state) {
357 store_->commit_genesis_state();
358 }
359}
360
361template <typename Store, typename HashingPolicy>
363 const MetaDataCallback& on_completion) const
364{
365 auto job = [=, this]() {
366 execute_and_report<TreeMetaResponse>(
367 [=, this](TypedResponse<TreeMetaResponse>& response) {
368 ReadTransactionPtr tx = store_->create_read_transaction();
369 store_->get_meta(response.inner.meta, *tx, includeUncommitted);
370 },
371 on_completion);
372 };
373 workers_->enqueue(job);
374}
375
376template <typename Store, typename HashingPolicy>
378 bool includeUncommitted,
379 const MetaDataCallback& on_completion) const
380{
381 auto job = [=, this]() {
382 execute_and_report<TreeMetaResponse>(
383 [=, this](TypedResponse<TreeMetaResponse>& response) {
384 ReadTransactionPtr tx = store_->create_read_transaction();
385 store_->get_meta(response.inner.meta, *tx, includeUncommitted);
386
387 BlockPayload blockData;
388 if (!store_->get_block_data(blockNumber, blockData, *tx)) {
389 throw std::runtime_error(
390 format("Unable to get meta data for block ", blockNumber, ", failed to get block data."));
391 }
392
393 response.inner.meta.size = blockData.size;
394 response.inner.meta.committedSize = blockData.size;
395 response.inner.meta.root = blockData.root;
396 },
397 on_completion);
398 };
399 workers_->enqueue(job);
400}
401
402template <typename Store, typename HashingPolicy>
404 const HashPathCallback& on_completion,
405 bool includeUncommitted) const
406{
407 get_subtree_sibling_path(index, 0, on_completion, includeUncommitted);
408}
409
410template <typename Store, typename HashingPolicy>
412 const block_number_t& blockNumber,
413 const HashPathCallback& on_completion,
414 bool includeUncommitted) const
415{
416 auto job = [=, this]() {
417 execute_and_report<GetSiblingPathResponse>(
418 [=, this](TypedResponse<GetSiblingPathResponse>& response) {
419 ReadTransactionPtr tx = store_->create_read_transaction();
420 BlockPayload blockData;
421 if (!store_->get_block_data(blockNumber, blockData, *tx)) {
422 throw std::runtime_error(format("Unable to get sibling path for index ",
423 index,
424 " at block ",
425 blockNumber,
426 ", failed to get block data."));
427 }
428
429 RequestContext requestContext;
430 requestContext.blockNumber = blockNumber;
431 requestContext.includeUncommitted = includeUncommitted;
432 requestContext.root = blockData.root;
433 OptionalSiblingPath optional_path = get_subtree_sibling_path_internal(index, 0, requestContext, *tx);
434 response.inner.path = optional_sibling_path_to_full_sibling_path(optional_path);
435 },
436 on_completion);
437 };
438 workers_->enqueue(job);
439}
440
441template <typename Store, typename HashingPolicy>
443 const std::vector<index_t>& indices, const GetBlockForIndexCallback& on_completion) const
444{
445 auto job = [=, this]() {
446 execute_and_report<BlockForIndexResponse>(
447 [=, this](TypedResponse<BlockForIndexResponse>& response) {
448 response.inner.blockNumbers.reserve(indices.size());
449 ReadTransactionPtr tx = store_->create_read_transaction();
450 for (index_t index : indices) {
451 std::optional<block_number_t> block = store_->find_block_for_index(index, *tx);
452 response.inner.blockNumbers.emplace_back(block);
453 }
454 },
455 on_completion);
456 };
457 workers_->enqueue(job);
458}
459
460template <typename Store, typename HashingPolicy>
462 const std::vector<index_t>& indices,
463 const block_number_t& blockNumber,
464 const GetBlockForIndexCallback& on_completion) const
465{
466 auto job = [=, this]() {
467 execute_and_report<BlockForIndexResponse>(
468 [=, this](TypedResponse<BlockForIndexResponse>& response) {
469 response.inner.blockNumbers.reserve(indices.size());
470 BlockPayload blockPayload;
471 ReadTransactionPtr tx = store_->create_read_transaction();
472 if (!store_->get_block_data(blockNumber, blockPayload, *tx)) {
473 throw std::runtime_error(format("Unable to find block numbers for indices for block ",
474 blockNumber,
475 ", failed to get block data."));
476 }
477 index_t maxIndex = blockPayload.size;
478 for (index_t index : indices) {
479 bool outOfRange = index >= maxIndex;
481 outOfRange ? std::nullopt : store_->find_block_for_index(index, *tx);
482 response.inner.blockNumbers.emplace_back(block);
483 }
484 },
485 on_completion);
486 };
487 workers_->enqueue(job);
488}
489
490template <typename Store, typename HashingPolicy>
492 uint32_t subtree_depth, const HashPathCallback& on_completion, bool includeUncommitted) const
493{
494 auto job = [=, this]() {
495 execute_and_report<GetSiblingPathResponse>(
496 [=, this](TypedResponse<GetSiblingPathResponse>& response) {
497 ReadTransactionPtr tx = store_->create_read_transaction();
498 TreeMeta meta;
499 store_->get_meta(meta, *tx, includeUncommitted);
500 RequestContext requestContext;
501 requestContext.includeUncommitted = includeUncommitted;
502 requestContext.root = store_->get_current_root(*tx, includeUncommitted);
503 OptionalSiblingPath optional_path =
504 get_subtree_sibling_path_internal(meta.size, subtree_depth, requestContext, *tx);
505 response.inner.path = optional_sibling_path_to_full_sibling_path(optional_path);
506 },
507 on_completion);
508 };
509 workers_->enqueue(job);
510}
511
512template <typename Store, typename HashingPolicy>
514 const index_t& leaf_index,
515 uint32_t subtree_depth,
516 const HashPathCallback& on_completion,
517 bool includeUncommitted) const
518{
519 auto job = [=, this]() {
520 execute_and_report<GetSiblingPathResponse>(
521 [=, this](TypedResponse<GetSiblingPathResponse>& response) {
522 ReadTransactionPtr tx = store_->create_read_transaction();
523 RequestContext requestContext;
524 requestContext.includeUncommitted = includeUncommitted;
525 requestContext.root = store_->get_current_root(*tx, includeUncommitted);
526 // std::cout << "Current root: " << requestContext.root << std::endl;
527 OptionalSiblingPath optional_path =
528 get_subtree_sibling_path_internal(leaf_index, subtree_depth, requestContext, *tx);
529 response.inner.path = optional_sibling_path_to_full_sibling_path(optional_path);
530 },
531 on_completion);
532 };
533 workers_->enqueue(job);
534}
535
536template <typename Store, typename HashingPolicy>
539{
540 if (optionalPath.empty()) {
541 return {};
542 }
543 fr_sibling_path path(optionalPath.size());
544 size_t pathIndex = optionalPath.size() - 1;
545 for (index_t level = 1; level <= optionalPath.size(); level++) {
546 std::optional<fr> op = optionalPath[pathIndex];
547 path[pathIndex] = op.has_value() ? op.value() : zero_hashes_[level];
548 --pathIndex;
549 }
550 return path;
551}
552
553template <typename Store, typename HashingPolicy>
555 const index_t& leaf_index,
556 const RequestContext& requestContext,
557 ReadTransaction& tx,
558 bool updateNodesByIndexCache) const
559{
560 // Note: reads at leaf_index >= max_size_ are treated as out-of-range and rejected. This helper
561 // returns std::nullopt only for valid in-range positions whose subtree is unwritten.
562 if (leaf_index >= max_size_) {
563 throw std::runtime_error(format("Leaf index ", leaf_index, " out of range for tree of depth ", depth_));
564 }
565
566 fr hash = requestContext.root;
567 // std::cout << "Finding leaf hash for root " << hash << " at index " << leaf_index << std::endl;
568 index_t mask = static_cast<index_t>(1) << (depth_ - 1);
569 index_t child_index_at_level = 0;
570 for (uint32_t i = 0; i < depth_; ++i) {
571
572 // Extract the node data
573 NodePayload nodePayload;
574 bool success = store_->get_node_by_hash(hash, nodePayload, tx, requestContext.includeUncommitted);
575 if (!success) {
576 // std::cout << "No root " << hash << std::endl;
577 return std::nullopt;
578 }
579 // std::cout << "Found root at depth " << i << " : " << hash << std::endl;
580
581 // Do we need to go right or left
582 bool is_right = static_cast<bool>(leaf_index & mask);
583 // std::cout << "Mask " << mask << " depth " << depth_ << std::endl;
584 mask >>= 1;
585
586 // If we don't have a child then this leaf isn't present
587 // If we do, then keep going down the tree
588 std::optional<fr> child = is_right ? nodePayload.right : nodePayload.left;
589
590 if (!child.has_value()) {
591 // std::cout << "No child" << std::endl;
592 // We still need to update the cache with the sibling. The fact that under us there is an empty subtree
593 // doesn't mean that same is happening with our sibling.
594 if (updateNodesByIndexCache) {
595 child_index_at_level = is_right ? (child_index_at_level * 2) + 1 : (child_index_at_level * 2);
596 std::optional<fr> sibling = is_right ? nodePayload.left : nodePayload.right;
597 index_t sibling_index_at_level = is_right ? child_index_at_level - 1 : child_index_at_level + 1;
598 if (sibling.has_value()) {
599 store_->put_cached_node_by_index(i + 1, sibling_index_at_level, sibling.value(), false);
600 }
601 }
602 return std::nullopt;
603 }
604 // std::cout << "Found child " << child.value() << std::endl;
605
606 hash = child.value();
607
608 if (!updateNodesByIndexCache) {
609 continue;
610 }
611
612 child_index_at_level = is_right ? (child_index_at_level * 2) + 1 : (child_index_at_level * 2);
613 // std::cout << "Storing child " << i + 1 << " : " << child_index_at_level << " is right " << is_right
614 // << std::endl;
615 store_->put_cached_node_by_index(i + 1, child_index_at_level, hash, false);
616 std::optional<fr> sibling = is_right ? nodePayload.left : nodePayload.right;
617 index_t sibling_index_at_level = is_right ? child_index_at_level - 1 : child_index_at_level + 1;
618 if (sibling.has_value()) {
619 // std::cout << "Storing sibling " << i + 1 << " : " << sibling_index_at_level << " is right " <<
620 // is_right
621 // << std::endl;
622 store_->put_cached_node_by_index(i + 1, sibling_index_at_level, sibling.value(), false);
623 }
624 }
625
626 // if we have arrived here then the leaf is in 'hash'
627 return std::optional<fr>(hash);
628}
629
630template <typename Store, typename HashingPolicy>
632 Store,
633 HashingPolicy>::get_subtree_sibling_path_internal(const index_t& leaf_index,
634 uint32_t subtree_depth,
635 const RequestContext& requestContext,
636 ReadTransaction& tx) const
637{
638 // skip the first levels, all the way to the subtree_root
640 if (subtree_depth >= depth_) {
641 return path;
642 }
643 path.resize(depth_ - subtree_depth);
644 size_t path_index = path.size() - 1;
645
646 index_t mask = index_t(1) << (depth_ - 1);
647 // std::cout << "Depth: " << depth_ << ", mask: " << mask << ", sub tree depth: " << subtree_depth
648 // << ", leaf index: " << leaf_index << std::endl;
649 fr hash = requestContext.root;
650 // std::cout << "Getting sibling path for root: " << hash << std::endl;
651
652 for (uint32_t level = 0; level < depth_ - subtree_depth; ++level) {
653 NodePayload nodePayload;
654 store_->get_node_by_hash(hash, nodePayload, tx, requestContext.includeUncommitted);
655 bool is_right = static_cast<bool>(leaf_index & mask);
656 // std::cout << "Level: " << level << ", mask: " << mask << ", is right: " << is_right << ", parent: " << hash
657 // << ", left has value: " << nodePayload.left.has_value()
658 // << ", right has value: " << nodePayload.right.has_value() << std::endl;
659 // if (nodePayload.left.has_value()) {
660 // std::cout << "LEFT " << nodePayload.left.value() << std::endl;
661 // }
662 // if (nodePayload.right.has_value()) {
663 // std::cout << "RIGHT " << nodePayload.right.value() << std::endl;
664 // }
665 mask >>= 1;
666 std::optional<fr> sibling = is_right ? nodePayload.left : nodePayload.right;
667 std::optional<fr> child = is_right ? nodePayload.right : nodePayload.left;
668 hash = child.has_value() ? child.value() : zero_hashes_[level + 1];
669 // fr sib = (sibling.has_value() ? sibling.value() : zero_hashes_[level + 1]);
670 // std::cout << "Pushed sibling: " << sib << ", hash: " << hash << ", path index " << path_index << std::endl;
671 path[path_index--] = sibling;
672 }
673
674 return path;
675}
676
677// Leaf read semantics
678// - If leaf_index >= max_size_ -> failure (index out of tree range)
679// - If leaf_index < max_size_ but leaf not written -> return 0 with success = true
680template <typename Store, typename HashingPolicy>
682 bool includeUncommitted,
683 const GetLeafCallback& on_completion) const
684{
685 auto job = [=, this]() {
686 execute_and_report<GetLeafResponse>(
687 [=, this](TypedResponse<GetLeafResponse>& response) {
688 ReadTransactionPtr tx = store_->create_read_transaction();
689 if (max_size_ <= leaf_index) {
690 // Note: leaf_index >= max_size_ is out of tree range and returns failure. Valid in-range but
691 // unwritten leaves return zero with success = true.
692 response.message =
693 format("Unable to get leaf at index ", leaf_index, ", leaf index out of tree range.");
694 response.success = false;
695 return;
696 }
697 RequestContext requestContext;
698 requestContext.includeUncommitted = includeUncommitted;
699 requestContext.root = store_->get_current_root(*tx, includeUncommitted);
700 std::optional<fr> leaf_hash = find_leaf_hash(leaf_index, requestContext, *tx, false);
701 response.success = leaf_hash.has_value();
702 if (response.success) {
703 response.inner.leaf = leaf_hash.value();
704 } else {
705 // We have an unwritten leaf, so return a 0 value, which is not a failure:
706 response.inner.leaf = fr::zero();
707 response.success = true;
708 response.message = format("Failed to find leaf hash at index ", leaf_index);
709 }
710 },
711 on_completion);
712 };
713 workers_->enqueue(job);
714}
715
716// Leaf read semantics
717// - If leaf_index >= max_size_ -> failure (index out of tree range)
718// - If leaf_index >= blockData.size -> failure (index out of block range)
719// - If leaf_index < blockData.size but traversal encounters an unwritten subtree -> return 0 with success = true
720template <typename Store, typename HashingPolicy>
722 const block_number_t& blockNumber,
723 bool includeUncommitted,
724 const GetLeafCallback& on_completion) const
725{
726 auto job = [=, this]() {
727 execute_and_report<GetLeafResponse>(
728 [=, this](TypedResponse<GetLeafResponse>& response) {
729 ReadTransactionPtr tx = store_->create_read_transaction();
730 BlockPayload blockData;
731 if (!store_->get_block_data(blockNumber, blockData, *tx)) {
732 throw std::runtime_error(format("Unable to get leaf at index ",
733 leaf_index,
734 " for block ",
735 blockNumber,
736 ", failed to get block data."));
737 }
738 if (max_size_ <= leaf_index) {
739 // Note: leaf_index >= max_size_ is out of tree range and returns failure.
740 response.message = format("Unable to get leaf at index ",
741 leaf_index,
742 " for block ",
743 blockNumber,
744 ", leaf index out of tree range.");
745 response.success = false;
746 return;
747 }
748 if (blockData.size <= leaf_index) {
749 // Note: this failure does diverge from the below behaviour (unwritten leaf at valid index returns a
750 // 0 value with success = true) but is intentional for snapshot-reliant trees where failure is
751 // expected when reading unwritten leaves.
752 response.message = format("Unable to get leaf at index ",
753 leaf_index,
754 " for block ",
755 blockNumber,
756 ", leaf index out of block range.");
757 response.success = false;
758 return;
759 }
760 RequestContext requestContext;
761 requestContext.blockNumber = blockNumber;
762 requestContext.includeUncommitted = includeUncommitted;
763 requestContext.root = blockData.root;
764 std::optional<fr> leaf_hash = find_leaf_hash(leaf_index, requestContext, *tx, false);
765 response.success = leaf_hash.has_value();
766 if (response.success) {
767 response.inner.leaf = leaf_hash.value();
768 } else {
769 // We have an unwritten leaf, so return a 0 value, which is not a failure:
770 response.inner.leaf = fr::zero();
771 response.success = true;
772 response.message =
773 format("Failed to find leaf hash at index ", leaf_index, " for block number ", blockNumber);
774 }
775 },
776 on_completion);
777 };
778 workers_->enqueue(job);
779}
780
781template <typename Store, typename HashingPolicy>
784 bool includeUncommitted,
785 const FindLeafCallback& on_completion) const
786{
787 find_leaf_indices_from(leaves, 0, includeUncommitted, on_completion);
788}
789
790template <typename Store, typename HashingPolicy>
793 const block_number_t& blockNumber,
794 bool includeUncommitted,
795 const FindLeafCallback& on_completion) const
796{
797 find_leaf_indices_from(leaves, 0, blockNumber, includeUncommitted, on_completion);
798}
799
800template <typename Store, typename HashingPolicy>
803 const index_t& start_index,
804 bool includeUncommitted,
805 const FindLeafCallback& on_completion) const
806{
807 auto job = [=, this]() -> void {
808 execute_and_report<FindLeafIndexResponse>(
809 [=, this](TypedResponse<FindLeafIndexResponse>& response) {
810 response.inner.leaf_indices.reserve(leaves.size());
811 ReadTransactionPtr tx = store_->create_read_transaction();
812
813 RequestContext requestContext;
814 requestContext.includeUncommitted = includeUncommitted;
815
816 for (const auto& leaf : leaves) {
817 std::optional<index_t> leaf_index =
818 store_->find_leaf_index_from(leaf, start_index, requestContext, *tx);
819 response.inner.leaf_indices.emplace_back(leaf_index);
820 }
821 },
822 on_completion);
823 };
824 workers_->enqueue(job);
825}
826
827template <typename Store, typename HashingPolicy>
830 const index_t& start_index,
831 const block_number_t& blockNumber,
832 bool includeUncommitted,
833 const FindLeafCallback& on_completion) const
834{
835 auto job = [=, this]() -> void {
836 execute_and_report<FindLeafIndexResponse>(
837 [=, this](TypedResponse<FindLeafIndexResponse>& response) {
838 response.inner.leaf_indices.reserve(leaves.size());
839 ReadTransactionPtr tx = store_->create_read_transaction();
840 BlockPayload blockData;
841 if (!store_->get_block_data(blockNumber, blockData, *tx)) {
842 throw std::runtime_error(format("Unable to find leaf from index ",
843 start_index,
844 " for block ",
845 blockNumber,
846 ", failed to get block data."));
847 }
848
849 RequestContext requestContext;
850 requestContext.blockNumber = blockNumber;
851 requestContext.includeUncommitted = includeUncommitted;
852 requestContext.maxIndex = blockData.size;
853
854 for (const auto& leaf : leaves) {
855 std::optional<index_t> leaf_index =
856 store_->find_leaf_index_from(leaf, start_index, requestContext, *tx);
857 response.inner.leaf_indices.emplace_back(leaf_index);
858 }
859 },
860 on_completion);
861 };
862 workers_->enqueue(job);
863}
864
865template <typename Store, typename HashingPolicy>
868 bool includeUncommitted,
869 const FindSiblingPathCallback& on_completion) const
870{
871 auto job = [=, this]() -> void {
872 execute_and_report<FindLeafPathResponse>(
873 [=, this](TypedResponse<FindLeafPathResponse>& response) {
874 response.inner.leaf_paths.reserve(leaves.size());
875 ReadTransactionPtr tx = store_->create_read_transaction();
876
877 RequestContext requestContext;
878 requestContext.includeUncommitted = includeUncommitted;
879 requestContext.root = store_->get_current_root(*tx, includeUncommitted);
880
881 for (const auto& leaf : leaves) {
882 std::optional<index_t> leaf_index = store_->find_leaf_index_from(leaf, 0, requestContext, *tx);
883 if (!leaf_index.has_value()) {
884 response.inner.leaf_paths.emplace_back(std::nullopt);
885 continue;
886 }
887 OptionalSiblingPath optional_path =
888 get_subtree_sibling_path_internal(leaf_index.value(), 0, requestContext, *tx);
889 SiblingPathAndIndex sibling_path_and_index;
890 sibling_path_and_index.path = optional_sibling_path_to_full_sibling_path(optional_path);
891 sibling_path_and_index.index = leaf_index.value();
892 response.inner.leaf_paths.emplace_back(sibling_path_and_index);
893 }
894 },
895 on_completion);
896 };
897 workers_->enqueue(job);
898}
899
900template <typename Store, typename HashingPolicy>
903 const block_number_t& blockNumber,
904 bool includeUncommitted,
905 const FindSiblingPathCallback& on_completion) const
906{
907 auto job = [=, this]() -> void {
908 execute_and_report<FindLeafPathResponse>(
909 [=, this](TypedResponse<FindLeafPathResponse>& response) {
910 response.inner.leaf_paths.reserve(leaves.size());
911 ReadTransactionPtr tx = store_->create_read_transaction();
912 BlockPayload blockData;
913 if (!store_->get_block_data(blockNumber, blockData, *tx)) {
914 throw std::runtime_error(
915 format("Unable to find sibling path for block ", blockNumber, ", failed to get block data."));
916 }
917
918 RequestContext requestContext;
919 requestContext.blockNumber = blockNumber;
920 requestContext.includeUncommitted = includeUncommitted;
921 requestContext.maxIndex = blockData.size;
922 requestContext.root = blockData.root;
923
924 for (const auto& leaf : leaves) {
925 std::optional<index_t> leaf_index = store_->find_leaf_index_from(leaf, 0, requestContext, *tx);
926 if (!leaf_index.has_value()) {
927 response.inner.leaf_paths.emplace_back(std::nullopt);
928 continue;
929 }
930 OptionalSiblingPath optional_path =
931 get_subtree_sibling_path_internal(leaf_index.value(), 0, requestContext, *tx);
932 SiblingPathAndIndex sibling_path_and_index;
933 sibling_path_and_index.path = optional_sibling_path_to_full_sibling_path(optional_path);
934 sibling_path_and_index.index = leaf_index.value();
935 response.inner.leaf_paths.emplace_back(sibling_path_and_index);
936 }
937 },
938 on_completion);
939 };
940 workers_->enqueue(job);
941}
942
943template <typename Store, typename HashingPolicy>
945 const AppendCompletionCallback& on_completion)
946{
947 add_values(std::vector<fr>{ value }, on_completion);
948}
949
950template <typename Store, typename HashingPolicy>
952 const AppendCompletionCallback& on_completion)
953{
954 add_values_internal(values, on_completion, true);
955}
956
957template <typename Store, typename HashingPolicy>
959 const std::vector<fr>& values, const AppendCompletionCallback& on_completion, bool update_index)
960{
962 auto append_op = [=, this]() -> void {
963 execute_and_report<AddDataResponse>(
964 [=, this](TypedResponse<AddDataResponse>& response) {
965 add_values_internal(hashes, response.inner.root, response.inner.size, update_index);
966 },
967 on_completion);
968 };
969 workers_->enqueue(append_op);
970}
971
972template <typename Store, typename HashingPolicy>
974{
975 auto job = [=, this]() {
976 execute_and_report<CommitResponse>(
977 [=, this](TypedResponse<CommitResponse>& response) {
978 store_->commit_block(response.inner.meta, response.inner.stats);
979 },
980 on_completion);
981 };
982 workers_->enqueue(job);
983}
984
985template <typename Store, typename HashingPolicy>
987{
988 auto job = [=, this]() { execute_and_report([=, this]() { store_->rollback(); }, on_completion); };
989 workers_->enqueue(job);
990}
991
992template <typename Store, typename HashingPolicy>
994{
995 auto job = [=, this]() {
996 execute_and_report<CheckpointResponse>(
997 [=, this](TypedResponse<CheckpointResponse>& response) { response.inner.depth = store_->checkpoint(); },
998 on_completion);
999 };
1000 workers_->enqueue(job);
1001}
1002
1003template <typename Store, typename HashingPolicy>
1005 const CheckpointCommitCallback& on_completion)
1006{
1007 auto job = [=, this]() { execute_and_report([=, this]() { store_->commit_checkpoint(); }, on_completion); };
1008 workers_->enqueue(job);
1009}
1010
1011template <typename Store, typename HashingPolicy>
1013 const CheckpointRevertCallback& on_completion)
1014{
1015 auto job = [=, this]() { execute_and_report([=, this]() { store_->revert_checkpoint(); }, on_completion); };
1016 workers_->enqueue(job);
1017}
1018
1019template <typename Store, typename HashingPolicy>
1021 const CheckpointCommitCallback& on_completion)
1022{
1023 auto job = [=, this]() { execute_and_report([=, this]() { store_->commit_all_checkpoints_to(); }, on_completion); };
1024 workers_->enqueue(job);
1025}
1026
1027template <typename Store, typename HashingPolicy>
1029 const CheckpointRevertCallback& on_completion)
1030{
1031 auto job = [=, this]() { execute_and_report([=, this]() { store_->revert_all_checkpoints_to(); }, on_completion); };
1032 workers_->enqueue(job);
1033}
1034
1035template <typename Store, typename HashingPolicy>
1037 uint32_t target_depth, const CheckpointCommitCallback& on_completion)
1038{
1039 auto job = [=, this]() {
1040 execute_and_report([=, this]() { store_->commit_to_depth(target_depth); }, on_completion);
1041 };
1042 workers_->enqueue(job);
1043}
1044
1045template <typename Store, typename HashingPolicy>
1047 uint32_t target_depth, const CheckpointRevertCallback& on_completion)
1048{
1049 auto job = [=, this]() {
1050 execute_and_report([=, this]() { store_->revert_to_depth(target_depth); }, on_completion);
1051 };
1052 workers_->enqueue(job);
1053}
1054
1055template <typename Store, typename HashingPolicy>
1057{
1058 return store_->checkpoint_depth();
1059}
1060template <typename Store, typename HashingPolicy>
1062 const block_number_t& blockNumber, const RemoveHistoricBlockCallback& on_completion)
1063{
1064 auto job = [=, this]() {
1065 execute_and_report<RemoveHistoricResponse>(
1066 [=, this](TypedResponse<RemoveHistoricResponse>& response) {
1067 if (blockNumber == 0) {
1068 throw std::runtime_error("Unable to remove historic block 0");
1069 }
1070 store_->remove_historical_block(blockNumber, response.inner.meta, response.inner.stats);
1071 },
1072 on_completion);
1073 };
1074 workers_->enqueue(job);
1075}
1076
1077template <typename Store, typename HashingPolicy>
1079 const UnwindBlockCallback& on_completion)
1080{
1081 auto job = [=, this]() {
1082 execute_and_report<UnwindResponse>(
1083 [=, this](TypedResponse<UnwindResponse>& response) {
1084 if (blockNumber == 0) {
1085 throw std::runtime_error("Unable to unwind block 0");
1086 }
1087 store_->unwind_block(blockNumber, response.inner.meta, response.inner.stats);
1088 },
1089 on_completion);
1090 };
1091 workers_->enqueue(job);
1092}
1093
1094template <typename Store, typename HashingPolicy>
1096 const FinalizeBlockCallback& on_completion)
1097{
1098 auto job = [=, this]() {
1100 [=, this]() {
1101 if (blockNumber == 0) {
1102 throw std::runtime_error("Unable to finalize block 0");
1103 }
1104 store_->advance_finalized_block(blockNumber);
1105 },
1106 on_completion);
1107 };
1108 workers_->enqueue(job);
1109}
1110
1111template <typename Store, typename HashingPolicy>
1113 const index_t& treeSize, const index_t& remainingAppendSize)
1114{
1115 index_t minPower2 = 1;
1116 if (treeSize != 0U) {
1117 while (!(minPower2 & treeSize)) {
1118 minPower2 <<= 1;
1119 }
1120 if (minPower2 <= remainingAppendSize) {
1121 return minPower2;
1122 }
1123 }
1124 index_t maxPower2 = 1;
1125 while (maxPower2 <= remainingAppendSize) {
1126 maxPower2 <<= 1;
1127 }
1128 return maxPower2 >> 1;
1129}
1130
1131template <typename Store, typename HashingPolicy>
1133 fr& new_root,
1134 index_t& new_size,
1135 bool update_index)
1136{
1137 ReadTransactionPtr tx = store_->create_read_transaction();
1138 TreeMeta meta;
1139 store_->get_meta(meta);
1140 index_t sizeToAppend = values->size();
1141 new_size = meta.size;
1142 index_t batchIndex = 0;
1143 while (sizeToAppend != 0U) {
1144 index_t batchSize = get_batch_insertion_size(new_size, sizeToAppend);
1145 sizeToAppend -= batchSize;
1146 int64_t start = static_cast<int64_t>(batchIndex);
1147 int64_t end = static_cast<int64_t>(batchIndex + batchSize);
1148 std::vector<fr> batch = std::vector<fr>(values->begin() + start, values->begin() + end);
1149 batchIndex += batchSize;
1150 add_batch_internal(batch, new_root, new_size, update_index, *tx);
1151 }
1152}
1153
1154template <typename Store, typename HashingPolicy>
1156 std::vector<fr>& values, fr& new_root, index_t& new_size, bool update_index, ReadTransaction& tx)
1157{
1158 uint32_t start_level = depth_;
1159 uint32_t level = start_level;
1160 std::vector<fr>& hashes_local = values;
1161 auto number_to_insert = static_cast<uint32_t>(hashes_local.size());
1162
1163 TreeMeta meta;
1164 store_->get_meta(meta);
1165 index_t index = meta.size;
1166 new_size = meta.size + number_to_insert;
1167
1168 // std::cout << "Appending new leaves" << std::endl;
1169 if (values.empty()) {
1170 return;
1171 }
1172
1173 if (new_size > max_size_) {
1174 throw std::runtime_error(
1175 format("Unable to append leaves to tree ", meta.name, " new size: ", new_size, " max size: ", max_size_));
1176 }
1177
1178 // Add the values at the leaf nodes of the tree
1179 for (uint32_t i = 0; i < number_to_insert; ++i) {
1180 // write_node(level, index + i, hashes_local[i]);
1181 // std::cout << "Writing leaf hash: " << hashes_local[i] << " level " << level << std::endl;
1182 store_->put_node_by_hash(hashes_local[i], { .left = std::nullopt, .right = std::nullopt, .ref = 1 });
1183 store_->put_cached_node_by_index(level, i + index, hashes_local[i]);
1184 }
1185
1186 // If we have been told to add these leaves to the index then do so now
1187 if (update_index) {
1188 for (uint32_t i = 0; i < number_to_insert; ++i) {
1189 // We don't store indices of zero leaves
1190 if (hashes_local[i] == fr::zero()) {
1191 continue;
1192 }
1193 // std::cout << "Updating index " << index + i << " : " << hashes_local[i] << std::endl;
1194 store_->update_index(index + i, hashes_local[i]);
1195 }
1196 }
1197
1198 // Hash the values as a sub tree and insert them
1199 while (number_to_insert > 1) {
1200 number_to_insert >>= 1;
1201 index >>= 1;
1202 --level;
1203 // std::cout << "To INSERT " << number_to_insert << std::endl;
1204 for (uint32_t i = 0; i < number_to_insert; ++i) {
1205 fr left = hashes_local[i * 2];
1206 fr right = hashes_local[i * 2 + 1];
1207 hashes_local[i] = HashingPolicy::hash_pair(left, right);
1208 // std::cout << "Left: " << left << ", right: " << right << ", parent: " << hashes_local[i] << std::endl;
1209 store_->put_node_by_hash(hashes_local[i], { .left = left, .right = right, .ref = 1 });
1210 store_->put_cached_node_by_index(level, index + i, hashes_local[i]);
1211 // std::cout << "Writing node hash " << hashes_local[i] << " level " << level << " index " << index + i
1212 // << std::endl;
1213 }
1214 }
1215
1216 fr new_hash = hashes_local[0];
1217
1218 // std::cout << "LEVEL: " << level << " hash " << new_hash << std::endl;
1219 RequestContext requestContext;
1220 requestContext.includeUncommitted = true;
1221 requestContext.root = store_->get_current_root(tx, true);
1222 OptionalSiblingPath optional_sibling_path_to_root =
1223 get_subtree_sibling_path_internal(meta.size, depth_ - level, requestContext, tx);
1224 fr_sibling_path sibling_path_to_root = optional_sibling_path_to_full_sibling_path(optional_sibling_path_to_root);
1225 size_t sibling_path_index = 0;
1226
1227 // Hash from the root of the sub-tree to the root of the overall tree
1228
1229 // std::cout << "Root hash: " << new_hash << std::endl;
1230 while (level > 0) {
1231 bool is_right = static_cast<bool>(index & 0x01);
1232 // std::cout << "index: " << index << " sibling path index: " << sibling_path_index << ", is right: " <<
1233 // is_right
1234 // << std::endl;
1235 fr left_hash = is_right ? sibling_path_to_root[sibling_path_index] : new_hash;
1236 fr right_hash = is_right ? new_hash : sibling_path_to_root[sibling_path_index];
1237
1238 std::optional<fr> left_op = is_right ? optional_sibling_path_to_root[sibling_path_index] : new_hash;
1239 std::optional<fr> right_op = is_right ? new_hash : optional_sibling_path_to_root[sibling_path_index];
1240
1241 new_hash = HashingPolicy::hash_pair(left_hash, right_hash);
1242 // std::cout << "Left: " << left_hash << ", right: " << right_hash << ", parent: " << new_hash << std::endl;
1243
1244 index >>= 1;
1245 --level;
1246 ++sibling_path_index;
1247 store_->put_cached_node_by_index(level, index, new_hash);
1248 store_->put_node_by_hash(new_hash, { .left = left_op, .right = right_op, .ref = 1 });
1249 // std::cout << "Writing node hash " << new_hash << " level " << level << " index " << index << std::endl;
1250 }
1251
1252 new_root = new_hash;
1253 meta.root = new_hash;
1254 meta.size = new_size;
1255 // std::cout << "New size: " << meta.size << ", root " << meta.root << std::endl;
1256 store_->put_meta(meta);
1257}
1258
1259} // namespace bb::crypto::merkle_tree
std::shared_ptr< Napi::ThreadSafeFunction > revert_checkpoint
std::shared_ptr< Napi::ThreadSafeFunction > commit_checkpoint
Implements a simple append-only merkle tree All methods are asynchronous unless specified as otherwis...
void get_leaf(const index_t &index, bool includeUncommitted, const GetLeafCallback &completion) const
Returns the leaf value at the provided index.
void remove_historic_block(const block_number_t &blockNumber, const RemoveHistoricBlockCallback &on_completion)
std::function< void(TypedResponse< FindLeafPathResponse > &)> FindSiblingPathCallback
void revert_all_checkpoints_to(const CheckpointRevertCallback &on_completion)
OptionalSiblingPath get_subtree_sibling_path_internal(const index_t &leaf_index, uint32_t subtree_depth, const RequestContext &requestContext, ReadTransaction &tx) const
void get_sibling_path(const index_t &index, const HashPathCallback &on_completion, bool includeUncommitted) const
Returns the sibling path from the leaf at the given index to the root.
void commit(const CommitCallback &on_completion)
Commit the tree to the backing store.
void add_values_internal(std::shared_ptr< std::vector< fr > > values, fr &new_root, index_t &new_size, bool update_index)
void commit_to_depth(uint32_t target_depth, const CheckpointCommitCallback &on_completion)
void add_batch_internal(std::vector< fr > &values, fr &new_root, index_t &new_size, bool update_index, ReadTransaction &tx)
void commit_checkpoint(const CheckpointCommitCallback &on_completion)
uint32_t depth() const
Synchronous method to retrieve the depth of the tree.
void commit_all_checkpoints_to(const CheckpointCommitCallback &on_completion)
std::function< void(TypedResponse< GetLeafResponse > &)> GetLeafCallback
virtual void add_values(const std::vector< fr > &values, const AppendCompletionCallback &on_completion)
Adds the given set of values to the end of the tree.
ContentAddressedAppendOnlyTree & operator=(ContentAddressedAppendOnlyTree const &other)=delete
void get_meta_data(bool includeUncommitted, const MetaDataCallback &on_completion) const
Returns the tree meta data.
fr_sibling_path optional_sibling_path_to_full_sibling_path(const OptionalSiblingPath &optionalPath) const
ContentAddressedAppendOnlyTree & operator=(ContentAddressedAppendOnlyTree const &&other)=delete
void find_block_numbers(const std::vector< index_t > &indices, const GetBlockForIndexCallback &on_completion) const
Returns the block numbers that correspond to the given indices values.
void unwind_block(const block_number_t &blockNumber, const UnwindBlockCallback &on_completion)
std::function< void(TypedResponse< FindLeafIndexResponse > &)> FindLeafCallback
std::function< void(TypedResponse< GetSiblingPathResponse > &)> HashPathCallback
std::function< void(TypedResponse< TreeMetaResponse > &)> MetaDataCallback
void revert_checkpoint(const CheckpointRevertCallback &on_completion)
std::function< void(TypedResponse< UnwindResponse > &)> UnwindBlockCallback
std::function< void(TypedResponse< AddDataResponse > &)> AppendCompletionCallback
virtual void add_value(const fr &value, const AppendCompletionCallback &on_completion)
Adds a single value to the end of the tree.
std::function< void(TypedResponse< CheckpointResponse > &)> CheckpointCallback
std::optional< fr > find_leaf_hash(const index_t &leaf_index, const RequestContext &requestContext, ReadTransaction &tx, bool updateNodesByIndexCache=false) const
void revert_to_depth(uint32_t target_depth, const CheckpointRevertCallback &on_completion)
void rollback(const RollbackCallback &on_completion)
Rollback the uncommitted changes.
void find_leaf_indices(const std::vector< typename Store::LeafType > &leaves, bool includeUncommitted, const FindLeafCallback &on_completion) const
Returns the index of the provided leaf in the tree.
void get_subtree_sibling_path(uint32_t subtree_depth, const HashPathCallback &on_completion, bool includeUncommitted) const
Get the subtree sibling path object.
std::function< void(TypedResponse< CommitResponse > &)> CommitCallback
index_t get_batch_insertion_size(const index_t &treeSize, const index_t &remainingAppendSize)
ContentAddressedAppendOnlyTree(ContentAddressedAppendOnlyTree &&other)=delete
void finalize_block(const block_number_t &blockNumber, const FinalizeBlockCallback &on_completion)
void find_leaf_indices_from(const std::vector< typename Store::LeafType > &leaves, const index_t &start_index, bool includeUncommitted, const FindLeafCallback &on_completion) const
Returns the index of the provided leaf in the tree only if it exists after the index value provided.
std::function< void(TypedResponse< BlockForIndexResponse > &)> GetBlockForIndexCallback
ContentAddressedAppendOnlyTree(std::unique_ptr< Store > store, std::shared_ptr< ThreadPool > workers, const std::vector< fr > &initial_values={}, bool commit_genesis_state=true)
std::function< void(TypedResponse< RemoveHistoricResponse > &)> RemoveHistoricBlockCallback
ContentAddressedAppendOnlyTree(ContentAddressedAppendOnlyTree const &other)=delete
void find_leaf_sibling_paths(const std::vector< typename Store::LeafType > &leaves, bool includeUncommitted, const FindSiblingPathCallback &on_completion) const
Returns the sibling paths for the provided leaves in the tree.
Used in parallel insertions in the the IndexedTree. Workers signal to other following workes as they ...
Definition signal.hpp:17
void signal_level(uint32_t level=0)
Signals that the given level has been passed.
Definition signal.hpp:54
void wait_for_level(uint32_t level=0)
Causes the thread to wait until the required level has been signalled.
Definition signal.hpp:40
std::string format(Args... args)
Definition log.hpp:23
ContentAddressedCachedTreeStore< bb::fr > Store
void add_values(TreeType &tree, const std::vector< NullifierLeafValue > &values)
void execute_and_report(const std::function< void(TypedResponse< ResponseType > &)> &f, const std::function< void(TypedResponse< ResponseType > &)> &on_completion)
Definition response.hpp:272
uint32_t block_number_t
Definition types.hpp:19
std::vector< fr > fr_sibling_path
Definition hash_path.hpp:14
constexpr uint64_t pow64(const uint64_t input, const uint64_t exponent)
Definition pow.hpp:13
Entry point for Barretenberg command-line interface.
Definition api.hpp:5
STL namespace.
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
std::optional< index_t > maxIndex
Definition types.hpp:29
std::optional< block_number_t > blockNumber
Definition types.hpp:27
static constexpr field zero()