Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
cached_content_addressed_tree_store.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#include "./tree_meta.hpp"
20#include "msgpack/assert.hpp"
21#include <cstdint>
22#include <exception>
23#include <iostream>
24#include <memory>
25#include <mutex>
26#include <optional>
27#include <sstream>
28#include <stdexcept>
29#include <unordered_map>
30#include <utility>
31#include <vector>
32
34
44template <typename LeafValueType> class ContentAddressedCachedTreeStore {
45 public:
53
54 ContentAddressedCachedTreeStore(std::string name, uint32_t levels, PersistedStoreType::SharedPtr dataStore);
56 uint32_t levels,
57 const block_number_t& referenceBlockNumber,
60
66
71 const RequestContext& requestContext,
72 ReadTransaction& tx) const;
73
79 bool includeUncommitted) const;
80
85
89 void update_index(const index_t& index, const fr& leaf);
90
94 void put_node_by_hash(const fr& nodeHash, const NodePayload& payload);
95
99 bool get_node_by_hash(const fr& nodeHash,
100 NodePayload& payload,
101 ReadTransaction& transaction,
102 bool includeUncommitted) const;
103
107 void put_cached_node_by_index(uint32_t level, const index_t& index, const fr& data, bool overwriteIfPresent = true);
108
112 bool get_cached_node_by_index(uint32_t level, const index_t& index, fr& data) const;
113
117 void put_meta(const TreeMeta& m);
118
122 void get_meta(TreeMeta& m, ReadTransaction& tx, bool includeUncommitted) const;
123
127 void get_meta(TreeMeta& m) const;
128
132 bool get_block_data(const block_number_t& blockNumber, BlockPayload& blockData, ReadTransaction& tx) const;
133
138 const RequestContext& requestContext,
139 ReadTransaction& tx) const;
140
145 const index_t& start_index,
146 const RequestContext& requestContext,
147 ReadTransaction& tx) const;
148
152 void commit_block(TreeMeta& finalMeta, TreeDBStats& dbStats);
153
158
162 void rollback();
163
167 std::string get_name() const { return forkConstantData_.name_; }
168
172 ReadTransactionPtr create_read_transaction() const { return dataStore_->create_read_transaction(); }
173
175 ReadTransaction& tx,
176 bool includeUncommitted) const;
177
178 void put_leaf_by_hash(const fr& leaf_hash, const IndexedLeafValueType& leafPreImage);
179
181
183
184 fr get_current_root(ReadTransaction& tx, bool includeUncommitted) const;
185
186 void remove_historical_block(const block_number_t& blockNumber, TreeMeta& finalMeta, TreeDBStats& dbStats);
187
188 void unwind_block(const block_number_t& blockNumber, TreeMeta& finalMeta, TreeDBStats& dbStats);
189
190 void advance_finalized_block(const block_number_t& blockNumber);
191
193
194 uint32_t checkpoint();
199 void commit_to_depth(uint32_t depth);
200 void revert_to_depth(uint32_t depth);
201 uint32_t checkpoint_depth() const;
202
203 private:
205
212 mutable std::mutex mtx_;
213
215
217
219
220 void initialize_from_block(const block_number_t& blockNumber);
221
223
225
227
228 void persist_node(const std::optional<fr>& optional_hash, uint32_t level, WriteTransaction& tx);
229
230 void remove_node(const std::optional<fr>& optional_hash,
231 uint32_t level,
232 const std::optional<index_t>& maxIndex,
233 WriteTransaction& tx);
234
235 void remove_leaf(const fr& hash, const std::optional<index_t>& maxIndex, WriteTransaction& tx);
236
237 void remove_leaf_index(const fr& key, const index_t& maxIndex, WriteTransaction& tx);
238
240
242
244
246
248
250
251 WriteTransactionPtr create_write_transaction() const { return dataStore_->create_write_transaction(); }
252};
253
254template <typename LeafValueType>
256 uint32_t levels,
258 : forkConstantData_{ .name_ = (std::move(name)), .depth_ = levels }
259 , dataStore_(dataStore)
260 , cache_(levels)
261{
262 initialize();
263}
264
265template <typename LeafValueType>
267 std::string name,
268 uint32_t levels,
269 const block_number_t& referenceBlockNumber,
271 : forkConstantData_{ .name_ = (std::move(name)), .depth_ = levels }
272 , dataStore_(dataStore)
273 , cache_(levels)
274{
275 initialize_from_block(referenceBlockNumber);
276}
277
278// Much Like the commit/rollback/set finalized/remove historic blocks apis
279// These checkpoint apis modify the cache's internal state.
280// They acquire the mutex to prevent races with concurrent read/write operations (e.g., when C++ AVM simulation
281// runs on a worker thread while TypeScript calls revert_checkpoint from a timeout handler).
282template <typename LeafValueType> uint32_t ContentAddressedCachedTreeStore<LeafValueType>::checkpoint()
283{
284 std::unique_lock lock(mtx_);
285 return cache_.checkpoint();
286}
287
289{
290 std::unique_lock lock(mtx_);
291 cache_.revert();
292}
293
295{
296 std::unique_lock lock(mtx_);
297 cache_.commit();
298}
299
301{
302 std::unique_lock lock(mtx_);
303 cache_.revert_all();
304}
305
307{
308 std::unique_lock lock(mtx_);
309 cache_.commit_all();
310}
311
312template <typename LeafValueType> void ContentAddressedCachedTreeStore<LeafValueType>::commit_to_depth(uint32_t depth)
313{
314 std::unique_lock lock(mtx_);
315 cache_.commit_to_depth(depth);
316}
317
318template <typename LeafValueType> void ContentAddressedCachedTreeStore<LeafValueType>::revert_to_depth(uint32_t depth)
319{
320 std::unique_lock lock(mtx_);
321 cache_.revert_to_depth(depth);
322}
323
324template <typename LeafValueType> uint32_t ContentAddressedCachedTreeStore<LeafValueType>::checkpoint_depth() const
325{
326 std::unique_lock lock(mtx_);
327 return cache_.depth();
328}
329
330template <typename LeafValueType>
332 const RequestContext& requestContext, ReadTransaction& tx) const
333{
334 // We need to identify the size of the committed tree as it exists from our perspective
335 // We either take from the fork's constant data if available or we read the meta data from the store
336 index_t sizeLimit = 0;
337 if (forkConstantData_.initialized_from_block_.has_value()) {
338 // We are a fork. Take from constant data
339 sizeLimit = forkConstantData_.initialized_from_block_.value().size;
340 } else {
341 // We are the main tree. Read from the store, only use committed so as to not violate any requests for purely
342 // committed data
343 TreeMeta m;
344 get_meta(m, tx, false);
345 sizeLimit = m.committedSize;
346 }
347 if (requestContext.maxIndex.has_value() && requestContext.maxIndex.value() < sizeLimit) {
348 sizeLimit = requestContext.maxIndex.value();
349 }
350 return sizeLimit;
351}
352
353template <typename LeafValueType>
355 const index_t& index, ReadTransaction& tx) const
356{
358 context.maxIndex = index + 1;
359 index_t constrainedSize = constrain_tree_size_to_only_committed(context, tx);
360 if (index >= constrainedSize) {
361 return std::nullopt;
362 }
363 block_number_t blockNumber = 0;
364 bool success = dataStore_->find_block_for_index(index, blockNumber, tx);
365 return success ? std::make_optional(blockNumber) : std::nullopt;
366}
367
368template <typename LeafValueType>
370 const index_t& index,
372{
373 dataStore_->write_block_index_data(blockNumber, index, tx);
374}
375
376template <typename LeafValueType>
378 const index_t& index,
380{
381 dataStore_->delete_block_index(index, blockNumber, tx);
382}
383
384template <typename LeafValueType>
386 const fr& new_leaf_key, const RequestContext& requestContext, ReadTransaction& tx) const
387{
388 auto new_value_as_number = uint256_t(new_leaf_key);
389 index_t committed = 0;
390
391 // We first read committed data, so we must constrin the search to only the data committed from our perspective
392 // That means, if we are a fork, the committed size is the size of the tree as it was when we forked
393 // If we are the main tree, the committed size is the size of the tree as it is now
394 std::optional<index_t> sizeLimit = constrain_tree_size_to_only_committed(requestContext, tx);
395
396 fr found_key = dataStore_->find_low_leaf(new_leaf_key, committed, sizeLimit, tx);
397 index_t db_index = committed;
398 uint256_t retrieved_value = found_key;
399
400 // If we already found the leaf then return it.
401 bool already_present = retrieved_value == new_value_as_number;
402 if (already_present) {
403 return std::make_pair(true, db_index);
404 }
405
406 // If we were asked not to include uncommitted then return what we have
407 if (!requestContext.includeUncommitted) {
408 return std::make_pair(false, db_index);
409 }
410
411 // Accessing the cache from here under a lock
412 std::unique_lock lock(mtx_);
413 return cache_.find_low_value(new_leaf_key, retrieved_value, db_index);
414}
415
416template <typename LeafValueType>
419 ReadTransaction& tx,
420 bool includeUncommitted) const
421{
422 IndexedLeafValueType leafData;
423 if (includeUncommitted) {
424 // Accessing the cache here under a lock
425 std::unique_lock lock(mtx_);
426 if (cache_.get_leaf_preimage_by_hash(leaf_hash, leafData)) {
427 return leafData;
428 }
429 }
430 if (dataStore_->read_leaf_by_hash(leaf_hash, leafData, tx)) {
431 return leafData;
432 }
433 return std::nullopt;
434}
435
436template <typename LeafValueType>
438 const IndexedLeafValueType& leafPreImage)
439{
440 // Accessing the cache under a lock
441 std::unique_lock lock(mtx_);
442 cache_.put_leaf_preimage_by_hash(leaf_hash, leafPreImage);
443}
444
445template <typename LeafValueType>
448{
449 // Accessing the cache under a lock
450 std::unique_lock lock(mtx_);
451 IndexedLeafValueType leafPreImage;
452 if (cache_.get_leaf_by_index(index, leafPreImage)) {
453 return leafPreImage;
454 }
455 return std::nullopt;
456}
457
458template <typename LeafValueType>
460 const IndexedLeafValueType& leafPreImage)
461{
462 // Accessing the cache under a lock
463 std::unique_lock lock(mtx_);
464 cache_.put_leaf_by_index(index, leafPreImage);
465}
466
467template <typename LeafValueType>
469 const IndexedLeafValueType& leaf)
470{
471 // std::cout << "Set leaf key at index " << index << std::endl;
472 update_index(index, leaf.leaf.get_key());
473}
474
475template <typename LeafValueType>
477{
478 // std::cout << "update_index at index " << index << " leaf " << leaf << std::endl;
479 // Accessing the cache under a lock
480 std::unique_lock lock(mtx_);
481 cache_.update_leaf_key_index(index, leaf);
482}
483
484template <typename LeafValueType>
486 const LeafValueType& leaf, const RequestContext& requestContext, ReadTransaction& tx) const
487{
488 return find_leaf_index_from(leaf, 0, requestContext, tx);
489}
490
491template <typename LeafValueType>
493 const LeafValueType& leaf,
494 const index_t& start_index,
495 const RequestContext& requestContext,
496 ReadTransaction& tx) const
497{
498 if (requestContext.includeUncommitted) {
499 // Accessing the cache under a lock
500 std::unique_lock lock(mtx_);
501 std::optional<index_t> cached = cache_.get_leaf_key_index(preimage_to_key(leaf));
502 if (cached.has_value()) {
503 // The is a cached value for the leaf
504 // We will return from here regardless
505 if (cached.value() >= start_index) {
506 return cached;
507 }
508 return std::nullopt;
509 }
510 }
511
512 // we have been asked to not include uncommitted data, or there is none available
513 index_t committed = 0;
514 FrKeyType key = leaf;
515 bool success = dataStore_->read_leaf_index(key, committed, tx);
516 if (success) {
517 // We must constrin the search to only the data committed from our perspective
518 // That means, if we are a fork, the committed size is the size of the tree as it was when we forked
519 // If we are the main tree, the committed size is the size of the tree as it is now
520 index_t sizeLimit = constrain_tree_size_to_only_committed(requestContext, tx);
521 if (committed < start_index) {
522 return std::nullopt;
523 }
524 if (committed >= sizeLimit) {
525 return std::nullopt;
526 }
527 return std::make_optional(committed);
528 }
529 return std::nullopt;
530}
531
532template <typename LeafValueType>
534{
535 // Accessing nodes_ under a lock
536 std::unique_lock lock(mtx_);
537 cache_.put_node(nodeHash, payload);
538}
539
540template <typename LeafValueType>
542 NodePayload& payload,
543 ReadTransaction& transaction,
544 bool includeUncommitted) const
545{
546 if (includeUncommitted) {
547 // Accessing nodes_ under a lock
548 std::unique_lock lock(mtx_);
549 if (cache_.get_node(nodeHash, payload)) {
550 return true;
551 }
552 }
553 return dataStore_->read_node(nodeHash, payload, transaction);
554}
555
556template <typename LeafValueType>
558 const index_t& index,
559 const fr& data,
560 bool overwriteIfPresent)
561{
562 // Accessing the cache under a lock
563 std::unique_lock lock(mtx_);
564 if (!overwriteIfPresent) {
565 std::optional<fr> cached = cache_.get_node_by_index(level, index);
566 if (cached.has_value()) {
567 return;
568 }
569 }
570 cache_.put_node_by_index(level, index, data);
571}
572
573template <typename LeafValueType>
575 const index_t& index,
576 fr& data) const
577{
578 // Accessing the cache under a lock
579 std::unique_lock lock(mtx_);
580 std::optional<fr> cached = cache_.get_node_by_index(level, index);
581 if (cached.has_value()) {
582 data = cached.value();
583 return true;
584 }
585 return false;
586}
587
588template <typename LeafValueType> void ContentAddressedCachedTreeStore<LeafValueType>::put_meta(const TreeMeta& m)
589{
590 // Accessing the cache under a lock
591 std::unique_lock lock(mtx_);
592 cache_.put_meta(m);
593}
594
595template <typename LeafValueType>
597 ReadTransaction& tx,
598 bool includeUncommitted) const
599{
600 if (includeUncommitted) {
601 get_meta(m);
602 return;
603 }
604 read_persisted_meta(m, tx);
605}
606
607template <typename LeafValueType> void ContentAddressedCachedTreeStore<LeafValueType>::get_meta(TreeMeta& m) const
608{
609 // Accessing meta_ under a lock
610 std::unique_lock lock(mtx_);
611 m = cache_.get_meta();
612}
613
614template <typename LeafValueType>
616 BlockPayload& blockData,
617 ReadTransaction& tx) const
618{
619 return dataStore_->read_block_data(blockNumber, blockData, tx);
620}
621
622template <typename LeafValueType>
624{
625 if (!dataStore_->read_meta_data(m, tx)) {
626 return false;
627 }
628 // Having read the meta from the store, we need to enrich it with the fork constant data if available
629 enrich_meta_from_fork_constant_data(m);
630 return true;
631}
632
633template <typename LeafValueType>
635{
636 // Here we update the given meta with properties from our constant fork data if available.
637 // If we are not a fork then nothing is to be updated
638 // If we are a fork then we will overwrite the root, size and committed size with the original fork values
639 if (forkConstantData_.initialized_from_block_.has_value()) {
640 m.size = forkConstantData_.initialized_from_block_->size;
641 m.committedSize = forkConstantData_.initialized_from_block_->size;
642 m.root = forkConstantData_.initialized_from_block_->root;
643 m.unfinalizedBlockHeight = forkConstantData_.initialized_from_block_->blockNumber;
644 }
645}
646
647template <typename LeafValueType>
649{
650 if (includeUncommitted) {
651 fr root = fr::zero();
652 if (get_cached_node_by_index(0, 0, root)) {
653 return root;
654 }
655 }
656 TreeMeta meta;
657 get_meta(meta, tx, includeUncommitted);
658 return meta.root;
659}
660
661// The following functions are related to either initialisation or committing data
662// It is assumed that when these operations are being executed, no other state accessing operations
663// are in progress, hence no data synchronisation is used.
664
665template <typename LeafValueType>
667{
668 const std::map<uint256_t, index_t>& indices = cache_.get_indices();
669 for (const auto& idx : indices) {
670 FrKeyType key = idx.first;
671 dataStore_->write_leaf_index(key, idx.second, tx);
672 }
673}
674
676{
677 // In this call, we will store any node/leaf data that has been created so far
678 bool dataPresent = false;
679 TreeMeta meta;
680 // We don't allow commits using images/forks
681 if (forkConstantData_.initialized_from_block_.has_value()) {
682 throw std::runtime_error("Committing a fork is forbidden");
683 }
684 get_meta(meta);
685 NodePayload rootPayload;
686 dataPresent = cache_.get_node(meta.root, rootPayload);
687 {
688 WriteTransactionPtr tx = create_write_transaction();
689 try {
690 if (dataPresent) {
691 persist_leaf_indices(*tx);
692 persist_node(std::optional<fr>(meta.root), 0, *tx);
693 }
694
695 meta.committedSize = meta.size;
696 persist_meta(meta, *tx);
697
698 // Persist a BlockPayload entry for block 0 so that genesis state can be queried as a first-class
699 // historical block. Without this, get_block_data(0) would fail and any historical read targeting block 0
700 // would throw. The payload captures the tree's initial root/size before any blocks have been committed.
701 BlockPayload genesisBlock{ .size = meta.initialSize, .blockNumber = 0, .root = meta.initialRoot };
702 dataStore_->write_block_data(0, genesisBlock, *tx);
703 dataStore_->write_block_index_data(0, meta.initialSize, *tx);
704
705 tx->commit();
706 } catch (std::exception& e) {
707 tx->try_abort();
708 throw std::runtime_error(
709 format("Unable to commit genesis data to tree: ", forkConstantData_.name_, " Error: ", e.what()));
710 }
711 }
712 // rolling back destroys all cache stores and also refreshes the cached meta_ from persisted state
713 rollback();
714}
715
716template <typename LeafValueType>
718{
719 bool dataPresent = false;
720 TreeMeta meta;
721
722 // We don't allow commits using images/forks
723 if (forkConstantData_.initialized_from_block_.has_value()) {
724 throw std::runtime_error("Committing a fork is forbidden");
725 }
726 get_meta(meta);
727 NodePayload rootPayload;
728 dataPresent = cache_.get_node(meta.root, rootPayload);
729 {
730 WriteTransactionPtr tx = create_write_transaction();
731 try {
732 if (dataPresent) {
733 // std::cout << "Persisting data for block " << uncommittedMeta.unfinalizedBlockHeight + 1 << std::endl;
734 // Persist the leaf indices
735 persist_leaf_indices(*tx);
736 }
737 // If we are commiting a block, we need to persist the root, since the new block "references" this root
738 // However, if the root is the empty root we can't persist it, since it's not a real node and doesn't have
739 // nodes beneath it. We coujld store a 'dummy' node to represent it but then we have to work around the
740 // absence of a real tree elsewhere. So, if the tree is completely empty we do not store any node data, the
741 // only issue is this needs to be recognised when we unwind or remove historic blocks i.e. there will be no
742 // node date to remove for these blocks
743 if (dataPresent || meta.size > 0) {
744 persist_node(std::optional<fr>(meta.root), 0, *tx);
745 }
747 if (meta.oldestHistoricBlock == 0) {
748 meta.oldestHistoricBlock = 1;
749 }
750 // std::cout << "New root " << uncommittedMeta.root << std::endl;
751 BlockPayload block{ .size = meta.size, .blockNumber = meta.unfinalizedBlockHeight, .root = meta.root };
752 dataStore_->write_block_data(meta.unfinalizedBlockHeight, block, *tx);
753 dataStore_->write_block_index_data(block.blockNumber, block.size, *tx);
754
755 meta.committedSize = meta.size;
756 persist_meta(meta, *tx);
757 tx->commit();
758 } catch (std::exception& e) {
759 tx->try_abort();
760 throw std::runtime_error(
761 format("Unable to commit data to tree: ", forkConstantData_.name_, " Error: ", e.what()));
762 }
763 }
764 finalMeta = meta;
765
766 // rolling back destroys all cache stores and also refreshes the cached meta_ from persisted state
767 rollback();
768
769 extract_db_stats(dbStats);
770}
771
772template <typename LeafValueType>
774{
775 try {
776 ReadTransactionPtr tx = create_read_transaction();
777 extract_db_stats(stats, *tx);
778 } catch (std::exception&) {
779 }
780}
781
782template <typename LeafValueType>
784{
785 try {
786 dataStore_->get_stats(stats, tx);
787 } catch (std::exception&) {
788 }
789}
790
791template <typename LeafValueType>
793 uint32_t level,
795{
796 struct StackObject {
797 std::optional<fr> opHash;
798 uint32_t lvl;
799 };
801 stack.push_back({ .opHash = optional_hash, .lvl = level });
802
803 while (!stack.empty()) {
804 StackObject so = stack.back();
805 stack.pop_back();
806
807 // If the optional hash does not have a value then it means it's the zero tree value at this level
808 // If it has a value but that value is not in our stores then it means it is referencing a node
809 // created in a previous block, so that will need to have it's reference count increased
810 if (!so.opHash.has_value()) {
811 continue;
812 }
813 fr hash = so.opHash.value();
814
815 if (so.lvl == forkConstantData_.depth_) {
816 // this is a leaf, we need to persist the pre-image
817 IndexedLeafValueType leafPreImage;
818 if (cache_.get_leaf_preimage_by_hash(hash, leafPreImage)) {
819 dataStore_->write_leaf_by_hash(hash, leafPreImage, tx);
820 }
821 }
822
823 // std::cout << "Persisting node hash " << hash << " at level " << so.lvl << std::endl;
824 NodePayload nodePayload;
825 if (!cache_.get_node(hash, nodePayload)) {
826 // need to increase the stored node's reference count here
827 dataStore_->increment_node_reference_count(hash, tx);
828 continue;
829 }
830
831 dataStore_->set_or_increment_node_reference_count(hash, nodePayload, tx);
832 if (nodePayload.ref != 1) {
833 // If the node now has a ref count greater then 1, we don't continue.
834 // It means that the entire sub-tree underneath already exists
835 continue;
836 }
837 stack.push_back({ .opHash = nodePayload.left, .lvl = so.lvl + 1 });
838 stack.push_back({ .opHash = nodePayload.right, .lvl = so.lvl + 1 });
839 }
840}
841
842template <typename LeafValueType> void ContentAddressedCachedTreeStore<LeafValueType>::rollback()
843{
844 // Extract the committed meta data and destroy the cache
845 cache_.reset(forkConstantData_.depth_);
846 {
847 ReadTransactionPtr tx = create_read_transaction();
848 TreeMeta committedMeta;
849 read_persisted_meta(committedMeta, *tx);
850 cache_.put_meta(committedMeta);
851 }
852}
853
854template <typename LeafValueType>
856{
857 dataStore_->write_meta_data(m, tx);
858}
859
860template <typename LeafValueType>
862{
863 TreeMeta committedMeta;
864 TreeMeta uncommittedMeta;
865 BlockPayload blockPayload;
866 if (blockNumber < 1) {
867 throw std::runtime_error(
868 format("Unable to advance finalized block: ", blockNumber, ". Tree name: ", forkConstantData_.name_));
869 }
870 if (forkConstantData_.initialized_from_block_.has_value()) {
871 throw std::runtime_error("Advancing the finalized block on a fork is forbidden");
872 }
873 {
874 // read both committed and uncommitted meta values
875 ReadTransactionPtr readTx = create_read_transaction();
876 get_meta(uncommittedMeta);
877 get_meta(committedMeta, *readTx, false);
878 // do nothing if the block is already finalized
879 if (committedMeta.finalizedBlockHeight >= blockNumber) {
880 return;
881 }
882 if (!dataStore_->read_block_data(blockNumber, blockPayload, *readTx)) {
883 throw std::runtime_error(format("Unable to advance finalized block: ",
884 blockNumber,
885 ". Failed to read block data. Tree name: ",
886 forkConstantData_.name_));
887 }
888 }
889
890 // can currently only finalize up to the unfinalized block height
891 if (committedMeta.finalizedBlockHeight > committedMeta.unfinalizedBlockHeight) {
892 throw std::runtime_error(format("Unable to finalize block ",
893 blockNumber,
894 " currently unfinalized block height ",
895 committedMeta.finalizedBlockHeight));
896 }
897
898 {
899 // commit the new finalized block
900 WriteTransactionPtr writeTx = create_write_transaction();
901 try {
902 committedMeta.finalizedBlockHeight = blockNumber;
903 // persist the new meta data
904 persist_meta(committedMeta, *writeTx);
905 writeTx->commit();
906 } catch (std::exception& e) {
907 writeTx->try_abort();
908 throw std::runtime_error(format("Unable to commit advance of finalized block: ",
909 blockNumber,
910 ". Tree name: ",
911 forkConstantData_.name_,
912 " Error: ",
913 e.what()));
914 }
915 }
916
917 // commit successful, now also update the uncommitted meta
918 uncommittedMeta.finalizedBlockHeight = committedMeta.finalizedBlockHeight;
919 put_meta(uncommittedMeta);
920}
921
922template <typename LeafValueType>
924 TreeMeta& finalMeta,
925 TreeDBStats& dbStats)
926{
927 TreeMeta uncommittedMeta;
928 TreeMeta committedMeta;
929 BlockPayload blockData;
930 BlockPayload previousBlockData;
931 if (blockNumber < 1) {
932 throw std::runtime_error(
933 format("Unable to unwind block: ", blockNumber, ". Tree name: ", forkConstantData_.name_));
934 }
935 if (forkConstantData_.initialized_from_block_.has_value()) {
936 throw std::runtime_error("Removing a block on a fork is forbidden");
937 }
938 {
939 ReadTransactionPtr readTx = create_read_transaction();
940 get_meta(uncommittedMeta);
941 get_meta(committedMeta, *readTx, false);
942 if (committedMeta != uncommittedMeta) {
943 throw std::runtime_error(
944 format("Unable to unwind block: ",
945 blockNumber,
946 " Can't unwind with uncommitted data, first rollback before unwinding. Tree name: ",
947 forkConstantData_.name_));
948 }
949 if (blockNumber > uncommittedMeta.unfinalizedBlockHeight) {
950 // Nothing to do, the block doesn't exist. Maybe it was already removed
951 finalMeta = uncommittedMeta;
952 extract_db_stats(dbStats, *readTx);
953 return;
954 }
955 if (blockNumber != uncommittedMeta.unfinalizedBlockHeight) {
956 throw std::runtime_error(format("Unable to unwind block: ",
957 blockNumber,
958 " unfinalizedBlockHeight: ",
959 committedMeta.unfinalizedBlockHeight,
960 ". Tree name: ",
961 forkConstantData_.name_));
962 }
963 if (blockNumber <= uncommittedMeta.finalizedBlockHeight) {
964 throw std::runtime_error(format("Unable to unwind block: ",
965 blockNumber,
966 " finalizedBlockHeight: ",
967 committedMeta.finalizedBlockHeight,
968 ". Tree name: ",
969 forkConstantData_.name_));
970 }
971
972 // populate the required data for the previous block
973 if (blockNumber == 1) {
974 previousBlockData.root = uncommittedMeta.initialRoot;
975 previousBlockData.size = uncommittedMeta.initialSize;
976 previousBlockData.blockNumber = 0;
977 } else if (!dataStore_->read_block_data(blockNumber - 1, previousBlockData, *readTx)) {
978 throw std::runtime_error(format("Unable to unwind block: ",
979 blockNumber,
980 ". Failed to read previous block data. Tree name: ",
981 forkConstantData_.name_));
982 }
983
984 // now get the root for the block we want to unwind
985 if (!dataStore_->read_block_data(blockNumber, blockData, *readTx)) {
986 throw std::runtime_error(format("Unable to unwind block: ",
987 blockNumber,
988 ". Failed to read block data. Tree name: ",
989 forkConstantData_.name_));
990 }
991 }
992
993 {
994 WriteTransactionPtr writeTx = create_write_transaction();
995 try {
996 // std::cout << "Removing block " << blockNumber << std::endl;
997
998 // If the tree was empty at the block being removed then we should not attempt to remove
999 // any nodes. (there were no nodes at the point this block was comitted)
1000 if (blockData.size > 0) {
1001 // Remove the block's node and leaf data given the max index of the previous block
1002 std::optional<index_t> maxIndex = std::optional<index_t>(previousBlockData.size);
1003 remove_node(std::optional<fr>(blockData.root), 0, maxIndex, *writeTx);
1004 }
1005 // remove the block from the block data table
1006 dataStore_->delete_block_data(blockNumber, *writeTx);
1007 dataStore_->delete_block_index(blockData.size, blockData.blockNumber, *writeTx);
1008 uncommittedMeta.unfinalizedBlockHeight = previousBlockData.blockNumber;
1009 uncommittedMeta.size = previousBlockData.size;
1010 uncommittedMeta.committedSize = previousBlockData.size;
1011 uncommittedMeta.root = previousBlockData.root;
1012 // std::cout << "New block root " << previousBlockData.root << std::endl;
1013 // commit this new meta data
1014 persist_meta(uncommittedMeta, *writeTx);
1015 writeTx->commit();
1016 } catch (std::exception& e) {
1017 writeTx->try_abort();
1018 throw std::runtime_error(format("Unable to commit unwind of block: ",
1019 blockNumber,
1020 ". Tree name: ",
1021 forkConstantData_.name_,
1022 " Error: ",
1023 e.what()));
1024 }
1025 }
1026
1027 // now update the uncommitted meta
1028 put_meta(uncommittedMeta);
1029 finalMeta = uncommittedMeta;
1030
1031 extract_db_stats(dbStats);
1032}
1033
1034template <typename LeafValueType>
1036 TreeMeta& finalMeta,
1037 TreeDBStats& dbStats)
1038{
1039 TreeMeta committedMeta;
1040 TreeMeta uncommittedMeta;
1041 BlockPayload blockData;
1042 if (blockNumber < 1) {
1043 throw std::runtime_error(
1044 format("Unable to remove historical block: ", blockNumber, ". Tree name: ", forkConstantData_.name_));
1045 }
1046 if (forkConstantData_.initialized_from_block_.has_value()) {
1047 throw std::runtime_error("Removing a block on a fork is forbidden");
1048 }
1049 {
1050 // retrieve both the committed and uncommitted meta data, validate the provide block is the oldest historical
1051 // block
1052 ReadTransactionPtr readTx = create_read_transaction();
1053 get_meta(uncommittedMeta);
1054 get_meta(committedMeta, *readTx, false);
1055 if (blockNumber < committedMeta.oldestHistoricBlock) {
1056 // Nothing to do, the block was probably already removed
1057 finalMeta = uncommittedMeta;
1058 extract_db_stats(dbStats, *readTx);
1059 return;
1060 }
1061 if (blockNumber != committedMeta.oldestHistoricBlock) {
1062 throw std::runtime_error(format("Unable to remove historical block: ",
1063 blockNumber,
1064 " oldestHistoricBlock: ",
1065 committedMeta.oldestHistoricBlock,
1066 ". Tree name: ",
1067 forkConstantData_.name_));
1068 }
1069 if (blockNumber >= committedMeta.finalizedBlockHeight) {
1070 throw std::runtime_error(format("Unable to remove historical block: ",
1071 blockNumber,
1072 " finalizedBlockHeight: ",
1073 committedMeta.finalizedBlockHeight,
1074 ". Tree name: ",
1075 forkConstantData_.name_));
1076 }
1077
1078 if (!dataStore_->read_block_data(blockNumber, blockData, *readTx)) {
1079 throw std::runtime_error(format("Unable to remove historical block: ",
1080 blockNumber,
1081 ". Failed to read block data. Tree name: ",
1082 forkConstantData_.name_));
1083 }
1084 }
1085 {
1086 WriteTransactionPtr writeTx = create_write_transaction();
1087 try {
1088 // If the tree was empty at the block being removed then we should not attempt to remove
1089 // any nodes. (there were no nodes at the point this block was comitted)
1090 if (blockData.size > 0) {
1091 // remove the historical block's node data
1093 remove_node(std::optional<fr>(blockData.root), 0, maxIndex, *writeTx);
1094 }
1095 // remove the block's entry in the block table
1096 dataStore_->delete_block_data(blockNumber, *writeTx);
1097 // increment the oldest historical block number as committed data
1098 committedMeta.oldestHistoricBlock++;
1099 persist_meta(committedMeta, *writeTx);
1100 writeTx->commit();
1101 } catch (std::exception& e) {
1102 writeTx->try_abort();
1103 throw std::runtime_error(format("Unable to commit removal of historical block: ",
1104 blockNumber,
1105 ". Tree name: ",
1106 forkConstantData_.name_,
1107 " Error: ",
1108 e.what()));
1109 }
1110 }
1111
1112 // commit was successful, update the uncommitted meta
1113 uncommittedMeta.oldestHistoricBlock = committedMeta.oldestHistoricBlock;
1114 put_meta(uncommittedMeta);
1115 finalMeta = uncommittedMeta;
1116
1117 extract_db_stats(dbStats);
1118}
1119
1120template <typename LeafValueType>
1122 const index_t& maxIndex,
1123 WriteTransaction& tx)
1124{
1125 // We now have the key, extract the index
1126 index_t index = 0;
1127 // std::cout << "Reading index for key " << key << std::endl;
1128 if (dataStore_->read_leaf_index(key, index, tx)) {
1129 if (index >= maxIndex) {
1130 // std::cout << "Deleting index" << std::endl;
1131 dataStore_->delete_leaf_index(key, tx);
1132 }
1133 }
1134}
1135
1136template <typename LeafValueType>
1138 const std::optional<index_t>& maxIndex,
1139 WriteTransaction& tx)
1140{
1141 // std::cout << "Removing leaf " << hash << std::endl;
1142 if (maxIndex.has_value()) {
1143 // std::cout << "Max Index" << std::endl;
1144 // We need to clear the entry from the leaf key to index database as this leaf never existed
1145 IndexedLeafValueType leaf_preimage;
1146 fr key;
1147 if constexpr (requires_preimage_for_key<LeafValueType>()) {
1148 // std::cout << "Reading leaf by hash " << hash << std::endl;
1149 if (!dataStore_->read_leaf_by_hash(hash, leaf_preimage, tx)) {
1150 throw std::runtime_error("Failed to find leaf pre-image when attempting to delete indices");
1151 }
1152 // std::cout << "Read leaf by hash " << hash << std::endl;
1153 key = preimage_to_key(leaf_preimage.leaf);
1154 } else {
1155 key = hash;
1156 }
1157 remove_leaf_index(key, maxIndex.value(), tx);
1158 }
1159 // std::cout << "Deleting leaf by hash " << std::endl;
1160 dataStore_->delete_leaf_by_hash(hash, tx);
1161}
1162
1163template <typename LeafValueType>
1165 uint32_t level,
1166 const std::optional<index_t>& maxIndex,
1167 WriteTransaction& tx)
1168{
1169 struct StackObject {
1170 std::optional<fr> opHash;
1171 uint32_t lvl;
1172 };
1174 stack.push_back({ .opHash = optional_hash, .lvl = level });
1175
1176 while (!stack.empty()) {
1177 StackObject so = stack.back();
1178 stack.pop_back();
1179
1180 if (!so.opHash.has_value()) {
1181 continue;
1182 }
1183 fr hash = so.opHash.value();
1184 // we need to retrieve the node and decrement it's reference count
1185 // std::cout << "Decrementing ref count for node " << hash << ", level " << so.lvl << std::endl;
1186 NodePayload nodeData;
1187 dataStore_->decrement_node_reference_count(hash, nodeData, tx);
1188
1189 if (nodeData.ref != 0) {
1190 // node was not deleted, we don't continue the search
1191 continue;
1192 }
1193 // the node was deleted, if it was a leaf then we need to remove the pre-image
1194 if (so.lvl == forkConstantData_.depth_) {
1195 remove_leaf(hash, maxIndex, tx);
1196 }
1197 // push the child nodes to the stack
1198 stack.push_back({ .opHash = std::optional<fr>(nodeData.left), .lvl = so.lvl + 1 });
1199 stack.push_back({ .opHash = std::optional<fr>(nodeData.right), .lvl = so.lvl + 1 });
1200 }
1201}
1202
1204{
1205 // Read the persisted meta data, if the name or depth of the tree is not consistent with what was provided during
1206 // construction then we throw
1207 TreeMeta meta;
1208 {
1209 ReadTransactionPtr tx = create_read_transaction();
1210 bool success = read_persisted_meta(meta, *tx);
1211 if (success) {
1212 if (forkConstantData_.name_ == meta.name && forkConstantData_.depth_ == meta.depth) {
1213 cache_.put_meta(meta);
1214 return;
1215 }
1216 throw std::runtime_error(
1217 format("Tree found to be uninitialized when attempting to create ", forkConstantData_.name_));
1218 }
1219 }
1220
1221 // No meta data available. Write the initial state down
1222 meta.name = forkConstantData_.name_;
1223 meta.size = 0;
1224 meta.committedSize = 0;
1225 meta.root = fr::zero();
1226 meta.initialRoot = fr::zero();
1227 meta.depth = forkConstantData_.depth_;
1228 meta.initialSize = 0;
1229 meta.oldestHistoricBlock = 0;
1230 meta.unfinalizedBlockHeight = 0;
1231 meta.finalizedBlockHeight = 0;
1232 WriteTransactionPtr tx = create_write_transaction();
1233 try {
1234 persist_meta(meta, *tx);
1235 tx->commit();
1236 } catch (std::exception& e) {
1237 tx->try_abort();
1238 throw e;
1239 }
1240 cache_.put_meta(meta);
1241}
1242
1243template <typename LeafValueType>
1245{
1246 // Read the persisted meta data, if the name or depth of the tree is not consistent with what was provided during
1247 // construction then we throw
1248 {
1249 ReadTransactionPtr tx = create_read_transaction();
1250 TreeMeta meta;
1251 bool success = read_persisted_meta(meta, *tx);
1252 if (success) {
1253 if (forkConstantData_.name_ != meta.name || forkConstantData_.depth_ != meta.depth) {
1254 throw std::runtime_error(format("Inconsistent tree meta data when initializing ",
1255 forkConstantData_.name_,
1256 " with depth ",
1257 forkConstantData_.depth_,
1258 " from block ",
1259 blockNumber,
1260 " stored name: ",
1261 meta.name,
1262 "stored depth: ",
1263 meta.depth));
1264 }
1265
1266 } else {
1267 throw std::runtime_error(format("Tree found to be uninitialized when attempting to create ",
1268 forkConstantData_.name_,
1269 " from block ",
1270 blockNumber));
1271 }
1272
1273 if (meta.unfinalizedBlockHeight < blockNumber) {
1274 throw std::runtime_error(format("Unable to initialize from future block: ",
1275 blockNumber,
1276 " unfinalizedBlockHeight: ",
1278 ". Tree name: ",
1279 forkConstantData_.name_));
1280 }
1281 if (meta.oldestHistoricBlock > blockNumber && blockNumber != 0) {
1282 throw std::runtime_error(format("Unable to fork from expired historical block: ",
1283 blockNumber,
1284 " unfinalizedBlockHeight: ",
1286 ". Tree name: ",
1287 forkConstantData_.name_));
1288 }
1289 BlockPayload blockData;
1290 if (blockNumber == 0) {
1291 blockData.blockNumber = 0;
1292 blockData.root = meta.initialRoot;
1293 blockData.size = meta.initialSize;
1294 } else if (get_block_data(blockNumber, blockData, *tx) == false) {
1295 throw std::runtime_error(
1296 format("Failed to retrieve block data: ", blockNumber, ". Tree name: ", forkConstantData_.name_));
1297 }
1298 forkConstantData_.initialized_from_block_ = blockData;
1299 // Ensure the meta reflects the fork constant data
1300 enrich_meta_from_fork_constant_data(meta);
1301 cache_.put_meta(meta);
1302 }
1303}
1304
1305} // namespace bb::crypto::merkle_tree
Serves as a key-value node store for merkle trees. Caches all changes in memory before persisting the...
ContentAddressedCachedTreeStore(std::string name, uint32_t levels, const block_number_t &referenceBlockNumber, PersistedStoreType::SharedPtr dataStore)
std::optional< IndexedLeafValueType > get_cached_leaf_by_index(const index_t &index) const
void persist_node(const std::optional< fr > &optional_hash, uint32_t level, WriteTransaction &tx)
void commit_block(TreeMeta &finalMeta, TreeDBStats &dbStats)
Commits the uncommitted data to the underlying store.
bool get_cached_node_by_index(uint32_t level, const index_t &index, fr &data) const
Returns the data at the given node coordinates if available.
void put_cached_node_by_index(uint32_t level, const index_t &index, const fr &data, bool overwriteIfPresent=true)
Writes the provided data at the given node coordinates. Only writes to uncommitted data.
std::optional< IndexedLeafValueType > get_leaf(const index_t &index, ReadTransaction &tx, bool includeUncommitted) const
Returns the leaf at the provided index, if one exists.
ContentAddressedCachedTreeStore & operator=(ContentAddressedCachedTreeStore const &&other)=delete
std::optional< index_t > find_leaf_index(const LeafValueType &leaf, const RequestContext &requestContext, ReadTransaction &tx) const
Finds the index of the given leaf value in the tree if available. Includes uncommitted data if reques...
std::optional< IndexedLeafValueType > get_leaf_by_hash(const fr &leaf_hash, ReadTransaction &tx, bool includeUncommitted) const
void put_node_by_hash(const fr &nodeHash, const NodePayload &payload)
Writes the provided data at the given node coordinates. Only writes to uncommitted data.
ContentAddressedCachedTreeStore(ContentAddressedCachedTreeStore const &other)=delete
void remove_leaf(const fr &hash, const std::optional< index_t > &maxIndex, WriteTransaction &tx)
void remove_historical_block(const block_number_t &blockNumber, TreeMeta &finalMeta, TreeDBStats &dbStats)
void commit_genesis_state()
Commits the initial state of uncommitted data to the underlying store.
bool get_node_by_hash(const fr &nodeHash, NodePayload &payload, ReadTransaction &transaction, bool includeUncommitted) const
Returns the data at the given node coordinates if available. Reads from uncommitted state if requeste...
void remove_node(const std::optional< fr > &optional_hash, uint32_t level, const std::optional< index_t > &maxIndex, WriteTransaction &tx)
void put_leaf_by_hash(const fr &leaf_hash, const IndexedLeafValueType &leafPreImage)
void set_leaf_key_at_index(const index_t &index, const IndexedLeafValueType &leaf)
Adds the leaf at the given index, updates the leaf index if requested.
ReadTransactionPtr create_read_transaction() const
Returns a read transaction against the underlying store.
void delete_block_for_index(const block_number_t &blockNumber, const index_t &index, WriteTransaction &tx)
ContentAddressedCachedTreeStore(ContentAddressedCachedTreeStore const &&other)=delete
void put_cached_leaf_by_index(const index_t &index, const IndexedLeafValueType &leafPreImage)
void remove_leaf_index(const fr &key, const index_t &maxIndex, WriteTransaction &tx)
void get_meta(TreeMeta &m) const
Reads the uncommitted tree meta data.
void persist_block_for_index(const block_number_t &blockNumber, const index_t &index, WriteTransaction &tx)
bool get_block_data(const block_number_t &blockNumber, BlockPayload &blockData, ReadTransaction &tx) const
Reads the tree meta data, including uncommitted data if requested.
void unwind_block(const block_number_t &blockNumber, TreeMeta &finalMeta, TreeDBStats &dbStats)
ContentAddressedCachedTreeStore & operator=(ContentAddressedCachedTreeStore const &other)=delete
void update_index(const index_t &index, const fr &leaf)
Updates the leaf index.
std::optional< block_number_t > find_block_for_index(const index_t &index, ReadTransaction &tx) const
void get_meta(TreeMeta &m, ReadTransaction &tx, bool includeUncommitted) const
Reads the tree meta data, including uncommitted data if requested.
fr get_current_root(ReadTransaction &tx, bool includeUncommitted) const
ContentAddressedCachedTreeStore(std::string name, uint32_t levels, PersistedStoreType::SharedPtr dataStore)
std::pair< bool, index_t > find_low_value(const fr &new_leaf_key, const RequestContext &requestContext, ReadTransaction &tx) const
Returns the index of the leaf with a value immediately lower than the value provided.
std::optional< index_t > find_leaf_index_from(const LeafValueType &leaf, const index_t &start_index, const RequestContext &requestContext, ReadTransaction &tx) const
Finds the index of the given leaf value in the tree if available. Includes uncommitted data if reques...
void put_meta(const TreeMeta &m)
Writes the provided meta data to uncommitted state.
index_t constrain_tree_size_to_only_committed(const RequestContext &requestContext, ReadTransaction &tx) const
std::shared_ptr< LMDBTreeStore > SharedPtr
std::string format(Args... args)
Definition log.hpp:23
const std::vector< MemoryValue > data
StrictMock< MockContext > context
PublicDataLeafValue LeafValueType
uint32_t block_number_t
Definition types.hpp:19
fr preimage_to_key(const LeafType &leaf)
Definition types.hpp:32
STL namespace.
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
std::optional< index_t > maxIndex
Definition types.hpp:29
static constexpr field zero()