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