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