Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
content_addressed_indexed_tree.test.cpp
Go to the documentation of this file.
2#include "../fixtures.hpp"
3#include "../hash.hpp"
4#include "../node_store/array_store.hpp"
5#include "../nullifier_tree/nullifier_memory_tree.hpp"
6#include "../test_fixtures.hpp"
17#include <algorithm>
18#include <atomic>
19#include <chrono>
20#include <cstdint>
21#include <filesystem>
22#include <future>
23#include <memory>
24#include <optional>
25#include <stdexcept>
26#include <vector>
27
29template <typename Store, typename HashingPolicy> struct ContentAddressedIndexedTreeTestAccess {
31 using LeafUpdate = typename Tree::LeafUpdate;
34
36 const index_t& highest_index,
37 std::shared_ptr<std::vector<LeafUpdate>> updates,
38 const UpdatesCompletionCallback& completion)
39 {
40 tree.perform_updates_without_witness(highest_index, std::move(updates), completion);
41 }
42};
43} // namespace bb::crypto::merkle_tree
44
45using namespace bb;
46using namespace bb::crypto::merkle_tree;
47
49
52
55
58
61
62// Throws on the Nth get_current_root call (fail_on_call). The tree dispatches on the concrete store type
63// at compile time, so this non-virtual override is what it actually calls.
64class FailingRootStore : public Store {
65 public:
66 using Store::Store;
67
68 mutable std::atomic<uint32_t> root_call_count{ 0 };
69 std::atomic<uint32_t> fail_on_call{ 0 }; // 0 disables injection
70
71 fr get_current_root(ReadTransaction& tx, bool includeUncommitted) const
72 {
73 uint32_t call = ++root_call_count;
74 if (fail_on_call != 0 && call >= fail_on_call) {
75 throw std::runtime_error("injected get_current_root failure");
76 }
77 return Store::get_current_root(tx, includeUncommitted);
78 }
79};
81
82inline IndexedNullifierLeafType create_indexed_nullifier_leaf(const fr& value, index_t nextIndex, const fr& nextValue)
83{
84 return IndexedNullifierLeafType{ NullifierLeafValue(value), nextIndex, nextValue };
85}
86
88 const fr& value,
89 index_t nextIndex,
90 const fr& nextValue)
91{
92 return IndexedPublicDataLeafType{ PublicDataLeafValue(slot, value), nextIndex, nextValue };
93}
94
95class PersistedContentAddressedIndexedTreeTest : public testing::Test {
96 protected:
97 void SetUp() override
98 {
100 _mapSize = 1024 * 1024;
101 _maxReaders = 16;
102 std::filesystem::create_directories(_directory);
103 }
104
105 void TearDown() override { std::filesystem::remove_all(_directory); }
106
107 static std::string _directory;
108 static uint64_t _maxReaders;
109 static uint64_t _mapSize;
110};
111
115
116std::unique_ptr<TreeType> create_tree(const std::string& rootDirectory,
117 uint64_t mapSize,
118 uint64_t maxReaders,
119 uint32_t depth,
120 uint32_t batchSize,
121 ThreadPoolPtr workers)
122{
123 std::string name = random_string();
124 std::filesystem::path directory = rootDirectory;
125 directory.append(name);
126 std::filesystem::create_directories(directory);
127 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(directory, name, mapSize, maxReaders);
129 return std::make_unique<TreeType>(std::move(store), workers, batchSize);
130}
131
132template <typename TypeOfTree> void check_size(TypeOfTree& tree, index_t expected_size, bool includeUncommitted = true)
133{
134 Signal signal;
135 auto completion = [&](const TypedResponse<TreeMetaResponse>& response) -> void {
136 EXPECT_EQ(response.success, true);
137 EXPECT_EQ(response.inner.meta.size, expected_size);
138 signal.signal_level();
139 };
140 tree.get_meta_data(includeUncommitted, completion);
141 signal.wait_for_level();
142}
143
144template <typename TypeOfTree> fr get_root(TypeOfTree& tree, bool includeUncommitted = true)
145{
146 fr r;
147 Signal signal;
148 auto completion = [&](const TypedResponse<TreeMetaResponse>& response) -> void {
149 r = response.inner.meta.root;
150 signal.signal_level();
151 };
152 tree.get_meta_data(includeUncommitted, completion);
153 signal.wait_for_level();
154 return r;
155}
156
157template <typename TypeOfTree> void check_root(TypeOfTree& tree, fr expected_root, bool includeUncommitted = true)
158{
159 fr root = get_root(tree, includeUncommitted);
160 EXPECT_EQ(root, expected_root);
161}
162
163template <typename TypeOfTree>
165 block_number_t blockNumber,
167 bool includeUncommitted = true,
168 bool expected_success = true)
169{
171 Signal signal;
172 auto completion = [&](const TypedResponse<GetSiblingPathResponse>& response) -> void {
173 EXPECT_EQ(response.success, expected_success);
174 if (response.success) {
175 h = response.inner.path;
176 }
177 signal.signal_level();
178 };
179 tree.get_sibling_path(index, blockNumber, completion, includeUncommitted);
180 signal.wait_for_level();
181 return h;
182}
183
184template <typename LeafValueType, typename TypeOfTree>
187 bool includeUncommitted = true,
188 bool expected_success = true)
189{
191 Signal signal;
192 auto completion = [&](const TypedResponse<GetIndexedLeafResponse<LeafValueType>>& leaf) -> void {
193 EXPECT_EQ(leaf.success, expected_success);
194 if (leaf.success) {
195 l = leaf.inner.indexed_leaf;
196 }
197 signal.signal_level();
198 };
199 tree.get_leaf(index, includeUncommitted, completion);
200 signal.wait_for_level();
201 return l.has_value() ? l.value() : IndexedLeaf<LeafValueType>();
202}
203
204template <typename LeafValueType, typename TypeOfTree>
205GetLowIndexedLeafResponse get_low_leaf(TypeOfTree& tree, const LeafValueType& leaf, bool includeUncommitted = true)
206{
207 GetLowIndexedLeafResponse low_leaf_info;
208 Signal signal;
209 auto completion = [&](const auto& leaf) -> void {
210 low_leaf_info = leaf.inner;
211 signal.signal_level();
212 };
213 tree.find_low_leaf(leaf.get_key(), includeUncommitted, completion);
214 signal.wait_for_level();
215 return low_leaf_info;
216}
217
218template <typename LeafValueType, typename TypeOfTree>
220 block_number_t blockNumber,
221 const LeafValueType& leaf,
222 bool includeUncommitted = true)
223{
224 GetLowIndexedLeafResponse low_leaf_info;
225 Signal signal;
226 auto completion = [&](const auto& leaf) -> void {
227 low_leaf_info = leaf.inner;
228 signal.signal_level();
229 };
230 tree.find_low_leaf(leaf.get_key(), blockNumber, includeUncommitted, completion);
231 signal.wait_for_level();
232 return low_leaf_info;
233}
234
235template <typename LeafValueType, typename TypeOfTree>
236void check_historic_leaf(TypeOfTree& tree,
237 const LeafValueType& leaf,
238 index_t expected_index,
239 block_number_t blockNumber,
240 bool expected_success,
241 bool includeUncommitted = true)
242{
243 Signal signal;
244 auto completion = [&](const TypedResponse<GetIndexedLeafResponse<LeafValueType>>& response) -> void {
245 EXPECT_EQ(response.success, expected_success);
246 if (response.success) {
247 EXPECT_EQ(response.inner.indexed_leaf.value().leaf, leaf);
248 }
249 signal.signal_level();
250 };
251
252 tree.get_leaf(expected_index, blockNumber, includeUncommitted, completion);
253 signal.wait_for_level();
254}
255
256template <typename TypeOfTree>
257void check_historic_sibling_path(TypeOfTree& tree,
259 block_number_t blockNumber,
260 const fr_sibling_path& expected_sibling_path,
261 bool includeUncommitted = true,
262 bool expected_success = true)
263{
264 fr_sibling_path path = get_historic_sibling_path(tree, blockNumber, index, includeUncommitted, expected_success);
265 if (expected_success) {
266 EXPECT_EQ(path, expected_sibling_path);
267 }
268}
269
270template <typename TypeOfTree>
271void check_sibling_path(TypeOfTree& tree,
273 const fr_sibling_path& expected_sibling_path,
274 bool includeUncommitted = true,
275 bool expected_success = true)
276{
277 fr_sibling_path path = get_sibling_path(tree, index, includeUncommitted, expected_success);
278 EXPECT_EQ(path, expected_sibling_path);
279}
280
281template <typename TypeOfTree> void check_unfinalized_block_height(TypeOfTree& tree, index_t expected_block_height)
282{
283 Signal signal;
284 auto completion = [&](const TypedResponse<TreeMetaResponse>& response) -> void {
285 EXPECT_EQ(response.success, true);
286 EXPECT_EQ(response.inner.meta.unfinalizedBlockHeight, expected_block_height);
287 signal.signal_level();
288 };
289 tree.get_meta_data(true, completion);
290 signal.wait_for_level();
291}
292
293template <typename TypeOfTree> void commit_tree(TypeOfTree& tree, bool expectedSuccess = true)
294{
295 Signal signal;
296 auto completion = [&](const TypedResponse<CommitResponse>& response) -> void {
297 EXPECT_EQ(response.success, expectedSuccess);
298 signal.signal_level();
299 };
300 tree.commit(completion);
301 signal.wait_for_level();
302}
303
304template <typename LeafValueType, typename TypeOfTree>
305void add_value(TypeOfTree& tree, const LeafValueType& value, bool expectedSuccess = true)
306{
307 Signal signal;
308 auto completion = [&](const TypedResponse<AddIndexedDataResponse<LeafValueType>>& response) -> void {
309 EXPECT_EQ(response.success, expectedSuccess);
310 signal.signal_level();
311 };
312
313 tree.add_or_update_value(value, completion);
314 signal.wait_for_level();
315}
316
317template <typename LeafValueType, typename TypeOfTree>
318void add_value_sequentially(TypeOfTree& tree, const LeafValueType& value, bool expectedSuccess = true)
319{
321 Signal signal;
322 auto completion = [&](const TypedResponse<AddIndexedDataSequentiallyResponse<LeafValueType>>& response) -> void {
323 EXPECT_EQ(response.success, expectedSuccess);
324 signal.signal_level();
325 };
326
327 tree.add_or_update_values_sequentially(values, completion);
328 signal.wait_for_level();
329}
330
331template <typename LeafValueType, typename TypeOfTree>
332void add_values(TypeOfTree& tree, const std::vector<LeafValueType>& values, bool expectedSuccess = true)
333{
334 Signal signal;
335 auto completion = [&](const TypedResponse<AddIndexedDataResponse<LeafValueType>>& response) -> void {
336 EXPECT_EQ(response.success, expectedSuccess);
337 signal.signal_level();
338 };
339
340 tree.add_or_update_values(values, completion);
341 signal.wait_for_level();
342}
343
344template <typename LeafValueType, typename TypeOfTree>
345void add_values_sequentially(TypeOfTree& tree, const std::vector<LeafValueType>& values, bool expectedSuccess = true)
346{
347 Signal signal;
348 auto completion = [&](const TypedResponse<AddIndexedDataSequentiallyResponse<LeafValueType>>& response) -> void {
349 EXPECT_EQ(response.success, expectedSuccess);
350 signal.signal_level();
351 };
352
353 tree.add_or_update_values_sequentially(values, completion);
354 signal.wait_for_level();
355}
356
357template <typename LeafValueType, typename TypeOfTree>
358void block_sync_values(TypeOfTree& tree, const std::vector<LeafValueType>& values, bool expectedSuccess = true)
359{
360 Signal signal;
361 auto completion = [&](const TypedResponse<AddDataResponse>& response) -> void {
362 EXPECT_EQ(response.success, expectedSuccess);
363 signal.signal_level();
364 };
365
366 tree.add_or_update_values(values, completion);
367 signal.wait_for_level();
368}
369
370template <typename LeafValueType, typename TypeOfTree>
371void block_sync_values_sequential(TypeOfTree& tree,
372 const std::vector<LeafValueType>& values,
373 bool expectedSuccess = true)
374{
375 Signal signal;
376 auto completion = [&](const TypedResponse<AddDataResponse>& response) -> void {
377 EXPECT_EQ(response.success, expectedSuccess);
378 signal.signal_level();
379 };
380
381 tree.add_or_update_values_sequentially(values, completion);
382 signal.wait_for_level();
383}
384
385template <typename TypeOfTree>
386void remove_historic_block(TypeOfTree& tree, const block_number_t& blockNumber, bool expected_success = true)
387{
388 Signal signal;
389 auto completion = [&](const TypedResponse<RemoveHistoricResponse>& response) -> void {
390 EXPECT_EQ(response.success, expected_success);
391 signal.signal_level();
392 };
393 tree.remove_historic_block(blockNumber, completion);
394 signal.wait_for_level();
395}
396
397template <typename TypeOfTree>
398void finalize_block(TypeOfTree& tree, const block_number_t& blockNumber, bool expected_success = true)
399{
400 Signal signal;
401 auto completion = [&](const Response& response) -> void {
402 EXPECT_EQ(response.success, expected_success);
403 signal.signal_level();
404 };
405 tree.finalize_block(blockNumber, completion);
406 signal.wait_for_level();
407}
408
409template <typename TypeOfTree>
410void unwind_block(TypeOfTree& tree, const block_number_t& blockNumber, bool expected_success = true)
411{
412 Signal signal;
413 auto completion = [&](const TypedResponse<UnwindResponse>& response) -> void {
414 EXPECT_EQ(response.success, expected_success);
415 signal.signal_level();
416 };
417 tree.unwind_block(blockNumber, completion);
418 signal.wait_for_level();
419}
420
421template <typename TypeOfTree> void check_block_height(TypeOfTree& tree, index_t expected_block_height)
422{
423 Signal signal;
424 auto completion = [&](const TypedResponse<TreeMetaResponse>& response) -> void {
425 EXPECT_EQ(response.success, true);
426 EXPECT_EQ(response.inner.meta.unfinalizedBlockHeight, expected_block_height);
427 signal.signal_level();
428 };
429 tree.get_meta_data(true, completion);
430 signal.wait_for_level();
431}
432
434{
435 constexpr size_t depth = 10;
436 std::string name = random_string();
437 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
438 EXPECT_NO_THROW(std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db));
440 ThreadPoolPtr workers = make_thread_pool(1);
441 TreeType tree = TreeType(std::move(store), workers, 2);
442 check_size(tree, 2);
443
445 check_root(tree, memdb.root());
446}
447
448TEST_F(PersistedContentAddressedIndexedTreeTest, can_only_recreate_with_same_name_and_depth)
449{
450 constexpr size_t depth = 10;
451 std::string name = random_string();
452 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
454
455 EXPECT_ANY_THROW(Store("Wrong name", depth, db));
456 EXPECT_ANY_THROW(Store(name, depth + 1, db));
457}
458
459// A failing subtree sibling path read during a witnessed insertion must still invoke the completion
460// callback (with success=false) rather than dropping it and deadlocking the caller.
461TEST_F(PersistedContentAddressedIndexedTreeTest, reports_failure_when_subtree_sibling_path_read_fails)
462{
463 constexpr size_t depth = 10;
464 std::string name = random_string();
465 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
466 auto store = std::make_unique<FailingRootStore>(name, depth, db);
467 FailingRootStore* store_ptr = store.get();
468 ThreadPoolPtr workers = make_thread_pool(1);
469 FailingRootTreeType tree(std::move(store), workers, 2);
470
471 // Ignore genesis reads; pass generate_insertions (call 1) and fail get_subtree_sibling_path (call 2).
472 store_ptr->root_call_count = 0;
473 store_ptr->fail_on_call = 2;
474
475 std::promise<bool> completed;
476 std::future<bool> completed_future = completed.get_future();
477 auto completion = [&](const TypedResponse<AddIndexedDataResponse<NullifierLeafValue>>& response) -> void {
478 completed.set_value(response.success);
479 };
481
482 // Bounded wait so a regression fails the test
483 ASSERT_EQ(completed_future.wait_for(std::chrono::seconds(60)), std::future_status::ready);
484 EXPECT_FALSE(completed_future.get());
485}
486
488{
489 index_t current_size = 2;
490 ThreadPoolPtr workers = make_thread_pool(1);
491 constexpr size_t depth = 10;
492 std::string name = random_string();
493 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
495 auto tree = TreeType(std::move(store), workers, current_size);
496
497 check_size(tree, current_size);
498
499 // We assume that the first leaf is already filled with (0, 0, 0).
500 for (uint32_t i = 0; i < 4; i++) {
502 check_size(tree, ++current_size);
503 }
504}
505
506TEST_F(PersistedContentAddressedIndexedTreeTest, indexed_tree_must_have_at_least_2_initial_size)
507{
508 index_t current_size = 1;
509 ThreadPoolPtr workers = make_thread_pool(1);
510 ;
511 constexpr size_t depth = 10;
512 std::string name = random_string();
513 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
515 EXPECT_THROW(TreeType(std::move(store), workers, current_size), std::runtime_error);
516}
517
518TEST_F(PersistedContentAddressedIndexedTreeTest, reports_an_error_if_tree_is_overfilled)
519{
520 index_t current_size = 2;
521 ThreadPoolPtr workers = make_thread_pool(1);
522 ;
523 constexpr size_t depth = 4;
524 std::string name = random_string();
525 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
527 auto tree = TreeType(std::move(store), workers, current_size);
528
530 for (uint32_t i = 0; i < 14; i++) {
531 values.emplace_back(get_value(i));
532 }
533 add_values(tree, values);
534
535 std::stringstream ss;
536 ss << "Unable to insert values into tree " << name << " new size: 17 max size: 16";
537
538 Signal signal;
539 auto add_completion = [&](const TypedResponse<AddIndexedDataResponse<NullifierLeafValue>>& response) {
540 EXPECT_EQ(response.success, false);
541 EXPECT_EQ(response.message, ss.str());
542 signal.signal_level();
543 };
544 tree.add_or_update_value(NullifierLeafValue(get_value(16)), add_completion);
545 signal.wait_for_level();
546}
547
549{
550 constexpr size_t depth = 10;
551 index_t current_size = 2;
552 NullifierMemoryTree<HashPolicy> memdb(depth, current_size);
553
554 ThreadPoolPtr workers = make_thread_pool(1);
555
556 std::string name = random_string();
557 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
559 auto tree = TreeType(std::move(store), workers, current_size);
560
561 check_size(tree, current_size);
562 check_root(tree, memdb.root());
563 check_sibling_path(tree, 0, memdb.get_sibling_path(0));
564
565 memdb.update_element(get_value(1000));
567
568 check_size(tree, ++current_size);
569 check_sibling_path(tree, 0, memdb.get_sibling_path(0));
570 check_sibling_path(tree, 1, memdb.get_sibling_path(1));
571
572 memdb.update_element(get_value(1001));
574
575 check_size(tree, ++current_size);
576 check_sibling_path(tree, 0, memdb.get_sibling_path(0));
577 check_sibling_path(tree, 1, memdb.get_sibling_path(1));
578
579 uint32_t num_to_append = 512;
580
581 for (uint32_t i = 0; i < num_to_append; i += 2) {
582 memdb.update_element(get_value(i));
583 memdb.update_element(get_value(i + 1));
584 add_values<NullifierLeafValue>(tree,
586 }
587 check_size(tree, num_to_append + current_size);
588 check_sibling_path(tree, 0, memdb.get_sibling_path(0));
589 check_sibling_path(tree, 512, memdb.get_sibling_path(512));
590}
591
593{
594 index_t initial_size = 2;
595 ThreadPoolPtr workers = make_thread_pool(1);
596 ;
597 constexpr size_t depth = 10;
598 std::string name = random_string();
599 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
601 auto tree = TreeType(std::move(store), workers, initial_size);
602
603 add_value(tree, NullifierLeafValue(30));
604 add_value(tree, NullifierLeafValue(10));
605 add_value(tree, NullifierLeafValue(20));
606 add_value(tree, NullifierLeafValue(40));
607
608 // check the committed state and that the uncommitted state is empty
609 check_find_leaf_index(tree, NullifierLeafValue(10), 1 + initial_size, true, true);
610 check_find_leaf_index<NullifierLeafValue, TreeType>(
611 tree, { NullifierLeafValue(10) }, { std::nullopt }, true, false);
612
613 check_find_leaf_index<NullifierLeafValue, TreeType>(tree, { NullifierLeafValue(15) }, { std::nullopt }, true, true);
614 check_find_leaf_index<NullifierLeafValue, TreeType>(
615 tree, { NullifierLeafValue(15) }, { std::nullopt }, true, false);
616
617 check_find_leaf_index(tree, NullifierLeafValue(40), 3 + initial_size, true, true);
618 check_find_leaf_index(tree, NullifierLeafValue(30), 0 + initial_size, true, true);
619 check_find_leaf_index(tree, NullifierLeafValue(20), 2 + initial_size, true, true);
620
621 check_find_leaf_index<NullifierLeafValue, TreeType>(
622 tree, { NullifierLeafValue(40) }, { std::nullopt }, true, false);
623 check_find_leaf_index<NullifierLeafValue, TreeType>(
624 tree, { NullifierLeafValue(30) }, { std::nullopt }, true, false);
625 check_find_leaf_index<NullifierLeafValue, TreeType>(
626 tree, { NullifierLeafValue(20) }, { std::nullopt }, true, false);
627
628 commit_tree(tree);
629
634 NullifierLeafValue(48) };
635 add_values(tree, values);
636
637 // check the now committed state
638 check_find_leaf_index(tree, NullifierLeafValue(40), 3 + initial_size, true, false);
639 check_find_leaf_index(tree, NullifierLeafValue(30), 0 + initial_size, true, false);
640 check_find_leaf_index(tree, NullifierLeafValue(20), 2 + initial_size, true, false);
641
642 // check the new uncommitted state
643 check_find_leaf_index(tree, NullifierLeafValue(18), 5 + initial_size, true, true);
644 check_find_leaf_index<NullifierLeafValue, TreeType>(
645 tree, { NullifierLeafValue(18) }, { std::nullopt }, true, false);
646
647 commit_tree(tree);
648
650 add_values(tree, values);
651
652 // we now have duplicate leaf 18, one committed the other not
653 check_find_leaf_index(tree, NullifierLeafValue(18), 5 + initial_size, true, true);
654 check_find_leaf_index(tree, NullifierLeafValue(18), 5 + initial_size, true, false);
655}
656
658{
660 index_t initial_size = 2;
661 index_t current_size = initial_size;
662 ThreadPoolPtr workers = make_thread_pool(1);
663 ;
664 constexpr size_t depth = 10;
665 std::string name = random_string();
666
667 {
668 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
670 auto tree = TreeType(std::move(store), workers, initial_size);
671
672 check_size(tree, current_size);
673 check_root(tree, memdb.root());
674 check_sibling_path(tree, 0, memdb.get_sibling_path(0));
675
677
678 // Committed data should not have changed
679 check_size(tree, current_size, false);
680 check_root(tree, memdb.root(), false);
681 check_sibling_path(tree, 0, memdb.get_sibling_path(0), false);
682 check_sibling_path(tree, 1, memdb.get_sibling_path(1), false);
683
684 memdb.update_element(get_value(512));
685
686 // Uncommitted data should have changed
687 check_size(tree, current_size + 1, true);
688 check_root(tree, memdb.root(), true);
689 check_sibling_path(tree, 0, memdb.get_sibling_path(0), true);
690 check_sibling_path(tree, 1, memdb.get_sibling_path(1), true);
691
692 // Now commit
693 commit_tree(tree);
694
695 // Now committed data should have changed
696 check_size(tree, ++current_size, false);
697 check_root(tree, memdb.root(), false);
698 check_sibling_path(tree, 0, memdb.get_sibling_path(0), false);
699 check_sibling_path(tree, 1, memdb.get_sibling_path(1), false);
700 }
701
702 // Now restore and it should continue from where we left off
703 {
704 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
706 auto tree = TreeType(std::move(store), workers, initial_size);
707
708 // check uncommitted state
709 check_size(tree, current_size);
710 check_root(tree, memdb.root());
711 check_sibling_path(tree, 0, memdb.get_sibling_path(0));
712
713 // check committed state
714 check_size(tree, current_size, false);
715 check_root(tree, memdb.root(), false);
716 check_sibling_path(tree, 0, memdb.get_sibling_path(0), false);
717 }
718}
719
720void test_batch_insert(uint32_t batchSize, std::string directory, uint64_t mapSize, uint64_t maxReaders)
721{
723 const uint32_t batch_size = batchSize;
724 const uint32_t num_batches = 16;
725 uint32_t depth = 10;
726 ThreadPoolPtr workers = make_thread_pool(1);
727 ThreadPoolPtr multi_workers = make_thread_pool(8);
728 NullifierMemoryTree<HashPolicy> memdb(depth, batch_size);
729
730 auto tree1 = create_tree(directory, mapSize, maxReaders, depth, batch_size, workers);
731 auto tree2 = create_tree(directory, mapSize, maxReaders, depth, batch_size, multi_workers);
732 auto tree3 = create_tree(directory, mapSize, maxReaders, depth, batch_size, multi_workers);
733
734 for (uint32_t i = 0; i < num_batches; i++) {
735
736 check_root(*tree1, memdb.root());
737 check_root(*tree2, memdb.root());
738 check_root(*tree3, memdb.root());
739 check_sibling_path(*tree1, 0, memdb.get_sibling_path(0));
740 check_sibling_path(*tree2, 0, memdb.get_sibling_path(0));
741 check_sibling_path(*tree3, 0, memdb.get_sibling_path(0));
742
743 check_sibling_path(*tree1, 512, memdb.get_sibling_path(512));
744 check_sibling_path(*tree2, 512, memdb.get_sibling_path(512));
745 check_sibling_path(*tree3, 512, memdb.get_sibling_path(512));
746
748 std::vector<fr_sibling_path> memory_tree_sibling_paths;
749 for (uint32_t j = 0; j < batch_size; j++) {
750 batch.emplace_back(random_engine.get_random_uint256());
751 fr_sibling_path path = memdb.update_element(batch[j].nullifier);
752 memory_tree_sibling_paths.push_back(path);
753 }
756 {
757 Signal signal;
758 CompletionCallback completion =
760 tree1_low_leaf_witness_data = response.inner.low_leaf_witness_data;
761 signal.signal_level();
762 };
763 tree1->add_or_update_values(batch, completion);
764 signal.wait_for_level();
765 }
766
767 {
768 Signal signal;
769 CompletionCallback completion =
771 tree2_low_leaf_witness_data = response.inner.low_leaf_witness_data;
772 signal.signal_level();
773 };
774 tree2->add_or_update_values(batch, completion);
775 signal.wait_for_level();
776 }
777
778 {
779 Signal signal;
780 auto completion = [&](const TypedResponse<AddDataResponse>&) { signal.signal_level(); };
781 tree3->add_or_update_values(batch, completion);
782 signal.wait_for_level();
783 }
784 check_root(*tree1, memdb.root());
785 check_root(*tree2, memdb.root());
786 check_root(*tree3, memdb.root());
787
788 check_sibling_path(*tree1, 0, memdb.get_sibling_path(0));
789 check_sibling_path(*tree2, 0, memdb.get_sibling_path(0));
790 check_sibling_path(*tree3, 0, memdb.get_sibling_path(0));
791
792 check_sibling_path(*tree1, 512, memdb.get_sibling_path(512));
793 check_sibling_path(*tree2, 512, memdb.get_sibling_path(512));
794 check_sibling_path(*tree3, 512, memdb.get_sibling_path(512));
795
796 for (uint32_t j = 0; j < batch_size; j++) {
797 EXPECT_EQ(tree1_low_leaf_witness_data->at(j).leaf, tree2_low_leaf_witness_data->at(j).leaf);
798 EXPECT_EQ(tree1_low_leaf_witness_data->at(j).index, tree2_low_leaf_witness_data->at(j).index);
799 EXPECT_EQ(tree1_low_leaf_witness_data->at(j).path, tree2_low_leaf_witness_data->at(j).path);
800 }
801 }
802}
803
805 std::string directory,
806 uint64_t mapSize,
807 uint64_t maxReaders)
808{
810 const uint32_t batch_size = batchSize;
811 const uint32_t num_batches = 16;
812 uint32_t depth = 10;
813 ThreadPoolPtr workers = make_thread_pool(1);
814 ThreadPoolPtr multi_workers = make_thread_pool(8);
815 NullifierMemoryTree<HashPolicy> memdb(depth, batch_size);
816
817 for (uint32_t i = 0; i < num_batches; i++) {
818
819 auto tree1 = create_tree(directory, mapSize, maxReaders, depth, batch_size, workers);
820 auto tree2 = create_tree(directory, mapSize, maxReaders, depth, batch_size, multi_workers);
821 auto tree3 = create_tree(directory, mapSize, maxReaders, depth, batch_size, multi_workers);
822
823 check_root(*tree1, memdb.root());
824 check_root(*tree2, memdb.root());
825 check_root(*tree3, memdb.root());
826 check_sibling_path(*tree1, 0, memdb.get_sibling_path(0));
827 check_sibling_path(*tree2, 0, memdb.get_sibling_path(0));
828 check_sibling_path(*tree3, 0, memdb.get_sibling_path(0));
829
830 check_sibling_path(*tree1, 512, memdb.get_sibling_path(512));
831 check_sibling_path(*tree2, 512, memdb.get_sibling_path(512));
832 check_sibling_path(*tree3, 512, memdb.get_sibling_path(512));
833
835 std::vector<fr_sibling_path> memory_tree_sibling_paths;
836 for (uint32_t j = 0; j < batch_size; j++) {
837 batch.emplace_back(random_engine.get_random_uint256());
838 fr_sibling_path path = memdb.update_element(batch[j].nullifier);
839 memory_tree_sibling_paths.push_back(path);
840 }
843 {
844 Signal signal;
845 CompletionCallback completion =
847 tree1_low_leaf_witness_data = response.inner.low_leaf_witness_data;
848 signal.signal_level();
849 };
850 tree1->add_or_update_values(batch, completion);
851 signal.wait_for_level();
852 }
853
854 {
855 Signal signal;
856 CompletionCallback completion =
858 tree2_low_leaf_witness_data = response.inner.low_leaf_witness_data;
859 signal.signal_level();
860 };
861 tree2->add_or_update_values(batch, completion);
862 signal.wait_for_level();
863 }
864
865 {
866 Signal signal;
867 auto completion = [&](const TypedResponse<AddDataResponse>&) { signal.signal_level(); };
868 tree3->add_or_update_values(batch, completion);
869 signal.wait_for_level();
870 }
871 check_root(*tree1, memdb.root());
872 check_root(*tree2, memdb.root());
873 check_root(*tree3, memdb.root());
874
875 check_sibling_path(*tree1, 0, memdb.get_sibling_path(0));
876 check_sibling_path(*tree2, 0, memdb.get_sibling_path(0));
877 check_sibling_path(*tree3, 0, memdb.get_sibling_path(0));
878
879 check_sibling_path(*tree1, 512, memdb.get_sibling_path(512));
880 check_sibling_path(*tree2, 512, memdb.get_sibling_path(512));
881 check_sibling_path(*tree3, 512, memdb.get_sibling_path(512));
882
883 for (uint32_t j = 0; j < batch_size; j++) {
884 EXPECT_EQ(tree1_low_leaf_witness_data->at(j).leaf, tree2_low_leaf_witness_data->at(j).leaf);
885 EXPECT_EQ(tree1_low_leaf_witness_data->at(j).index, tree2_low_leaf_witness_data->at(j).index);
886 EXPECT_EQ(tree1_low_leaf_witness_data->at(j).path, tree2_low_leaf_witness_data->at(j).path);
887 }
888
889 commit_tree(*tree1);
890 commit_tree(*tree2);
891 commit_tree(*tree3);
892 }
893}
894
896{
897 uint32_t batchSize = 2;
898 while (batchSize <= 2) {
899 test_batch_insert(batchSize, _directory, _mapSize, _maxReaders);
900 batchSize <<= 1;
901 }
902}
903
905{
906 uint32_t batchSize = 2;
907 while (batchSize <= 32) {
908 test_batch_insert(batchSize, _directory, _mapSize, _maxReaders);
909 batchSize <<= 1;
910 }
911}
912
913TEST_F(PersistedContentAddressedIndexedTreeTest, test_compare_batch_inserts_different_sized_thread_pools)
914{
915 const uint32_t batch_size = 128;
916 uint32_t depth = 20;
917 ThreadPoolPtr workers = make_thread_pool(1);
918 NullifierMemoryTree<HashPolicy> memdb(depth, batch_size);
919
920 auto tree1 = create_tree(_directory, _mapSize, _maxReaders, depth, batch_size, workers);
921 auto tree2 = create_tree(_directory, _mapSize, _maxReaders, depth, batch_size, workers);
922
924 for (uint32_t i = 1; i <= 12; i++) {
925 ThreadPoolPtr multiWorkers = make_thread_pool(i);
926 auto tree = create_tree(_directory, _mapSize, _maxReaders, depth, batch_size, multiWorkers);
927 trees.emplace_back(std::move(tree));
928 }
929
930 std::vector<fr> tree1Roots;
931 std::vector<fr> tree2Roots;
932
933 for (uint32_t round = 0; round < 10; round++) {
934 std::vector<fr> frValues1 = create_values(3);
935 std::vector<fr> frValues2 = create_values(3);
937 for (uint32_t i = 0; i < 3; i++) {
938 leaves[i] = frValues1[i];
939 leaves[i + 64] = frValues2[i];
940 }
941
942 std::vector<NullifierLeafValue> first(leaves.begin(), leaves.begin() + 64);
943 std::vector<NullifierLeafValue> second(leaves.begin() + 64, leaves.end());
944
945 add_values(*tree1, first);
946 add_values(*tree1, second);
947
948 block_sync_values(*tree2, leaves);
949
950 tree1Roots.push_back(get_root(*tree1));
951 tree2Roots.push_back(get_root(*tree2, true));
952 EXPECT_EQ(tree1Roots[round], tree2Roots[round]);
953
954 for (const auto& tree : trees) {
955 block_sync_values(*tree, leaves);
956 const fr treeRoot = get_root(*tree, true);
957 EXPECT_EQ(treeRoot, tree1Roots[round]);
958 }
959 }
960}
961
962TEST_F(PersistedContentAddressedIndexedTreeTest, reports_an_error_if_batch_contains_duplicate)
963{
964 index_t current_size = 2;
965 ThreadPoolPtr workers = make_thread_pool(1);
966 ;
967 constexpr size_t depth = 10;
968 std::string name = random_string();
969 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
971 auto tree = TreeType(std::move(store), workers, current_size);
972
974 for (uint32_t i = 0; i < 16; i++) {
975 values.emplace_back(get_value(i));
976 }
977 values[8] = values[0];
978
979 std::stringstream ss;
980 ss << "Duplicate key not allowed in same batch, key value: " << values[0].nullifier << ", tree: " << name;
981
982 Signal signal;
983 auto add_completion = [&](const TypedResponse<AddIndexedDataResponse<NullifierLeafValue>>& response) {
984 EXPECT_EQ(response.success, false);
985 EXPECT_EQ(response.message, ss.str());
986 signal.signal_level();
987 };
988 tree.add_or_update_values(values, add_completion);
989 signal.wait_for_level();
990}
991
992void test_sequential_insert_vs_batch(uint32_t batchSize, std::string directory, uint64_t mapSize, uint64_t maxReaders)
993{
995 const uint32_t batch_size = batchSize;
996 const uint32_t num_batches = 16;
997 uint32_t depth = 10;
998 ThreadPoolPtr workers = make_thread_pool(1);
999 ThreadPoolPtr multi_workers = make_thread_pool(8);
1000 NullifierMemoryTree<HashPolicy> memdb(depth, batch_size);
1001
1002 auto sequential_tree_1 = create_tree(directory, mapSize, maxReaders, depth, batch_size, workers);
1003 auto sequential_tree_2 = create_tree(directory, mapSize, maxReaders, depth, batch_size, multi_workers);
1004 auto sequential_tree_3 = create_tree(directory, mapSize, maxReaders, depth, batch_size, multi_workers);
1005 auto batch_tree = create_tree(directory, mapSize, maxReaders, depth, batch_size, multi_workers);
1006
1007 for (uint32_t i = 0; i < num_batches; i++) {
1008
1009 check_root(*sequential_tree_1, memdb.root());
1010 check_root(*sequential_tree_2, memdb.root());
1011 check_root(*sequential_tree_3, memdb.root());
1012 check_root(*batch_tree, memdb.root());
1013 check_sibling_path(*sequential_tree_1, 0, memdb.get_sibling_path(0));
1014 check_sibling_path(*sequential_tree_2, 0, memdb.get_sibling_path(0));
1015 check_sibling_path(*sequential_tree_3, 0, memdb.get_sibling_path(0));
1016 check_sibling_path(*batch_tree, 0, memdb.get_sibling_path(0));
1017
1018 check_sibling_path(*sequential_tree_1, 512, memdb.get_sibling_path(512));
1019 check_sibling_path(*sequential_tree_2, 512, memdb.get_sibling_path(512));
1020 check_sibling_path(*sequential_tree_3, 512, memdb.get_sibling_path(512));
1021 check_sibling_path(*batch_tree, 512, memdb.get_sibling_path(512));
1022
1024 std::vector<fr_sibling_path> memory_tree_sibling_paths;
1025 for (uint32_t j = 0; j < batch_size; j++) {
1026 batch.emplace_back(random_engine.get_random_uint256());
1027 fr_sibling_path path = memdb.update_element(batch[j].nullifier);
1028 memory_tree_sibling_paths.push_back(path);
1029 }
1030 std::shared_ptr<std::vector<LeafUpdateWitnessData<NullifierLeafValue>>> sequential_tree_1_low_leaf_witness_data;
1032 sequential_tree_1_insertion_witness_data;
1033 std::shared_ptr<std::vector<LeafUpdateWitnessData<NullifierLeafValue>>> sequential_tree_2_low_leaf_witness_data;
1035 sequential_tree_2_insertion_witness_data;
1036
1037 {
1038 Signal signal;
1039 SequentialCompletionCallback completion =
1041 sequential_tree_1_low_leaf_witness_data = response.inner.low_leaf_witness_data;
1042 sequential_tree_1_insertion_witness_data = response.inner.insertion_witness_data;
1043 signal.signal_level();
1044 };
1045 sequential_tree_1->add_or_update_values_sequentially(batch, completion);
1046 signal.wait_for_level();
1047 }
1048
1049 {
1050 Signal signal;
1051 SequentialCompletionCallback completion =
1053 sequential_tree_2_low_leaf_witness_data = response.inner.low_leaf_witness_data;
1054 sequential_tree_2_insertion_witness_data = response.inner.insertion_witness_data;
1055 signal.signal_level();
1056 };
1057 sequential_tree_2->add_or_update_values_sequentially(batch, completion);
1058 signal.wait_for_level();
1059 }
1060
1061 {
1062 Signal signal;
1063 auto completion = [&](const TypedResponse<AddDataResponse>&) { signal.signal_level(); };
1064 sequential_tree_3->add_or_update_values_sequentially(batch, completion);
1065 signal.wait_for_level();
1066 }
1067
1068 {
1069 Signal signal;
1070 auto completion = [&](const TypedResponse<AddDataResponse>&) { signal.signal_level(); };
1071 batch_tree->add_or_update_values(batch, completion);
1072 signal.wait_for_level();
1073 }
1074 check_root(*sequential_tree_1, memdb.root());
1075 check_root(*sequential_tree_2, memdb.root());
1076 check_root(*sequential_tree_3, memdb.root());
1077 check_root(*batch_tree, memdb.root());
1078
1079 check_sibling_path(*sequential_tree_1, 0, memdb.get_sibling_path(0));
1080 check_sibling_path(*sequential_tree_2, 0, memdb.get_sibling_path(0));
1081 check_sibling_path(*sequential_tree_3, 0, memdb.get_sibling_path(0));
1082 check_sibling_path(*batch_tree, 0, memdb.get_sibling_path(0));
1083
1084 check_sibling_path(*sequential_tree_1, 512, memdb.get_sibling_path(512));
1085 check_sibling_path(*sequential_tree_2, 512, memdb.get_sibling_path(512));
1086 check_sibling_path(*sequential_tree_3, 512, memdb.get_sibling_path(512));
1087 check_sibling_path(*batch_tree, 512, memdb.get_sibling_path(512));
1088
1089 for (uint32_t j = 0; j < batch_size; j++) {
1090 EXPECT_EQ(sequential_tree_1_low_leaf_witness_data->at(j).leaf,
1091 sequential_tree_2_low_leaf_witness_data->at(j).leaf);
1092 EXPECT_EQ(sequential_tree_1_low_leaf_witness_data->at(j).index,
1093 sequential_tree_2_low_leaf_witness_data->at(j).index);
1094 EXPECT_EQ(sequential_tree_1_low_leaf_witness_data->at(j).path,
1095 sequential_tree_2_low_leaf_witness_data->at(j).path);
1096
1097 EXPECT_EQ(sequential_tree_1_insertion_witness_data->at(j).leaf,
1098 sequential_tree_2_insertion_witness_data->at(j).leaf);
1099 EXPECT_EQ(sequential_tree_1_insertion_witness_data->at(j).index,
1100 sequential_tree_2_insertion_witness_data->at(j).index);
1101 EXPECT_EQ(sequential_tree_1_insertion_witness_data->at(j).path,
1102 sequential_tree_2_insertion_witness_data->at(j).path);
1103 }
1104 }
1105}
1106
1108{
1109 uint32_t batchSize = 2;
1110 while (batchSize <= 2) {
1111 test_sequential_insert_vs_batch(batchSize, _directory, _mapSize, _maxReaders);
1112 batchSize <<= 1;
1113 }
1114}
1115
1116TEST_F(PersistedContentAddressedIndexedTreeTest, sequential_insert_allows_multiple_inserts_to_the_same_key)
1117{
1118 index_t current_size = 2;
1119 ThreadPoolPtr workers = make_thread_pool(8);
1120 // Create a depth-3 indexed merkle tree
1121 constexpr size_t depth = 3;
1122 std::string name = random_string();
1123 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
1127 std::move(store), workers, current_size);
1128
1130 add_values_sequentially(tree, values);
1131
1132 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 2).leaf.value, values[1].value);
1133 check_size(tree, 3);
1134}
1135
1136template <typename LeafValueType> fr hash_leaf(const IndexedLeaf<LeafValueType>& leaf)
1137{
1138 return HashPolicy::hash(leaf.get_hash_inputs());
1139}
1140
1141bool verify_sibling_path(TreeType& tree, const IndexedNullifierLeafType& leaf_value, const uint32_t idx)
1142{
1143 fr root = get_root(tree, true);
1144 fr_sibling_path path = get_sibling_path(tree, idx, true);
1145 auto current = hash_leaf(leaf_value);
1146 uint32_t depth_ = static_cast<uint32_t>(path.size());
1147 uint32_t index = idx;
1148 for (uint32_t i = 0; i < depth_; ++i) {
1149 fr left = (index & 1) ? path[i] : current;
1150 fr right = (index & 1) ? current : path[i];
1151 current = HashPolicy::hash_pair(left, right);
1152 index >>= 1;
1153 }
1154 return current == root;
1155}
1156
1158{
1159 index_t current_size = 2;
1160 ThreadPoolPtr workers = make_thread_pool(8);
1161 // Create a depth-3 indexed merkle tree
1162 constexpr size_t depth = 3;
1163 std::string name = random_string();
1164 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
1166 auto tree = TreeType(std::move(store), workers, current_size);
1167
1177 IndexedNullifierLeafType zero_leaf(NullifierLeafValue(0), 1, 1);
1179 check_size(tree, current_size);
1180 EXPECT_EQ(get_leaf<NullifierLeafValue>(tree, 0), zero_leaf);
1181 EXPECT_EQ(get_leaf<NullifierLeafValue>(tree, 1), one_leaf);
1182
1192 add_value(tree, NullifierLeafValue(30));
1193 check_size(tree, ++current_size);
1194 EXPECT_EQ(get_leaf<NullifierLeafValue>(tree, 0), create_indexed_nullifier_leaf(0, 1, 1));
1195 EXPECT_EQ(get_leaf<NullifierLeafValue>(tree, 1), create_indexed_nullifier_leaf(1, 2, 30));
1196 EXPECT_EQ(get_leaf<NullifierLeafValue>(tree, 2), create_indexed_nullifier_leaf(30, 0, 0));
1197
1207 add_value(tree, NullifierLeafValue(10));
1208 check_size(tree, ++current_size);
1209 EXPECT_EQ(get_leaf<NullifierLeafValue>(tree, 0), create_indexed_nullifier_leaf(0, 1, 1));
1210 EXPECT_EQ(get_leaf<NullifierLeafValue>(tree, 1), create_indexed_nullifier_leaf(1, 3, 10));
1211 EXPECT_EQ(get_leaf<NullifierLeafValue>(tree, 2), create_indexed_nullifier_leaf(30, 0, 0));
1212 EXPECT_EQ(get_leaf<NullifierLeafValue>(tree, 3), create_indexed_nullifier_leaf(10, 2, 30));
1213
1223 add_value(tree, NullifierLeafValue(20));
1224 check_size(tree, ++current_size);
1225 EXPECT_EQ(get_leaf<NullifierLeafValue>(tree, 0), create_indexed_nullifier_leaf(0, 1, 1));
1226 EXPECT_EQ(get_leaf<NullifierLeafValue>(tree, 1), create_indexed_nullifier_leaf(1, 3, 10));
1227 EXPECT_EQ(get_leaf<NullifierLeafValue>(tree, 2), create_indexed_nullifier_leaf(30, 0, 0));
1228 EXPECT_EQ(get_leaf<NullifierLeafValue>(tree, 3), create_indexed_nullifier_leaf(10, 4, 20));
1229 EXPECT_EQ(get_leaf<NullifierLeafValue>(tree, 4), create_indexed_nullifier_leaf(20, 2, 30));
1230
1231 // Adding the same value must not affect anything
1232 // tree.update_element(20);
1233 // EXPECT_EQ(tree.get_leaves().size(), 4);
1234 // EXPECT_EQ(tree.get_leaves()[0], hash_leaf({ 0, 2, 10 }));
1235 // EXPECT_EQ(tree.get_leaves()[1], hash_leaf({ 30, 0, 0 }));
1236 // EXPECT_EQ(tree.get_leaves()[2], hash_leaf({ 10, 3, 20 }));
1237 // EXPECT_EQ(tree.get_leaves()[3], hash_leaf({ 20, 1, 30 }));
1238
1248 add_value(tree, NullifierLeafValue(50));
1249 check_size(tree, ++current_size);
1250 EXPECT_EQ(get_leaf<NullifierLeafValue>(tree, 0), create_indexed_nullifier_leaf(0, 1, 1));
1251 EXPECT_EQ(get_leaf<NullifierLeafValue>(tree, 1), create_indexed_nullifier_leaf(1, 3, 10));
1252 EXPECT_EQ(get_leaf<NullifierLeafValue>(tree, 2), create_indexed_nullifier_leaf(30, 5, 50));
1253 EXPECT_EQ(get_leaf<NullifierLeafValue>(tree, 3), create_indexed_nullifier_leaf(10, 4, 20));
1254 EXPECT_EQ(get_leaf<NullifierLeafValue>(tree, 4), create_indexed_nullifier_leaf(20, 2, 30));
1255 EXPECT_EQ(get_leaf<NullifierLeafValue>(tree, 5), create_indexed_nullifier_leaf(50, 0, 0));
1256
1257 // Manually compute the node values
1258 auto e000 = hash_leaf(get_leaf<NullifierLeafValue>(tree, 0));
1259 auto e001 = hash_leaf(get_leaf<NullifierLeafValue>(tree, 1));
1260 auto e010 = hash_leaf(get_leaf<NullifierLeafValue>(tree, 2));
1261 auto e011 = hash_leaf(get_leaf<NullifierLeafValue>(tree, 3));
1262 auto e100 = hash_leaf(get_leaf<NullifierLeafValue>(tree, 4));
1263 auto e101 = hash_leaf(get_leaf<NullifierLeafValue>(tree, 5));
1264 auto e110 = fr::zero();
1265 auto e111 = fr::zero();
1266
1267 auto e00 = HashPolicy::hash_pair(e000, e001);
1268 auto e01 = HashPolicy::hash_pair(e010, e011);
1269 auto e10 = HashPolicy::hash_pair(e100, e101);
1270 auto e11 = HashPolicy::hash_pair(e110, e111);
1271
1272 auto e0 = HashPolicy::hash_pair(e00, e01);
1273 auto e1 = HashPolicy::hash_pair(e10, e11);
1274 auto root = HashPolicy::hash_pair(e0, e1);
1275
1276 // Check the hash path at index 2 and 3
1277 // Note: This merkle proof would also serve as a non-membership proof of values in (10, 20) and (20, 30)
1278 fr_sibling_path expected = {
1279 e001,
1280 e01,
1281 e1,
1282 };
1283 check_sibling_path(tree, 0, expected);
1284 expected = {
1285 e000,
1286 e01,
1287 e1,
1288 };
1289 check_sibling_path(tree, 1, expected);
1290 expected = {
1291 e011,
1292 e00,
1293 e1,
1294 };
1295 check_sibling_path(tree, 2, expected);
1296 expected = {
1297 e010,
1298 e00,
1299 e1,
1300 };
1301 check_sibling_path(tree, 3, expected);
1302 check_root(tree, root);
1303
1304 // Check the hash path at index 6 and 7
1305 expected = {
1306 e111,
1307 e10,
1308 e0,
1309 };
1310 check_sibling_path(tree, 6, expected);
1311 expected = {
1312 e110,
1313 e10,
1314 e0,
1315 };
1316 check_sibling_path(tree, 7, expected);
1317}
1318
1320{
1321 index_t current_size = 2;
1322 ThreadPoolPtr workers = make_thread_pool(1);
1323 ;
1324 // Create a depth-8 indexed merkle tree
1325 constexpr uint32_t depth = 8;
1326 std::string name = random_string();
1327 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
1329 auto tree = TreeType(std::move(store), workers, current_size);
1330
1332 check_size(tree, current_size);
1333 EXPECT_EQ(hash_leaf(get_leaf<NullifierLeafValue>(tree, 0)), hash_leaf(zero_leaf));
1334
1335 // Add 20 random values to the tree
1336 for (uint32_t i = 0; i < 20; i++) {
1337 auto value = fr::random_element();
1339 ++current_size;
1340 }
1341
1342 auto abs_diff = [](uint256_t a, uint256_t b) {
1343 if (a > b) {
1344 return (a - b);
1345 } else {
1346 return (b - a);
1347 }
1348 };
1349
1350 check_size(tree, current_size);
1351
1352 // Check if a new random value is not a member of this tree.
1353 fr new_member = fr::random_element();
1354 std::vector<uint256_t> differences;
1355 for (uint32_t i = 0; i < uint32_t(21); i++) {
1356 uint256_t diff_hi =
1357 abs_diff(uint256_t(new_member), uint256_t(get_leaf<NullifierLeafValue>(tree, i).leaf.get_key()));
1358 uint256_t diff_lo =
1359 abs_diff(uint256_t(new_member), uint256_t(get_leaf<NullifierLeafValue>(tree, i).leaf.get_key()));
1360 differences.push_back(diff_hi + diff_lo);
1361 }
1362 auto it = std::min_element(differences.begin(), differences.end());
1363 auto index = static_cast<uint32_t>(it - differences.begin());
1364
1365 // Merkle proof at `index` proves non-membership of `new_member`
1366 EXPECT_TRUE(verify_sibling_path(tree, get_leaf<NullifierLeafValue>(tree, index), index));
1367}
1368
1370{
1371 constexpr size_t depth = 10;
1373 fr_sibling_path initial_path = memdb.get_sibling_path(0);
1374 memdb.update_element(get_value(0));
1375 fr_sibling_path final_sibling_path = memdb.get_sibling_path(0);
1376
1377 uint32_t num_reads = 16 * 1024;
1378 std::vector<fr_sibling_path> paths(num_reads);
1379
1380 {
1381 std::string name = random_string();
1382 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
1385 TreeType tree(std::move(store), pool, 2);
1386
1387 check_size(tree, 2);
1388
1389 Signal signal(1 + num_reads);
1390
1391 auto add_completion = [&](const TypedResponse<AddIndexedDataResponse<NullifierLeafValue>>&) {
1392 auto commit_completion = [&](const TypedResponse<CommitResponse>&) { signal.signal_decrement(); };
1393 tree.commit(commit_completion);
1394 };
1395 tree.add_or_update_value(get_value(0), add_completion);
1396
1397 for (size_t i = 0; i < num_reads; i++) {
1398 auto completion = [&, i](const TypedResponse<GetSiblingPathResponse>& response) {
1399 paths[i] = response.inner.path;
1400 signal.signal_decrement();
1401 };
1402 tree.get_sibling_path(0, completion, false);
1403 }
1404 signal.wait_for_level();
1405 }
1406}
1407
1408TEST_F(PersistedContentAddressedIndexedTreeTest, test_indexed_memory_with_public_data_writes)
1409{
1410 index_t current_size = 2;
1411 ThreadPoolPtr workers = make_thread_pool(8);
1412 // Create a depth-3 indexed merkle tree
1413 constexpr size_t depth = 3;
1414 std::string name = random_string();
1415 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
1419 std::move(store), workers, current_size);
1420
1433 check_size(tree, current_size);
1434 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 0), zero_leaf);
1435 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 1), one_leaf);
1436
1447 add_value(tree, PublicDataLeafValue(30, 5));
1448 check_size(tree, ++current_size);
1449 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 0), create_indexed_public_data_leaf(0, 0, 1, 1));
1450 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 1), create_indexed_public_data_leaf(1, 0, 2, 30));
1451 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 2), create_indexed_public_data_leaf(30, 5, 0, 0));
1452
1463 add_value(tree, PublicDataLeafValue(10, 20));
1464 check_size(tree, ++current_size);
1465 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 0), create_indexed_public_data_leaf(0, 0, 1, 1));
1466 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 1), create_indexed_public_data_leaf(1, 0, 3, 10));
1467 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 2), create_indexed_public_data_leaf(30, 5, 0, 0));
1468 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 3), create_indexed_public_data_leaf(10, 20, 2, 30));
1469
1480 add_value(tree, PublicDataLeafValue(30, 6));
1481 // The size still increases as we pad with an empty leaf
1482 check_size(tree, ++current_size);
1483
1484 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 0), create_indexed_public_data_leaf(0, 0, 1, 1));
1485 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 1), create_indexed_public_data_leaf(1, 0, 3, 10));
1486 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 2), create_indexed_public_data_leaf(30, 6, 0, 0));
1487 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 3), create_indexed_public_data_leaf(10, 20, 2, 30));
1488 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 4), create_indexed_public_data_leaf(0, 0, 0, 0));
1489
1500 add_value(tree, PublicDataLeafValue(50, 8));
1501 check_size(tree, ++current_size);
1502 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 0), create_indexed_public_data_leaf(0, 0, 1, 1));
1503 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 1), create_indexed_public_data_leaf(1, 0, 3, 10));
1504 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 2), create_indexed_public_data_leaf(30, 6, 5, 50));
1505 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 3), create_indexed_public_data_leaf(10, 20, 2, 30));
1506 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 4), create_indexed_public_data_leaf(0, 0, 0, 0));
1507 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 5), create_indexed_public_data_leaf(50, 8, 0, 0));
1508
1509 // Manually compute the node values
1510 auto e000 = hash_leaf(get_leaf<PublicDataLeafValue>(tree, 0));
1511 auto e001 = hash_leaf(get_leaf<PublicDataLeafValue>(tree, 1));
1512 auto e010 = hash_leaf(get_leaf<PublicDataLeafValue>(tree, 2));
1513 auto e011 = hash_leaf(get_leaf<PublicDataLeafValue>(tree, 3));
1514 auto e100 = fr::zero(); // tree doesn't hash 0 leaves!
1515 auto e101 = hash_leaf(get_leaf<PublicDataLeafValue>(tree, 5));
1516 auto e110 = fr::zero();
1517 auto e111 = fr::zero();
1518
1519 auto e00 = HashPolicy::hash_pair(e000, e001);
1520 auto e01 = HashPolicy::hash_pair(e010, e011);
1521 auto e10 = HashPolicy::hash_pair(e100, e101);
1522 auto e11 = HashPolicy::hash_pair(e110, e111);
1523
1524 auto e0 = HashPolicy::hash_pair(e00, e01);
1525 auto e1 = HashPolicy::hash_pair(e10, e11);
1526 auto root = HashPolicy::hash_pair(e0, e1);
1527
1528 fr_sibling_path expected = {
1529 e001,
1530 e01,
1531 e1,
1532 };
1533 check_sibling_path(tree, 0, expected);
1534 expected = {
1535 e000,
1536 e01,
1537 e1,
1538 };
1539 check_sibling_path(tree, 1, expected);
1540 expected = {
1541 e011,
1542 e00,
1543 e1,
1544 };
1545 check_sibling_path(tree, 2, expected);
1546 expected = {
1547 e010,
1548 e00,
1549 e1,
1550 };
1551 check_sibling_path(tree, 3, expected);
1552 check_root(tree, root);
1553
1554 // Check the hash path at index 6 and 7
1555 expected = {
1556 e111,
1557 e10,
1558 e0,
1559 };
1560 check_sibling_path(tree, 6, expected);
1561 expected = {
1562 e110,
1563 e10,
1564 e0,
1565 };
1566 check_sibling_path(tree, 7, expected);
1567}
1568
1569TEST_F(PersistedContentAddressedIndexedTreeTest, test_indexed_memory_with_sequential_public_data_writes)
1570{
1571 index_t current_size = 2;
1572 ThreadPoolPtr workers = make_thread_pool(8);
1573 // Create a depth-3 indexed merkle tree
1574 constexpr size_t depth = 3;
1575 std::string name = random_string();
1576 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
1580 std::move(store), workers, current_size);
1581
1594 check_size(tree, current_size);
1595 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 0), zero_leaf);
1596 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 1), one_leaf);
1597
1609 check_size(tree, ++current_size);
1610 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 0), create_indexed_public_data_leaf(0, 0, 1, 1));
1611 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 1), create_indexed_public_data_leaf(1, 0, 2, 30));
1612 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 2), create_indexed_public_data_leaf(30, 5, 0, 0));
1613
1625 check_size(tree, ++current_size);
1626 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 0), create_indexed_public_data_leaf(0, 0, 1, 1));
1627 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 1), create_indexed_public_data_leaf(1, 0, 3, 10));
1628 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 2), create_indexed_public_data_leaf(30, 5, 0, 0));
1629 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 3), create_indexed_public_data_leaf(10, 20, 2, 30));
1630
1642 // The size does not increase since sequential insertion doesn't pad
1643 check_size(tree, current_size);
1644
1645 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 0), create_indexed_public_data_leaf(0, 0, 1, 1));
1646 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 1), create_indexed_public_data_leaf(1, 0, 3, 10));
1647 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 2), create_indexed_public_data_leaf(30, 6, 0, 0));
1648 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 3), create_indexed_public_data_leaf(10, 20, 2, 30));
1649
1661 check_size(tree, ++current_size);
1662 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 0), create_indexed_public_data_leaf(0, 0, 1, 1));
1663 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 1), create_indexed_public_data_leaf(1, 0, 3, 10));
1664 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 2), create_indexed_public_data_leaf(30, 6, 4, 50));
1665 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 3), create_indexed_public_data_leaf(10, 20, 2, 30));
1666 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 4), create_indexed_public_data_leaf(50, 8, 0, 0));
1667
1668 // Manually compute the node values
1669 auto e000 = hash_leaf(get_leaf<PublicDataLeafValue>(tree, 0));
1670 auto e001 = hash_leaf(get_leaf<PublicDataLeafValue>(tree, 1));
1671 auto e010 = hash_leaf(get_leaf<PublicDataLeafValue>(tree, 2));
1672 auto e011 = hash_leaf(get_leaf<PublicDataLeafValue>(tree, 3));
1673 auto e100 = hash_leaf(get_leaf<PublicDataLeafValue>(tree, 4));
1674 auto e101 = fr::zero();
1675 auto e110 = fr::zero();
1676 auto e111 = fr::zero();
1677
1678 auto e00 = HashPolicy::hash_pair(e000, e001);
1679 auto e01 = HashPolicy::hash_pair(e010, e011);
1680 auto e10 = HashPolicy::hash_pair(e100, e101);
1681 auto e11 = HashPolicy::hash_pair(e110, e111);
1682
1683 auto e0 = HashPolicy::hash_pair(e00, e01);
1684 auto e1 = HashPolicy::hash_pair(e10, e11);
1685 auto root = HashPolicy::hash_pair(e0, e1);
1686
1687 fr_sibling_path expected = {
1688 e001,
1689 e01,
1690 e1,
1691 };
1692 check_sibling_path(tree, 0, expected);
1693 expected = {
1694 e000,
1695 e01,
1696 e1,
1697 };
1698 check_sibling_path(tree, 1, expected);
1699 expected = {
1700 e011,
1701 e00,
1702 e1,
1703 };
1704 check_sibling_path(tree, 2, expected);
1705 expected = {
1706 e010,
1707 e00,
1708 e1,
1709 };
1710 check_sibling_path(tree, 3, expected);
1711 check_root(tree, root);
1712
1713 // Check the hash path at index 6 and 7
1714 expected = {
1715 e111,
1716 e10,
1717 e0,
1718 };
1719 check_sibling_path(tree, 6, expected);
1720 expected = {
1721 e110,
1722 e10,
1723 e0,
1724 };
1725 check_sibling_path(tree, 7, expected);
1726}
1727
1729{
1730 // Create a depth-8 indexed merkle tree
1731 constexpr uint32_t depth = 8;
1732
1733 ThreadPoolPtr workers = make_thread_pool(1);
1734 std::string name = random_string();
1735 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
1737 auto tree = TreeType(std::move(store), workers, 2);
1738
1739 auto predecessor = get_low_leaf(tree, NullifierLeafValue(42));
1740
1741 EXPECT_EQ(predecessor.is_already_present, false);
1742 EXPECT_EQ(predecessor.index, 1);
1743
1744 add_value(tree, NullifierLeafValue(42));
1745
1746 predecessor = get_low_leaf(tree, NullifierLeafValue(42));
1747 // returns the current leaf since it exists already. Inserting 42 again would modify the existing leaf
1748 EXPECT_EQ(predecessor.is_already_present, true);
1749 EXPECT_EQ(predecessor.index, 2);
1750}
1751
1753{
1754 // Create a depth-8 indexed merkle tree
1755 constexpr uint32_t depth = 8;
1756
1757 ThreadPoolPtr workers = make_thread_pool(1);
1758 ;
1759 std::string name = random_string();
1760 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
1762 auto tree = TreeType(std::move(store), workers, 2);
1763
1764 add_value(tree, NullifierLeafValue(42));
1765 check_size(tree, 3);
1766
1767 commit_tree(tree);
1768
1769 add_value(tree, NullifierLeafValue(42), false);
1770 // expect this to fail as no data is present
1771 commit_tree(tree);
1772 check_size(tree, 3);
1773}
1774
1775TEST_F(PersistedContentAddressedIndexedTreeTest, test_historic_sibling_path_retrieval)
1776{
1778 const uint32_t batch_size = 16;
1779 const uint32_t num_batches = 8;
1780 std::string name1 = random_string();
1781 std::string name2 = random_string();
1782 uint32_t depth = 10;
1783 ThreadPoolPtr multi_workers = make_thread_pool(8);
1784 NullifierMemoryTree<HashPolicy> memdb(depth, batch_size);
1785
1786 LMDBTreeStore::SharedPtr db1 = std::make_shared<LMDBTreeStore>(_directory, name1, _mapSize, _maxReaders);
1787 std::unique_ptr<Store> store1 = std::make_unique<Store>(name1, depth, db1);
1788 auto tree1 = TreeType(std::move(store1), multi_workers, batch_size);
1789
1790 std::vector<fr_sibling_path> memory_tree_sibling_paths_index_0;
1791
1792 auto check = [&]() {
1793 check_root(tree1, memdb.root());
1794 check_sibling_path(tree1, 0, memdb.get_sibling_path(0));
1795 check_sibling_path(tree1, 512, memdb.get_sibling_path(512));
1796
1797 for (uint32_t i = 0; i < memory_tree_sibling_paths_index_0.size(); i++) {
1798 check_historic_sibling_path(tree1, 0, i + 1, memory_tree_sibling_paths_index_0[i]);
1799 }
1800 };
1801
1802 for (uint32_t i = 0; i < num_batches; i++) {
1803
1804 check_root(tree1, memdb.root());
1805 check_sibling_path(tree1, 0, memdb.get_sibling_path(0));
1806 check_sibling_path(tree1, 512, memdb.get_sibling_path(512));
1807
1809
1810 for (uint32_t j = 0; j < batch_size; j++) {
1811 batch.emplace_back(random_engine.get_random_uint256());
1812 memdb.update_element(batch[j].get_key());
1813 }
1814 memory_tree_sibling_paths_index_0.push_back(memdb.get_sibling_path(0));
1817 {
1818 Signal signal;
1819 CompletionCallback completion =
1821 tree1_low_leaf_witness_data = response.inner.low_leaf_witness_data;
1822 signal.signal_level();
1823 };
1824 tree1.add_or_update_values(batch, completion);
1825 signal.wait_for_level();
1826 }
1827 check_root(tree1, memdb.root());
1828 check_sibling_path(tree1, 0, memdb.get_sibling_path(0));
1829 check_sibling_path(tree1, 512, memdb.get_sibling_path(512));
1830 commit_tree(tree1);
1831 check();
1832 }
1833}
1834
1836{
1837 index_t current_size = 2;
1838 ThreadPoolPtr workers = make_thread_pool(8);
1839 // Create a depth-3 indexed merkle tree
1840 constexpr size_t depth = 3;
1841 std::string name = random_string();
1842 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
1845 using LocalTreeType =
1847 auto tree = LocalTreeType(std::move(store), workers, current_size);
1848
1861 check_size(tree, current_size);
1862 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 0), zero_leaf);
1863 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 1), one_leaf);
1864
1876 commit_tree(tree);
1877 check_size(tree, ++current_size);
1878 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 0), create_indexed_public_data_leaf(0, 0, 1, 1));
1879 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 1), create_indexed_public_data_leaf(1, 0, 2, 30));
1880 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 2), create_indexed_public_data_leaf(30, 5, 0, 0));
1881
1882 auto leaf1AtBlock1 = PublicDataLeafValue(1, 0);
1883
1895 check_size(tree, ++current_size);
1896 commit_tree(tree);
1897 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 0), create_indexed_public_data_leaf(0, 0, 1, 1));
1898 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 1), create_indexed_public_data_leaf(1, 0, 3, 10));
1899 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 2), create_indexed_public_data_leaf(30, 5, 0, 0));
1900 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 3), create_indexed_public_data_leaf(10, 20, 2, 30));
1901
1902 auto leaf2AtBlock2 = PublicDataLeafValue(30, 5);
1903 check_historic_leaf(tree, leaf1AtBlock1, 1, 1, true);
1904
1905 // should find this leaf at both blocks 1 and 2 as it looks for the slot which doesn't change
1906 check_historic_find_leaf_index(tree, leaf1AtBlock1, 1, 1, true);
1907 check_historic_find_leaf_index(tree, leaf1AtBlock1, 2, 1, true);
1908
1920 // The size does not increase since sequential insertion doesn't pad
1921 check_size(tree, current_size);
1922 commit_tree(tree);
1923 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 0), create_indexed_public_data_leaf(0, 0, 1, 1));
1924 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 1), create_indexed_public_data_leaf(1, 0, 3, 10));
1925 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 2), create_indexed_public_data_leaf(30, 6, 0, 0));
1926 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 3), create_indexed_public_data_leaf(10, 20, 2, 30));
1927
1928 auto leaf2AtBlock3 = PublicDataLeafValue(30, 6);
1929 check_historic_leaf(tree, leaf2AtBlock2, 2, 2, true);
1930
1931 // should find this leaf at both blocks 1 and 2 as it looks for the slot which doesn't change
1932 check_historic_find_leaf_index(tree, leaf1AtBlock1, 1, 1, true);
1933 check_historic_find_leaf_index(tree, leaf1AtBlock1, 2, 1, true);
1934
1946 check_size(tree, ++current_size);
1947 commit_tree(tree);
1948 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 0), create_indexed_public_data_leaf(0, 0, 1, 1));
1949 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 1), create_indexed_public_data_leaf(1, 0, 3, 10));
1950 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 2), create_indexed_public_data_leaf(30, 6, 4, 50));
1951 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 3), create_indexed_public_data_leaf(10, 20, 2, 30));
1952 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 4), create_indexed_public_data_leaf(50, 8, 0, 0));
1953
1954 check_historic_leaf(tree, leaf2AtBlock3, 2, 3, true);
1955
1956 // should not be found at block 1
1957 check_historic_find_leaf_index_from<PublicDataLeafValue, LocalTreeType>(
1958 tree, { PublicDataLeafValue(10, 20) }, 1, 0, { std::nullopt }, true);
1959 // should be found at block
1960 check_historic_find_leaf_index_from(tree, PublicDataLeafValue(10, 20), 2, 0, 3, true);
1961
1963 EXPECT_EQ(lowLeaf.index, 1);
1964
1965 lowLeaf = get_historic_low_leaf(tree, 2, PublicDataLeafValue(20, 0));
1966 EXPECT_EQ(lowLeaf.index, 3);
1967
1968 lowLeaf = get_historic_low_leaf(tree, 2, PublicDataLeafValue(60, 0));
1969 EXPECT_EQ(lowLeaf.index, 2);
1970}
1971
1972TEST_F(PersistedContentAddressedIndexedTreeTest, test_inserting_a_duplicate_committed_nullifier_should_fail)
1973{
1974 const uint32_t batch_size = 16;
1975 uint32_t depth = 10;
1976 ThreadPoolPtr multi_workers = make_thread_pool(1);
1977 NullifierMemoryTree<HashPolicy> memdb(depth, batch_size);
1978
1979 std::string name1 = random_string();
1980 LMDBTreeStore::SharedPtr db1 = std::make_shared<LMDBTreeStore>(_directory, name1, _mapSize, _maxReaders);
1981 std::unique_ptr<Store> store1 = std::make_unique<Store>(name1, depth, db1);
1982 auto tree = TreeType(std::move(store1), multi_workers, batch_size);
1983
1984 std::vector<fr> values = create_values(batch_size);
1985 std::vector<NullifierLeafValue> nullifierValues(batch_size);
1986 std::transform(
1987 values.begin(), values.end(), nullifierValues.begin(), [](const fr& v) { return NullifierLeafValue(v); });
1988
1989 add_values(tree, nullifierValues);
1990 commit_tree(tree);
1991
1992 // create a new set of values
1993 std::vector<fr> values2 = create_values(batch_size);
1994
1995 // copy one of the previous values into the middle of the batch
1996 values2[batch_size / 2] = values[0];
1997 std::vector<NullifierLeafValue> nullifierValues2(batch_size);
1998 std::transform(
1999 values2.begin(), values2.end(), nullifierValues2.begin(), [](const fr& v) { return NullifierLeafValue(v); });
2000 add_values(tree, nullifierValues2, false);
2001}
2002
2003TEST_F(PersistedContentAddressedIndexedTreeTest, test_inserting_a_duplicate_uncommitted_nullifier_should_fail)
2004{
2005 const uint32_t batch_size = 16;
2006 uint32_t depth = 10;
2007 ThreadPoolPtr multi_workers = make_thread_pool(1);
2008 NullifierMemoryTree<HashPolicy> memdb(depth, batch_size);
2009
2010 std::string name1 = random_string();
2011 LMDBTreeStore::SharedPtr db1 = std::make_shared<LMDBTreeStore>(_directory, name1, _mapSize, _maxReaders);
2012 std::unique_ptr<Store> store1 = std::make_unique<Store>(name1, depth, db1);
2013 auto tree = TreeType(std::move(store1), multi_workers, batch_size);
2014
2015 std::vector<fr> values = create_values(batch_size);
2016 std::vector<NullifierLeafValue> nullifierValues(batch_size);
2017 std::transform(
2018 values.begin(), values.end(), nullifierValues.begin(), [](const fr& v) { return NullifierLeafValue(v); });
2019
2020 add_values(tree, nullifierValues);
2021
2022 // create a new set of values
2023 std::vector<fr> values2 = create_values(batch_size);
2024
2025 // copy one of the previous values into the middle of the batch
2026 values2[batch_size / 2] = values[0];
2027 std::vector<NullifierLeafValue> nullifierValues2(batch_size);
2028 std::transform(
2029 values2.begin(), values2.end(), nullifierValues2.begin(), [](const fr& v) { return NullifierLeafValue(v); });
2030 add_values(tree, nullifierValues2, false);
2031}
2032
2033TEST_F(PersistedContentAddressedIndexedTreeTest, test_can_create_forks_at_historic_blocks)
2034{
2036 const uint32_t batch_size = 16;
2037 uint32_t depth = 10;
2038 ThreadPoolPtr multi_workers = make_thread_pool(8);
2039 NullifierMemoryTree<HashPolicy> memdb(depth, batch_size);
2040
2041 std::string name1 = random_string();
2042 LMDBTreeStore::SharedPtr db1 = std::make_shared<LMDBTreeStore>(_directory, name1, _mapSize, _maxReaders);
2043 std::unique_ptr<Store> store1 = std::make_unique<Store>(name1, depth, db1);
2044 auto tree1 = TreeType(std::move(store1), multi_workers, batch_size);
2045
2046 check_root(tree1, memdb.root());
2047 check_sibling_path(tree1, 0, memdb.get_sibling_path(0));
2048
2049 check_sibling_path(tree1, 512, memdb.get_sibling_path(512));
2050
2052 for (uint32_t j = 0; j < batch_size; j++) {
2053 batch1.emplace_back(random_engine.get_random_uint256());
2054 memdb.update_element(batch1[j].nullifier);
2055 }
2056
2057 fr_sibling_path block1SiblingPathIndex3 = memdb.get_sibling_path(3 + batch_size);
2058
2059 add_values(tree1, batch1);
2060 commit_tree(tree1, true);
2061
2063 for (uint32_t j = 0; j < batch_size; j++) {
2064 batch2.emplace_back(random_engine.get_random_uint256());
2065 memdb.update_element(batch2[j].nullifier);
2066 }
2067
2068 add_values(tree1, batch2);
2069 commit_tree(tree1, true);
2070
2071 fr block2Root = memdb.root();
2072
2073 fr_sibling_path block2SiblingPathIndex19 = memdb.get_sibling_path(19 + batch_size);
2074 fr_sibling_path block2SiblingPathIndex3 = memdb.get_sibling_path(3 + batch_size);
2075
2077 for (uint32_t j = 0; j < batch_size; j++) {
2078 batch3.emplace_back(random_engine.get_random_uint256());
2079 memdb.update_element(batch3[j].nullifier);
2080 }
2081
2082 add_values(tree1, batch3);
2083 commit_tree(tree1, true);
2084
2085 fr_sibling_path block3SiblingPathIndex35 = memdb.get_sibling_path(35 + batch_size);
2086 fr_sibling_path block3SiblingPathIndex19 = memdb.get_sibling_path(19 + batch_size);
2087 fr_sibling_path block3SiblingPathIndex3 = memdb.get_sibling_path(3 + batch_size);
2088
2089 std::unique_ptr<Store> storeAtBlock2 = std::make_unique<Store>(name1, depth, 2, db1);
2090 auto treeAtBlock2 = TreeType(std::move(storeAtBlock2), multi_workers, batch_size);
2091
2092 check_root(treeAtBlock2, block2Root);
2093 check_sibling_path(treeAtBlock2, 3 + batch_size, block2SiblingPathIndex3, false, true);
2094 auto block2TreeLeaf10 = get_leaf<NullifierLeafValue>(treeAtBlock2, 7 + batch_size);
2095 EXPECT_EQ(block2TreeLeaf10.leaf.nullifier, batch1[7].nullifier);
2096
2097 check_find_leaf_index(treeAtBlock2, batch1[5], 5 + batch_size, true);
2098 check_find_leaf_index_from(treeAtBlock2, batch1[5], 0, 5 + batch_size, true);
2099
2100 // should not exist in our image
2101 get_leaf<NullifierLeafValue>(treeAtBlock2, 35 + batch_size, false, false);
2102 check_find_leaf_index<NullifierLeafValue, TreeType>(treeAtBlock2, { batch3[4] }, { std::nullopt }, true);
2103
2104 // now add the same values to our image
2105 add_values(treeAtBlock2, batch3);
2106
2107 // the state of our image should match the original tree
2108 check_sibling_path(tree1, 3 + batch_size, block3SiblingPathIndex3, false, true);
2109 check_sibling_path(tree1, 19 + batch_size, block3SiblingPathIndex19, false, true);
2110 check_sibling_path(tree1, 35 + batch_size, block3SiblingPathIndex35, false, true);
2111
2112 // needs to use uncommitted for this check
2113 check_sibling_path(treeAtBlock2, 3 + batch_size, block3SiblingPathIndex3, true, true);
2114 check_sibling_path(treeAtBlock2, 19 + batch_size, block3SiblingPathIndex19, true, true);
2115 check_sibling_path(treeAtBlock2, 35 + batch_size, block3SiblingPathIndex35, true, true);
2116
2117 // now check historic data
2118 auto historicSiblingPath = get_historic_sibling_path(treeAtBlock2, 1, 3 + batch_size);
2119 EXPECT_EQ(historicSiblingPath, block1SiblingPathIndex3);
2120 check_historic_find_leaf_index(treeAtBlock2, batch1[3], 1, 3 + batch_size, true);
2121 check_historic_find_leaf_index(treeAtBlock2, batch3[3], 2, 35 + batch_size, true, true);
2122 check_historic_find_leaf_index<NullifierLeafValue, TreeType>(
2123 treeAtBlock2, { batch3[3] }, 2, { std::nullopt }, true, false);
2124
2125 check_historic_find_leaf_index_from(treeAtBlock2, batch1[3], 2, 0, 3 + batch_size, true, false);
2126 check_historic_find_leaf_index_from<NullifierLeafValue, TreeType>(
2127 treeAtBlock2, { batch3[3] }, 2, 20 + batch_size, { std::nullopt }, true, false);
2128 check_historic_find_leaf_index_from(treeAtBlock2, batch3[3], 2, 20 + batch_size, 35 + batch_size, true, true);
2129
2130 check_unfinalized_block_height(treeAtBlock2, 2);
2131
2132 // It should be impossible to commit using the image
2133 commit_tree(treeAtBlock2, false);
2134}
2135
2137{
2138 index_t current_size = 2;
2139 ThreadPoolPtr workers = make_thread_pool(8);
2140 // Create a depth-3 indexed merkle tree
2141 constexpr size_t depth = 3;
2142 std::string name = random_string();
2143 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
2146 using LocalTreeType =
2148 auto tree = LocalTreeType(std::move(store), workers, current_size);
2149
2162 check_size(tree, current_size);
2163 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 0), zero_leaf);
2164 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 1), one_leaf);
2165
2177 commit_tree(tree);
2178 check_size(tree, ++current_size);
2179 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 0), create_indexed_public_data_leaf(0, 0, 1, 1));
2180 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 1), create_indexed_public_data_leaf(1, 0, 2, 30));
2181 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 2), create_indexed_public_data_leaf(30, 5, 0, 0));
2182
2183 auto leaf1AtBlock1 = PublicDataLeafValue(1, 0);
2184 check_block_and_size_data(db, 1, current_size, true);
2185
2197 check_size(tree, ++current_size);
2198 commit_tree(tree);
2199 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 0), create_indexed_public_data_leaf(0, 0, 1, 1));
2200 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 1), create_indexed_public_data_leaf(1, 0, 3, 10));
2201 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 2), create_indexed_public_data_leaf(30, 5, 0, 0));
2202 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 3), create_indexed_public_data_leaf(10, 20, 2, 30));
2203
2204 check_block_and_size_data(db, 2, current_size, true);
2205
2206 auto leaf2AtBlock2 = PublicDataLeafValue(30, 5);
2207 check_historic_leaf(tree, leaf1AtBlock1, 1, 1, true);
2208
2209 // shoudl find this leaf at both blocks 1 and 2 as it looks for the slot which doesn't change
2210 check_historic_find_leaf_index(tree, leaf1AtBlock1, 1, 1, true);
2211 check_historic_find_leaf_index(tree, leaf1AtBlock1, 2, 1, true);
2212
2224 check_size(tree, current_size);
2225 commit_tree(tree);
2226 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 0), create_indexed_public_data_leaf(0, 0, 1, 1));
2227 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 1), create_indexed_public_data_leaf(1, 0, 3, 10));
2228 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 2), create_indexed_public_data_leaf(30, 6, 0, 0));
2229 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 3), create_indexed_public_data_leaf(10, 20, 2, 30));
2230
2231 check_block_and_size_data(db, 3, current_size, true);
2232
2233 auto leaf2AtBlock3 = PublicDataLeafValue(30, 6);
2234 check_historic_leaf(tree, leaf2AtBlock2, 2, 2, true);
2235
2236 // should find this leaf at both blocks 1 and 2 as it looks for the slot which doesn't change
2237 check_historic_find_leaf_index(tree, leaf1AtBlock1, 1, 1, true);
2238 check_historic_find_leaf_index(tree, leaf1AtBlock1, 2, 1, true);
2239
2251 check_size(tree, ++current_size);
2252 commit_tree(tree);
2253 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 0), create_indexed_public_data_leaf(0, 0, 1, 1));
2254 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 1), create_indexed_public_data_leaf(1, 0, 3, 10));
2255 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 2), create_indexed_public_data_leaf(30, 6, 4, 50));
2256 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 3), create_indexed_public_data_leaf(10, 20, 2, 30));
2257 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 4), create_indexed_public_data_leaf(50, 8, 0, 0));
2258
2259 check_block_and_size_data(db, 4, current_size, true);
2260
2261 check_historic_leaf(tree, leaf2AtBlock3, 2, 3, true);
2262
2263 // should not be found at block 1
2264 check_historic_find_leaf_index_from<PublicDataLeafValue, LocalTreeType>(
2265 tree, { PublicDataLeafValue(10, 20) }, 1, 0, { std::nullopt }, true);
2266 // should be found at block
2267 check_historic_find_leaf_index_from(tree, PublicDataLeafValue(10, 20), 2, 0, 3, true);
2268
2270 EXPECT_EQ(lowLeaf.index, 1);
2271
2272 lowLeaf = get_historic_low_leaf(tree, 2, PublicDataLeafValue(20, 0));
2273 EXPECT_EQ(lowLeaf.index, 3);
2274
2275 lowLeaf = get_historic_low_leaf(tree, 2, PublicDataLeafValue(60, 0));
2276 EXPECT_EQ(lowLeaf.index, 2);
2277
2278 finalize_block(tree, 3);
2279
2280 // remove historical block 1
2281 remove_historic_block(tree, 1);
2282
2283 // Historic queries against block 1 should no longer work
2284 check_historic_leaf(tree, leaf1AtBlock1, 1, 1, false);
2285 check_historic_find_leaf_index<PublicDataLeafValue, LocalTreeType>(
2286 tree, { leaf1AtBlock1 }, 1, { std::nullopt }, false);
2287
2288 // Queries against block 2 should work
2289 check_historic_leaf(tree, leaf2AtBlock2, 2, 2, true);
2290 check_historic_find_leaf_index(tree, leaf1AtBlock1, 2, 1, true);
2291
2292 // now remove block 2 and queries against it should no longer work
2293 remove_historic_block(tree, 2);
2294 check_historic_leaf(tree, leaf2AtBlock2, 2, 2, false);
2295
2296 // size doesn't matter, should fail to find the data
2297 check_block_and_size_data(db, 1, current_size, false);
2298}
2299
2301{
2302 index_t current_size = 2;
2303 ThreadPoolPtr workers = make_thread_pool(8);
2304 // Create a depth-3 indexed merkle tree
2305 constexpr size_t depth = 3;
2306 std::string name = random_string();
2307 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
2310 using LocalTreeType =
2312 auto tree = LocalTreeType(std::move(store), workers, current_size);
2313
2326 check_size(tree, current_size);
2327 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 0), zero_leaf);
2328 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 1), one_leaf);
2329
2330 check_leaf_by_hash<PublicDataLeafValue, HashPolicy>(db, zero_leaf, true);
2331 check_leaf_by_hash<PublicDataLeafValue, HashPolicy>(db, one_leaf, true);
2332
2333 check_indices_data(db, 0, 0, true, true);
2334 check_indices_data(db, 1, 1, true, true);
2335
2347 commit_tree(tree);
2348 check_size(tree, ++current_size);
2349
2350 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 0), create_indexed_public_data_leaf(0, 0, 1, 1));
2351 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 1), create_indexed_public_data_leaf(1, 0, 2, 30));
2352 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 2), create_indexed_public_data_leaf(30, 5, 0, 0));
2353
2354 check_block_and_size_data(db, 1, current_size, true);
2355
2356 // All historical pre-images should be present
2357 check_leaf_by_hash<PublicDataLeafValue, HashPolicy>(db, zero_leaf, true);
2358 check_leaf_by_hash<PublicDataLeafValue, HashPolicy>(db, one_leaf, true);
2359 check_leaf_by_hash<PublicDataLeafValue, HashPolicy>(db, create_indexed_public_data_leaf(1, 0, 2, 30), true);
2360 check_leaf_by_hash<PublicDataLeafValue, HashPolicy>(db, create_indexed_public_data_leaf(30, 5, 0, 0), true);
2361
2362 check_indices_data(db, 30, 2, true, true);
2363
2364 auto leaf1AtBlock1 = PublicDataLeafValue(1, 0);
2365
2377 check_size(tree, ++current_size);
2378 commit_tree(tree);
2379 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 0), create_indexed_public_data_leaf(0, 0, 1, 1));
2380 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 1), create_indexed_public_data_leaf(1, 0, 3, 10));
2381 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 2), create_indexed_public_data_leaf(30, 5, 0, 0));
2382 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 3), create_indexed_public_data_leaf(10, 20, 2, 30));
2383
2384 // All historical pre-images should be present
2385 check_leaf_by_hash<PublicDataLeafValue, HashPolicy>(db, zero_leaf, true);
2386 check_leaf_by_hash<PublicDataLeafValue, HashPolicy>(db, one_leaf, true);
2387 check_leaf_by_hash<PublicDataLeafValue, HashPolicy>(db, create_indexed_public_data_leaf(1, 0, 3, 10), true);
2388 check_leaf_by_hash<PublicDataLeafValue, HashPolicy>(db, create_indexed_public_data_leaf(30, 5, 0, 0), true);
2389 check_leaf_by_hash<PublicDataLeafValue, HashPolicy>(db, create_indexed_public_data_leaf(10, 20, 2, 30), true);
2390
2391 check_indices_data(db, 10, 3, true, true);
2392
2393 check_block_and_size_data(db, 2, current_size, true);
2394
2395 auto leaf2AtBlock2 = PublicDataLeafValue(30, 5);
2396 check_historic_leaf(tree, leaf1AtBlock1, 1, 1, true);
2397
2398 // shoudl find this leaf at both blocks 1 and 2 as it looks for the slot which doesn't change
2399 check_historic_find_leaf_index(tree, leaf1AtBlock1, 1, 1, true);
2400 check_historic_find_leaf_index(tree, leaf1AtBlock1, 2, 1, true);
2401
2413 check_size(tree, current_size);
2414 commit_tree(tree);
2415 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 0), create_indexed_public_data_leaf(0, 0, 1, 1));
2416 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 1), create_indexed_public_data_leaf(1, 0, 3, 10));
2417 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 2), create_indexed_public_data_leaf(30, 6, 0, 0));
2418 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 3), create_indexed_public_data_leaf(10, 20, 2, 30));
2419
2420 // All historical pre-images should be present
2421 check_leaf_by_hash<PublicDataLeafValue, HashPolicy>(db, zero_leaf, true);
2422 check_leaf_by_hash<PublicDataLeafValue, HashPolicy>(db, one_leaf, true);
2423 check_leaf_by_hash<PublicDataLeafValue, HashPolicy>(db, create_indexed_public_data_leaf(1, 0, 3, 10), true);
2424 check_leaf_by_hash<PublicDataLeafValue, HashPolicy>(db, create_indexed_public_data_leaf(30, 5, 0, 0), true);
2425 check_leaf_by_hash<PublicDataLeafValue, HashPolicy>(db, create_indexed_public_data_leaf(10, 20, 2, 30), true);
2426 check_leaf_by_hash<PublicDataLeafValue, HashPolicy>(db, create_indexed_public_data_leaf(30, 5, 0, 0), true);
2427 check_leaf_by_hash<PublicDataLeafValue, HashPolicy>(db, create_indexed_public_data_leaf(30, 6, 0, 0), true);
2428
2429 // Zero leaves should not have their indices added
2430 check_indices_data(db, 0, 4, true, false);
2431
2432 check_block_and_size_data(db, 3, current_size, true);
2433
2434 auto leaf2AtBlock3 = PublicDataLeafValue(30, 6);
2435 check_historic_leaf(tree, leaf2AtBlock2, 2, 2, true);
2436
2437 // should find this leaf at both blocks 1 and 2 as it looks for the slot which doesn't change
2438 check_historic_find_leaf_index(tree, leaf1AtBlock1, 1, 1, true);
2439 check_historic_find_leaf_index(tree, leaf1AtBlock1, 2, 1, true);
2440
2452 check_size(tree, ++current_size);
2453 commit_tree(tree);
2454 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 0), create_indexed_public_data_leaf(0, 0, 1, 1));
2455 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 1), create_indexed_public_data_leaf(1, 0, 3, 10));
2456 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 2), create_indexed_public_data_leaf(30, 6, 4, 50));
2457 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 3), create_indexed_public_data_leaf(10, 20, 2, 30));
2458 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 4), create_indexed_public_data_leaf(50, 8, 0, 0));
2459
2460 check_indices_data(db, 50, 4, true, true);
2461 // All historical pre-images should be present
2462 check_leaf_by_hash<PublicDataLeafValue, HashPolicy>(db, zero_leaf, true);
2463 check_leaf_by_hash<PublicDataLeafValue, HashPolicy>(db, one_leaf, true);
2464 check_leaf_by_hash<PublicDataLeafValue, HashPolicy>(db, create_indexed_public_data_leaf(1, 0, 3, 10), true);
2465 check_leaf_by_hash<PublicDataLeafValue, HashPolicy>(db, create_indexed_public_data_leaf(30, 5, 0, 0), true);
2466 check_leaf_by_hash<PublicDataLeafValue, HashPolicy>(db, create_indexed_public_data_leaf(10, 20, 2, 30), true);
2467 check_leaf_by_hash<PublicDataLeafValue, HashPolicy>(db, create_indexed_public_data_leaf(30, 6, 0, 0), true);
2468 check_leaf_by_hash<PublicDataLeafValue, HashPolicy>(db, create_indexed_public_data_leaf(50, 8, 0, 0), true);
2469 check_leaf_by_hash<PublicDataLeafValue, HashPolicy>(db, create_indexed_public_data_leaf(30, 6, 4, 50), true);
2470
2471 check_block_and_size_data(db, 4, current_size, true);
2472
2473 check_historic_leaf(tree, leaf2AtBlock3, 2, 3, true);
2474
2475 // should not be found at block 1
2476 check_historic_find_leaf_index_from<PublicDataLeafValue, LocalTreeType>(
2477 tree, { PublicDataLeafValue(10, 20) }, 1, 0, { std::nullopt }, true);
2478 // should be found at block
2479 check_historic_find_leaf_index_from(tree, PublicDataLeafValue(10, 20), 2, 0, 3, true);
2480
2482 EXPECT_EQ(lowLeaf.index, 1);
2483
2484 lowLeaf = get_historic_low_leaf(tree, 2, PublicDataLeafValue(20, 0));
2485 EXPECT_EQ(lowLeaf.index, 3);
2486
2487 lowLeaf = get_historic_low_leaf(tree, 2, PublicDataLeafValue(60, 0));
2488 EXPECT_EQ(lowLeaf.index, 2);
2489
2490 unwind_block(tree, 4);
2491
2492 // Index 4 should be removed
2493 check_indices_data(db, 50, 4, false, false);
2494 // The pre-images created before block 4 should be present
2495 check_leaf_by_hash<PublicDataLeafValue, HashPolicy>(db, zero_leaf, true);
2496 check_leaf_by_hash<PublicDataLeafValue, HashPolicy>(db, one_leaf, true);
2497 check_leaf_by_hash<PublicDataLeafValue, HashPolicy>(db, create_indexed_public_data_leaf(1, 0, 3, 10), true);
2498 check_leaf_by_hash<PublicDataLeafValue, HashPolicy>(db, create_indexed_public_data_leaf(30, 5, 0, 0), true);
2499 check_leaf_by_hash<PublicDataLeafValue, HashPolicy>(db, create_indexed_public_data_leaf(10, 20, 2, 30), true);
2500 check_leaf_by_hash<PublicDataLeafValue, HashPolicy>(db, create_indexed_public_data_leaf(30, 5, 0, 0), true);
2501 check_leaf_by_hash<PublicDataLeafValue, HashPolicy>(db, create_indexed_public_data_leaf(30, 6, 0, 0), true);
2502
2503 // The pre-images created in block 4 should be gone
2504 check_leaf_by_hash<PublicDataLeafValue, HashPolicy>(db, create_indexed_public_data_leaf(50, 8, 0, 0), false);
2505 check_leaf_by_hash<PublicDataLeafValue, HashPolicy>(db, create_indexed_public_data_leaf(30, 6, 4, 50), false);
2506
2507 check_size(tree, --current_size);
2508
2509 // should fail to find block 4
2510 check_block_and_size_data(db, 4, current_size, false);
2511
2512 // block 3 should work
2513 check_block_and_size_data(db, 3, current_size, true);
2514
2515 // should fail to find the leaf at index 4
2516 check_find_leaf_index<PublicDataLeafValue, LocalTreeType>(
2517 tree, { PublicDataLeafValue(50, 8) }, { std::nullopt }, true);
2518 check_find_leaf_index_from<PublicDataLeafValue, LocalTreeType>(
2519 tree, { PublicDataLeafValue(50, 8) }, 0, { std::nullopt }, true);
2520
2521 // the leaf at index 2 should no longer be as it was after block 5
2522 EXPECT_NE(get_leaf<PublicDataLeafValue>(tree, 2), create_indexed_public_data_leaf(30, 6, 4, 50));
2523
2524 // it should be as it was after block 4
2525 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 2), create_indexed_public_data_leaf(30, 6, 0, 0));
2526
2527 unwind_block(tree, 3);
2528
2529 // The pre-images created before block 3 should be present
2530 check_leaf_by_hash<PublicDataLeafValue, HashPolicy>(db, zero_leaf, true);
2531 check_leaf_by_hash<PublicDataLeafValue, HashPolicy>(db, one_leaf, true);
2532 check_leaf_by_hash<PublicDataLeafValue, HashPolicy>(db, create_indexed_public_data_leaf(1, 0, 3, 10), true);
2533 check_leaf_by_hash<PublicDataLeafValue, HashPolicy>(db, create_indexed_public_data_leaf(30, 5, 0, 0), true);
2534 check_leaf_by_hash<PublicDataLeafValue, HashPolicy>(db, create_indexed_public_data_leaf(10, 20, 2, 30), true);
2535
2536 check_size(tree, current_size);
2537
2538 // the leaf at index 2 should no longer be as it was after block 4
2539 EXPECT_NE(get_leaf<PublicDataLeafValue>(tree, 2), create_indexed_public_data_leaf(30, 6, 0, 0));
2540
2541 // it should be as it was after block 3
2542 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 2), create_indexed_public_data_leaf(30, 5, 0, 0));
2543}
2544
2546{
2547 index_t current_size = 2;
2548 ThreadPoolPtr workers = make_thread_pool(8);
2549 // Create a depth-3 indexed merkle tree
2550 constexpr size_t depth = 3;
2551 std::string name = random_string();
2552 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
2556 std::move(store), workers, current_size);
2557
2570 check_size(tree, current_size);
2571 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 0), zero_leaf);
2572 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 1), one_leaf);
2573
2585 commit_tree(tree);
2586 check_size(tree, ++current_size);
2587 fr rootAfterBlock1 = get_root(tree, false);
2588 fr_sibling_path pathAfterBlock1 = get_sibling_path(tree, 0, false);
2589 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 0), create_indexed_public_data_leaf(0, 0, 1, 1));
2590 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 1), create_indexed_public_data_leaf(1, 0, 2, 30));
2591 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 2), create_indexed_public_data_leaf(30, 5, 0, 0));
2592
2593 check_block_and_size_data(db, 1, current_size, true);
2594
2595 // All historical pre-images should be present
2596 check_leaf_by_hash<PublicDataLeafValue, HashPolicy>(db, zero_leaf, true);
2597 check_leaf_by_hash<PublicDataLeafValue, HashPolicy>(db, one_leaf, true);
2598 check_leaf_by_hash<PublicDataLeafValue, HashPolicy>(db, create_indexed_public_data_leaf(1, 0, 2, 30), true);
2599 check_leaf_by_hash<PublicDataLeafValue, HashPolicy>(db, create_indexed_public_data_leaf(30, 5, 0, 0), true);
2600
2601 check_indices_data(db, 30, 2, true, true);
2602
2614 commit_tree(tree);
2615 check_size(tree, current_size);
2616 fr rootAfterBlock2 = get_root(tree, false);
2617 fr_sibling_path pathAfterBlock2 = get_sibling_path(tree, 0, false);
2618 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 0), create_indexed_public_data_leaf(0, 0, 1, 1));
2619 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 1), create_indexed_public_data_leaf(1, 0, 2, 30));
2620 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 2), create_indexed_public_data_leaf(30, 8, 0, 0));
2621
2622 // All historical pre-images should be present
2623 check_leaf_by_hash<PublicDataLeafValue, HashPolicy>(db, zero_leaf, true);
2624 check_leaf_by_hash<PublicDataLeafValue, HashPolicy>(db, one_leaf, true);
2625 check_leaf_by_hash<PublicDataLeafValue, HashPolicy>(db, create_indexed_public_data_leaf(1, 0, 2, 30), true);
2626 check_leaf_by_hash<PublicDataLeafValue, HashPolicy>(db, create_indexed_public_data_leaf(30, 5, 0, 0), true);
2627 check_leaf_by_hash<PublicDataLeafValue, HashPolicy>(db, create_indexed_public_data_leaf(30, 8, 0, 0), true);
2628
2629 check_indices_data(db, 30, 2, true, true);
2630
2642 commit_tree(tree);
2643 check_size(tree, current_size);
2644 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 0), create_indexed_public_data_leaf(0, 0, 1, 1));
2645 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 1), create_indexed_public_data_leaf(1, 0, 2, 30));
2646 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 2), create_indexed_public_data_leaf(30, 5, 0, 0));
2647
2648 // All historical pre-images should be present
2649 check_leaf_by_hash<PublicDataLeafValue, HashPolicy>(db, zero_leaf, true);
2650 check_leaf_by_hash<PublicDataLeafValue, HashPolicy>(db, one_leaf, true);
2651 check_leaf_by_hash<PublicDataLeafValue, HashPolicy>(db, create_indexed_public_data_leaf(1, 0, 2, 30), true);
2652 check_leaf_by_hash<PublicDataLeafValue, HashPolicy>(db, create_indexed_public_data_leaf(30, 5, 0, 0), true);
2653 check_leaf_by_hash<PublicDataLeafValue, HashPolicy>(db, create_indexed_public_data_leaf(30, 8, 0, 0), true);
2654
2655 check_indices_data(db, 30, 2, true, true);
2656
2657 // Unwind block 3 and the state should be reverted back to block 2
2658 unwind_block(tree, 3);
2659
2660 check_root(tree, rootAfterBlock2);
2661 check_sibling_path(tree, 0, pathAfterBlock2, false);
2662 check_size(tree, current_size);
2663 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 0), create_indexed_public_data_leaf(0, 0, 1, 1));
2664 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 1), create_indexed_public_data_leaf(1, 0, 2, 30));
2665 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 2), create_indexed_public_data_leaf(30, 8, 0, 0));
2666
2667 // All historical pre-images should be present
2668 check_leaf_by_hash<PublicDataLeafValue, HashPolicy>(db, zero_leaf, true);
2669 check_leaf_by_hash<PublicDataLeafValue, HashPolicy>(db, one_leaf, true);
2670 check_leaf_by_hash<PublicDataLeafValue, HashPolicy>(db, create_indexed_public_data_leaf(1, 0, 2, 30), true);
2671 check_leaf_by_hash<PublicDataLeafValue, HashPolicy>(db, create_indexed_public_data_leaf(30, 5, 0, 0), true);
2672 check_leaf_by_hash<PublicDataLeafValue, HashPolicy>(db, create_indexed_public_data_leaf(30, 8, 0, 0), true);
2673
2674 check_indices_data(db, 30, 2, true, true);
2675
2676 // Unwind block 2 and the state should be reverted back to block 1
2677 unwind_block(tree, 2);
2678
2679 check_root(tree, rootAfterBlock1);
2680 check_sibling_path(tree, 0, pathAfterBlock1, false);
2681 check_size(tree, current_size);
2682 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 0), create_indexed_public_data_leaf(0, 0, 1, 1));
2683 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 1), create_indexed_public_data_leaf(1, 0, 2, 30));
2684 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 2), create_indexed_public_data_leaf(30, 5, 0, 0));
2685
2686 // All historical pre-images should be present
2687 check_leaf_by_hash<PublicDataLeafValue, HashPolicy>(db, zero_leaf, true);
2688 check_leaf_by_hash<PublicDataLeafValue, HashPolicy>(db, one_leaf, true);
2689 check_leaf_by_hash<PublicDataLeafValue, HashPolicy>(db, create_indexed_public_data_leaf(1, 0, 2, 30), true);
2690 check_leaf_by_hash<PublicDataLeafValue, HashPolicy>(db, create_indexed_public_data_leaf(30, 5, 0, 0), true);
2691
2692 // Check the pre-image was removed
2693 check_leaf_by_hash<PublicDataLeafValue, HashPolicy>(db, create_indexed_public_data_leaf(30, 8, 0, 0), false);
2694
2695 check_indices_data(db, 30, 2, true, true);
2696
2697 // Now apply block 2 again and it should be moved forward back tot where it was
2698 add_value(tree, PublicDataLeafValue(30, 8));
2699 commit_tree(tree);
2700 check_size(tree, ++current_size);
2701 check_root(tree, rootAfterBlock2);
2702 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 0), create_indexed_public_data_leaf(0, 0, 1, 1));
2703 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 1), create_indexed_public_data_leaf(1, 0, 2, 30));
2704 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 2), create_indexed_public_data_leaf(30, 8, 0, 0));
2705}
2706
2707void test_nullifier_tree_unwind(std::string directory,
2708 std::string name,
2709 uint64_t mapSize,
2710 uint64_t maxReaders,
2711 uint32_t depth,
2712 uint32_t blockSize,
2713 uint32_t numBlocks,
2714 uint32_t numBlocksToUnwind,
2715 std::vector<fr> values)
2716{
2717 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(directory, name, mapSize, maxReaders);
2720 TreeType tree(std::move(store), pool, blockSize);
2721 NullifierMemoryTree<Poseidon2HashPolicy> memdb(depth, blockSize);
2722
2723 auto it = std::find_if(values.begin(), values.end(), [&](const fr& v) { return v != fr::zero(); });
2724 bool emptyBlocks = it == values.end();
2725
2726 uint32_t batchSize = blockSize;
2727
2728 std::vector<fr_sibling_path> historicPathsZeroIndex;
2729 std::vector<fr_sibling_path> historicPathsMaxIndex;
2730 std::vector<fr> roots;
2731
2732 fr initialRoot = memdb.root();
2733 fr_sibling_path initialPath = memdb.get_sibling_path(0);
2734
2736 leafValues.reserve(values.size());
2737 for (const fr& v : values) {
2738 leafValues.emplace_back(v);
2739 }
2740
2741 for (uint32_t i = 0; i < numBlocks; i++) {
2743
2744 for (size_t j = 0; j < batchSize; ++j) {
2745 size_t ind = i * batchSize + j;
2746 memdb.update_element(values[ind]);
2747 to_add.push_back(leafValues[ind]);
2748 }
2749 // Indexed trees have an initial 'batch' inserted at startup
2750 index_t expected_size = (i + 2) * batchSize;
2751 add_values(tree, to_add);
2752 commit_tree(tree);
2753
2754 historicPathsZeroIndex.push_back(memdb.get_sibling_path(0));
2755 historicPathsMaxIndex.push_back(memdb.get_sibling_path(expected_size - 1));
2756 roots.push_back(memdb.root());
2757 check_root(tree, memdb.root());
2758 check_sibling_path(tree, 0, memdb.get_sibling_path(0));
2759 check_sibling_path(tree, expected_size - 1, memdb.get_sibling_path(expected_size - 1));
2760 check_size(tree, expected_size);
2761 check_block_and_size_data(db, i + 1, expected_size, true);
2762 check_block_and_root_data(db, i + 1, memdb.root(), true);
2763 }
2764
2765 const uint32_t blocksToRemove = numBlocksToUnwind;
2766 for (uint32_t i = 0; i < blocksToRemove; i++) {
2767 const block_number_t blockNumber = numBlocks - i;
2768
2769 check_block_and_root_data(db, blockNumber, roots[blockNumber - 1], true);
2770 unwind_block(tree, blockNumber);
2771 if (emptyBlocks) {
2772 // with empty blocks, we should not find the block data but we do find the root
2773 check_block_and_root_data(db, blockNumber, roots[blockNumber - 1], false, true);
2774 } else {
2775 // if blocks are not empty, this query should fail
2776 check_block_and_root_data(db, blockNumber, roots[blockNumber - 1], false);
2777 }
2778
2779 const index_t previousValidBlock = blockNumber - 1;
2780 // Indexed trees have an initial 'batch' inserted at startup
2781 index_t deletedBlockStartIndex = (1 + previousValidBlock) * batchSize;
2782 index_t deletedBlockStartIndexIntoLocalValues = previousValidBlock * batchSize;
2783
2784 check_block_height(tree, previousValidBlock);
2785 check_size(tree, deletedBlockStartIndex);
2786 check_root(tree, previousValidBlock == 0 ? initialRoot : roots[previousValidBlock - 1]);
2787
2788 // The zero index sibling path should be as it was at the previous block
2789 check_sibling_path(tree,
2790 0,
2791 previousValidBlock == 0 ? initialPath : historicPathsZeroIndex[previousValidBlock - 1],
2792 false,
2793 true);
2794
2795 if (!emptyBlocks) {
2796 // Trying to find leaves appended in the block that was removed should fail
2797 get_leaf<NullifierLeafValue>(tree, 1 + deletedBlockStartIndex, false, false);
2798
2799 check_find_leaf_index<NullifierLeafValue, TreeType>(
2800 tree, { leafValues[1 + deletedBlockStartIndexIntoLocalValues] }, { std::nullopt }, true);
2801 }
2802
2803 for (index_t j = 0; j < numBlocks; j++) {
2804 block_number_t historicBlockNumber = static_cast<block_number_t>(j + 1);
2805 bool expectedSuccess = historicBlockNumber <= previousValidBlock;
2807 tree, 0, historicBlockNumber, historicPathsZeroIndex[j], false, expectedSuccess);
2808 index_t maxSizeAtBlock = ((j + 2) * batchSize) - 1;
2810 tree, maxSizeAtBlock, historicBlockNumber, historicPathsMaxIndex[j], false, expectedSuccess);
2811
2812 if (emptyBlocks) {
2813 continue;
2814 }
2815 const index_t leafIndex = 1;
2816 const index_t expectedIndexInTree = leafIndex + batchSize;
2818 tree, leafValues[leafIndex], expectedIndexInTree, historicBlockNumber, expectedSuccess, false);
2819
2820 std::vector<std::optional<index_t>> expectedResults;
2821 if (expectedSuccess) {
2822 expectedResults.emplace_back(std::make_optional(expectedIndexInTree));
2823 }
2824 check_historic_find_leaf_index<NullifierLeafValue, TreeType>(
2825 tree, { leafValues[leafIndex] }, historicBlockNumber, expectedResults, expectedSuccess, true);
2826 check_historic_find_leaf_index_from<NullifierLeafValue, TreeType>(
2827 tree, { leafValues[leafIndex] }, historicBlockNumber, 0, expectedResults, expectedSuccess, true);
2828 }
2829 }
2830}
2831
2833{
2834
2835 constexpr uint32_t numBlocks = 8;
2836 constexpr uint32_t numBlocksToUnwind = 4;
2837 std::vector<uint32_t> blockSizes = { 2, 4, 8, 16, 32 };
2838 for (const uint32_t& size : blockSizes) {
2839 uint32_t actualSize = size;
2840 std::vector<fr> values = create_values(actualSize * numBlocks);
2841 std::stringstream ss;
2842 ss << "DB " << actualSize;
2844 _directory, ss.str(), _mapSize, _maxReaders, 20, actualSize, numBlocks, numBlocksToUnwind, values);
2845 }
2846}
2847
2848TEST_F(PersistedContentAddressedIndexedTreeTest, can_sync_and_unwind_empty_blocks)
2849{
2850
2851 constexpr uint32_t numBlocks = 8;
2852 constexpr uint32_t numBlocksToUnwind = 4;
2853 std::vector<uint32_t> blockSizes = { 2, 4, 8, 16, 32 };
2854 for (const uint32_t& size : blockSizes) {
2855 uint32_t actualSize = size;
2856 std::vector<fr> values = std::vector<fr>(actualSize * numBlocks, fr::zero());
2857 std::stringstream ss;
2858 ss << "DB " << actualSize;
2860 _directory, ss.str(), _mapSize, _maxReaders, 20, actualSize, numBlocks, numBlocksToUnwind, values);
2861 }
2862}
2863
2865{
2866 ThreadPoolPtr workers = make_thread_pool(1);
2867 constexpr size_t depth = 3;
2868 std::string name = random_string();
2869 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
2871
2872 index_t initial_size = 4;
2874 auto tree = PublicDataTreeType(std::move(store), workers, initial_size, prefilled_values);
2875
2890 check_size(tree, initial_size);
2891 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 0), leaf_0);
2892 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 1), leaf_1);
2893 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 2), leaf_2);
2894 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 3), leaf_3);
2895}
2896
2897// Regression: a low leaf whose value is zero (the head of the list) must not be treated as padding when it
2898// is re-hashed. Inserting into the gap beneath such a head leaf previously zeroed its hash and corrupted
2899// the root. The leaf only hashes to zero when its next pointers are also zero (i.e. the true padding leaf).
2900TEST_F(PersistedContentAddressedIndexedTreeTest, low_leaf_with_zero_value_is_not_treated_as_padding)
2901{
2902 ThreadPoolPtr workers = make_thread_pool(1);
2903 constexpr size_t depth = 4;
2904 std::string name = random_string();
2905 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
2907
2908 // index 0 = {nullifier=0, nextIndex=1, nextKey=1000}, index 1 = {nullifier=1000, nextIndex=0, nextKey=0}
2909 index_t initial_size = 2;
2910 std::vector<NullifierLeafValue> prefilled_values = { NullifierLeafValue(1000) };
2911 auto tree = TreeType(std::move(store), workers, initial_size, prefilled_values);
2912
2913 // Insert into the (0, 1000) gap; index 0 (value 0) is selected as the low leaf and re-hashed.
2914 add_value(tree, NullifierLeafValue(500));
2915
2916 // Expected post-insert leaves.
2917 IndexedNullifierLeafType expected_leaf_0 = create_indexed_nullifier_leaf(0, 2, 500);
2918 IndexedNullifierLeafType expected_leaf_1 = create_indexed_nullifier_leaf(1000, 0, 0);
2919 IndexedNullifierLeafType expected_leaf_2 = create_indexed_nullifier_leaf(500, 1, 1000);
2920 EXPECT_EQ(get_leaf<NullifierLeafValue>(tree, 0), expected_leaf_0);
2921
2922 // Independently compute the expected root; the low leaf must contribute its real hash, not zero.
2923 MemoryTree<HashPolicy> expected(depth);
2924 expected.update_element(0, HashPolicy::hash(expected_leaf_0.get_hash_inputs()));
2925 expected.update_element(1, HashPolicy::hash(expected_leaf_1.get_hash_inputs()));
2926 expected.update_element(2, HashPolicy::hash(expected_leaf_2.get_hash_inputs()));
2927 check_root(tree, expected.root());
2928}
2929
2930TEST_F(PersistedContentAddressedIndexedTreeTest, test_full_prefilled_public_data)
2931{
2932 ThreadPoolPtr workers = make_thread_pool(1);
2933 constexpr size_t depth = 3;
2934 std::string name = random_string();
2935 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
2937
2938 index_t initial_size = 4;
2939 std::vector<PublicDataLeafValue> prefilled_values = {
2941 };
2942 auto tree = PublicDataTreeType(std::move(store), workers, initial_size, prefilled_values);
2943
2958 check_size(tree, initial_size);
2959 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 0), leaf_0);
2960 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 1), leaf_1);
2961 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 2), leaf_2);
2962 EXPECT_EQ(get_leaf<PublicDataLeafValue>(tree, 3), leaf_3);
2963}
2964
2965TEST_F(PersistedContentAddressedIndexedTreeTest, test_prefilled_unsorted_public_data_should_fail)
2966{
2967 ThreadPoolPtr workers = make_thread_pool(1);
2968 constexpr size_t depth = 3;
2969 std::string name = random_string();
2970 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
2972
2973 index_t initial_size = 4;
2974 // The prefilled values are not sorted: 5 > 3.
2976 EXPECT_THROW(PublicDataTreeType(std::move(store), workers, initial_size, prefilled_values), std::runtime_error);
2977}
2978
2979TEST_F(PersistedContentAddressedIndexedTreeTest, test_prefilled_default_public_data_should_fail)
2980{
2981 ThreadPoolPtr workers = make_thread_pool(1);
2982 constexpr size_t depth = 3;
2983 std::string name = random_string();
2984 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
2986
2987 index_t initial_size = 4;
2988 // The first prefilled value is the same as one of the default values (1).
2990 EXPECT_THROW(PublicDataTreeType(std::move(store), workers, initial_size, prefilled_values), std::runtime_error);
2991}
2992
2993TEST_F(PersistedContentAddressedIndexedTreeTest, test_can_commit_and_revert_checkpoints)
2994{
2995 index_t initial_size = 2;
2996 index_t current_size = initial_size;
2997 ThreadPoolPtr workers = make_thread_pool(8);
2998 // Create a depth-3 indexed merkle tree
2999 constexpr size_t depth = 3;
3000 std::string name = random_string();
3001 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
3005 std::move(store), workers, current_size);
3006
3029 check_size(tree, ++current_size);
3030
3042 check_size(tree, ++current_size);
3043
3055 // The size does not increase since sequential insertion doesn't pad
3056 check_size(tree, current_size);
3057 commit_tree(tree);
3058
3059 {
3060 index_t fork_size = current_size;
3063 auto forkTree =
3065 std::move(forkStore), workers, initial_size);
3066
3067 // Find the low leaf of slot 60
3068 auto predecessor = get_low_leaf(forkTree, PublicDataLeafValue(60, 5));
3069
3070 // It should be at index 2
3071 EXPECT_EQ(predecessor.is_already_present, false);
3072 EXPECT_EQ(predecessor.index, 2);
3073
3074 // checkpoint the fork
3075 checkpoint_tree(forkTree);
3076
3088 check_size(forkTree, ++fork_size);
3089 EXPECT_EQ(get_leaf<PublicDataLeafValue>(forkTree, 0), create_indexed_public_data_leaf(0, 0, 1, 1));
3090 EXPECT_EQ(get_leaf<PublicDataLeafValue>(forkTree, 1), create_indexed_public_data_leaf(1, 0, 3, 10));
3091 EXPECT_EQ(get_leaf<PublicDataLeafValue>(forkTree, 2), create_indexed_public_data_leaf(30, 6, 4, 50));
3092 EXPECT_EQ(get_leaf<PublicDataLeafValue>(forkTree, 3), create_indexed_public_data_leaf(10, 20, 2, 30));
3093 EXPECT_EQ(get_leaf<PublicDataLeafValue>(forkTree, 4), create_indexed_public_data_leaf(50, 8, 0, 0));
3094
3095 // Find the low leaf of slot 60
3096 predecessor = get_low_leaf(forkTree, PublicDataLeafValue(60, 5));
3097
3098 // It should be at index 4
3099 EXPECT_EQ(predecessor.is_already_present, false);
3100 EXPECT_EQ(predecessor.index, 4);
3101
3102 // Now revert the fork and see that it is rolled back to the checkpoint
3103 revert_checkpoint_tree(forkTree);
3104 check_size(forkTree, --fork_size);
3105 EXPECT_EQ(get_leaf<PublicDataLeafValue>(forkTree, 0), create_indexed_public_data_leaf(0, 0, 1, 1));
3106 EXPECT_EQ(get_leaf<PublicDataLeafValue>(forkTree, 1), create_indexed_public_data_leaf(1, 0, 3, 10));
3107 EXPECT_EQ(get_leaf<PublicDataLeafValue>(forkTree, 2), create_indexed_public_data_leaf(30, 6, 0, 0));
3108 EXPECT_EQ(get_leaf<PublicDataLeafValue>(forkTree, 3), create_indexed_public_data_leaf(10, 20, 2, 30));
3109
3110 // Find the low leaf of slot 60
3111 predecessor = get_low_leaf(forkTree, PublicDataLeafValue(60, 5));
3112
3113 // It should be back at index 2
3114 EXPECT_EQ(predecessor.is_already_present, false);
3115 EXPECT_EQ(predecessor.index, 2);
3116
3117 // checkpoint the fork again
3118 checkpoint_tree(forkTree);
3119
3120 // We now advance the fork again by a few checkpoints
3121
3133 // Make the same change again, commit the checkpoint and see that the changes remain
3135 check_size(forkTree, ++fork_size);
3136 EXPECT_EQ(get_leaf<PublicDataLeafValue>(forkTree, 0), create_indexed_public_data_leaf(0, 0, 1, 1));
3137 EXPECT_EQ(get_leaf<PublicDataLeafValue>(forkTree, 1), create_indexed_public_data_leaf(1, 0, 3, 10));
3138 EXPECT_EQ(get_leaf<PublicDataLeafValue>(forkTree, 2), create_indexed_public_data_leaf(30, 6, 4, 50));
3139 EXPECT_EQ(get_leaf<PublicDataLeafValue>(forkTree, 3), create_indexed_public_data_leaf(10, 20, 2, 30));
3140 EXPECT_EQ(get_leaf<PublicDataLeafValue>(forkTree, 4), create_indexed_public_data_leaf(50, 8, 0, 0));
3141
3142 // Find the low leaf of slot 60
3143 predecessor = get_low_leaf(forkTree, PublicDataLeafValue(60, 5));
3144
3145 // It should be back at index 4
3146 EXPECT_EQ(predecessor.is_already_present, false);
3147 EXPECT_EQ(predecessor.index, 4);
3148
3149 // Checkpoint again
3150 checkpoint_tree(forkTree);
3151
3163 check_size(forkTree, fork_size);
3164 EXPECT_EQ(get_leaf<PublicDataLeafValue>(forkTree, 0), create_indexed_public_data_leaf(0, 0, 1, 1));
3165 EXPECT_EQ(get_leaf<PublicDataLeafValue>(forkTree, 1), create_indexed_public_data_leaf(1, 0, 3, 10));
3166 EXPECT_EQ(get_leaf<PublicDataLeafValue>(forkTree, 2), create_indexed_public_data_leaf(30, 12, 4, 50));
3167 EXPECT_EQ(get_leaf<PublicDataLeafValue>(forkTree, 3), create_indexed_public_data_leaf(10, 20, 2, 30));
3168 EXPECT_EQ(get_leaf<PublicDataLeafValue>(forkTree, 4), create_indexed_public_data_leaf(50, 8, 0, 0));
3169
3170 // Find the low leaf of slot 60
3171 predecessor = get_low_leaf(forkTree, PublicDataLeafValue(60, 5));
3172
3173 // It should be back at index 4
3174 EXPECT_EQ(predecessor.is_already_present, false);
3175 EXPECT_EQ(predecessor.index, 4);
3176
3177 // Checkpoint again
3178 checkpoint_tree(forkTree);
3179
3191
3192 check_size(forkTree, ++fork_size);
3193 EXPECT_EQ(get_leaf<PublicDataLeafValue>(forkTree, 0), create_indexed_public_data_leaf(0, 0, 1, 1));
3194 EXPECT_EQ(get_leaf<PublicDataLeafValue>(forkTree, 1), create_indexed_public_data_leaf(1, 0, 3, 10));
3195 EXPECT_EQ(get_leaf<PublicDataLeafValue>(forkTree, 2), create_indexed_public_data_leaf(30, 12, 5, 45));
3196 EXPECT_EQ(get_leaf<PublicDataLeafValue>(forkTree, 3), create_indexed_public_data_leaf(10, 20, 2, 30));
3197 EXPECT_EQ(get_leaf<PublicDataLeafValue>(forkTree, 4), create_indexed_public_data_leaf(50, 8, 0, 0));
3198 EXPECT_EQ(get_leaf<PublicDataLeafValue>(forkTree, 5), create_indexed_public_data_leaf(45, 15, 4, 50));
3199
3200 // Find the low leaf of slot 60
3201 predecessor = get_low_leaf(forkTree, PublicDataLeafValue(60, 5));
3202
3203 // It should be back at index 4
3204 EXPECT_EQ(predecessor.is_already_present, false);
3205 EXPECT_EQ(predecessor.index, 4);
3206
3207 // Find the low leaf of slot 46
3208 predecessor = get_low_leaf(forkTree, PublicDataLeafValue(46, 5));
3209
3210 // It should be back at index 4
3211 EXPECT_EQ(predecessor.is_already_present, false);
3212 EXPECT_EQ(predecessor.index, 5);
3213
3214 // Now commit the last checkpoint
3215 commit_checkpoint_tree(forkTree);
3216
3217 // The state should be identical
3218 check_size(forkTree, fork_size);
3219 EXPECT_EQ(get_leaf<PublicDataLeafValue>(forkTree, 0), create_indexed_public_data_leaf(0, 0, 1, 1));
3220 EXPECT_EQ(get_leaf<PublicDataLeafValue>(forkTree, 1), create_indexed_public_data_leaf(1, 0, 3, 10));
3221 EXPECT_EQ(get_leaf<PublicDataLeafValue>(forkTree, 2), create_indexed_public_data_leaf(30, 12, 5, 45));
3222 EXPECT_EQ(get_leaf<PublicDataLeafValue>(forkTree, 3), create_indexed_public_data_leaf(10, 20, 2, 30));
3223 EXPECT_EQ(get_leaf<PublicDataLeafValue>(forkTree, 4), create_indexed_public_data_leaf(50, 8, 0, 0));
3224 EXPECT_EQ(get_leaf<PublicDataLeafValue>(forkTree, 5), create_indexed_public_data_leaf(45, 15, 4, 50));
3225
3226 // Find the low leaf of slot 60
3227 predecessor = get_low_leaf(forkTree, PublicDataLeafValue(60, 5));
3228
3229 // It should be back at index 4
3230 EXPECT_EQ(predecessor.is_already_present, false);
3231 EXPECT_EQ(predecessor.index, 4);
3232
3233 // Find the low leaf of slot 46
3234 predecessor = get_low_leaf(forkTree, PublicDataLeafValue(46, 5));
3235
3236 // It should be back at index 4
3237 EXPECT_EQ(predecessor.is_already_present, false);
3238 EXPECT_EQ(predecessor.index, 5);
3239
3240 // Now revert the fork and we should remove both the new slot 45 and the update to slot 30
3241
3253 revert_checkpoint_tree(forkTree);
3254
3255 check_size(forkTree, --fork_size);
3256 EXPECT_EQ(get_leaf<PublicDataLeafValue>(forkTree, 0), create_indexed_public_data_leaf(0, 0, 1, 1));
3257 EXPECT_EQ(get_leaf<PublicDataLeafValue>(forkTree, 1), create_indexed_public_data_leaf(1, 0, 3, 10));
3258 EXPECT_EQ(get_leaf<PublicDataLeafValue>(forkTree, 2), create_indexed_public_data_leaf(30, 6, 4, 50));
3259 EXPECT_EQ(get_leaf<PublicDataLeafValue>(forkTree, 3), create_indexed_public_data_leaf(10, 20, 2, 30));
3260 EXPECT_EQ(get_leaf<PublicDataLeafValue>(forkTree, 4), create_indexed_public_data_leaf(50, 8, 0, 0));
3261
3262 // Find the low leaf of slot 60
3263 predecessor = get_low_leaf(forkTree, PublicDataLeafValue(60, 5));
3264
3265 // It should be back at index 4
3266 EXPECT_EQ(predecessor.is_already_present, false);
3267 EXPECT_EQ(predecessor.index, 4);
3268
3269 // Find the low leaf of slot 46
3270 predecessor = get_low_leaf(forkTree, PublicDataLeafValue(46, 5));
3271
3272 // It should be back at index 4
3273 EXPECT_EQ(predecessor.is_already_present, false);
3274 EXPECT_EQ(predecessor.index, 2);
3275 }
3276}
3277
3278void advance_state(TreeType& fork, uint32_t size)
3279{
3280 std::vector<fr> values = create_values(size);
3282 for (uint32_t j = 0; j < size; j++) {
3283 leaves.emplace_back(values[j]);
3284 }
3285 add_values(fork, leaves);
3286}
3287
3288TEST_F(PersistedContentAddressedIndexedTreeTest, nullifiers_can_be_inserted_after_revert)
3289{
3290 index_t current_size = 2;
3291 ThreadPoolPtr workers = make_thread_pool(1);
3292 constexpr size_t depth = 10;
3293 std::string name = "Nullifier Tree";
3294 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
3296 auto tree = TreeType(std::move(store), workers, current_size);
3297
3298 {
3299 std::unique_ptr<Store> forkStore = std::make_unique<Store>(name, depth, db);
3300 auto forkTree = TreeType(std::move(forkStore), workers, current_size);
3301
3302 check_size(tree, current_size);
3303
3304 uint32_t size_to_insert = 8;
3305 uint32_t num_insertions = 5;
3306
3307 for (uint32_t i = 0; i < num_insertions - 1; i++) {
3308 advance_state(forkTree, size_to_insert);
3309 current_size += size_to_insert;
3310 check_size(forkTree, current_size);
3311 checkpoint_tree(forkTree);
3312 }
3313
3314 advance_state(forkTree, size_to_insert);
3315 current_size += size_to_insert;
3316 check_size(forkTree, current_size);
3317 revert_checkpoint_tree(forkTree);
3318
3319 current_size -= size_to_insert;
3320 check_size(forkTree, current_size);
3321
3322 commit_checkpoint_tree(forkTree);
3323
3324 check_size(forkTree, current_size);
3325
3326 advance_state(forkTree, size_to_insert);
3327
3328 current_size += size_to_insert;
3329 check_size(forkTree, current_size);
3330 }
3331}
3332
3333// Configured to throw an exception when put_cached_node_by_index is called, to simulate a failure in the thread during
3334// a path update.
3336 public:
3338 using Base::Base;
3339
3340 std::atomic<bool> throw_on_put_cached_node_by_index{ false };
3341 std::atomic<uint64_t> num_put_cached_node_calls{ 0 };
3342
3343 void put_cached_node_by_index(uint32_t level, index_t index, const fr& value)
3344 {
3346 num_put_cached_node_calls.fetch_add(1);
3347 throw std::runtime_error("injected failure from put_cached_node_by_index");
3348 }
3350 }
3351};
3352
3355
3356// The test checks that the perform_updates_without_witness callback has success=false
3357// and an error message, and that at least one call to put_cached_node_by_index was made (to ensure the failure was
3358// injected at the right place). This tests that the tree correctly handles exceptions thrown from the thread.
3359TEST_F(PersistedContentAddressedIndexedTreeTest, perform_updates_without_witness_when_thread_fails)
3360{
3361 constexpr size_t depth = 6;
3362 constexpr index_t initial_size = 2;
3363
3364 std::string name = random_string();
3365 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
3366 auto store = std::make_unique<ThrowingNullifierStore>(name, depth, db);
3367 auto* raw_store = store.get();
3368
3369 ThreadPoolPtr workers = make_thread_pool(4);
3370 ThrowingTreeType tree(std::move(store), workers, initial_size);
3371
3373 updates->push_back(typename TestAccess::LeafUpdate{
3374 .leaf_index = 1,
3375 .updated_leaf = IndexedLeaf<NullifierLeafValue>(NullifierLeafValue(fr(123)), 0, fr(0)),
3376 .original_leaf = IndexedLeaf<NullifierLeafValue>::empty(),
3377 });
3378
3379 // Configure to throw an exception when put_cached_node_by_index is called, to simulate a failure in the thread
3380 // during a path update.
3381 raw_store->throw_on_put_cached_node_by_index = true;
3382
3383 Signal signal;
3384 bool callback_success = false;
3385 std::string callback_message;
3386
3388 tree,
3389 /*highest_index=*/1,
3390 updates,
3392 callback_success = response.success;
3393 callback_message = response.message;
3394 signal.signal_level();
3395 });
3396
3397 signal.wait_for_level();
3398
3399 // At least one call to put_cached_node_by_index should have been made
3400 EXPECT_GT(raw_store->num_put_cached_node_calls.load(), 0);
3401
3402 // The callback should indicate failure and contain an error message
3403 EXPECT_FALSE(callback_success);
3404 EXPECT_FALSE(callback_message.empty());
3405}
std::atomic< uint32_t > root_call_count
fr get_current_root(ReadTransaction &tx, bool includeUncommitted) const
void put_cached_node_by_index(uint32_t level, index_t index, const fr &value)
Implements a simple append-only merkle tree All methods are asynchronous unless specified as otherwis...
void get_sibling_path(const index_t &index, const HashPathCallback &on_completion, bool includeUncommitted) const
Returns the sibling path from the leaf at the given index to the root.
void commit(const CommitCallback &on_completion)
Commit the tree to the backing store.
Serves as a key-value node store for merkle trees. Caches all changes in memory before persisting the...
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.
Implements a parallelized batch insertion indexed tree Accepts template argument of the type of store...
std::function< void(TypedResponse< AddIndexedDataSequentiallyResponse< LeafValueType > > &)> AddSequentiallyCompletionCallbackWithWitness
void add_or_update_values(const std::vector< LeafValueType > &values, const AddCompletionCallbackWithWitness &completion)
Adds or updates the given set of values in the tree using subtree insertion.
void perform_updates_without_witness(const index_t &highest_index, std::shared_ptr< std::vector< LeafUpdate > > updates, const UpdatesCompletionCallback &completion)
std::function< void(const TypedResponse< UpdatesCompletionResponse > &)> UpdatesCompletionCallback
std::function< void(TypedResponse< AddIndexedDataResponse< LeafValueType > > &)> AddCompletionCallbackWithWitness
std::shared_ptr< LMDBTreeStore > SharedPtr
fr update_element(size_t index, fr const &value)
fr_sibling_path get_sibling_path(size_t index) const
Used in parallel insertions in the the IndexedTree. Workers signal to other following workes as they ...
Definition signal.hpp:17
void signal_level(uint32_t level=0)
Signals that the given level has been passed.
Definition signal.hpp:54
void signal_decrement(uint32_t delta=1)
Definition signal.hpp:60
void wait_for_level(uint32_t level=0)
Causes the thread to wait until the required level has been signalled.
Definition signal.hpp:40
FF a
FF b
void commit_tree(TypeOfTree &tree, bool expectedSuccess=true)
void test_batch_insert(uint32_t batchSize, std::string directory, uint64_t mapSize, uint64_t maxReaders)
ContentAddressedCachedTreeStore< NullifierLeafValue > Store
fr get_root(TypeOfTree &tree, bool includeUncommitted=true)
void advance_state(TreeType &fork, uint32_t size)
void add_value_sequentially(TypeOfTree &tree, const LeafValueType &value, bool expectedSuccess=true)
void add_values(TypeOfTree &tree, const std::vector< LeafValueType > &values, bool expectedSuccess=true)
void add_values_sequentially(TypeOfTree &tree, const std::vector< LeafValueType > &values, bool expectedSuccess=true)
void check_sibling_path(TypeOfTree &tree, index_t index, const fr_sibling_path &expected_sibling_path, bool includeUncommitted=true, bool expected_success=true)
TreeType::AddCompletionCallbackWithWitness CompletionCallback
void check_historic_sibling_path(TypeOfTree &tree, index_t index, block_number_t blockNumber, const fr_sibling_path &expected_sibling_path, bool includeUncommitted=true, bool expected_success=true)
void test_batch_insert_with_commit_restore(uint32_t batchSize, std::string directory, uint64_t mapSize, uint64_t maxReaders)
ContentAddressedIndexedTree< PublicDataStore, Poseidon2HashPolicy > PublicDataTreeType
IndexedNullifierLeafType create_indexed_nullifier_leaf(const fr &value, index_t nextIndex, const fr &nextValue)
void add_value(TypeOfTree &tree, const LeafValueType &value, bool expectedSuccess=true)
std::unique_ptr< TreeType > create_tree(const std::string &rootDirectory, uint64_t mapSize, uint64_t maxReaders, uint32_t depth, uint32_t batchSize, ThreadPoolPtr workers)
fr_sibling_path get_historic_sibling_path(TypeOfTree &tree, block_number_t blockNumber, index_t index, bool includeUncommitted=true, bool expected_success=true)
GetLowIndexedLeafResponse get_historic_low_leaf(TypeOfTree &tree, block_number_t blockNumber, const LeafValueType &leaf, bool includeUncommitted=true)
void test_sequential_insert_vs_batch(uint32_t batchSize, std::string directory, uint64_t mapSize, uint64_t maxReaders)
void check_block_height(TypeOfTree &tree, index_t expected_block_height)
IndexedLeaf< LeafValueType > get_leaf(TypeOfTree &tree, index_t index, bool includeUncommitted=true, bool expected_success=true)
void check_root(TypeOfTree &tree, fr expected_root, bool includeUncommitted=true)
GetLowIndexedLeafResponse get_low_leaf(TypeOfTree &tree, const LeafValueType &leaf, bool includeUncommitted=true)
void check_historic_leaf(TypeOfTree &tree, const LeafValueType &leaf, index_t expected_index, block_number_t blockNumber, bool expected_success, bool includeUncommitted=true)
void block_sync_values_sequential(TypeOfTree &tree, const std::vector< LeafValueType > &values, bool expectedSuccess=true)
IndexedPublicDataLeafType create_indexed_public_data_leaf(const fr &slot, const fr &value, index_t nextIndex, const fr &nextValue)
void check_unfinalized_block_height(TypeOfTree &tree, index_t expected_block_height)
void test_nullifier_tree_unwind(std::string directory, std::string name, uint64_t mapSize, uint64_t maxReaders, uint32_t depth, uint32_t blockSize, uint32_t numBlocks, uint32_t numBlocksToUnwind, std::vector< fr > values)
void check_size(TypeOfTree &tree, index_t expected_size, bool includeUncommitted=true)
ContentAddressedIndexedTree< Store, HashPolicy > TreeType
void remove_historic_block(TypeOfTree &tree, const block_number_t &blockNumber, bool expected_success=true)
void block_sync_values(TypeOfTree &tree, const std::vector< LeafValueType > &values, bool expectedSuccess=true)
fr hash_leaf(const IndexedLeaf< LeafValueType > &leaf)
void finalize_block(TypeOfTree &tree, const block_number_t &blockNumber, bool expected_success=true)
void unwind_block(TypeOfTree &tree, const block_number_t &blockNumber, bool expected_success=true)
TreeType::AddSequentiallyCompletionCallbackWithWitness SequentialCompletionCallback
bool verify_sibling_path(TreeType &tree, const IndexedNullifierLeafType &leaf_value, const uint32_t idx)
ThreadPoolPtr make_thread_pool(uint64_t numThreads)
Definition fixtures.hpp:60
const fr & get_value(size_t index)
void check_indices_data(LMDBTreeStore::SharedPtr db, fr leaf, index_t index, bool entryShouldBePresent, bool indexShouldBePresent)
void check_historic_find_leaf_index_from(TypeOfTree &tree, const std::vector< LeafValueType > &leaves, block_number_t blockNumber, index_t start_index, const std::vector< std::optional< index_t > > &expected_indices, bool expected_success, bool includeUncommitted=true)
std::string random_temp_directory()
Definition fixtures.hpp:37
uint32_t block_number_t
Definition types.hpp:19
uint32_t checkpoint_tree(TreeType &tree)
void check_find_leaf_index(TypeOfTree &tree, const std::vector< LeafValueType > &leaves, const std::vector< std::optional< index_t > > &expected_indices, bool expected_success, bool includeUncommitted=true)
void check_block_and_root_data(LMDBTreeStore::SharedPtr db, block_number_t blockNumber, fr root, bool expectedSuccess)
void check_find_leaf_index_from(TypeOfTree &tree, const std::vector< LeafValueType > &leaves, index_t start_index, const std::vector< std::optional< index_t > > &expected_indices, bool expected_success, bool includeUncommitted=true)
std::string random_string()
Definition fixtures.hpp:30
std::shared_ptr< ThreadPool > ThreadPoolPtr
Definition fixtures.hpp:58
void check_block_and_size_data(LMDBTreeStore::SharedPtr db, block_number_t blockNumber, index_t expectedSize, bool expectedSuccess)
void commit_checkpoint_tree(TreeType &tree, bool expected_success=true)
std::vector< fr > fr_sibling_path
Definition hash_path.hpp:14
void revert_checkpoint_tree(TreeType &tree, bool expected_success=true)
void check_historic_find_leaf_index(TypeOfTree &tree, const std::vector< LeafValueType > &leaves, block_number_t blockNumber, const std::vector< std::optional< index_t > > &expected_indices, bool expected_success, bool includeUncommitted=true)
fr_sibling_path get_sibling_path(TypeOfTree &tree, index_t index, bool includeUncommitted=true, bool expected_success=true)
Key get_key(int64_t keyCount)
Definition fixtures.hpp:30
RNG & get_randomness()
Definition engine.cpp:258
Entry point for Barretenberg command-line interface.
Definition api.hpp:5
TEST_F(IPATest, ChallengesAreZero)
Definition ipa.test.cpp:160
field< Bn254FrParams > fr
Definition fr.hpp:155
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
std::string name
static void perform_updates_without_witness(Tree &tree, const index_t &highest_index, std::shared_ptr< std::vector< LeafUpdate > > updates, const UpdatesCompletionCallback &completion)
static IndexedLeaf< LeafType > empty()
std::vector< fr > get_hash_inputs() const
static fr hash_pair(const fr &lhs, const fr &rhs)
Definition hash.hpp:19
static fr hash(const std::vector< fr > &inputs)
Definition hash.hpp:14
static field random_element(numeric::RNG *engine=nullptr) noexcept
static constexpr field zero()