Skip to content

Commit f515cb2

Browse files
committed
test(fast-inbox): cover block-root message appends at a non-zero tree index (A-1374)
1 parent 626d8e5 commit f515cb2

3 files changed

Lines changed: 116 additions & 8 deletions

File tree

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
use super::TestBuilder;
2+
use types::constants::{PRIVATE_TX_BASE_ROLLUP_VK_INDEX, PUBLIC_TX_BASE_ROLLUP_VK_INDEX};
3+
4+
global IS_FIRST_BLOCK_ROOT: bool = true;
5+
6+
// A start index that is a right child at some levels and a left child at others, so the append has to merge the batch
7+
// into a real frontier rather than drop an aligned subtree into an empty slot.
8+
global NON_ALIGNED_START_INDEX: u32 = 5;
9+
10+
fn builder_at_non_aligned_index(is_first_block_root: bool) -> TestBuilder {
11+
TestBuilder::new_at_l1_to_l2_index(
12+
PRIVATE_TX_BASE_ROLLUP_VK_INDEX,
13+
1,
14+
PUBLIC_TX_BASE_ROLLUP_VK_INDEX,
15+
1,
16+
is_first_block_root,
17+
NON_ALIGNED_START_INDEX,
18+
)
19+
}
20+
21+
#[test]
22+
fn first_block_appends_at_non_aligned_index() {
23+
let builder = builder_at_non_aligned_index(IS_FIRST_BLOCK_ROOT);
24+
let pi = builder.execute();
25+
builder.assert_expected_public_inputs(pi);
26+
}
27+
28+
#[test]
29+
fn non_first_block_appends_at_non_aligned_index() {
30+
let builder = builder_at_non_aligned_index(!IS_FIRST_BLOCK_ROOT);
31+
let pi = builder.execute();
32+
builder.assert_expected_public_inputs(pi);
33+
}
34+
35+
#[test]
36+
fn unpinned_frontier_hint_lane_is_ignored() {
37+
// Index 5 is a left child at level 1, so the snapshot root cannot pin that hint lane. The append overwrites it
38+
// with the batch's own node before anything reads it, so a wrong value there must not change the result.
39+
let mut builder = builder_at_non_aligned_index(!IS_FIRST_BLOCK_ROOT);
40+
builder.l1_to_l2_message_frontier_hint[1] += 1;
41+
42+
let pi = builder.execute();
43+
builder.assert_expected_public_inputs(pi);
44+
}
45+
46+
#[test(should_fail_with = "Frontier hint does not match the snapshot root")]
47+
fn corrupted_pinned_frontier_hint_lane_fails() {
48+
// Index 5 is a right child at level 0, so that hint lane is pinned by the snapshot root and cannot be tampered
49+
// with. Note this is only testable at a non-zero start index: appending into an empty tree pins no lane at all.
50+
let mut builder = builder_at_non_aligned_index(!IS_FIRST_BLOCK_ROOT);
51+
builder.l1_to_l2_message_frontier_hint[0] += 1;
52+
53+
builder.execute_and_fail();
54+
}

noir-projects/noir-protocol-circuits/crates/rollup-lib/src/block_root/tests/mod.nr

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
mod child_proof_vk_tests;
22
mod consecutive_rollups_tests;
33
mod failures_tests;
4+
mod message_bundle_tests;
45
mod rollup_structure_tests;
56

67
use crate::{
@@ -85,6 +86,26 @@ impl TestBuilder {
8586
right_rollup_vk_index: u32,
8687
num_right_txs: u16,
8788
is_first_block_root: bool,
89+
) -> Self {
90+
Self::new_at_l1_to_l2_index(
91+
left_rollup_vk_index,
92+
num_left_txs,
93+
right_rollup_vk_index,
94+
num_right_txs,
95+
is_first_block_root,
96+
0,
97+
)
98+
}
99+
100+
/// Same as `new`, but the block's bundle is appended into an l1-to-l2 tree that already holds
101+
/// `l1_to_l2_start_index` leaves instead of into an empty one.
102+
pub fn new_at_l1_to_l2_index(
103+
left_rollup_vk_index: u32,
104+
num_left_txs: u16,
105+
right_rollup_vk_index: u32,
106+
num_right_txs: u16,
107+
is_first_block_root: bool,
108+
l1_to_l2_start_index: u32,
88109
) -> Self {
89110
Self::validate_input_vk_index(left_rollup_vk_index, num_left_txs);
90111
Self::validate_input_vk_index(right_rollup_vk_index, num_right_txs);
@@ -103,9 +124,9 @@ impl TestBuilder {
103124

104125
let slot_number = fixture_builder.start_slot_number;
105126

106-
// Build the block's message bundle appended to an empty l1-to-l2 tree.
107-
let (previous_l1_to_l2, l1_to_l2_messages, l1_to_l2_message_frontier_hint, new_l1_to_l2) =
108-
fixture_builder.build_l1_to_l2_message_bundle(TEST_NUM_MSGS);
127+
// Build the block's message bundle appended at the l1-to-l2 tree's next available index.
128+
let (previous_l1_to_l2, l1_to_l2_messages, l1_to_l2_message_frontier_hint, new_l1_to_l2) = fixture_builder
129+
.build_l1_to_l2_message_bundle_at_index(TEST_NUM_MSGS, l1_to_l2_start_index);
109130

110131
// The l1-to-l2 tree snapshot carried in the constants is the pre-append snapshot for non-first blocks (the tree
111132
// where this block picks up), and the post-append snapshot for first blocks (checked by the circuit against the

noir-projects/noir-protocol-circuits/crates/rollup-lib/src/tests/rollup_fixture_builder.nr

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ use types::{
3535
traits::{Deserialize, Empty, Hash},
3636
};
3737

38+
// The fixture pre-fills l1-to-l2 leaves into a single subtree of 2^3 = 8 leaves, which bounds how far into the tree a
39+
// block's bundle can be made to start. The bundle append itself is not aligned to that subtree.
40+
global PRE_EXISTING_L1_TO_L2_SUBTREE_HEIGHT: u32 = 3;
41+
global PRE_EXISTING_L1_TO_L2_LEAF_COUNT: u32 = 8;
42+
3843
pub struct RollupFixtureBuilder {
3944
// Epoch constants.
4045
pub chain_id: Field,
@@ -181,23 +186,51 @@ impl RollupFixtureBuilder {
181186
}
182187

183188
/// Builds a per-block L1-to-L2 message bundle appended to an empty tree, returning
184-
/// `(previous_snapshot, leaves, num_msgs, frontier_hint, new_snapshot)`. The empty tree has an all-zero frontier,
185-
/// and the resulting `new_snapshot` is exactly what the block root circuit recomputes from the same inputs.
189+
/// `(previous_snapshot, leaves, frontier_hint, new_snapshot)`. The resulting `new_snapshot` is exactly what the
190+
/// block root circuit recomputes from the same inputs.
186191
pub fn build_l1_to_l2_message_bundle(
192+
self,
193+
num_msgs: u32,
194+
) -> (AppendOnlyTreeSnapshot, [Field; MAX_L1_TO_L2_MSGS_PER_BLOCK], [Field; L1_TO_L2_MSG_TREE_HEIGHT], AppendOnlyTreeSnapshot) {
195+
self.build_l1_to_l2_message_bundle_at_index(num_msgs, 0)
196+
}
197+
198+
/// Same as `build_l1_to_l2_message_bundle`, but appending into a tree that already holds `start_index` leaves —
199+
/// the compact, unaligned append every block after the very first one performs. The pre-existing leaves sit in a
200+
/// single subtree of `PRE_EXISTING_L1_TO_L2_LEAF_COUNT` leaves, so `start_index` must stay below that. The frontier
201+
/// hint is the tree's sibling path at `start_index`, which is how a tree database answers for it: the pending left
202+
/// sibling at every level where the index is a right child, and the zero-subtree root everywhere else.
203+
pub fn build_l1_to_l2_message_bundle_at_index(
187204
_self: Self,
188205
num_msgs: u32,
206+
start_index: u32,
189207
) -> (AppendOnlyTreeSnapshot, [Field; MAX_L1_TO_L2_MSGS_PER_BLOCK], [Field; L1_TO_L2_MSG_TREE_HEIGHT], AppendOnlyTreeSnapshot) {
208+
assert(
209+
start_index < PRE_EXISTING_L1_TO_L2_LEAF_COUNT,
210+
"The fixture cannot pre-fill that many l1-to-l2 leaves",
211+
);
212+
213+
let mut pre_existing = [0; PRE_EXISTING_L1_TO_L2_LEAF_COUNT];
214+
for i in 0..PRE_EXISTING_L1_TO_L2_LEAF_COUNT {
215+
if i < start_index {
216+
pre_existing[i] = (i + 1) as Field * 7;
217+
}
218+
}
219+
let tree = SingleSubtreeMerkleTree::<PRE_EXISTING_L1_TO_L2_LEAF_COUNT, PRE_EXISTING_L1_TO_L2_SUBTREE_HEIGHT, L1_TO_L2_MSG_TREE_HEIGHT>::new(
220+
pre_existing,
221+
);
222+
190223
let mut leaves = [0; MAX_L1_TO_L2_MSGS_PER_BLOCK];
191224
for i in 0..MAX_L1_TO_L2_MSGS_PER_BLOCK {
192225
if i < num_msgs {
193226
leaves[i] = (i + 1) as Field * 100;
194227
}
195228
}
196229
let previous = AppendOnlyTreeSnapshot {
197-
root: compute_empty_tree_root::<L1_TO_L2_MSG_TREE_HEIGHT>(),
198-
next_available_leaf_index: 0,
230+
root: tree.get_root(),
231+
next_available_leaf_index: start_index as Field,
199232
};
200-
let frontier_hint = [0; L1_TO_L2_MSG_TREE_HEIGHT];
233+
let frontier_hint = tree.get_sibling_path(start_index as Field);
201234
let new = append_leaves_to_snapshot::<L1_TO_L2_MSG_TREE_HEIGHT, MAX_L1_TO_L2_MSGS_PER_BLOCK>(
202235
previous,
203236
leaves,

0 commit comments

Comments
 (0)