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
108 index_t index,
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
147 index_t index,
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 TreeType& tree, const fr& leaf, index_t leaf_index, bool expected_success, bool includeUncommitted = true)
258{
259 Signal signal;
260 tree.get_leaf(leaf_index, includeUncommitted, [&](const TypedResponse<GetLeafResponse>& response) {
261 EXPECT_EQ(response.success, expected_success);
262 if (response.success) {
263 EXPECT_EQ(response.inner.leaf, leaf);
264 }
265 signal.signal_level();
266 });
267 signal.wait_for_level();
268}
269
271 const block_number_t& blockNumber,
272 const fr& leaf,
273 index_t leaf_index,
274 bool expected_success,
275 bool includeUncommitted = true)
276{
277 Signal signal;
278 tree.get_leaf(leaf_index, blockNumber, includeUncommitted, [&](const TypedResponse<GetLeafResponse>& response) {
279 EXPECT_EQ(response.success, expected_success);
280 if (response.success) {
281 EXPECT_EQ(response.inner.leaf, leaf);
282 }
283 signal.signal_level();
284 });
285 signal.wait_for_level();
286}
287
288void check_sibling_path(fr expected_root, fr node, index_t index, fr_sibling_path sibling_path)
289{
290 fr left;
291 fr right;
292 fr hash = node;
293 for (const auto& i : sibling_path) {
294 if (index % 2 == 0) {
295 left = hash;
296 right = i;
297 } else {
298 left = i;
299 right = hash;
300 }
301
303 index >>= 1;
304 }
305
306 EXPECT_EQ(hash, expected_root);
307}
308
310 const std::vector<index_t>& indices,
311 std::vector<std::optional<block_number_t>>& blockNumbers)
312{
313 Signal signal;
314 tree.find_block_numbers(indices, [&](const TypedResponse<BlockForIndexResponse>& response) {
315 blockNumbers = response.inner.blockNumbers;
316 signal.signal_level();
317 });
318 signal.wait_for_level();
319}
320
322 const block_number_t& blockNumber,
323 const std::vector<index_t>& indices,
324 std::vector<std::optional<block_number_t>>& blockNumbers)
325{
326 Signal signal;
327 tree.find_block_numbers(indices, blockNumber, [&](const TypedResponse<BlockForIndexResponse>& response) {
328 blockNumbers = response.inner.blockNumbers;
329 signal.signal_level();
330 });
331 signal.wait_for_level();
332}
333
335{
336 constexpr size_t depth = 10;
337 std::string name = random_string();
338 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
339 EXPECT_NO_THROW(Store store(name, depth, db));
340 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
341
343 TreeType tree(std::move(store), pool);
345
346 check_size(tree, 0);
347 check_root(tree, memdb.root());
348}
349
350TEST_F(PersistedContentAddressedAppendOnlyTreeTest, committing_with_no_changes_should_succeed)
351{
352 constexpr size_t depth = 10;
353 std::string name = random_string();
354 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
355 EXPECT_NO_THROW(Store store(name, depth, db));
356 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
357
359 TreeType tree(std::move(store), pool);
361
362 add_value(tree, VALUES[0]);
363 memdb.update_element(0, VALUES[0]);
364
365 commit_tree(tree, true);
366 check_root(tree, memdb.root());
367 check_size(tree, 1, false);
368 commit_tree(tree, true);
369 check_root(tree, memdb.root());
370 check_size(tree, 1, false);
371 // rollbacks should do nothing
372 rollback_tree(tree);
373 check_root(tree, memdb.root());
374 check_size(tree, 1, false);
375 add_value(tree, VALUES[1]);
376
377 // committed should be the same
378 check_root(tree, memdb.root(), false);
379 check_size(tree, 1, false);
380
381 // rollback
382 rollback_tree(tree);
383 // commit should do nothing
384 commit_tree(tree, true);
385 check_root(tree, memdb.root());
386 check_size(tree, 1, false);
387}
388
389TEST_F(PersistedContentAddressedAppendOnlyTreeTest, can_only_recreate_with_same_name_and_depth)
390{
391 constexpr size_t depth = 10;
392 std::string name = random_string();
393 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
394 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
395
396 EXPECT_ANY_THROW(Store store_wrong_name("Wrong name", depth, db));
397 EXPECT_ANY_THROW(Store store_wrong_depth(name, depth + 1, db));
398}
399
400TEST_F(PersistedContentAddressedAppendOnlyTreeTest, can_add_value_and_get_sibling_path)
401{
402 constexpr size_t depth = 3;
403 std::string name = random_string();
404 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
405 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
406
408 TreeType tree(std::move(store), pool);
410
411 check_size(tree, 0);
412 check_root(tree, memdb.root());
413
414 memdb.update_element(0, VALUES[0]);
415 add_value(tree, VALUES[0]);
416
417 check_size(tree, 1);
418 check_root(tree, memdb.root());
419 check_sibling_path(tree, 0, memdb.get_sibling_path(0));
420 check_sibling_path_by_value(tree, VALUES[0], memdb.get_sibling_path(0), 0);
421}
422
423TEST_F(PersistedContentAddressedAppendOnlyTreeTest, reports_an_error_if_tree_is_overfilled)
424{
425 constexpr size_t depth = 4;
426 std::string name = random_string();
427 std::string directory = random_temp_directory();
428 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
429 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
430
432 TreeType tree(std::move(store), pool);
433
434 std::vector<fr> values;
435 for (uint32_t i = 0; i < 16; i++) {
436 values.push_back(VALUES[i]);
437 }
438 add_values(tree, values);
439
440 std::stringstream ss;
441 ss << "Unable to append leaves to tree " << name << " new size: 17 max size: 16";
442
443 Signal signal;
444 auto add_completion = [&](const TypedResponse<AddDataResponse>& response) {
445 EXPECT_EQ(response.success, false);
446 EXPECT_EQ(response.message, ss.str());
447 signal.signal_level();
448 };
449 tree.add_value(VALUES[16], add_completion);
450 signal.wait_for_level();
451}
452
454{
455 // We use a deep tree with a small amount of storage (100 * 1024) bytes
456 constexpr size_t depth = 16;
457 std::string name = random_string();
458 std::string directory = random_temp_directory();
459 std::filesystem::create_directories(directory);
460
461 {
462 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, 100, _maxReaders);
463 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
464
466 TreeType tree(std::move(store), pool);
468
469 // check the committed data only, so we read from the db
470 check_size(tree, 0, false);
471 check_root(tree, memdb.root(), false);
472
473 fr empty_root = memdb.root();
474
475 // Add lots of values to the tree
476 uint32_t num_values_to_add = 16 * 1024;
477 std::vector<fr> values;
478 for (uint32_t i = 0; i < num_values_to_add; i++) {
479 values.emplace_back(random_engine.get_random_uint256());
480 memdb.update_element(i, values[i]);
481 }
482 add_values(tree, values);
483
484 // check the uncommitted data is accurate
485 check_size(tree, num_values_to_add, true);
486 check_root(tree, memdb.root(), true);
487
488 // trying to commit that should fail
489 Signal signal;
490 auto completion = [&](const TypedResponse<CommitResponse>& response) -> void {
491 EXPECT_EQ(response.success, false);
492 signal.signal_level();
493 };
494
495 tree.commit(completion);
496 signal.wait_for_level();
497
498 // At this stage, the tree is still in an uncommited state despite the error
499 // Reading both committed and uncommitted data shold be ok
500
501 // check the uncommitted data is accurate
502 check_size(tree, num_values_to_add, true);
503 check_root(tree, memdb.root(), true);
504
505 // Reading committed data should still work
506 check_size(tree, 0, false);
507 check_root(tree, empty_root, false);
508
509 // Now rollback the tree
510 rollback_tree(tree);
511
512 // committed and uncommitted data should be as an empty tree
513 check_size(tree, 0, true);
514 check_root(tree, empty_root, true);
515
516 // Reading committed data should still work
517 check_size(tree, 0, false);
518 check_root(tree, empty_root, false);
519 }
520
521 {
522 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, 500, _maxReaders);
523 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
524
526 TreeType tree(std::move(store), pool);
528
529 fr empty_root = memdb.root();
530
531 // committed and uncommitted data should be as an empty tree
532 check_size(tree, 0, true);
533 check_root(tree, empty_root, true);
534
535 // Reading committed data should still work
536 check_size(tree, 0, false);
537 check_root(tree, empty_root, false);
538
539 // Now add a single value and commit it
540 add_value(tree, VALUES[0]);
541
542 commit_tree(tree);
543
545 memdb2.update_element(0, VALUES[0]);
546
547 // committed and uncommitted data should be equal to the tree with 1 item
548 check_size(tree, 1, true);
549 check_root(tree, memdb2.root(), true);
550
551 // Reading committed data should still work
552 check_size(tree, 1, false);
553 check_root(tree, memdb2.root(), false);
554 }
555}
556
558{
559 constexpr size_t depth = 5;
560 std::string name = random_string();
562 {
563 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
564 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
565
567 TreeType tree(std::move(store), pool);
568
569 check_size(tree, 0);
570 check_root(tree, memdb.root());
571 check_sibling_path(tree, 0, memdb.get_sibling_path(0));
572
573 bb::fr initial_root = memdb.root();
574 fr_sibling_path initial_sibling_path = memdb.get_sibling_path(0);
575 memdb.update_element(0, VALUES[0]);
576 add_value(tree, VALUES[0]);
577
578 // check uncommitted state
579 check_size(tree, 1);
580 check_root(tree, memdb.root());
581 check_sibling_path(tree, 0, memdb.get_sibling_path(0));
582
583 // check committed state
584 check_size(tree, 0, false);
585 check_root(tree, initial_root, false);
586 check_sibling_path(tree, 0, initial_sibling_path, false);
587
588 // commit the changes
589 commit_tree(tree);
590
591 check_block_and_root_data(db, 1, memdb.root(), true);
592 // now committed and uncommitted should be the same
593
594 // check uncommitted state
595 check_size(tree, 1);
596 check_root(tree, memdb.root());
597 check_sibling_path(tree, 0, memdb.get_sibling_path(0));
598
599 // check committed state
600 check_size(tree, 1, false);
601 check_root(tree, memdb.root(), false);
602 check_sibling_path(tree, 0, memdb.get_sibling_path(0), false);
603 }
604
605 // Re-create the store and tree, it should be the same as how we left it
606 {
607 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
608 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
609
611 TreeType tree(std::move(store), pool);
612
613 // check uncommitted state
614 check_size(tree, 1);
615 check_root(tree, memdb.root());
616 check_sibling_path(tree, 0, memdb.get_sibling_path(0));
617
618 check_block_and_root_data(db, 1, memdb.root(), true);
619
620 // check committed state
621 check_size(tree, 1, false);
622 check_root(tree, memdb.root(), false);
623 check_sibling_path(tree, 0, memdb.get_sibling_path(0), false);
624 }
625}
626
628{
629 constexpr size_t depth = 10;
630 std::string name = random_string();
631 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
632 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
634 TreeType tree(std::move(store), pool);
635
636 check_size(tree, 0);
637
638 // Add a new non-zero leaf at index 0.
639 add_value(tree, 30);
640 check_size(tree, 1);
641
642 // Add second.
643 add_value(tree, 10);
644 check_size(tree, 2);
645
646 // Add third.
647 add_value(tree, 20);
648 check_size(tree, 3);
649
650 // Add forth but with same value.
651 add_value(tree, 40);
652 check_size(tree, 4);
653}
654
656{
657 constexpr size_t depth = 5;
658 std::string name = random_string();
659 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
660 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
662 TreeType tree(std::move(store), pool);
663
664 add_value(tree, 30);
665 add_value(tree, 10);
666 add_value(tree, 20);
667 add_value(tree, 40);
668
669 // check the committed state and that the uncommitted state is empty
670 check_find_leaf_index(tree, fr(10), 1, true, true);
671 check_find_leaf_index<fr, TreeType>(tree, { fr(10) }, { std::nullopt }, true, false);
672
673 check_find_leaf_index<fr, TreeType>(tree, { fr(15) }, { std::nullopt }, true, true);
674 check_find_leaf_index<fr, TreeType>(tree, { fr(15) }, { std::nullopt }, true, false);
675
676 check_find_leaf_index(tree, fr(40), 3, true, true);
677 check_find_leaf_index(tree, fr(30), 0, true, true);
678 check_find_leaf_index(tree, fr(20), 2, true, true);
679
680 check_find_leaf_index<fr, TreeType>(tree, { fr(40) }, { std::nullopt }, true, false);
681 check_find_leaf_index<fr, TreeType>(tree, { fr(30) }, { std::nullopt }, true, false);
682 check_find_leaf_index<fr, TreeType>(tree, { fr(20) }, { std::nullopt }, true, false);
683
684 commit_tree(tree);
685
686 std::vector<fr> values{ 15, 18, 26, 2 };
687 add_values(tree, values);
688
689 // check the now committed state
690 check_find_leaf_index(tree, fr(40), 3, true, false);
691 check_find_leaf_index(tree, fr(30), 0, true, false);
692 check_find_leaf_index(tree, fr(20), 2, true, false);
693
694 // check the new uncommitted state
695 check_find_leaf_index(tree, fr(18), 5, true, true);
696 check_find_leaf_index<fr, TreeType>(tree, { fr(18) }, { std::nullopt }, true, false);
697
698 commit_tree(tree);
699
700 values = { 16, 4, 19, 22 };
701 add_values(tree, values);
702
703 // verify the find index from api
704 check_find_leaf_index_from(tree, fr(18), 0, 5, true, true);
705 check_find_leaf_index_from(tree, fr(19), 6, 10, true, true);
706 check_find_leaf_index_from<fr, TreeType>(tree, { fr(19) }, 0, { std::nullopt }, true, false);
707
708 commit_tree(tree);
709
710 add_value(tree, 88);
711
712 add_value(tree, 32);
713
714 check_size(tree, 14);
715 check_size(tree, 12, false);
716
717 // look past the last instance of this leaf
718 check_find_leaf_index_from<fr, TreeType>(tree, { fr(18) }, 6, { std::nullopt }, true, true);
719
720 // look beyond the end of uncommitted
721 check_find_leaf_index_from<fr, TreeType>(tree, { fr(18) }, 15, { std::nullopt }, true, true);
722
723 // look beyond the end of committed and don't include uncomitted
724 check_find_leaf_index_from<fr, TreeType>(tree, { fr(88) }, 13, { std::nullopt }, true, false);
725}
726
728{
729 constexpr size_t depth = 10;
730 std::string name = random_string();
731 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
732 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
734 TreeType tree(std::move(store), pool);
736
737 for (size_t i = 0; i < NUM_VALUES; ++i) {
738 fr mock_root = memdb.update_element(i, VALUES[i]);
739 add_value(tree, VALUES[i]);
740 check_root(tree, mock_root);
741
742 check_sibling_path(tree, 0, memdb.get_sibling_path(0));
743 check_sibling_path(tree, i, memdb.get_sibling_path(i));
744
745 check_sibling_path_by_value(tree, VALUES[0], memdb.get_sibling_path(0), 0);
746 check_sibling_path_by_value(tree, VALUES[i], memdb.get_sibling_path(i), i);
747 }
748}
749
750TEST_F(PersistedContentAddressedAppendOnlyTreeTest, can_add_multiple_values_in_a_batch)
751{
752 constexpr size_t depth = 3;
753 std::string name = random_string();
754 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
755 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
757 TreeType tree(std::move(store), pool);
759
760 std::vector<fr> to_add;
761
762 for (size_t i = 0; i < 4; ++i) {
763 memdb.update_element(i, VALUES[i]);
764 to_add.push_back(VALUES[i]);
765 }
766 add_values(tree, to_add);
767 check_size(tree, 4);
768 check_root(tree, memdb.root());
769 check_sibling_path(tree, 0, memdb.get_sibling_path(0));
770 check_sibling_path(tree, 4 - 1, memdb.get_sibling_path(4 - 1));
771 check_sibling_path_by_value(tree, VALUES[0], memdb.get_sibling_path(0), 0);
772 check_sibling_path_by_value(tree, VALUES[4 - 1], memdb.get_sibling_path(4 - 1), 4 - 1);
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 std::vector<fr> to_add(32, fr::zero());
786 to_add[0] = VALUES[0];
787
788 for (size_t i = 0; i < 32; ++i) {
789 memdb.update_element(i, to_add[i]);
790 }
791 add_values(tree, to_add);
792 check_size(tree, 32);
793 check_root(tree, memdb.root());
794
795 commit_tree(tree, true);
796
797 check_size(tree, 32);
798 check_root(tree, memdb.root());
799}
800
801TEST_F(PersistedContentAddressedAppendOnlyTreeTest, can_not_retrieve_zero_leaf_indices)
802{
803 constexpr size_t depth = 8;
804 std::string name = random_string();
805 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
806 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
808 TreeType tree(std::move(store), pool);
810
811 std::vector<fr> to_add(32, fr::zero());
812 to_add[0] = VALUES[0];
813
814 for (size_t i = 0; i < 32; ++i) {
815 memdb.update_element(i, VALUES[i]);
816 }
817 add_values(tree, to_add);
818 commit_tree(tree);
819 fr leaf = fr::zero();
820 check_find_leaf_index<fr, TreeType>(tree, { leaf }, { std::nullopt }, true);
821 check_historic_find_leaf_index<fr, TreeType>(tree, { leaf }, 1, { std::nullopt }, true);
822 check_find_leaf_index_from<fr, TreeType>(tree, { leaf }, 0, { std::nullopt }, true);
823 check_historic_find_leaf_index_from<fr, TreeType>(tree, { leaf }, 1, 0, { std::nullopt }, true);
824}
825
827{
828 constexpr size_t depth = 10;
829 std::string name = random_string();
830 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
831 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
833 TreeType tree(std::move(store), pool);
835
836 auto check = [&](index_t expected_size, index_t expected_unfinalized_block_height) {
837 check_size(tree, expected_size);
838 check_block_height(tree, expected_unfinalized_block_height);
839 check_root(tree, memdb.root());
840 check_sibling_path(tree, 0, memdb.get_sibling_path(0));
841 check_sibling_path(tree, expected_size - 1, memdb.get_sibling_path(expected_size - 1));
842 };
843
844 constexpr uint32_t num_blocks = 10;
845 constexpr uint32_t batch_size = 4;
846 for (uint32_t i = 0; i < num_blocks; i++) {
847 std::vector<fr> to_add;
848
849 for (size_t j = 0; j < batch_size; ++j) {
850 size_t ind = i * batch_size + j;
851 memdb.update_element(ind, VALUES[ind]);
852 to_add.push_back(VALUES[ind]);
853 }
854 index_t expected_size = (i + 1) * batch_size;
855 add_values(tree, to_add);
856 check(expected_size, i);
857 commit_tree(tree);
858 check(expected_size, i + 1);
859 check_block_and_root_data(db, 1 + i, memdb.root(), true);
860 }
861}
862
864{
865 constexpr size_t depth = 10;
866 std::string name = random_string();
867 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
868 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
870 TreeType tree(std::move(store), pool);
872
873 auto check = [&](index_t expected_size, index_t expected_unfinalized_block_height) {
874 check_size(tree, expected_size);
875 check_block_height(tree, expected_unfinalized_block_height);
876 check_root(tree, memdb.root());
877 check_sibling_path(tree, 0, memdb.get_sibling_path(0));
878 check_sibling_path(tree, expected_size - 1, memdb.get_sibling_path(expected_size - 1));
879 };
880
881 std::vector<size_t> batchSize = { 8, 20, 64, 32 };
882 index_t expected_size = 0;
883
884 for (uint32_t i = 0; i < batchSize.size(); i++) {
885 std::vector<fr> to_add;
886
887 for (size_t j = 0; j < batchSize[i]; ++j) {
888 size_t ind = expected_size + j;
889 memdb.update_element(ind, VALUES[ind]);
890 to_add.push_back(VALUES[ind]);
891 }
892 expected_size += batchSize[i];
893 add_values(tree, to_add);
894 check(expected_size, i);
895 commit_tree(tree);
896 check(expected_size, i + 1);
897 check_block_and_root_data(db, 1 + i, memdb.root(), true);
898 }
899}
900
901TEST_F(PersistedContentAddressedAppendOnlyTreeTest, can_retrieve_historic_sibling_paths)
902{
903 constexpr size_t depth = 10;
904 std::string name = random_string();
905 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
906 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
908 TreeType tree(std::move(store), pool);
910
911 constexpr uint32_t num_blocks = 10;
912 constexpr uint32_t batch_size = 4;
913
914 std::vector<fr_sibling_path> historicPathsZeroIndex;
915 std::vector<fr_sibling_path> historicPathsMaxIndex;
916
917 auto check = [&](index_t expected_size, index_t expected_unfinalized_block_height) {
918 check_size(tree, expected_size);
919 check_block_height(tree, expected_unfinalized_block_height);
920 check_root(tree, memdb.root());
921 check_sibling_path(tree, 0, memdb.get_sibling_path(0));
922 check_sibling_path(tree, expected_size - 1, memdb.get_sibling_path(expected_size - 1));
923
924 for (uint32_t i = 0; i < historicPathsZeroIndex.size(); i++) {
925 check_historic_sibling_path(tree, 0, historicPathsZeroIndex[i], i + 1);
926 check_historic_sibling_path_by_value(tree, VALUES[0], historicPathsZeroIndex[i], 0, i + 1);
927 index_t maxSizeAtBlock = ((i + 1) * batch_size) - 1;
928 check_historic_sibling_path(tree, maxSizeAtBlock, historicPathsMaxIndex[i], i + 1);
930 tree, VALUES[maxSizeAtBlock], historicPathsMaxIndex[i], maxSizeAtBlock, i + 1);
931 }
932 };
933
934 for (uint32_t i = 0; i < num_blocks; i++) {
935 std::vector<fr> to_add;
936
937 for (size_t j = 0; j < batch_size; ++j) {
938 size_t ind = i * batch_size + j;
939 memdb.update_element(ind, VALUES[ind]);
940 to_add.push_back(VALUES[ind]);
941 }
942 index_t expected_size = (i + 1) * batch_size;
943 add_values(tree, to_add);
944 check(expected_size, i);
945 commit_tree(tree);
946 check(expected_size, i + 1);
947 historicPathsZeroIndex.push_back(memdb.get_sibling_path(0));
948 historicPathsMaxIndex.push_back(memdb.get_sibling_path(expected_size - 1));
949 }
950}
951
953{
954 constexpr size_t depth = 10;
955 std::string name = random_string();
956 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
957 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
959 TreeType tree(std::move(store), pool);
961
962 constexpr uint32_t num_blocks = 10;
963 constexpr uint32_t batch_size = 4;
964
965 for (uint32_t i = 0; i < num_blocks; i++) {
966 std::vector<fr> to_add;
967
968 for (size_t j = 0; j < batch_size; ++j) {
969 size_t ind = i * batch_size + j;
970 memdb.update_element(ind, VALUES[ind]);
971 to_add.push_back(VALUES[ind]);
972 }
973 add_values(tree, to_add);
974 commit_tree(tree);
975 }
976
977 for (uint32_t i = 0; i < num_blocks; i++) {
978 for (uint32_t j = 0; j < num_blocks; j++) {
979 index_t indexToQuery = j * batch_size;
980 fr expectedLeaf = j <= i ? VALUES[indexToQuery] : fr::zero();
981 check_historic_leaf(tree, i + 1, expectedLeaf, indexToQuery, j <= i, false);
982 }
983 }
984}
985
987{
988 constexpr size_t depth = 5;
989 std::string name = random_string();
990 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
991 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
993 TreeType tree(std::move(store), pool);
994
995 std::vector<fr> values{ 30, 10, 20, 40 };
996 add_values(tree, values);
997
998 commit_tree(tree);
999
1000 values = { 15, 18, 26, 2 };
1001 add_values(tree, values);
1002
1003 commit_tree(tree);
1004
1005 values = { 16, 4, 19, 22 };
1006 add_values(tree, values);
1007
1008 // should not be present at block 1
1009 check_historic_find_leaf_index<fr, TreeType>(tree, { fr(26) }, 1, { std::nullopt }, true);
1010 // should be present at block 2
1011 check_historic_find_leaf_index(tree, fr(26), 2, 6, true);
1012
1013 // at block 1 leaf 18 should not be found if only considering committed
1014 check_historic_find_leaf_index_from<fr, TreeType>(tree, { fr(18) }, 1, 2, { std::nullopt }, true, false);
1015 // at block 2 it should be
1016 check_historic_find_leaf_index_from(tree, fr(18), 2, 2, 5, true);
1017 // at block 2, from index 6, 19 should not be found if looking only at committed
1018 check_historic_find_leaf_index_from<fr, TreeType>(tree, { fr(19) }, 2, 6, { std::nullopt }, true, false);
1019 // at block 2, from index 6, 19 should be found if looking at uncommitted too
1020 check_historic_find_leaf_index_from(tree, fr(19), 2, 6, 10, true);
1021
1022 commit_tree(tree);
1023
1024 // at block 3, from index 6, should now be found in committed only
1025 check_historic_find_leaf_index_from(tree, fr(19), 3, 6, 10, true, false);
1026}
1027
1029{
1030 constexpr size_t depth = 3;
1031 std::string name = random_string();
1032 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
1033 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
1035 TreeType tree(std::move(store), pool);
1037
1038 check_size(tree, 0);
1039 check_root(tree, memdb.root());
1040
1041 for (size_t i = 0; i < 8; i++) {
1042 memdb.update_element(i, VALUES[i]);
1043 add_value(tree, VALUES[i]);
1044 }
1045
1046 check_root(tree, memdb.root());
1047 check_sibling_path(tree, 0, memdb.get_sibling_path(0));
1048 check_sibling_path(tree, 7, memdb.get_sibling_path(7));
1049}
1050
1052{
1053 constexpr size_t depth = 10;
1055 fr_sibling_path initial_path = memdb.get_sibling_path(0);
1056 memdb.update_element(0, VALUES[0]);
1057 fr_sibling_path final_sibling_path = memdb.get_sibling_path(0);
1058
1059 uint32_t num_reads = 16 * 1024;
1060 std::vector<fr_sibling_path> paths(num_reads);
1061
1062 {
1063 std::string name = random_string();
1064 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
1065 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
1067 TreeType tree(std::move(store), pool);
1068
1069 check_size(tree, 0);
1070
1071 Signal signal(1 + num_reads);
1072
1073 auto add_completion = [&](const TypedResponse<AddDataResponse>&) {
1074 auto commit_completion = [&](const TypedResponse<CommitResponse>&) { signal.signal_decrement(); };
1075 tree.commit(commit_completion);
1076 };
1077 tree.add_value(VALUES[0], add_completion);
1078
1079 for (size_t i = 0; i < num_reads; i++) {
1080 auto completion = [&, i](const TypedResponse<GetSiblingPathResponse>& response) {
1081 paths[i] = response.inner.path;
1082 signal.signal_decrement();
1083 };
1084 tree.get_sibling_path(0, completion, false);
1085 }
1086 signal.wait_for_level(0);
1087 }
1088
1089 for (auto& path : paths) {
1090 EXPECT_TRUE(path == initial_path || path == final_sibling_path);
1091 }
1092}
1093
1095{
1096 constexpr size_t depth = 3;
1097 std::string name = random_string();
1098 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
1099 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
1101 TreeType tree(std::move(store), pool);
1102
1103 add_values(tree, { 30, 10, 20, 40 });
1104
1105 check_leaf(tree, 30, 0, true);
1106 check_leaf(tree, 10, 1, true);
1107 check_leaf(tree, 20, 2, true);
1108 check_leaf(tree, 40, 3, true);
1109
1110 check_leaf(tree, 0, 4, false);
1111}
1112
1114{
1115 constexpr size_t depth = 4;
1116 std::string name = random_string();
1117 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
1118 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
1120 TreeType tree(std::move(store), pool);
1122
1123 add_values(tree, { 30, 10, 20, 40 });
1124 memdb.update_element(0, 30);
1125 memdb.update_element(1, 10);
1126 memdb.update_element(2, 20);
1127 memdb.update_element(3, 40);
1128
1129 {
1130 Signal signal(1);
1131 tree.get_subtree_sibling_path(
1132 0,
1133 [&](auto& resp) {
1134 fr_sibling_path expected_sibling_path = memdb.get_sibling_path(4);
1135 EXPECT_EQ(resp.inner.path, expected_sibling_path);
1136 signal.signal_level();
1137 },
1138 true);
1139 signal.wait_for_level();
1140 }
1141
1142 {
1143 Signal signal(1);
1144 tree.get_subtree_sibling_path(
1145 2,
1146 [&](auto& resp) {
1147 fr_sibling_path expected_sibling_path = { memdb.get_node(2, 0), memdb.get_node(1, 1) };
1148 EXPECT_EQ(resp.inner.path, expected_sibling_path);
1149 signal.signal_level();
1150 },
1151 true);
1152 signal.wait_for_level();
1153 }
1154}
1155
1156TEST_F(PersistedContentAddressedAppendOnlyTreeTest, can_create_images_at_historic_blocks)
1157{
1158 constexpr size_t depth = 5;
1159 std::string name = random_string();
1161 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
1163 size_t index = 0;
1164
1165 std::unique_ptr<Store> store1 = std::make_unique<Store>(name, depth, db);
1166 TreeType tree1(std::move(store1), pool);
1167
1168 std::vector<fr> values{ 30, 10, 20, 40 };
1169 add_values(tree1, values);
1170 for (auto v : values) {
1171 memdb.update_element(index++, v);
1172 }
1173
1174 commit_tree(tree1);
1175
1176 check_block_and_root_data(db, 1, memdb.root(), true);
1177
1178 fr_sibling_path block1SiblingPathIndex3 = memdb.get_sibling_path(3);
1179
1180 values = { 15, 18, 26, 2 };
1181 add_values(tree1, values);
1182 for (auto v : values) {
1183 memdb.update_element(index++, v);
1184 }
1185
1186 commit_tree(tree1);
1187
1188 check_block_and_root_data(db, 2, memdb.root(), true);
1189
1190 fr block2Root = memdb.root();
1191
1192 fr_sibling_path block2SiblingPathIndex7 = memdb.get_sibling_path(7);
1193 fr_sibling_path block2SiblingPathIndex3 = memdb.get_sibling_path(3);
1194
1195 values = { 16, 4, 18, 22 };
1196 add_values(tree1, values);
1197 for (auto v : values) {
1198 memdb.update_element(index++, v);
1199 }
1200
1201 commit_tree(tree1);
1202
1203 check_block_and_root_data(db, 3, memdb.root(), true);
1204
1205 fr_sibling_path block3SiblingPathIndex11 = memdb.get_sibling_path(11);
1206 fr_sibling_path block3SiblingPathIndex7 = memdb.get_sibling_path(7);
1207 fr_sibling_path block3SiblingPathIndex3 = memdb.get_sibling_path(3);
1208
1209 // Create a new image at block 2
1210 std::unique_ptr<Store> storeAtBlock2 = std::make_unique<Store>(name, depth, 2, db);
1211 TreeType treeAtBlock2(std::move(storeAtBlock2), pool);
1212
1213 check_root(treeAtBlock2, block2Root);
1214 check_sibling_path(treeAtBlock2, 3, block2SiblingPathIndex3, false, true);
1215 check_leaf(treeAtBlock2, 20, 2, true);
1216 check_find_leaf_index(treeAtBlock2, fr(10), 1, true);
1217 check_find_leaf_index_from(treeAtBlock2, fr(15), 1, 4, true);
1218
1219 // should not exist in our image
1220 check_leaf(treeAtBlock2, 4, 9, false);
1221 check_find_leaf_index<fr, TreeType>(treeAtBlock2, { fr(4) }, { std::nullopt }, true);
1222
1223 // now add the same values to our image
1224 add_values(treeAtBlock2, values);
1225
1226 // the state of our image should match the original tree
1227 check_sibling_path(tree1, 3, block3SiblingPathIndex3, false, true);
1228 check_sibling_path(tree1, 7, block3SiblingPathIndex7, false, true);
1229
1230 // needs to use uncommitted for this check
1231 check_sibling_path(treeAtBlock2, 3, block3SiblingPathIndex3, true, true);
1232 check_sibling_path(treeAtBlock2, 7, block3SiblingPathIndex7, true, true);
1233
1234 // now check historic data
1235 check_historic_sibling_path(treeAtBlock2, 3, block1SiblingPathIndex3, 1);
1236 check_historic_find_leaf_index(treeAtBlock2, fr(10), 2, 1, true);
1237 check_historic_find_leaf_index(treeAtBlock2, fr(16), 2, 8, true, true);
1238 check_historic_find_leaf_index<fr, TreeType>(treeAtBlock2, { fr(16) }, 2, { std::nullopt }, true, false);
1239
1240 check_historic_find_leaf_index_from<fr, TreeType>(treeAtBlock2, { fr(18) }, 1, 3, { std::nullopt }, true, false);
1241 check_historic_find_leaf_index_from(treeAtBlock2, fr(20), 1, 0, 2, true, false);
1242
1243 check_block_height(treeAtBlock2, 2);
1244
1245 // It should be impossible to commit using the image
1246 commit_tree(treeAtBlock2, false);
1247}
1248
1250{
1251 constexpr size_t depth = 10;
1252 std::string name = random_string();
1253 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
1254 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
1256 TreeType tree(std::move(store), pool);
1258
1259 constexpr uint32_t numBlocks = 50;
1260 constexpr uint32_t batchSize = 16;
1261 constexpr uint32_t windowSize = 8;
1262
1263 std::vector<fr_sibling_path> historicPathsZeroIndex;
1264 std::vector<fr_sibling_path> historicPathsMaxIndex;
1265 std::vector<fr> roots;
1266
1267 auto check = [&](index_t expectedSize, index_t expectedBlockHeight) {
1268 check_size(tree, expectedSize);
1269 check_block_height(tree, expectedBlockHeight);
1270 check_root(tree, memdb.root());
1271 check_sibling_path(tree, 0, memdb.get_sibling_path(0));
1272 check_sibling_path(tree, expectedSize - 1, memdb.get_sibling_path(expectedSize - 1));
1273
1274 for (uint32_t i = 0; i < historicPathsZeroIndex.size(); i++) {
1275 // retrieving historic data should fail if the block is outside of the window
1276 const block_number_t blockNumber = i + 1;
1277 const bool expectedSuccess =
1278 expectedBlockHeight <= windowSize || blockNumber > (expectedBlockHeight - windowSize);
1279 check_historic_sibling_path(tree, 0, historicPathsZeroIndex[i], blockNumber, expectedSuccess);
1280 index_t maxSizeAtBlock = ((i + 1) * batchSize) - 1;
1281 check_historic_sibling_path(tree, maxSizeAtBlock, historicPathsMaxIndex[i], blockNumber, expectedSuccess);
1282
1283 const index_t leafIndex = 6;
1284 check_historic_leaf(tree, blockNumber, VALUES[leafIndex], leafIndex, expectedSuccess);
1285 check_historic_find_leaf_index(tree, VALUES[leafIndex], blockNumber, leafIndex, expectedSuccess);
1286 check_historic_find_leaf_index_from(tree, VALUES[leafIndex], blockNumber, 0, leafIndex, expectedSuccess);
1287 }
1288 };
1289
1290 for (uint32_t i = 0; i < numBlocks; i++) {
1291 std::vector<fr> to_add;
1292
1293 for (size_t j = 0; j < batchSize; ++j) {
1294 size_t ind = i * batchSize + j;
1295 memdb.update_element(ind, VALUES[ind]);
1296 to_add.push_back(VALUES[ind]);
1297 }
1298 index_t expected_size = (i + 1) * batchSize;
1299 add_values(tree, to_add);
1300 check(expected_size, i);
1301 commit_tree(tree);
1302
1303 // immediately finalize the block
1304 finalize_block(tree, i + 1);
1305
1306 historicPathsZeroIndex.push_back(memdb.get_sibling_path(0));
1307 historicPathsMaxIndex.push_back(memdb.get_sibling_path(expected_size - 1));
1308 roots.push_back(memdb.root());
1309
1310 // Now remove the oldest block if outside of the window
1311 if (i >= windowSize) {
1312 const block_number_t oldestBlock = (i + 1) - windowSize;
1313 // trying to remove a block that is not the most historic should fail
1314 remove_historic_block(tree, oldestBlock + 1, false);
1315
1316 if (oldestBlock > 1) {
1317 // trying to remove a block that is older than the most historic should succeed
1318 remove_historic_block(tree, oldestBlock - 1, true);
1319 }
1320
1321 fr rootToRemove = roots[oldestBlock - 1];
1322 check_block_and_root_data(db, oldestBlock, rootToRemove, true);
1323
1324 // removing the most historic should succeed
1325 remove_historic_block(tree, oldestBlock, true);
1326
1327 // the block data should have been removed
1328 check_block_and_root_data(db, oldestBlock, rootToRemove, false);
1329 }
1330 check(expected_size, i + 1);
1331 }
1332
1333 // Attempting to remove block 0 should fail as there isn't one
1334 remove_historic_block(tree, 0, false);
1335}
1336
1337void test_unwind(std::string directory,
1338 std::string name,
1339 uint64_t mapSize,
1340 uint64_t maxReaders,
1341 uint32_t depth,
1342 uint32_t blockSize,
1343 uint32_t numBlocks,
1344 uint32_t numBlocksToUnwind,
1345 std::vector<fr> values)
1346{
1347 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(directory, name, mapSize, maxReaders);
1348 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
1350 TreeType tree(std::move(store), pool);
1352
1353 uint32_t batchSize = blockSize;
1354
1355 std::vector<fr_sibling_path> historicPathsZeroIndex;
1356 std::vector<fr_sibling_path> historicPathsMaxIndex;
1357 std::vector<fr> roots;
1358
1359 fr initialRoot = memdb.root();
1360 fr_sibling_path initialPath = memdb.get_sibling_path(0);
1361
1362 for (uint32_t i = 0; i < numBlocks; i++) {
1363 std::vector<fr> to_add;
1364
1365 for (size_t j = 0; j < batchSize; ++j) {
1366 size_t ind = i * batchSize + j;
1367 memdb.update_element(ind, values[ind]);
1368 to_add.push_back(values[ind]);
1369 }
1370 index_t expected_size = (i + 1) * batchSize;
1371 add_values(tree, to_add);
1372
1373 // attempting an unwind of the block being built should fail
1374 unwind_block(tree, i + 1, false);
1375
1376 if (i > 0) {
1377 // attemnpting an unwind of the most recent committed block should fail as we have uncommitted changes
1378 unwind_block(tree, i, false);
1379 }
1380
1381 commit_tree(tree);
1382
1383 historicPathsZeroIndex.push_back(memdb.get_sibling_path(0));
1384 historicPathsMaxIndex.push_back(memdb.get_sibling_path(expected_size - 1));
1385 roots.push_back(memdb.root());
1386 check_root(tree, memdb.root());
1387 check_sibling_path(tree, 0, memdb.get_sibling_path(0));
1388 check_sibling_path(tree, expected_size - 1, memdb.get_sibling_path(expected_size - 1));
1389 check_size(tree, expected_size);
1390 check_block_and_size_data(db, i + 1, expected_size, true);
1391 check_block_and_root_data(db, i + 1, memdb.root(), true);
1392 }
1393
1394 const uint32_t blocksToRemove = numBlocksToUnwind;
1395 for (uint32_t i = 0; i < blocksToRemove; i++) {
1396 const block_number_t blockNumber = numBlocks - i;
1397
1398 check_block_and_root_data(db, blockNumber, roots[blockNumber - 1], true);
1399 // attempting to unwind a block that is ahead of the pending chain should just succeed
1400 unwind_block(tree, blockNumber + 1, true);
1401
1402 // attempting to unwind a block that is behind the pending chain should fail
1403 if (blockNumber > 1) {
1404 unwind_block(tree, blockNumber - 1, false);
1405 }
1406
1407 unwind_block(tree, blockNumber);
1408
1409 // the root should now only exist if there are other blocks with same root
1410 const auto last = roots.begin() + long(blockNumber - 1);
1411 const auto it =
1412 std::find_if(roots.begin(), last, [=](const fr& r) -> bool { return r == roots[blockNumber - 1]; });
1413 check_block_and_root_data(db, blockNumber, roots[blockNumber - 1], false, it != last);
1414
1415 const index_t previousValidBlock = blockNumber - 1;
1416 index_t deletedBlockStartIndex = previousValidBlock * batchSize;
1417
1418 check_block_height(tree, previousValidBlock);
1419 check_size(tree, deletedBlockStartIndex);
1420 check_root(tree, previousValidBlock == 0 ? initialRoot : roots[previousValidBlock - 1]);
1421
1422 // The zero index sibling path should be as it was at the previous block
1423 check_sibling_path(tree,
1424 0,
1425 previousValidBlock == 0 ? initialPath : historicPathsZeroIndex[previousValidBlock - 1],
1426 false,
1427 true);
1428
1429 // Trying to find leaves appended in the block that was removed should fail
1430 check_leaf(tree, values[1 + deletedBlockStartIndex], 1 + deletedBlockStartIndex, false);
1431 check_find_leaf_index<fr, TreeType>(tree, { values[1 + deletedBlockStartIndex] }, { std::nullopt }, true);
1432
1433 for (block_number_t j = 0; j < numBlocks; j++) {
1434 block_number_t historicBlockNumber = j + 1;
1435 bool expectedSuccess = historicBlockNumber <= previousValidBlock;
1436 check_historic_sibling_path(tree, 0, historicPathsZeroIndex[j], historicBlockNumber, expectedSuccess);
1437 index_t maxSizeAtBlock = ((j + 1) * batchSize) - 1;
1439 tree, maxSizeAtBlock, historicPathsMaxIndex[j], historicBlockNumber, expectedSuccess);
1440
1441 const index_t leafIndex = 1;
1442 check_historic_leaf(tree, historicBlockNumber, values[leafIndex], leafIndex, expectedSuccess);
1443
1444 std::vector<std::optional<index_t>> expected_results;
1445 if (expectedSuccess) {
1446 if (values[leafIndex] != fr::zero()) {
1447 expected_results.emplace_back(std::make_optional(leafIndex));
1448 } else {
1449 expected_results.emplace_back(std::nullopt);
1450 }
1451 }
1452
1453 // find historic leaves, provided they are not zero leaves
1454 check_historic_find_leaf_index<fr, TreeType>(
1455 tree, { values[leafIndex] }, historicBlockNumber, expected_results, expectedSuccess);
1456 check_historic_find_leaf_index_from<fr, TreeType>(
1457 tree, { values[leafIndex] }, historicBlockNumber, 0, expected_results, expectedSuccess);
1458 }
1459 }
1460}
1461
1463{
1464 std::vector<fr> first = create_values(1024);
1465 test_unwind(_directory, "DB", _mapSize, _maxReaders, 10, 16, 16, 8, first);
1466}
1467
1469{
1470 std::vector<fr> first = create_values(1024);
1471 test_unwind(_directory, "DB", _mapSize, _maxReaders, 10, 16, 16, 16, first);
1472 std::vector<fr> second = create_values(1024);
1473 test_unwind(_directory, "DB", _mapSize, _maxReaders, 10, 16, 16, 16, second);
1474}
1475
1476TEST_F(PersistedContentAddressedAppendOnlyTreeTest, can_unwind_initial_blocks_that_are_full_of_zeros)
1477{
1478 const size_t block_size = 16;
1479 const uint32_t num_blocks = 16;
1480 // First we add 16 blocks worth pf zero leaves and unwind them all
1481 std::vector<fr> first(1024, fr::zero());
1482 test_unwind(_directory, "DB", _mapSize, _maxReaders, 10, block_size, num_blocks, num_blocks, first);
1483 // now we add 1 block of zero leaves and the other blocks non-zero leaves and unwind them all
1484 std::vector<fr> second = create_values(1024);
1485 // set the first 16 values to be zeros
1486 for (size_t i = 0; i < block_size; i++) {
1487 second[i] = fr::zero();
1488 }
1489 test_unwind(_directory, "DB", _mapSize, _maxReaders, 10, block_size, num_blocks, num_blocks, second);
1490
1491 // now we add 2 block of zero leaves in the middle and the other blocks non-zero leaves and unwind them all
1492 std::vector<fr> third = create_values(1024);
1493 size_t offset = block_size * 2;
1494 for (size_t i = 0; i < block_size * 2; i++) {
1495 third[i + offset] = fr::zero();
1496 }
1497 test_unwind(_directory, "DB", _mapSize, _maxReaders, 10, block_size, num_blocks, num_blocks, third);
1498
1499 // Now we add a number of regular blocks and unwind
1500 std::vector<fr> fourth = create_values(1024);
1501 test_unwind(_directory, "DB", _mapSize, _maxReaders, 10, block_size, num_blocks, num_blocks, fourth);
1502}
1503
1504TEST_F(PersistedContentAddressedAppendOnlyTreeTest, can_sync_and_unwind_large_blocks)
1505{
1506
1507 constexpr uint32_t numBlocks = 4;
1508 constexpr uint32_t numBlocksToUnwind = 2;
1509 std::vector<uint32_t> blockSizes = { 2, 4, 8, 16, 32 };
1510 for (const uint32_t& size : blockSizes) {
1511 uint32_t actualSize = size * 1024;
1512 std::vector<fr> values = create_values(actualSize * numBlocks);
1513 std::stringstream ss;
1514 ss << "DB " << actualSize;
1515 test_unwind(_directory, ss.str(), _mapSize, _maxReaders, 20, actualSize, numBlocks, numBlocksToUnwind, values);
1516 }
1517}
1518
1519TEST_F(PersistedContentAddressedAppendOnlyTreeTest, can_commit_and_unwind_empty_blocks)
1520{
1521 std::string name = random_string();
1522 constexpr uint32_t depth = 10;
1523 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
1524 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
1526 TreeType tree(std::move(store), pool);
1528
1529 // The first path stored is the genesis state. This effectively makes everything 1 based.
1531 block_number_t blockNumber = 0;
1532 uint32_t batchSize = 64;
1533
1535 // commit an empty block
1536 values.emplace_back();
1537 // and another one
1538 values.emplace_back();
1539 // then a non-empty block
1540 values.push_back(create_values(batchSize));
1541 // then 2 more empty blocks
1542 values.emplace_back();
1543 values.emplace_back();
1544 // then a non-empty block
1545 values.push_back(create_values(batchSize));
1546
1547 index_t index = 0;
1548
1549 for (auto& blockValues : values) {
1550 add_values(tree, blockValues);
1551 commit_tree(tree);
1552
1553 for (const auto& blockValue : blockValues) {
1554 memdb.update_element(index++, blockValue);
1555 }
1556
1557 ++blockNumber;
1558
1559 paths.push_back(memdb.get_sibling_path(0));
1560
1561 check_sibling_path(tree, 0, memdb.get_sibling_path(0));
1562 check_historic_sibling_path(tree, 0, paths[1], 1);
1563 check_block_height(tree, blockNumber);
1564 }
1565
1566 while (blockNumber > 0) {
1567 // Unwind the next block
1568 unwind_block(tree, blockNumber);
1569
1570 --blockNumber;
1571
1572 check_sibling_path(tree, 0, paths[blockNumber]);
1573
1574 if (blockNumber > 0) {
1575 check_historic_sibling_path(tree, 0, paths[1], 1);
1576 check_block_height(tree, blockNumber);
1577 }
1578 }
1579}
1580
1581TEST_F(PersistedContentAddressedAppendOnlyTreeTest, can_commit_and_remove_historic_empty_blocks)
1582{
1583 std::string name = random_string();
1584 constexpr uint32_t depth = 10;
1585 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
1586 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
1588 TreeType tree(std::move(store), pool);
1590
1591 // The first path stored is the genesis state. This effectively makes everything 1 based.
1593 index_t blockNumber = 0;
1594 uint32_t batchSize = 64;
1595
1597 // commit an empty block
1598 values.emplace_back();
1599 // and another one
1600 values.emplace_back();
1601 // then a non-empty block
1602 values.push_back(create_values(batchSize));
1603 // then 2 more empty blocks
1604 values.emplace_back();
1605 values.emplace_back();
1606 // then a non-empty block
1607 values.push_back(create_values(batchSize));
1608
1609 index_t index = 0;
1610
1611 for (auto& blockValues : values) {
1612 add_values(tree, blockValues);
1613 commit_tree(tree);
1614
1615 for (const auto& blockValue : blockValues) {
1616 memdb.update_element(index++, blockValue);
1617 }
1618
1619 ++blockNumber;
1620
1621 paths.push_back(memdb.get_sibling_path(0));
1622
1623 check_sibling_path(tree, 0, memdb.get_sibling_path(0));
1624 check_historic_sibling_path(tree, 0, paths[1], 1);
1625 check_block_height(tree, blockNumber);
1626 }
1627
1628 block_number_t blockToRemove = 1;
1629
1630 while (blockToRemove < blockNumber) {
1631 finalize_block(tree, blockToRemove + 1);
1632 // Remove the historic next block
1633 remove_historic_block(tree, blockToRemove);
1634
1635 check_sibling_path(tree, 0, paths[blockNumber]);
1636
1637 check_historic_sibling_path(tree, 0, paths[blockToRemove + 1], blockToRemove + 1);
1638 check_block_height(tree, blockNumber);
1639 blockToRemove++;
1640 }
1641}
1642
1643TEST_F(PersistedContentAddressedAppendOnlyTreeTest, can_retrieve_block_numbers_by_index)
1644{
1645 std::string name = random_string();
1646 constexpr uint32_t depth = 10;
1647 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
1648 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
1650 TreeType tree(std::move(store), pool);
1651
1652 const size_t block_size = 32;
1653
1654 for (size_t i = 0; i < 5; i++) {
1655 std::vector<fr> values = create_values(block_size);
1656 add_values(tree, values);
1657 commit_tree(tree);
1658 }
1659 std::vector<index_t> indices{ 12, 33, 63, 64, 65, 80, 96, 159, 160 };
1661
1662 // All but the last block number should be valid when looking at latest
1663 get_blocks_for_indices(tree, indices, blockNumbers);
1664 EXPECT_EQ(blockNumbers.size(), indices.size());
1665
1666 index_t maxIndex = 5 * block_size - 1;
1667 for (size_t i = 0; i < blockNumbers.size(); i++) {
1668 bool present = indices[i] <= maxIndex;
1669 if (present) {
1670 block_number_t expected = 1 + static_cast<block_number_t>(indices[i]) / block_size;
1671 EXPECT_EQ(blockNumbers[i].value(), expected);
1672 }
1673 EXPECT_EQ(blockNumbers[i].has_value(), present);
1674 }
1675
1676 // Now get blocks for indices from the perspective of block 2
1677 get_blocks_for_indices(tree, 2, indices, blockNumbers);
1678 EXPECT_EQ(blockNumbers.size(), indices.size());
1679
1680 maxIndex = 2 * block_size - 1;
1681 for (size_t i = 0; i < blockNumbers.size(); i++) {
1682 bool present = indices[i] <= maxIndex;
1683 if (present) {
1684 block_number_t expected = 1 + static_cast<block_number_t>(indices[i]) / block_size;
1685 EXPECT_EQ(blockNumbers[i].value(), expected);
1686 }
1687 EXPECT_EQ(blockNumbers[i].has_value(), present);
1688 }
1689
1690 unwind_block(tree, 5);
1691 unwind_block(tree, 4);
1692
1693 get_blocks_for_indices(tree, indices, blockNumbers);
1694 EXPECT_EQ(blockNumbers.size(), indices.size());
1695 maxIndex = 3 * block_size - 1;
1696 for (size_t i = 0; i < blockNumbers.size(); i++) {
1697 bool present = indices[i] <= maxIndex;
1698 if (present) {
1699 block_number_t expected = 1 + static_cast<block_number_t>(indices[i]) / block_size;
1700 EXPECT_EQ(blockNumbers[i].value(), expected);
1701 }
1702 EXPECT_EQ(blockNumbers[i].has_value(), present);
1703 }
1704
1705 // fork from block 1
1706 std::unique_ptr<Store> forkStore = std::make_unique<Store>(name, depth, 1, db);
1707 TreeType treeFork(std::move(forkStore), pool);
1708
1709 // Now, using the fork, get block indices but find it's limited to those of block 1
1710 get_blocks_for_indices(treeFork, indices, blockNumbers);
1711 EXPECT_EQ(blockNumbers.size(), indices.size());
1712
1713 maxIndex = block_size - 1;
1714 for (size_t i = 0; i < blockNumbers.size(); i++) {
1715 bool present = indices[i] <= maxIndex;
1716 if (present) {
1717 block_number_t expected = 1 + static_cast<block_number_t>(indices[i]) / block_size;
1718 EXPECT_EQ(blockNumbers[i].value(), expected);
1719 }
1720 EXPECT_EQ(blockNumbers[i].has_value(), present);
1721 }
1722
1723 // Now, using the fork, get block indics from the perspective of block 2, but find it's limited to those of block 1
1724 get_blocks_for_indices(treeFork, 2, indices, blockNumbers);
1725 EXPECT_EQ(blockNumbers.size(), indices.size());
1726
1727 maxIndex = block_size - 1;
1728 for (size_t i = 0; i < blockNumbers.size(); i++) {
1729 bool present = indices[i] <= maxIndex;
1730 if (present) {
1731 block_number_t expected = 1 + static_cast<block_number_t>(indices[i]) / block_size;
1732 EXPECT_EQ(blockNumbers[i].value(), expected);
1733 }
1734 EXPECT_EQ(blockNumbers[i].has_value(), present);
1735 }
1736}
1737
1739{
1740 std::string name = random_string();
1741 constexpr uint32_t depth = 10;
1742 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
1743 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
1745 TreeType tree(std::move(store), pool);
1747
1748 uint32_t blockSize = 16;
1749 uint32_t numBlocks = 16;
1750 uint32_t finalizedBlockDelay = 4;
1751 std::vector<fr> values = create_values(blockSize * numBlocks);
1752
1753 for (uint32_t i = 0; i < numBlocks; i++) {
1754 std::vector<fr> to_add;
1755
1756 for (size_t j = 0; j < blockSize; ++j) {
1757 size_t ind = i * blockSize + j;
1758 memdb.update_element(ind, values[ind]);
1759 to_add.push_back(values[ind]);
1760 }
1761 add_values(tree, to_add);
1762 commit_tree(tree);
1763
1764 block_number_t expectedFinalizedBlock = i < finalizedBlockDelay ? 0 : i - finalizedBlockDelay;
1765 check_finalized_block_height(tree, expectedFinalizedBlock);
1766
1767 if (i >= finalizedBlockDelay) {
1768
1769 block_number_t blockToFinalize = expectedFinalizedBlock + 1;
1770
1771 // attempting to finalize a block that doesn't exist should fail
1772 finalize_block(tree, blockToFinalize + numBlocks, false);
1773
1774 finalize_block(tree, blockToFinalize, true);
1775
1776 // finalising the currently finalized block should succeed
1777 finalize_block(tree, blockToFinalize, true);
1778 }
1779 }
1780}
1781
1783{
1784 std::string name = random_string();
1785 constexpr uint32_t depth = 10;
1786 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
1787 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
1789 TreeType tree(std::move(store), pool);
1791
1792 uint32_t blockSize = 16;
1793 uint32_t numBlocks = 16;
1794 std::vector<fr> values = create_values(blockSize * numBlocks);
1795
1796 for (uint32_t i = 0; i < numBlocks; i++) {
1797 std::vector<fr> to_add;
1798
1799 for (size_t j = 0; j < blockSize; ++j) {
1800 size_t ind = i * blockSize + j;
1801 memdb.update_element(ind, values[ind]);
1802 to_add.push_back(values[ind]);
1803 }
1804 add_values(tree, to_add);
1805 commit_tree(tree);
1806 }
1807
1808 check_block_height(tree, numBlocks);
1809
1810 block_number_t blockToFinalize = 8;
1811
1812 finalize_block(tree, blockToFinalize);
1813}
1814
1815TEST_F(PersistedContentAddressedAppendOnlyTreeTest, can_not_finalize_block_beyond_pending_chain)
1816{
1817 std::string name = random_string();
1818 constexpr uint32_t depth = 10;
1819 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
1820 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
1822 TreeType tree(std::move(store), pool);
1824
1825 uint32_t blockSize = 16;
1826 uint32_t numBlocks = 16;
1827 std::vector<fr> values = create_values(blockSize * numBlocks);
1828
1829 // finalising block 1 should fail
1830 finalize_block(tree, 1, false);
1831
1832 for (uint32_t i = 0; i < numBlocks; i++) {
1833 std::vector<fr> to_add;
1834
1835 for (size_t j = 0; j < blockSize; ++j) {
1836 size_t ind = i * blockSize + j;
1837 memdb.update_element(ind, values[ind]);
1838 to_add.push_back(values[ind]);
1839 }
1840 add_values(tree, to_add);
1841 commit_tree(tree);
1842 }
1843
1844 check_block_height(tree, numBlocks);
1845
1846 // should fail
1847 finalize_block(tree, numBlocks + 1, false);
1848
1849 // finalize the entire chain
1850 block_number_t blockToFinalize = numBlocks;
1851
1852 finalize_block(tree, blockToFinalize);
1853}
1854
1855TEST_F(PersistedContentAddressedAppendOnlyTreeTest, can_not_fork_from_unwound_blocks)
1856{
1857 std::string name = random_string();
1858 uint32_t depth = 20;
1859 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
1860 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
1862 TreeType tree(std::move(store), pool);
1863
1864 for (uint32_t i = 0; i < 5; i++) {
1865 std::vector<fr> values = create_values(1024);
1866 add_values(tree, values);
1867 commit_tree(tree);
1868 }
1869
1870 unwind_block(tree, 5);
1871 unwind_block(tree, 4);
1872
1873 EXPECT_THROW(Store(name, depth, 5, db), std::runtime_error);
1874 EXPECT_THROW(Store(name, depth, 4, db), std::runtime_error);
1875}
1876
1877TEST_F(PersistedContentAddressedAppendOnlyTreeTest, can_not_fork_from_expired_historical_blocks)
1878{
1879 std::string name = random_string();
1880 uint32_t depth = 20;
1881 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
1882 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
1884 TreeType tree(std::move(store), pool);
1885
1886 for (uint32_t i = 0; i < 5; i++) {
1887 std::vector<fr> values = create_values(1024);
1888 add_values(tree, values);
1889 commit_tree(tree);
1890 }
1891 finalize_block(tree, 3);
1892
1893 remove_historic_block(tree, 1);
1894 remove_historic_block(tree, 2);
1895
1896 EXPECT_THROW(Store(name, depth, 1, db), std::runtime_error);
1897 EXPECT_THROW(Store(name, depth, 2, db), std::runtime_error);
1898}
1899TEST_F(PersistedContentAddressedAppendOnlyTreeTest, can_fork_from_block_zero_when_not_latest)
1900{
1901 std::string name = random_string();
1902 uint32_t depth = 20;
1903 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
1904 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
1906 TreeType tree(std::move(store), pool);
1908
1909 uint32_t numBlocks = 5;
1910
1911 const fr initialRoot = memdb.root();
1912 const fr_sibling_path path = memdb.get_sibling_path(0);
1913
1914 for (uint32_t i = 0; i < numBlocks; i++) {
1915 std::vector<fr> values = create_values(1024);
1916 add_values(tree, values);
1917 commit_tree(tree);
1918 }
1919
1920 check_block_height(tree, numBlocks);
1921
1922 EXPECT_NO_THROW(Store(name, depth, 0, db));
1923
1924 std::unique_ptr<Store> store2 = std::make_unique<Store>(name, depth, 0, db);
1925 TreeType tree2(std::move(store2), pool);
1926
1927 check_root(tree2, initialRoot, false);
1928 check_sibling_path(tree2, 0, path, false, true);
1929}
1930
1932{
1933 std::string name = random_string();
1934 constexpr uint32_t depth = 10;
1935 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
1936 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
1938 TreeType tree(std::move(store), pool);
1940
1941 uint32_t blockSize = 16;
1942 uint32_t numBlocks = 16;
1943 std::vector<fr> values = create_values(blockSize * numBlocks);
1944
1945 for (uint32_t i = 0; i < numBlocks; i++) {
1946 std::vector<fr> to_add;
1947
1948 for (size_t j = 0; j < blockSize; ++j) {
1949 size_t ind = i * blockSize + j;
1950 memdb.update_element(ind, values[ind]);
1951 to_add.push_back(values[ind]);
1952 }
1953 add_values(tree, to_add);
1954 commit_tree(tree);
1955 }
1956
1957 check_block_height(tree, numBlocks);
1958
1959 block_number_t blockToFinalize = 8;
1960
1961 finalize_block(tree, blockToFinalize);
1962
1963 for (uint32_t i = numBlocks; i > blockToFinalize; i--) {
1964 unwind_block(tree, i);
1965 }
1966 unwind_block(tree, blockToFinalize, false);
1967}
1968
1969TEST_F(PersistedContentAddressedAppendOnlyTreeTest, can_not_historically_remove_finalized_block)
1970{
1971 std::string name = random_string();
1972 constexpr uint32_t depth = 10;
1973 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
1974 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
1976 TreeType tree(std::move(store), pool);
1978
1979 uint32_t blockSize = 16;
1980 uint32_t numBlocks = 16;
1981 std::vector<fr> values = create_values(blockSize * numBlocks);
1982
1983 for (uint32_t i = 0; i < numBlocks; i++) {
1984 std::vector<fr> to_add;
1985
1986 for (size_t j = 0; j < blockSize; ++j) {
1987 size_t ind = i * blockSize + j;
1988 memdb.update_element(ind, values[ind]);
1989 to_add.push_back(values[ind]);
1990 }
1991 add_values(tree, to_add);
1992 commit_tree(tree);
1993 }
1994
1995 check_block_height(tree, numBlocks);
1996
1997 block_number_t blockToFinalize = 8;
1998
1999 finalize_block(tree, blockToFinalize);
2000
2001 for (uint32_t i = 0; i < blockToFinalize - 1; i++) {
2002 remove_historic_block(tree, i + 1);
2003 }
2004 remove_historic_block(tree, blockToFinalize, false);
2005}
2006
2008{
2009 constexpr size_t depth = 10;
2010 uint32_t blockSize = 16;
2011 std::string name = random_string();
2013 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
2015
2016 {
2017 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
2018 TreeType tree(std::move(store), pool);
2019
2020 std::vector<fr> values = create_values(blockSize);
2021 add_values(tree, values);
2022
2023 commit_tree(tree);
2024 }
2025
2026 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
2027 TreeType tree(std::move(store), pool);
2028
2029 // We apply a number of updates and checkpoint the tree each time
2030
2031 uint32_t stackDepth = 20;
2032
2033 std::vector<fr_sibling_path> paths(stackDepth);
2034 uint32_t index = 0;
2035 for (; index < stackDepth - 1; index++) {
2036 std::vector<fr> values = create_values(blockSize);
2037 add_values(tree, values);
2038
2039 paths[index] = get_sibling_path(tree, 3);
2040
2041 try {
2042 checkpoint_tree(tree);
2043 } catch (std::exception& e) {
2044 std::cout << e.what() << std::endl;
2045 }
2046 }
2047
2048 // Now add one more depth, this will be un-checkpointed
2049 {
2050 std::vector<fr> values = create_values(blockSize);
2051 add_values(tree, values);
2052 paths[index] = get_sibling_path(tree, 3);
2053 }
2054
2055 index_t checkpointIndex = index;
2056
2057 // The tree is currently at the state of index 19
2058 EXPECT_EQ(get_sibling_path(tree, 3), paths[checkpointIndex]);
2059
2060 // We now alternate committing and reverting the checkpoints half way up the stack
2061
2062 for (; index > stackDepth / 2; index--) {
2063 if (index % 2 == 0) {
2064 revert_checkpoint_tree(tree, true);
2065 checkpointIndex = index - 1;
2066 } else {
2067 commit_checkpoint_tree(tree, true);
2068 }
2069
2070 EXPECT_EQ(get_sibling_path(tree, 3), paths[checkpointIndex]);
2071 }
2072
2073 // Now apply another set of updates and checkpoints back to the original stack depth
2074 for (; index < stackDepth - 1; index++) {
2075 std::vector<fr> values = create_values(blockSize);
2076 add_values(tree, values);
2077
2078 paths[index] = get_sibling_path(tree, 3);
2079
2080 try {
2081 checkpoint_tree(tree);
2082 } catch (std::exception& e) {
2083 std::cout << e.what() << std::endl;
2084 }
2085 }
2086
2087 // Now add one more depth, this will be un-checkpointed
2088 {
2089 std::vector<fr> values = create_values(blockSize);
2090 add_values(tree, values);
2091 paths[index] = get_sibling_path(tree, 3);
2092 }
2093
2094 // We now alternatively commit and revert all the way back to the start
2095 checkpointIndex = index;
2096
2097 // The tree is currently at the state of index 19
2098 EXPECT_EQ(get_sibling_path(tree, 3), paths[checkpointIndex]);
2099
2100 for (; index > 0; index--) {
2101 if (index % 2 == 0) {
2102 revert_checkpoint_tree(tree, true);
2103 checkpointIndex = index - 1;
2104 } else {
2105 commit_checkpoint_tree(tree, true);
2106 }
2107
2108 EXPECT_EQ(get_sibling_path(tree, 3), paths[checkpointIndex]);
2109 }
2110
2111 // Should not be able to commit or revert where there is no active checkpoint
2112 revert_checkpoint_tree(tree, false);
2113 commit_checkpoint_tree(tree, false);
2114}
2115
2117{
2118 constexpr size_t depth = 10;
2119 uint32_t blockSize = 16;
2120 std::string name = random_string();
2122 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
2124
2125 {
2126 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
2127 TreeType tree(std::move(store), pool);
2128
2129 std::vector<fr> values = create_values(blockSize);
2130 add_values(tree, values);
2131
2132 commit_tree(tree);
2133 }
2134
2135 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
2136 TreeType tree(std::move(store), pool);
2137
2138 // We apply a number of updates and checkpoint the tree each time
2139
2140 uint32_t stackDepth = 20;
2141
2142 std::vector<fr_sibling_path> paths(stackDepth);
2143 uint32_t index = 0;
2144 for (; index < stackDepth - 1; index++) {
2145 std::vector<fr> values = create_values(blockSize);
2146 add_values(tree, values);
2147
2148 paths[index] = get_sibling_path(tree, 3);
2149
2150 try {
2151 checkpoint_tree(tree);
2152 } catch (std::exception& e) {
2153 std::cout << e.what() << std::endl;
2154 }
2155 }
2156
2157 // Now we commit all the checkpoints
2159
2160 // Tree should still be at the final state
2161 check_sibling_path(tree, 3, paths[stackDepth - 2]);
2162
2163 // Should not be able to commit or revert where there is no active checkpoint
2164 revert_checkpoint_tree(tree, false);
2165 commit_checkpoint_tree(tree, false);
2166}
2167
2169{
2170 constexpr size_t depth = 10;
2171 uint32_t blockSize = 16;
2172 std::string name = random_string();
2174 LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
2176
2177 {
2178 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
2179 TreeType tree(std::move(store), pool);
2180
2181 std::vector<fr> values = create_values(blockSize);
2182 add_values(tree, values);
2183
2184 commit_tree(tree);
2185 }
2186
2187 std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
2188 TreeType tree(std::move(store), pool);
2189
2190 // We apply a number of updates and checkpoint the tree each time
2191
2192 uint32_t stackDepth = 20;
2193
2194 std::vector<fr_sibling_path> paths(stackDepth);
2195 uint32_t index = 0;
2196 for (; index < stackDepth - 1; index++) {
2197 std::vector<fr> values = create_values(blockSize);
2198 add_values(tree, values);
2199
2200 paths[index] = get_sibling_path(tree, 3);
2201
2202 try {
2203 checkpoint_tree(tree);
2204 } catch (std::exception& e) {
2205 std::cout << e.what() << std::endl;
2206 }
2207 }
2208
2209 // Now we revert all the checkpoints
2211
2212 // Tree should still be at the initial state
2213 check_sibling_path(tree, 3, paths[0]);
2214
2215 // Should not be able to commit or revert where there is no active checkpoint
2216 revert_checkpoint_tree(tree, false);
2217 commit_checkpoint_tree(tree, false);
2218}
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 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_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_leaf(TreeType &tree, const block_number_t &blockNumber, const fr &leaf, index_t leaf_index, bool expected_success, bool includeUncommitted=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_leaf(TreeType &tree, const fr &leaf, index_t leaf_index, bool expected_success, bool includeUncommitted=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:65
void commit_all_tree_checkpoints(TreeType &tree, bool expected_success=true)
void rollback_tree(TreeType &tree)
const uint32_t NUM_VALUES
Definition fixtures.hpp:21
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:42
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:35
std::shared_ptr< ThreadPool > ThreadPoolPtr
Definition fixtures.hpp:63
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.
TEST_F(IPATest, ChallengesAreZero)
Definition ipa.test.cpp:123
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:35
static constexpr field zero()