feat(fast-inbox): flip consensus to streaming inbox (A-1384) - #24789
Closed
spalladino wants to merge 43 commits into
Closed
feat(fast-inbox): flip consensus to streaming inbox (A-1384)#24789spalladino wants to merge 43 commits into
spalladino wants to merge 43 commits into
Conversation
This was referenced Jul 18, 2026
spalladino
force-pushed
the
spl/a-1383-validator-streaming
branch
from
July 19, 2026 14:05
a102839 to
e73dd44
Compare
spalladino
force-pushed
the
spl/a-1384-flip-streaming-inbox
branch
from
July 19, 2026 14:05
6fef625 to
36ac36d
Compare
spalladino
force-pushed
the
spl/a-1383-validator-streaming
branch
from
July 19, 2026 17:57
e73dd44 to
ee895d3
Compare
spalladino
force-pushed
the
spl/a-1384-flip-streaming-inbox
branch
from
July 19, 2026 17:57
97fce26 to
4d7c269
Compare
spalladino
force-pushed
the
spl/a-1383-validator-streaming
branch
from
July 19, 2026 20:48
ee895d3 to
8c8328f
Compare
spalladino
force-pushed
the
spl/a-1384-flip-streaming-inbox
branch
from
July 19, 2026 20:48
73e288c to
4c6d86b
Compare
spalladino
force-pushed
the
spl/a-1383-validator-streaming
branch
from
July 20, 2026 15:30
8c8328f to
5d841f5
Compare
spalladino
force-pushed
the
spl/a-1384-flip-streaming-inbox
branch
2 times, most recently
from
July 20, 2026 17:28
976062a to
de56dbe
Compare
spalladino
force-pushed
the
spl/a-1383-validator-streaming
branch
from
July 20, 2026 21:21
38daa76 to
28efaeb
Compare
spalladino
force-pushed
the
spl/a-1384-flip-streaming-inbox
branch
from
July 20, 2026 21:21
de56dbe to
66aff9d
Compare
spalladino
force-pushed
the
spl/a-1383-validator-streaming
branch
from
July 21, 2026 02:58
28efaeb to
7b89eb4
Compare
spalladino
force-pushed
the
spl/a-1384-flip-streaming-inbox
branch
2 times, most recently
from
July 21, 2026 03:39
1740bc9 to
a17dce8
Compare
spalladino
force-pushed
the
spl/a-1383-validator-streaming
branch
from
July 21, 2026 03:39
7b89eb4 to
7e29709
Compare
spalladino
force-pushed
the
spl/a-1383-validator-streaming
branch
from
July 27, 2026 20:53
7e29709 to
812d923
Compare
spalladino
force-pushed
the
spl/a-1384-flip-streaming-inbox
branch
from
July 27, 2026 20:53
d97760c to
1cc2caa
Compare
…384)
Switch Rollup.propose from the legacy inbox.consume()/inHash frontier flow to
the streaming-inbox validation (AZIP-22 Fast Inbox):
- ProposeLib.propose calls validateInboxConsumption against the parent
checkpoint's consumed position, read from the parent temp-log record, and
stores the returned consumed total in the new record. The Rollup__InvalidInHash
check and the inbox.consume() call are removed.
- ProposeArgs gains an unsigned bucketHint calldata field (kept out of the
attested payload digest).
- TempCheckpointLog / CompressedTempCheckpointLog carry the consumption record
{inboxRollingHash, inboxMsgTotal, inboxConsumedBucket}; genesis is {0,0,0}.
- EpochProofLib.getEpochProofPublicInputs anchors the rolling-hash chain start to
the record of checkpoint start-1, mirroring previousArchive
(Rollup__InvalidPreviousInboxRollingHash).
- Inbox.sendL2Message returns and emits the compact cumulative message index.
Frontier trees keep running unread (deleted in cleanup). Propose-path test
harnesses reference the live buckets; inbox index and rolling-hash suites updated
for compact indexing.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…uits (A-1384) Flip the block-root circuits to the streaming inbox (AZIP-22 Fast Inbox): - MAX_L1_TO_L2_MSGS_PER_BLOCK 1024 -> 256 (per-checkpoint cap and the InboxParity size ladder are unchanged). - L1ToL2MessageBundle drops num_real_msgs; a single num_msgs count drives both the compact (unpadded) tree append and the message-sponge absorb. - BlockRollupPublicInputsComposer::with_message_bundle appends and absorbs the real num_msgs leaves and drops the dual-count assert. - SpongeBlob::absorb_block_end_data absorbs the L1-to-L2 tree root for every block (the root is now per-block), dropping the is_first_block_in_checkpoint gating. InboxParity already absorbs the real count (A-1427), so the checkpoint-root sponge equality holds. VK and Prover.toml sample-input regen ride CI. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…he node (A-1384) Mirror the circuit blob-format flip in the node (AZIP-22 Fast Inbox): - Regenerate MAX_L1_TO_L2_MSGS_PER_BLOCK to 256 in @aztec/constants. - Every block's blob carries the L1-to-L2 message tree root; drop the first-block conditional in encode/decode, the producers (L2Block, prover orchestrator), and the NUM_FIRST_BLOCK_END_BLOB_FIELDS distinction. - getNumBlockEndBlobFields loses its parameter; blob capacity accounting spends the block-end overhead (now 7 fields) for every block, dropping the provable per-checkpoint ceiling from 2457 to 2234. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…-1384) Post-flip the L1-to-L2 message tree grows by real message counts at compact (unaligned) indices, so StateReference.validate no longer requires its next-available leaf index to be a multiple of NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP (AZIP-22 Fast Inbox). The per-tx partial-state checks are unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Remove the streamingInbox flag and wire streaming as the sole path (AZIP-22 Fast Inbox): - Config: drop streamingInbox from foundation env vars, stdlib sequencer/validator config + schemas, and the sequencer/validator config plumbing. - Sequencer: source the parent checkpoint's consumed bucket from the fork's L1-to-L2 tree leaf count + getInboxBucketByTotalMsgCount (cross-checkpoint, not genesis-only); feed the header/block inHash zero; relax waitForMinTxs for message-only blocks; pass the consumed bucket seq as the propose bucketHint. Automine selects its single block's bundle the same way. - Validator: per-block acceptance and checkpoint re-execution always run; the checkpoint rebuild derives the consumed message list from the buckets between the parent checkpoint's position and the last block. - World state: appendL1ToL2MessagesToTree and handleL2BlockAndMessages append real leaves at compact indices; the synchronizer derives each block's bundle from its leaf-index range. - Lightweight checkpoint builder feeds the checkpoint inHash zero. Legacy bulk-fetch/insert paths go dead (deleted in FI-18). Verified via jest: validator proposal_handler (34) + streaming checks (14), sequencer inbox selector (9), world-state native (56). tsc rides CI. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…es (A-1384) The flip made every block carry the l1-to-l2 message tree root, so makeBlockEndBlobData no longer branches on isFirstBlock. The parameter is still accepted for call-site compatibility (makeBlockBlobData, the archiver data-retrieval test) but is now bound to an underscore name to satisfy no-unused-vars, which was failing lint --check from this branch upwards.
… flip (A-1384) The flip switched the inbox message index to a compact cumulative counter (totalMessagesInserted, zero-based), but left the fee-portal and token-portal forge tests asserting the old lag-based tree-geometry index (lag * SIZE). Update depositToAztecPublic and TokenPortal expectations to the compact index; the MessageSent checkpointNumber field and the consume/lag machinery are unchanged at this branch, so only the index expectations move. Without this, cd l1-contracts && forge test failed 4 tests once the lint gate stopped masking it.
…-message-seen works pre-consumption (A-1384) The flip repointed getL1ToL2MessageCheckpoint to answer from block records (it now returns undefined until a block consumes the message), silently breaking waitForL1ToL2MessageSeen's contract that it only confirms the archiver ingested the message from L1, without requiring the L2 chain to advance. Every caller uses the two-phase wait-seen-then-advance pattern with tight windows, so the l1-reorgs messages e2e (and message harnesses) time out waiting for a checkpoint that needs inbox lag plus a consuming block. Add getL1ToL2MessageIndex to the AztecNode interface/schema/server (delegating to the existing message source method) and repoint waitForL1ToL2MessageSeen to it, restoring the pre-consumption lookup over a remote-capable node.
…hiver (A-1384) The flip made the Inbox emit a compact zero-based totalMessagesInserted index, but the archiver message store still validated the legacy per-checkpoint tree-geometry range, so it rejected every real message with 'Invalid index 0 ... expected value in range [..)'. Replace the checkpoint range/gap/first-index checks with the compact contiguous check (index == previous + 1, first == 0), mirroring the eventual node-cleanup. The legacy 128-bit rolling-hash check, the checkpointNumber field and the vestigial per-checkpoint getter stay until that cleanup. Move the test fixtures to compact indices (keeping checkpointNumber/rollingHash) and drop the store tests that only exercised the geometry getter / removed checks.
…x (A-1384) The test encoded the pre-flip protocol: it asserted an L1-to-L2 message had no membership witness during a proposed-chain drift and could not be consumed until the chain re-reached the message's insertion-time checkpoint. Post-flip messages carry a compact L1-assigned leaf index and stream into the tree in insertion order, so there is no per-message assigned checkpoint and the witness legitimately appears as the chain catches up. Rewrite the scenario to assert the genuinely regression-worthy post-flip invariants: after the rollup prunes the drifted chain, the message is still re-consumed on the new chain (bucket and rolling-hash state surviving the L2 reorg) from both private and public scope, and its witness leaf index still equals the L1-assigned global index. Drop the witness-absent and cannot-consume-early probes and the fragile proven-block destructure; retitle and refresh the scenario comments.
…ions (A-1384) The flip added compact contiguous-index validation to the message store, but the archiver-sync fixtures still assigned legacy per-checkpoint tree-geometry indices, so every sync loop rejected them and the suite failed wholesale. Assign the compact global insertion index in FakeL1State and switch the assertions to insertion-order reads, keeping the pre-cleanup getState reorg detection and inHash hint intact.
…ator suites (A-1384) Streaming is now the only consumption path, so the sequencer checkpoint job resolves the parent Inbox bucket from a world-state fork and every block proposal carries a bucket reference. Stub getInboxBucketByTotalMsgCount and the fork tree info in the sequencer unit suites, and attach the consumed bucket reference (plus a non-zero L1 genesis time) in the validator integration suite so proposals pass the streaming acceptance checks.
…-state synchronizer suite (A-1384) The flip makes streaming the only consumption path: messages derive per-block from the Inbox rather than being attached to the first block of a checkpoint, and non-first blocks may carry messages. The two transitional tests (first-block-only message fetch, non-first- block rejection) and their now-unused mock/helper no longer reflect the behavior and fail. Removes them, matching the up-stack cleanup.
… cursor (A-1384) When a checkpoint builds several blocks in one slot and the final sub-slot block fails to build after earlier blocks already consumed L1-to-L2 messages, no block is held for broadcast, so the L1 propose bucket hint fell back to genesis bucket 0 while the checkpoint header committed to a non-genesis rolling hash — L1 rejects this as Rollup__InvalidInboxRollingHash. Take the hint from the streaming consumption cursor's final bucket reference, which is always defined and always matches the bucket the header committed to, and carry it through the broadcast result. Behavior-preserving on the normal held-block path (block bucket refs are cumulative, so the held last block's ref already equals the cursor's). Adds a regression test for the no-held-block checkpoint.
…aming inbox (A-1384) The flip makes world-state sync reconstruct L1-to-L2 messages per block from the Inbox, and propose validates the checkpoint's inboxRollingHash against a bucket hint. Back the blockSource mock with MockL1ToL2MessageSource, register a bucket per block from the real on-chain currentBucketSeq, chain the previous checkpoint's rolling hash, and derive the propose bucket hint from the mock. Updates the propose-revert assertion to the streaming Rollup__InvalidInboxRollingHash selector (a-1384 already validates rolling hash, not inHash).
…A-1384) The shared cross-chain readiness helper evaluated isL1ToL2MessageReady against the default 'latest' tip while these suites anchor the PXE to 'checkpointed'. With per-block inbox insertion the proposed chain reaches a message's consume-checkpoint before that checkpoint is published, so readiness flipped true a checkpoint early and the private consume simulated against a PXE that had not yet synced the message. Gate readiness on the configured PXE sync tip instead.
…rom the blob (A-1384) Post-flip the checkpoint blob carries a per-block L1-to-L2 message tree root, since any block within a checkpoint can insert messages. The archiver reconstruction still read the root once from the checkpoint's first block and applied it to every block, a stale pre-flip per-checkpoint assumption. Follower nodes (prover / sync-only) that rebuild a non-first message-inserting block via retrievedToPublishedCheckpoint therefore got the first block's root, so the world-state synchronizer inserted the correct message, recomputed the real root, and mismatched the reconstructed header (block state does not match world state), forking the node from the sequencer on the first message-consuming block. Reconstruct each block's tree root from its own blob data instead.
…r (A-1384) The world-state integration test drives the streaming synchronizer through MockPrefilledArchiver, but setPrefilled only seeded the legacy per-checkpoint message map and never registered Inbox buckets. Post-flip the synchronizer reconstructs each block's consumed L1-to-L2 bundle from buckets via getInboxBucketByTotalMsgCount, which returned undefined against the empty bucket map, so every block synced with an empty bundle and its reconstructed state diverged from the committed header (block state does not match world state). Register a genesis sentinel bucket plus one bucket per message-carrying checkpoint with cumulative totalMsgCount matching the block's post-insertion leaf count.
…icitly (A-1384) getEpochProofPublicInputs anchored only the start of the consumed rolling-hash chain segment; a wrong endInboxRollingHash was caught transitively, surfacing as a bare Rollup__InvalidProof. Compare it against the hash recorded at propose for the epoch's last checkpoint and revert with Rollup__InvalidEndInboxRollingHash, mirroring the endArchive check. Test helpers that submitted a zero end hash now source it from the proposed header.
…ber slot (A-1384) inboxMsgTotal and inboxConsumedBucket occupied a storage slot of their own in CompressedTempCheckpointLog, costing an extra zero-to-nonzero SSTORE on every propose and a cold SLOAD when reading the parent checkpoint's total. Declare them next to CompressedSlot slotNumber so all three share one slot (20 of 32 bytes), which propose already touches for the slot-progression check; the checkpoint record drops from nine slots to eight. Both counts keep their full uint64 width, and feeHeader stays at its previous slot offset. The pipelined-simulation state override writes that slot as a whole word, so it now packs the two counts alongside the slot number instead of silently zeroing them, and accepts them as override inputs.
…nt range (A-1384) The prover node resolved the boundary Inbox buckets for a checkpoint's leaf-count range itself before asking for the messages between them. The message source now takes the leaf counts directly and reports an unresolvable boundary, so drop the two-step lookup.
…in the checkpoint builder (A-1384) Every block now carries its own L1-to-L2 message bundle, so the up-front whole-checkpoint insertion has no callers left. Delete the `insertMessagesPerBlock` toggle and the checkpoint-wide `l1ToL2Messages` argument of `startCheckpoint`/`startNewCheckpoint` (which would otherwise be silently double-counted into the checkpoint's rolling hash), and promote the bundle from `addBlock`'s options bag to a required argument, since a block always states what it consumes. `openCheckpoint` keeps the list for the resume path, where it seeds the rolling hash of blocks already built, and now rejects a non-empty list when starting fresh. Tests that relied on the up-front path insert the bundle through the checkpoint's first block instead.
… in propose simulation (A-1384) `ProposeLib.validateInboxConsumption` reads the parent checkpoint's recorded `inboxMsgTotal` and caps `bucket.totalMsgCount - parentTotal` at the per-checkpoint limit. When the sequencer simulates a propose against a parent that is proposed but not yet mined, the parent has no temp-checkpoint-log cell on L1, so that total read as zero and the consumed count came out as the Inbox's entire history — a spurious `Rollup__TooManyInboxMessagesConsumed` revert once the Inbox has accumulated more than one checkpoint's cap of messages. Carry the total on `ProposedCheckpointData` (derived by the archiver from the checkpoint's last block L1-to-L2 leaf count, which is what `propose` records) and feed it into the pending temp-log override alongside the slot number it shares a word with.
…propose harnesses (A-1384) Both propose harnesses warped to the checkpoint's timestamp and only then sent their L1-to-L2 messages, so the checkpoint referenced a bucket still accumulating in the same L1 block. Propose rejects that reference: the snapshot could still be overwritten in place. Sending the messages first puts them in an earlier L1 block, which is what the sequencer does anyway, since it never selects a bucket younger than the inbox lag.
spalladino
force-pushed
the
spl/a-1384-flip-streaming-inbox
branch
from
July 28, 2026 12:37
1cc2caa to
bcc37cf
Compare
spalladino
force-pushed
the
spl/a-1383-validator-streaming
branch
from
July 28, 2026 12:38
812d923 to
678fd72
Compare
Contributor
Author
|
Superseded by #25037. The Fast Inbox stack has been regrouped from 20 per-issue PRs (plus 2 umbrellas) down to 3 area PRs plus an umbrella, and rebased onto New structure: #25036 (circuits + L1) → #25037 (node + flip) → #25038 (cleanup), with #25039 as the full-stack umbrella. The original branch for this PR is left on the remote as a recovery point. Closing here to cut the rebase and review overhead of maintaining 22 PRs; the work is not abandoned. |
spalladino
added a commit
that referenced
this pull request
Jul 28, 2026
Deletes the legacy L1 Inbox path now that `propose` enforces streaming-inbox consumption (AZIP-22 Fast Inbox, FI-16). Stacked on `spl/a-1385-streaming-e2e`; part of the Fast Inbox stack (#24784..#24790 below this one). ## What is deleted - **Frontier trees**: the `trees` mapping, `forest`, `HEIGHT`/`SIZE`/`EMPTY_ROOT`, the per-message tree insert, `getRoot()`, and the whole `consume()` flow. - **LAG / inboxLag**: the `LAG` immutable and `_lag`/`_height` constructor args (the Inbox constructor is now `(rollup, feeAsset, version, bucketRingSize)`), `RollupConfigInput.inboxLag` and its `RollupCore` pass-through, and `Constants.L1_TO_L2_MSG_SUBTREE_HEIGHT` from the Inbox construction. - **Dead event field**: the `MessageSent.checkpointNumber` (former `inProgress`) argument, which became meaningless once `consume()` stopped advancing it. - **Orphaned errors**: `Inbox__Unauthorized`, `Inbox__MustBuildBeforeConsume`, `Rollup__InvalidInHash`. ## What is re-homed / kept - The compact message index now reads the current bucket's running total (`totalMsgCount`) instead of a parallel counter; `InboxState.inProgress` and `getInProgress()` are gone. - The 128-bit `rollingHash`, `InboxState.{rollingHash, totalMessagesInserted}`, `getState()`, and `getTotalMessagesInserted()` are **kept**: the node still consumes them for message sync and L1-reorg detection. Their removal is the node cleanup's job (FI-18). - `FrontierLib` is **kept** — it still backs the base-parity (`test/Parity.t.sol`) and merkle (`test/merkle/Frontier.t.sol`) tests, which are unrelated to the Inbox (parity-circuit territory, FI-17). The plan's "delete the file" assumption is contradicted by grep. ## TS follow-through - `ethereum` InboxContract: drop `getLag()`, drop the `MessageSent.checkpointNumber` decode, stop reading `inProgress`; `InboxContractState.treeInProgress` is now optional (no longer tracked on-chain). - Stop threading `inboxLag` through `queries.getL1ContractsConfig` and the L1 deploy env. The broader `AZTEC_INBOX_LAG` config sweep (`ethereum/src/config.ts`, `foundation`, `stdlib`, ~30 e2e configs) is deferred to FI-18, which explicitly owns it. - Archiver decode derives the message's checkpoint number from the compact index (the event no longer carries it), keeping the legacy per-checkpoint store shape untouched for FI-18. - Removed the obsolete `advanceInboxInProgress` cheat code + its inbox-drift tests, and the orphaned archiver `inHash`-mismatch sync test (the cross-check was removed at the flip). ## Gas Deleting the frontier insert is a large `sendL2Message` win. Whole-test gas (`forge test` on `InboxBuckets.t.sol`), before → after: | Case | Before | After | | --- | --- | --- | | First-ever message | 175,251 | 116,835 | | First message of a new L1 block | 329,460 | 195,555 | | Absorb into an existing bucket | 286,673 | 149,968 | | Rollover mid-block (256 messages) | 18,923,988 | 3,068,480 | Per-call `sendL2Message` gas after cleanup: first-ever 99,526; first-of-new-block 53,763; existing-bucket absorb 9,273; rollover 53,801. ## Testing - `forge build` + `forge test` green: 887 passed, 0 failed. This fixes 4 pre-existing baseline failures the flip stranded (`fee_portal`/`TokenPortal` deposit tests using tree-relative indices + the old event shape). - `@aztec/ethereum` builds clean; `config`/`queries` unit tests green. - `@aztec/archiver` has no self-owned type errors; `message_store` (36), `archiver-sync` (59), and the decode/struct suites (58) all pass. (Pre-existing `noir-protocol-circuits-types` stale-artifact errors are unaffected.) - e2e not run locally. The stability gate ("a few days of green e2e/networks before deleting the fallback") applies at merge time for this stacked line, not at PR creation. ## Review follow-up (phase-2 final review) Deleting the inbox-drift bot test left `bot.test.ts`'s `cheatCodes` variable unused (lint error); a follow-up commit removes it. ## Leftover sweep (post-review) - Corrected the `PublicInputArgs` natspec in `IRollup.sol`: it still described `previousInboxRollingHash` / `endInboxRollingHash` as deliberately unvalidated "until the Fast Inbox flip", but the flip (#24789, below this branch) added exactly that validation — `EpochProofLib` anchors the start boundary against the propose-time record, and the end boundary is covered transitively by the stored checkpoint header hashes. The comment now describes the implemented behavior. - The 128-bit rolling hash this PR deliberately kept (see "What is re-homed / kept") is now removed at the node-cleanup PR #24793, where its last TS readers disappear. Replaces #24791.
spalladino
added a commit
that referenced
this pull request
Jul 28, 2026
…387) FI-17 of the Fast Inbox (AZIP-22) stack. Stacked on #24791 (A-1386); it removes the legacy `inHash` from the checkpoint header now that the consensus flip (#24789, A-1384) has switched validation to the streaming rolling hash. After this PR, `inboxRollingHash` is the checkpoint's only inbox commitment. ## Consensus-format change - The checkpoint header loses its `inHash` field across all three preimage layers in lockstep: noir `CheckpointHeader` (`checkpoint_header.nr`), TS `CheckpointHeader` (`stdlib/src/rollup/checkpoint_header.ts`), and Solidity `ProposedHeaderLib` (struct + hash packing). The field ordering is otherwise unchanged; the header shrinks by one field (32 bytes: `CHECKPOINT_HEADER_SIZE_IN_BYTES` 380 to 348, `CHECKPOINT_HEADER_LENGTH` 14 to 13). - The header-hash fixtures were regenerated from `checkpoint_header.test.ts` (the established workflow) and pasted into the noir tests; the p2p golden-byte fixture (`wire_compat_fixtures.ts`) was refreshed for the checkpoint-proposal serialization, which shrinks by exactly the one removed field. The block-proposal wire fixture is unchanged (block-level `inHash` is out of scope, see A-1388). ## Circuits and constants - Strips the dead unconstrained `in_hash` pass-through from the sized `InboxParity<S>` circuit and `ParityPublicInputs` (noir + TS + the hand-written `noir-protocol-circuits-types` conversion wrappers). The circuit is kept (sized-parity, per FI-06's topology decision), so no VK-tree indices move. - Retires `NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP` (replaced everywhere by `MAX_L1_TO_L2_MSGS_PER_CHECKPOINT`, same value 1024) and the now-dead `NUM_MSGS_PER_BASE_PARITY`, `NUM_BASE_PARITY_PER_ROOT_PARITY`, and `L1_TO_L2_MSG_SUBTREE_ROOT_SIBLING_PATH_LENGTH`. Generated constants (`constants.gen.ts`, `ConstantsGen.sol`) were regenerated via `remake-constants`. - Deletes `FrontierLib.sol` and its tests (`Parity.t.sol`, the `Frontier` harness and merkle test) now that their last users are gone. ## Verification - Noir: `types::checkpoint_header` (5/5), `rollup_lib::parity` and `checkpoint_root::parity_tests` (15/15), and the checkpoint-root header-assembly path pass locally. The full `checkpoint_root` suite (68 tests) is too slow to run in full locally and rides CI. - Solidity: `forge test` green across `Rollup`, `RollupFieldRange`, `Inbox`, `InboxBuckets`, `RollupGetters` (76/76), covering header hashing and the regenerated checkpoint fixtures. - Jest: `checkpoint_header`, the p2p wire-compat suites, and the parity/messaging suites pass (43 tests), confirming the fixture regeneration is byte-consistent. - VK/artifact/Prover.toml regeneration and the generated Noir ABI types ride CI (local `yarn build` in `noir-protocol-circuits-types` / `simulator` / `prover-client` is expected to fail on stale circuit artifacts until then); e2e and the broken-zone typecheck are the CI-of-record for those. ## Stack Part of the Fast Inbox stack #24784 through #24791 (umbrella #24774): A-1379 (#24784), A-1380 (#24785), A-1381 (#24786), A-1382 (#24787), A-1383 (#24788), A-1384 flip (#24789), A-1385 (#24790), A-1386 (#24791, base of this PR). Resolves A-1387. ## l1 publisher inbox consumption via the streaming selector (CI fix, pass-8) The A-1387 inHash-removal rewrite left `l1_publisher.integration.test.ts` hardcoding `previousInboxRollingHash = Fr.ZERO` and `bucketHint = 0n` while consuming real L1→L2 messages, so message-consuming checkpoints reverted on L1 with `Rollup__InvalidInboxRollingHash` / `UnconsumedInboxMessages` (masked every prior CI round behind earlier failures). `INBOX_LAG_SECONDS` is an L1-time censorship cutoff, not whole checkpoints, so no checkpoint-depth shift register reproduces the aged bucket set. The test now mirrors the real Inbox buckets into its `MockL1ToL2MessageSource` and reuses 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. Box: 13/13, verified at a-1387 and a-1388. Replaces #24792.
spalladino
added a commit
that referenced
this pull request
Jul 28, 2026
Node cleanup for the Fast Inbox (AZIP-22) project — removes the legacy L1-to-L2 message paths the flip (#24789) left dead. Sits on the Fast Inbox stack (#24784..#24792); base is `spl/a-1387-circuits-cleanup`. ## What's removed - **stdlib / p2p**: `computeInHashFromL1ToL2Messages` and the whole `in_hash.ts`; the `inHash` field on `BlockProposal` (constructor, signed payload, wire, and the `createBlockProposal` validator-interface argument); the `CheckpointProposal.getBlockProposal` `inHash` pass-through; the padded per-checkpoint `InboxLeaf` helpers (`smallestIndexForCheckpoint` / `indexRangeForCheckpoint` / `checkpointNumberFromIndex`); the legacy `getL1ToL2Messages(checkpointNumber)` member from the `L1ToL2MessageSource` and archiver RPC interfaces. - **archiver**: the legacy per-checkpoint `getL1ToL2Messages` flow, the padded per-checkpoint index invariants, the `inboxTreeInProgress` readiness gate + `L1ToL2MessagesNotReadyError`, and the 128-bit keccak rolling hash. `InboxMessage` now carries only the compact global index and the full-width consensus rolling hash (the vacuous derived `checkpointNumber` and the 128-bit `rollingHash` are gone). Reorg detection compares the local consensus rolling hash and total against the Inbox's current rolling-hash bucket (new `getBucket` / `getCurrentBucketSeq` / `getCurrentBucket` wrappers) instead of the 128-bit `getState`. - **sequencer / validator**: the dead `inHash = Fr.ZERO` threading through the checkpoint proposal job and the validator/validation-service create-proposal path, plus the dead `in_hash_mismatch` validation-failure reason. - **world-state**: the no-op first-in-checkpoint padding alias and the obsolete non-first-block-empty-bundle transitional test (the production assertion was already removed at the flip). - **node**: the public-calls simulator's dead next-checkpoint message fetch and its now-unused `l1ToL2MessageSource` dependency. - **config / env**: `AZTEC_INBOX_LAG` / `inboxLag` from ethereum config, foundation env vars, the network-consensus-config list, the l1-contracts + spartan network defaults, and the e2e option plumbing. - **docs**: `THREAT_MODEL.md` and the archiver README rewritten from the `inHash == inbox.consume(...)` model to the consensus rolling-hash / bucket model. ## Format / store / wire notes - **Store version bump**: `ARCHIVER_DB_VERSION` 8 → 9 because `InboxMessage` serialization dropped `rollingHash` + `checkpointNumber` and the `inboxTreeInProgress` singleton is gone. No migration — nodes resync (fresh rollup instance per release line, same no-migration policy as the rest of the stack). - **p2p wire format**: dropping the (zeroed-since-flip) `inHash` shrinks the block-proposal bytes. Done as a plain removal at a release boundary (fresh networks), matching the A-1381 optional-tail precedent; the golden `wire_compat_fixtures.ts` buffers were regenerated. The checkpoint-proposal fixture is unaffected (it never carried `inHash`). - **L1 follow-through: done (leftover sweep, below).** The on-chain Inbox 128-bit `messagesRollingHash` accumulation, `InboxState.rollingHash`, and the `MessageSent.rollingHash` event arg are now removed in this PR — this is the earliest branch where it is safe, since the TS reads disappear here. `getState()` / `getTotalMessagesInserted()` are kept: `chain_monitor` and fast node sync still read the message count. ## Testing Locally green (unit suites that run without `@aztec/bb-avm-sim`): archiver (561), stdlib p2p + interfaces (76), world-state synchronizer + native (74), validator-client unit (38), ethereum config (6). Suites that import `@aztec/bb-avm-sim` (p2p libp2p, sequencer-client, node simulator, validator integration) and full typecheck of the packages downstream of the pinned-VK blocker are CI-validated. No e2e / VK regen locally. ## Review follow-up (phase-2 final review) Four commits were appended by the final review pass: - The env sweep removed `AZTEC_INBOX_LAG` from `scripts/network-defaults.json` while both L1 deploy-script test setUps still `readUint` the key, reverting before any test ran; the reads are gone. - The p2p attestation store version is bumped 2 -> 3: it persists proposal/attestation buffers whose formats changed in this stack (checkpoint header lost `inHash`; block-proposal wire dropped it), and stored checkpoint attestations decode without a tolerant fallback. - `l1_publisher.integration.test.ts` is reworked for streaming consumption: each checkpoint consumes every message sent while it was built, threading the previous checkpoint's rolling hash and reading the propose `bucketHint` from the Inbox's current bucket (the legacy inboxLag shift register is gone). - Remaining `inboxLag`/`inHash` references are swept from the spartan environment profiles, the governance-upgrade tutorial, the validator/sequencer READMEs, and stale e2e comments. ## l1 publisher test dissolved into A-1387 (pass-8) This branch's rewrite of `l1_publisher.integration.test.ts` is superseded by the corrected streaming-selector version landed at A-1387 (#24792): the branch's own message-reconstruction rework commit is dropped and the file is byte-identical to A-1387's here (segment diff for this file is zero). The A-1384 world-state mock bucket registration coexists with this branch's legacy-message-path deletion (the buckets survive the cleanup). First real box verification of this suite at A-1388: l1_publisher 13/13; world-state integration 12/12 at top. ## Leftover sweep (post-review) - Removed the legacy 128-bit keccak inbox rolling hash from L1: `InboxState.rollingHash`, the `MessageSent` `bytes16 rollingHash` event arg, and its keccak accumulation in `Inbox.sol`, updating the four Solidity test suites that asserted it (`Inbox.t`, `InboxBuckets.t`, `TokenPortal.t`, `depositToAztecPublic.t`) and the three live docs-example inbox ABIs that embedded the old event shape. The node's message sync and L1-reorg detection already run entirely on the full-width consensus rolling hash from the buckets. - Deleted the dead `InboxLeaf` class from stdlib (this branch had already removed its per-checkpoint index helpers) and fixed a stale `InboxLeaf` mention in the archiver retrieval JSDoc. - Clarified the checkpoint-builder comments: the up-front whole-checkpoint message insertion (`insertMessagesPerBlock = false`) is only exercised by tests; production always streams messages per block. Replaces #24793.
spalladino
added a commit
that referenced
this pull request
Jul 28, 2026
…neration (A-1435) Deduplicates how the committed rollup protocol-circuit sample inputs (`noir-projects/noir-protocol-circuits/crates/rollup-*/Prover.toml`) get regenerated. 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. - Makes the prover-client `regenerate_rollup_sample_inputs.test.ts` suite the sole owner of every rollup circuit at or above the transaction merge: the block-root variants, block-merge, both checkpoint roots, checkpoint-merge, tx-merge, and root. - Stops the e2e `full.test.ts` dump from writing the block-root and checkpoint samples it overlapped on. 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 (`rollup-tx-base-private`, `rollup-tx-base-public`). - Adds a dedicated three-tx scenario to the suite to restore `rollup-tx-merge` coverage, which was dropped when `orchestrator_single_checkpoint.test.ts` was deleted and replaced by this suite. A block with three txs forces one tx-merge before the two-input block root, whereas one- or two-tx blocks feed the block root directly. - Updates the regen docs (`barretenberg/cpp/CLAUDE.md`, the `update-prover-toml` and `gate-counts` skills, and the `updateProtocolCircuitSampleInputs` JSDoc) to the two-command split and removes the dead `orchestrator_single_checkpoint.test.ts` references. Ownership after this change (each committed rollup `Prover.toml` has exactly one writer, no overlap): - prover-client suite (`AZTEC_GENERATE_TEST_DATA=1 yarn workspace @aztec/prover-client test regenerate_rollup_sample_inputs`): the 11 block-root / block-merge / checkpoint / tx-merge / root tomls. - e2e (`AZTEC_GENERATE_TEST_DATA=1 FAKE_PROOFS=1` full.test): private-kernel circuits plus `rollup-tx-base-private` / `rollup-tx-base-public`. Part of the Fast Inbox (AZIP-22) cleanup stack, on top of #24784..#24794. Validation: neither regeneration path runs in this environment (the prover-client build here has stale artifacts and e2e needs a full L1/anvil stack), so the actual fixture regeneration and the downstream `nargo execute` checks are validated on CI. Locally verified statically that the suite's scenario `dump` arrays plus the e2e list exactly cover all 13 committed rollup tomls with no overlap, and that a three-tx block routes through `getTxMergeRollupProof` and captures `rollup-tx-merge` test data. Post-flip a zero-tx non-first block carrying a bundle is a live shape routed through the msgs-only block root (wired in #24789), so the regen suite gains a per-block-distribution scenario dumping `rollup-block-root-msgs-only`, and the circuit joins the CI `nargo execute` list — the first regen run must create and commit its `Prover.toml`. The documented e2e regen command also moves to the file's real location (`single-node/prover/server/full.test`; the old `e2e_prover/full.test` jest pattern matches nothing). Replaces #24795.
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.
Coordinated cutover of the Fast Inbox (AZIP-22) project (issue A-1384, FI-14). Flips the authoritative path across L1, circuits, and the node from the legacy
inHash/frontier-tree consumption to the streaming rolling-hash Inbox.Stacked on the node phase: #24784 (A-1379) -> #24785 (A-1380) -> #24786 (A-1381) -> #24787 (A-1382) -> #24788 (A-1383). Base is
spl/a-1383-validator-streaming.Merge gate
L1 (
l1-contracts)ProposeLib.proposecallsvalidateInboxConsumptionagainst the parent checkpoint's consumed position (read from the parent temp-log record) instead ofinbox.consume()+ theRollup__InvalidInHashcheck; the returned consumed total is stored in the new record.ProposeArgsgains an unsignedbucketHintcalldata field (out of the attested payload digest).TempCheckpointLog/CompressedTempCheckpointLogcarry{inboxRollingHash, inboxMsgTotal, inboxConsumedBucket}; genesis is{0,0,0}.EpochProofLib.getEpochProofPublicInputsanchors the rolling-hash chain start to the record of checkpointstart - 1(Rollup__InvalidPreviousInboxRollingHash).Inbox.sendL2Messagereturns and emits the compact cumulative message index.Circuits (
noir-projects)MAX_L1_TO_L2_MSGS_PER_BLOCK1024 -> 256.L1ToL2MessageBundledropsnum_real_msgs; a singlenum_msgsdrives the compact tree append and the message-sponge absorb.SpongeBlob::absorb_block_end_dataabsorbs the L1-to-L2 tree root for every block.Node (
yarn-project)streamingInboxflag is removed across foundation/stdlib config + the sequencer/validator plumbing.getInboxBucketByTotalMsgCount(cross-checkpoint); feeds inHash zero; relaxeswaitForMinTxsfor message-only blocks; threads the consumed bucket seq as the proposebucketHint. Automine mirrors the selection.appendL1ToL2MessagesToTree/handleL2BlockAndMessagesappend real leaves at compact indices; the synchronizer derives each block's bundle from its leaf-index range.MAX_L1_TO_L2_MSGS_PER_BLOCK = 256,L1ToL2MessageBundledropsnumRealMsgs; provable per-checkpoint ceiling 2457 -> 2234.@aztec/l1-artifactsregenerated for the newProposeArgs(gitignored generated output; theethereumpackage builds against it).Verification
forge test: full propose/inbox suite green (ProposeInboxConsumption, Inbox, InboxBuckets, Rollup incl. a new anchoring negative, RollupFieldRange, FeeRollup, ValidatorSelection, escape-hatch, tmnt207/419).nargo test: parity, block_root structure, msgs_only green on touched crates.(fromExclusive, toInclusive]and pipelining-parent sourcing verified correct.VK regen and
Prover.tomlsample-input regen ride CI (localbb write_vkOOMs onrollup_root).Node prover + archiver keying (completed in this PR)
startCheckpointno longer inserts messages up front.getL1ToL2MessageCheckpointbinary-searches the block records for the first block whose L1-to-L2 leaf count exceeds the message index (portal claim helpers depend on this); the prover-node derives the checkpoint's consumed messages from the Inbox buckets.Codex (gpt-5.6-terra) reviewed the prover/keying rework: no consensus defect; slice partitioning, bucket-range agreement, fork ordering, frontier height, and the checkpoint boundary rule all verified.
Left for FI-18 cleanup
The
message_storeInboxLeafindex-formula methods (smallestIndexForCheckpointetc.) have no correctness-critical live callers post-flip; the public-simulator's next-checkpoint fetch degrades safely without them (simulates without the not-yet-consumed messages). Deleted in FI-18.Accepted residual risk
A-1390 (unconsumed-bucket overwrite protection) stays post-flip: a consumption backlog older than the Inbox ring can overwrite unconsumed buckets and halt proposals until an upgrade. Acceptable for non-production lines.
Review follow-up (phase-2 final review)
A final review pass appended
fix(fast-inbox): thread per-block message sponges and wire the msgs-only block root in the prover:BlockProvingStateand the lightweight builder, and the orchestrator never selected the msgs-only block root. Both now accept it and route it throughBlockRootMsgsOnlyRollupPrivateInputs.bucketHintparameter added toenqueueProposeCheckpointhere is now passed by the publisher unit/integration tests and the e2e synching test (the integration suite's consumption model is reworked for streaming semantics in chore(fast-inbox): delete legacy node message paths (A-1388) #24793).Bucket-hint carry on final-block failure (CI fix)
The
cross_chain_messagese2e ("builds multiple blocks per slot with L1 to L2 messages") caught a product regression: when a checkpoint's first block is message-only (consuming a bucket) and the final block fails to build, thebucketHinton the propose payload was reconstructed from the last successfully-built block's streaming cursor and lost the already-consumed bucket, so L1validateInboxConsumptionrejected the checkpoint.CheckpointProposalBroadcastnow carriesbucketHintexplicitly, sourced from the streaming state'slastBucketRef.bucketSeqat both propose sites (fisherman and main), so the consumed bucket survives a final-block build failure. Acheckpoint_proposal_job.test.tsregression case drives message-only-first-block + final-block-failure and asserts the enqueued hint is the consumed bucket (RED reverting the fix:Expected 2n, Received 0n).L1→L2 readiness gate on the PXE sync tip (CI fix)
The shared cross-chain e2e helper (
message_test_helpers.ts) evaluatedisL1ToL2MessageReadyagainst the default'latest'tip, while these suites anchor the PXE tosyncChainTip: 'checkpointed'. Pre-flip every message entered at the first block of the next checkpoint, so'latest'readiness coincided with checkpointed availability; post-flip per-block insertion lets the proposed chain reach a message's consume-checkpoint a full checkpoint before it is published on L1, so readiness flipped true while the PXE had not yet synced the message and the private consume simulated against a missing membership witness (No L1 to L2 message found). This deterministically failedl1_to_l2.test.tsfrom this branch up. The helper now gates on the configured PXE sync tip via a newCrossChainMessagingTest.pxeSyncChainTipgetter (defaulting to'latest', so suites that do not pin a tip keep their semantics). Test-support only; box-verified green on all three cross-chain suites (l1_to_l2,streaming_inbox,l1_to_l2_inbox_drift).Per-block L1→L2 message tree root in archiver reconstruction (consensus fix)
The archiver's checkpoint reconstruction (
retrievedToPublishedCheckpointindata_retrieval.ts) read the L1→L2 message tree root once from the checkpoint's first block and applied it to every block — a stale pre-flip per-checkpoint assumption. Post-flip the blob carries a per-block root (any block in a checkpoint can insert messages). A follower node (prover / sync-only) that rebuilds a non-first message-inserting block therefore got the first block's root, so its world-state synchronizer inserted the correct message, recomputed the real root, and mismatched the reconstructed header (block state does not match world state) — forking the node from the sequencer on the first message-consuming block. In production this would fork every prover/follower on the first cross-chain-consuming block; it surfaced here ascross_chain_public_messageandtoken_bridgefailing (only follower nodes reconstruct via the archiver, which is why proposal validation — building from each validator's own world-state fork — never caught it). Reconstruction now uses each block's ownl1ToL2MessageRootfrom the blob. Adata_retrieval.test.tsunit case asserts each reconstructed block's tree root matches its own blob root (RED: non-first blocks got the first block's root); both e2es green at a-1384.World-state mock Inbox buckets (CI fix, pass-8)
world-state/src/test/integration.test.tsdrives the streaming synchronizer throughMockPrefilledArchiver, butsetPrefilledonly seeded the legacy per-checkpoint message map and never registered Inbox buckets. Post-flip the synchronizer reconstructs each block's consumed L1→L2 bundle from buckets (getInboxBucketByTotalMsgCount), which returned undefined against the empty bucket map, so every block synced an empty bundle and the reconstructed state diverged from the header (block state does not match world state, then a 600s timeout).setPrefillednow registers a genesis sentinel plus one bucket per message-carrying checkpoint (cumulativetotalMsgCount= the block's post-insertion leaf count), rebuilt from the full prefilled chain so reorg re-prefills stay aligned. Box: 12/12 (reorg 32s→2.4s), verified at a-1384 and top.Leftover sweep (post-review)
isFirstBlockparameter from the blob-data test fixtures (makeBlockEndBlobData/makeBlockBlobDataand the archivermakeBlockBlobDataFromBodyhelper). Post-flip every block carries its own L1-to-L2 root, so the flag had no effect on output; the earlier lint fix had only underscore-bound it "for call-site compatibility". The two now-identical blob-data tests are collapsed into one.