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
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 tree.get_meta_data(i + 1, false, [&](auto response) -> void { block_tree_size = response.inner.meta.size; });
1029 for (uint32_t j = 0; j < num_blocks; j++) {
1030 index_t indexToQuery = j * batch_size;
1031 fr expectedLeaf = j <= i ? get_value(indexToQuery) : fr::zero();
1032 check_historic_leaf(tree, i + 1, expectedLeaf, indexToQuery, indexToQuery <= block_tree_size, true, false);
1033 }
1034 }
1035}
1036
1038{
1039 constexpr size_t depth = 5;
1040 std::string name = random_string();
1041 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
1042 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
1044 TreeType tree(std::move(store), pool);
1045
1046 std::vector<fr> values{ 30, 10, 20, 40 };
1047 add_values(tree, values);
1048
1049 commit_tree(tree);
1050
1051 values = { 15, 18, 26, 2 };
1052 add_values(tree, values);
1053
1054 commit_tree(tree);
1055
1056 values = { 16, 4, 19, 22 };
1057 add_values(tree, values);
1058
1059 // should not be present at block 1
1060 check_historic_find_leaf_index<fr, TreeType>(tree, { fr(26) }, 1, { std::nullopt }, true);
1061 // should be present at block 2
1062 check_historic_find_leaf_index(tree, fr(26), 2, 6, true);
1063
1064 // at block 1 leaf 18 should not be found if only considering committed
1065 check_historic_find_leaf_index_from<fr, TreeType>(tree, { fr(18) }, 1, 2, { std::nullopt }, true, false);
1066 // at block 2 it should be
1067 check_historic_find_leaf_index_from(tree, fr(18), 2, 2, 5, true);
1068 // at block 2, from index 6, 19 should not be found if looking only at committed
1069 check_historic_find_leaf_index_from<fr, TreeType>(tree, { fr(19) }, 2, 6, { std::nullopt }, true, false);
1070 // at block 2, from index 6, 19 should be found if looking at uncommitted too
1071 check_historic_find_leaf_index_from(tree, fr(19), 2, 6, 10, true);
1072
1073 commit_tree(tree);
1074
1075 // at block 3, from index 6, should now be found in committed only
1076 check_historic_find_leaf_index_from(tree, fr(19), 3, 6, 10, true, false);
1077}
1078
1080{
1081 constexpr size_t depth = 3;
1082 std::string name = random_string();
1083 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
1084 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
1086 TreeType tree(std::move(store), pool);
1088
1089 check_size(tree, 0);
1090 check_root(tree, memdb.root());
1091
1092 for (size_t i = 0; i < 8; i++) {
1093 memdb.update_element(i, get_value(i));
1094 add_value(tree, get_value(i));
1095 }
1096
1097 check_root(tree, memdb.root());
1098 check_sibling_path(tree, 0, memdb.get_sibling_path(0));
1099 check_sibling_path(tree, 7, memdb.get_sibling_path(7));
1100}
1101
1103{
1104 constexpr size_t depth = 10;
1106 fr_sibling_path initial_path = memdb.get_sibling_path(0);
1107 memdb.update_element(0, get_value(0));
1108 fr_sibling_path final_sibling_path = memdb.get_sibling_path(0);
1109
1110 uint32_t num_reads = 16 * 1024;
1111 std::vector<fr_sibling_path> paths(num_reads);
1112
1113 {
1114 std::string name = random_string();
1115 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
1116 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
1118 TreeType tree(std::move(store), pool);
1119
1120 check_size(tree, 0);
1121
1122 Signal signal(1 + num_reads);
1123
1124 auto add_completion = [&](const TypedResponse<AddDataResponse>&) {
1125 auto commit_completion = [&](const TypedResponse<CommitResponse>&) { signal.signal_decrement(); };
1126 tree.commit(commit_completion);
1127 };
1128 tree.add_value(get_value(0), add_completion);
1129
1130 for (size_t i = 0; i < num_reads; i++) {
1131 auto completion = [&, i](const TypedResponse<GetSiblingPathResponse>& response) {
1132 paths[i] = response.inner.path;
1133 signal.signal_decrement();
1134 };
1135 tree.get_sibling_path(0, completion, false);
1136 }
1137 signal.wait_for_level(0);
1138 }
1139
1140 for (auto& path : paths) {
1141 EXPECT_TRUE(path == initial_path || path == final_sibling_path);
1142 }
1143}
1144
1146{
1147 constexpr size_t depth = 3;
1148 std::string name = random_string();
1149 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
1150 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
1152 TreeType tree(std::move(store), pool);
1153
1154 add_values(tree, { 30, 10, 20, 40 });
1155
1156 check_leaf(tree, 30, 0, true);
1157 check_leaf(tree, 10, 1, true);
1158 check_leaf(tree, 20, 2, true);
1159 check_leaf(tree, 40, 3, true);
1160
1161 check_leaf(tree, 1, 4, true, false);
1162 check_leaf(tree, 0, 4, true);
1163 check_leaf(tree, 0, 9, false);
1164}
1165
1167{
1168 constexpr size_t depth = 4;
1169 std::string name = random_string();
1170 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
1171 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
1173 TreeType tree(std::move(store), pool);
1175
1176 add_values(tree, { 30, 10, 20, 40 });
1177 memdb.update_element(0, 30);
1178 memdb.update_element(1, 10);
1179 memdb.update_element(2, 20);
1180 memdb.update_element(3, 40);
1181
1182 {
1183 Signal signal(1);
1184 tree.get_subtree_sibling_path(
1185 0,
1186 [&](auto& resp) {
1187 fr_sibling_path expected_sibling_path = memdb.get_sibling_path(4);
1188 EXPECT_EQ(resp.inner.path, expected_sibling_path);
1189 signal.signal_level();
1190 },
1191 true);
1192 signal.wait_for_level();
1193 }
1194
1195 {
1196 Signal signal(1);
1197 tree.get_subtree_sibling_path(
1198 2,
1199 [&](auto& resp) {
1200 fr_sibling_path expected_sibling_path = { memdb.get_node(2, 0), memdb.get_node(1, 1) };
1201 EXPECT_EQ(resp.inner.path, expected_sibling_path);
1202 signal.signal_level();
1203 },
1204 true);
1205 signal.wait_for_level();
1206 }
1207}
1208
1209TEST_F(PersistedContentAddressedAppendOnlyTreeTest, can_create_images_at_historic_blocks)
1210{
1211 constexpr size_t depth = 5;
1212 std::string name = random_string();
1214 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
1216 size_t index = 0;
1217
1218 std::unique_ptr<Store> store1 = std::make_unique<Store>(name, depth, db);
1219 TreeType tree1(std::move(store1), pool);
1220
1221 std::vector<fr> values{ 30, 10, 20, 40 };
1222 add_values(tree1, values);
1223 for (auto v : values) {
1224 memdb.update_element(index++, v);
1225 }
1226
1227 commit_tree(tree1);
1228
1229 check_block_and_root_data(db, 1, memdb.root(), true);
1230
1231 fr_sibling_path block1SiblingPathIndex3 = memdb.get_sibling_path(3);
1232
1233 values = { 15, 18, 26, 2 };
1234 add_values(tree1, values);
1235 for (auto v : values) {
1236 memdb.update_element(index++, v);
1237 }
1238
1239 commit_tree(tree1);
1240
1241 check_block_and_root_data(db, 2, memdb.root(), true);
1242
1243 fr block2Root = memdb.root();
1244
1245 fr_sibling_path block2SiblingPathIndex7 = memdb.get_sibling_path(7);
1246 fr_sibling_path block2SiblingPathIndex3 = memdb.get_sibling_path(3);
1247
1248 values = { 16, 4, 18, 22 };
1249 add_values(tree1, values);
1250 for (auto v : values) {
1251 memdb.update_element(index++, v);
1252 }
1253
1254 commit_tree(tree1);
1255
1256 check_block_and_root_data(db, 3, memdb.root(), true);
1257
1258 fr_sibling_path block3SiblingPathIndex11 = memdb.get_sibling_path(11);
1259 fr_sibling_path block3SiblingPathIndex7 = memdb.get_sibling_path(7);
1260 fr_sibling_path block3SiblingPathIndex3 = memdb.get_sibling_path(3);
1261
1262 // Create a new image at block 2
1263 std::unique_ptr<Store> storeAtBlock2 = std::make_unique<Store>(name, depth, 2, db);
1264 TreeType treeAtBlock2(std::move(storeAtBlock2), pool);
1265
1266 check_root(treeAtBlock2, block2Root);
1267 check_sibling_path(treeAtBlock2, 3, block2SiblingPathIndex3, false, true);
1268 check_leaf(treeAtBlock2, 20, 2, true);
1269 check_find_leaf_index(treeAtBlock2, fr(10), 1, true);
1270 check_find_leaf_index_from(treeAtBlock2, fr(15), 1, 4, true);
1271
1272 // should not exist in our image
1273 check_leaf(treeAtBlock2, 4, 9, true, false);
1274 check_find_leaf_index<fr, TreeType>(treeAtBlock2, { fr(4) }, { std::nullopt }, true);
1275
1276 // now add the same values to our image
1277 add_values(treeAtBlock2, values);
1278
1279 // the state of our image should match the original tree
1280 check_sibling_path(tree1, 3, block3SiblingPathIndex3, false, true);
1281 check_sibling_path(tree1, 7, block3SiblingPathIndex7, false, true);
1282
1283 // needs to use uncommitted for this check
1284 check_sibling_path(treeAtBlock2, 3, block3SiblingPathIndex3, true, true);
1285 check_sibling_path(treeAtBlock2, 7, block3SiblingPathIndex7, true, true);
1286
1287 // now check historic data
1288 check_historic_sibling_path(treeAtBlock2, 3, block1SiblingPathIndex3, 1);
1289 check_historic_find_leaf_index(treeAtBlock2, fr(10), 2, 1, true);
1290 check_historic_find_leaf_index(treeAtBlock2, fr(16), 2, 8, true, true);
1291 check_historic_find_leaf_index<fr, TreeType>(treeAtBlock2, { fr(16) }, 2, { std::nullopt }, true, false);
1292
1293 check_historic_find_leaf_index_from<fr, TreeType>(treeAtBlock2, { fr(18) }, 1, 3, { std::nullopt }, true, false);
1294 check_historic_find_leaf_index_from(treeAtBlock2, fr(20), 1, 0, 2, true, false);
1295
1296 check_block_height(treeAtBlock2, 2);
1297
1298 // It should be impossible to commit using the image
1299 commit_tree(treeAtBlock2, false);
1300}
1301
1303{
1304 constexpr size_t depth = 10;
1305 std::string name = random_string();
1306 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
1307 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
1309 TreeType tree(std::move(store), pool);
1311
1312 constexpr uint32_t numBlocks = 50;
1313 constexpr uint32_t batchSize = 16;
1314 constexpr uint32_t windowSize = 8;
1315
1316 std::vector<fr_sibling_path> historicPathsZeroIndex;
1317 std::vector<fr_sibling_path> historicPathsMaxIndex;
1318 std::vector<fr> roots;
1319
1320 auto check = [&](index_t expectedSize, index_t expectedBlockHeight) {
1321 check_size(tree, expectedSize);
1322 check_block_height(tree, expectedBlockHeight);
1323 check_root(tree, memdb.root());
1324 check_sibling_path(tree, 0, memdb.get_sibling_path(0));
1325 check_sibling_path(tree, expectedSize - 1, memdb.get_sibling_path(expectedSize - 1));
1326
1327 for (uint32_t i = 0; i < historicPathsZeroIndex.size(); i++) {
1328 // retrieving historic data should fail if the block is outside of the window
1329 const block_number_t blockNumber = i + 1;
1330 const bool expectedSuccess =
1331 expectedBlockHeight <= windowSize || blockNumber > (expectedBlockHeight - windowSize);
1332 check_historic_sibling_path(tree, 0, historicPathsZeroIndex[i], blockNumber, expectedSuccess);
1333 index_t maxSizeAtBlock = ((i + 1) * batchSize) - 1;
1334 check_historic_sibling_path(tree, maxSizeAtBlock, historicPathsMaxIndex[i], blockNumber, expectedSuccess);
1335
1336 const index_t leafIndex = 6;
1337 check_historic_leaf(tree, blockNumber, get_value(leafIndex), leafIndex, expectedSuccess);
1338 check_historic_find_leaf_index(tree, get_value(leafIndex), blockNumber, leafIndex, expectedSuccess);
1339 check_historic_find_leaf_index_from(tree, get_value(leafIndex), blockNumber, 0, leafIndex, expectedSuccess);
1340 }
1341 };
1342
1343 for (uint32_t i = 0; i < numBlocks; i++) {
1344 std::vector<fr> to_add;
1345
1346 for (size_t j = 0; j < batchSize; ++j) {
1347 size_t ind = i * batchSize + j;
1348 memdb.update_element(ind, get_value(ind));
1349 to_add.push_back(get_value(ind));
1350 }
1351 index_t expected_size = (i + 1) * batchSize;
1352 add_values(tree, to_add);
1353 check(expected_size, i);
1354 commit_tree(tree);
1355
1356 // immediately finalize the block
1357 finalize_block(tree, i + 1);
1358
1359 historicPathsZeroIndex.push_back(memdb.get_sibling_path(0));
1360 historicPathsMaxIndex.push_back(memdb.get_sibling_path(expected_size - 1));
1361 roots.push_back(memdb.root());
1362
1363 // Now remove the oldest block if outside of the window
1364 if (i >= windowSize) {
1365 const block_number_t oldestBlock = (i + 1) - windowSize;
1366 // trying to remove a block that is not the most historic should fail
1367 remove_historic_block(tree, oldestBlock + 1, false);
1368
1369 if (oldestBlock > 1) {
1370 // trying to remove a block that is older than the most historic should succeed
1371 remove_historic_block(tree, oldestBlock - 1, true);
1372 }
1373
1374 fr rootToRemove = roots[oldestBlock - 1];
1375 check_block_and_root_data(db, oldestBlock, rootToRemove, true);
1376
1377 // removing the most historic should succeed
1378 remove_historic_block(tree, oldestBlock, true);
1379
1380 // the block data should have been removed
1381 check_block_and_root_data(db, oldestBlock, rootToRemove, false);
1382 }
1383 check(expected_size, i + 1);
1384 }
1385
1386 // Attempting to remove block 0 should fail as there isn't one
1387 remove_historic_block(tree, 0, false);
1388}
1389
1390void test_unwind(std::string directory,
1391 std::string name,
1392 uint64_t mapSize,
1393 uint64_t maxReaders,
1394 uint32_t depth,
1395 uint32_t blockSize,
1396 uint32_t numBlocks,
1397 uint32_t numBlocksToUnwind,
1398 std::vector<fr> values)
1399{
1400 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(directory, name, mapSize, maxReaders);
1401 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
1403 TreeType tree(std::move(store), pool);
1405
1406 uint32_t batchSize = blockSize;
1407
1408 std::vector<fr_sibling_path> historicPathsZeroIndex;
1409 std::vector<fr_sibling_path> historicPathsMaxIndex;
1410 std::vector<fr> roots;
1411
1412 fr initialRoot = memdb.root();
1413 fr_sibling_path initialPath = memdb.get_sibling_path(0);
1414
1415 for (uint32_t i = 0; i < numBlocks; i++) {
1416 std::vector<fr> to_add;
1417
1418 for (size_t j = 0; j < batchSize; ++j) {
1419 size_t ind = i * batchSize + j;
1420 memdb.update_element(ind, values[ind]);
1421 to_add.push_back(values[ind]);
1422 }
1423 index_t expected_size = (i + 1) * batchSize;
1424 add_values(tree, to_add);
1425
1426 // attempting an unwind of the block being built should fail
1427 unwind_block(tree, i + 1, false);
1428
1429 if (i > 0) {
1430 // attemnpting an unwind of the most recent committed block should fail as we have uncommitted changes
1431 unwind_block(tree, i, false);
1432 }
1433
1434 commit_tree(tree);
1435
1436 historicPathsZeroIndex.push_back(memdb.get_sibling_path(0));
1437 historicPathsMaxIndex.push_back(memdb.get_sibling_path(expected_size - 1));
1438 roots.push_back(memdb.root());
1439 check_root(tree, memdb.root());
1440 check_sibling_path(tree, 0, memdb.get_sibling_path(0));
1441 check_sibling_path(tree, expected_size - 1, memdb.get_sibling_path(expected_size - 1));
1442 check_size(tree, expected_size);
1443 check_block_and_size_data(db, i + 1, expected_size, true);
1444 check_block_and_root_data(db, i + 1, memdb.root(), true);
1445 }
1446
1447 const uint32_t blocksToRemove = numBlocksToUnwind;
1448 for (uint32_t i = 0; i < blocksToRemove; i++) {
1449 const block_number_t blockNumber = numBlocks - i;
1450
1451 check_block_and_root_data(db, blockNumber, roots[blockNumber - 1], true);
1452 // attempting to unwind a block that is ahead of the pending chain should just succeed
1453 unwind_block(tree, blockNumber + 1, true);
1454
1455 // attempting to unwind a block that is behind the pending chain should fail
1456 if (blockNumber > 1) {
1457 unwind_block(tree, blockNumber - 1, false);
1458 }
1459
1460 unwind_block(tree, blockNumber);
1461
1462 // the root should now only exist if there are other blocks with same root
1463 const auto last = roots.begin() + long(blockNumber - 1);
1464 const auto it =
1465 std::find_if(roots.begin(), last, [=](const fr& r) -> bool { return r == roots[blockNumber - 1]; });
1466 check_block_and_root_data(db, blockNumber, roots[blockNumber - 1], false, it != last);
1467
1468 const index_t previousValidBlock = blockNumber - 1;
1469 index_t deletedBlockStartIndex = previousValidBlock * batchSize;
1470
1471 check_block_height(tree, previousValidBlock);
1472 check_size(tree, deletedBlockStartIndex);
1473 check_root(tree, previousValidBlock == 0 ? initialRoot : roots[previousValidBlock - 1]);
1474
1475 // The zero index sibling path should be as it was at the previous block
1476 check_sibling_path(tree,
1477 0,
1478 previousValidBlock == 0 ? initialPath : historicPathsZeroIndex[previousValidBlock - 1],
1479 false,
1480 true);
1481
1482 // Trying to find leaves appended in the block that was removed should fail
1483 check_leaf(tree, values[1 + deletedBlockStartIndex], 1 + deletedBlockStartIndex, true, false);
1484 check_find_leaf_index<fr, TreeType>(tree, { values[1 + deletedBlockStartIndex] }, { std::nullopt }, true);
1485
1486 for (block_number_t j = 0; j < numBlocks; j++) {
1487 block_number_t historicBlockNumber = j + 1;
1488 bool expectedSuccess = historicBlockNumber <= previousValidBlock;
1489 check_historic_sibling_path(tree, 0, historicPathsZeroIndex[j], historicBlockNumber, expectedSuccess);
1490 index_t maxSizeAtBlock = ((j + 1) * batchSize) - 1;
1492 tree, maxSizeAtBlock, historicPathsMaxIndex[j], historicBlockNumber, expectedSuccess);
1493
1494 const index_t leafIndex = 1;
1495 check_historic_leaf(tree, historicBlockNumber, values[leafIndex], leafIndex, expectedSuccess);
1496
1497 std::vector<std::optional<index_t>> expected_results;
1498 if (expectedSuccess) {
1499 if (values[leafIndex] != fr::zero()) {
1500 expected_results.emplace_back(std::make_optional(leafIndex));
1501 } else {
1502 expected_results.emplace_back(std::nullopt);
1503 }
1504 }
1505
1506 // find historic leaves, provided they are not zero leaves
1507 check_historic_find_leaf_index<fr, TreeType>(
1508 tree, { values[leafIndex] }, historicBlockNumber, expected_results, expectedSuccess);
1509 check_historic_find_leaf_index_from<fr, TreeType>(
1510 tree, { values[leafIndex] }, historicBlockNumber, 0, expected_results, expectedSuccess);
1511 }
1512 }
1513}
1514
1516{
1517 std::vector<fr> first = create_values(1024);
1518 test_unwind(_directory, "DB", _mapSize, _maxReaders, 10, 16, 16, 8, first);
1519}
1520
1522{
1523 std::vector<fr> first = create_values(1024);
1524 test_unwind(_directory, "DB", _mapSize, _maxReaders, 10, 16, 16, 16, first);
1525 std::vector<fr> second = create_values(1024);
1526 test_unwind(_directory, "DB", _mapSize, _maxReaders, 10, 16, 16, 16, second);
1527}
1528
1529TEST_F(PersistedContentAddressedAppendOnlyTreeTest, can_unwind_initial_blocks_that_are_full_of_zeros)
1530{
1531 const size_t block_size = 16;
1532 const uint32_t num_blocks = 16;
1533 // First we add 16 blocks worth pf zero leaves and unwind them all
1534 std::vector<fr> first(1024, fr::zero());
1535 test_unwind(_directory, "DB", _mapSize, _maxReaders, 10, block_size, num_blocks, num_blocks, first);
1536 // now we add 1 block of zero leaves and the other blocks non-zero leaves and unwind them all
1537 std::vector<fr> second = create_values(1024);
1538 // set the first 16 values to be zeros
1539 for (size_t i = 0; i < block_size; i++) {
1540 second[i] = fr::zero();
1541 }
1542 test_unwind(_directory, "DB", _mapSize, _maxReaders, 10, block_size, num_blocks, num_blocks, second);
1543
1544 // now we add 2 block of zero leaves in the middle and the other blocks non-zero leaves and unwind them all
1545 std::vector<fr> third = create_values(1024);
1546 size_t offset = block_size * 2;
1547 for (size_t i = 0; i < block_size * 2; i++) {
1548 third[i + offset] = fr::zero();
1549 }
1550 test_unwind(_directory, "DB", _mapSize, _maxReaders, 10, block_size, num_blocks, num_blocks, third);
1551
1552 // Now we add a number of regular blocks and unwind
1553 std::vector<fr> fourth = create_values(1024);
1554 test_unwind(_directory, "DB", _mapSize, _maxReaders, 10, block_size, num_blocks, num_blocks, fourth);
1555}
1556
1557TEST_F(PersistedContentAddressedAppendOnlyTreeTest, can_sync_and_unwind_large_blocks)
1558{
1559
1560 constexpr uint32_t numBlocks = 4;
1561 constexpr uint32_t numBlocksToUnwind = 2;
1562 std::vector<uint32_t> blockSizes = { 2, 4, 8, 16, 32 };
1563 for (const uint32_t& size : blockSizes) {
1564 uint32_t actualSize = size * 1024;
1565 std::vector<fr> values = create_values(actualSize * numBlocks);
1566 std::stringstream ss;
1567 ss << "DB " << actualSize;
1568 test_unwind(_directory, ss.str(), _mapSize, _maxReaders, 20, actualSize, numBlocks, numBlocksToUnwind, values);
1569 }
1570}
1571
1572TEST_F(PersistedContentAddressedAppendOnlyTreeTest, can_commit_and_unwind_empty_blocks)
1573{
1574 std::string name = random_string();
1575 constexpr uint32_t depth = 10;
1576 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
1577 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
1579 TreeType tree(std::move(store), pool);
1581
1582 // The first path stored is the genesis state. This effectively makes everything 1 based.
1584 block_number_t blockNumber = 0;
1585 uint32_t batchSize = 64;
1586
1588 // commit an empty block
1589 values.emplace_back();
1590 // and another one
1591 values.emplace_back();
1592 // then a non-empty block
1593 values.push_back(create_values(batchSize));
1594 // then 2 more empty blocks
1595 values.emplace_back();
1596 values.emplace_back();
1597 // then a non-empty block
1598 values.push_back(create_values(batchSize));
1599
1600 index_t index = 0;
1601
1602 for (auto& blockValues : values) {
1603 add_values(tree, blockValues);
1604 commit_tree(tree);
1605
1606 for (const auto& blockValue : blockValues) {
1607 memdb.update_element(index++, blockValue);
1608 }
1609
1610 ++blockNumber;
1611
1612 paths.push_back(memdb.get_sibling_path(0));
1613
1614 check_sibling_path(tree, 0, memdb.get_sibling_path(0));
1615 check_historic_sibling_path(tree, 0, paths[1], 1);
1616 check_block_height(tree, blockNumber);
1617 }
1618
1619 while (blockNumber > 0) {
1620 // Unwind the next block
1621 unwind_block(tree, blockNumber);
1622
1623 --blockNumber;
1624
1625 check_sibling_path(tree, 0, paths[blockNumber]);
1626
1627 if (blockNumber > 0) {
1628 check_historic_sibling_path(tree, 0, paths[1], 1);
1629 check_block_height(tree, blockNumber);
1630 }
1631 }
1632}
1633
1634TEST_F(PersistedContentAddressedAppendOnlyTreeTest, can_commit_and_remove_historic_empty_blocks)
1635{
1636 std::string name = random_string();
1637 constexpr uint32_t depth = 10;
1638 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
1639 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
1641 TreeType tree(std::move(store), pool);
1643
1644 // The first path stored is the genesis state. This effectively makes everything 1 based.
1646 index_t blockNumber = 0;
1647 uint32_t batchSize = 64;
1648
1650 // commit an empty block
1651 values.emplace_back();
1652 // and another one
1653 values.emplace_back();
1654 // then a non-empty block
1655 values.push_back(create_values(batchSize));
1656 // then 2 more empty blocks
1657 values.emplace_back();
1658 values.emplace_back();
1659 // then a non-empty block
1660 values.push_back(create_values(batchSize));
1661
1662 index_t index = 0;
1663
1664 for (auto& blockValues : values) {
1665 add_values(tree, blockValues);
1666 commit_tree(tree);
1667
1668 for (const auto& blockValue : blockValues) {
1669 memdb.update_element(index++, blockValue);
1670 }
1671
1672 ++blockNumber;
1673
1674 paths.push_back(memdb.get_sibling_path(0));
1675
1676 check_sibling_path(tree, 0, memdb.get_sibling_path(0));
1677 check_historic_sibling_path(tree, 0, paths[1], 1);
1678 check_block_height(tree, blockNumber);
1679 }
1680
1681 block_number_t blockToRemove = 1;
1682
1683 while (blockToRemove < blockNumber) {
1684 finalize_block(tree, blockToRemove + 1);
1685 // Remove the historic next block
1686 remove_historic_block(tree, blockToRemove);
1687
1688 check_sibling_path(tree, 0, paths[blockNumber]);
1689
1690 check_historic_sibling_path(tree, 0, paths[blockToRemove + 1], blockToRemove + 1);
1691 check_block_height(tree, blockNumber);
1692 blockToRemove++;
1693 }
1694}
1695
1696TEST_F(PersistedContentAddressedAppendOnlyTreeTest, can_retrieve_block_numbers_by_index)
1697{
1698 std::string name = random_string();
1699 constexpr uint32_t depth = 10;
1700 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
1701 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
1703 TreeType tree(std::move(store), pool);
1704
1705 const size_t block_size = 32;
1706
1707 for (size_t i = 0; i < 5; i++) {
1708 std::vector<fr> values = create_values(block_size);
1709 add_values(tree, values);
1710 commit_tree(tree);
1711 }
1712 std::vector<index_t> indices{ 12, 33, 63, 64, 65, 80, 96, 159, 160 };
1714
1715 // All but the last block number should be valid when looking at latest
1716 get_blocks_for_indices(tree, indices, blockNumbers);
1717 EXPECT_EQ(blockNumbers.size(), indices.size());
1718
1719 index_t maxIndex = 5 * block_size - 1;
1720 for (size_t i = 0; i < blockNumbers.size(); i++) {
1721 bool present = indices[i] <= maxIndex;
1722 if (present) {
1723 block_number_t expected = 1 + static_cast<block_number_t>(indices[i]) / block_size;
1724 EXPECT_EQ(blockNumbers[i].value(), expected);
1725 }
1726 EXPECT_EQ(blockNumbers[i].has_value(), present);
1727 }
1728
1729 // Now get blocks for indices from the perspective of block 2
1730 get_blocks_for_indices(tree, 2, indices, blockNumbers);
1731 EXPECT_EQ(blockNumbers.size(), indices.size());
1732
1733 maxIndex = 2 * block_size - 1;
1734 for (size_t i = 0; i < blockNumbers.size(); i++) {
1735 bool present = indices[i] <= maxIndex;
1736 if (present) {
1737 block_number_t expected = 1 + static_cast<block_number_t>(indices[i]) / block_size;
1738 EXPECT_EQ(blockNumbers[i].value(), expected);
1739 }
1740 EXPECT_EQ(blockNumbers[i].has_value(), present);
1741 }
1742
1743 unwind_block(tree, 5);
1744 unwind_block(tree, 4);
1745
1746 get_blocks_for_indices(tree, indices, blockNumbers);
1747 EXPECT_EQ(blockNumbers.size(), indices.size());
1748 maxIndex = 3 * block_size - 1;
1749 for (size_t i = 0; i < blockNumbers.size(); i++) {
1750 bool present = indices[i] <= maxIndex;
1751 if (present) {
1752 block_number_t expected = 1 + static_cast<block_number_t>(indices[i]) / block_size;
1753 EXPECT_EQ(blockNumbers[i].value(), expected);
1754 }
1755 EXPECT_EQ(blockNumbers[i].has_value(), present);
1756 }
1757
1758 // fork from block 1
1759 std::unique_ptr<Store> forkStore = std::make_unique<Store>(name, depth, 1, db);
1760 TreeType treeFork(std::move(forkStore), pool);
1761
1762 // Now, using the fork, get block indices but find it's limited to those of block 1
1763 get_blocks_for_indices(treeFork, indices, blockNumbers);
1764 EXPECT_EQ(blockNumbers.size(), indices.size());
1765
1766 maxIndex = block_size - 1;
1767 for (size_t i = 0; i < blockNumbers.size(); i++) {
1768 bool present = indices[i] <= maxIndex;
1769 if (present) {
1770 block_number_t expected = 1 + static_cast<block_number_t>(indices[i]) / block_size;
1771 EXPECT_EQ(blockNumbers[i].value(), expected);
1772 }
1773 EXPECT_EQ(blockNumbers[i].has_value(), present);
1774 }
1775
1776 // Now, using the fork, get block indics from the perspective of block 2, but find it's limited to those of block 1
1777 get_blocks_for_indices(treeFork, 2, indices, blockNumbers);
1778 EXPECT_EQ(blockNumbers.size(), indices.size());
1779
1780 maxIndex = block_size - 1;
1781 for (size_t i = 0; i < blockNumbers.size(); i++) {
1782 bool present = indices[i] <= maxIndex;
1783 if (present) {
1784 block_number_t expected = 1 + static_cast<block_number_t>(indices[i]) / block_size;
1785 EXPECT_EQ(blockNumbers[i].value(), expected);
1786 }
1787 EXPECT_EQ(blockNumbers[i].has_value(), present);
1788 }
1789}
1790
1792{
1793 std::string name = random_string();
1794 constexpr uint32_t depth = 10;
1795 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
1796 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
1798 TreeType tree(std::move(store), pool);
1800
1801 uint32_t blockSize = 16;
1802 uint32_t numBlocks = 16;
1803 uint32_t finalizedBlockDelay = 4;
1804 std::vector<fr> values = create_values(blockSize * numBlocks);
1805
1806 for (uint32_t i = 0; i < numBlocks; i++) {
1807 std::vector<fr> to_add;
1808
1809 for (size_t j = 0; j < blockSize; ++j) {
1810 size_t ind = i * blockSize + j;
1811 memdb.update_element(ind, values[ind]);
1812 to_add.push_back(values[ind]);
1813 }
1814 add_values(tree, to_add);
1815 commit_tree(tree);
1816
1817 block_number_t expectedFinalizedBlock = i < finalizedBlockDelay ? 0 : i - finalizedBlockDelay;
1818 check_finalized_block_height(tree, expectedFinalizedBlock);
1819
1820 if (i >= finalizedBlockDelay) {
1821
1822 block_number_t blockToFinalize = expectedFinalizedBlock + 1;
1823
1824 // attempting to finalize a block that doesn't exist should fail
1825 finalize_block(tree, blockToFinalize + numBlocks, false);
1826
1827 finalize_block(tree, blockToFinalize, true);
1828
1829 // finalising the currently finalized block should succeed
1830 finalize_block(tree, blockToFinalize, true);
1831 }
1832 }
1833}
1834
1836{
1837 std::string name = random_string();
1838 constexpr uint32_t depth = 10;
1839 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
1840 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
1842 TreeType tree(std::move(store), pool);
1844
1845 uint32_t blockSize = 16;
1846 uint32_t numBlocks = 16;
1847 std::vector<fr> values = create_values(blockSize * numBlocks);
1848
1849 for (uint32_t i = 0; i < numBlocks; i++) {
1850 std::vector<fr> to_add;
1851
1852 for (size_t j = 0; j < blockSize; ++j) {
1853 size_t ind = i * blockSize + j;
1854 memdb.update_element(ind, values[ind]);
1855 to_add.push_back(values[ind]);
1856 }
1857 add_values(tree, to_add);
1858 commit_tree(tree);
1859 }
1860
1861 check_block_height(tree, numBlocks);
1862
1863 block_number_t blockToFinalize = 8;
1864
1865 finalize_block(tree, blockToFinalize);
1866}
1867
1868TEST_F(PersistedContentAddressedAppendOnlyTreeTest, can_not_finalize_block_beyond_pending_chain)
1869{
1870 std::string name = random_string();
1871 constexpr uint32_t depth = 10;
1872 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
1873 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
1875 TreeType tree(std::move(store), pool);
1877
1878 uint32_t blockSize = 16;
1879 uint32_t numBlocks = 16;
1880 std::vector<fr> values = create_values(blockSize * numBlocks);
1881
1882 // finalising block 1 should fail
1883 finalize_block(tree, 1, false);
1884
1885 for (uint32_t i = 0; i < numBlocks; i++) {
1886 std::vector<fr> to_add;
1887
1888 for (size_t j = 0; j < blockSize; ++j) {
1889 size_t ind = i * blockSize + j;
1890 memdb.update_element(ind, values[ind]);
1891 to_add.push_back(values[ind]);
1892 }
1893 add_values(tree, to_add);
1894 commit_tree(tree);
1895 }
1896
1897 check_block_height(tree, numBlocks);
1898
1899 // should fail
1900 finalize_block(tree, numBlocks + 1, false);
1901
1902 // finalize the entire chain
1903 block_number_t blockToFinalize = numBlocks;
1904
1905 finalize_block(tree, blockToFinalize);
1906}
1907
1908TEST_F(PersistedContentAddressedAppendOnlyTreeTest, can_not_fork_from_unwound_blocks)
1909{
1910 std::string name = random_string();
1911 uint32_t depth = 20;
1912 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
1913 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
1915 TreeType tree(std::move(store), pool);
1916
1917 for (uint32_t i = 0; i < 5; i++) {
1918 std::vector<fr> values = create_values(1024);
1919 add_values(tree, values);
1920 commit_tree(tree);
1921 }
1922
1923 unwind_block(tree, 5);
1924 unwind_block(tree, 4);
1925
1926 EXPECT_THROW(Store(name, depth, 5, db), std::runtime_error);
1927 EXPECT_THROW(Store(name, depth, 4, db), std::runtime_error);
1928}
1929
1930TEST_F(PersistedContentAddressedAppendOnlyTreeTest, can_not_fork_from_expired_historical_blocks)
1931{
1932 std::string name = random_string();
1933 uint32_t depth = 20;
1934 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
1935 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
1937 TreeType tree(std::move(store), pool);
1938
1939 for (uint32_t i = 0; i < 5; i++) {
1940 std::vector<fr> values = create_values(1024);
1941 add_values(tree, values);
1942 commit_tree(tree);
1943 }
1944 finalize_block(tree, 3);
1945
1946 remove_historic_block(tree, 1);
1947 remove_historic_block(tree, 2);
1948
1949 EXPECT_THROW(Store(name, depth, 1, db), std::runtime_error);
1950 EXPECT_THROW(Store(name, depth, 2, db), std::runtime_error);
1951}
1952TEST_F(PersistedContentAddressedAppendOnlyTreeTest, can_fork_from_block_zero_when_not_latest)
1953{
1954 std::string name = random_string();
1955 uint32_t depth = 20;
1956 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
1957 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
1959 TreeType tree(std::move(store), pool);
1961
1962 uint32_t numBlocks = 5;
1963
1964 const fr initialRoot = memdb.root();
1965 const fr_sibling_path path = memdb.get_sibling_path(0);
1966
1967 for (uint32_t i = 0; i < numBlocks; i++) {
1968 std::vector<fr> values = create_values(1024);
1969 add_values(tree, values);
1970 commit_tree(tree);
1971 }
1972
1973 check_block_height(tree, numBlocks);
1974
1975 EXPECT_NO_THROW(Store(name, depth, 0, db));
1976
1977 std::unique_ptr<Store> store2 = std::make_unique<Store>(name, depth, 0, db);
1978 TreeType tree2(std::move(store2), pool);
1979
1980 check_root(tree2, initialRoot, false);
1981 check_sibling_path(tree2, 0, path, false, true);
1982}
1983
1985{
1986 std::string name = random_string();
1987 constexpr uint32_t depth = 10;
1988 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
1989 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
1991 TreeType tree(std::move(store), pool);
1993
1994 uint32_t blockSize = 16;
1995 uint32_t numBlocks = 16;
1996 std::vector<fr> values = create_values(blockSize * numBlocks);
1997
1998 for (uint32_t i = 0; i < numBlocks; i++) {
1999 std::vector<fr> to_add;
2000
2001 for (size_t j = 0; j < blockSize; ++j) {
2002 size_t ind = i * blockSize + j;
2003 memdb.update_element(ind, values[ind]);
2004 to_add.push_back(values[ind]);
2005 }
2006 add_values(tree, to_add);
2007 commit_tree(tree);
2008 }
2009
2010 check_block_height(tree, numBlocks);
2011
2012 block_number_t blockToFinalize = 8;
2013
2014 finalize_block(tree, blockToFinalize);
2015
2016 for (uint32_t i = numBlocks; i > blockToFinalize; i--) {
2017 unwind_block(tree, i);
2018 }
2019 unwind_block(tree, blockToFinalize, false);
2020}
2021
2022TEST_F(PersistedContentAddressedAppendOnlyTreeTest, can_not_historically_remove_finalized_block)
2023{
2024 std::string name = random_string();
2025 constexpr uint32_t depth = 10;
2026 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
2027 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
2029 TreeType tree(std::move(store), pool);
2031
2032 uint32_t blockSize = 16;
2033 uint32_t numBlocks = 16;
2034 std::vector<fr> values = create_values(blockSize * numBlocks);
2035
2036 for (uint32_t i = 0; i < numBlocks; i++) {
2037 std::vector<fr> to_add;
2038
2039 for (size_t j = 0; j < blockSize; ++j) {
2040 size_t ind = i * blockSize + j;
2041 memdb.update_element(ind, values[ind]);
2042 to_add.push_back(values[ind]);
2043 }
2044 add_values(tree, to_add);
2045 commit_tree(tree);
2046 }
2047
2048 check_block_height(tree, numBlocks);
2049
2050 block_number_t blockToFinalize = 8;
2051
2052 finalize_block(tree, blockToFinalize);
2053
2054 for (uint32_t i = 0; i < blockToFinalize - 1; i++) {
2055 remove_historic_block(tree, i + 1);
2056 }
2057 remove_historic_block(tree, blockToFinalize, false);
2058}
2059
2061{
2062 constexpr size_t depth = 10;
2063 uint32_t blockSize = 16;
2064 std::string name = random_string();
2066 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
2068
2069 {
2070 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
2071 TreeType tree(std::move(store), pool);
2072
2073 std::vector<fr> values = create_values(blockSize);
2074 add_values(tree, values);
2075
2076 commit_tree(tree);
2077 }
2078
2079 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
2080 TreeType tree(std::move(store), pool);
2081
2082 // We apply a number of updates and checkpoint the tree each time
2083
2084 uint32_t stackDepth = 20;
2085
2086 std::vector<fr_sibling_path> paths(stackDepth);
2087 uint32_t index = 0;
2088 for (; index < stackDepth - 1; index++) {
2089 std::vector<fr> values = create_values(blockSize);
2090 add_values(tree, values);
2091
2092 paths[index] = get_sibling_path(tree, 3);
2093
2094 try {
2095 checkpoint_tree(tree);
2096 } catch (std::exception& e) {
2097 std::cout << e.what() << std::endl;
2098 }
2099 }
2100
2101 // Now add one more depth, this will be un-checkpointed
2102 {
2103 std::vector<fr> values = create_values(blockSize);
2104 add_values(tree, values);
2105 paths[index] = get_sibling_path(tree, 3);
2106 }
2107
2108 index_t checkpointIndex = index;
2109
2110 // The tree is currently at the state of index 19
2111 EXPECT_EQ(get_sibling_path(tree, 3), paths[checkpointIndex]);
2112
2113 // We now alternate committing and reverting the checkpoints half way up the stack
2114
2115 for (; index > stackDepth / 2; index--) {
2116 if (index % 2 == 0) {
2117 revert_checkpoint_tree(tree, true);
2118 checkpointIndex = index - 1;
2119 } else {
2120 commit_checkpoint_tree(tree, true);
2121 }
2122
2123 EXPECT_EQ(get_sibling_path(tree, 3), paths[checkpointIndex]);
2124 }
2125
2126 // Now apply another set of updates and checkpoints back to the original stack depth
2127 for (; index < stackDepth - 1; index++) {
2128 std::vector<fr> values = create_values(blockSize);
2129 add_values(tree, values);
2130
2131 paths[index] = get_sibling_path(tree, 3);
2132
2133 try {
2134 checkpoint_tree(tree);
2135 } catch (std::exception& e) {
2136 std::cout << e.what() << std::endl;
2137 }
2138 }
2139
2140 // Now add one more depth, this will be un-checkpointed
2141 {
2142 std::vector<fr> values = create_values(blockSize);
2143 add_values(tree, values);
2144 paths[index] = get_sibling_path(tree, 3);
2145 }
2146
2147 // We now alternatively commit and revert all the way back to the start
2148 checkpointIndex = index;
2149
2150 // The tree is currently at the state of index 19
2151 EXPECT_EQ(get_sibling_path(tree, 3), paths[checkpointIndex]);
2152
2153 for (; index > 0; index--) {
2154 if (index % 2 == 0) {
2155 revert_checkpoint_tree(tree, true);
2156 checkpointIndex = index - 1;
2157 } else {
2158 commit_checkpoint_tree(tree, true);
2159 }
2160
2161 EXPECT_EQ(get_sibling_path(tree, 3), paths[checkpointIndex]);
2162 }
2163
2164 // Should not be able to commit or revert where there is no active checkpoint
2165 revert_checkpoint_tree(tree, false);
2166 commit_checkpoint_tree(tree, false);
2167}
2168
2170{
2171 constexpr size_t depth = 10;
2172 uint32_t blockSize = 16;
2173 std::string name = random_string();
2175 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
2177
2178 {
2179 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
2180 TreeType tree(std::move(store), pool);
2181
2182 std::vector<fr> values = create_values(blockSize);
2183 add_values(tree, values);
2184
2185 commit_tree(tree);
2186 }
2187
2188 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
2189 TreeType tree(std::move(store), pool);
2190
2191 // We apply a number of updates and checkpoint the tree each time
2192
2193 uint32_t stackDepth = 20;
2194
2195 std::vector<fr_sibling_path> paths(stackDepth);
2196 uint32_t index = 0;
2197 for (; index < stackDepth - 1; index++) {
2198 std::vector<fr> values = create_values(blockSize);
2199 add_values(tree, values);
2200
2201 paths[index] = get_sibling_path(tree, 3);
2202
2203 try {
2204 checkpoint_tree(tree);
2205 } catch (std::exception& e) {
2206 std::cout << e.what() << std::endl;
2207 }
2208 }
2209
2210 // Now we commit all the checkpoints
2212
2213 // Tree should still be at the final state
2214 check_sibling_path(tree, 3, paths[stackDepth - 2]);
2215
2216 // Should not be able to commit or revert where there is no active checkpoint
2217 revert_checkpoint_tree(tree, false);
2218 commit_checkpoint_tree(tree, false);
2219}
2220
2222{
2223 constexpr size_t depth = 10;
2224 uint32_t blockSize = 16;
2225 std::string name = random_string();
2227 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
2229
2230 {
2231 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
2232 TreeType tree(std::move(store), pool);
2233
2234 std::vector<fr> values = create_values(blockSize);
2235 add_values(tree, values);
2236
2237 commit_tree(tree);
2238 }
2239
2240 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
2241 TreeType tree(std::move(store), pool);
2242
2243 // We apply a number of updates and checkpoint the tree each time
2244
2245 uint32_t stackDepth = 20;
2246
2247 std::vector<fr_sibling_path> paths(stackDepth);
2248 uint32_t index = 0;
2249 for (; index < stackDepth - 1; index++) {
2250 std::vector<fr> values = create_values(blockSize);
2251 add_values(tree, values);
2252
2253 paths[index] = get_sibling_path(tree, 3);
2254
2255 try {
2256 checkpoint_tree(tree);
2257 } catch (std::exception& e) {
2258 std::cout << e.what() << std::endl;
2259 }
2260 }
2261
2262 // Now we revert all the checkpoints
2264
2265 // Tree should still be at the initial state
2266 check_sibling_path(tree, 3, paths[0]);
2267
2268 // Should not be able to commit or revert where there is no active checkpoint
2269 revert_checkpoint_tree(tree, false);
2270 commit_checkpoint_tree(tree, false);
2271}
Implements a simple append-only merkle tree All methods are asynchronous unless specified as otherwis...
std::function< void(TypedResponse< CommitResponse > &)> CommitCallback
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
fr_sibling_path get_sibling_path(index_t index)
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:36
void hash(State &state) noexcept
void checkpoint_tree(TreeType &tree)
ThreadPoolPtr make_thread_pool(uint64_t numThreads)
Definition fixtures.hpp:66
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:22
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:43
uint32_t block_number_t
Definition types.hpp:19
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:36
std::shared_ptr< ThreadPool > ThreadPoolPtr
Definition fixtures.hpp:64
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:16
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:185
field< Bn254FrParams > fr
Definition fr.hpp:174
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:33
static constexpr field zero()