chore(fast-inbox): umbrella stack for circuits and L1#24774
Open
spalladino wants to merge 36 commits into
Open
chore(fast-inbox): umbrella stack for circuits and L1#24774spalladino wants to merge 36 commits into
spalladino wants to merge 36 commits into
Conversation
spalladino
requested review from
LeilaWang,
Thunkar and
just-mitch
as code owners
July 17, 2026 18:41
Pure library components for the Fast Inbox (AZIP-22), consumed by nothing yet: new MAX_L1_TO_L2_MSGS_PER_BLOCK / MAX_L1_TO_L2_MSGS_PER_CHECKPOINT constants, a variable-length frontier-based append to an AppendOnlyTreeSnapshot at arbitrary (non-aligned) indices, an absorb-only poseidon message-bundle sponge (L1ToL2MessageSponge), and the rolling sha256 chain helper (accumulate_inbox_rolling_hash) matching the L1 truncated-to-field policy. No circuit interface changes.
Replaces the per-leaf frontier walk (MaxLeaves x TreeHeight poseidon hashes) with a level-by-level batched merge: the batch is prepended with the pending left sibling when it starts as a right child, dangling odd nodes become the new frontier entries, and remaining nodes are paired into the next level with lane bounds halving per level. Total cost drops to ~MaxLeaves + 3 x TreeHeight hashes (1,166 for 1024 leaves in the height-36 tree vs ~36,900 before). Same signature, semantics, and error messages. Adds an exhaustive small-tree sweep over every (start, num) combination and a fail-closed test for appending to a completely full tree.
Adds the Fast Inbox (AZIP-22) rolling-hash commitment as a dual of the
legacy inHash, flowing through parity circuits, block/checkpoint rollup
public inputs, the checkpoint header, L1, and the node/prover.
Circuits (noir):
- Parity base absorbs only real leaves into a truncated-to-field sha256
chain (accumulate_inbox_rolling_hash, guarded by num_msgs +
assert_trailing_zeros); merkle roots keep absorbing padding. Parity
public inputs gain start_rolling_hash / end_rolling_hash / num_msgs;
parity root threads the four base segments sequentially and sums counts.
- BlockRollupPublicInputs and CheckpointRollupPublicInputs carry a
{start,end} pair propagated exactly like in_hash (first block root sets
it, merges take left's, validate asserts right's is zero). Checkpoint
merge asserts right.start == left.end (decision 11). CheckpointHeader
gains inbox_rolling_hash (the end value) immediately after in_hash.
- Root rollup public inputs are unchanged: exposing the epoch pair is
inseparable from the L1 epoch-proof anchoring that is out of scope here
(FI-08/FI-14), so it is deferred with the rest of L1 anchoring.
TS: stdlib inbox_rolling_hash mirror (unit-tested against the FI-02
vectors), CheckpointHeader / parity / block / checkpoint PI classes,
noir-protocol-circuits-types conversions, orchestrator threading of
per-base start hashes + counts, and node header population at the
sequencer / prover / validator sites (sourced from the previous
checkpoint header via getPreviousCheckpointInboxRollingHash).
L1: ProposedHeader + ProposedHeaderLib.hash pack inboxRollingHash after
inHash (no new propose checks); test fixtures + headerHash regenerated.
Add the {previous, end} inbox rolling-hash pair to RootRollupPublicInputs so
the epoch's consumed chain segment is carried through to proof verification.
The root rollup sources the pair from the merged checkpoint public inputs;
continuity within the range is already enforced by the checkpoint merges.
On L1, PublicInputArgs gains previousInboxRollingHash/endInboxRollingHash and
EpochProofLib places them at public-input positions 3 and 4 (header hashes,
fees, constants and blob inputs shift by two). Both values are deliberately
unvalidated until the Fast Inbox flip, when they will be checked against the
per-checkpoint records written at propose; for now they are pass-through only.
Also fixes two ivc-integration benchmark generators whose hand-built parity
base inputs were missing start_rolling_hash/num_msgs.
…1373) Exposing the inbox rolling hash on the parity-root and rollup public inputs changed the ABI of the block-root-first variants, block-merge, checkpoint-merge and root circuits, but their committed crates/rollup-*/Prover.toml sample inputs were not refreshed. CI runs `nargo execute` against these fixtures, so all six failed to deserialize (e.g. missing parity_root.public_inputs.start_rolling_hash and previous_rollups[].public_inputs.start_inbox_rolling_hash). Regenerate the six stale fixtures. The circuits push their serialized inputs via pushTestData when run through the prover, so a new prover-client test drives representative epochs through the simulated orchestrator and dumps the captured inputs, replacing the dead orchestrator_single_checkpoint.test.ts regeneration path. The suite is skipped unless AZTEC_GENERATE_TEST_DATA=1 is set.
A-1373 changes the rollup public-input ABIs, so the base branch's committed pinned-build.tar.gz no longer matches the compiled circuits. Remove it rather than carry a stale archive: bootstrap recompiles from source whenever the pin is absent, and the pin is regenerated once the ABI settles.
getEpochProofPublicInputs sources the fee recipient/value from the supplied checkpoint headers but never validated that they hash to the stored headers, so a mismatch only reverted on the on-chain submit path instead of when an off-chain caller assembles the public inputs. Call verifyHeaders in the getter as well, and import ProposedHeaderLib in Rollup.t.sol so the test that exercises this path compiles.
…oot (A-1374) Implements AZIP-22 Fast Inbox FI-04. Parity verification moves from the block-root variants to the checkpoint root. Every block-root variant now takes a per-block message bundle (l1_to_l2_messages, num_msgs, frontier hint) that appends to the L1-to-L2 tree via append_leaves_to_snapshot and absorbs the same leaves into an L1ToL2MessageSponge threaded across the checkpoint's blocks. BlockRollupPublicInputs replaces in_hash and the FI-03 inbox_rolling_hash pair with an explicit is_first_block flag and start/end message sponges. The checkpoint root gains the parity-root proof, asserts sponge continuity and that the merged block sponge equals the parity sponge, and sources the header's legacy in_hash and inbox_rolling_hash from parity. Transitionally the first block carries the whole checkpoint's messages padded to 1024 with num_msgs=1024; non-first blocks carry empty bundles. Behavior stays bit-identical to the current state (same tree roots, same header bytes).
…undle change (A-1374)
…checkpoint (A-1374) A no-tx block has no base or merge proof whose completion enqueues its block root, and A-1374 moved the parity proof (which previously fired it) from the first block root to the checkpoint root. Nothing then enqueued the empty-tx first block root, so its sub-tree never resolved and proving deadlocked. Enqueue it directly once the empty block's end state is set.
…BI (A-1374) Regenerates the stale rollup Prover.toml fixtures for A-1374's ABI (per-block message bundles, parity moved to checkpoint root). Extends the regeneration harness to cover the non-first block-root and block-root-single-tx variants and fixes its blockProofs mapping for the checkpoint sub-tree's parity root proof.
spalladino
force-pushed
the
spl/a-1378-propose-validation
branch
from
July 17, 2026 19:38
aa85a03 to
531e71a
Compare
…essages (A-1375) Adds the block_root_msgs_only_rollup variant (crate rollup-block-root-msgs-only, VK index BLOCK_ROOT_MSGS_ONLY_ROLLUP_VK_INDEX) — a non-first, transaction-less block that still inserts a non-empty L1-to-L2 message bundle, so a proposer can keep draining the Inbox when the tx pool is dry (AZIP-22 Fast Inbox, FI-05). The circuit asserts num_msgs > 0, inherits the sponge blob and message sponge from the previous block, sets is_first_block = false, appends the bundle and absorbs the message sponge, and threads the start sponge blob through unchanged (no tx effects). It is deliberately unable to be leftmost: the checkpoint root requires the leftmost rollup's is_first_block to be true. Wires the new VK into the allowed-VK lists of the block merge and both checkpoint-root variants, the vk tree, and the proving request / artifact / bb-prover registrations. Noir tests prove a checkpoint [first block, msgs-only block, normal block] and cover the num_msgs == 0, non-leftmost, and sponge-continuity failure cases. The orchestrator selection (block-proving-state picking this variant) and its integration test are deferred to FI-12/FI-15: driving a mid-checkpoint msgs-only block requires reworking the transitional per-block message distribution (today the first block carries the whole padded checkpoint bundle and non-first blocks are empty), which belongs with the sequencer that actually produces these blocks. Nothing produces the shape pre-flip.
…s_first_block (A-1375) The message-only block root is listed in the single-block checkpoint-root allowlist only for symmetry; today the inputs validator's is_first_block assertion is the sole reason it cannot stand as a checkpoint's only block. Note that if that assertion is ever relaxed, this entry must be dropped in the same change, so the two decisions are not made independently.
Replace the parity base (x4) + parity root fan-in with one variable-size
InboxParity<S> proof per checkpoint, S in {64, 256, 1024} (one VK per size;
the prover proves the smallest rung >= the checkpoint's message count). The
parity-root circuit is deleted; the checkpoint root keeps one parity
verification, now accepting the 3-rung VK ladder. Net +1 VK.
Two transitional decisions (dev-only, closed by the future Fast Inbox flip):
- in_hash is kept in the parity public inputs as an unconstrained pass-through
hint (the orchestrator supplies the true sha256 frontier root via
computeInHashFromL1ToL2Messages), so L1's inHash == inbox.consume() check
still passes with no L1 changes. The frontier tree is no longer built in
circuit.
- The block-root message sponge absorbs at the real message count while the
L1-to-L2 tree insert stays a padded fixed subtree, decoupled via a new
L1ToL2MessageBundle { messages, num_msgs, num_real_msgs } struct so the real
count can be dropped later with minimal change. num_msgs is not otherwise
pinned in-circuit; documented as a dev-mode gap backstopped by the L1
pending-chain header hash.
Threads the single sized proof through the orchestrator, bb-prover, proving
broker, and TS bindings, collapsing getBaseParityProof/getRootParityProof into
getInboxParityProof and PARITY_BASE/PARITY_ROOT into one INBOX_PARITY request
type. The UltraHonk parity benchmark and bb.js debug test move to InboxParity256.
…e parity method merge Deleting getRootParityProof removed the last uses of NESTED_RECURSIVE_PROOF_LENGTH in mock_prover.ts and broker_prover_facade.ts; @typescript-eslint/no-unused-vars is error-level so yarn lint would fail. Also corrects two 'base parity' JSDoc lines to 'inbox parity'.
The generated L1ToL2MessageBundle.messages type is a fixed-length (1024) array, but the bundle's messages field is a plain Fr[], so mapFieldArrayToNoir inferred length `number` and tsc rejected it (TS2322) at the six block-root mapper call sites. Pass MAX_L1_TO_L2_MSGS_PER_BLOCK explicitly to produce the fixed length.
Removing the value constants (NUM_BASE_PARITY_PER_ROOT_PARITY, NUM_MSGS_PER_BASE_PARITY) left an @aztec/constants import whose members are all inline-type, which @typescript-eslint/no-import-type-side-effects rejects. Convert to import type.
Replacing the parity base/root artifacts with the single variable-size InboxParity ladder (64/256/1024) grew the playground main entrypoint to 1936.74 KB on CI, just over the 1925 KB hard limit. Bump with headroom, following the existing bump-log pattern in vite.config.ts.
ParityPublicInputs requires inHash to be a 31-byte (top-byte-zero) sha frontier root; the InboxParity circuit passes in_hash through unconstrained, so a raw Fr.random() input reappears in the output and fails the constructor check. Compute inHash from the messages instead.
spalladino
force-pushed
the
spl/a-1378-propose-validation
branch
from
July 17, 2026 20:04
531e71a to
b011406
Compare
…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).
spalladino
force-pushed
the
spl/a-1378-propose-validation
branch
2 times, most recently
from
July 18, 2026 04:52
57ce718 to
b853b87
Compare
…in the TS BlockConstantData (A-1432)
…amp-key note (A-1377)
…1377) Raise the constructor guard from `_bucketRingSize > 1` to a floor of 512. The ring must cover the longest stall the chain recovers from on its own: the prune-and-repropose window of 64 checkpoints (2 epochs = 384 L1 blocks) at the natural one-bucket-per-L1-block cadence, so buckets re-consumed after a prune are not overwritten first. 384 rounded up to the next power of two, kept at or below the production ring of 1024. Rework testRingWraparound to exercise a real wraparound against a 512-slot ring and add a negative test asserting construction below the floor reverts.
…itten-hint test (A-1378)
spalladino
force-pushed
the
spl/a-1378-propose-validation
branch
from
July 18, 2026 05:23
b853b87 to
6e642ec
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Umbrella / consolidated PR for the Fast Inbox (AZIP-22) work.
This PR spans the entire Fast Inbox stack — from the topmost branch down to
merge-train/spartan— so CI runs once over the combined diff and reviewers get a single consolidated view of everything.What it contains, at a high level
The stack lands the circuit and L1 (Solidity + archiver/TS) changes for Fast Inbox:
rollup-lib; the sha256inboxRollingHashchain threaded end-to-end (dual with the legacyinHash); per-block L1-to-L2 message bundles with parity moved to the checkpoint root; ablock_root_msgs_only_rollupcircuit for no-tx blocks that still carry messages; a single variable-sizeInboxParity<S>proof per checkpoint (replacing the parity base ×4 + parity root fan-in); and same-block L1-to-L2 message consumption for non-first blocks (every block'sBlockConstantData.l1_to_l2_tree_snapshotis its own post-bundle root).Inboxalongside the existing frontier trees, and the (still-unwired) streaming propose-time consumption validation (validateInboxConsumption).Contained PRs (stack order, bottom → top)
Review and land the individual PRs above; this umbrella exists only to consolidate CI and provide a full-stack diff.