Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
world_state.test.cpp
Go to the documentation of this file.
2#include "barretenberg/aztec/aztec_constants.hpp"
10#include <array>
11#include <cstdint>
12#include <filesystem>
13#include <gtest/gtest.h>
14#include <optional>
15#include <stdexcept>
16#include <sys/types.h>
17#include <unordered_map>
18
19using namespace bb::world_state;
20using namespace bb::crypto::merkle_tree;
21
22class WorldStateTest : public testing::Test {
23 protected:
24 void SetUp() override
25 {
27 std::filesystem::create_directories(data_dir);
28 }
29
30 void TearDown() override { std::filesystem::remove_all(data_dir); }
31
32 static std::string data_dir;
33 uint64_t map_size = 10240;
34 uint64_t thread_pool_size = 1;
35
36 // TODO(): https://github.com/AztecProtocol/aztec-packages/issues/8084
37 std::unordered_map<MerkleTreeId, uint32_t> tree_heights{
38 { MerkleTreeId::NULLIFIER_TREE, NULLIFIER_TREE_HEIGHT },
39 { MerkleTreeId::NOTE_HASH_TREE, NOTE_HASH_TREE_HEIGHT },
40 { MerkleTreeId::PUBLIC_DATA_TREE, PUBLIC_DATA_TREE_HEIGHT },
41 { MerkleTreeId::L1_TO_L2_MESSAGE_TREE, L1_TO_L2_MSG_TREE_HEIGHT },
42 { MerkleTreeId::ARCHIVE, ARCHIVE_HEIGHT },
43 };
44 std::unordered_map<MerkleTreeId, index_t> tree_prefill{
45 { MerkleTreeId::NULLIFIER_TREE, 128 },
46 { MerkleTreeId::PUBLIC_DATA_TREE, 128 },
47 };
48 uint32_t initial_header_generator_point = DOM_SEP__BLOCK_HEADER_HASH;
49};
50
51std::string WorldStateTest::data_dir;
52
53template <typename Leaf>
55 const WorldState& ws, WorldStateRevision revision, MerkleTreeId tree_id, index_t leaf_index, bool exists)
56{
57 std::optional<Leaf> leaf = ws.get_leaf<Leaf>(revision, tree_id, leaf_index);
58 // TODO(#17755): Unwritten leaves at valid indices (index <= max_index) now return 0-value leaves with a message in
59 // the response from the tree, so leaf.has_value() is always true:
60 EXPECT_EQ(leaf.has_value(), true);
61 if (exists) {
62 EXPECT_NE(leaf.value(), fr::zero());
63 } else {
64 EXPECT_EQ(leaf.value(), fr::zero());
65 }
66}
67
68template <typename Leaf>
70 WorldStateRevision revision,
71 MerkleTreeId tree_id,
72 index_t leaf_index,
73 const Leaf& expected_value)
74{
75 std::optional<Leaf> leaf = ws.get_leaf<Leaf>(revision, tree_id, leaf_index);
76 EXPECT_EQ(leaf.has_value(), true);
77 EXPECT_EQ(leaf.value(), expected_value);
78}
79
80template <typename Leaf>
82 const WorldState& ws, WorldStateRevision revision, MerkleTreeId tree_id, const Leaf& expected_value, bool exists)
83{
85 ws.find_leaf_indices<Leaf>(revision, tree_id, { expected_value }, indices);
86 EXPECT_EQ(indices.size(), 1);
87 EXPECT_EQ(indices[0].has_value(), exists);
88}
89
90template <typename Leaf>
92 const WorldState& ws, WorldStateRevision revision, MerkleTreeId tree_id, const Leaf& value, index_t expected_index)
93{
95 ws.find_leaf_indices<Leaf>(revision, tree_id, { value }, indices);
96 EXPECT_EQ(indices.size(), 1);
97 EXPECT_TRUE(indices[0].has_value());
98 if (!indices[0].has_value()) {
99 return;
100 }
101 EXPECT_EQ(indices[0].value(), expected_index);
102}
103
104void assert_tree_size(const WorldState& ws, WorldStateRevision revision, MerkleTreeId tree_id, size_t expected_size)
105{
106 auto info = ws.get_tree_info(revision, tree_id);
107 EXPECT_EQ(info.meta.size, expected_size);
108}
109
111 const WorldState& ws, WorldStateRevision revision, MerkleTreeId tree_id, fr root, fr leaf, index_t index)
112{
113 auto sibling_path = ws.get_sibling_path(revision, tree_id, index);
114 fr left;
115 fr right;
116 fr hash = leaf;
117 // Pick the merkle-pair hasher that matches the target tree.
118 auto hash_pair_for_tree = [tree_id](const fr& l, const fr& r) -> fr {
119 using namespace aztec;
120 switch (tree_id) {
121 case MerkleTreeId::NULLIFIER_TREE:
122 return NullifierMerkleHashPolicy::hash_pair(l, r);
123 case MerkleTreeId::PUBLIC_DATA_TREE:
124 return PublicDataMerkleHashPolicy::hash_pair(l, r);
125 default:
127 }
128 };
129 for (const auto& node : sibling_path) {
130 if (index % 2 == 0) {
131 left = hash;
132 right = node;
133 } else {
134 left = node;
135 right = hash;
136 }
137
138 hash = hash_pair_for_tree(left, right);
139 index >>= 1;
140 }
141
142 EXPECT_EQ(hash, root);
143}
144
146 Fork::Id forkId,
147 bool includeUncommitted,
148 const std::vector<MerkleTreeId>& trees = { MerkleTreeId::NULLIFIER_TREE,
149 MerkleTreeId::NOTE_HASH_TREE,
150 MerkleTreeId::PUBLIC_DATA_TREE,
151 MerkleTreeId::L1_TO_L2_MESSAGE_TREE,
152 MerkleTreeId::ARCHIVE })
153{
154
155 for (auto tree_id : trees) {
156 auto canonical_tree_info =
157 ws.get_tree_info(WorldStateRevision{ .includeUncommitted = includeUncommitted }, tree_id);
158 auto fork_tree_info = ws.get_tree_info(
160 .forkId = forkId,
161 .includeUncommitted = includeUncommitted,
162 },
163 tree_id);
164
165 EXPECT_EQ(canonical_tree_info.meta, fork_tree_info.meta);
166 }
167}
168
169TEST_F(WorldStateTest, GetInitialTreeInfoForAllTrees)
170{
171 WorldState ws(thread_pool_size, data_dir, map_size, tree_heights, tree_prefill, initial_header_generator_point);
172
173 {
174 auto info = ws.get_tree_info(WorldStateRevision::committed(), MerkleTreeId::NULLIFIER_TREE);
175 EXPECT_EQ(info.meta.size, 128);
176 EXPECT_EQ(info.meta.depth, tree_heights.at(MerkleTreeId::NULLIFIER_TREE));
177 EXPECT_EQ(info.meta.root, bb::fr("0x18935581a8ed73d08ffd00386fba55ba6c89f3ab848a76b8fedfa9034cee0454"));
178 }
179
180 {
181 auto info = ws.get_tree_info(WorldStateRevision::committed(), MerkleTreeId::NOTE_HASH_TREE);
182 EXPECT_EQ(info.meta.size, 0);
183 EXPECT_EQ(info.meta.depth, tree_heights.at(MerkleTreeId::NOTE_HASH_TREE));
184 EXPECT_EQ(info.meta.root, bb::fr("0x2590f2aab19dd791700b4a43d3f52bb88ef2409a3731da8e848663559202e4c6"));
185 }
186
187 {
188 auto info = ws.get_tree_info(WorldStateRevision::committed(), MerkleTreeId::PUBLIC_DATA_TREE);
189 EXPECT_EQ(info.meta.size, 128);
190 EXPECT_EQ(info.meta.depth, tree_heights.at(MerkleTreeId::PUBLIC_DATA_TREE));
191 EXPECT_EQ(info.meta.root, bb::fr("0x1bef38b621017d3c7416663d0cd81369424560710526a3fbaaec13e356b9d084"));
192 }
193
194 {
195 auto info = ws.get_tree_info(WorldStateRevision::committed(), MerkleTreeId::L1_TO_L2_MESSAGE_TREE);
196 EXPECT_EQ(info.meta.size, 0);
197 EXPECT_EQ(info.meta.depth, tree_heights.at(MerkleTreeId::L1_TO_L2_MESSAGE_TREE));
198 EXPECT_EQ(info.meta.root, bb::fr("0x0fef6d80d31109ddb56d6b3f607cbc9c0af0bff3ea0d43e8f278983c64c11f7a"));
199 }
200
201 {
202 auto info = ws.get_tree_info(WorldStateRevision::committed(), MerkleTreeId::ARCHIVE);
203 EXPECT_EQ(info.meta.size, 1);
204 EXPECT_EQ(info.meta.depth, tree_heights.at(MerkleTreeId::ARCHIVE));
205 // this is the expected archive tree root at genesis
206 EXPECT_EQ(info.meta.root, bb::fr(GENESIS_ARCHIVE_ROOT));
207
208 // The leaf at index 0 is the genesis block hash.
210 ws, WorldStateRevision::committed(), MerkleTreeId::ARCHIVE, 0, bb::fr(GENESIS_BLOCK_HEADER_HASH));
211 }
212}
213
214TEST_F(WorldStateTest, GetInitialTreeInfoWithPrefilledNullifiers)
215{
216 // Prefilled nullifier leaves must be unique and strictly increasing, and larger than the padding leaves that fill
217 // the initial 128-leaf prefill region (whose keys are the low integers 0..127), so we use full-size field values.
218 std::vector<bb::fr> prefilled_nullifiers = {
219 bb::fr("0x073b5e41abe9d7f8466bca9c81c9572b558f953bbd70081317f6a80ac65f3dd5"),
220 bb::fr("0x0d99507b7ecac720c73bf197a0e7366a5ed80c1c1b0afe8ff8c6ecc7b5a7aefe"),
221 bb::fr("0x1c0bf82e0c51834780e61ef091b17e3a1d39ae891db7a70bfdb5221f134996ac"),
222 };
223
224 std::string data_dir_prefilled = random_temp_directory();
225 std::filesystem::create_directories(data_dir_prefilled);
226
227 WorldState ws_prefilled(thread_pool_size,
228 data_dir_prefilled,
229 map_size,
230 tree_heights,
231 tree_prefill,
233 prefilled_nullifiers,
234 initial_header_generator_point);
235
236 // Baseline world state with no prefilled nullifiers (the canonical empty genesis).
237 WorldState ws(thread_pool_size, data_dir, map_size, tree_heights, tree_prefill, initial_header_generator_point);
238
239 auto prefilled = ws_prefilled.get_tree_info(WorldStateRevision::committed(), MerkleTreeId::NULLIFIER_TREE);
240 auto info = ws.get_tree_info(WorldStateRevision::committed(), MerkleTreeId::NULLIFIER_TREE);
241
242 // The prefilled nullifiers occupy the last slots of the 128-leaf initial prefill region (they replace padding
243 // leaves rather than being appended), so the tree size stays 128 for both.
244 EXPECT_EQ(prefilled.meta.size, 128);
245 EXPECT_EQ(info.meta.size, 128);
246
247 // Seeding the nullifiers changes the nullifier-tree root away from the empty-genesis baseline.
248 EXPECT_NE(prefilled.meta.root, info.meta.root);
249 // The empty-genesis baseline root is unchanged from the canonical value, confirming that a default (empty)
250 // prefilled-nullifiers list leaves the genesis nullifier-tree root bit-identical to today.
251 EXPECT_EQ(info.meta.root, bb::fr("0x18935581a8ed73d08ffd00386fba55ba6c89f3ab848a76b8fedfa9034cee0454"));
252
253 // The seeded nullifiers are present in the tree.
254 for (const auto& nullifier : prefilled_nullifiers) {
255 assert_leaf_exists<NullifierLeafValue>(ws_prefilled,
257 MerkleTreeId::NULLIFIER_TREE,
258 NullifierLeafValue(nullifier),
259 true);
260 }
261
262 std::filesystem::remove_all(data_dir_prefilled);
263}
264
265TEST_F(WorldStateTest, GetInitialTreeInfoWithPrefilledPublicData)
266{
267 std::string data_dir_prefilled = random_temp_directory();
268 std::filesystem::create_directories(data_dir_prefilled);
269
270 std::vector<PublicDataLeafValue> prefilled_values = { PublicDataLeafValue(1000, 2000),
271 PublicDataLeafValue(3000, 4000) };
272
273 WorldState ws_prefilled(thread_pool_size,
274 data_dir_prefilled,
275 map_size,
276 tree_heights,
277 tree_prefill,
278 prefilled_values,
279 std::vector<bb::fr>(),
280 initial_header_generator_point);
281
282 WorldState ws(thread_pool_size, data_dir, map_size, tree_heights, tree_prefill, initial_header_generator_point);
283
284 {
285 auto prefilled = ws_prefilled.get_tree_info(WorldStateRevision::committed(), MerkleTreeId::NULLIFIER_TREE);
286 auto info = ws.get_tree_info(WorldStateRevision::committed(), MerkleTreeId::NULLIFIER_TREE);
287 EXPECT_EQ(prefilled.meta.size, info.meta.size);
288 EXPECT_EQ(prefilled.meta.depth, info.meta.depth);
289 EXPECT_EQ(prefilled.meta.root, info.meta.root);
290 }
291
292 {
293 auto prefilled = ws_prefilled.get_tree_info(WorldStateRevision::committed(), MerkleTreeId::NOTE_HASH_TREE);
294 auto info = ws.get_tree_info(WorldStateRevision::committed(), MerkleTreeId::NOTE_HASH_TREE);
295 EXPECT_EQ(prefilled.meta.size, info.meta.size);
296 EXPECT_EQ(prefilled.meta.depth, info.meta.depth);
297 EXPECT_EQ(prefilled.meta.root, info.meta.root);
298 }
299
300 {
301 auto prefilled = ws_prefilled.get_tree_info(WorldStateRevision::committed(), MerkleTreeId::PUBLIC_DATA_TREE);
302 auto info = ws.get_tree_info(WorldStateRevision::committed(), MerkleTreeId::PUBLIC_DATA_TREE);
303 EXPECT_EQ(prefilled.meta.size, info.meta.size);
304 EXPECT_EQ(prefilled.meta.depth, info.meta.depth);
305 // Public data tree roots are different.
306 EXPECT_NE(prefilled.meta.root, info.meta.root);
307
308 // Prefilled values are appended at the end.
309 {
310 auto leaf = ws_prefilled.get_indexed_leaf<PublicDataLeafValue>(
311 WorldStateRevision::uncommitted(), MerkleTreeId::PUBLIC_DATA_TREE, prefilled.meta.size - 2);
312 EXPECT_EQ(leaf.value().leaf, prefilled_values[0]);
313 }
314 {
315 auto leaf = ws_prefilled.get_indexed_leaf<PublicDataLeafValue>(
316 WorldStateRevision::uncommitted(), MerkleTreeId::PUBLIC_DATA_TREE, prefilled.meta.size - 1);
317 EXPECT_EQ(leaf.value().leaf, prefilled_values[1]);
318 }
319 }
320
321 {
322 auto prefilled = ws_prefilled.get_tree_info(WorldStateRevision::committed(), MerkleTreeId::ARCHIVE);
323 auto info = ws.get_tree_info(WorldStateRevision::committed(), MerkleTreeId::ARCHIVE);
324 EXPECT_EQ(prefilled.meta.size, info.meta.size);
325 EXPECT_EQ(prefilled.meta.depth, info.meta.depth);
326 // Archive tree roots are different.
327 EXPECT_NE(prefilled.meta.root, info.meta.root);
328 }
329}
330
331TEST_F(WorldStateTest, GetStateReference)
332{
333 WorldState ws(thread_pool_size, data_dir, map_size, tree_heights, tree_prefill, initial_header_generator_point);
334
335 {
337 {
338 auto snapshot = state_ref.at(MerkleTreeId::NULLIFIER_TREE);
339 EXPECT_EQ(
340 snapshot,
341 std::make_pair(bb::fr("0x18935581a8ed73d08ffd00386fba55ba6c89f3ab848a76b8fedfa9034cee0454"), 128UL));
342 }
343
344 {
345 auto snapshot = state_ref.at(MerkleTreeId::NOTE_HASH_TREE);
346 EXPECT_EQ(
347 snapshot,
348 std::make_pair(bb::fr("0x2590f2aab19dd791700b4a43d3f52bb88ef2409a3731da8e848663559202e4c6"), 0UL));
349 }
350
351 {
352 auto snapshot = state_ref.at(MerkleTreeId::PUBLIC_DATA_TREE);
353 EXPECT_EQ(
354 snapshot,
355 std::make_pair(bb::fr("0x1bef38b621017d3c7416663d0cd81369424560710526a3fbaaec13e356b9d084"), 128UL));
356 }
357
358 {
359 auto snapshot = state_ref.at(MerkleTreeId::L1_TO_L2_MESSAGE_TREE);
360 EXPECT_EQ(
361 snapshot,
362 std::make_pair(bb::fr("0x0fef6d80d31109ddb56d6b3f607cbc9c0af0bff3ea0d43e8f278983c64c11f7a"), 0UL));
363 }
364 }
365
366 {
367 ws.append_leaves<bb::fr>(MerkleTreeId::NOTE_HASH_TREE, { 1 });
368
370 {
371 auto snapshot = state_ref.at(MerkleTreeId::NULLIFIER_TREE);
372 EXPECT_EQ(
373 snapshot,
374 std::make_pair(bb::fr("0x18935581a8ed73d08ffd00386fba55ba6c89f3ab848a76b8fedfa9034cee0454"), 128UL));
375 }
376
377 {
378 auto snapshot = state_ref.at(MerkleTreeId::NOTE_HASH_TREE);
379 EXPECT_EQ(
380 snapshot,
381 std::make_pair(bb::fr("0x278449ce779093eaaa1bd9fc608f7ed7b595417710e2baf359e1488650967f47"), 1UL));
382 }
383
384 {
385 auto snapshot = state_ref.at(MerkleTreeId::PUBLIC_DATA_TREE);
386 EXPECT_EQ(
387 snapshot,
388 std::make_pair(bb::fr("0x1bef38b621017d3c7416663d0cd81369424560710526a3fbaaec13e356b9d084"), 128UL));
389 }
390
391 {
392 auto snapshot = state_ref.at(MerkleTreeId::L1_TO_L2_MESSAGE_TREE);
393 EXPECT_EQ(
394 snapshot,
395 std::make_pair(bb::fr("0x0fef6d80d31109ddb56d6b3f607cbc9c0af0bff3ea0d43e8f278983c64c11f7a"), 0UL));
396 }
397 }
398}
399
400TEST_F(WorldStateTest, GetInitialStateReference)
401{
402 WorldState ws(thread_pool_size, data_dir, map_size, tree_heights, tree_prefill, initial_header_generator_point);
403
404 auto before_commit = ws.get_initial_state_reference();
405 ws.append_leaves<bb::fr>(MerkleTreeId::NOTE_HASH_TREE, { 1 });
407 ws.commit(status);
408
409 auto after_commit = ws.get_initial_state_reference();
410
411 EXPECT_EQ(before_commit, after_commit);
412}
413
414TEST_F(WorldStateTest, AppendOnlyTrees)
415{
416 WorldState ws(thread_pool_size, data_dir, map_size, tree_heights, tree_prefill, initial_header_generator_point);
417
418 // the trees that start out empty
419 std::vector tree_ids{ MerkleTreeId::NOTE_HASH_TREE, MerkleTreeId::L1_TO_L2_MESSAGE_TREE };
420
421 for (auto tree_id : tree_ids) {
422 auto initial = ws.get_tree_info(WorldStateRevision::committed(), tree_id);
423 assert_leaf_status<fr>(ws, WorldStateRevision::committed(), tree_id, 0, false);
424
425 ws.append_leaves<fr>(tree_id, { fr(42) });
427 assert_leaf_status<fr>(ws, WorldStateRevision::committed(), tree_id, 0, false);
429
430 auto uncommitted = ws.get_tree_info(WorldStateRevision::uncommitted(), tree_id);
431 // uncommitted state diverges from committed state
432 EXPECT_EQ(uncommitted.meta.size, initial.meta.size + 1);
433 EXPECT_NE(uncommitted.meta.root, initial.meta.root);
434
435 assert_sibling_path(ws, WorldStateRevision::uncommitted(), tree_id, uncommitted.meta.root, fr(42), 0);
436
437 auto committed = ws.get_tree_info(WorldStateRevision::committed(), tree_id);
438 EXPECT_EQ(committed.meta.size, initial.meta.size);
439 EXPECT_EQ(committed.meta.root, initial.meta.root);
440
442 ws.commit(status);
443 assert_leaf_value(ws, WorldStateRevision::committed(), tree_id, 0, fr(42));
444 assert_leaf_index(ws, WorldStateRevision::committed(), tree_id, fr(42), 0);
445
446 auto after_commit = ws.get_tree_info(WorldStateRevision::committed(), tree_id);
447 // commiting updates the committed state
448 EXPECT_EQ(after_commit.meta.size, uncommitted.meta.size);
449 EXPECT_EQ(after_commit.meta.root, uncommitted.meta.root);
450
451 assert_sibling_path(ws, WorldStateRevision::committed(), tree_id, after_commit.meta.root, fr(42), 0);
452
453 ws.append_leaves<fr>(tree_id, { fr(43) });
455 assert_leaf_status<fr>(ws, WorldStateRevision::committed(), tree_id, 1, false);
457
458 auto before_rollback = ws.get_tree_info(WorldStateRevision::uncommitted(), tree_id);
459 EXPECT_EQ(before_rollback.meta.size, after_commit.meta.size + 1);
460 EXPECT_NE(before_rollback.meta.root, after_commit.meta.root);
461
462 ws.rollback();
463 assert_leaf_status<fr>(ws, WorldStateRevision::uncommitted(), tree_id, 1, false);
464 assert_leaf_status<fr>(ws, WorldStateRevision::committed(), tree_id, 1, false);
465
466 auto after_rollback = ws.get_tree_info(WorldStateRevision::committed(), tree_id);
467 // rollback restores the committed state
468 EXPECT_EQ(after_rollback.meta.size, after_commit.meta.size);
469 EXPECT_EQ(after_rollback.meta.root, after_commit.meta.root);
470 }
471}
472
473TEST_F(WorldStateTest, AppendOnlyAllowDuplicates)
474{
475 WorldState ws(thread_pool_size, data_dir, map_size, tree_heights, tree_prefill, initial_header_generator_point);
476
477 // the trees that start out empty
478 std::vector tree_ids{
479 MerkleTreeId::NOTE_HASH_TREE,
480 MerkleTreeId::L1_TO_L2_MESSAGE_TREE,
481 };
482
483 for (auto tree_id : tree_ids) {
484 ws.append_leaves<fr>(tree_id, { fr(42), fr(42) });
485 ws.append_leaves<fr>(tree_id, { fr(42) });
486
490
492 ws.commit(status);
493
494 assert_leaf_value(ws, WorldStateRevision::committed(), tree_id, 0, fr(42));
495 assert_leaf_value(ws, WorldStateRevision::committed(), tree_id, 1, fr(42));
496 assert_leaf_value(ws, WorldStateRevision::committed(), tree_id, 2, fr(42));
497 }
498}
499
501{
502 WorldState ws(thread_pool_size, data_dir, map_size, tree_heights, tree_prefill, initial_header_generator_point);
503 auto tree_id = MerkleTreeId::NULLIFIER_TREE;
504 NullifierLeafValue test_nullifier(142);
505
506 auto predecessor_of_142 =
507 ws.find_low_leaf_index(WorldStateRevision::committed(), tree_id, test_nullifier.get_key());
508 EXPECT_EQ(predecessor_of_142, GetLowIndexedLeafResponse(false, 127UL));
509
510 ws.append_leaves<NullifierLeafValue>(tree_id, { test_nullifier });
511 assert_leaf_value(ws, WorldStateRevision::uncommitted(), tree_id, 128, test_nullifier);
512
514 ws.commit(status);
515
516 auto test_leaf = ws.get_indexed_leaf<NullifierLeafValue>(WorldStateRevision::committed(), tree_id, 128);
517 // at this point 142 should be the biggest leaf so it wraps back to 0
518 EXPECT_TRUE(test_leaf.has_value());
519 EXPECT_EQ(test_leaf.value(), IndexedLeaf(test_nullifier, 0, 0));
520
521 auto predecessor_of_142_again =
522 ws.find_low_leaf_index(WorldStateRevision::committed(), tree_id, test_nullifier.get_key());
523 EXPECT_EQ(predecessor_of_142_again, GetLowIndexedLeafResponse(true, 128UL));
524
525 auto predecessor_of_143 = ws.find_low_leaf_index(WorldStateRevision::committed(), tree_id, 143);
526 EXPECT_EQ(predecessor_of_143,
527 GetLowIndexedLeafResponse(false, 128UL)); // predecessor is going to be nullifier 142 on slot 127
528
532 tree_id,
533 info.meta.root,
534 AppendOnlyHashPolicy::hash(test_leaf.value().get_hash_inputs()),
535 128);
536}
537
538TEST_F(WorldStateTest, NullifierTreeDuplicates)
539{
540 WorldState ws(thread_pool_size, data_dir, map_size, tree_heights, tree_prefill, initial_header_generator_point);
541 auto tree_id = MerkleTreeId::NULLIFIER_TREE;
542 NullifierLeafValue test_nullifier(142);
543
544 ws.append_leaves<NullifierLeafValue>(tree_id, { test_nullifier });
546 ws.commit(status);
547
549 EXPECT_THROW(ws.append_leaves<NullifierLeafValue>(tree_id, { test_nullifier }), std::runtime_error);
551}
552
553TEST_F(WorldStateTest, NullifierBatchInsert)
554{
555 WorldState ws(thread_pool_size, data_dir, map_size, tree_heights, tree_prefill, initial_header_generator_point);
557 MerkleTreeId::NULLIFIER_TREE, { NullifierLeafValue(150), NullifierLeafValue(142), NullifierLeafValue(180) }, 2);
558
559 std::vector<std::pair<NullifierLeafValue, index_t>> expected_sorted_leaves = { { NullifierLeafValue(180), 2 },
560 { NullifierLeafValue(150), 0 },
561 { NullifierLeafValue(142), 1 } };
562 EXPECT_EQ(response.sorted_leaves, expected_sorted_leaves);
563
564 {
565 // insertion happens in descending order, but keeping original indices
566 // first insert leaf 180, at index 130 (tree had size 127, 180 is the third item => 127 + 3)
567 // predecessor will be 127, currently linked to head of the list (0)
568 auto low_leaf = response.low_leaf_witness_data[0];
569 auto expected_low_leaf = IndexedLeaf(NullifierLeafValue(127), 0, fr(0));
570 EXPECT_EQ(low_leaf.index, 127);
571 EXPECT_EQ(low_leaf.leaf, expected_low_leaf);
572 }
573
574 {
575 // insert 150 on position 128 (127 + 1)
576 // predecessor will be 127 linked to 180
577 auto low_leaf = response.low_leaf_witness_data[1];
578 auto expected_low_leaf = IndexedLeaf(NullifierLeafValue(127), 130, fr(180));
579 EXPECT_EQ(low_leaf.index, 127);
580 EXPECT_EQ(low_leaf.leaf, expected_low_leaf);
581 }
582
583 {
584 // finally, insert 142 on position 129(127 + 2)
585 // prededecessor will be 127 linked to 150
586 auto low_leaf = response.low_leaf_witness_data[2];
587 auto expected_low_leaf = IndexedLeaf(NullifierLeafValue(127), 128, fr(150));
588 EXPECT_EQ(low_leaf.index, 127);
589 EXPECT_EQ(low_leaf.leaf, expected_low_leaf);
590 }
591}
592
594{
595 WorldState ws(thread_pool_size, data_dir, map_size, tree_heights, tree_prefill, initial_header_generator_point);
596
597 ws.append_leaves(MerkleTreeId::PUBLIC_DATA_TREE, std::vector{ PublicDataLeafValue(142, 0) });
598 assert_tree_size(ws, WorldStateRevision::uncommitted(), MerkleTreeId::PUBLIC_DATA_TREE, 129);
599
601 WorldStateRevision::uncommitted(), MerkleTreeId::PUBLIC_DATA_TREE, 128);
602 EXPECT_EQ(leaf.value().leaf, PublicDataLeafValue(142, 0));
603
605 // updating insert a dummy leaf
606 assert_tree_size(ws, WorldStateRevision::uncommitted(), MerkleTreeId::PUBLIC_DATA_TREE, 130);
607
609 WorldStateRevision::uncommitted(), MerkleTreeId::PUBLIC_DATA_TREE, 128);
610 EXPECT_EQ(leaf.value().leaf, PublicDataLeafValue(142, 1));
611}
612
613TEST_F(WorldStateTest, CommitsAndRollsBackAllTrees)
614{
615 WorldState ws(thread_pool_size, data_dir, map_size, tree_heights, tree_prefill, initial_header_generator_point);
616
617 ws.append_leaves<fr>(MerkleTreeId::NOTE_HASH_TREE, { fr(42) });
618 ws.append_leaves<fr>(MerkleTreeId::L1_TO_L2_MESSAGE_TREE, { fr(42) });
619 ws.append_leaves<fr>(MerkleTreeId::ARCHIVE, { fr(42) });
620 ws.append_leaves<NullifierLeafValue>(MerkleTreeId::NULLIFIER_TREE, { NullifierLeafValue(142) });
621 ws.append_leaves<PublicDataLeafValue>(MerkleTreeId::PUBLIC_DATA_TREE, { PublicDataLeafValue(142, 1) });
622
624 ws.commit(status);
625
626 assert_leaf_value(ws, WorldStateRevision::committed(), MerkleTreeId::NOTE_HASH_TREE, 0, fr(42));
627 assert_leaf_value(ws, WorldStateRevision::committed(), MerkleTreeId::L1_TO_L2_MESSAGE_TREE, 0, fr(42));
628 assert_leaf_value(ws, WorldStateRevision::committed(), MerkleTreeId::ARCHIVE, 1, fr(42));
629 assert_leaf_value(ws, WorldStateRevision::committed(), MerkleTreeId::NULLIFIER_TREE, 128, NullifierLeafValue(142));
631 ws, WorldStateRevision::committed(), MerkleTreeId::PUBLIC_DATA_TREE, 128, PublicDataLeafValue(142, 1));
632
633 ws.append_leaves<fr>(MerkleTreeId::NOTE_HASH_TREE, { fr(43) });
634 ws.append_leaves<fr>(MerkleTreeId::L1_TO_L2_MESSAGE_TREE, { fr(43) });
635 ws.append_leaves<fr>(MerkleTreeId::ARCHIVE, { fr(43) });
636 ws.append_leaves<NullifierLeafValue>(MerkleTreeId::NULLIFIER_TREE, { NullifierLeafValue(143) });
637 ws.append_leaves<PublicDataLeafValue>(MerkleTreeId::PUBLIC_DATA_TREE, { PublicDataLeafValue(143, 1) });
638
639 ws.rollback();
640
641 assert_leaf_exists(ws, WorldStateRevision::committed(), MerkleTreeId::NOTE_HASH_TREE, fr(43), false);
642 assert_leaf_exists(ws, WorldStateRevision::committed(), MerkleTreeId::L1_TO_L2_MESSAGE_TREE, fr(43), false);
643 assert_leaf_exists(ws, WorldStateRevision::committed(), MerkleTreeId::ARCHIVE, fr(43), false);
645 ws, WorldStateRevision::committed(), MerkleTreeId::NULLIFIER_TREE, NullifierLeafValue(143), false);
647 ws, WorldStateRevision::committed(), MerkleTreeId::PUBLIC_DATA_TREE, PublicDataLeafValue(143, 1), false);
648}
649
650TEST_F(WorldStateTest, SyncExternalBlockFromEmpty)
651{
652 WorldState ws(thread_pool_size, data_dir, map_size, tree_heights, tree_prefill, initial_header_generator_point);
653 StateReference block_state_ref = {
654 { MerkleTreeId::NULLIFIER_TREE,
655 { fr("0x2e2e2d8b72294a440c728a646f01476624063f0b50dcfe293cc0fc26bef9e311"), 129 } },
656 { MerkleTreeId::NOTE_HASH_TREE,
657 { fr("0x25c4ef02ba2bec9490376d5b56b8f1a8e5bcf5ecff91636e76660b68c2a9952d"), 1 } },
658 { MerkleTreeId::PUBLIC_DATA_TREE,
659 { fr("0x1e2d8d1c3ea2449b3e4787d8295df3f137e08b56e891c006b3d93faef56ca3df"), 129 } },
660 { MerkleTreeId::L1_TO_L2_MESSAGE_TREE,
661 { fr("0x22c6f7877092ecea5b313b22515e31f2e1e37349b787da10eff298800e3c7c0c"), 1 } },
662 };
663
665 block_state_ref, fr(1), { 42 }, { 43 }, { NullifierLeafValue(144) }, { { PublicDataLeafValue(145, 1) } });
666 WorldStateStatusSummary expected(1, 0, 1, true);
667 EXPECT_EQ(status.summary, expected);
668
669 assert_leaf_value(ws, WorldStateRevision::committed(), MerkleTreeId::NOTE_HASH_TREE, 0, fr(42));
670 assert_leaf_value(ws, WorldStateRevision::committed(), MerkleTreeId::L1_TO_L2_MESSAGE_TREE, 0, fr(43));
671 assert_leaf_value(ws, WorldStateRevision::committed(), MerkleTreeId::NULLIFIER_TREE, 128, NullifierLeafValue(144));
673 ws, WorldStateRevision::committed(), MerkleTreeId::PUBLIC_DATA_TREE, 128, PublicDataLeafValue(145, 1));
674 assert_leaf_value(ws, WorldStateRevision::committed(), MerkleTreeId::ARCHIVE, 1, fr(1));
675
677 for (const auto& [tree_id, snapshot] : block_state_ref) {
678 EXPECT_EQ(state_ref.at(tree_id), snapshot);
679 }
680
683 WorldStateRevision::committed(), MerkleTreeId::NOTE_HASH_TREE, { 0 }, blockNumbers);
684 EXPECT_EQ(blockNumbers.size(), 1);
685 EXPECT_EQ(blockNumbers[0], 1);
686
688 WorldStateRevision{ .forkId = CANONICAL_FORK_ID, .blockNumber = 2, .includeUncommitted = false },
689 MerkleTreeId::NOTE_HASH_TREE,
690 { 0 },
691 blockNumbers),
692 std::runtime_error);
693}
694
695TEST_F(WorldStateTest, SyncBlockRejectsDivergentArchiveRoot)
696{
697 StateReference block_state_ref = {
698 { MerkleTreeId::NULLIFIER_TREE,
699 { fr("0x2e2e2d8b72294a440c728a646f01476624063f0b50dcfe293cc0fc26bef9e311"), 129 } },
700 { MerkleTreeId::NOTE_HASH_TREE,
701 { fr("0x25c4ef02ba2bec9490376d5b56b8f1a8e5bcf5ecff91636e76660b68c2a9952d"), 1 } },
702 { MerkleTreeId::PUBLIC_DATA_TREE,
703 { fr("0x1e2d8d1c3ea2449b3e4787d8295df3f137e08b56e891c006b3d93faef56ca3df"), 129 } },
704 { MerkleTreeId::L1_TO_L2_MESSAGE_TREE,
705 { fr("0x22c6f7877092ecea5b313b22515e31f2e1e37349b787da10eff298800e3c7c0c"), 1 } },
706 };
707
708 // Learn the canonical previous (genesis) and resulting archive roots from an untracked sync.
709 bb::fr previous_root;
710 bb::fr resulting_root;
711 {
712 WorldState scratch(
713 thread_pool_size, data_dir, map_size, tree_heights, tree_prefill, initial_header_generator_point);
714 previous_root = scratch.get_tree_info(WorldStateRevision::committed(), MerkleTreeId::ARCHIVE).meta.root;
715 scratch.sync_block(
716 block_state_ref, fr(1), { 42 }, { 43 }, { NullifierLeafValue(144) }, { { PublicDataLeafValue(145, 1) } });
717 resulting_root = scratch.get_tree_info(WorldStateRevision::committed(), MerkleTreeId::ARCHIVE).meta.root;
718 }
719
720 std::string data_dir2 = random_temp_directory();
721 std::filesystem::create_directories(data_dir2);
722 WorldState ws(thread_pool_size, data_dir2, map_size, tree_heights, tree_prefill, initial_header_generator_point);
723
724 // A wrong previous archive root is rejected before any leaves are appended.
725 EXPECT_THROW(ws.sync_block(block_state_ref,
726 fr(1),
727 { 42 },
728 { 43 },
729 { NullifierLeafValue(144) },
730 { { PublicDataLeafValue(145, 1) } },
731 resulting_root,
732 previous_root + fr(1)),
733 std::runtime_error);
734
735 // A wrong resulting archive root is rejected before commit.
736 EXPECT_THROW(ws.sync_block(block_state_ref,
737 fr(1),
738 { 42 },
739 { 43 },
740 { NullifierLeafValue(144) },
741 { { PublicDataLeafValue(145, 1) } },
742 resulting_root + fr(1),
743 previous_root),
744 std::runtime_error);
745
746 // Both rejections rolled back cleanly: world state is still at the genesis archive root.
747 EXPECT_EQ(ws.get_tree_info(WorldStateRevision::committed(), MerkleTreeId::ARCHIVE).meta.root, previous_root);
748
749 // Matching roots are accepted and advance the chain.
750 WorldStateStatusFull status = ws.sync_block(block_state_ref,
751 fr(1),
752 { 42 },
753 { 43 },
754 { NullifierLeafValue(144) },
755 { { PublicDataLeafValue(145, 1) } },
756 resulting_root,
757 previous_root);
758 WorldStateStatusSummary expected(1, 0, 1, true);
759 EXPECT_EQ(status.summary, expected);
760 EXPECT_EQ(ws.get_tree_info(WorldStateRevision::committed(), MerkleTreeId::ARCHIVE).meta.root, resulting_root);
761}
762
763TEST_F(WorldStateTest, SyncBlockFromDirtyState)
764{
765 WorldState ws(thread_pool_size, data_dir, map_size, tree_heights, tree_prefill, initial_header_generator_point);
766 StateReference block_state_ref = {
767 { MerkleTreeId::NULLIFIER_TREE,
768 { fr("0x2e2e2d8b72294a440c728a646f01476624063f0b50dcfe293cc0fc26bef9e311"), 129 } },
769 { MerkleTreeId::NOTE_HASH_TREE,
770 { fr("0x25c4ef02ba2bec9490376d5b56b8f1a8e5bcf5ecff91636e76660b68c2a9952d"), 1 } },
771 { MerkleTreeId::PUBLIC_DATA_TREE,
772 { fr("0x1e2d8d1c3ea2449b3e4787d8295df3f137e08b56e891c006b3d93faef56ca3df"), 129 } },
773 { MerkleTreeId::L1_TO_L2_MESSAGE_TREE,
774 { fr("0x22c6f7877092ecea5b313b22515e31f2e1e37349b787da10eff298800e3c7c0c"), 1 } },
775 };
776
777 ws.append_leaves<fr>(MerkleTreeId::NOTE_HASH_TREE, { fr(142) });
778 ws.append_leaves<fr>(MerkleTreeId::L1_TO_L2_MESSAGE_TREE, { fr(143) });
779 ws.append_leaves<NullifierLeafValue>(MerkleTreeId::NULLIFIER_TREE, { NullifierLeafValue(142) });
780 ws.append_leaves<PublicDataLeafValue>(MerkleTreeId::PUBLIC_DATA_TREE, { PublicDataLeafValue(142, 1) });
781
782 auto uncommitted_state_ref = ws.get_state_reference(WorldStateRevision::uncommitted());
783 for (const auto& [tree_id, snapshot] : block_state_ref) {
784 EXPECT_NE(uncommitted_state_ref.at(tree_id), snapshot);
785 }
786
788 block_state_ref, fr(1), { 42 }, { 43 }, { NullifierLeafValue(144) }, { { PublicDataLeafValue(145, 1) } });
789 WorldStateStatusSummary expected{ 1, 0, 1, true };
790 EXPECT_EQ(status.summary, expected);
791
792 assert_leaf_value(ws, WorldStateRevision::committed(), MerkleTreeId::NOTE_HASH_TREE, 0, fr(42));
793 assert_leaf_value(ws, WorldStateRevision::committed(), MerkleTreeId::L1_TO_L2_MESSAGE_TREE, 0, fr(43));
794 assert_leaf_value(ws, WorldStateRevision::committed(), MerkleTreeId::NULLIFIER_TREE, 128, NullifierLeafValue(144));
796 ws, WorldStateRevision::committed(), MerkleTreeId::PUBLIC_DATA_TREE, 128, PublicDataLeafValue(145, 1));
797 assert_leaf_value(ws, WorldStateRevision::committed(), MerkleTreeId::ARCHIVE, 1, fr(1));
798
800 for (const auto& [tree_id, snapshot] : block_state_ref) {
801 EXPECT_EQ(state_ref.at(tree_id), snapshot);
802 }
803}
804
805TEST_F(WorldStateTest, SyncCurrentBlock)
806{
807 WorldState ws(thread_pool_size, data_dir, map_size, tree_heights, tree_prefill, initial_header_generator_point);
808 bb::fr block_hash(1);
809 StateReference block_state_ref = {
810 { MerkleTreeId::NULLIFIER_TREE,
811 { fr("0x2e2e2d8b72294a440c728a646f01476624063f0b50dcfe293cc0fc26bef9e311"), 129 } },
812 { MerkleTreeId::NOTE_HASH_TREE,
813 { fr("0x25c4ef02ba2bec9490376d5b56b8f1a8e5bcf5ecff91636e76660b68c2a9952d"), 1 } },
814 { MerkleTreeId::PUBLIC_DATA_TREE,
815 { fr("0x1e2d8d1c3ea2449b3e4787d8295df3f137e08b56e891c006b3d93faef56ca3df"), 129 } },
816 { MerkleTreeId::L1_TO_L2_MESSAGE_TREE,
817 { fr("0x22c6f7877092ecea5b313b22515e31f2e1e37349b787da10eff298800e3c7c0c"), 1 } },
818 };
819
820 ws.append_leaves<fr>(MerkleTreeId::NOTE_HASH_TREE, { 42 });
821 ws.append_leaves<fr>(MerkleTreeId::L1_TO_L2_MESSAGE_TREE, { 43 });
822 ws.append_leaves<NullifierLeafValue>(MerkleTreeId::NULLIFIER_TREE, { NullifierLeafValue(144) });
823 ws.append_leaves<PublicDataLeafValue>(MerkleTreeId::PUBLIC_DATA_TREE, { PublicDataLeafValue(145, 1) });
824 ws.append_leaves<fr>(MerkleTreeId::ARCHIVE, { block_hash });
825
826 auto uncommitted_state_ref = ws.get_state_reference(WorldStateRevision::uncommitted());
827 for (const auto& [tree_id, snapshot] : block_state_ref) {
828 EXPECT_EQ(uncommitted_state_ref.at(tree_id), snapshot);
829 }
830
832 block_state_ref, fr(1), { 42 }, { 43 }, { NullifierLeafValue(144) }, { { PublicDataLeafValue(145, 1) } });
833 WorldStateStatusSummary expected{ 1, 0, 1, true };
834 EXPECT_EQ(status.summary, expected);
835
836 assert_leaf_value(ws, WorldStateRevision::uncommitted(), MerkleTreeId::ARCHIVE, 1, fr(1));
837
839 for (const auto& [tree_id, snapshot] : block_state_ref) {
840 EXPECT_EQ(state_ref.at(tree_id), snapshot);
841 }
842}
843
844TEST_F(WorldStateTest, RejectSyncBlockWithBadPublicWriteBatches)
845{
846 WorldState ws(thread_pool_size, data_dir, map_size, tree_heights, tree_prefill, initial_header_generator_point);
847 StateReference block_state_ref = {
848 { MerkleTreeId::NULLIFIER_TREE,
849 { fr("0x2e2e2d8b72294a440c728a646f01476624063f0b50dcfe293cc0fc26bef9e311"), 129 } },
850 { MerkleTreeId::NOTE_HASH_TREE,
851 { fr("0x25c4ef02ba2bec9490376d5b56b8f1a8e5bcf5ecff91636e76660b68c2a9952d"), 1 } },
852 { MerkleTreeId::PUBLIC_DATA_TREE,
853 { fr("0x1e2d8d1c3ea2449b3e4787d8295df3f137e08b56e891c006b3d93faef56ca3df"), 129 } },
854 { MerkleTreeId::L1_TO_L2_MESSAGE_TREE,
855 { fr("0x22c6f7877092ecea5b313b22515e31f2e1e37349b787da10eff298800e3c7c0c"), 1 } },
856 };
857
858 auto sync = [&]() {
859 return ws.sync_block(block_state_ref,
860 fr(1),
861 { 42 },
862 { 43 },
863 { NullifierLeafValue(144) },
864 // this should be rejected because we can't have duplicate slots in the same batch
865 { { PublicDataLeafValue(145, 1), PublicDataLeafValue(145, 2) } });
866 };
867
868 EXPECT_THROW(sync(), std::runtime_error);
869}
870
871TEST_F(WorldStateTest, RejectSyncBlockWithInvalidStateRef)
872{
873 WorldState ws(thread_pool_size, data_dir, map_size, tree_heights, tree_prefill, initial_header_generator_point);
874 StateReference block_state_ref = {
875 { MerkleTreeId::NULLIFIER_TREE,
876 { fr("0x2e2e2d8b72294a440c728a646f01476624063f0b50dcfe293cc0fc26bef9e311"), 129 } },
877 { MerkleTreeId::NOTE_HASH_TREE,
878 { fr("0x25c4ef02ba2bec9490376d5b56b8f1a8e5bcf5ecff91636e76660b68c2a9952d"), 1 } },
879 { MerkleTreeId::PUBLIC_DATA_TREE,
880 { fr("0x1e2d8d1c3ea2449b3e4787d8295df3f137e08b56e891c006b3d93faef56ca3df"), 129 } },
881 { MerkleTreeId::L1_TO_L2_MESSAGE_TREE,
882 { fr("0x22c6f7877092ecea5b313b22515e31f2e1e37349b787da10eff298800e3c7c0c"), 1 } },
883 };
884
885 auto sync = [&]() {
886 return ws.sync_block(block_state_ref,
887 fr(1),
888 { 42 },
889 { 43 },
890 { NullifierLeafValue(144) },
891 // this should be rejected because public data tree root will not match the state ref above
892 // (state ref above is for slot[145]=1, not slot[145]=2)
893 { { PublicDataLeafValue(145, 2) } });
894 };
895
896 EXPECT_THROW(sync(), std::runtime_error);
897}
898
899TEST_F(WorldStateTest, SyncEmptyBlock)
900{
901 WorldState ws(thread_pool_size, data_dir, map_size, tree_heights, tree_prefill, initial_header_generator_point);
903 ws.sync_block(block_state_ref, fr(1), {}, {}, {}, {});
905 EXPECT_EQ(block_state_ref, after_sync);
906
908 ws.find_leaf_indices<fr>(WorldStateRevision::committed(), MerkleTreeId::ARCHIVE, { fr(1) }, indices);
910 EXPECT_EQ(indices, expected);
911}
912
913TEST_F(WorldStateTest, ForkingAtBlock0SameState)
914{
915 WorldState ws(thread_pool_size, data_dir, map_size, tree_heights, tree_prefill, initial_header_generator_point);
916 auto fork_id = ws.create_fork(0);
917
918 assert_fork_state_unchanged(ws, fork_id, false);
919 assert_fork_state_unchanged(ws, fork_id, true);
920}
921
922TEST_F(WorldStateTest, ForkingAtBlock0AndAdvancingFork)
923{
924 WorldState ws(thread_pool_size, data_dir, map_size, tree_heights, tree_prefill, initial_header_generator_point);
925 auto fork_id = ws.create_fork(0);
926
927 auto canonical_archive_state_before = ws.get_tree_info(WorldStateRevision::uncommitted(), MerkleTreeId::ARCHIVE);
928 auto fork_archive_state_before = ws.get_tree_info(
930 .forkId = fork_id,
931 .includeUncommitted = true,
932 },
933 MerkleTreeId::ARCHIVE);
934
935 ws.append_leaves<bb::fr>(MerkleTreeId::ARCHIVE, { fr(1) }, fork_id);
936
937 auto canonical_archive_state_after = ws.get_tree_info(WorldStateRevision::uncommitted(), MerkleTreeId::ARCHIVE);
938 auto fork_archive_state_after = ws.get_tree_info(
940 .forkId = fork_id,
941 .includeUncommitted = true,
942 },
943 MerkleTreeId::ARCHIVE);
944
945 EXPECT_EQ(canonical_archive_state_after.meta, canonical_archive_state_before.meta);
946 EXPECT_EQ(fork_archive_state_before.meta, canonical_archive_state_before.meta);
947 EXPECT_NE(fork_archive_state_after.meta, fork_archive_state_before.meta);
948}
949
950TEST_F(WorldStateTest, ForkingAtBlock0AndAdvancingCanonicalState)
951{
952 WorldState ws(thread_pool_size, data_dir, map_size, tree_heights, tree_prefill, initial_header_generator_point);
953 auto fork_id = ws.create_fork(0);
954
955 auto canonical_archive_state_before = ws.get_tree_info(WorldStateRevision::uncommitted(), MerkleTreeId::ARCHIVE);
956 auto fork_archive_state_before_insert = ws.get_tree_info(
958 .forkId = fork_id,
959 .includeUncommitted = true,
960 },
961 MerkleTreeId::ARCHIVE);
962
963 // fork the state
964 ws.append_leaves<bb::fr>(MerkleTreeId::ARCHIVE, { fr(1) });
965 ws.append_leaves<bb::fr>(MerkleTreeId::ARCHIVE, { fr(2) }, fork_id);
966
967 auto canonical_archive_state_after_insert =
968 ws.get_tree_info(WorldStateRevision::uncommitted(), MerkleTreeId::ARCHIVE);
969 auto fork_archive_state_after_insert = ws.get_tree_info(
971 .forkId = fork_id,
972 .includeUncommitted = true,
973 },
974 MerkleTreeId::ARCHIVE);
975
976 EXPECT_EQ(fork_archive_state_before_insert.meta, canonical_archive_state_before.meta);
977
978 EXPECT_NE(canonical_archive_state_after_insert.meta, canonical_archive_state_before.meta);
979 EXPECT_NE(fork_archive_state_after_insert.meta, fork_archive_state_before_insert.meta);
980 EXPECT_NE(fork_archive_state_after_insert.meta, canonical_archive_state_after_insert.meta);
981
983 ws.commit(status);
984 auto canonical_archive_state_after_commit =
985 ws.get_tree_info(WorldStateRevision::committed(), MerkleTreeId::ARCHIVE);
986 auto fork_archive_state_after_commit = ws.get_tree_info(
988 .forkId = fork_id,
989 .includeUncommitted = false,
990 },
991 MerkleTreeId::ARCHIVE);
992
993 // committed fork state should match the state before fork had been modified
994 EXPECT_EQ(fork_archive_state_after_commit.meta.size, fork_archive_state_before_insert.meta.size);
995 EXPECT_EQ(fork_archive_state_after_commit.meta.root, fork_archive_state_before_insert.meta.root);
996 // canonical state before commit should match state after commit
997 // EXPECT_EQ(canonical_archive_state_after_commit.meta, canonical_archive_state_after_insert.meta);
998 EXPECT_EQ(canonical_archive_state_after_commit.meta.root, canonical_archive_state_after_insert.meta.root);
999 EXPECT_EQ(canonical_archive_state_after_commit.meta.size, canonical_archive_state_after_insert.meta.size);
1000
1001 // canonical should have value 1 as the first leaf (committed state)
1002 assert_leaf_value<bb::fr>(ws, WorldStateRevision{ .includeUncommitted = false }, MerkleTreeId::ARCHIVE, 1, 1);
1003 // fork should still have value 2 as the first leaf (uncommitted)
1004 assert_leaf_value<bb::fr>(
1005 ws, WorldStateRevision{ .forkId = fork_id, .includeUncommitted = true }, MerkleTreeId::ARCHIVE, 1, 2);
1006}
1007
1008TEST_F(WorldStateTest, BuildsABlockInAFork)
1009{
1010 WorldState ws(thread_pool_size, data_dir, map_size, tree_heights, tree_prefill, initial_header_generator_point);
1011 auto fork_id = ws.create_fork(0);
1012
1013 ws.append_leaves<bb::fr>(MerkleTreeId::NOTE_HASH_TREE, { 42 }, fork_id);
1014 ws.append_leaves<bb::fr>(MerkleTreeId::L1_TO_L2_MESSAGE_TREE, { 43 }, fork_id);
1015 ws.batch_insert_indexed_leaves<NullifierLeafValue>(MerkleTreeId::NULLIFIER_TREE, { { 129 } }, 0, fork_id);
1016 ws.batch_insert_indexed_leaves<PublicDataLeafValue>(MerkleTreeId::PUBLIC_DATA_TREE, { { 129, 1 } }, 0, fork_id);
1017
1018 auto fork_state_ref = ws.get_state_reference(WorldStateRevision{ .forkId = fork_id, .includeUncommitted = true });
1019
1020 ws.update_archive(fork_state_ref, { 1 }, fork_id);
1021 auto fork_archive =
1022 ws.get_tree_info(WorldStateRevision{ .forkId = fork_id, .includeUncommitted = true }, MerkleTreeId::ARCHIVE);
1023
1024 ws.delete_fork(fork_id);
1025
1026 ws.sync_block(fork_state_ref, { 1 }, { 42 }, { 43 }, { { 129 } }, { { { 129, 1 } } });
1027
1028 EXPECT_EQ(fork_state_ref, ws.get_state_reference(WorldStateRevision::committed()));
1029}
1030
1031TEST_F(WorldStateTest, GetBlockForIndex)
1032{
1033 WorldState ws(thread_pool_size, data_dir, map_size, tree_heights, tree_prefill, initial_header_generator_point);
1034 // bb::fr block_hash(1);
1035 StateReference block_state_ref = {
1036 { MerkleTreeId::NULLIFIER_TREE,
1037 { fr("0x2e2e2d8b72294a440c728a646f01476624063f0b50dcfe293cc0fc26bef9e311"), 129 } },
1038 { MerkleTreeId::NOTE_HASH_TREE,
1039 { fr("0x25c4ef02ba2bec9490376d5b56b8f1a8e5bcf5ecff91636e76660b68c2a9952d"), 1 } },
1040 { MerkleTreeId::PUBLIC_DATA_TREE,
1041 { fr("0x1e2d8d1c3ea2449b3e4787d8295df3f137e08b56e891c006b3d93faef56ca3df"), 129 } },
1042 { MerkleTreeId::L1_TO_L2_MESSAGE_TREE,
1043 { fr("0x22c6f7877092ecea5b313b22515e31f2e1e37349b787da10eff298800e3c7c0c"), 1 } },
1044 };
1045
1046 WorldStateStatusFull status = ws.sync_block(
1047 block_state_ref, fr(1), { 42 }, { 43 }, { NullifierLeafValue(144) }, { { PublicDataLeafValue(145, 1) } });
1048 WorldStateStatusSummary expected{ 1, 0, 1, true };
1049 EXPECT_EQ(status.summary, expected);
1050
1052
1054 MerkleTreeId::NULLIFIER_TREE,
1055 MerkleTreeId::NOTE_HASH_TREE,
1056 MerkleTreeId::PUBLIC_DATA_TREE,
1057 MerkleTreeId::L1_TO_L2_MESSAGE_TREE,
1058 };
1059
1060 for (const auto& id : tree_ids) {
1063 WorldStateRevision::committed(), id, { state_ref[id].second - 1 }, blockNumbers);
1064
1065 EXPECT_EQ(blockNumbers.size(), 1);
1066 EXPECT_TRUE(blockNumbers[0].has_value());
1067 EXPECT_EQ(blockNumbers[0].value(), 1);
1068 }
1069}
1070
1071// Demonstrates the bug: syncing an empty block with a bogus block_header_hash succeeds when the optional
1072// expected-archive-root arguments are omitted. The 4-tree state-ref check passes (nothing changed), but
1073// the wrong hash is committed to the ARCHIVE, silently diverging from the canonical chain.
1074TEST_F(WorldStateTest, SyncEmptyBlockAcceptsBogusHashWithoutArchiveCheck)
1075{
1076 // Learn the canonical previous and resulting archive roots from an empty-block sync on a scratch instance.
1077 bb::fr previous_archive_root;
1078 bb::fr canonical_archive_root;
1079 {
1080 WorldState scratch(
1081 thread_pool_size, data_dir, map_size, tree_heights, tree_prefill, initial_header_generator_point);
1082 previous_archive_root = scratch.get_tree_info(WorldStateRevision::committed(), MerkleTreeId::ARCHIVE).meta.root;
1084 scratch.sync_block(genesis_state_ref, fr(1), {}, {}, {}, {});
1085 canonical_archive_root =
1086 scratch.get_tree_info(WorldStateRevision::committed(), MerkleTreeId::ARCHIVE).meta.root;
1087 }
1088
1089 std::string data_dir2 = random_temp_directory();
1090 std::filesystem::create_directories(data_dir2);
1091 WorldState ws(thread_pool_size, data_dir2, map_size, tree_heights, tree_prefill, initial_header_generator_point);
1092
1094
1095 // Sync an empty block using a bogus hash — no expected-archive-root arguments provided.
1096 EXPECT_NO_THROW(ws.sync_block(genesis_state_ref, fr(42), {}, {}, {}, {}));
1097
1098 bb::fr actual_archive_root = ws.get_tree_info(WorldStateRevision::committed(), MerkleTreeId::ARCHIVE).meta.root;
1099
1100 // The bogus hash committed successfully, so the resulting archive root differs from the canonical one.
1101 EXPECT_NE(actual_archive_root, canonical_archive_root);
1102 EXPECT_NE(actual_archive_root, previous_archive_root);
1103}
1104
1105// Demonstrates the fix: supplying the canonical expected archive roots causes sync_block to reject the bogus
1106// block_header_hash for an empty block, and rolls back cleanly.
1107TEST_F(WorldStateTest, SyncEmptyBlockRejectsBogusHashWhenArchiveRootsAreChecked)
1108{
1109 // Learn the canonical previous and resulting archive roots from an empty-block sync on a scratch instance.
1110 bb::fr previous_archive_root;
1111 bb::fr canonical_archive_root;
1112 {
1113 WorldState scratch(
1114 thread_pool_size, data_dir, map_size, tree_heights, tree_prefill, initial_header_generator_point);
1115 previous_archive_root = scratch.get_tree_info(WorldStateRevision::committed(), MerkleTreeId::ARCHIVE).meta.root;
1117 scratch.sync_block(genesis_state_ref, fr(1), {}, {}, {}, {});
1118 canonical_archive_root =
1119 scratch.get_tree_info(WorldStateRevision::committed(), MerkleTreeId::ARCHIVE).meta.root;
1120 }
1121
1122 std::string data_dir2 = random_temp_directory();
1123 std::filesystem::create_directories(data_dir2);
1124 WorldState ws(thread_pool_size, data_dir2, map_size, tree_heights, tree_prefill, initial_header_generator_point);
1125
1127
1128 // Sync an empty block using a bogus hash, but supply the canonical archive roots for validation.
1129 // The resulting archive root will not match canonical_archive_root, so this must throw.
1130 EXPECT_THROW(
1131 ws.sync_block(genesis_state_ref, fr(42), {}, {}, {}, {}, canonical_archive_root, previous_archive_root),
1132 std::runtime_error);
1133
1134 // The committed archive root must be unchanged (rollback was clean).
1135 EXPECT_EQ(ws.get_tree_info(WorldStateRevision::committed(), MerkleTreeId::ARCHIVE).meta.root,
1136 previous_archive_root);
1137}
void TearDown() override
void SetUp() override
static std::string data_dir
uint32_t initial_header_generator_point
std::unordered_map< MerkleTreeId, index_t > tree_prefill
std::unordered_map< MerkleTreeId, uint32_t > tree_heights
Implements a parallelized batch insertion indexed tree Accepts template argument of the type of store...
Holds the Merkle trees responsible for storing the state of the Aztec protocol.
BatchInsertionResult< T > batch_insert_indexed_leaves(MerkleTreeId tree_id, const std::vector< T > &leaves, uint32_t subtree_depth, Fork::Id fork_id=CANONICAL_FORK_ID)
Batch inserts a set of leaves into an indexed Merkle Tree.
void append_leaves(MerkleTreeId tree_id, const std::vector< T > &leaves, Fork::Id fork_id=CANONICAL_FORK_ID)
Appends a set of leaves to an existing Merkle Tree.
StateReference get_initial_state_reference() const
Gets the initial state reference for all the trees in the world state.
std::optional< crypto::merkle_tree::IndexedLeaf< T > > get_indexed_leaf(const WorldStateRevision &revision, MerkleTreeId tree_id, index_t leaf_index) const
Get the leaf preimage object.
crypto::merkle_tree::TreeMetaResponse get_tree_info(const WorldStateRevision &revision, MerkleTreeId tree_id) const
Get tree metadata for a particular tree.
std::pair< bool, std::string > commit(WorldStateStatusFull &status)
Commits the current state of the world state.
void get_block_numbers_for_leaf_indices(const WorldStateRevision &revision, MerkleTreeId tree_id, const std::vector< index_t > &leafIndices, std::vector< std::optional< block_number_t > > &blockNumbers) const
StateReference get_state_reference(const WorldStateRevision &revision) const
Gets the state reference for all the trees in the world state.
void update_public_data(const crypto::merkle_tree::PublicDataLeafValue &new_value, Fork::Id fork_id=CANONICAL_FORK_ID)
Updates a leaf in an existing Merkle Tree.
WorldStateStatusFull sync_block(const StateReference &block_state_ref, const bb::fr &block_header_hash, const std::vector< bb::fr > &notes, const std::vector< bb::fr > &l1_to_l2_messages, const std::vector< crypto::merkle_tree::NullifierLeafValue > &nullifiers, const std::vector< crypto::merkle_tree::PublicDataLeafValue > &public_writes, const std::optional< bb::fr > &expected_archive_root=std::nullopt, const std::optional< bb::fr > &expected_previous_archive_root=std::nullopt)
void rollback()
Rolls back any uncommitted changes made to the world state.
void delete_fork(const uint64_t &forkId)
uint64_t create_fork(const std::optional< block_number_t > &blockNumber)
crypto::merkle_tree::fr_sibling_path get_sibling_path(const WorldStateRevision &revision, MerkleTreeId tree_id, index_t leaf_index) const
Get the sibling path object for a leaf in a tree.
void find_leaf_indices(const WorldStateRevision &revision, MerkleTreeId tree_id, const std::vector< T > &leaves, std::vector< std::optional< index_t > > &indices, index_t start_index=0) const
Finds the index of a leaf in a tree.
std::optional< T > get_leaf(const WorldStateRevision &revision, MerkleTreeId tree_id, index_t leaf_index) const
Gets the value of a leaf in a tree.
crypto::merkle_tree::GetLowIndexedLeafResponse find_low_leaf_index(const WorldStateRevision &revision, MerkleTreeId tree_id, const bb::fr &leaf_key) const
Finds the leaf that would have its nextIdx/nextValue fields modified if the target leaf were to be in...
void update_archive(const StateReference &block_state_ref, const bb::fr &block_header_hash, Fork::Id fork_id=CANONICAL_FORK_ID)
Updates the archive tree with a new block.
#define info(...)
Definition log.hpp:93
IndexedTreeLeafData low_leaf
std::string random_temp_directory()
Definition fixtures.hpp:37
std::unordered_map< MerkleTreeId, TreeStateReference > StateReference
Definition types.hpp:33
TEST_F(IPATest, ChallengesAreZero)
Definition ipa.test.cpp:160
field< Bn254FrParams > fr
Definition fr.hpp:155
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
static bb::fr hash_pair(const bb::fr &lhs, const bb::fr &rhs)
static fr hash(const std::vector< fr > &inputs)
Definition hash.hpp:14
static constexpr field zero()
static WorldStateRevision committed()
Definition types.hpp:50
static WorldStateRevision uncommitted()
Definition types.hpp:51
WorldStateStatusSummary summary
Definition types.hpp:222
void assert_fork_state_unchanged(const WorldState &ws, Fork::Id forkId, bool includeUncommitted, const std::vector< MerkleTreeId > &trees={ MerkleTreeId::NULLIFIER_TREE, MerkleTreeId::NOTE_HASH_TREE, MerkleTreeId::PUBLIC_DATA_TREE, MerkleTreeId::L1_TO_L2_MESSAGE_TREE, MerkleTreeId::ARCHIVE })
void assert_leaf_value(const WorldState &ws, WorldStateRevision revision, MerkleTreeId tree_id, index_t leaf_index, const Leaf &expected_value)
void assert_leaf_index(const WorldState &ws, WorldStateRevision revision, MerkleTreeId tree_id, const Leaf &value, index_t expected_index)
void assert_tree_size(const WorldState &ws, WorldStateRevision revision, MerkleTreeId tree_id, size_t expected_size)
void assert_sibling_path(const WorldState &ws, WorldStateRevision revision, MerkleTreeId tree_id, fr root, fr leaf, index_t index)
void assert_leaf_exists(const WorldState &ws, WorldStateRevision revision, MerkleTreeId tree_id, const Leaf &expected_value, bool exists)
void assert_leaf_status(const WorldState &ws, WorldStateRevision revision, MerkleTreeId tree_id, index_t leaf_index, bool exists)