Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
content_addressed_append_only_tree.test.cpp
Go to the documentation of this file.
2#include "../fixtures.hpp"
3#include "../memory_tree.hpp"
4#include "../test_fixtures.hpp"
19#include <algorithm>
20#include <array>
21#include <cstddef>
22#include <cstdint>
23#include <exception>
24#include <filesystem>
25#include <memory>
26#include <optional>
27#include <stdexcept>
28#include <vector>
29
30using namespace bb;
31using namespace bb::crypto::merkle_tree;
32using namespace bb::lmdblib;
33
37
38class PersistedContentAddressedAppendOnlyTreeTest : public testing::Test {
39 protected:
40 void SetUp() override
41 {
43 _mapSize = 1024 * 1024;
44 _maxReaders = 16;
45 std::filesystem::create_directories(_directory);
46 }
47
48 void TearDown() override { std::filesystem::remove_all(_directory); }
49
50 static std::string _directory;
51 static uint64_t _maxReaders;
52 static uint64_t _mapSize;
53};
54
58
59void check_size(TreeType& tree, index_t expected_size, bool includeUncommitted = true)
60{
61 Signal signal;
62 auto completion = [&](const TypedResponse<TreeMetaResponse>& response) -> void {
63 EXPECT_EQ(response.success, true);
64 EXPECT_EQ(response.inner.meta.size, expected_size);
65 signal.signal_level();
66 };
67 tree.get_meta_data(includeUncommitted, completion);
68 signal.wait_for_level();
69}
70
71void check_finalized_block_height(TreeType& tree, block_number_t expected_finalized_block)
72{
73 Signal signal;
74 auto completion = [&](const TypedResponse<TreeMetaResponse>& response) -> void {
75 EXPECT_EQ(response.success, true);
76 EXPECT_EQ(response.inner.meta.finalizedBlockHeight, expected_finalized_block);
77 signal.signal_level();
78 };
79 tree.get_meta_data(false, completion);
80 signal.wait_for_level();
81}
82
83void check_block_height(TreeType& tree, index_t expected_block_height)
84{
85 Signal signal;
86 auto completion = [&](const TypedResponse<TreeMetaResponse>& response) -> void {
87 EXPECT_EQ(response.success, true);
88 EXPECT_EQ(response.inner.meta.unfinalizedBlockHeight, expected_block_height);
89 signal.signal_level();
90 };
91 tree.get_meta_data(true, completion);
92 signal.wait_for_level();
93}
94
95void check_root(TreeType& tree, fr expected_root, bool includeUncommitted = true)
96{
97 Signal signal;
98 auto completion = [&](const TypedResponse<TreeMetaResponse>& response) -> void {
99 EXPECT_EQ(response.success, true);
100 EXPECT_EQ(response.inner.meta.root, expected_root);
101 signal.signal_level();
102 };
103 tree.get_meta_data(includeUncommitted, completion);
104 signal.wait_for_level();
105}
106
109 fr_sibling_path expected_sibling_path,
110 bool includeUncommitted = true,
111 bool expected_result = true)
112{
113 Signal signal;
114 auto completion = [&](const TypedResponse<GetSiblingPathResponse>& response) -> void {
115 EXPECT_EQ(response.success, expected_result);
116 if (expected_result) {
117 EXPECT_EQ(response.inner.path, expected_sibling_path);
118 }
119 signal.signal_level();
120 };
121 tree.get_sibling_path(index, completion, includeUncommitted);
122 signal.wait_for_level();
123}
124
126 fr value,
127 fr_sibling_path expected_sibling_path,
128 index_t expected_index,
129 bool includeUncommitted = true,
130 bool expected_result = true)
131{
132 Signal signal;
133 auto completion = [&](const TypedResponse<FindLeafPathResponse>& response) -> void {
134 EXPECT_EQ(response.success, expected_result);
135 if (expected_result) {
136 EXPECT_TRUE(response.inner.leaf_paths[0].has_value());
137 EXPECT_EQ(response.inner.leaf_paths[0].value().path, expected_sibling_path);
138 EXPECT_EQ(response.inner.leaf_paths[0].value().index, expected_index);
139 }
140 signal.signal_level();
141 };
142 tree.find_leaf_sibling_paths({ value }, includeUncommitted, completion);
143 signal.wait_for_level();
144}
145
148 fr_sibling_path expected_sibling_path,
149 block_number_t blockNumber,
150 bool expected_success = true)
151{
152 Signal signal;
153 auto completion = [&](const TypedResponse<GetSiblingPathResponse>& response) -> void {
154 EXPECT_EQ(response.success, expected_success);
155 if (response.success) {
156 EXPECT_EQ(response.inner.path, expected_sibling_path);
157 }
158 signal.signal_level();
159 };
160 tree.get_sibling_path(index, blockNumber, completion, false);
161 signal.wait_for_level();
162}
163
165 fr value,
166 fr_sibling_path expected_sibling_path,
167 index_t expected_index,
168 block_number_t blockNumber,
169 bool expected_success = true)
170{
171 Signal signal;
172 auto completion = [&](const TypedResponse<FindLeafPathResponse>& response) -> void {
173 EXPECT_EQ(response.success, expected_success);
174 if (expected_success) {
175 EXPECT_TRUE(response.inner.leaf_paths[0].has_value());
176 EXPECT_EQ(response.inner.leaf_paths[0].value().path, expected_sibling_path);
177 EXPECT_EQ(response.inner.leaf_paths[0].value().index, expected_index);
178 }
179 signal.signal_level();
180 };
181 tree.find_leaf_sibling_paths({ value }, blockNumber, false, completion);
182 signal.wait_for_level();
183}
184
185void commit_tree(TreeType& tree, bool expected_success = true)
186{
187 Signal signal;
188 TreeType::CommitCallback completion = [&](const TypedResponse<CommitResponse>& response) -> void {
189 EXPECT_EQ(response.success, expected_success);
190 signal.signal_level();
191 };
192 tree.commit(completion);
193 signal.wait_for_level();
194}
195
196void remove_historic_block(TreeType& tree, const block_number_t& blockNumber, bool expected_success = true)
197{
198 Signal signal;
199 auto completion = [&](const TypedResponse<RemoveHistoricResponse>& response) -> void {
200 EXPECT_EQ(response.success, expected_success);
201 signal.signal_level();
202 };
203 tree.remove_historic_block(blockNumber, completion);
204 signal.wait_for_level();
205}
206
207void unwind_block(TreeType& tree, const block_number_t& blockNumber, bool expected_success = true)
208{
209 Signal signal;
210 auto completion = [&](const TypedResponse<UnwindResponse>& response) -> void {
211 EXPECT_EQ(response.success, expected_success);
212 signal.signal_level();
213 };
214 tree.unwind_block(blockNumber, completion);
215 signal.wait_for_level();
216}
217
218void add_value(TreeType& tree, const fr& value)
219{
220 Signal signal;
221 auto completion = [&](const TypedResponse<AddDataResponse>& response) -> void {
222 EXPECT_EQ(response.success, true);
223 signal.signal_level();
224 };
225
226 tree.add_value(value, completion);
227 signal.wait_for_level();
228}
229
230void add_values(TreeType& tree, const std::vector<fr>& values)
231{
232 Signal signal;
233 auto completion = [&](const TypedResponse<AddDataResponse>& response) -> void {
234 EXPECT_EQ(response.success, true);
235 signal.signal_level();
236 };
237
238 tree.add_values(values, completion);
239 signal.wait_for_level();
240}
241
242void finalize_block(TreeType& tree, const block_number_t& blockNumber, bool expected_success = true)
243{
244 Signal signal;
245 auto completion = [&](const Response& response) -> void {
246 EXPECT_EQ(response.success, expected_success);
247 if (!response.success && expected_success) {
248 std::cout << response.message << std::endl;
249 }
250 signal.signal_level();
251 };
252 tree.finalize_block(blockNumber, completion);
253 signal.wait_for_level();
254}
255
257 const fr& leaf,
258 index_t leaf_index,
259 bool expected_success,
260 bool expected_match = true,
261 bool includeUncommitted = true)
262{
263 Signal signal;
264 tree.get_leaf(leaf_index, includeUncommitted, [&](const TypedResponse<GetLeafResponse>& response) {
265 EXPECT_EQ(response.success, expected_success);
266 if (response.success && expected_match) {
267 EXPECT_EQ(response.inner.leaf, leaf);
268 }
269 signal.signal_level();
270 });
271 signal.wait_for_level();
272}
273
275 const block_number_t& blockNumber,
276 const fr& leaf,
277 index_t leaf_index,
278 bool expected_success,
279 bool expected_match = true,
280 bool includeUncommitted = true)
281{
282 Signal signal;
283 tree.get_leaf(leaf_index, blockNumber, includeUncommitted, [&](const TypedResponse<GetLeafResponse>& response) {
284 EXPECT_EQ(response.success, expected_success);
285 if (response.success && expected_match) {
286 EXPECT_EQ(response.inner.leaf, leaf);
287 }
288 signal.signal_level();
289 });
290 signal.wait_for_level();
291}
292
293void check_sibling_path(fr expected_root, fr node, index_t index, fr_sibling_path sibling_path)
294{
295 fr left;
296 fr right;
297 fr hash = node;
298 for (const auto& i : sibling_path) {
299 if (index % 2 == 0) {
300 left = hash;
301 right = i;
302 } else {
303 left = i;
304 right = hash;
305 }
306
307 hash = Poseidon2HashPolicy::hash_pair(left, right);
308 index >>= 1;
309 }
310
311 EXPECT_EQ(hash, expected_root);
312}
313
315 const std::vector<index_t>& indices,
316 std::vector<std::optional<block_number_t>>& blockNumbers)
317{
318 Signal signal;
319 tree.find_block_numbers(indices, [&](const TypedResponse<BlockForIndexResponse>& response) {
320 blockNumbers = response.inner.blockNumbers;
321 signal.signal_level();
322 });
323 signal.wait_for_level();
324}
325
327 const block_number_t& blockNumber,
328 const std::vector<index_t>& indices,
329 std::vector<std::optional<block_number_t>>& blockNumbers)
330{
331 Signal signal;
332 tree.find_block_numbers(indices, blockNumber, [&](const TypedResponse<BlockForIndexResponse>& response) {
333 blockNumbers = response.inner.blockNumbers;
334 signal.signal_level();
335 });
336 signal.wait_for_level();
337}
338
340{
341 constexpr size_t depth = 10;
342 std::string name = random_string();
343 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
344 EXPECT_NO_THROW(Store store(name, depth, db));
345 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
346
348 TreeType tree(std::move(store), pool);
350
351 check_size(tree, 0);
352 check_root(tree, memdb.root());
353}
354
355TEST_F(PersistedContentAddressedAppendOnlyTreeTest, committing_with_no_changes_should_succeed)
356{
357 constexpr size_t depth = 10;
358 std::string name = random_string();
359 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
360 EXPECT_NO_THROW(Store store(name, depth, db));
361 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
362
364 TreeType tree(std::move(store), pool);
366
367 add_value(tree, get_value(0));
368 memdb.update_element(0, get_value(0));
369
370 commit_tree(tree, true);
371 check_root(tree, memdb.root());
372 check_size(tree, 1, false);
373 commit_tree(tree, true);
374 check_root(tree, memdb.root());
375 check_size(tree, 1, false);
376 // rollbacks should do nothing
377 rollback_tree(tree);
378 check_root(tree, memdb.root());
379 check_size(tree, 1, false);
380 add_value(tree, get_value(1));
381
382 // committed should be the same
383 check_root(tree, memdb.root(), false);
384 check_size(tree, 1, false);
385
386 // rollback
387 rollback_tree(tree);
388 // commit should do nothing
389 commit_tree(tree, true);
390 check_root(tree, memdb.root());
391 check_size(tree, 1, false);
392}
393
394TEST_F(PersistedContentAddressedAppendOnlyTreeTest, can_only_recreate_with_same_name_and_depth)
395{
396 constexpr size_t depth = 10;
397 std::string name = random_string();
398 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
399 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
400
401 EXPECT_ANY_THROW(Store store_wrong_name("Wrong name", depth, db));
402 EXPECT_ANY_THROW(Store store_wrong_depth(name, depth + 1, db));
403}
404
405TEST_F(PersistedContentAddressedAppendOnlyTreeTest, can_add_value_and_get_sibling_path)
406{
407 constexpr size_t depth = 3;
408 std::string name = random_string();
409 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
410 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
411
413 TreeType tree(std::move(store), pool);
415
416 check_size(tree, 0);
417 check_root(tree, memdb.root());
418
419 memdb.update_element(0, get_value(0));
420 add_value(tree, get_value(0));
421
422 check_size(tree, 1);
423 check_root(tree, memdb.root());
424 check_sibling_path(tree, 0, memdb.get_sibling_path(0));
426}
427
428TEST_F(PersistedContentAddressedAppendOnlyTreeTest, reports_an_error_if_tree_is_overfilled)
429{
430 constexpr size_t depth = 4;
431 std::string name = random_string();
432 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
433 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
434
436 TreeType tree(std::move(store), pool);
437
438 std::vector<fr> values;
439 values.reserve(16);
440 for (uint32_t i = 0; i < 16; i++) {
441 values.push_back(get_value(i));
442 }
443 add_values(tree, values);
444
445 std::stringstream ss;
446 ss << "Unable to append leaves to tree " << name << " new size: 17 max size: 16";
447
448 Signal signal;
449 auto add_completion = [&](const TypedResponse<AddDataResponse>& response) {
450 EXPECT_EQ(response.success, false);
451 EXPECT_EQ(response.message, ss.str());
452 signal.signal_level();
453 };
454 tree.add_value(get_value(16), add_completion);
455 signal.wait_for_level();
456}
457
458TEST_F(PersistedContentAddressedAppendOnlyTreeTest, reports_an_error_if_index_out_of_tree_range)
459{
460 constexpr size_t depth = 4;
461 std::string name = random_string();
462 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
463 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
464
466 TreeType tree(std::move(store), pool);
467
468 std::vector<fr> values;
469 values.reserve(16);
470 for (uint32_t i = 0; i < 16; i++) {
471 values.push_back(get_value(i));
472 }
473 add_values(tree, values);
474 commit_tree(tree);
475
476 {
477 std::string str = "Unable to get leaf at index 17, leaf index out of tree range.";
478 Signal signal;
479 auto get_completion = [&](const TypedResponse<GetLeafResponse>& response) {
480 EXPECT_EQ(response.success, false);
481 EXPECT_EQ(response.message, str);
482 signal.signal_level();
483 };
484 tree.get_leaf(17, true, get_completion);
485 signal.wait_for_level();
486 }
487
488 {
489 std::string str = "Unable to get leaf at index 17 for block 1, leaf index out of tree range.";
490 Signal signal;
491 auto get_completion = [&](const TypedResponse<GetLeafResponse>& response) {
492 EXPECT_EQ(response.success, false);
493 EXPECT_EQ(response.message, str);
494 signal.signal_level();
495 };
496 tree.get_leaf(17, 1, true, get_completion);
497 signal.wait_for_level();
498 }
499}
500
502{
503 // We use a deep tree with a small amount of storage (100 * 1024) bytes
504 constexpr size_t depth = 16;
505 std::string name = random_string();
506 std::string directory = random_temp_directory();
507 std::filesystem::create_directories(directory);
508
509 {
510 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, 100, _maxReaders);
511 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
512
514 TreeType tree(std::move(store), pool);
516
517 // check the committed data only, so we read from the db
518 check_size(tree, 0, false);
519 check_root(tree, memdb.root(), false);
520
521 fr empty_root = memdb.root();
522
523 // Add lots of values to the tree
524 uint32_t num_values_to_add = 16 * 1024;
525 std::vector<fr> values;
526 for (uint32_t i = 0; i < num_values_to_add; i++) {
527 values.emplace_back(random_engine.get_random_uint256());
528 memdb.update_element(i, values[i]);
529 }
530 add_values(tree, values);
531
532 // check the uncommitted data is accurate
533 check_size(tree, num_values_to_add, true);
534 check_root(tree, memdb.root(), true);
535
536 // trying to commit that should fail
537 Signal signal;
538 auto completion = [&](const TypedResponse<CommitResponse>& response) -> void {
539 EXPECT_EQ(response.success, false);
540 signal.signal_level();
541 };
542
543 tree.commit(completion);
544 signal.wait_for_level();
545
546 // At this stage, the tree is still in an uncommited state despite the error
547 // Reading both committed and uncommitted data shold be ok
548
549 // check the uncommitted data is accurate
550 check_size(tree, num_values_to_add, true);
551 check_root(tree, memdb.root(), true);
552
553 // Reading committed data should still work
554 check_size(tree, 0, false);
555 check_root(tree, empty_root, false);
556
557 // Now rollback the tree
558 rollback_tree(tree);
559
560 // committed and uncommitted data should be as an empty tree
561 check_size(tree, 0, true);
562 check_root(tree, empty_root, true);
563
564 // Reading committed data should still work
565 check_size(tree, 0, false);
566 check_root(tree, empty_root, false);
567 }
568
569 {
570 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, 500, _maxReaders);
571 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
572
574 TreeType tree(std::move(store), pool);
576
577 fr empty_root = memdb.root();
578
579 // committed and uncommitted data should be as an empty tree
580 check_size(tree, 0, true);
581 check_root(tree, empty_root, true);
582
583 // Reading committed data should still work
584 check_size(tree, 0, false);
585 check_root(tree, empty_root, false);
586
587 // Now add a single value and commit it
588 add_value(tree, get_value(0));
589
590 commit_tree(tree);
591
593 memdb2.update_element(0, get_value(0));
594
595 // committed and uncommitted data should be equal to the tree with 1 item
596 check_size(tree, 1, true);
597 check_root(tree, memdb2.root(), true);
598
599 // Reading committed data should still work
600 check_size(tree, 1, false);
601 check_root(tree, memdb2.root(), false);
602 }
603}
604
606{
607 constexpr size_t depth = 5;
608 std::string name = random_string();
610 {
611 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
612 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
613
615 TreeType tree(std::move(store), pool);
616
617 check_size(tree, 0);
618 check_root(tree, memdb.root());
619 check_sibling_path(tree, 0, memdb.get_sibling_path(0));
620
621 bb::fr initial_root = memdb.root();
622 fr_sibling_path initial_sibling_path = memdb.get_sibling_path(0);
623 memdb.update_element(0, get_value(0));
624 add_value(tree, get_value(0));
625
626 // check uncommitted state
627 check_size(tree, 1);
628 check_root(tree, memdb.root());
629 check_sibling_path(tree, 0, memdb.get_sibling_path(0));
630
631 // check committed state
632 check_size(tree, 0, false);
633 check_root(tree, initial_root, false);
634 check_sibling_path(tree, 0, initial_sibling_path, false);
635
636 // commit the changes
637 commit_tree(tree);
638
639 check_block_and_root_data(db, 1, memdb.root(), true);
640 // now committed and uncommitted should be the same
641
642 // check uncommitted state
643 check_size(tree, 1);
644 check_root(tree, memdb.root());
645 check_sibling_path(tree, 0, memdb.get_sibling_path(0));
646
647 // check committed state
648 check_size(tree, 1, false);
649 check_root(tree, memdb.root(), false);
650 check_sibling_path(tree, 0, memdb.get_sibling_path(0), false);
651 }
652
653 // Re-create the store and tree, it should be the same as how we left it
654 {
655 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
656 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
657
659 TreeType tree(std::move(store), pool);
660
661 // check uncommitted state
662 check_size(tree, 1);
663 check_root(tree, memdb.root());
664 check_sibling_path(tree, 0, memdb.get_sibling_path(0));
665
666 check_block_and_root_data(db, 1, memdb.root(), true);
667
668 // check committed state
669 check_size(tree, 1, false);
670 check_root(tree, memdb.root(), false);
671 check_sibling_path(tree, 0, memdb.get_sibling_path(0), false);
672 }
673}
674
676{
677 constexpr size_t depth = 10;
678 std::string name = random_string();
679 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
680 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
682 TreeType tree(std::move(store), pool);
683
684 check_size(tree, 0);
685
686 // Add a new non-zero leaf at index 0.
687 add_value(tree, 30);
688 check_size(tree, 1);
689
690 // Add second.
691 add_value(tree, 10);
692 check_size(tree, 2);
693
694 // Add third.
695 add_value(tree, 20);
696 check_size(tree, 3);
697
698 // Add forth but with same value.
699 add_value(tree, 40);
700 check_size(tree, 4);
701}
702
704{
705 constexpr size_t depth = 5;
706 std::string name = random_string();
707 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
708 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
710 TreeType tree(std::move(store), pool);
711
712 add_value(tree, 30);
713 add_value(tree, 10);
714 add_value(tree, 20);
715 add_value(tree, 40);
716
717 // check the committed state and that the uncommitted state is empty
718 check_find_leaf_index(tree, fr(10), 1, true, true);
719 check_find_leaf_index<fr, TreeType>(tree, { fr(10) }, { std::nullopt }, true, false);
720
721 check_find_leaf_index<fr, TreeType>(tree, { fr(15) }, { std::nullopt }, true, true);
722 check_find_leaf_index<fr, TreeType>(tree, { fr(15) }, { std::nullopt }, true, false);
723
724 check_find_leaf_index(tree, fr(40), 3, true, true);
725 check_find_leaf_index(tree, fr(30), 0, true, true);
726 check_find_leaf_index(tree, fr(20), 2, true, true);
727
728 check_find_leaf_index<fr, TreeType>(tree, { fr(40) }, { std::nullopt }, true, false);
729 check_find_leaf_index<fr, TreeType>(tree, { fr(30) }, { std::nullopt }, true, false);
730 check_find_leaf_index<fr, TreeType>(tree, { fr(20) }, { std::nullopt }, true, false);
731
732 commit_tree(tree);
733
734 std::vector<fr> values{ 15, 18, 26, 2 };
735 add_values(tree, values);
736
737 // check the now committed state
738 check_find_leaf_index(tree, fr(40), 3, true, false);
739 check_find_leaf_index(tree, fr(30), 0, true, false);
740 check_find_leaf_index(tree, fr(20), 2, true, false);
741
742 // check the new uncommitted state
743 check_find_leaf_index(tree, fr(18), 5, true, true);
744 check_find_leaf_index<fr, TreeType>(tree, { fr(18) }, { std::nullopt }, true, false);
745
746 commit_tree(tree);
747
748 values = { 16, 4, 19, 22 };
749 add_values(tree, values);
750
751 // verify the find index from api
752 check_find_leaf_index_from(tree, fr(18), 0, 5, true, true);
753 check_find_leaf_index_from(tree, fr(19), 6, 10, true, true);
754 check_find_leaf_index_from<fr, TreeType>(tree, { fr(19) }, 0, { std::nullopt }, true, false);
755
756 commit_tree(tree);
757
758 add_value(tree, 88);
759
760 add_value(tree, 32);
761
762 check_size(tree, 14);
763 check_size(tree, 12, false);
764
765 // look past the last instance of this leaf
766 check_find_leaf_index_from<fr, TreeType>(tree, { fr(18) }, 6, { std::nullopt }, true, true);
767
768 // look beyond the end of uncommitted
769 check_find_leaf_index_from<fr, TreeType>(tree, { fr(18) }, 15, { std::nullopt }, true, true);
770
771 // look beyond the end of committed and don't include uncomitted
772 check_find_leaf_index_from<fr, TreeType>(tree, { fr(88) }, 13, { std::nullopt }, true, false);
773}
774
776{
777 constexpr size_t depth = 10;
778 std::string name = random_string();
779 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
780 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
782 TreeType tree(std::move(store), pool);
784
785 for (size_t i = 0; i < NUM_VALUES; ++i) {
786 fr mock_root = memdb.update_element(i, get_value(i));
787 add_value(tree, get_value(i));
788 check_root(tree, mock_root);
789
790 check_sibling_path(tree, 0, memdb.get_sibling_path(0));
791 check_sibling_path(tree, i, memdb.get_sibling_path(i));
792
795 }
796}
797
798TEST_F(PersistedContentAddressedAppendOnlyTreeTest, can_add_multiple_values_in_a_batch)
799{
800 constexpr size_t depth = 3;
801 std::string name = random_string();
802 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
803 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
805 TreeType tree(std::move(store), pool);
807
808 std::vector<fr> to_add;
809
810 for (size_t i = 0; i < 4; ++i) {
811 memdb.update_element(i, get_value(i));
812 to_add.push_back(get_value(i));
813 }
814 add_values(tree, to_add);
815 check_size(tree, 4);
816 check_root(tree, memdb.root());
817 check_sibling_path(tree, 0, memdb.get_sibling_path(0));
818 check_sibling_path(tree, 4 - 1, memdb.get_sibling_path(4 - 1));
820 check_sibling_path_by_value(tree, get_value(4 - 1), memdb.get_sibling_path(4 - 1), 4 - 1);
821}
822
824{
825 constexpr size_t depth = 10;
826 std::string name = random_string();
827 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
828 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
830 TreeType tree(std::move(store), pool);
832
833 std::vector<fr> to_add(32, fr::zero());
834 to_add[0] = get_value(0);
835
836 for (size_t i = 0; i < 32; ++i) {
837 memdb.update_element(i, to_add[i]);
838 }
839 add_values(tree, to_add);
840 check_size(tree, 32);
841 check_root(tree, memdb.root());
842
843 commit_tree(tree, true);
844
845 check_size(tree, 32);
846 check_root(tree, memdb.root());
847}
848
849TEST_F(PersistedContentAddressedAppendOnlyTreeTest, can_not_retrieve_zero_leaf_indices)
850{
851 constexpr size_t depth = 8;
852 std::string name = random_string();
853 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
854 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
856 TreeType tree(std::move(store), pool);
858
859 std::vector<fr> to_add(32, fr::zero());
860 to_add[0] = get_value(0);
861
862 for (size_t i = 0; i < 32; ++i) {
863 memdb.update_element(i, get_value(i));
864 }
865 add_values(tree, to_add);
866 commit_tree(tree);
867 fr leaf = fr::zero();
868 check_find_leaf_index<fr, TreeType>(tree, { leaf }, { std::nullopt }, true);
869 check_historic_find_leaf_index<fr, TreeType>(tree, { leaf }, 1, { std::nullopt }, true);
870 check_find_leaf_index_from<fr, TreeType>(tree, { leaf }, 0, { std::nullopt }, true);
871 check_historic_find_leaf_index_from<fr, TreeType>(tree, { leaf }, 1, 0, { std::nullopt }, true);
872}
873
875{
876 constexpr size_t depth = 10;
877 std::string name = random_string();
878 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
879 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
881 TreeType tree(std::move(store), pool);
883
884 auto check = [&](index_t expected_size, index_t expected_unfinalized_block_height) {
885 check_size(tree, expected_size);
886 check_block_height(tree, expected_unfinalized_block_height);
887 check_root(tree, memdb.root());
888 check_sibling_path(tree, 0, memdb.get_sibling_path(0));
889 check_sibling_path(tree, expected_size - 1, memdb.get_sibling_path(expected_size - 1));
890 };
891
892 constexpr uint32_t num_blocks = 10;
893 constexpr uint32_t batch_size = 4;
894 for (uint32_t i = 0; i < num_blocks; i++) {
895 std::vector<fr> to_add;
896
897 for (size_t j = 0; j < batch_size; ++j) {
898 size_t ind = i * batch_size + j;
899 memdb.update_element(ind, get_value(ind));
900 to_add.push_back(get_value(ind));
901 }
902 index_t expected_size = (i + 1) * batch_size;
903 add_values(tree, to_add);
904 check(expected_size, i);
905 commit_tree(tree);
906 check(expected_size, i + 1);
907 check_block_and_root_data(db, 1 + i, memdb.root(), true);
908 }
909}
910
912{
913 constexpr size_t depth = 10;
914 std::string name = random_string();
915 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
916 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
918 TreeType tree(std::move(store), pool);
920
921 auto check = [&](index_t expected_size, index_t expected_unfinalized_block_height) {
922 check_size(tree, expected_size);
923 check_block_height(tree, expected_unfinalized_block_height);
924 check_root(tree, memdb.root());
925 check_sibling_path(tree, 0, memdb.get_sibling_path(0));
926 check_sibling_path(tree, expected_size - 1, memdb.get_sibling_path(expected_size - 1));
927 };
928
929 std::vector<size_t> batchSize = { 8, 20, 64, 32 };
930 index_t expected_size = 0;
931
932 for (uint32_t i = 0; i < batchSize.size(); i++) {
933 std::vector<fr> to_add;
934
935 for (size_t j = 0; j < batchSize[i]; ++j) {
936 size_t ind = expected_size + j;
937 memdb.update_element(ind, get_value(ind));
938 to_add.push_back(get_value(ind));
939 }
940 expected_size += batchSize[i];
941 add_values(tree, to_add);
942 check(expected_size, i);
943 commit_tree(tree);
944 check(expected_size, i + 1);
945 check_block_and_root_data(db, 1 + i, memdb.root(), true);
946 }
947}
948
949TEST_F(PersistedContentAddressedAppendOnlyTreeTest, can_retrieve_historic_sibling_paths)
950{
951 constexpr size_t depth = 10;
952 std::string name = random_string();
953 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
954 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
956 TreeType tree(std::move(store), pool);
958
959 constexpr uint32_t num_blocks = 10;
960 constexpr uint32_t batch_size = 4;
961
962 std::vector<fr_sibling_path> historicPathsZeroIndex;
963 std::vector<fr_sibling_path> historicPathsMaxIndex;
964
965 auto check = [&](index_t expected_size, index_t expected_unfinalized_block_height) {
966 check_size(tree, expected_size);
967 check_block_height(tree, expected_unfinalized_block_height);
968 check_root(tree, memdb.root());
969 check_sibling_path(tree, 0, memdb.get_sibling_path(0));
970 check_sibling_path(tree, expected_size - 1, memdb.get_sibling_path(expected_size - 1));
971
972 for (uint32_t i = 0; i < historicPathsZeroIndex.size(); i++) {
973 check_historic_sibling_path(tree, 0, historicPathsZeroIndex[i], i + 1);
974 check_historic_sibling_path_by_value(tree, get_value(0), historicPathsZeroIndex[i], 0, i + 1);
975 index_t maxSizeAtBlock = ((i + 1) * batch_size) - 1;
976 check_historic_sibling_path(tree, maxSizeAtBlock, historicPathsMaxIndex[i], i + 1);
978 tree, get_value(maxSizeAtBlock), historicPathsMaxIndex[i], maxSizeAtBlock, i + 1);
979 }
980 };
981
982 for (uint32_t i = 0; i < num_blocks; i++) {
983 std::vector<fr> to_add;
984
985 for (size_t j = 0; j < batch_size; ++j) {
986 size_t ind = i * batch_size + j;
987 memdb.update_element(ind, get_value(ind));
988 to_add.push_back(get_value(ind));
989 }
990 index_t expected_size = (i + 1) * batch_size;
991 add_values(tree, to_add);
992 check(expected_size, i);
993 commit_tree(tree);
994 check(expected_size, i + 1);
995 historicPathsZeroIndex.push_back(memdb.get_sibling_path(0));
996 historicPathsMaxIndex.push_back(memdb.get_sibling_path(expected_size - 1));
997 }
998}
999
1001{
1002 constexpr size_t depth = 10;
1003 std::string name = random_string();
1004 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
1005 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
1007 TreeType tree(std::move(store), pool);
1009
1010 constexpr uint32_t num_blocks = 10;
1011 constexpr uint32_t batch_size = 4;
1012
1013 for (uint32_t i = 0; i < num_blocks; i++) {
1014 std::vector<fr> to_add;
1015
1016 for (size_t j = 0; j < batch_size; ++j) {
1017 size_t ind = i * batch_size + j;
1018 memdb.update_element(ind, get_value(ind));
1019 to_add.push_back(get_value(ind));
1020 }
1021 add_values(tree, to_add);
1022 commit_tree(tree);
1023 }
1024
1025 uint64_t block_tree_size = 0;
1026
1027 for (uint32_t i = 0; i < num_blocks; i++) {
1028 Signal meta_signal;
1029 tree.get_meta_data(i + 1, false, [&](auto response) -> void {
1030 block_tree_size = response.inner.meta.size;
1031 meta_signal.signal_level();
1032 });
1033 meta_signal.wait_for_level();
1034 for (uint32_t j = 0; j < num_blocks; j++) {
1035 index_t indexToQuery = j * batch_size;
1036 fr expectedLeaf = j <= i ? get_value(indexToQuery) : fr::zero();
1037 check_historic_leaf(tree, i + 1, expectedLeaf, indexToQuery, indexToQuery < block_tree_size, true, false);
1038 }
1039 }
1040}
1041
1043{
1044 constexpr size_t depth = 5;
1045 std::string name = random_string();
1046 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
1047 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
1049 TreeType tree(std::move(store), pool);
1050
1051 std::vector<fr> values{ 30, 10, 20, 40 };
1052 add_values(tree, values);
1053
1054 commit_tree(tree);
1055
1056 values = { 15, 18, 26, 2 };
1057 add_values(tree, values);
1058
1059 commit_tree(tree);
1060
1061 values = { 16, 4, 19, 22 };
1062 add_values(tree, values);
1063
1064 // should not be present at block 1
1065 check_historic_find_leaf_index<fr, TreeType>(tree, { fr(26) }, 1, { std::nullopt }, true);
1066 // should be present at block 2
1067 check_historic_find_leaf_index(tree, fr(26), 2, 6, true);
1068
1069 // at block 1 leaf 18 should not be found if only considering committed
1070 check_historic_find_leaf_index_from<fr, TreeType>(tree, { fr(18) }, 1, 2, { std::nullopt }, true, false);
1071 // at block 2 it should be
1072 check_historic_find_leaf_index_from(tree, fr(18), 2, 2, 5, true);
1073 // at block 2, from index 6, 19 should not be found if looking only at committed
1074 check_historic_find_leaf_index_from<fr, TreeType>(tree, { fr(19) }, 2, 6, { std::nullopt }, true, false);
1075 // at block 2, from index 6, 19 should be found if looking at uncommitted too
1076 check_historic_find_leaf_index_from(tree, fr(19), 2, 6, 10, true);
1077
1078 commit_tree(tree);
1079
1080 // at block 3, from index 6, should now be found in committed only
1081 check_historic_find_leaf_index_from(tree, fr(19), 3, 6, 10, true, false);
1082}
1083
1085{
1086 constexpr size_t depth = 3;
1087 std::string name = random_string();
1088 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
1089 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
1091 TreeType tree(std::move(store), pool);
1093
1094 check_size(tree, 0);
1095 check_root(tree, memdb.root());
1096
1097 for (size_t i = 0; i < 8; i++) {
1098 memdb.update_element(i, get_value(i));
1099 add_value(tree, get_value(i));
1100 }
1101
1102 check_root(tree, memdb.root());
1103 check_sibling_path(tree, 0, memdb.get_sibling_path(0));
1104 check_sibling_path(tree, 7, memdb.get_sibling_path(7));
1105}
1106
1108{
1109 constexpr size_t depth = 10;
1111 fr_sibling_path initial_path = memdb.get_sibling_path(0);
1112 memdb.update_element(0, get_value(0));
1113 fr_sibling_path final_sibling_path = memdb.get_sibling_path(0);
1114
1115 uint32_t num_reads = 16 * 1024;
1116 std::vector<fr_sibling_path> paths(num_reads);
1117
1118 {
1119 std::string name = random_string();
1120 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
1121 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
1123 TreeType tree(std::move(store), pool);
1124
1125 check_size(tree, 0);
1126
1127 Signal signal(1 + num_reads);
1128
1129 auto add_completion = [&](const TypedResponse<AddDataResponse>&) {
1130 auto commit_completion = [&](const TypedResponse<CommitResponse>&) { signal.signal_decrement(); };
1131 tree.commit(commit_completion);
1132 };
1133 tree.add_value(get_value(0), add_completion);
1134
1135 for (size_t i = 0; i < num_reads; i++) {
1136 auto completion = [&, i](const TypedResponse<GetSiblingPathResponse>& response) {
1137 paths[i] = response.inner.path;
1138 signal.signal_decrement();
1139 };
1140 tree.get_sibling_path(0, completion, false);
1141 }
1142 signal.wait_for_level(0);
1143 }
1144
1145 for (auto& path : paths) {
1146 EXPECT_TRUE(path == initial_path || path == final_sibling_path);
1147 }
1148}
1149
1151{
1152 constexpr size_t depth = 3;
1153 std::string name = random_string();
1154 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
1155 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
1157 TreeType tree(std::move(store), pool);
1158
1159 add_values(tree, { 30, 10, 20, 40 });
1160
1161 check_leaf(tree, 30, 0, true);
1162 check_leaf(tree, 10, 1, true);
1163 check_leaf(tree, 20, 2, true);
1164 check_leaf(tree, 40, 3, true);
1165
1166 check_leaf(tree, 1, 4, true, false);
1167 check_leaf(tree, 0, 4, true);
1168 check_leaf(tree, 0, 9, false);
1169}
1170
1172{
1173 constexpr size_t depth = 4;
1174 std::string name = random_string();
1175 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
1176 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
1178 TreeType tree(std::move(store), pool);
1180
1181 add_values(tree, { 30, 10, 20, 40 });
1182 memdb.update_element(0, 30);
1183 memdb.update_element(1, 10);
1184 memdb.update_element(2, 20);
1185 memdb.update_element(3, 40);
1186
1187 {
1188 Signal signal(1);
1190 0,
1191 [&](auto& resp) {
1192 fr_sibling_path expected_sibling_path = memdb.get_sibling_path(4);
1193 EXPECT_EQ(resp.inner.path, expected_sibling_path);
1194 signal.signal_level();
1195 },
1196 true);
1197 signal.wait_for_level();
1198 }
1199
1200 {
1201 Signal signal(1);
1203 2,
1204 [&](auto& resp) {
1205 fr_sibling_path expected_sibling_path = { memdb.get_node(2, 0), memdb.get_node(1, 1) };
1206 EXPECT_EQ(resp.inner.path, expected_sibling_path);
1207 signal.signal_level();
1208 },
1209 true);
1210 signal.wait_for_level();
1211 }
1212}
1213
1214TEST_F(PersistedContentAddressedAppendOnlyTreeTest, can_create_images_at_historic_blocks)
1215{
1216 constexpr size_t depth = 5;
1217 std::string name = random_string();
1219 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
1221 size_t index = 0;
1222
1223 std::unique_ptr<Store> store1 = std::make_unique<Store>(name, depth, db);
1224 TreeType tree1(std::move(store1), pool);
1225
1226 std::vector<fr> values{ 30, 10, 20, 40 };
1227 add_values(tree1, values);
1228 for (auto v : values) {
1229 memdb.update_element(index++, v);
1230 }
1231
1232 commit_tree(tree1);
1233
1234 check_block_and_root_data(db, 1, memdb.root(), true);
1235
1236 fr_sibling_path block1SiblingPathIndex3 = memdb.get_sibling_path(3);
1237
1238 values = { 15, 18, 26, 2 };
1239 add_values(tree1, values);
1240 for (auto v : values) {
1241 memdb.update_element(index++, v);
1242 }
1243
1244 commit_tree(tree1);
1245
1246 check_block_and_root_data(db, 2, memdb.root(), true);
1247
1248 fr block2Root = memdb.root();
1249
1250 fr_sibling_path block2SiblingPathIndex7 = memdb.get_sibling_path(7);
1251 fr_sibling_path block2SiblingPathIndex3 = memdb.get_sibling_path(3);
1252
1253 values = { 16, 4, 18, 22 };
1254 add_values(tree1, values);
1255 for (auto v : values) {
1256 memdb.update_element(index++, v);
1257 }
1258
1259 commit_tree(tree1);
1260
1261 check_block_and_root_data(db, 3, memdb.root(), true);
1262
1263 fr_sibling_path block3SiblingPathIndex11 = memdb.get_sibling_path(11);
1264 fr_sibling_path block3SiblingPathIndex7 = memdb.get_sibling_path(7);
1265 fr_sibling_path block3SiblingPathIndex3 = memdb.get_sibling_path(3);
1266
1267 // Create a new image at block 2
1268 std::unique_ptr<Store> storeAtBlock2 = std::make_unique<Store>(name, depth, 2, db);
1269 TreeType treeAtBlock2(std::move(storeAtBlock2), pool);
1270
1271 check_root(treeAtBlock2, block2Root);
1272 check_sibling_path(treeAtBlock2, 3, block2SiblingPathIndex3, false, true);
1273 check_leaf(treeAtBlock2, 20, 2, true);
1274 check_find_leaf_index(treeAtBlock2, fr(10), 1, true);
1275 check_find_leaf_index_from(treeAtBlock2, fr(15), 1, 4, true);
1276
1277 // should not exist in our image
1278 check_leaf(treeAtBlock2, 4, 9, true, false);
1279 check_find_leaf_index<fr, TreeType>(treeAtBlock2, { fr(4) }, { std::nullopt }, true);
1280
1281 // now add the same values to our image
1282 add_values(treeAtBlock2, values);
1283
1284 // the state of our image should match the original tree
1285 check_sibling_path(tree1, 3, block3SiblingPathIndex3, false, true);
1286 check_sibling_path(tree1, 7, block3SiblingPathIndex7, false, true);
1287
1288 // needs to use uncommitted for this check
1289 check_sibling_path(treeAtBlock2, 3, block3SiblingPathIndex3, true, true);
1290 check_sibling_path(treeAtBlock2, 7, block3SiblingPathIndex7, true, true);
1291
1292 // now check historic data
1293 check_historic_sibling_path(treeAtBlock2, 3, block1SiblingPathIndex3, 1);
1294 check_historic_find_leaf_index(treeAtBlock2, fr(10), 2, 1, true);
1295 check_historic_find_leaf_index(treeAtBlock2, fr(16), 2, 8, true, true);
1296 check_historic_find_leaf_index<fr, TreeType>(treeAtBlock2, { fr(16) }, 2, { std::nullopt }, true, false);
1297
1298 check_historic_find_leaf_index_from<fr, TreeType>(treeAtBlock2, { fr(18) }, 1, 3, { std::nullopt }, true, false);
1299 check_historic_find_leaf_index_from(treeAtBlock2, fr(20), 1, 0, 2, true, false);
1300
1301 check_block_height(treeAtBlock2, 2);
1302
1303 // It should be impossible to commit using the image
1304 commit_tree(treeAtBlock2, false);
1305}
1306
1308{
1309 constexpr size_t depth = 10;
1310 std::string name = random_string();
1311 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
1312 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
1314 TreeType tree(std::move(store), pool);
1316
1317 constexpr uint32_t numBlocks = 50;
1318 constexpr uint32_t batchSize = 16;
1319 constexpr uint32_t windowSize = 8;
1320
1321 std::vector<fr_sibling_path> historicPathsZeroIndex;
1322 std::vector<fr_sibling_path> historicPathsMaxIndex;
1323 std::vector<fr> roots;
1324
1325 auto check = [&](index_t expectedSize, index_t expectedBlockHeight) {
1326 check_size(tree, expectedSize);
1327 check_block_height(tree, expectedBlockHeight);
1328 check_root(tree, memdb.root());
1329 check_sibling_path(tree, 0, memdb.get_sibling_path(0));
1330 check_sibling_path(tree, expectedSize - 1, memdb.get_sibling_path(expectedSize - 1));
1331
1332 for (uint32_t i = 0; i < historicPathsZeroIndex.size(); i++) {
1333 // retrieving historic data should fail if the block is outside of the window
1334 const block_number_t blockNumber = i + 1;
1335 const bool expectedSuccess =
1336 expectedBlockHeight <= windowSize || blockNumber > (expectedBlockHeight - windowSize);
1337 check_historic_sibling_path(tree, 0, historicPathsZeroIndex[i], blockNumber, expectedSuccess);
1338 index_t maxSizeAtBlock = ((i + 1) * batchSize) - 1;
1339 check_historic_sibling_path(tree, maxSizeAtBlock, historicPathsMaxIndex[i], blockNumber, expectedSuccess);
1340
1341 const index_t leafIndex = 6;
1342 check_historic_leaf(tree, blockNumber, get_value(leafIndex), leafIndex, expectedSuccess);
1343 check_historic_find_leaf_index(tree, get_value(leafIndex), blockNumber, leafIndex, expectedSuccess);
1344 check_historic_find_leaf_index_from(tree, get_value(leafIndex), blockNumber, 0, leafIndex, expectedSuccess);
1345 }
1346 };
1347
1348 for (uint32_t i = 0; i < numBlocks; i++) {
1349 std::vector<fr> to_add;
1350
1351 for (size_t j = 0; j < batchSize; ++j) {
1352 size_t ind = i * batchSize + j;
1353 memdb.update_element(ind, get_value(ind));
1354 to_add.push_back(get_value(ind));
1355 }
1356 index_t expected_size = (i + 1) * batchSize;
1357 add_values(tree, to_add);
1358 check(expected_size, i);
1359 commit_tree(tree);
1360
1361 // immediately finalize the block
1362 finalize_block(tree, i + 1);
1363
1364 historicPathsZeroIndex.push_back(memdb.get_sibling_path(0));
1365 historicPathsMaxIndex.push_back(memdb.get_sibling_path(expected_size - 1));
1366 roots.push_back(memdb.root());
1367
1368 // Now remove the oldest block if outside of the window
1369 if (i >= windowSize) {
1370 const block_number_t oldestBlock = (i + 1) - windowSize;
1371 // trying to remove a block that is not the most historic should fail
1372 remove_historic_block(tree, oldestBlock + 1, false);
1373
1374 if (oldestBlock > 1) {
1375 // trying to remove a block that is older than the most historic should succeed
1376 remove_historic_block(tree, oldestBlock - 1, true);
1377 }
1378
1379 fr rootToRemove = roots[oldestBlock - 1];
1380 check_block_and_root_data(db, oldestBlock, rootToRemove, true);
1381
1382 // removing the most historic should succeed
1383 remove_historic_block(tree, oldestBlock, true);
1384
1385 // the block data should have been removed
1386 check_block_and_root_data(db, oldestBlock, rootToRemove, false);
1387 }
1388 check(expected_size, i + 1);
1389 }
1390
1391 // Attempting to remove block 0 should fail as there isn't one
1392 remove_historic_block(tree, 0, false);
1393}
1394
1395void test_unwind(std::string directory,
1396 std::string name,
1397 uint64_t mapSize,
1398 uint64_t maxReaders,
1399 uint32_t depth,
1400 uint32_t blockSize,
1401 uint32_t numBlocks,
1402 uint32_t numBlocksToUnwind,
1403 std::vector<fr> values)
1404{
1405 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(directory, name, mapSize, maxReaders);
1406 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
1408 TreeType tree(std::move(store), pool);
1410
1411 uint32_t batchSize = blockSize;
1412
1413 std::vector<fr_sibling_path> historicPathsZeroIndex;
1414 std::vector<fr_sibling_path> historicPathsMaxIndex;
1415 std::vector<fr> roots;
1416
1417 fr initialRoot = memdb.root();
1418 fr_sibling_path initialPath = memdb.get_sibling_path(0);
1419
1420 for (uint32_t i = 0; i < numBlocks; i++) {
1421 std::vector<fr> to_add;
1422
1423 for (size_t j = 0; j < batchSize; ++j) {
1424 size_t ind = i * batchSize + j;
1425 memdb.update_element(ind, values[ind]);
1426 to_add.push_back(values[ind]);
1427 }
1428 index_t expected_size = (i + 1) * batchSize;
1429 add_values(tree, to_add);
1430
1431 // attempting an unwind of the block being built should fail
1432 unwind_block(tree, i + 1, false);
1433
1434 if (i > 0) {
1435 // attemnpting an unwind of the most recent committed block should fail as we have uncommitted changes
1436 unwind_block(tree, i, false);
1437 }
1438
1439 commit_tree(tree);
1440
1441 historicPathsZeroIndex.push_back(memdb.get_sibling_path(0));
1442 historicPathsMaxIndex.push_back(memdb.get_sibling_path(expected_size - 1));
1443 roots.push_back(memdb.root());
1444 check_root(tree, memdb.root());
1445 check_sibling_path(tree, 0, memdb.get_sibling_path(0));
1446 check_sibling_path(tree, expected_size - 1, memdb.get_sibling_path(expected_size - 1));
1447 check_size(tree, expected_size);
1448 check_block_and_size_data(db, i + 1, expected_size, true);
1449 check_block_and_root_data(db, i + 1, memdb.root(), true);
1450 }
1451
1452 const uint32_t blocksToRemove = numBlocksToUnwind;
1453 for (uint32_t i = 0; i < blocksToRemove; i++) {
1454 const block_number_t blockNumber = numBlocks - i;
1455
1456 check_block_and_root_data(db, blockNumber, roots[blockNumber - 1], true);
1457 // attempting to unwind a block that is ahead of the pending chain should just succeed
1458 unwind_block(tree, blockNumber + 1, true);
1459
1460 // attempting to unwind a block that is behind the pending chain should fail
1461 if (blockNumber > 1) {
1462 unwind_block(tree, blockNumber - 1, false);
1463 }
1464
1465 unwind_block(tree, blockNumber);
1466
1467 // the root should now only exist if there are other blocks with same root
1468 const auto last = roots.begin() + long(blockNumber - 1);
1469 const auto it =
1470 std::find_if(roots.begin(), last, [=](const fr& r) -> bool { return r == roots[blockNumber - 1]; });
1471 check_block_and_root_data(db, blockNumber, roots[blockNumber - 1], false, it != last);
1472
1473 const index_t previousValidBlock = blockNumber - 1;
1474 index_t deletedBlockStartIndex = previousValidBlock * batchSize;
1475
1476 check_block_height(tree, previousValidBlock);
1477 check_size(tree, deletedBlockStartIndex);
1478 check_root(tree, previousValidBlock == 0 ? initialRoot : roots[previousValidBlock - 1]);
1479
1480 // The zero index sibling path should be as it was at the previous block
1481 check_sibling_path(tree,
1482 0,
1483 previousValidBlock == 0 ? initialPath : historicPathsZeroIndex[previousValidBlock - 1],
1484 false,
1485 true);
1486
1487 // Trying to find leaves appended in the block that was removed should fail
1488 check_leaf(tree, values[1 + deletedBlockStartIndex], 1 + deletedBlockStartIndex, true, false);
1489 check_find_leaf_index<fr, TreeType>(tree, { values[1 + deletedBlockStartIndex] }, { std::nullopt }, true);
1490
1491 for (block_number_t j = 0; j < numBlocks; j++) {
1492 block_number_t historicBlockNumber = j + 1;
1493 bool expectedSuccess = historicBlockNumber <= previousValidBlock;
1494 check_historic_sibling_path(tree, 0, historicPathsZeroIndex[j], historicBlockNumber, expectedSuccess);
1495 index_t maxSizeAtBlock = ((j + 1) * batchSize) - 1;
1497 tree, maxSizeAtBlock, historicPathsMaxIndex[j], historicBlockNumber, expectedSuccess);
1498
1499 const index_t leafIndex = 1;
1500 check_historic_leaf(tree, historicBlockNumber, values[leafIndex], leafIndex, expectedSuccess);
1501
1502 std::vector<std::optional<index_t>> expected_results;
1503 if (expectedSuccess) {
1504 if (values[leafIndex] != fr::zero()) {
1505 expected_results.emplace_back(std::make_optional(leafIndex));
1506 } else {
1507 expected_results.emplace_back(std::nullopt);
1508 }
1509 }
1510
1511 // find historic leaves, provided they are not zero leaves
1512 check_historic_find_leaf_index<fr, TreeType>(
1513 tree, { values[leafIndex] }, historicBlockNumber, expected_results, expectedSuccess);
1514 check_historic_find_leaf_index_from<fr, TreeType>(
1515 tree, { values[leafIndex] }, historicBlockNumber, 0, expected_results, expectedSuccess);
1516 }
1517 }
1518}
1519
1521{
1522 std::vector<fr> first = create_values(1024);
1523 test_unwind(_directory, "DB", _mapSize, _maxReaders, 10, 16, 16, 8, first);
1524}
1525
1527{
1528 std::vector<fr> first = create_values(1024);
1529 test_unwind(_directory, "DB", _mapSize, _maxReaders, 10, 16, 16, 16, first);
1530 std::vector<fr> second = create_values(1024);
1531 test_unwind(_directory, "DB", _mapSize, _maxReaders, 10, 16, 16, 16, second);
1532}
1533
1534TEST_F(PersistedContentAddressedAppendOnlyTreeTest, can_unwind_initial_blocks_that_are_full_of_zeros)
1535{
1536 const size_t block_size = 16;
1537 const uint32_t num_blocks = 16;
1538 // First we add 16 blocks worth pf zero leaves and unwind them all
1539 std::vector<fr> first(1024, fr::zero());
1540 test_unwind(_directory, "DB", _mapSize, _maxReaders, 10, block_size, num_blocks, num_blocks, first);
1541 // now we add 1 block of zero leaves and the other blocks non-zero leaves and unwind them all
1542 std::vector<fr> second = create_values(1024);
1543 // set the first 16 values to be zeros
1544 for (size_t i = 0; i < block_size; i++) {
1545 second[i] = fr::zero();
1546 }
1547 test_unwind(_directory, "DB", _mapSize, _maxReaders, 10, block_size, num_blocks, num_blocks, second);
1548
1549 // now we add 2 block of zero leaves in the middle and the other blocks non-zero leaves and unwind them all
1550 std::vector<fr> third = create_values(1024);
1551 size_t offset = block_size * 2;
1552 for (size_t i = 0; i < block_size * 2; i++) {
1553 third[i + offset] = fr::zero();
1554 }
1555 test_unwind(_directory, "DB", _mapSize, _maxReaders, 10, block_size, num_blocks, num_blocks, third);
1556
1557 // Now we add a number of regular blocks and unwind
1558 std::vector<fr> fourth = create_values(1024);
1559 test_unwind(_directory, "DB", _mapSize, _maxReaders, 10, block_size, num_blocks, num_blocks, fourth);
1560}
1561
1562TEST_F(PersistedContentAddressedAppendOnlyTreeTest, can_sync_and_unwind_large_blocks)
1563{
1564
1565 constexpr uint32_t numBlocks = 4;
1566 constexpr uint32_t numBlocksToUnwind = 2;
1567 std::vector<uint32_t> blockSizes = { 2, 4, 8, 16, 32 };
1568 for (const uint32_t& size : blockSizes) {
1569 uint32_t actualSize = size * 1024;
1570 std::vector<fr> values = create_values(actualSize * numBlocks);
1571 std::stringstream ss;
1572 ss << "DB " << actualSize;
1573 test_unwind(_directory, ss.str(), _mapSize, _maxReaders, 20, actualSize, numBlocks, numBlocksToUnwind, values);
1574 }
1575}
1576
1577TEST_F(PersistedContentAddressedAppendOnlyTreeTest, can_commit_and_unwind_empty_blocks)
1578{
1579 std::string name = random_string();
1580 constexpr uint32_t depth = 10;
1581 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
1582 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
1584 TreeType tree(std::move(store), pool);
1586
1587 // The first path stored is the genesis state. This effectively makes everything 1 based.
1589 block_number_t blockNumber = 0;
1590 uint32_t batchSize = 64;
1591
1593 // commit an empty block
1594 values.emplace_back();
1595 // and another one
1596 values.emplace_back();
1597 // then a non-empty block
1598 values.push_back(create_values(batchSize));
1599 // then 2 more empty blocks
1600 values.emplace_back();
1601 values.emplace_back();
1602 // then a non-empty block
1603 values.push_back(create_values(batchSize));
1604
1605 index_t index = 0;
1606
1607 for (auto& blockValues : values) {
1608 add_values(tree, blockValues);
1609 commit_tree(tree);
1610
1611 for (const auto& blockValue : blockValues) {
1612 memdb.update_element(index++, blockValue);
1613 }
1614
1615 ++blockNumber;
1616
1617 paths.push_back(memdb.get_sibling_path(0));
1618
1619 check_sibling_path(tree, 0, memdb.get_sibling_path(0));
1620 check_historic_sibling_path(tree, 0, paths[1], 1);
1621 check_block_height(tree, blockNumber);
1622 }
1623
1624 while (blockNumber > 0) {
1625 // Unwind the next block
1626 unwind_block(tree, blockNumber);
1627
1628 --blockNumber;
1629
1630 check_sibling_path(tree, 0, paths[blockNumber]);
1631
1632 if (blockNumber > 0) {
1633 check_historic_sibling_path(tree, 0, paths[1], 1);
1634 check_block_height(tree, blockNumber);
1635 }
1636 }
1637}
1638
1639TEST_F(PersistedContentAddressedAppendOnlyTreeTest, can_commit_and_remove_historic_empty_blocks)
1640{
1641 std::string name = random_string();
1642 constexpr uint32_t depth = 10;
1643 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
1644 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
1646 TreeType tree(std::move(store), pool);
1648
1649 // The first path stored is the genesis state. This effectively makes everything 1 based.
1651 index_t blockNumber = 0;
1652 uint32_t batchSize = 64;
1653
1655 // commit an empty block
1656 values.emplace_back();
1657 // and another one
1658 values.emplace_back();
1659 // then a non-empty block
1660 values.push_back(create_values(batchSize));
1661 // then 2 more empty blocks
1662 values.emplace_back();
1663 values.emplace_back();
1664 // then a non-empty block
1665 values.push_back(create_values(batchSize));
1666
1667 index_t index = 0;
1668
1669 for (auto& blockValues : values) {
1670 add_values(tree, blockValues);
1671 commit_tree(tree);
1672
1673 for (const auto& blockValue : blockValues) {
1674 memdb.update_element(index++, blockValue);
1675 }
1676
1677 ++blockNumber;
1678
1679 paths.push_back(memdb.get_sibling_path(0));
1680
1681 check_sibling_path(tree, 0, memdb.get_sibling_path(0));
1682 check_historic_sibling_path(tree, 0, paths[1], 1);
1683 check_block_height(tree, blockNumber);
1684 }
1685
1686 block_number_t blockToRemove = 1;
1687
1688 while (blockToRemove < blockNumber) {
1689 finalize_block(tree, blockToRemove + 1);
1690 // Remove the historic next block
1691 remove_historic_block(tree, blockToRemove);
1692
1693 check_sibling_path(tree, 0, paths[blockNumber]);
1694
1695 check_historic_sibling_path(tree, 0, paths[blockToRemove + 1], blockToRemove + 1);
1696 check_block_height(tree, blockNumber);
1697 blockToRemove++;
1698 }
1699}
1700
1701TEST_F(PersistedContentAddressedAppendOnlyTreeTest, can_retrieve_block_numbers_by_index)
1702{
1703 std::string name = random_string();
1704 constexpr uint32_t depth = 10;
1705 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
1706 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
1708 TreeType tree(std::move(store), pool);
1709
1710 const size_t block_size = 32;
1711
1712 for (size_t i = 0; i < 5; i++) {
1713 std::vector<fr> values = create_values(block_size);
1714 add_values(tree, values);
1715 commit_tree(tree);
1716 }
1717 std::vector<index_t> indices{ 12, 33, 63, 64, 65, 80, 96, 159, 160 };
1719
1720 // All but the last block number should be valid when looking at latest
1721 get_blocks_for_indices(tree, indices, blockNumbers);
1722 EXPECT_EQ(blockNumbers.size(), indices.size());
1723
1724 index_t maxIndex = 5 * block_size - 1;
1725 for (size_t i = 0; i < blockNumbers.size(); i++) {
1726 bool present = indices[i] <= maxIndex;
1727 if (present) {
1728 block_number_t expected =
1729 static_cast<block_number_t>(1 + static_cast<block_number_t>(indices[i]) / block_size);
1730 EXPECT_EQ(blockNumbers[i].value(), expected);
1731 }
1732 EXPECT_EQ(blockNumbers[i].has_value(), present);
1733 }
1734
1735 // Now get blocks for indices from the perspective of block 2
1736 get_blocks_for_indices(tree, 2, indices, blockNumbers);
1737 EXPECT_EQ(blockNumbers.size(), indices.size());
1738
1739 maxIndex = 2 * block_size - 1;
1740 for (size_t i = 0; i < blockNumbers.size(); i++) {
1741 bool present = indices[i] <= maxIndex;
1742 if (present) {
1743 block_number_t expected =
1744 static_cast<block_number_t>(1 + static_cast<block_number_t>(indices[i]) / block_size);
1745 EXPECT_EQ(blockNumbers[i].value(), expected);
1746 }
1747 EXPECT_EQ(blockNumbers[i].has_value(), present);
1748 }
1749
1750 unwind_block(tree, 5);
1751 unwind_block(tree, 4);
1752
1753 get_blocks_for_indices(tree, indices, blockNumbers);
1754 EXPECT_EQ(blockNumbers.size(), indices.size());
1755 maxIndex = 3 * block_size - 1;
1756 for (size_t i = 0; i < blockNumbers.size(); i++) {
1757 bool present = indices[i] <= maxIndex;
1758 if (present) {
1759 block_number_t expected =
1760 static_cast<block_number_t>(1 + static_cast<block_number_t>(indices[i]) / block_size);
1761 EXPECT_EQ(blockNumbers[i].value(), expected);
1762 }
1763 EXPECT_EQ(blockNumbers[i].has_value(), present);
1764 }
1765
1766 // fork from block 1
1767 std::unique_ptr<Store> forkStore = std::make_unique<Store>(name, depth, 1, db);
1768 TreeType treeFork(std::move(forkStore), pool);
1769
1770 // Now, using the fork, get block indices but find it's limited to those of block 1
1771 get_blocks_for_indices(treeFork, indices, blockNumbers);
1772 EXPECT_EQ(blockNumbers.size(), indices.size());
1773
1774 maxIndex = block_size - 1;
1775 for (size_t i = 0; i < blockNumbers.size(); i++) {
1776 bool present = indices[i] <= maxIndex;
1777 if (present) {
1778 block_number_t expected =
1779 static_cast<block_number_t>(1 + static_cast<block_number_t>(indices[i]) / block_size);
1780 EXPECT_EQ(blockNumbers[i].value(), expected);
1781 }
1782 EXPECT_EQ(blockNumbers[i].has_value(), present);
1783 }
1784
1785 // Now, using the fork, get block indics from the perspective of block 2, but find it's limited to those of block 1
1786 get_blocks_for_indices(treeFork, 2, indices, blockNumbers);
1787 EXPECT_EQ(blockNumbers.size(), indices.size());
1788
1789 maxIndex = block_size - 1;
1790 for (size_t i = 0; i < blockNumbers.size(); i++) {
1791 bool present = indices[i] <= maxIndex;
1792 if (present) {
1793 block_number_t expected =
1794 static_cast<block_number_t>(1 + static_cast<block_number_t>(indices[i]) / block_size);
1795 EXPECT_EQ(blockNumbers[i].value(), expected);
1796 }
1797 EXPECT_EQ(blockNumbers[i].has_value(), present);
1798 }
1799}
1800
1802{
1803 std::string name = random_string();
1804 constexpr uint32_t depth = 10;
1805 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
1806 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
1808 TreeType tree(std::move(store), pool);
1810
1811 uint32_t blockSize = 16;
1812 uint32_t numBlocks = 16;
1813 uint32_t finalizedBlockDelay = 4;
1814 std::vector<fr> values = create_values(blockSize * numBlocks);
1815
1816 for (uint32_t i = 0; i < numBlocks; i++) {
1817 std::vector<fr> to_add;
1818
1819 for (size_t j = 0; j < blockSize; ++j) {
1820 size_t ind = i * blockSize + j;
1821 memdb.update_element(ind, values[ind]);
1822 to_add.push_back(values[ind]);
1823 }
1824 add_values(tree, to_add);
1825 commit_tree(tree);
1826
1827 block_number_t expectedFinalizedBlock = i < finalizedBlockDelay ? 0 : i - finalizedBlockDelay;
1828 check_finalized_block_height(tree, expectedFinalizedBlock);
1829
1830 if (i >= finalizedBlockDelay) {
1831
1832 block_number_t blockToFinalize = expectedFinalizedBlock + 1;
1833
1834 // attempting to finalize a block that doesn't exist should fail
1835 finalize_block(tree, blockToFinalize + numBlocks, false);
1836
1837 finalize_block(tree, blockToFinalize, true);
1838
1839 // finalising the currently finalized block should succeed
1840 finalize_block(tree, blockToFinalize, true);
1841 }
1842 }
1843}
1844
1846{
1847 std::string name = random_string();
1848 constexpr uint32_t depth = 10;
1849 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
1850 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
1852 TreeType tree(std::move(store), pool);
1854
1855 uint32_t blockSize = 16;
1856 uint32_t numBlocks = 16;
1857 std::vector<fr> values = create_values(blockSize * numBlocks);
1858
1859 for (uint32_t i = 0; i < numBlocks; i++) {
1860 std::vector<fr> to_add;
1861
1862 for (size_t j = 0; j < blockSize; ++j) {
1863 size_t ind = i * blockSize + j;
1864 memdb.update_element(ind, values[ind]);
1865 to_add.push_back(values[ind]);
1866 }
1867 add_values(tree, to_add);
1868 commit_tree(tree);
1869 }
1870
1871 check_block_height(tree, numBlocks);
1872
1873 block_number_t blockToFinalize = 8;
1874
1875 finalize_block(tree, blockToFinalize);
1876}
1877
1878TEST_F(PersistedContentAddressedAppendOnlyTreeTest, can_not_finalize_block_beyond_pending_chain)
1879{
1880 std::string name = random_string();
1881 constexpr uint32_t depth = 10;
1882 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
1883 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
1885 TreeType tree(std::move(store), pool);
1887
1888 uint32_t blockSize = 16;
1889 uint32_t numBlocks = 16;
1890 std::vector<fr> values = create_values(blockSize * numBlocks);
1891
1892 // finalising block 1 should fail
1893 finalize_block(tree, 1, false);
1894
1895 for (uint32_t i = 0; i < numBlocks; i++) {
1896 std::vector<fr> to_add;
1897
1898 for (size_t j = 0; j < blockSize; ++j) {
1899 size_t ind = i * blockSize + j;
1900 memdb.update_element(ind, values[ind]);
1901 to_add.push_back(values[ind]);
1902 }
1903 add_values(tree, to_add);
1904 commit_tree(tree);
1905 }
1906
1907 check_block_height(tree, numBlocks);
1908
1909 // should fail
1910 finalize_block(tree, numBlocks + 1, false);
1911
1912 // finalize the entire chain
1913 block_number_t blockToFinalize = numBlocks;
1914
1915 finalize_block(tree, blockToFinalize);
1916}
1917
1918TEST_F(PersistedContentAddressedAppendOnlyTreeTest, can_not_fork_from_unwound_blocks)
1919{
1920 std::string name = random_string();
1921 uint32_t depth = 20;
1922 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
1923 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
1925 TreeType tree(std::move(store), pool);
1926
1927 for (uint32_t i = 0; i < 5; i++) {
1928 std::vector<fr> values = create_values(1024);
1929 add_values(tree, values);
1930 commit_tree(tree);
1931 }
1932
1933 unwind_block(tree, 5);
1934 unwind_block(tree, 4);
1935
1936 EXPECT_THROW(Store(name, depth, 5, db), std::runtime_error);
1937 EXPECT_THROW(Store(name, depth, 4, db), std::runtime_error);
1938}
1939
1940TEST_F(PersistedContentAddressedAppendOnlyTreeTest, can_not_fork_from_expired_historical_blocks)
1941{
1942 std::string name = random_string();
1943 uint32_t depth = 20;
1944 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
1945 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
1947 TreeType tree(std::move(store), pool);
1948
1949 for (uint32_t i = 0; i < 5; i++) {
1950 std::vector<fr> values = create_values(1024);
1951 add_values(tree, values);
1952 commit_tree(tree);
1953 }
1954 finalize_block(tree, 3);
1955
1956 remove_historic_block(tree, 1);
1957 remove_historic_block(tree, 2);
1958
1959 EXPECT_THROW(Store(name, depth, 1, db), std::runtime_error);
1960 EXPECT_THROW(Store(name, depth, 2, db), std::runtime_error);
1961}
1962TEST_F(PersistedContentAddressedAppendOnlyTreeTest, can_fork_from_block_zero_when_not_latest)
1963{
1964 std::string name = random_string();
1965 uint32_t depth = 20;
1966 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
1967 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
1969 TreeType tree(std::move(store), pool);
1971
1972 uint32_t numBlocks = 5;
1973
1974 const fr initialRoot = memdb.root();
1975 const fr_sibling_path path = memdb.get_sibling_path(0);
1976
1977 for (uint32_t i = 0; i < numBlocks; i++) {
1978 std::vector<fr> values = create_values(1024);
1979 add_values(tree, values);
1980 commit_tree(tree);
1981 }
1982
1983 check_block_height(tree, numBlocks);
1984
1985 EXPECT_NO_THROW(Store(name, depth, 0, db));
1986
1987 std::unique_ptr<Store> store2 = std::make_unique<Store>(name, depth, 0, db);
1988 TreeType tree2(std::move(store2), pool);
1989
1990 check_root(tree2, initialRoot, false);
1991 check_sibling_path(tree2, 0, path, false, true);
1992}
1993
1995{
1996 std::string name = random_string();
1997 constexpr uint32_t depth = 10;
1998 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
1999 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
2001 TreeType tree(std::move(store), pool);
2003
2004 uint32_t blockSize = 16;
2005 uint32_t numBlocks = 16;
2006 std::vector<fr> values = create_values(blockSize * numBlocks);
2007
2008 for (uint32_t i = 0; i < numBlocks; i++) {
2009 std::vector<fr> to_add;
2010
2011 for (size_t j = 0; j < blockSize; ++j) {
2012 size_t ind = i * blockSize + j;
2013 memdb.update_element(ind, values[ind]);
2014 to_add.push_back(values[ind]);
2015 }
2016 add_values(tree, to_add);
2017 commit_tree(tree);
2018 }
2019
2020 check_block_height(tree, numBlocks);
2021
2022 block_number_t blockToFinalize = 8;
2023
2024 finalize_block(tree, blockToFinalize);
2025
2026 for (uint32_t i = numBlocks; i > blockToFinalize; i--) {
2027 unwind_block(tree, i);
2028 }
2029 unwind_block(tree, blockToFinalize, false);
2030}
2031
2032TEST_F(PersistedContentAddressedAppendOnlyTreeTest, can_not_historically_remove_finalized_block)
2033{
2034 std::string name = random_string();
2035 constexpr uint32_t depth = 10;
2036 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
2037 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
2039 TreeType tree(std::move(store), pool);
2041
2042 uint32_t blockSize = 16;
2043 uint32_t numBlocks = 16;
2044 std::vector<fr> values = create_values(blockSize * numBlocks);
2045
2046 for (uint32_t i = 0; i < numBlocks; i++) {
2047 std::vector<fr> to_add;
2048
2049 for (size_t j = 0; j < blockSize; ++j) {
2050 size_t ind = i * blockSize + j;
2051 memdb.update_element(ind, values[ind]);
2052 to_add.push_back(values[ind]);
2053 }
2054 add_values(tree, to_add);
2055 commit_tree(tree);
2056 }
2057
2058 check_block_height(tree, numBlocks);
2059
2060 block_number_t blockToFinalize = 8;
2061
2062 finalize_block(tree, blockToFinalize);
2063
2064 for (uint32_t i = 0; i < blockToFinalize - 1; i++) {
2065 remove_historic_block(tree, i + 1);
2066 }
2067 remove_historic_block(tree, blockToFinalize, false);
2068}
2069
2071{
2072 constexpr size_t depth = 10;
2073 uint32_t blockSize = 16;
2074 std::string name = random_string();
2076 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
2078
2079 {
2080 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
2081 TreeType tree(std::move(store), pool);
2082
2083 std::vector<fr> values = create_values(blockSize);
2084 add_values(tree, values);
2085
2086 commit_tree(tree);
2087 }
2088
2089 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
2090 TreeType tree(std::move(store), pool);
2091
2092 // We apply a number of updates and checkpoint the tree each time
2093
2094 uint32_t stackDepth = 20;
2095
2096 std::vector<fr_sibling_path> paths(stackDepth);
2097 uint32_t index = 0;
2098 for (; index < stackDepth - 1; index++) {
2099 std::vector<fr> values = create_values(blockSize);
2100 add_values(tree, values);
2101
2102 paths[index] = get_sibling_path(tree, 3);
2103
2104 try {
2105 checkpoint_tree(tree);
2106 } catch (std::exception& e) {
2107 std::cout << e.what() << std::endl;
2108 }
2109 }
2110
2111 // Now add one more depth, this will be un-checkpointed
2112 {
2113 std::vector<fr> values = create_values(blockSize);
2114 add_values(tree, values);
2115 paths[index] = get_sibling_path(tree, 3);
2116 }
2117
2118 index_t checkpointIndex = index;
2119
2120 // The tree is currently at the state of index 19
2121 EXPECT_EQ(get_sibling_path(tree, 3), paths[checkpointIndex]);
2122
2123 // We now alternate committing and reverting the checkpoints half way up the stack
2124
2125 for (; index > stackDepth / 2; index--) {
2126 if (index % 2 == 0) {
2127 revert_checkpoint_tree(tree, true);
2128 checkpointIndex = index - 1;
2129 } else {
2130 commit_checkpoint_tree(tree, true);
2131 }
2132
2133 EXPECT_EQ(get_sibling_path(tree, 3), paths[checkpointIndex]);
2134 }
2135
2136 // Now apply another set of updates and checkpoints back to the original stack depth
2137 for (; index < stackDepth - 1; index++) {
2138 std::vector<fr> values = create_values(blockSize);
2139 add_values(tree, values);
2140
2141 paths[index] = get_sibling_path(tree, 3);
2142
2143 try {
2144 checkpoint_tree(tree);
2145 } catch (std::exception& e) {
2146 std::cout << e.what() << std::endl;
2147 }
2148 }
2149
2150 // Now add one more depth, this will be un-checkpointed
2151 {
2152 std::vector<fr> values = create_values(blockSize);
2153 add_values(tree, values);
2154 paths[index] = get_sibling_path(tree, 3);
2155 }
2156
2157 // We now alternatively commit and revert all the way back to the start
2158 checkpointIndex = index;
2159
2160 // The tree is currently at the state of index 19
2161 EXPECT_EQ(get_sibling_path(tree, 3), paths[checkpointIndex]);
2162
2163 for (; index > 0; index--) {
2164 if (index % 2 == 0) {
2165 revert_checkpoint_tree(tree, true);
2166 checkpointIndex = index - 1;
2167 } else {
2168 commit_checkpoint_tree(tree, true);
2169 }
2170
2171 EXPECT_EQ(get_sibling_path(tree, 3), paths[checkpointIndex]);
2172 }
2173
2174 // Should not be able to commit or revert where there is no active checkpoint
2175 revert_checkpoint_tree(tree, false);
2176 commit_checkpoint_tree(tree, false);
2177}
2178
2180{
2181 constexpr size_t depth = 10;
2182 uint32_t blockSize = 16;
2183 std::string name = random_string();
2185 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
2187
2188 {
2189 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
2190 TreeType tree(std::move(store), pool);
2191
2192 std::vector<fr> values = create_values(blockSize);
2193 add_values(tree, values);
2194
2195 commit_tree(tree);
2196 }
2197
2198 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
2199 TreeType tree(std::move(store), pool);
2200
2201 // We apply a number of updates and checkpoint the tree each time
2202
2203 uint32_t stackDepth = 20;
2204
2205 std::vector<fr_sibling_path> paths(stackDepth);
2206 uint32_t index = 0;
2207 for (; index < stackDepth - 1; index++) {
2208 std::vector<fr> values = create_values(blockSize);
2209 add_values(tree, values);
2210
2211 paths[index] = get_sibling_path(tree, 3);
2212
2213 try {
2214 checkpoint_tree(tree);
2215 } catch (std::exception& e) {
2216 std::cout << e.what() << std::endl;
2217 }
2218 }
2219
2220 // Now we commit all the checkpoints
2222
2223 // Tree should still be at the final state
2224 check_sibling_path(tree, 3, paths[stackDepth - 2]);
2225
2226 // Should not be able to commit or revert where there is no active checkpoint
2227 revert_checkpoint_tree(tree, false);
2228 commit_checkpoint_tree(tree, false);
2229}
2230
2232{
2233 constexpr size_t depth = 10;
2234 uint32_t blockSize = 16;
2235 std::string name = random_string();
2237 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
2239
2240 {
2241 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
2242 TreeType tree(std::move(store), pool);
2243
2244 std::vector<fr> values = create_values(blockSize);
2245 add_values(tree, values);
2246
2247 commit_tree(tree);
2248 }
2249
2250 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
2251 TreeType tree(std::move(store), pool);
2252
2253 // We apply a number of updates and checkpoint the tree each time
2254
2255 uint32_t stackDepth = 20;
2256
2257 std::vector<fr_sibling_path> paths(stackDepth);
2258 uint32_t index = 0;
2259 for (; index < stackDepth - 1; index++) {
2260 std::vector<fr> values = create_values(blockSize);
2261 add_values(tree, values);
2262
2263 paths[index] = get_sibling_path(tree, 3);
2264
2265 try {
2266 checkpoint_tree(tree);
2267 } catch (std::exception& e) {
2268 std::cout << e.what() << std::endl;
2269 }
2270 }
2271
2272 // Now we revert all the checkpoints
2274
2275 // Tree should still be at the initial state
2276 check_sibling_path(tree, 3, paths[0]);
2277
2278 // Should not be able to commit or revert where there is no active checkpoint
2279 revert_checkpoint_tree(tree, false);
2280 commit_checkpoint_tree(tree, false);
2281}
2282
2284{
2285 constexpr size_t depth = 10;
2286 uint32_t blockSize = 16;
2287 std::string name = random_string();
2289 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
2290
2291 {
2292 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
2293 TreeType tree(std::move(store), pool);
2294 std::vector<fr> values = create_values(blockSize);
2295 add_values(tree, values);
2296 commit_tree(tree);
2297 }
2298
2299 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
2300 TreeType tree(std::move(store), pool);
2301
2302 // Capture initial state
2303 fr_sibling_path initial_path = get_sibling_path(tree, 0);
2304
2305 // Depth 1
2306 checkpoint_tree(tree);
2307 add_values(tree, create_values(blockSize));
2308 fr_sibling_path after_depth1_path = get_sibling_path(tree, 0);
2309
2310 // Depth 2
2311 checkpoint_tree(tree);
2312 add_values(tree, create_values(blockSize));
2313
2314 // Depth 3
2315 checkpoint_tree(tree);
2316 add_values(tree, create_values(blockSize));
2317 fr_sibling_path after_depth3_path = get_sibling_path(tree, 0);
2318
2319 // Commit depths 3 and 2 into depth 1, leaving depth at 1
2320 commit_tree_to_depth(tree, 1);
2321
2322 // Data from all depths should be present
2323 check_sibling_path(tree, 0, after_depth3_path);
2324
2325 // Revert depth 1 — should go back to initial state
2327 check_sibling_path(tree, 0, initial_path);
2328}
2329
2331{
2332 constexpr size_t depth = 10;
2333 uint32_t blockSize = 16;
2334 std::string name = random_string();
2336 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
2337
2338 {
2339 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
2340 TreeType tree(std::move(store), pool);
2341 std::vector<fr> values = create_values(blockSize);
2342 add_values(tree, values);
2343 commit_tree(tree);
2344 }
2345
2346 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
2347 TreeType tree(std::move(store), pool);
2348
2349 // Depth 1
2350 checkpoint_tree(tree);
2351 add_values(tree, create_values(blockSize));
2352 fr_sibling_path after_depth1_path = get_sibling_path(tree, 0);
2353
2354 // Depth 2
2355 checkpoint_tree(tree);
2356 add_values(tree, create_values(blockSize));
2357
2358 // Depth 3
2359 checkpoint_tree(tree);
2360 add_values(tree, create_values(blockSize));
2361
2362 // Revert depths 3 and 2, leaving depth at 1
2363 revert_tree_to_depth(tree, 1);
2364
2365 // Should be back to after depth 1 state
2366 check_sibling_path(tree, 0, after_depth1_path);
2367
2368 // Depth 1 still active — commit it
2370
2371 // Should still have depth 1 data
2372 check_sibling_path(tree, 0, after_depth1_path);
2373}
Implements a simple append-only merkle tree All methods are asynchronous unless specified as otherwis...
void get_leaf(const index_t &index, bool includeUncommitted, const GetLeafCallback &completion) const
Returns the leaf value at the provided index.
void remove_historic_block(const block_number_t &blockNumber, const RemoveHistoricBlockCallback &on_completion)
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.
virtual void add_values(const std::vector< fr > &values, const AppendCompletionCallback &on_completion)
Adds the given set of values to the end of the tree.
void get_meta_data(bool includeUncommitted, const MetaDataCallback &on_completion) const
Returns the tree meta data.
void find_block_numbers(const std::vector< index_t > &indices, const GetBlockForIndexCallback &on_completion) const
Returns the block numbers that correspond to the given indices values.
void unwind_block(const block_number_t &blockNumber, const UnwindBlockCallback &on_completion)
virtual void add_value(const fr &value, const AppendCompletionCallback &on_completion)
Adds a single value to the end of the tree.
void get_subtree_sibling_path(uint32_t subtree_depth, const HashPathCallback &on_completion, bool includeUncommitted) const
Get the subtree sibling path object.
std::function< void(TypedResponse< CommitResponse > &)> CommitCallback
void finalize_block(const block_number_t &blockNumber, const FinalizeBlockCallback &on_completion)
void find_leaf_sibling_paths(const std::vector< typename Store::LeafType > &leaves, bool includeUncommitted, const FindSiblingPathCallback &on_completion) const
Returns the sibling paths for the provided leaves in the tree.
Serves as a key-value node store for merkle trees. Caches all changes in memory before persisting the...
std::shared_ptr< LMDBTreeStore > SharedPtr
fr update_element(size_t index, fr const &value)
fr get_node(uint32_t level, size_t index) const
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
bool expected_result
void check_historic_leaf(TreeType &tree, const block_number_t &blockNumber, const fr &leaf, index_t leaf_index, bool expected_success, bool expected_match=true, bool includeUncommitted=true)
void add_value(TreeType &tree, const fr &value)
void check_size(TreeType &tree, index_t expected_size, bool includeUncommitted=true)
void check_historic_sibling_path_by_value(TreeType &tree, fr value, fr_sibling_path expected_sibling_path, index_t expected_index, block_number_t blockNumber, bool expected_success=true)
void test_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)
ContentAddressedCachedTreeStore< bb::fr > Store
void get_blocks_for_indices(TreeType &tree, const std::vector< index_t > &indices, std::vector< std::optional< block_number_t > > &blockNumbers)
void check_finalized_block_height(TreeType &tree, block_number_t expected_finalized_block)
void check_sibling_path_by_value(TreeType &tree, fr value, fr_sibling_path expected_sibling_path, index_t expected_index, bool includeUncommitted=true, bool expected_result=true)
void check_leaf(TreeType &tree, const fr &leaf, index_t leaf_index, bool expected_success, bool expected_match=true, bool includeUncommitted=true)
void check_block_height(TreeType &tree, index_t expected_block_height)
void remove_historic_block(TreeType &tree, const block_number_t &blockNumber, bool expected_success=true)
void unwind_block(TreeType &tree, const block_number_t &blockNumber, bool expected_success=true)
void add_values(TreeType &tree, const std::vector< fr > &values)
void finalize_block(TreeType &tree, const block_number_t &blockNumber, bool expected_success=true)
void check_historic_sibling_path(TreeType &tree, index_t index, fr_sibling_path expected_sibling_path, block_number_t blockNumber, bool expected_success=true)
void check_sibling_path(TreeType &tree, index_t index, fr_sibling_path expected_sibling_path, bool includeUncommitted=true, bool expected_result=true)
void commit_tree(TreeType &tree, bool expected_success=true)
void check_root(TreeType &tree, fr expected_root, bool includeUncommitted=true)
ssize_t offset
Definition engine.cpp:62
ThreadPoolPtr make_thread_pool(uint64_t numThreads)
Definition fixtures.hpp:60
void commit_all_tree_checkpoints(TreeType &tree, bool expected_success=true)
void rollback_tree(TreeType &tree)
const fr & get_value(size_t index)
const uint32_t NUM_VALUES
Definition fixtures.hpp:16
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
void revert_tree_to_depth(TreeType &tree, uint32_t depth, bool expected_success=true)
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 commit_tree_to_depth(TreeType &tree, uint32_t depth, bool expected_success=true)
void revert_checkpoint_tree(TreeType &tree, bool expected_success=true)
void revert_all_tree_checkpoints(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)
Entry point for Barretenberg command-line interface.
Definition api.hpp:5
TEST_F(IPATest, ChallengesAreZero)
Definition ipa.test.cpp:155
field< Bn254FrParams > fr
Definition fr.hpp:155
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
static fr hash_pair(const fr &lhs, const fr &rhs)
Definition hash.hpp:19
static constexpr field zero()