Skip to content

feat(fast-inbox): same-block L1-to-L2 message consumption for non-first blocks (A-1432)#24781

Open
spalladino wants to merge 2 commits into
spl/a-1427-inbox-parityfrom
spl/a-1432-same-block-msgs
Open

feat(fast-inbox): same-block L1-to-L2 message consumption for non-first blocks (A-1432)#24781
spalladino wants to merge 2 commits into
spl/a-1427-inbox-parityfrom
spl/a-1432-same-block-msgs

Conversation

@spalladino

@spalladino spalladino commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

What

Makes every block's BlockConstantData.l1_to_l2_tree_snapshot the post-bundle snapshot for that block — first block or not — so a public/AVM tx in block N can read the L1-to-L2 messages block N inserts (same-block consumption), instead of only from block N+1.

Previously the non-first tx-carrying variants (block_root, block_root_single_tx) read their start snapshot from constants.l1_to_l2_tree_snapshot and never asserted the post-bundle root, giving next-block visibility for mid-checkpoint insertions. Now they:

  • take the start L1-to-L2 snapshot as a witness input (previous_l1_to_l2), pinned by block-merge continuity (right.start_state == left.end_state) to the previous block's end state — the checkpoint root forces the leftmost block to be a first-block variant, so every non-first block has a left neighbour pinning its start;
  • append their bundle and assert validate_l1_to_l2_tree_snapshot_in_constants(constants, new_l1_to_l2), exactly like the first-block variants.

The tx-less variants (block_root_empty_tx_first, block_root_msgs_only) carry no tx constants to read against and already witness their start snapshot, so they are unchanged.

Soundness

The witnessed start snapshot cannot be forged: block-merge / checkpoint-root continuity asserts right.start_state == left.end_state, and the checkpoint root asserts the leftmost block is a first-block variant (whose start is pinned to the previous checkpoint). The start is therefore anchored, and the new assert pins the tx-constants snapshot to the computed post-bundle root. The start is not derived from "constants minus the bundle" (not expressible).

Bit-identical pre-flip (public inputs)

Non-first bundles are empty today, so new_l1_to_l2 == start == today's constants value; the new assert passes with unchanged values and the block-root public inputs / state are unchanged. The circuit constraints, private-input ABI, proof, and VK do change (hence the regen below). This is a restructure-now / flip-minimal change.

Flip follow-on (not in this PR)

The recursive pinning of the witnessed previous_l1_to_l2 is already exercised by block_merge::tests::consecutive_block_rollups_tests::non_consecutive_l1_to_l2_message_tree_snapshots (a right block whose start_state.l1_to_l2_message_tree — i.e. this witness — doesn't match the left block's end is rejected on continuity).

One flip requirement this change surfaces: today the prover gives every non-first block the checkpoint's post-first-block snapshot as its previousL1ToL2 (block-proving-state.ts), which is correct only while non-first bundles are empty. Once non-first blocks insert their own bundles (A-1384), the prover must instead feed block N+1 the end snapshot of block N plus the matching frontier hint. Flagged for the flip plan (A-1384).

Tests

Adds negative nargo tests: a non-first block (two-tx and single-tx) whose constants.l1_to_l2_tree_snapshot differs from its post-bundle root must fail. Red/green verified locally — with the new asserts removed the four negative tests report "Test passed when it should have failed"; restored, they pass. rollup_lib block_root suite green (63 tests), block_merge (45), checkpoint_root structure tests (17).

TS wiring

Mirrors the new previous_l1_to_l2 field through stdlib (BlockRootRollupPrivateInputs / BlockRootSingleTxRollupPrivateInputs serialization + factory), the Noir ABI conversion (server.ts), and the prover-client block-root input builders (block-proving-state.ts), which pass the block's lastL1ToL2MessageTreeSnapshot (for non-first blocks, the checkpoint's post-first-block snapshot). stdlib builds and its serialization test passes.

Artifact regen (rides CI / mainframe)

This changes the block_root and block_root_single_tx circuit ABIs, so it invalidates their VKs, the checkpoint-root/block-root Prover.toml sample inputs, and the generated noir-protocol-circuits-types/src/types/index.ts. These are not regenerated locally (bb write_vk OOMs on large circuits on dev boxes; the generated index.ts also depends on recompiled circuit artifacts). They ride the seeded-S3-cache mainframe/CI flow, consistent with the rest of the Fast Inbox stack.

Stack

Stacked on spl/a-1427-inbox-parity; the L1 PRs (#24771, #24773) are rebased on top of this. Part of the Fast Inbox stack (umbrella #24774).

Fixes A-1432

…st blocks (A-1432)

The non-first tx-carrying block-root variants (block_root, block_root_single_tx) now take
their start L1-to-L2 tree snapshot as a witness input, pinned by block-merge continuity
(right.start_state == left.end_state) to the previous block's end state, and assert the tx
constants carry their own post-bundle root via validate_l1_to_l2_tree_snapshot_in_constants,
exactly like the first-block variants. This lets a public/AVM tx in block N read the messages
block N inserts (same-block consumption) instead of only from block N+1.

Previously the non-first variants read their start snapshot from constants.l1_to_l2_tree_snapshot
and never asserted the post-bundle root, so mid-checkpoint insertions were next-block-visible.

Bit-identical pre-flip: non-first bundles are empty, so post-bundle == start == today's constants
value, and the new assert passes with unchanged values. The tx-less variants (empty-first,
msgs-only) carry no tx constants and already witness their start snapshot, so they are unchanged.

Adds negative nargo tests (a non-first block whose constants snapshot differs from its post-bundle
root must fail) and mirrors the new witness field through stdlib serialization, the Noir ABI
conversion, and the prover-client block-root input builders.

VK/artifact regen, checkpoint-root/block-root Prover.toml sample-input regen, and the generated
noir-protocol-circuits-types (index.ts) regen ride the mainframe/CI flow; they are not regenerated
locally (bb write_vk OOMs on large circuits on dev boxes).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant