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: not started, auditors: [], date: YYYY-MM-DD }
3// external_1: { status: not started, auditors: [], date: YYYY-MM-DD }
4// external_2: { status: not started, auditors: [], date: YYYY-MM-DD }
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(const CheckpointCommitCallback& on_completion);
258 void revert_all_checkpoints(const CheckpointRevertCallback& on_completion);
259
260 protected:
261 using ReadTransaction = typename Store::ReadTransaction;
262 using ReadTransactionPtr = typename Store::ReadTransactionPtr;
263
265
267
268 void add_values_internal(std::shared_ptr<std::vector<fr>> values,
269 fr& new_root,
270 index_t& new_size,
271 bool update_index);
272
273 void add_values_internal(const std::vector<fr>& values,
274 const AppendCompletionCallback& on_completion,
275 bool update_index);
276
278 uint32_t subtree_depth,
279 const RequestContext& requestContext,
280 ReadTransaction& tx) const;
281
282 std::optional<fr> find_leaf_hash(const index_t& leaf_index,
283 const RequestContext& requestContext,
284 ReadTransaction& tx,
285 bool updateNodesByIndexCache = false) const;
286
287 index_t get_batch_insertion_size(const index_t& treeSize, const index_t& remainingAppendSize);
288
290 std::vector<fr>& values, fr& new_root, index_t& new_size, bool update_index, ReadTransaction& tx);
291
292 std::unique_ptr<Store> store_;
293 uint32_t depth_;
294 uint64_t max_size_;
295 std::vector<fr> zero_hashes_;
297};
298
299template <typename Store, typename HashingPolicy>
301 std::unique_ptr<Store> store,
303 const std::vector<fr>& initial_values,
304 bool commit_genesis_state)
305 : store_(std::move(store))
306 , workers_(workers)
307{
308 TreeMeta meta;
309 // start by reading the meta data from the backing store
310 store_->get_meta(meta);
311 depth_ = meta.depth;
312 zero_hashes_.resize(depth_ + 1);
313
314 // Create the zero hashes for the tree
315 auto current = HashingPolicy::zero_hash();
316 for (size_t i = depth_; i > 0; --i) {
317 // std::cout << "Zero hash at " << i << " : " << current << std::endl;
318 zero_hashes_[i] = current;
319 current = HashingPolicy::hash_pair(current, current);
320 }
321 zero_hashes_[0] = current;
322 // std::cout << "Zero root: " << current << std::endl;
323
325 // if root is non-zero it means the tree has already been initialized
326 if (meta.root != fr::zero()) {
327 return;
328 }
329
330 if (initial_values.empty()) {
331 meta.initialRoot = meta.root = current;
332 meta.initialSize = meta.size = 0;
333 } else {
334 Signal signal(1);
336 add_values(initial_values, [&](const TypedResponse<AddDataResponse>& resp) {
337 result = resp;
338 signal.signal_level(0);
339 });
340
341 signal.wait_for_level(0);
342 if (!result.success) {
343 throw std::runtime_error(format("Failed to initialize tree: ", result.message));
344 }
345
346 store_->get_meta(meta);
347
348 meta.initialRoot = meta.root = result.inner.root;
349 meta.initialSize = meta.size = result.inner.size;
350 }
351 store_->put_meta(meta);
352
353 if (commit_genesis_state) {
354 store_->commit_genesis_state();
355 }
356}
357
358template <typename Store, typename HashingPolicy>
360 const MetaDataCallback& on_completion) const
361{
362 auto job = [=, this]() {
363 execute_and_report<TreeMetaResponse>(
364 [=, this](TypedResponse<TreeMetaResponse>& response) {
365 ReadTransactionPtr tx = store_->create_read_transaction();
366 store_->get_meta(response.inner.meta, *tx, includeUncommitted);
367 },
368 on_completion);
369 };
370 workers_->enqueue(job);
371}
372
373template <typename Store, typename HashingPolicy>
375 bool includeUncommitted,
376 const MetaDataCallback& on_completion) const
377{
378 auto job = [=, this]() {
379 execute_and_report<TreeMetaResponse>(
380 [=, this](TypedResponse<TreeMetaResponse>& response) {
381 ReadTransactionPtr tx = store_->create_read_transaction();
382 store_->get_meta(response.inner.meta, *tx, includeUncommitted);
383
384 BlockPayload blockData;
385 if (!store_->get_block_data(blockNumber, blockData, *tx)) {
386 throw std::runtime_error(
387 format("Unable to get meta data for block ", blockNumber, ", failed to get block data."));
388 }
389
390 response.inner.meta.size = blockData.size;
391 response.inner.meta.committedSize = blockData.size;
392 response.inner.meta.root = blockData.root;
393 },
394 on_completion);
395 };
396 workers_->enqueue(job);
397}
398
399template <typename Store, typename HashingPolicy>
401 const HashPathCallback& on_completion,
402 bool includeUncommitted) const
403{
404 get_subtree_sibling_path(index, 0, on_completion, includeUncommitted);
405}
406
407template <typename Store, typename HashingPolicy>
409 const block_number_t& blockNumber,
410 const HashPathCallback& on_completion,
411 bool includeUncommitted) const
412{
413 auto job = [=, this]() {
414 execute_and_report<GetSiblingPathResponse>(
415 [=, this](TypedResponse<GetSiblingPathResponse>& response) {
416 if (blockNumber == 0) {
417 throw std::runtime_error("Unable to get sibling path at block 0");
418 }
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 fr hash = requestContext.root;
561 // std::cout << "Finding leaf hash for root " << hash << " at index " << leaf_index << std::endl;
562 index_t mask = static_cast<index_t>(1) << (depth_ - 1);
563 index_t child_index_at_level = 0;
564 for (uint32_t i = 0; i < depth_; ++i) {
565
566 // Extract the node data
567 NodePayload nodePayload;
568 bool success = store_->get_node_by_hash(hash, nodePayload, tx, requestContext.includeUncommitted);
569 if (!success) {
570 // std::cout << "No root " << hash << std::endl;
571 return std::nullopt;
572 }
573 // std::cout << "Found root at depth " << i << " : " << hash << std::endl;
574
575 // TODO(#17755): This does not consider maximum leaf index and will wrap around to give incorrect values.
576 // e.g. if leaf_index = maximum + 1, returns the leaf at index + 1. See #17684
577 // Do we need to go right or left
578 bool is_right = static_cast<bool>(leaf_index & mask);
579 // std::cout << "Mask " << mask << " depth " << depth_ << std::endl;
580 mask >>= 1;
581
582 // If we don't have a child then this leaf isn't present
583 // If we do, then keep going down the tree
584 std::optional<fr> child = is_right ? nodePayload.right : nodePayload.left;
585
586 if (!child.has_value()) {
587 // std::cout << "No child" << std::endl;
588 // We still need to update the cache with the sibling. The fact that under us there is an empty subtree
589 // doesn't mean that same is happening with our sibling.
590 if (updateNodesByIndexCache) {
591 child_index_at_level = is_right ? (child_index_at_level * 2) + 1 : (child_index_at_level * 2);
592 std::optional<fr> sibling = is_right ? nodePayload.left : nodePayload.right;
593 index_t sibling_index_at_level = is_right ? child_index_at_level - 1 : child_index_at_level + 1;
594 if (sibling.has_value()) {
595 store_->put_cached_node_by_index(i + 1, sibling_index_at_level, sibling.value(), false);
596 }
597 }
598 return std::nullopt;
599 }
600 // std::cout << "Found child " << child.value() << std::endl;
601
602 hash = child.value();
603
604 if (!updateNodesByIndexCache) {
605 continue;
606 }
607
608 child_index_at_level = is_right ? (child_index_at_level * 2) + 1 : (child_index_at_level * 2);
609 // std::cout << "Storing child " << i + 1 << " : " << child_index_at_level << " is right " << is_right
610 // << std::endl;
611 store_->put_cached_node_by_index(i + 1, child_index_at_level, hash, false);
612 std::optional<fr> sibling = is_right ? nodePayload.left : nodePayload.right;
613 index_t sibling_index_at_level = is_right ? child_index_at_level - 1 : child_index_at_level + 1;
614 if (sibling.has_value()) {
615 // std::cout << "Storing sibling " << i + 1 << " : " << sibling_index_at_level << " is right " <<
616 // is_right
617 // << std::endl;
618 store_->put_cached_node_by_index(i + 1, sibling_index_at_level, sibling.value(), false);
619 }
620 }
621
622 // if we have arrived here then the leaf is in 'hash'
623 return std::optional<fr>(hash);
624}
625
626template <typename Store, typename HashingPolicy>
628 Store,
629 HashingPolicy>::get_subtree_sibling_path_internal(const index_t& leaf_index,
630 uint32_t subtree_depth,
631 const RequestContext& requestContext,
632 ReadTransaction& tx) const
633{
634 // skip the first levels, all the way to the subtree_root
636 if (subtree_depth >= depth_) {
637 return path;
638 }
639 path.resize(depth_ - subtree_depth);
640 size_t path_index = path.size() - 1;
641
642 index_t mask = index_t(1) << (depth_ - 1);
643 // std::cout << "Depth: " << depth_ << ", mask: " << mask << ", sub tree depth: " << subtree_depth
644 // << ", leaf index: " << leaf_index << std::endl;
645 fr hash = requestContext.root;
646 // std::cout << "Getting sibling path for root: " << hash << std::endl;
647
648 for (uint32_t level = 0; level < depth_ - subtree_depth; ++level) {
649 NodePayload nodePayload;
650 store_->get_node_by_hash(hash, nodePayload, tx, requestContext.includeUncommitted);
651 bool is_right = static_cast<bool>(leaf_index & mask);
652 // std::cout << "Level: " << level << ", mask: " << mask << ", is right: " << is_right << ", parent: " << hash
653 // << ", left has value: " << nodePayload.left.has_value()
654 // << ", right has value: " << nodePayload.right.has_value() << std::endl;
655 // if (nodePayload.left.has_value()) {
656 // std::cout << "LEFT " << nodePayload.left.value() << std::endl;
657 // }
658 // if (nodePayload.right.has_value()) {
659 // std::cout << "RIGHT " << nodePayload.right.value() << std::endl;
660 // }
661 mask >>= 1;
662 std::optional<fr> sibling = is_right ? nodePayload.left : nodePayload.right;
663 std::optional<fr> child = is_right ? nodePayload.right : nodePayload.left;
664 hash = child.has_value() ? child.value() : zero_hashes_[level + 1];
665 // fr sib = (sibling.has_value() ? sibling.value() : zero_hashes_[level + 1]);
666 // std::cout << "Pushed sibling: " << sib << ", hash: " << hash << ", path index " << path_index << std::endl;
667 path[path_index--] = sibling;
668 }
669
670 return path;
671}
672
673template <typename Store, typename HashingPolicy>
675 bool includeUncommitted,
676 const GetLeafCallback& on_completion) const
677{
678 auto job = [=, this]() {
679 execute_and_report<GetLeafResponse>(
680 [=, this](TypedResponse<GetLeafResponse>& response) {
681 ReadTransactionPtr tx = store_->create_read_transaction();
682 if (max_size_ < leaf_index) {
683 // TODO(#17755): Throw error to world state -> TS? (native_world_state_instance.ts -> call()
684 // translates this to null)
685 response.message =
686 format("Unable to get leaf at index ", leaf_index, ", leaf index out of tree range.");
687 response.success = false;
688 return;
689 }
690 RequestContext requestContext;
691 requestContext.includeUncommitted = includeUncommitted;
692 requestContext.root = store_->get_current_root(*tx, includeUncommitted);
693 std::optional<fr> leaf_hash = find_leaf_hash(leaf_index, requestContext, *tx, false);
694 response.success = leaf_hash.has_value();
695 if (response.success) {
696 response.inner.leaf = leaf_hash.value();
697 } else {
698 // We have an unwritten leaf, so return a 0 value, which is not a failure:
699 response.inner.leaf = fr::zero();
700 // TODO(#17755): The below is now redundant (we will always set response.success = true at this
701 // point), but keeping it in case we want to handle specific (non out of range index) errors e.g. no
702 // root
703 response.success = true;
704 response.message = format("Failed to find leaf hash at index ", leaf_index);
705 }
706 },
707 on_completion);
708 };
709 workers_->enqueue(job);
710}
711
712template <typename Store, typename HashingPolicy>
714 const block_number_t& blockNumber,
715 bool includeUncommitted,
716 const GetLeafCallback& on_completion) const
717{
718 auto job = [=, this]() {
719 execute_and_report<GetLeafResponse>(
720 [=, this](TypedResponse<GetLeafResponse>& response) {
721 if (blockNumber == 0) {
722 throw std::runtime_error("Unable to get leaf at block 0");
723 }
724 ReadTransactionPtr tx = store_->create_read_transaction();
725 BlockPayload blockData;
726 if (!store_->get_block_data(blockNumber, blockData, *tx)) {
727 throw std::runtime_error(format("Unable to get leaf at index ",
728 leaf_index,
729 " for block ",
730 blockNumber,
731 ", failed to get block data."));
732 }
733 if (max_size_ < leaf_index) {
734 // TODO(#17755): Throw error to world state -> TS? (native_world_state_instance.ts -> call()
735 // translates this to null)
736 response.message = format("Unable to get leaf at index ",
737 leaf_index,
738 " for block ",
739 blockNumber,
740 ", leaf index out of tree range.");
741 response.success = false;
742 return;
743 }
744 if (blockData.size < leaf_index) {
745 // Note: this failure does diverge from the below behaviour (unwritten leaf at valid index returns a
746 // 0 value with success = true) but is intentional for snapshot-reliant trees where failure is
747 // expected when reading unwritten leaves. Should be considered with #17755.
748 response.message = format("Unable to get leaf at index ",
749 leaf_index,
750 " for block ",
751 blockNumber,
752 ", leaf index out of block range.");
753 response.success = false;
754 return;
755 }
756 RequestContext requestContext;
757 requestContext.blockNumber = blockNumber;
758 requestContext.includeUncommitted = includeUncommitted;
759 requestContext.root = blockData.root;
760 std::optional<fr> leaf_hash = find_leaf_hash(leaf_index, requestContext, *tx, false);
761 response.success = leaf_hash.has_value();
762 if (response.success) {
763 response.inner.leaf = leaf_hash.value();
764 } else {
765 // We have an unwritten leaf, so return a 0 value, which is not a failure:
766 response.inner.leaf = fr::zero();
767 // TODO(#17755): The below is now redundant (we will always set response.success = true at this
768 // point), but keeping it in case we want to handle specific (non out of range index) errors e.g. no
769 // root
770 response.success = true;
771 response.message =
772 format("Failed to find leaf hash at index ", leaf_index, " for block number ", blockNumber);
773 }
774 },
775 on_completion);
776 };
777 workers_->enqueue(job);
778}
779
780template <typename Store, typename HashingPolicy>
783 bool includeUncommitted,
784 const FindLeafCallback& on_completion) const
785{
786 find_leaf_indices_from(leaves, 0, includeUncommitted, on_completion);
787}
788
789template <typename Store, typename HashingPolicy>
792 const block_number_t& blockNumber,
793 bool includeUncommitted,
794 const FindLeafCallback& on_completion) const
795{
796 find_leaf_indices_from(leaves, 0, blockNumber, includeUncommitted, on_completion);
797}
798
799template <typename Store, typename HashingPolicy>
802 const index_t& start_index,
803 bool includeUncommitted,
804 const FindLeafCallback& on_completion) const
805{
806 auto job = [=, this]() -> void {
807 execute_and_report<FindLeafIndexResponse>(
808 [=, this](TypedResponse<FindLeafIndexResponse>& response) {
809 response.inner.leaf_indices.reserve(leaves.size());
810 ReadTransactionPtr tx = store_->create_read_transaction();
811
812 RequestContext requestContext;
813 requestContext.includeUncommitted = includeUncommitted;
814
815 for (const auto& leaf : leaves) {
816 std::optional<index_t> leaf_index =
817 store_->find_leaf_index_from(leaf, start_index, requestContext, *tx);
818 response.inner.leaf_indices.emplace_back(leaf_index);
819 }
820 },
821 on_completion);
822 };
823 workers_->enqueue(job);
824}
825
826template <typename Store, typename HashingPolicy>
829 const index_t& start_index,
830 const block_number_t& blockNumber,
831 bool includeUncommitted,
832 const FindLeafCallback& on_completion) const
833{
834 auto job = [=, this]() -> void {
835 execute_and_report<FindLeafIndexResponse>(
836 [=, this](TypedResponse<FindLeafIndexResponse>& response) {
837 response.inner.leaf_indices.reserve(leaves.size());
838 if (blockNumber == 0) {
839 throw std::runtime_error("Unable to find leaf index for block number 0");
840 }
841 ReadTransactionPtr tx = store_->create_read_transaction();
842 BlockPayload blockData;
843 if (!store_->get_block_data(blockNumber, blockData, *tx)) {
844 throw std::runtime_error(format("Unable to find leaf from index ",
845 start_index,
846 " for block ",
847 blockNumber,
848 ", failed to get block data."));
849 }
850
851 RequestContext requestContext;
852 requestContext.blockNumber = blockNumber;
853 requestContext.includeUncommitted = includeUncommitted;
854 requestContext.maxIndex = blockData.size;
855
856 for (const auto& leaf : leaves) {
857 std::optional<index_t> leaf_index =
858 store_->find_leaf_index_from(leaf, start_index, requestContext, *tx);
859 response.inner.leaf_indices.emplace_back(leaf_index);
860 }
861 },
862 on_completion);
863 };
864 workers_->enqueue(job);
865}
866
867template <typename Store, typename HashingPolicy>
870 bool includeUncommitted,
871 const FindSiblingPathCallback& on_completion) const
872{
873 auto job = [=, this]() -> void {
874 execute_and_report<FindLeafPathResponse>(
875 [=, this](TypedResponse<FindLeafPathResponse>& response) {
876 response.inner.leaf_paths.reserve(leaves.size());
877 ReadTransactionPtr tx = store_->create_read_transaction();
878
879 RequestContext requestContext;
880 requestContext.includeUncommitted = includeUncommitted;
881 requestContext.root = store_->get_current_root(*tx, includeUncommitted);
882
883 for (const auto& leaf : leaves) {
884 std::optional<index_t> leaf_index = store_->find_leaf_index_from(leaf, 0, requestContext, *tx);
885 if (!leaf_index.has_value()) {
886 response.inner.leaf_paths.emplace_back(std::nullopt);
887 continue;
888 }
889 OptionalSiblingPath optional_path =
890 get_subtree_sibling_path_internal(leaf_index.value(), 0, requestContext, *tx);
891 SiblingPathAndIndex sibling_path_and_index;
892 sibling_path_and_index.path = optional_sibling_path_to_full_sibling_path(optional_path);
893 sibling_path_and_index.index = leaf_index.value();
894 response.inner.leaf_paths.emplace_back(sibling_path_and_index);
895 }
896 },
897 on_completion);
898 };
899 workers_->enqueue(job);
900}
901
902template <typename Store, typename HashingPolicy>
905 const block_number_t& blockNumber,
906 bool includeUncommitted,
907 const FindSiblingPathCallback& on_completion) const
908{
909 auto job = [=, this]() -> void {
910 execute_and_report<FindLeafPathResponse>(
911 [=, this](TypedResponse<FindLeafPathResponse>& response) {
912 response.inner.leaf_paths.reserve(leaves.size());
913 if (blockNumber == 0) {
914 throw std::runtime_error("Unable to find leaf index for block number 0");
915 }
916 ReadTransactionPtr tx = store_->create_read_transaction();
917 BlockPayload blockData;
918 if (!store_->get_block_data(blockNumber, blockData, *tx)) {
919 throw std::runtime_error(
920 format("Unable to find sibling path for block ", blockNumber, ", failed to get block data."));
921 }
922
923 RequestContext requestContext;
924 requestContext.blockNumber = blockNumber;
925 requestContext.includeUncommitted = includeUncommitted;
926 requestContext.maxIndex = blockData.size;
927 requestContext.root = blockData.root;
928
929 for (const auto& leaf : leaves) {
930 std::optional<index_t> leaf_index = store_->find_leaf_index_from(leaf, 0, requestContext, *tx);
931 if (!leaf_index.has_value()) {
932 response.inner.leaf_paths.emplace_back(std::nullopt);
933 continue;
934 }
935 OptionalSiblingPath optional_path =
936 get_subtree_sibling_path_internal(leaf_index.value(), 0, requestContext, *tx);
937 SiblingPathAndIndex sibling_path_and_index;
938 sibling_path_and_index.path = optional_sibling_path_to_full_sibling_path(optional_path);
939 sibling_path_and_index.index = leaf_index.value();
940 response.inner.leaf_paths.emplace_back(sibling_path_and_index);
941 }
942 },
943 on_completion);
944 };
945 workers_->enqueue(job);
946}
947
948template <typename Store, typename HashingPolicy>
950 const AppendCompletionCallback& on_completion)
951{
952 add_values(std::vector<fr>{ value }, on_completion);
953}
954
955template <typename Store, typename HashingPolicy>
957 const AppendCompletionCallback& on_completion)
958{
959 add_values_internal(values, on_completion, true);
960}
961
962template <typename Store, typename HashingPolicy>
964 const std::vector<fr>& values, const AppendCompletionCallback& on_completion, bool update_index)
965{
967 auto append_op = [=, this]() -> void {
968 execute_and_report<AddDataResponse>(
969 [=, this](TypedResponse<AddDataResponse>& response) {
970 add_values_internal(hashes, response.inner.root, response.inner.size, update_index);
971 },
972 on_completion);
973 };
974 workers_->enqueue(append_op);
975}
976
977template <typename Store, typename HashingPolicy>
979{
980 auto job = [=, this]() {
981 execute_and_report<CommitResponse>(
982 [=, this](TypedResponse<CommitResponse>& response) {
983 store_->commit_block(response.inner.meta, response.inner.stats);
984 },
985 on_completion);
986 };
987 workers_->enqueue(job);
988}
989
990template <typename Store, typename HashingPolicy>
992{
993 auto job = [=, this]() { execute_and_report([=, this]() { store_->rollback(); }, on_completion); };
994 workers_->enqueue(job);
995}
996
997// TODO(PhilWindle): One possible optimisation is for the following 3 functions
998// checkpoint, commit_checkpoint and revert_checkpoint to not use the thread pool
999// It is not stricly necessary for these operations to use it. The balance is whether
1000// the cost of using it outweighs the benefit or checkpointing/reverting all tree concurrently
1001
1002template <typename Store, typename HashingPolicy>
1004{
1005 auto job = [=, this]() { execute_and_report([=, this]() { store_->checkpoint(); }, on_completion); };
1006 workers_->enqueue(job);
1007}
1008
1009template <typename Store, typename HashingPolicy>
1011 const CheckpointCommitCallback& on_completion)
1012{
1013 auto job = [=, this]() { execute_and_report([=, this]() { store_->commit_checkpoint(); }, on_completion); };
1014 workers_->enqueue(job);
1015}
1016
1017template <typename Store, typename HashingPolicy>
1019 const CheckpointRevertCallback& on_completion)
1020{
1021 auto job = [=, this]() { execute_and_report([=, this]() { store_->revert_checkpoint(); }, on_completion); };
1022 workers_->enqueue(job);
1023}
1024
1025template <typename Store, typename HashingPolicy>
1027 const CheckpointCommitCallback& on_completion)
1028{
1029 auto job = [=, this]() { execute_and_report([=, this]() { store_->commit_all_checkpoints(); }, on_completion); };
1030 workers_->enqueue(job);
1031}
1032
1033template <typename Store, typename HashingPolicy>
1035 const CheckpointRevertCallback& on_completion)
1036{
1037 auto job = [=, this]() { execute_and_report([=, this]() { store_->revert_all_checkpoints(); }, on_completion); };
1038 workers_->enqueue(job);
1039}
1040
1041template <typename Store, typename HashingPolicy>
1043 const block_number_t& blockNumber, const RemoveHistoricBlockCallback& on_completion)
1044{
1045 auto job = [=, this]() {
1046 execute_and_report<RemoveHistoricResponse>(
1047 [=, this](TypedResponse<RemoveHistoricResponse>& response) {
1048 if (blockNumber == 0) {
1049 throw std::runtime_error("Unable to remove historic block 0");
1050 }
1051 store_->remove_historical_block(blockNumber, response.inner.meta, response.inner.stats);
1052 },
1053 on_completion);
1054 };
1055 workers_->enqueue(job);
1056}
1057
1058template <typename Store, typename HashingPolicy>
1060 const UnwindBlockCallback& on_completion)
1061{
1062 auto job = [=, this]() {
1063 execute_and_report<UnwindResponse>(
1064 [=, this](TypedResponse<UnwindResponse>& response) {
1065 if (blockNumber == 0) {
1066 throw std::runtime_error("Unable to unwind block 0");
1067 }
1068 store_->unwind_block(blockNumber, response.inner.meta, response.inner.stats);
1069 },
1070 on_completion);
1071 };
1072 workers_->enqueue(job);
1073}
1074
1075template <typename Store, typename HashingPolicy>
1077 const FinalizeBlockCallback& on_completion)
1078{
1079 auto job = [=, this]() {
1081 [=, this]() {
1082 if (blockNumber == 0) {
1083 throw std::runtime_error("Unable to finalize block 0");
1084 }
1085 store_->advance_finalized_block(blockNumber);
1086 },
1087 on_completion);
1088 };
1089 workers_->enqueue(job);
1090}
1091
1092template <typename Store, typename HashingPolicy>
1094 const index_t& treeSize, const index_t& remainingAppendSize)
1095{
1096 index_t minPower2 = 1;
1097 if (treeSize != 0U) {
1098 while (!(minPower2 & treeSize)) {
1099 minPower2 <<= 1;
1100 }
1101 if (minPower2 <= remainingAppendSize) {
1102 return minPower2;
1103 }
1104 }
1105 index_t maxPower2 = 1;
1106 while (maxPower2 <= remainingAppendSize) {
1107 maxPower2 <<= 1;
1108 }
1109 return maxPower2 >> 1;
1110}
1111
1112template <typename Store, typename HashingPolicy>
1114 fr& new_root,
1115 index_t& new_size,
1116 bool update_index)
1117{
1118 ReadTransactionPtr tx = store_->create_read_transaction();
1119 TreeMeta meta;
1120 store_->get_meta(meta);
1121 index_t sizeToAppend = values->size();
1122 new_size = meta.size;
1123 index_t batchIndex = 0;
1124 while (sizeToAppend != 0U) {
1125 index_t batchSize = get_batch_insertion_size(new_size, sizeToAppend);
1126 sizeToAppend -= batchSize;
1127 int64_t start = static_cast<int64_t>(batchIndex);
1128 int64_t end = static_cast<int64_t>(batchIndex + batchSize);
1129 std::vector<fr> batch = std::vector<fr>(values->begin() + start, values->begin() + end);
1130 batchIndex += batchSize;
1131 add_batch_internal(batch, new_root, new_size, update_index, *tx);
1132 }
1133}
1134
1135template <typename Store, typename HashingPolicy>
1137 std::vector<fr>& values, fr& new_root, index_t& new_size, bool update_index, ReadTransaction& tx)
1138{
1139 uint32_t start_level = depth_;
1140 uint32_t level = start_level;
1141 std::vector<fr>& hashes_local = values;
1142 auto number_to_insert = static_cast<uint32_t>(hashes_local.size());
1143
1144 TreeMeta meta;
1145 store_->get_meta(meta);
1146 index_t index = meta.size;
1147 new_size = meta.size + number_to_insert;
1148
1149 // std::cout << "Appending new leaves" << std::endl;
1150 if (values.empty()) {
1151 return;
1152 }
1153
1154 if (new_size > max_size_) {
1155 throw std::runtime_error(
1156 format("Unable to append leaves to tree ", meta.name, " new size: ", new_size, " max size: ", max_size_));
1157 }
1158
1159 // Add the values at the leaf nodes of the tree
1160 for (uint32_t i = 0; i < number_to_insert; ++i) {
1161 // write_node(level, index + i, hashes_local[i]);
1162 // std::cout << "Writing leaf hash: " << hashes_local[i] << " level " << level << std::endl;
1163 store_->put_node_by_hash(hashes_local[i], { .left = std::nullopt, .right = std::nullopt, .ref = 1 });
1164 store_->put_cached_node_by_index(level, i + index, hashes_local[i]);
1165 }
1166
1167 // If we have been told to add these leaves to the index then do so now
1168 if (update_index) {
1169 for (uint32_t i = 0; i < number_to_insert; ++i) {
1170 // We don't store indices of zero leaves
1171 if (hashes_local[i] == fr::zero()) {
1172 continue;
1173 }
1174 // std::cout << "Updating index " << index + i << " : " << hashes_local[i] << std::endl;
1175 store_->update_index(index + i, hashes_local[i]);
1176 }
1177 }
1178
1179 // Hash the values as a sub tree and insert them
1180 while (number_to_insert > 1) {
1181 number_to_insert >>= 1;
1182 index >>= 1;
1183 --level;
1184 // std::cout << "To INSERT " << number_to_insert << std::endl;
1185 for (uint32_t i = 0; i < number_to_insert; ++i) {
1186 fr left = hashes_local[i * 2];
1187 fr right = hashes_local[i * 2 + 1];
1188 hashes_local[i] = HashingPolicy::hash_pair(left, right);
1189 // std::cout << "Left: " << left << ", right: " << right << ", parent: " << hashes_local[i] << std::endl;
1190 store_->put_node_by_hash(hashes_local[i], { .left = left, .right = right, .ref = 1 });
1191 store_->put_cached_node_by_index(level, index + i, hashes_local[i]);
1192 // std::cout << "Writing node hash " << hashes_local[i] << " level " << level << " index " << index + i
1193 // << std::endl;
1194 }
1195 }
1196
1197 fr new_hash = hashes_local[0];
1198
1199 // std::cout << "LEVEL: " << level << " hash " << new_hash << std::endl;
1200 RequestContext requestContext;
1201 requestContext.includeUncommitted = true;
1202 requestContext.root = store_->get_current_root(tx, true);
1203 OptionalSiblingPath optional_sibling_path_to_root =
1204 get_subtree_sibling_path_internal(meta.size, depth_ - level, requestContext, tx);
1205 fr_sibling_path sibling_path_to_root = optional_sibling_path_to_full_sibling_path(optional_sibling_path_to_root);
1206 size_t sibling_path_index = 0;
1207
1208 // Hash from the root of the sub-tree to the root of the overall tree
1209
1210 // std::cout << "Root hash: " << new_hash << std::endl;
1211 while (level > 0) {
1212 bool is_right = static_cast<bool>(index & 0x01);
1213 // std::cout << "index: " << index << " sibling path index: " << sibling_path_index << ", is right: " <<
1214 // is_right
1215 // << std::endl;
1216 fr left_hash = is_right ? sibling_path_to_root[sibling_path_index] : new_hash;
1217 fr right_hash = is_right ? new_hash : sibling_path_to_root[sibling_path_index];
1218
1219 std::optional<fr> left_op = is_right ? optional_sibling_path_to_root[sibling_path_index] : new_hash;
1220 std::optional<fr> right_op = is_right ? new_hash : optional_sibling_path_to_root[sibling_path_index];
1221
1222 new_hash = HashingPolicy::hash_pair(left_hash, right_hash);
1223 // std::cout << "Left: " << left_hash << ", right: " << right_hash << ", parent: " << new_hash << std::endl;
1224
1225 index >>= 1;
1226 --level;
1227 ++sibling_path_index;
1228 store_->put_cached_node_by_index(level, index, new_hash);
1229 store_->put_node_by_hash(new_hash, { .left = left_op, .right = right_op, .ref = 1 });
1230 // std::cout << "Writing node hash " << new_hash << " level " << level << " index " << index << std::endl;
1231 }
1232
1233 new_root = new_hash;
1234 meta.root = new_hash;
1235 meta.size = new_size;
1236 // std::cout << "New size: " << meta.size << ", root " << meta.root << std::endl;
1237 store_->put_meta(meta);
1238}
1239
1240} // 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
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 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)
void commit_all_checkpoints(const CheckpointCommitCallback &on_completion)
uint32_t depth() const
Synchronous method to retrieve the depth of the tree.
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
void revert_all_checkpoints(const CheckpointRevertCallback &on_completion)
virtual void add_value(const fr &value, const AppendCompletionCallback &on_completion)
Adds a single value to the end of the tree.
std::optional< fr > find_leaf_hash(const index_t &leaf_index, const RequestContext &requestContext, ReadTransaction &tx, bool updateNodesByIndexCache=false) const
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:22
ContentAddressedCachedTreeStore< bb::fr > Store
void add_values(TreeType &tree, const std::vector< NullifierLeafValue > &values)
void hash(State &state) noexcept
void execute_and_report(const std::function< void(TypedResponse< ResponseType > &)> &f, const std::function< void(TypedResponse< ResponseType > &)> &on_completion)
Definition response.hpp:261
uint32_t block_number_t
Definition types.hpp:19
std::vector< fr > fr_sibling_path
Definition hash_path.hpp:16
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()