Skip to content

feat(fast-inbox): streaming inbox node integration, flip, and cleanup (umbrella)#24796

Open
spalladino wants to merge 137 commits into
merge-train/spartanfrom
spl/a-1435-regen-dedup
Open

feat(fast-inbox): streaming inbox node integration, flip, and cleanup (umbrella)#24796
spalladino wants to merge 137 commits into
merge-train/spartanfrom
spl/a-1435-regen-dedup

Conversation

@spalladino

@spalladino spalladino commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Umbrella / consolidated PR for phase 2 of the Fast Inbox (AZIP-22) work.

This PR spans the phase-2 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. It sits on top of the phase-1 (circuits + L1) umbrella #24774: until the phase-1 PRs merge, this diff includes them too.

What it contains, at a high level

Phase 2 lands the node integration, the consensus flip, the streaming e2e coverage, and the legacy-path cleanup for the Fast Inbox:

  • Node integration (pre-flip, degenerate or flag-gated off): the archiver mirrors the Inbox rolling-hash buckets and validates the full-width chain; world-state accepts per-block message bundles; block proposals carry a signed Inbox bucket reference; the sequencer selects buckets per block mirroring the L1 predicate; validators run the AZIP's four acceptance checks plus the last-block censorship rule.
  • The flip: Rollup.propose validates streaming inbox consumption against per-checkpoint temp-log records (genesis {0,0,0}), ProposeArgs gains an unsigned bucketHint, MAX_L1_TO_L2_MSGS_PER_BLOCK drops 1024 → 256, every block carries its own L1-to-L2 tree root in the blob, the prover slices a checkpoint's messages per block at compact indices, and the streamingInbox flag is removed — streaming is the only path.
  • e2e: mid-checkpoint inclusion, slot-denominated latency bound, message-only blocks, send-then-consume, plus a prover-orchestrator multi-block-slice test.
  • Cleanup: the legacy L1 inbox path (frontier trees, consume(), LAG), the checkpoint header's inHash (noir/TS/Solidity in lockstep), the node's legacy per-checkpoint message paths and the 128-bit sync hash (node reads and, via the leftover sweep, the L1 field and event arg too), the AZTEC_INBOX_LAG env sweep, inbox constants emitted from the generator, and a single canonical path for rollup sample-input regeneration.

Contained PRs (stack order, bottom → top)

  1. feat(fast-inbox): archiver syncs Inbox buckets (A-1379) #24784 — archiver syncs Inbox buckets and serves bucket queries (A-1379)
  2. feat(fast-inbox): world-state per-block message insertion (A-1380) #24785 — world-state per-block message insertion (A-1380)
  3. feat(fast-inbox): block proposals carry a bucket reference (A-1381) #24786 — block proposals carry a signed Inbox bucket reference (A-1381)
  4. feat(fast-inbox): sequencer streaming message selection, flag off (A-1382) #24787 — sequencer streaming message selection, flag off (A-1382)
  5. feat(fast-inbox): validator streaming acceptance conditions, flag off (A-1383) #24788 — validator streaming acceptance conditions, flag off (A-1383)
  6. feat(fast-inbox): flip consensus to streaming inbox (A-1384) #24789the flip: consensus cutover to the streaming inbox (A-1384)
  7. test(fast-inbox): streaming inbox e2e latency and mid-checkpoint inclusion (A-1385) #24790 — streaming inbox e2e + prover multi-block-slice coverage (A-1385)
  8. chore(fast-inbox): delete legacy L1 inbox path (A-1386) #24791 — delete the legacy L1 inbox path (A-1386)
  9. chore(fast-inbox): circuits cleanup and checkpoint header format (A-1387) #24792 — circuits cleanup; checkpoint header loses inHash (A-1387)
  10. chore(fast-inbox): delete legacy node message paths (A-1388) #24793 — delete legacy node message paths, 128-bit sync hash, env sweep (A-1388)
  11. chore(fast-inbox): emit inbox lag and checkpoint cap from the constants generator (A-1434) #24794 — emit inbox lag and checkpoint cap from the constants generator (A-1434)
  12. chore(fast-inbox): single canonical path for rollup sample-input regeneration (A-1435) #24795 — single canonical path for rollup sample-input regeneration (A-1435)

Merge notes

  • A-1431 merge gate: the domain-separation decision must resolve before feat(fast-inbox): flip consensus to streaming inbox (A-1384) #24789 (the flip) merges. If it adopts a separator, the rolling-hash link-encoding change must be inserted below the flip in the stack.
  • Regeneration (done): the rollup Prover.toml sample inputs were regenerated on a bb-capable box and committed at the top of the stack (bbfaf57daa), including the new rollup-block-root-msgs-only/Prover.toml — the circuit joined the regen scenarios and the CI nargo execute list in this stack. All regenerated inputs pass nargo execute.
  • Store versions: ARCHIVER_DB_VERSION 7 → 9 across the stack and the p2p attestation store 2 → 3; no migrations — nodes resync (fresh rollup instance per release line).
  • Consensus-format changes: the checkpoint header drops inHash (preimage changes in noir/TS/Solidity lockstep, fixtures regenerated), the block-proposal p2p wire format drops its zeroed inHash, the per-block blob encoding always carries the L1-to-L2 root, and the per-block message cap is 256. All are release-boundary changes for fresh networks.
  • Review fixes: the final review appended fix commits to feat(fast-inbox): flip consensus to streaming inbox (A-1384) #24789 (per-block message-sponge threading, msgs-only block-root wiring, message-only block building) and chore(fast-inbox): delete legacy node message paths (A-1388) #24793 (deploy-script test env reads, attestation store bump, streaming rework of the L1 publisher integration test), with the stack rebased on top.
  • CI hardening: driving the stack green added a min-work rule (a non-empty message bundle satisfies the block threshold), oldest-epoch-first proving-job scheduling, an EpochProofExtLib split keeping RollupOperationsExtLib deployable post-flip, streaming mocks for the validator/prover-node unit suites, a pxe L2 tips snapshot regen for the inHash-less header (read-defaultable, no schema bump), and docs-example Inbox ABIs kept in lockstep with the MessageSent event.
  • Pass-4 product fixes: two consensus-path bugs surfaced by the cross-chain e2e were fixed in their owning branches, not at the top: the ProvingJobResult Zod union was missing a BLOCK_ROOT_MSGS_ONLY_ROLLUP member (feat(fast-inbox): add block_root_msgs_only_rollup circuit for no-tx blocks with messages (A-1375) #24612 / A-1375, where the request type is introduced), which stalled epochs decoding a message-only block-root result; and the propose bucketHint was lost when a checkpoint's final block failed to build after a message-only first block consumed a bucket (feat(fast-inbox): flip consensus to streaming inbox (A-1384) #24789 / A-1384). Both carry unit-level round-trip / RED-GREEN regression tests. The relocated proving-scheduling test (base of the stack) also swapped its parity reference for PUBLIC_VM: no parity name type-checks across the whole stack (INBOX_PARITY exists only at A-1427+, PARITY_BASE only at A-1374/A-1375, since A-1427 collapses PARITY_BASE/PARITY_ROOT into INBOX_PARITY), whereas PUBLIC_VM is stable everywhere and stays lower-priority than PRIVATE_TX_BASE_ROLLUP. Verified with real tsc at A-1374, A-1384, and the top.
  • Readiness-tip fix (A-1384): the shared cross-chain e2e helper evaluated L1→L2 message readiness against the default 'latest' tip while these suites anchor the PXE to 'checkpointed'. Post-flip per-block inbox insertion lets the proposed chain reach a message's consume-checkpoint a checkpoint before it is published, so readiness flipped true while the PXE had not synced the message and the private consume simulated too early — a deterministic failure on l1_to_l2.test.ts from the flip up. The helper now gates on the configured PXE sync tip (CrossChainMessagingTest.pxeSyncChainTip, defaulting to 'latest'). Test-only, unchanged A-1384→top; box-verified green on all three cross-chain suites (l1_to_l2, streaming_inbox, l1_to_l2_inbox_drift).
  • Archiver per-block message-root reconstruction (A-1384, consensus fix): the archiver rebuilt every block's L1→L2 message tree root from the checkpoint's first block (stale pre-flip per-checkpoint assumption), so any follower node (prover / sync-only) reconstructing a non-first message-inserting block got the wrong root and forked from the sequencer (block state does not match world state) on the first cross-chain-consuming block. Post-flip the blob carries a per-block root; reconstruction now uses each block's own. Surfaced as cross_chain_public_message + token_bridge (only followers reconstruct via the archiver, so validation never caught it). A data_retrieval.test.ts unit pins per-block reconstruction; both e2es green at a-1384. A full box sweep of every L1→L2/inbox/cross-chain/proving e2e at a-1384 is now green.
  • Known first-run flakes: if the new streaming_inbox.test.ts tests 1–2 (mid-checkpoint inclusion, latency bound) flake on their first CI runs, add tight error_regex entries to .test_patterns.yml for the specific assertion rather than broad skips. None are pre-added.
  • Accepted residuals: unconsumed-bucket overwrite protection (A-1390), L1-reorg bucket invalidation (A-1389), and the validator L1-sync race (A-1393) land in the hardening phase; a failed final-block build can leave a checkpoint below the censorship floor (costing that slot), to be tightened with the A-1392 boundary matrix.

Review and land the individual PRs above; this umbrella exists only to consolidate CI and provide a full-stack diff.

  • Pass-8 CI fixes (test-only, both surfaced by fail-fast unmasking): (1) the world-state integration.test.ts mock archiver never registered Inbox buckets, so the post-flip synchronizer reconstructed empty bundles and diverged (block state does not match world state) — fixed at A-1384 by registering genesis + per-checkpoint buckets in MockPrefilledArchiver.setPrefilled; (2) the l1_publisher.integration.test.ts inbox-consumption modeling (hardcoded rolling hash / bucket hint, then a wrong checkpoint-depth shift register) reverted on L1 — fixed at A-1387 by mirroring the real Inbox buckets and reusing the production selectInboxBucketForBlock (INBOX_LAG_SECONDS is an L1-time cutoff). A-1388's own rewrite of that file dissolves into the A-1387 version. Box-verified: world-state 12/12 (a-1384, top), l1_publisher 13/13 (a-1387, a-1388); full tsc green at a-1384/a-1387/a-1388/top.

  • Leftover sweep (post-review of this umbrella diff): five flip/cleanup leftovers found while reviewing this diff were fixed in their owning branches. The legacy 128-bit keccak inbox rolling hash is now fully removed on L1 (InboxState.rollingHash, the MessageSent bytes16 arg, its keccak accumulation, the four Solidity test suites asserting it, and the three live docs-example inbox ABIs) at A-1388 — the earliest branch whose TS no longer reads it. The stale IRollup.sol PublicInputArgs natspec now describes the implemented epoch-proof anchoring (A-1386). The inert isFirstBlock blob-fixture parameter is removed (A-1384). The dead InboxLeaf class is deleted (A-1388). The checkpoint-builder comments now mark up-front whole-checkpoint message insertion as test-only (A-1388).

  • Gas benchmarks refreshed (top of stack): gas_report.json, gas_benchmark.md, and gas_benchmark_results.json regenerated via ./bootstrap.sh gas_report / gas_benchmark at the pinned foundry v1.4.1. Headlines: sendL2Message mean gas 53,020 → 44,796 (−15.5%), Inbox bytecode −19%; propose +~4% mean from the bucketHint calldata and the added inbox-consumption validation. Much of the raw diff is pre-existing baseline staleness (the committed report predated the block→checkpoint rename and the Outbox.getRootData signature change), now corrected. Known quirk for future regens: three Rollup.t.sol tests (testExtraBlobs, testRevertInvalidCoinbase, testRevertInvalidTimestamp) fail only under FORGE_GAS_REPORT=true — a Foundry vm.expectRevert call-depth interaction with the IInbox.getBucket() staticcall in ProposeLib.validateInboxConsumption, invisible to normal CI; a real fix or a --no-match-test entry in bootstrap.sh gas_report is a follow-up.

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.
@spalladino
spalladino force-pushed the spl/a-1435-regen-dedup branch 3 times, most recently from 7b88209 to de28e00 Compare July 19, 2026 15:36
…h header (A-1373)

The checkpoint header now carries inboxRollingHash, changing the checkpoint
hash preimage. The stored format is unchanged (read-defaultable), so only the
snapshot is regenerated; no PXE_DATA_SCHEMA_VERSION bump.
…ng hash (A-1373)

The proposal handler, sequencer job, and prover node now source the parent
checkpoint's inboxRollingHash. Serve zeroed parent headers from the mocks so
scenarios beyond the genesis checkpoint resolve their chain start: the
validator suite stubs the proposed-checkpoint fallback (getCheckpointData
doubles as the already-published existence check), the sequencer suite stubs
getCheckpointData, and the prover-node suite serves synthetic ancestors below
the mined window.
@spalladino
spalladino force-pushed the spl/a-1435-regen-dedup branch 5 times, most recently from a6dbe2a to bd8896e Compare July 19, 2026 20:48
@spalladino
spalladino requested a review from a team as a code owner July 19, 2026 20:48
@spalladino
spalladino force-pushed the spl/a-1435-regen-dedup branch 7 times, most recently from f4a62b3 to 147b7ff Compare July 21, 2026 02:58
…f types (A-1427)

The legacy L1-to-L2 tree gated block roots on parity outputs, so the broker's
type-major priority still progressed one epoch at a time. The streaming inbox
parity only gates the checkpoint root: under sustained block production the
type-major order kept serving younger epochs' block roots and never scheduled
INBOX_PARITY, stalling every epoch at awaiting-root. Select the oldest-epoch
job across the allowed queues and tie-break by proof-type priority.
…uits (A-1387)

Remove the dead unconstrained in_hash pass-through from InboxParity and
ParityPublicInputs, and remove in_hash from the CheckpointHeader consensus
format (struct, to_be_bytes, serialization length, Empty, hash fixtures). The
Inbox rolling hash is now the checkpoint's only inbox commitment.

Retire NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP in favor of
MAX_L1_TO_L2_MSGS_PER_CHECKPOINT, and drop the now-unused
L1_TO_L2_MSG_SUBTREE_ROOT_SIBLING_PATH_LENGTH and the NUM_MSGS_PER_BASE_PARITY /
NUM_BASE_PARITY_PER_ROOT_PARITY frontier fan-in constants. Regenerated
constants.gen.ts and ConstantsGen.sol via remake-constants.
…te FrontierLib (A-1387)

Remove inHash from the ProposedHeader struct and its hash preimage in
ProposedHeaderLib, keeping the packing byte-for-byte in lockstep with the noir
and stdlib checkpoint headers. Update the decoder base struct and the propose
test helpers, and regenerate the checkpoint fixtures (drop header.inHash,
recompute headerHash).

Delete FrontierLib and its only remaining users: the parity cross-check test
(Parity.t.sol, which verified the deleted parity circuits) and the frontier
merkle test/harness. The frontier tree is gone from both circuits and L1.
…h stdlib and node (A-1387)

Mirror the consensus-format change in the stdlib CheckpointHeader (struct,
serialization, viem, hash fixtures) and the circuit-ABI stdlib types
(ParityPublicInputs, InboxParityPrivateInputs) and their noir conversion
mappings. Sweep the header-inHash populate/read sites: the lightweight
checkpoint builder, the checkpoint-proving orchestrator, the sequencer fixture
writer, and the checkpoint proposal (its block-level inHash now sources zero
rather than the removed header field). Regenerate the checkpoint p2p wire
fixture for the shorter header.

Replace NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP with MAX_L1_TO_L2_MSGS_PER_CHECKPOINT
across the TS consumers. The block-level inHash struct fields (BlockProposal,
L2Block) and stdlib/src/messaging/in_hash.ts remain for the node cleanup issue.
…oval (A-1387)

The propose-failure test asserted the deleted Rollup__InvalidInHash selector
(0xcd6f4233). With the legacy inHash L1 check gone, a checkpoint carrying
messages that do not match the Inbox now reverts at validateInboxConsumption
with Rollup__InvalidInboxRollingHash (0xed1f7bb5).
… checkpoint header (A-1387)

Dropping inHash from the checkpoint header changes the checkpoint hash preimage, so the checkpoint ids the
tips store persists change value. The stored format is unchanged (read-defaultable), so only the snapshot is
regenerated; no PXE_DATA_SCHEMA_VERSION bump.
…reaming bucket selector (A-1387)

The A-1387 rewrite that removed inHash left this test's inbox handling half-adapted: it hardcoded
previousInboxRollingHash to Fr.ZERO and proposed with bucketHint 0n while consuming real L1->L2 messages, so
message-consuming checkpoints reverted on L1 with Rollup__InvalidInboxRollingHash / UnconsumedInboxMessages.
INBOX_LAG_SECONDS is an L1-time censorship cutoff (not whole checkpoints), so no checkpoint-depth shift
register can reproduce the aged bucket set. Mirror the real Inbox buckets into the test's
MockL1ToL2MessageSource, then reuse the production selectInboxBucketForBlock (which mirrors
ProposeLib.validateInboxConsumption) to pick exactly the buckets each checkpoint must consume, deriving the
consumed bundle, the propose bucket hint, and the header rolling hash from that one selection so header,
world state, and L1 agree. Matches the A-1388 rewrite of this file so the segment rebase dissolves to zero.
Removes the legacy inboxLag / AZTEC_INBOX_LAG config field and env var from
ethereum config, foundation env vars, the network-consensus-config list, the
l1-contracts and spartan network defaults, and the e2e test option plumbing.
Also drops the removed inHash argument from the e2e web3signer createBlockProposal
wrapper.
…ers from stdlib/p2p (A-1388)

Deletes computeInHashFromL1ToL2Messages and the inHash field from BlockProposal
(including its signed-payload slot), the CheckpointProposal getBlockProposal
pass-through, and the createBlockProposal inHash argument on the validator
interface. Removes the padded per-checkpoint InboxLeaf helpers
(smallestIndexForCheckpoint / indexRangeForCheckpoint / checkpointNumberFromIndex)
and the legacy getL1ToL2Messages(checkpointNumber) member from the L1ToL2MessageSource
and archiver RPC interfaces. Regenerates the block-proposal golden wire fixtures for
the shrunk payload; the p2p wire format changes at a release boundary (fresh networks),
matching the A-1381 optional-tail precedent.
…aths in the archiver (A-1388)

Removes the legacy getL1ToL2Messages(checkpointNumber) flow, the padded
per-checkpoint index invariants, the inboxTreeInProgress readiness gate, and the
L1ToL2MessagesNotReadyError. InboxMessage drops the 128-bit keccak rollingHash and
the vacuous derived checkpointNumber, so messages carry only the compact global
index and the full-width consensus rolling hash; the store serialization changes and
ARCHIVER_DB_VERSION bumps to 9 (nodes resync, no migration). Reorg detection now
compares the local consensus rolling hash and total against the Inbox's current
rolling-hash bucket via new getBucket/getCurrentBucketSeq/getCurrentBucket wrappers,
replacing the 128-bit getState comparison. Test fakes/mocks move to compact indexing.
…lidator (A-1388)

Removes the dead inHash=Fr.ZERO plumbing through the checkpoint proposal job and
the validator/validation-service createBlockProposal path, and deletes the dead
in_hash_mismatch validation-failure reason from proposal_handler, validator, and
metrics. Updates the sequencer/validator test mocks and callers for the dropped
inHash argument and the removed getL1ToL2Messages message source member.
…ator message fetch (A-1388)

Drops the no-op first-in-checkpoint padding alias and its stale docs in world-state,
and removes the obsolete non-first-block-empty-bundle transitional test. The node
public-calls simulator no longer fetches next-checkpoint messages via the removed
per-checkpoint API and drops its now-unused l1ToL2MessageSource dependency, simulating
against the fork's current tree (streaming Inbox consumes per block). Rewrites the
THREAT_MODEL inHash/consume passages to the consensus rolling-hash / bucket model.
…ts (A-1388)

The optional bucket-reference tail keeps proposals that omit it round-tripping
cleanly; it does not make the wire byte-identical to the pre-inHash-removal format.
Reword the toBuffer/fromBuffer comments to describe only the tail's unset case.
… tests (A-1388)

The env sweep removed AZTEC_INBOX_LAG from scripts/network-defaults.json but
both deploy-script test setUps still readUint the key, which reverts
(vm.parseJsonUint on a missing path), failing the suites before any test
runs. The deploy configuration no longer consumes the env var.
…proposal formats (A-1388)

The attestation store persists raw BlockProposal and CheckpointAttestation
buffers. Both changed shape in this stack (the checkpoint header lost inHash
and the block-proposal wire format dropped its zeroed inHash), and stored
checkpoint attestations are decoded without a tolerant fallback, so a store
written by a pre-Fast-Inbox node would throw on read. Bump 2 -> 3 to wipe
stale pools, matching the archiver's no-migration bump.
…388)

Removes the dead AZTEC_INBOX_LAG entries from the spartan environment
profiles and the governance-upgrade tutorial, rewrites the validator and
sequencer README sections that still described the legacy inHash flow, and
drops e2e comments that still explained fixture settings in terms of the
deleted inboxLag / L1ToL2MessagesNotReadyError behavior. Versioned docs
snapshots are left untouched.
@spalladino
spalladino force-pushed the spl/a-1435-regen-dedup branch from c6ea5bc to 7d1d7d3 Compare July 21, 2026 22:32
spalladino and others added 7 commits July 21, 2026 19:37
…ts generator (A-1434)

Add INBOX_LAG_SECONDS and MAX_L1_TO_L2_MSGS_PER_CHECKPOINT to the Solidity
constants allowlist so the generator emits them into ConstantsGen.sol, matching
the values already present in constants.gen.ts. ProposeLib and its consumption
test now read the generated Constants library instead of the hand-declared
file-scope copies, giving L1 and TS a single source of truth.
…neration (A-1435)

Make the prover-client regenerate_rollup_sample_inputs suite the sole owner of
every block-root, block-merge, checkpoint, tx-merge, and root rollup Prover.toml,
and stop the e2e full.test dump from writing the block-root and checkpoint samples
it overlapped on. The two paths previously both wrote rollup-block-root-first,
rollup-block-root-first-single-tx, rollup-checkpoint-root-single-block,
rollup-checkpoint-merge, and rollup-root, so the committed fixtures drifted
depending on which ran last.

The e2e dump now regenerates only what needs real client-proved transactions the
simulated orchestrator cannot produce: the private-kernel circuits and the
transaction-base rollups. A dedicated three-tx scenario restores rollup-tx-merge
coverage (dropped when orchestrator_single_checkpoint.test.ts was replaced), so
the suite covers every rollup circuit at or above the transaction merge.

Update the regen docs (barretenberg cpp CLAUDE.md, update-prover-toml and
gate-counts skills) to the two-command split and drop the dead
orchestrator_single_checkpoint references.
…en and fix stale regen command patterns (A-1435)

Post-flip a zero-tx non-first block carrying a message bundle is a live block
shape routed through the msgs-only block root, so the regen suite gains a
scenario with a per-block message distribution that dumps
rollup-block-root-msgs-only, and the circuit joins the CI nargo-execute list.
The first regen run creates crates/rollup-block-root-msgs-only/Prover.toml,
which must be committed alongside the other regenerated tomls.

Also fixes the documented e2e regen command: the prover full test lives at
single-node/prover/server/full.test.ts, so the old e2e_prover/full.test jest
pattern matches nothing.
…-1435)

makeCheckpoint concentrates every scenario message into the first block, whose bundle pads to
MAX_L1_TO_L2_MSGS_PER_BLOCK (256), so seeding scenarios with the per-checkpoint cap (1024) threw
on regeneration. Also distribute messages across both blocks of the rollup-block-root scenario so
the committed non-first block-root sample carries a non-empty bundle.
@spalladino
spalladino force-pushed the spl/a-1435-regen-dedup branch from 7d1d7d3 to 9e12351 Compare July 21, 2026 22:39

@spalladino spalladino left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got as far as sequencer-client (excluded)

// The fee recipient/value below are sourced from the supplied headers, so the header hashes must be validated here
// as well as on the submit path - otherwise an off-chain caller of this getter would assemble public inputs from
// unverified fee fields and only discover the mismatch when the on-chain proof reverts.
verifyHeaders(_start, _end, _headers);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why wasn't this needed before?

Comment on lines +192 to +198
bytes32 expectedPreviousInboxRollingHash = STFLib.getInboxRollingHash(_start - 1);
require(
expectedPreviousInboxRollingHash == _args.previousInboxRollingHash,
Errors.Rollup__InvalidPreviousInboxRollingHash(
expectedPreviousInboxRollingHash, _args.previousInboxRollingHash
)
);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why don't we need to do the same for the endInboxRollingHash?

v.header.inboxRollingHash,
_args.bucketHint,
v.header.slotNumber,
STFLib.getInboxMsgTotal(checkpointNumber - 1)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is requiring yet another SLOAD, right? Any way we can optimize that?

Comment on lines +55 to +57
bytes32 inboxRollingHash;
uint64 inboxMsgTotal;
uint64 inboxConsumedBucket;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are two extra SSTOREs. How do these compare vs what we were storing on the Inbox?

Comment on lines -161 to -166
function testFuzzSendAndConsume(
DataStructures.L1ToL2Msg[] memory _messagesFirstBatch,
DataStructures.L1ToL2Msg[] memory _messagesSecondBatch,
uint256 _numTreesToConsumeFirstBatch,
uint256 _numTreesToConsumeSecondBatch
) public {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have an equivalent fuzzing test in the new suite? If not, let's add it.

Comment on lines +72 to +74
// Streaming Inbox (AZIP-22 Fast Inbox): messages are inserted per block via `addBlock`, so `l1ToL2Messages` here
// is empty and the up-front checkpoint-wide insertion is skipped.
insertMessagesPerBlock: boolean = false,

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're post-flip now. No need for a flag to control pre vs post flip behaviour. Just write the code for post-flip.

@@ -165,14 +177,15 @@ export class LightweightCheckpointBuilder {
public async addBlock(
globalVariables: GlobalVariables,
txs: ProcessedTx[],
opts: { insertTxsEffects?: boolean; expectedEndState?: StateReference } = {},
opts: { insertTxsEffects?: boolean; expectedEndState?: StateReference; l1ToL2Messages?: Fr[] } = {},

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

l1ToL2Messages should not be an "opt", it's now a first-class thing we add to each block. Promote it to a proper argument.

Comment on lines +739 to +744
case 'rollup-block-root-first':
return this.prover.getBlockRootFirstRollupProof(rollup.inputs, signal, provingState.epochNumber);
case 'rollup-block-root-first-single-tx':
return this.prover.getBlockRootSingleTxFirstRollupProof(rollup.inputs, signal, provingState.epochNumber);
case 'rollup-block-root-first-empty-tx':
return this.prover.getBlockRootEmptyTxFirstRollupProof(rollup.inputs, signal, provingState.epochNumber);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I should've surfaced this during the circuits review (probably should funnel this comment to the circuits agent), but why do we still need the first flavor of block root circuit? Can't we merge it with the others? Isn't the only difference whether it's allowed to be fully empty?

Comment on lines +47 to +53
blockProofs: Promise<{
blockProofOutputs: PublicInputsAndRecursiveProof<
BlockRollupPublicInputs,
typeof NESTED_RECURSIVE_ROLLUP_HONK_PROOF_LENGTH
>[];
inboxParityProof: PublicInputsAndRecursiveProof<ParityPublicInputs>;
}>;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should either rename blockProofs, or split this into two different fields, one for blockProofs and one for inboxParityProof.

Comment on lines +379 to +393
private async deriveCheckpointConsumedMessages(previousBlockHeader: BlockHeader, lastBlock: L2Block): Promise<Fr[]> {
const startLeafCount = BigInt(previousBlockHeader.state.l1ToL2MessageTree.nextAvailableLeafIndex);
const endLeafCount = BigInt(lastBlock.header.state.l1ToL2MessageTree.nextAvailableLeafIndex);
if (endLeafCount <= startLeafCount) {
return [];
}
const startBucket = await this.l1ToL2MessageSource.getInboxBucketByTotalMsgCount(startLeafCount);
const endBucket = await this.l1ToL2MessageSource.getInboxBucketByTotalMsgCount(endLeafCount);
if (startBucket === undefined || endBucket === undefined) {
throw new Error(
`Cannot resolve consumed messages for checkpoint ending at block ${lastBlock.number} from the Inbox buckets`,
);
}
return this.l1ToL2MessageSource.getL1ToL2MessagesBetweenBuckets(startBucket.seq, endBucket.seq);
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Get all message between these indices" (ie between startLeafCount and endLeafCount) should probably be a method of the messageStore, so clients don't need to bother with explicit buckets.

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