Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
lmdb_tree_store.test.cpp
Go to the documentation of this file.
1#include <cstddef>
2#include <cstdint>
3#include <gtest/gtest.h>
4
5#include <chrono>
6#include <cstdlib>
7#include <filesystem>
8#include <stdexcept>
9#include <vector>
10
23#include "lmdb_tree_store.hpp"
24
25using namespace bb::stdlib;
26using namespace bb::crypto::merkle_tree;
27
29
32
33class LMDBTreeStoreTest : public testing::Test {
34 protected:
35 void SetUp() override
36 {
38 _mapSize = 1024 * 1024;
39 _maxReaders = 16;
40 std::filesystem::create_directories(_directory);
41 }
42
43 void TearDown() override { std::filesystem::remove_all(_directory); }
44
45 static std::string _directory;
46 static uint64_t _maxReaders;
47 static uint64_t _mapSize;
48};
49
53
54TEST_F(LMDBTreeStoreTest, can_write_and_read_block_data)
55{
56 BlockPayload blockData;
57 blockData.blockNumber = 3;
58 blockData.root = VALUES[0];
59 blockData.size = 45;
60 LMDBTreeStore store(_directory, "DB1", _mapSize, _maxReaders);
61 {
63 store.write_block_data(3, blockData, *transaction);
64 transaction->commit();
65 }
66
67 {
69 BlockPayload readBack;
70 bool success = store.read_block_data(3, readBack, *transaction);
71 EXPECT_TRUE(success);
72 EXPECT_EQ(readBack, blockData);
73
74 success = store.read_block_data(4, readBack, *transaction);
75 EXPECT_FALSE(success);
76 }
77}
78
79TEST_F(LMDBTreeStoreTest, can_write_and_read_meta_data)
80{
81 TreeMeta metaData;
82 metaData.committedSize = 56;
83 metaData.initialSize = 12;
84 metaData.initialRoot = VALUES[1];
85 metaData.root = VALUES[2];
86 metaData.depth = 40;
87 metaData.oldestHistoricBlock = 87;
88 metaData.unfinalizedBlockHeight = 95;
89 metaData.name = "Note hash tree";
90 metaData.size = 60;
91 LMDBTreeStore store(_directory, "DB1", _mapSize, _maxReaders);
92 {
94 store.write_meta_data(metaData, *transaction);
95 transaction->commit();
96 }
97
98 {
100 TreeMeta readBack;
101 bool success = store.read_meta_data(readBack, *transaction);
102 EXPECT_TRUE(success);
103 EXPECT_EQ(readBack, metaData);
104 }
105}
106
107TEST_F(LMDBTreeStoreTest, can_read_data_from_multiple_threads)
108{
109 TreeMeta metaData;
110 metaData.committedSize = 56;
111 metaData.initialSize = 12;
112 metaData.initialRoot = VALUES[1];
113 metaData.root = VALUES[2];
114 metaData.depth = 40;
115 metaData.oldestHistoricBlock = 87;
116 metaData.unfinalizedBlockHeight = 95;
117 metaData.name = "Note hash tree";
118 metaData.size = 60;
119 LMDBTreeStore store(_directory, "DB1", _mapSize, 2);
120 {
122 store.write_meta_data(metaData, *transaction);
123 transaction->commit();
124 }
125
126 uint64_t numIterationsPerThread = 1000;
127 uint32_t numThreads = 16;
128
129 {
130 auto func = [&]() -> void {
131 for (uint64_t iteration = 0; iteration < numIterationsPerThread; iteration++) {
133 TreeMeta readBack;
134 bool success = store.read_meta_data(readBack, *transaction);
135 EXPECT_TRUE(success);
136 EXPECT_EQ(readBack, metaData);
137 }
138 };
140 for (uint64_t count = 0; count < numThreads; count++) {
141 threads.emplace_back(std::make_unique<std::thread>(func));
142 }
143 for (uint64_t count = 0; count < numThreads; count++) {
144 threads[count]->join();
145 }
146 }
147}
148
149TEST_F(LMDBTreeStoreTest, can_write_and_read_multiple_blocks_with_meta)
150{
151 LMDBTreeStore store(_directory, "DB1", _mapSize, _maxReaders);
152 block_number_t start_block = 647810461;
153 block_number_t num_blocks = 1000;
154 for (block_number_t i = 0; i < num_blocks; i++) {
155 BlockPayload blockData;
156 blockData.blockNumber = i + start_block;
157 blockData.root = VALUES[i];
158 blockData.size = 45 + (i * 97);
160 store.write_block_data(i + start_block, blockData, *transaction);
161
162 TreeMeta meta;
163 meta.committedSize = blockData.size;
164 meta.size = blockData.size;
165 meta.root = blockData.root;
166 meta.depth = 32;
167 meta.unfinalizedBlockHeight = i + start_block;
168 meta.name = "NullifierTree";
169 store.write_meta_data(meta, *transaction);
170 transaction->commit();
171 }
172
173 BlockPayload blockData;
174 for (block_number_t i = 0; i < num_blocks; i++) {
176 BlockPayload readBack;
177 bool success = store.read_block_data(i + start_block, readBack, *transaction);
178 EXPECT_TRUE(success);
179
180 blockData.blockNumber = i + start_block;
181 blockData.root = VALUES[i];
182 blockData.size = 45 + (i * 97);
183 EXPECT_EQ(readBack, blockData);
184 }
185
186 {
187 TreeMeta meta;
189 store.read_meta_data(meta, *transaction);
190
191 EXPECT_EQ(meta.committedSize, blockData.size);
192 EXPECT_EQ(meta.size, blockData.size);
193 EXPECT_EQ(meta.root, blockData.root);
194 EXPECT_EQ(meta.depth, 32);
195 EXPECT_EQ(meta.unfinalizedBlockHeight, blockData.blockNumber);
196 EXPECT_EQ(meta.name, "NullifierTree");
197 }
198}
199
200uint64_t serde_value(uint64_t value)
201{
202 std::vector<uint8_t> data = serialise_key(value);
203 uint64_t return_value = 0;
204 deserialise_key(data.data(), return_value);
205 return return_value;
206}
207
208TEST_F(LMDBTreeStoreTest, can_serde_64bit_values)
209{
210 union mapped {
211 uint64_t u64;
212 uint8_t arr[8];
213 };
214 mapped value1;
215 mapped value2;
216 value1.arr[0] = value2.arr[7] = 0x11;
217 value1.arr[1] = value2.arr[6] = 0x22;
218 value1.arr[2] = value2.arr[5] = 0x33;
219 value1.arr[3] = value2.arr[4] = 0x44;
220 value1.arr[4] = value2.arr[3] = 0x55;
221 value1.arr[5] = value2.arr[2] = 0x66;
222 value1.arr[6] = value2.arr[1] = 0x77;
223 value1.arr[7] = value2.arr[0] = 0x88;
224
225 EXPECT_EQ(value1.u64, serde_value(value1.u64));
226 EXPECT_EQ(value2.u64, serde_value(value2.u64));
227}
228
229TEST_F(LMDBTreeStoreTest, can_write_and_read_leaf_indices)
230{
231 index_t index = 47;
232 bb::fr key = VALUES[5];
233 LMDBTreeStore store(_directory, "DB1", _mapSize, _maxReaders);
234 {
236 store.write_leaf_index(key, index, *transaction);
237 transaction->commit();
238 }
239
240 {
242 index_t readBack = 0;
243 bool success = store.read_leaf_index(key, readBack, *transaction);
244 EXPECT_TRUE(success);
245 EXPECT_EQ(readBack, index);
246
247 success = store.read_leaf_index(VALUES[6], readBack, *transaction);
248 EXPECT_FALSE(success);
249 }
250}
251
252TEST_F(LMDBTreeStoreTest, can_write_and_read_nodes)
253{
254 NodePayload nodePayload;
255 nodePayload.left = VALUES[4];
256 nodePayload.right = VALUES[5];
257 nodePayload.ref = 4;
258 bb::fr key = VALUES[6];
259 LMDBTreeStore store(_directory, "DB1", _mapSize, _maxReaders);
260 {
262 store.write_node(key, nodePayload, *transaction);
263 transaction->commit();
264 }
265
266 {
268 NodePayload readBack;
269 bool success = store.read_node(key, readBack, *transaction);
270 EXPECT_TRUE(success);
271 EXPECT_EQ(readBack, nodePayload);
272
273 success = store.read_node(VALUES[9], readBack, *transaction);
274 EXPECT_FALSE(success);
275 }
276}
277
278TEST_F(LMDBTreeStoreTest, can_write_and_read_leaves_by_hash)
279{
280 PublicDataLeafValue leafData;
281 leafData.slot = VALUES[0];
282 leafData.value = VALUES[1];
283 bb::fr key = VALUES[2];
284 LMDBTreeStore store(_directory, "DB1", _mapSize, _maxReaders);
285 {
287 store.write_leaf_by_hash(key, leafData, *transaction);
288 transaction->commit();
289 }
290
291 {
293 PublicDataLeafValue readBack;
294 bool success = store.read_leaf_by_hash(key, readBack, *transaction);
295 EXPECT_TRUE(success);
296 EXPECT_EQ(readBack, leafData);
297
298 success = store.read_leaf_by_hash(VALUES[9], readBack, *transaction);
299 EXPECT_FALSE(success);
300 }
301}
302
303TEST_F(LMDBTreeStoreTest, can_write_and_retrieve_block_numbers_by_index)
304{
305 struct BlockAndIndex {
306 block_number_t blockNumber;
307 // this block contains leaves up to index (0 based)
308 index_t index;
309 };
310
311 std::vector<BlockAndIndex> blocks{ BlockAndIndex{ .blockNumber = 1, .index = 25 },
312 BlockAndIndex{ .blockNumber = 2, .index = 60 },
313 BlockAndIndex{ .blockNumber = 3, .index = 82 },
314 BlockAndIndex{ .blockNumber = 4, .index = 114 },
315 BlockAndIndex{ .blockNumber = 5, .index = 130 } };
316 LMDBTreeStore store(_directory, "DB1", _mapSize, _maxReaders);
317 {
318 // write all of the blocks.
320 for (auto block : blocks) {
321 // the arg is block size so add 1
322 store.write_block_index_data(block.blockNumber, block.index + 1, *transaction);
323 }
324 transaction->commit();
325 }
326
327 {
328 // read back some blocks and check them
330 block_number_t readBack = 0;
331 EXPECT_TRUE(store.find_block_for_index(5, readBack, *transaction));
332 EXPECT_EQ(readBack, 1);
333
334 EXPECT_TRUE(store.find_block_for_index(30, readBack, *transaction));
335 EXPECT_EQ(readBack, 2);
336
337 EXPECT_TRUE(store.find_block_for_index(82, readBack, *transaction));
338 EXPECT_EQ(readBack, 3);
339
340 EXPECT_TRUE(store.find_block_for_index(83, readBack, *transaction));
341 EXPECT_EQ(readBack, 4);
342
343 EXPECT_TRUE(store.find_block_for_index(130, readBack, *transaction));
344 EXPECT_EQ(readBack, 5);
345
346 EXPECT_FALSE(store.find_block_for_index(131, readBack, *transaction));
347 }
348
349 {
350 // delete the last block
352 // the arg is block size so add 1
353 store.delete_block_index(blocks[4].index + 1, blocks[4].blockNumber, *transaction);
354 transaction->commit();
355 }
356
357 {
358 // check the blocks again
360 block_number_t readBack = 0;
361 EXPECT_TRUE(store.find_block_for_index(5, readBack, *transaction));
362 EXPECT_EQ(readBack, 1);
363
364 EXPECT_TRUE(store.find_block_for_index(30, readBack, *transaction));
365 EXPECT_EQ(readBack, 2);
366
367 EXPECT_TRUE(store.find_block_for_index(82, readBack, *transaction));
368 EXPECT_EQ(readBack, 3);
369
370 EXPECT_TRUE(store.find_block_for_index(83, readBack, *transaction));
371 EXPECT_EQ(readBack, 4);
372
373 EXPECT_FALSE(store.find_block_for_index(130, readBack, *transaction));
374
375 EXPECT_FALSE(store.find_block_for_index(131, readBack, *transaction));
376 }
377
378 {
379 // delete 2 more blocks
381 // the arg is block size so add 1
382 store.delete_block_index(blocks[3].index + 1, blocks[3].blockNumber, *transaction);
383 store.delete_block_index(blocks[2].index + 1, blocks[2].blockNumber, *transaction);
384 transaction->commit();
385 }
386
387 {
388 // check the blocks again
390 block_number_t readBack = 0;
391 EXPECT_TRUE(store.find_block_for_index(5, readBack, *transaction));
392 EXPECT_EQ(readBack, 1);
393
394 EXPECT_TRUE(store.find_block_for_index(30, readBack, *transaction));
395 EXPECT_EQ(readBack, 2);
396
397 EXPECT_FALSE(store.find_block_for_index(82, readBack, *transaction));
398
399 EXPECT_FALSE(store.find_block_for_index(83, readBack, *transaction));
400
401 EXPECT_FALSE(store.find_block_for_index(130, readBack, *transaction));
402
403 EXPECT_FALSE(store.find_block_for_index(131, readBack, *transaction));
404 }
405
406 {
407 // delete non-exisatent indices to check it does nothing
409 // the arg is block size so add 1
410 store.delete_block_index(blocks[3].index + 1, blocks[3].blockNumber, *transaction);
411 store.delete_block_index(blocks[2].index + 1, blocks[2].blockNumber, *transaction);
412 store.delete_block_index(21, 1, *transaction);
413 store.delete_block_index(150, 6, *transaction);
414 transaction->commit();
415 }
416
417 {
418 // check the blocks again
420 block_number_t readBack = 0;
421 EXPECT_TRUE(store.find_block_for_index(5, readBack, *transaction));
422 EXPECT_EQ(readBack, 1);
423
424 EXPECT_TRUE(store.find_block_for_index(30, readBack, *transaction));
425 EXPECT_EQ(readBack, 2);
426
427 EXPECT_FALSE(store.find_block_for_index(82, readBack, *transaction));
428
429 EXPECT_FALSE(store.find_block_for_index(83, readBack, *transaction));
430
431 EXPECT_FALSE(store.find_block_for_index(130, readBack, *transaction));
432
433 EXPECT_FALSE(store.find_block_for_index(131, readBack, *transaction));
434 }
435}
436
437TEST_F(LMDBTreeStoreTest, can_write_and_retrieve_block_numbers_with_duplicate_indices)
438{
439 struct BlockAndIndex {
440 block_number_t blockNumber;
441 index_t index;
442 };
443
444 std::vector<BlockAndIndex> blocks{ BlockAndIndex{ .blockNumber = 1, .index = 25 },
445 BlockAndIndex{ .blockNumber = 2, .index = 60 },
446 BlockAndIndex{ .blockNumber = 3, .index = 60 },
447 BlockAndIndex{ .blockNumber = 4, .index = 60 },
448 BlockAndIndex{ .blockNumber = 5, .index = 130 } };
449 LMDBTreeStore store(_directory, "DB1", _mapSize, _maxReaders);
450 {
451 // write all of the blocks. we will write them in reverse order
453 for (auto block : blocks) {
454 // the arg is block size so add 1
455 store.write_block_index_data(block.blockNumber, block.index + 1, *transaction);
456 }
457 transaction->commit();
458 }
459
460 {
461 // we can't add a duplicate block at an index if it is not the next block number
463 // the arg is block size so add 1
464 EXPECT_THROW(store.write_block_index_data(3, 60 + 1, *transaction), std::runtime_error);
465 EXPECT_THROW(store.write_block_index_data(6, 60 + 1, *transaction), std::runtime_error);
466 EXPECT_THROW(store.write_block_index_data(1, 25 + 1, *transaction), std::runtime_error);
467 EXPECT_THROW(store.write_block_index_data(3, 25 + 1, *transaction), std::runtime_error);
468 transaction->abort();
469 }
470
471 {
472 // read back some blocks and check them
474 block_number_t readBack = 0;
475 EXPECT_TRUE(store.find_block_for_index(5, readBack, *transaction));
476 EXPECT_EQ(readBack, 1);
477
478 // should be the lowest block at this index
479 EXPECT_TRUE(store.find_block_for_index(30, readBack, *transaction));
480 EXPECT_EQ(readBack, 2);
481
482 EXPECT_TRUE(store.find_block_for_index(82, readBack, *transaction));
483 EXPECT_EQ(readBack, 5);
484
485 EXPECT_FALSE(store.find_block_for_index(131, readBack, *transaction));
486 }
487
488 {
489 // attempting to delete block 2 at index 60 should fail as it is not the last block in the series at index 60
491 // the arg is block size so add 1
492 EXPECT_THROW(store.delete_block_index(blocks[1].index + 1, blocks[1].blockNumber, *transaction),
493 std::runtime_error);
494 transaction->abort();
495 }
496
497 {
498 // read back some blocks and check them
500 block_number_t readBack = 0;
501 EXPECT_TRUE(store.find_block_for_index(5, readBack, *transaction));
502 EXPECT_EQ(readBack, 1);
503
504 // should still be the lowest block at this index
505 EXPECT_TRUE(store.find_block_for_index(30, readBack, *transaction));
506 EXPECT_EQ(readBack, 2);
507
508 EXPECT_TRUE(store.find_block_for_index(82, readBack, *transaction));
509 EXPECT_EQ(readBack, 5);
510
511 EXPECT_FALSE(store.find_block_for_index(131, readBack, *transaction));
512 }
513
514 {
515 // try and delete blocks that don't exist at index 60
517 // the arg is block size so add 1
518 EXPECT_THROW(store.delete_block_index(blocks[1].index + 1, 2, *transaction), std::runtime_error);
519 EXPECT_THROW(store.delete_block_index(blocks[1].index + 1, 5, *transaction), std::runtime_error);
520 transaction->abort();
521 }
522
523 {
524 // read back some blocks and check them
526 block_number_t readBack = 0;
527 EXPECT_TRUE(store.find_block_for_index(5, readBack, *transaction));
528 EXPECT_EQ(readBack, 1);
529
530 EXPECT_TRUE(store.find_block_for_index(30, readBack, *transaction));
531 EXPECT_EQ(readBack, 2);
532
533 EXPECT_TRUE(store.find_block_for_index(82, readBack, *transaction));
534 EXPECT_EQ(readBack, 5);
535
536 EXPECT_FALSE(store.find_block_for_index(131, readBack, *transaction));
537 }
538
539 {
540 // delete the last 2 blocks at index 60
542 // the arg is block size so add 1
543 store.delete_block_index(blocks[3].index + 1, blocks[3].blockNumber, *transaction);
544 store.delete_block_index(blocks[2].index + 1, blocks[2].blockNumber, *transaction);
545 transaction->commit();
546 }
547
548 {
549 // check the blocks again
551 block_number_t readBack = 0;
552 EXPECT_TRUE(store.find_block_for_index(5, readBack, *transaction));
553 EXPECT_EQ(readBack, 1);
554
555 EXPECT_TRUE(store.find_block_for_index(30, readBack, *transaction));
556 EXPECT_EQ(readBack, 2);
557
558 EXPECT_TRUE(store.find_block_for_index(82, readBack, *transaction));
559 EXPECT_EQ(readBack, 5);
560 }
561
562 {
563 // delete the last final block at index 60
565 // the arg is block size so add 1
566 // Only one block remains at index 60, try and delete one that doesn't exist, it should do nothing
567 store.delete_block_index(blocks[3].index + 1, blocks[3].blockNumber, *transaction);
568
569 // Now delete the last block
570 store.delete_block_index(blocks[1].index + 1, blocks[1].blockNumber, *transaction);
571 transaction->commit();
572 }
573
574 {
575 // check the blocks again
577 block_number_t readBack = 0;
578 EXPECT_TRUE(store.find_block_for_index(5, readBack, *transaction));
579 EXPECT_EQ(readBack, 1);
580
581 EXPECT_TRUE(store.find_block_for_index(30, readBack, *transaction));
582 EXPECT_EQ(readBack, 5);
583
584 EXPECT_TRUE(store.find_block_for_index(82, readBack, *transaction));
585 EXPECT_EQ(readBack, 5);
586 }
587}
588
589TEST_F(LMDBTreeStoreTest, reports_physical_file_size)
590{
591 LMDBTreeStore store(_directory, "DB1", _mapSize, _maxReaders);
592 std::string dataDbPath = (std::filesystem::path(_directory) / "data.mdb").string();
593 size_t previousFileSize = 0;
594
595 for (size_t i = 0; i < 3; i++) {
596 {
597 BlockPayload blockData;
598 blockData.blockNumber = static_cast<block_number_t>(i);
599 blockData.root = VALUES[i];
600 blockData.size = 45 + (i * 97);
601
602 TreeMeta metaData;
603 metaData.committedSize = blockData.size;
604 metaData.size = blockData.size;
605 metaData.root = blockData.root;
606 metaData.depth = 32;
607 metaData.unfinalizedBlockHeight = static_cast<block_number_t>(i);
608 metaData.name = "NullifierTree";
609
610 // Write metadata and block data with different values each iteration
612 store.write_meta_data(metaData, *transaction);
613 store.write_block_data(blockData.blockNumber, blockData, *transaction);
614 transaction->commit();
615 }
616
617 {
619 TreeDBStats stats;
620 store.get_stats(stats, *transaction);
621
622 EXPECT_TRUE(std::filesystem::exists(dataDbPath));
623
624 // Verify reported size matches actual file size
625 EXPECT_EQ(stats.physicalFileSize, std::filesystem::file_size(dataDbPath));
626
627 // Verify size is increasing due to the new DB writes
628 EXPECT_GT(stats.physicalFileSize, previousFileSize);
629
630 previousFileSize = stats.physicalFileSize;
631 }
632 }
633}
static uint64_t _maxReaders
static std::string _directory
void write_leaf_by_hash(const fr &leafHash, const LeafType &leafData, WriteTransaction &tx)
bool find_block_for_index(const index_t &index, block_number_t &blockNumber, ReadTransaction &tx)
void write_node(const fr &nodeHash, const NodePayload &nodeData, WriteTransaction &tx)
void write_block_data(const block_number_t &blockNumber, const BlockPayload &blockData, WriteTransaction &tx)
void write_leaf_index(const fr &leafValue, const index_t &leafIndex, WriteTransaction &tx)
void delete_block_index(const index_t &sizeAtBlock, const block_number_t &blockNumber, WriteTransaction &tx)
bool read_node(const fr &nodeHash, NodePayload &nodeData, ReadTransaction &tx)
void write_meta_data(const TreeMeta &metaData, WriteTransaction &tx)
bool read_block_data(const block_number_t &blockNumber, BlockPayload &blockData, ReadTransaction &tx)
void get_stats(TreeDBStats &stats, ReadTransaction &tx)
bool read_leaf_by_hash(const fr &leafHash, LeafType &leafData, TxType &tx)
void write_block_index_data(const block_number_t &blockNumber, const index_t &sizeAtBlock, WriteTransaction &tx)
bool read_meta_data(TreeMeta &metaData, ReadTransaction &tx)
bool read_leaf_index(const fr &leafValue, index_t &leafIndex, TxType &tx)
std::unique_ptr< LMDBReadTransaction > Ptr
WriteTransaction::Ptr create_write_transaction() const
ReadTransaction::Ptr create_read_transaction() const
std::unique_ptr< LMDBWriteTransaction > Ptr
const std::vector< FF > data
uint64_t serde_value(uint64_t value)
std::string random_temp_directory()
Definition fixtures.hpp:42
uint32_t block_number_t
Definition types.hpp:19
void deserialise_key(void *data, uint8_t &key)
std::vector< uint8_t > serialise_key(uint8_t key)
TEST_F(IPATest, ChallengesAreZero)
Definition ipa.test.cpp:123
UltraCircuitBuilder_< UltraExecutionTraceBlocks > UltraCircuitBuilder
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13