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 // Do we need to go right or left
576 bool is_right = static_cast<bool>(leaf_index & mask);
577 // std::cout << "Mask " << mask << " depth " << depth_ << std::endl;
578 mask >>= 1;
579
580 // If we don't have a child then this leaf isn't present
581 // If we do, then keep going down the tree
582 std::optional<fr> child = is_right ? nodePayload.right : nodePayload.left;
583
584 if (!child.has_value()) {
585 // std::cout << "No child" << std::endl;
586 // We still need to update the cache with the sibling. The fact that under us there is an empty subtree
587 // doesn't mean that same is happening with our sibling.
588 if (updateNodesByIndexCache) {
589 child_index_at_level = is_right ? (child_index_at_level * 2) + 1 : (child_index_at_level * 2);
590 std::optional<fr> sibling = is_right ? nodePayload.left : nodePayload.right;
591 index_t sibling_index_at_level = is_right ? child_index_at_level - 1 : child_index_at_level + 1;
592 if (sibling.has_value()) {
593 store_->put_cached_node_by_index(i + 1, sibling_index_at_level, sibling.value(), false);
594 }
595 }
596 return std::nullopt;
597 }
598 // std::cout << "Found child " << child.value() << std::endl;
599
600 hash = child.value();
601
602 if (!updateNodesByIndexCache) {
603 continue;
604 }
605
606 child_index_at_level = is_right ? (child_index_at_level * 2) + 1 : (child_index_at_level * 2);
607 // std::cout << "Storing child " << i + 1 << " : " << child_index_at_level << " is right " << is_right
608 // << std::endl;
609 store_->put_cached_node_by_index(i + 1, child_index_at_level, hash, false);
610 std::optional<fr> sibling = is_right ? nodePayload.left : nodePayload.right;
611 index_t sibling_index_at_level = is_right ? child_index_at_level - 1 : child_index_at_level + 1;
612 if (sibling.has_value()) {
613 // std::cout << "Storing sibling " << i + 1 << " : " << sibling_index_at_level << " is right " <<
614 // is_right
615 // << std::endl;
616 store_->put_cached_node_by_index(i + 1, sibling_index_at_level, sibling.value(), false);
617 }
618 }
619
620 // if we have arrived here then the leaf is in 'hash'
621 return std::optional<fr>(hash);
622}
623
624template <typename Store, typename HashingPolicy>
626 Store,
627 HashingPolicy>::get_subtree_sibling_path_internal(const index_t& leaf_index,
628 uint32_t subtree_depth,
629 const RequestContext& requestContext,
630 ReadTransaction& tx) const
631{
632 // skip the first levels, all the way to the subtree_root
634 if (subtree_depth >= depth_) {
635 return path;
636 }
637 path.resize(depth_ - subtree_depth);
638 size_t path_index = path.size() - 1;
639
640 index_t mask = index_t(1) << (depth_ - 1);
641 // std::cout << "Depth: " << depth_ << ", mask: " << mask << ", sub tree depth: " << subtree_depth
642 // << ", leaf index: " << leaf_index << std::endl;
643 fr hash = requestContext.root;
644 // std::cout << "Getting sibling path for root: " << hash << std::endl;
645
646 for (uint32_t level = 0; level < depth_ - subtree_depth; ++level) {
647 NodePayload nodePayload;
648 store_->get_node_by_hash(hash, nodePayload, tx, requestContext.includeUncommitted);
649 bool is_right = static_cast<bool>(leaf_index & mask);
650 // std::cout << "Level: " << level << ", mask: " << mask << ", is right: " << is_right << ", parent: " << hash
651 // << ", left has value: " << nodePayload.left.has_value()
652 // << ", right has value: " << nodePayload.right.has_value() << std::endl;
653 // if (nodePayload.left.has_value()) {
654 // std::cout << "LEFT " << nodePayload.left.value() << std::endl;
655 // }
656 // if (nodePayload.right.has_value()) {
657 // std::cout << "RIGHT " << nodePayload.right.value() << std::endl;
658 // }
659 mask >>= 1;
660 std::optional<fr> sibling = is_right ? nodePayload.left : nodePayload.right;
661 std::optional<fr> child = is_right ? nodePayload.right : nodePayload.left;
662 hash = child.has_value() ? child.value() : zero_hashes_[level + 1];
663 // fr sib = (sibling.has_value() ? sibling.value() : zero_hashes_[level + 1]);
664 // std::cout << "Pushed sibling: " << sib << ", hash: " << hash << ", path index " << path_index << std::endl;
665 path[path_index--] = sibling;
666 }
667
668 return path;
669}
670
671template <typename Store, typename HashingPolicy>
673 bool includeUncommitted,
674 const GetLeafCallback& on_completion) const
675{
676 auto job = [=, this]() {
677 execute_and_report<GetLeafResponse>(
678 [=, this](TypedResponse<GetLeafResponse>& response) {
679 ReadTransactionPtr tx = store_->create_read_transaction();
680 RequestContext requestContext;
681 requestContext.includeUncommitted = includeUncommitted;
682 requestContext.root = store_->get_current_root(*tx, includeUncommitted);
683 std::optional<fr> leaf_hash = find_leaf_hash(leaf_index, requestContext, *tx, false);
684 response.success = leaf_hash.has_value();
685 if (response.success) {
686 response.inner.leaf = leaf_hash.value();
687 } else {
688 response.message = format("Failed to find leaf hash at index ", leaf_index);
689 }
690 },
691 on_completion);
692 };
693 workers_->enqueue(job);
694}
695
696template <typename Store, typename HashingPolicy>
698 const block_number_t& blockNumber,
699 bool includeUncommitted,
700 const GetLeafCallback& on_completion) const
701{
702 auto job = [=, this]() {
703 execute_and_report<GetLeafResponse>(
704 [=, this](TypedResponse<GetLeafResponse>& response) {
705 if (blockNumber == 0) {
706 throw std::runtime_error("Unable to get leaf at block 0");
707 }
708 ReadTransactionPtr tx = store_->create_read_transaction();
709 BlockPayload blockData;
710 if (!store_->get_block_data(blockNumber, blockData, *tx)) {
711 throw std::runtime_error(format("Unable to get leaf at index ",
712 leaf_index,
713 " for block ",
714 blockNumber,
715 ", failed to get block data."));
716 }
717 if (blockData.size < leaf_index) {
718 response.message = format("Unable to get leaf at index ",
719 leaf_index,
720 " for block ",
721 blockNumber,
722 ", leaf index out of range.");
723 response.success = false;
724 return;
725 }
726 RequestContext requestContext;
727 requestContext.blockNumber = blockNumber;
728 requestContext.includeUncommitted = includeUncommitted;
729 requestContext.root = blockData.root;
730 std::optional<fr> leaf_hash = find_leaf_hash(leaf_index, requestContext, *tx, false);
731 response.success = leaf_hash.has_value();
732 if (response.success) {
733 response.inner.leaf = leaf_hash.value();
734 } else {
735 response.message =
736 format("Failed to find leaf hash at index ", leaf_index, " for block number ", blockNumber);
737 }
738 },
739 on_completion);
740 };
741 workers_->enqueue(job);
742}
743
744template <typename Store, typename HashingPolicy>
747 bool includeUncommitted,
748 const FindLeafCallback& on_completion) const
749{
750 find_leaf_indices_from(leaves, 0, includeUncommitted, on_completion);
751}
752
753template <typename Store, typename HashingPolicy>
756 const block_number_t& blockNumber,
757 bool includeUncommitted,
758 const FindLeafCallback& on_completion) const
759{
760 find_leaf_indices_from(leaves, 0, blockNumber, includeUncommitted, on_completion);
761}
762
763template <typename Store, typename HashingPolicy>
766 const index_t& start_index,
767 bool includeUncommitted,
768 const FindLeafCallback& on_completion) const
769{
770 auto job = [=, this]() -> void {
771 execute_and_report<FindLeafIndexResponse>(
772 [=, this](TypedResponse<FindLeafIndexResponse>& response) {
773 response.inner.leaf_indices.reserve(leaves.size());
774 ReadTransactionPtr tx = store_->create_read_transaction();
775
776 RequestContext requestContext;
777 requestContext.includeUncommitted = includeUncommitted;
778
779 for (const auto& leaf : leaves) {
780 std::optional<index_t> leaf_index =
781 store_->find_leaf_index_from(leaf, start_index, requestContext, *tx);
782 response.inner.leaf_indices.emplace_back(leaf_index);
783 }
784 },
785 on_completion);
786 };
787 workers_->enqueue(job);
788}
789
790template <typename Store, typename HashingPolicy>
793 const index_t& start_index,
794 const block_number_t& blockNumber,
795 bool includeUncommitted,
796 const FindLeafCallback& on_completion) const
797{
798 auto job = [=, this]() -> void {
799 execute_and_report<FindLeafIndexResponse>(
800 [=, this](TypedResponse<FindLeafIndexResponse>& response) {
801 response.inner.leaf_indices.reserve(leaves.size());
802 if (blockNumber == 0) {
803 throw std::runtime_error("Unable to find leaf index for block number 0");
804 }
805 ReadTransactionPtr tx = store_->create_read_transaction();
806 BlockPayload blockData;
807 if (!store_->get_block_data(blockNumber, blockData, *tx)) {
808 throw std::runtime_error(format("Unable to find leaf from index ",
809 start_index,
810 " for block ",
811 blockNumber,
812 ", failed to get block data."));
813 }
814
815 RequestContext requestContext;
816 requestContext.blockNumber = blockNumber;
817 requestContext.includeUncommitted = includeUncommitted;
818 requestContext.maxIndex = blockData.size;
819
820 for (const auto& leaf : leaves) {
821 std::optional<index_t> leaf_index =
822 store_->find_leaf_index_from(leaf, start_index, requestContext, *tx);
823 response.inner.leaf_indices.emplace_back(leaf_index);
824 }
825 },
826 on_completion);
827 };
828 workers_->enqueue(job);
829}
830
831template <typename Store, typename HashingPolicy>
834 bool includeUncommitted,
835 const FindSiblingPathCallback& on_completion) const
836{
837 auto job = [=, this]() -> void {
838 execute_and_report<FindLeafPathResponse>(
839 [=, this](TypedResponse<FindLeafPathResponse>& response) {
840 response.inner.leaf_paths.reserve(leaves.size());
841 ReadTransactionPtr tx = store_->create_read_transaction();
842
843 RequestContext requestContext;
844 requestContext.includeUncommitted = includeUncommitted;
845 requestContext.root = store_->get_current_root(*tx, includeUncommitted);
846
847 for (const auto& leaf : leaves) {
848 std::optional<index_t> leaf_index = store_->find_leaf_index_from(leaf, 0, requestContext, *tx);
849 if (!leaf_index.has_value()) {
850 response.inner.leaf_paths.emplace_back(std::nullopt);
851 continue;
852 }
853 OptionalSiblingPath optional_path =
854 get_subtree_sibling_path_internal(leaf_index.value(), 0, requestContext, *tx);
855 SiblingPathAndIndex sibling_path_and_index;
856 sibling_path_and_index.path = optional_sibling_path_to_full_sibling_path(optional_path);
857 sibling_path_and_index.index = leaf_index.value();
858 response.inner.leaf_paths.emplace_back(sibling_path_and_index);
859 }
860 },
861 on_completion);
862 };
863 workers_->enqueue(job);
864}
865
866template <typename Store, typename HashingPolicy>
869 const block_number_t& blockNumber,
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 if (blockNumber == 0) {
878 throw std::runtime_error("Unable to find leaf index for block number 0");
879 }
880 ReadTransactionPtr tx = store_->create_read_transaction();
881 BlockPayload blockData;
882 if (!store_->get_block_data(blockNumber, blockData, *tx)) {
883 throw std::runtime_error(
884 format("Unable to find sibling path for block ", blockNumber, ", failed to get block data."));
885 }
886
887 RequestContext requestContext;
888 requestContext.blockNumber = blockNumber;
889 requestContext.includeUncommitted = includeUncommitted;
890 requestContext.maxIndex = blockData.size;
891 requestContext.root = blockData.root;
892
893 for (const auto& leaf : leaves) {
894 std::optional<index_t> leaf_index = store_->find_leaf_index_from(leaf, 0, requestContext, *tx);
895 if (!leaf_index.has_value()) {
896 response.inner.leaf_paths.emplace_back(std::nullopt);
897 continue;
898 }
899 OptionalSiblingPath optional_path =
900 get_subtree_sibling_path_internal(leaf_index.value(), 0, requestContext, *tx);
901 SiblingPathAndIndex sibling_path_and_index;
902 sibling_path_and_index.path = optional_sibling_path_to_full_sibling_path(optional_path);
903 sibling_path_and_index.index = leaf_index.value();
904 response.inner.leaf_paths.emplace_back(sibling_path_and_index);
905 }
906 },
907 on_completion);
908 };
909 workers_->enqueue(job);
910}
911
912template <typename Store, typename HashingPolicy>
914 const AppendCompletionCallback& on_completion)
915{
916 add_values(std::vector<fr>{ value }, on_completion);
917}
918
919template <typename Store, typename HashingPolicy>
921 const AppendCompletionCallback& on_completion)
922{
923 add_values_internal(values, on_completion, true);
924}
925
926template <typename Store, typename HashingPolicy>
928 const std::vector<fr>& values, const AppendCompletionCallback& on_completion, bool update_index)
929{
931 auto append_op = [=, this]() -> void {
932 execute_and_report<AddDataResponse>(
933 [=, this](TypedResponse<AddDataResponse>& response) {
934 add_values_internal(hashes, response.inner.root, response.inner.size, update_index);
935 },
936 on_completion);
937 };
938 workers_->enqueue(append_op);
939}
940
941template <typename Store, typename HashingPolicy>
943{
944 auto job = [=, this]() {
945 execute_and_report<CommitResponse>(
946 [=, this](TypedResponse<CommitResponse>& response) {
947 store_->commit_block(response.inner.meta, response.inner.stats);
948 },
949 on_completion);
950 };
951 workers_->enqueue(job);
952}
953
954template <typename Store, typename HashingPolicy>
956{
957 auto job = [=, this]() { execute_and_report([=, this]() { store_->rollback(); }, on_completion); };
958 workers_->enqueue(job);
959}
960
961// TODO(PhilWindle): One possible optimisation is for the following 3 functions
962// checkpoint, commit_checkpoint and revert_checkpoint to not use the thread pool
963// It is not stricly necessary for these operations to use it. The balance is whether
964// the cost of using it outweighs the benefit or checkpointing/reverting all tree concurrently
965
966template <typename Store, typename HashingPolicy>
968{
969 auto job = [=, this]() { execute_and_report([=, this]() { store_->checkpoint(); }, on_completion); };
970 workers_->enqueue(job);
971}
972
973template <typename Store, typename HashingPolicy>
975 const CheckpointCommitCallback& on_completion)
976{
977 auto job = [=, this]() { execute_and_report([=, this]() { store_->commit_checkpoint(); }, on_completion); };
978 workers_->enqueue(job);
979}
980
981template <typename Store, typename HashingPolicy>
983 const CheckpointRevertCallback& on_completion)
984{
985 auto job = [=, this]() { execute_and_report([=, this]() { store_->revert_checkpoint(); }, on_completion); };
986 workers_->enqueue(job);
987}
988
989template <typename Store, typename HashingPolicy>
991 const CheckpointCommitCallback& on_completion)
992{
993 auto job = [=, this]() { execute_and_report([=, this]() { store_->commit_all_checkpoints(); }, on_completion); };
994 workers_->enqueue(job);
995}
996
997template <typename Store, typename HashingPolicy>
999 const CheckpointRevertCallback& on_completion)
1000{
1001 auto job = [=, this]() { execute_and_report([=, this]() { store_->revert_all_checkpoints(); }, on_completion); };
1002 workers_->enqueue(job);
1003}
1004
1005template <typename Store, typename HashingPolicy>
1007 const block_number_t& blockNumber, const RemoveHistoricBlockCallback& on_completion)
1008{
1009 auto job = [=, this]() {
1010 execute_and_report<RemoveHistoricResponse>(
1011 [=, this](TypedResponse<RemoveHistoricResponse>& response) {
1012 if (blockNumber == 0) {
1013 throw std::runtime_error("Unable to remove historic block 0");
1014 }
1015 store_->remove_historical_block(blockNumber, response.inner.meta, response.inner.stats);
1016 },
1017 on_completion);
1018 };
1019 workers_->enqueue(job);
1020}
1021
1022template <typename Store, typename HashingPolicy>
1024 const UnwindBlockCallback& on_completion)
1025{
1026 auto job = [=, this]() {
1027 execute_and_report<UnwindResponse>(
1028 [=, this](TypedResponse<UnwindResponse>& response) {
1029 if (blockNumber == 0) {
1030 throw std::runtime_error("Unable to unwind block 0");
1031 }
1032 store_->unwind_block(blockNumber, response.inner.meta, response.inner.stats);
1033 },
1034 on_completion);
1035 };
1036 workers_->enqueue(job);
1037}
1038
1039template <typename Store, typename HashingPolicy>
1041 const FinalizeBlockCallback& on_completion)
1042{
1043 auto job = [=, this]() {
1045 [=, this]() {
1046 if (blockNumber == 0) {
1047 throw std::runtime_error("Unable to finalize block 0");
1048 }
1049 store_->advance_finalized_block(blockNumber);
1050 },
1051 on_completion);
1052 };
1053 workers_->enqueue(job);
1054}
1055
1056template <typename Store, typename HashingPolicy>
1058 const index_t& treeSize, const index_t& remainingAppendSize)
1059{
1060 index_t minPower2 = 1;
1061 if (treeSize != 0U) {
1062 while (!(minPower2 & treeSize)) {
1063 minPower2 <<= 1;
1064 }
1065 if (minPower2 <= remainingAppendSize) {
1066 return minPower2;
1067 }
1068 }
1069 index_t maxPower2 = 1;
1070 while (maxPower2 <= remainingAppendSize) {
1071 maxPower2 <<= 1;
1072 }
1073 return maxPower2 >> 1;
1074}
1075
1076template <typename Store, typename HashingPolicy>
1078 fr& new_root,
1079 index_t& new_size,
1080 bool update_index)
1081{
1082 ReadTransactionPtr tx = store_->create_read_transaction();
1083 TreeMeta meta;
1084 store_->get_meta(meta);
1085 index_t sizeToAppend = values->size();
1086 new_size = meta.size;
1087 index_t batchIndex = 0;
1088 while (sizeToAppend != 0U) {
1089 index_t batchSize = get_batch_insertion_size(new_size, sizeToAppend);
1090 sizeToAppend -= batchSize;
1091 int64_t start = static_cast<int64_t>(batchIndex);
1092 int64_t end = static_cast<int64_t>(batchIndex + batchSize);
1093 std::vector<fr> batch = std::vector<fr>(values->begin() + start, values->begin() + end);
1094 batchIndex += batchSize;
1095 add_batch_internal(batch, new_root, new_size, update_index, *tx);
1096 }
1097}
1098
1099template <typename Store, typename HashingPolicy>
1101 std::vector<fr>& values, fr& new_root, index_t& new_size, bool update_index, ReadTransaction& tx)
1102{
1103 uint32_t start_level = depth_;
1104 uint32_t level = start_level;
1105 std::vector<fr>& hashes_local = values;
1106 auto number_to_insert = static_cast<uint32_t>(hashes_local.size());
1107
1108 TreeMeta meta;
1109 store_->get_meta(meta);
1110 index_t index = meta.size;
1111 new_size = meta.size + number_to_insert;
1112
1113 // std::cout << "Appending new leaves" << std::endl;
1114 if (values.empty()) {
1115 return;
1116 }
1117
1118 if (new_size > max_size_) {
1119 throw std::runtime_error(
1120 format("Unable to append leaves to tree ", meta.name, " new size: ", new_size, " max size: ", max_size_));
1121 }
1122
1123 // Add the values at the leaf nodes of the tree
1124 for (uint32_t i = 0; i < number_to_insert; ++i) {
1125 // write_node(level, index + i, hashes_local[i]);
1126 // std::cout << "Writing leaf hash: " << hashes_local[i] << " level " << level << std::endl;
1127 store_->put_node_by_hash(hashes_local[i], { .left = std::nullopt, .right = std::nullopt, .ref = 1 });
1128 store_->put_cached_node_by_index(level, i + index, hashes_local[i]);
1129 }
1130
1131 // If we have been told to add these leaves to the index then do so now
1132 if (update_index) {
1133 for (uint32_t i = 0; i < number_to_insert; ++i) {
1134 // We don't store indices of zero leaves
1135 if (hashes_local[i] == fr::zero()) {
1136 continue;
1137 }
1138 // std::cout << "Updating index " << index + i << " : " << hashes_local[i] << std::endl;
1139 store_->update_index(index + i, hashes_local[i]);
1140 }
1141 }
1142
1143 // Hash the values as a sub tree and insert them
1144 while (number_to_insert > 1) {
1145 number_to_insert >>= 1;
1146 index >>= 1;
1147 --level;
1148 // std::cout << "To INSERT " << number_to_insert << std::endl;
1149 for (uint32_t i = 0; i < number_to_insert; ++i) {
1150 fr left = hashes_local[i * 2];
1151 fr right = hashes_local[i * 2 + 1];
1152 hashes_local[i] = HashingPolicy::hash_pair(left, right);
1153 // std::cout << "Left: " << left << ", right: " << right << ", parent: " << hashes_local[i] << std::endl;
1154 store_->put_node_by_hash(hashes_local[i], { .left = left, .right = right, .ref = 1 });
1155 store_->put_cached_node_by_index(level, index + i, hashes_local[i]);
1156 // std::cout << "Writing node hash " << hashes_local[i] << " level " << level << " index " << index + i
1157 // << std::endl;
1158 }
1159 }
1160
1161 fr new_hash = hashes_local[0];
1162
1163 // std::cout << "LEVEL: " << level << " hash " << new_hash << std::endl;
1164 RequestContext requestContext;
1165 requestContext.includeUncommitted = true;
1166 requestContext.root = store_->get_current_root(tx, true);
1167 OptionalSiblingPath optional_sibling_path_to_root =
1168 get_subtree_sibling_path_internal(meta.size, depth_ - level, requestContext, tx);
1169 fr_sibling_path sibling_path_to_root = optional_sibling_path_to_full_sibling_path(optional_sibling_path_to_root);
1170 size_t sibling_path_index = 0;
1171
1172 // Hash from the root of the sub-tree to the root of the overall tree
1173
1174 // std::cout << "Root hash: " << new_hash << std::endl;
1175 while (level > 0) {
1176 bool is_right = static_cast<bool>(index & 0x01);
1177 // std::cout << "index: " << index << " sibling path index: " << sibling_path_index << ", is right: " <<
1178 // is_right
1179 // << std::endl;
1180 fr left_hash = is_right ? sibling_path_to_root[sibling_path_index] : new_hash;
1181 fr right_hash = is_right ? new_hash : sibling_path_to_root[sibling_path_index];
1182
1183 std::optional<fr> left_op = is_right ? optional_sibling_path_to_root[sibling_path_index] : new_hash;
1184 std::optional<fr> right_op = is_right ? new_hash : optional_sibling_path_to_root[sibling_path_index];
1185
1186 new_hash = HashingPolicy::hash_pair(left_hash, right_hash);
1187 // std::cout << "Left: " << left_hash << ", right: " << right_hash << ", parent: " << new_hash << std::endl;
1188
1189 index >>= 1;
1190 --level;
1191 ++sibling_path_index;
1192 store_->put_cached_node_by_index(level, index, new_hash);
1193 store_->put_node_by_hash(new_hash, { .left = left_op, .right = right_op, .ref = 1 });
1194 // std::cout << "Writing node hash " << new_hash << " level " << level << " index " << index << std::endl;
1195 }
1196
1197 new_root = new_hash;
1198 meta.root = new_hash;
1199 meta.size = new_size;
1200 // std::cout << "New size: " << meta.size << ", root " << meta.root << std::endl;
1201 store_->put_meta(meta);
1202}
1203
1204} // namespace bb::crypto::merkle_tree
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:20
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:260
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.
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()