Skip to content

feat(fast-inbox): sequencer streaming message selection, flag off (A-1382) - #24787

Closed
spalladino wants to merge 4 commits into
spl/a-1381-proposal-bucket-reffrom
spl/a-1382-sequencer-streaming
Closed

spalladino wants to merge 4 commits into
spl/a-1381-proposal-bucket-reffrom
spl/a-1382-sequencer-streaming

Conversation

@spalladino

Copy link
Copy Markdown
Contributor

Implements A-1382 (FI-12): sequencer streaming Inbox message selection behind a streamingInbox flag, default off. Part of the Fast Inbox stack (AZIP-22).

Flag semantics

  • One shared env var STREAMING_INBOX -> streamingInbox: boolean (default false), defined once in stdlib's sharedSequencerConfigMappings so the sequencer reads it now and the validator (A-1383) maps the same env. Flag off is byte-identical: the new paths are gated at checkpoint start and per block.
  • With the flag on, checkpoints are expected to fail L1 submission (on-chain propose still enforces the legacy per-checkpoint consumption until the flip, A-1384). The streaming path is exercised with unit/mock tests only.

Selection policy (mirrors ProposeLib.validateInboxConsumption)

New pure selector sequencer-client/src/sequencer/inbox_bucket_selector.ts, run per block:

  • Pick the newest lag-eligible bucket (getLatestInboxBucketAtOrBefore(now - INBOX_LAG_SECONDS)); on the checkpoint's final block, also consider the cutoff bucket and take the newer, so consumption reaches the censorship floor.
  • Walk back from the candidate to the newest bucket that fits both the per-block cap (bucket.totalMsgCount - parent.totalMsgCount) and the per-checkpoint cap (bucket.totalMsgCount - checkpointStartTotalMsgCount). If even the first forward bucket overshoots the per-checkpoint cap, consume nothing — matching L1's cap-escape (next.totalMsgCount - parentTotal > MAX_L1_TO_L2_MSGS_PER_CHECKPOINT).
  • The inclusive <= comparisons make a bucket exactly INBOX_LAG_SECONDS old eligible and a bucket exactly at the cutoff mandatory, matching L1's strict next.timestamp > cutoff.

Cutoff is computed as getTimestampForSlot(slot - 1) - INBOX_LAG_SECONDS, matching ProposeLib exactly (not the consensus timetable's getBuildFrameStart, which subtracts an extra ethereum-slot). Boundary vectors pinned against A-1371 resolution section 13.

Cutoff floor

On the checkpoint's final block (including the block that reaches the per-checkpoint block cap) the cutoff is a consumption floor, so the checkpoint's own header would pass the L1 censorship assert post-flip.

Builder interface change

  • ICheckpointBlockBuilder.buildBlock gains an optional per-block l1ToL2Messages bundle; ICheckpointsBuilder.startCheckpoint gains insertMessagesPerBlock. Threaded through FullNodeCheckpointsBuilder/CheckpointBuilder and the lightweight builder (first-in-checkpoint bundle padded, non-first compact; the inHash/rolling hash recompute over the accumulated logical messages).
  • An optional signed bucket reference is threaded onto block proposals (Validator.createBlockProposal -> ValidationService -> BlockProposal.createProposalFromSigner).

Constant

INBOX_LAG_SECONDS = 12 added to constants.nr and regenerated into the TS constants (constants.gen.ts). Not emitted to ConstantsGen.sol (Solidity whitelist); ProposeLib keeps its local copy pending consolidation (A-1434).

Open item / to verify on CI

  • Non-genesis cross-checkpoint parent-bucket sourcing is not wired: there is no by-rolling-hash archiver lookup and legacy parents do not sit on a bucket boundary, so only the genesis base case is resolved and non-genesis throws (safely caught -> skipped proposal). This is a flip-time concern.
  • Local build/typecheck of sequencer-client, validator-client, prover-client is blocked by the known stale-artifact/@aztec/bb-avm-sim breakage, so CI is the typecheck of record for the job/builder/proposal edits. The pure selector unit tests and the stdlib config changes were verified locally.

@spalladino
spalladino requested a review from LeilaWang as a July 18, 2026 08:15
@spalladino spalladino changed the title feat: sequencer streaming message selection, flag off (A-1382) feat(fast-inbox): sequencer streaming message selection, flag off (A-1382) Jul 19, 2026
@spalladino
spalladino force-pushed the spl/a-1382-sequencer-streaming branch from 8cc06b5 to 2e9f13e Compare July 19, 2026 14:05
@spalladino
spalladino force-pushed the spl/a-1381-proposal-bucket-ref branch from 9eb8813 to 123af00 Compare July 19, 2026 14:05
@spalladino
spalladino force-pushed the spl/a-1382-sequencer-streaming branch from 2e9f13e to d06df3f Compare July 19, 2026 17:57
@spalladino
spalladino force-pushed the spl/a-1381-proposal-bucket-ref branch from 123af00 to 9bdee0d Compare July 19, 2026 17:57
@spalladino
spalladino force-pushed the spl/a-1382-sequencer-streaming branch from d06df3f to a88a509 Compare July 19, 2026 20:48
@spalladino
spalladino force-pushed the spl/a-1381-proposal-bucket-ref branch 2 times, most recently from 9973f4f to 3b13708 Compare July 20, 2026 15:30
@spalladino
spalladino force-pushed the spl/a-1382-sequencer-streaming branch 2 times, most recently from 19e9567 to 896ea89 Compare July 20, 2026 17:28
@spalladino
spalladino force-pushed the spl/a-1381-proposal-bucket-ref branch from 3b13708 to 4d4af43 Compare July 20, 2026 17:28
@spalladino
spalladino force-pushed the spl/a-1382-sequencer-streaming branch from 896ea89 to a1feb7d Compare July 20, 2026 21:21
@spalladino
spalladino force-pushed the spl/a-1381-proposal-bucket-ref branch 2 times, most recently from 4e21b51 to 8700011 Compare July 21, 2026 02:58
@spalladino
spalladino force-pushed the spl/a-1382-sequencer-streaming branch from a1feb7d to 4bbd6cd Compare July 21, 2026 02:59
@spalladino
spalladino force-pushed the spl/a-1381-proposal-bucket-ref branch from 8700011 to 7f36f7b Compare July 21, 2026 03:39
@spalladino
spalladino force-pushed the spl/a-1382-sequencer-streaming branch from 4bbd6cd to 00a7d98 Compare July 21, 2026 03:39
@spalladino
spalladino force-pushed the spl/a-1381-proposal-bucket-ref branch from 7f36f7b to e9ea2cf Compare July 27, 2026 20:53
@spalladino
spalladino force-pushed the spl/a-1382-sequencer-streaming branch from 00a7d98 to f0cccf4 Compare July 27, 2026 20:53
…ING_INBOX (A-1382)

Behind the shared `streamingInbox` flag (env `STREAMING_INBOX`, default off), the
sequencer selects L1-to-L2 messages from the Inbox rolling-hash buckets per block
instead of consuming the whole checkpoint's messages up front.

- Adds a pure `inbox_bucket_selector` that mirrors `ProposeLib.validateInboxConsumption`:
  newest lag-eligible bucket, per-block/per-checkpoint cap walk-back with cap-escape,
  and a last-block cutoff floor (cutoff = toTimestamp(slot-1) - INBOX_LAG_SECONDS).
- Adds `INBOX_LAG_SECONDS` (12) to constants.nr, regenerated into the TS constants.
- Threads a per-block message bundle through the checkpoint builder interface and an
  optional signed bucket reference onto block proposals.
- Flag off is byte-identical: the new paths are gated at checkpoint start and per block.
…inal block and defer bundle accumulation (A-1382)

- The censorship cutoff floor now applies on whichever block ends the checkpoint,
  including the block that reaches the per-checkpoint block cap, not only the
  timetable's last sub-slot, so the checkpoint always consumes through the cutoff.
- The lightweight builder accumulates a block's streaming message bundle only after
  the block is fully built, so a mid-build failure leaves the checkpoint's message
  list (and its inHash/rolling hash) consistent with the blocks actually built.
@spalladino
spalladino force-pushed the spl/a-1382-sequencer-streaming branch from f0cccf4 to 8b430fa Compare July 28, 2026 12:37
@spalladino

Copy link
Copy Markdown
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 merge-train/spartan including #25007 (the noir-projects fnd//labs/ split). This PR's diff is preserved verbatim as a single squashed commit in #25037, and this description is preserved in that commit's message.

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 spalladino closed this Jul 28, 2026
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 Aug 18, 2026
…1382)

Implements A-1382 (FI-12): sequencer streaming Inbox message selection behind a `streamingInbox` flag, default off. Part of the Fast Inbox stack (AZIP-22).

- One shared env var `STREAMING_INBOX` -> `streamingInbox: boolean` (default false), defined once in `stdlib`'s `sharedSequencerConfigMappings` so the sequencer reads it now and the validator (A-1383) maps the same env. Flag off is byte-identical: the new paths are gated at checkpoint start and per block.
- With the flag on, checkpoints are expected to fail L1 submission (on-chain `propose` still enforces the legacy per-checkpoint consumption until the flip, A-1384). The streaming path is exercised with unit/mock tests only.

New pure selector `sequencer-client/src/sequencer/inbox_bucket_selector.ts`, run per block:

- Pick the newest lag-eligible bucket (`getLatestInboxBucketAtOrBefore(now - INBOX_LAG_SECONDS)`); on the checkpoint's final block, also consider the cutoff bucket and take the newer, so consumption reaches the censorship floor.
- Walk back from the candidate to the newest bucket that fits both the per-block cap (`bucket.totalMsgCount - parent.totalMsgCount`) and the per-checkpoint cap (`bucket.totalMsgCount - checkpointStartTotalMsgCount`). If even the first forward bucket overshoots the per-checkpoint cap, consume nothing — matching L1's cap-escape (`next.totalMsgCount - parentTotal > MAX_L1_TO_L2_MSGS_PER_CHECKPOINT`).
- The inclusive `<=` comparisons make a bucket exactly `INBOX_LAG_SECONDS` old eligible and a bucket exactly at the cutoff mandatory, matching L1's strict `next.timestamp > cutoff`.

Cutoff is computed as `getTimestampForSlot(slot - 1) - INBOX_LAG_SECONDS`, matching `ProposeLib` exactly (not the consensus timetable's `getBuildFrameStart`, which subtracts an extra ethereum-slot). Boundary vectors pinned against A-1371 resolution section 13.

On the checkpoint's final block (including the block that reaches the per-checkpoint block cap) the cutoff is a consumption floor, so the checkpoint's own header would pass the L1 censorship assert post-flip.

- `ICheckpointBlockBuilder.buildBlock` gains an optional per-block `l1ToL2Messages` bundle; `ICheckpointsBuilder.startCheckpoint` gains `insertMessagesPerBlock`. Threaded through `FullNodeCheckpointsBuilder`/`CheckpointBuilder` and the lightweight builder (first-in-checkpoint bundle padded, non-first compact; the inHash/rolling hash recompute over the accumulated logical messages).
- An optional signed bucket reference is threaded onto block proposals (`Validator.createBlockProposal` -> `ValidationService` -> `BlockProposal.createProposalFromSigner`).

`INBOX_LAG_SECONDS = 12` added to `constants.nr` and regenerated into the TS constants (`constants.gen.ts`). Not emitted to `ConstantsGen.sol` (Solidity whitelist); `ProposeLib` keeps its local copy pending consolidation (A-1434).

- Non-genesis cross-checkpoint parent-bucket sourcing is not wired: there is no by-rolling-hash archiver lookup and legacy parents do not sit on a bucket boundary, so only the genesis base case is resolved and non-genesis throws (safely caught -> skipped proposal). This is a flip-time concern.
- Local build/typecheck of `sequencer-client`, `validator-client`, `prover-client` is blocked by the known stale-artifact/`@aztec/bb-avm-sim` breakage, so CI is the typecheck of record for the job/builder/proposal edits. The pure selector unit tests and the stdlib config changes were verified locally.

Replaces #24787.
spalladino added a commit that referenced this pull request Aug 18, 2026
… (A-1383)

Implements A-1383: the validator's streaming-Inbox acceptance conditions (AZIP-22 Fast Inbox), behind the shared `streamingInbox` flag (default off). Flag off ⇒ byte-identical behavior. Stacked on #24787 (A-1382).

- **Exists**: the referenced bucket resolves in the node's own Inbox view and its consensus rolling hash matches the reference. An unknown bucket is an immediate reject (`bucket_unknown`); the bounded-wait soft path is A-1393. The reference is trusted only as a `bucketSeq` lookup hint — timestamp/counts are read from the locally resolved bucket.
- **Moves forward**: the bucket's cumulative total is at least the parent block's (equal ⇒ empty bundle).
- **Not too new**: the bucket is at least `INBOX_LAG_SECONDS` old at validation time (`dateProvider.now()`, inclusive boundary).
- **Caps**: the per-block count and the running per-checkpoint total fit `MAX_L1_TO_L2_MSGS_PER_BLOCK` / `MAX_L1_TO_L2_MSGS_PER_CHECKPOINT`.

The derived bundle is fed into per-block re-execution (`insertMessagesPerBlock` threaded through `openCheckpoint` → `reexecuteTransactions`); the existing state-ref comparison after re-execution stays the final arbiter.

Before attesting, the minimum-consumption rule runs via the shared `isInboxConsumptionSufficient` predicate: the first unconsumed bucket must be absent, past the cutoff, or a cap-escape. A mandatory unconsumed bucket rejects the checkpoint (no attestation, `inbox_consumption_insufficient`).

The cutoff formula and the minimum-consumption/cap-escape rule moved to `stdlib/messaging/inbox_consumption`, so the sequencer's bucket selection and the validator share one source of truth. The sequencer's `selectStreamingBundle` now calls `getInboxCutoffTimestamp` instead of inlining it — behavior unchanged (selector suite still green).

The cutoff is `getTimestampForSlot(slot - 1) - INBOX_LAG_SECONDS` (mirroring `ProposeLib.validateInboxConsumption`), **not** `getBuildFrameStart`. The A-1371 §13 cross-layer vectors are pinned in `inbox_consumption.test.ts`.

Parent/last-consumed buckets are resolved from L1-to-L2 tree leaf counts (compact post-flip indexing == bucket cumulative total) via a new `getInboxBucketByTotalMsgCount` archiver lookup. A leaf count that does not land on a bucket boundary (a pre-flip padded parent) is rejected/deferred; the flip (A-1384) closes that gap.

`tsc -b` cannot complete locally due to pre-existing `noir-protocol-circuits-types` breakage (missing artifacts / stale generated types), which cascades to `archiver`/`validator-client`. stdlib builds clean; no errors in the changed files outside the broken zone. Runnable unit suites are green: `streaming_inbox_checks` (14), `inbox_consumption` (10), `proposal_handler` (34, incl. flag-off unchanged + new streaming/censorship tests), `message_store` (35), stdlib `archiver` interface (RPC schema). CI is the typecheck of record for the unbuildable zone.

Out of scope: bounded-wait (A-1393), the flip (A-1384), reorg rejection (A-1389), the full streaming checkpoint rebuild (A-1385).

Replaces #24788.
spalladino added a commit that referenced this pull request Aug 18, 2026
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`.

- **A-1431 (domain-separation decision) must resolve before this merges.** It is deliberately deferred and nothing is adopted here. If it adopts a separator, the rolling-hash link-encoding change must be inserted below this PR in the stack.

- `ProposeLib.propose` calls `validateInboxConsumption` against the parent checkpoint's consumed position (read from the parent temp-log record) instead of `inbox.consume()` + the `Rollup__InvalidInHash` check; the returned consumed total is stored in the new record.
- `ProposeArgs` gains an unsigned `bucketHint` calldata field (out of the attested payload digest).
- `TempCheckpointLog` / `CompressedTempCheckpointLog` carry `{inboxRollingHash, inboxMsgTotal, inboxConsumedBucket}`; genesis is `{0,0,0}`.
- `EpochProofLib.getEpochProofPublicInputs` anchors the rolling-hash chain start to the record of checkpoint `start - 1` (`Rollup__InvalidPreviousInboxRollingHash`).
- `Inbox.sendL2Message` returns and emits the compact cumulative message index.

- `MAX_L1_TO_L2_MSGS_PER_BLOCK` 1024 -> 256.
- `L1ToL2MessageBundle` drops `num_real_msgs`; a single `num_msgs` drives the compact tree append and the message-sponge absorb.
- `SpongeBlob::absorb_block_end_data` absorbs the L1-to-L2 tree root for every block.

- **Streaming is the only path**: the `streamingInbox` flag is removed across foundation/stdlib config + the sequencer/validator plumbing.
- **Sequencer**: sources the parent bucket from the fork's L1-to-L2 leaf count + `getInboxBucketByTotalMsgCount` (cross-checkpoint); feeds inHash zero; relaxes `waitForMinTxs` for message-only blocks; threads the consumed bucket seq as the propose `bucketHint`. Automine mirrors the selection.
- **Validator**: per-block acceptance + 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` / `handleL2BlockAndMessages` append real leaves at compact indices; the synchronizer derives each block's bundle from its leaf-index range.
- **Blob / constants / bundle**: per-block blob root, `MAX_L1_TO_L2_MSGS_PER_BLOCK = 256`, `L1ToL2MessageBundle` drops `numRealMsgs`; provable per-checkpoint ceiling 2457 -> 2234.
- **Archiver / TXE**: the dead inHash cross-check is removed; TXE appends unpadded compact leaves.
- **L1 ABI**: `@aztec/l1-artifacts` regenerated for the new `ProposeArgs` (gitignored generated output; the `ethereum` package builds against it).

- L1 `forge test`: full propose/inbox suite green (ProposeInboxConsumption, Inbox, InboxBuckets, Rollup incl. a new anchoring negative, RollupFieldRange, FeeRollup, ValidatorSelection, escape-hatch, tmnt207/419).
- Circuits `nargo test`: parity, block_root structure, msgs_only green on touched crates.
- Node jest (runnable locally): validator proposal_handler (34) + streaming checks (14), sequencer inbox selector (9), world-state native (56), blob-lib + stdlib. tsc for the broken-build-zone packages (prover-client, sequencer-client, validator-client, etc.) is CI's job of record.
- Codex (gpt-5.6-terra) reviewed both the L1/circuits layer and the node wiring: no consensus-critical issue; bucket cursor/range `(fromExclusive, toInclusive]` and pipelining-parent sourcing verified correct.

VK regen and `Prover.toml` sample-input regen ride CI (local `bb write_vk` OOMs on `rollup_root`).

- **Prover per-block message split**: the checkpoint's messages are sliced per block from the blocks' L1-to-L2 leaf-count ranges; each block root appends its own real slice at compact indices with per-block start/end snapshots and a full-height frontier hint. `startCheckpoint` no longer inserts messages up front.
- **Checkpoint keying from records**: `getL1ToL2MessageCheckpoint` binary-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.

The `message_store` `InboxLeaf` index-formula methods (`smallestIndexForCheckpoint` etc.) 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.

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.

A final review pass appended `fix(fast-inbox): thread per-block message sponges and wire the msgs-only block root in the prover`:

- The prover supplied the checkpoint-wide message sponge as every non-first block root's start sponge, which the block-merge/checkpoint-root continuity asserts reject whenever a non-first block carries messages. The sponge is now threaded per block.
- A zero-tx non-first block (the message-only shape this PR lets the sequencer produce) was rejected by `BlockProvingState` and the lightweight builder, and the orchestrator never selected the msgs-only block root. Both now accept it and route it through `BlockRootMsgsOnlyRollupPrivateInputs`.
- The required `bucketHint` parameter added to `enqueueProposeCheckpoint` here 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 #24793).

The `cross_chain_messages` e2e ("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, the `bucketHint` on the propose payload was reconstructed from the last successfully-built block's streaming cursor and lost the already-consumed bucket, so L1 `validateInboxConsumption` rejected the checkpoint. `CheckpointProposalBroadcast` now carries `bucketHint` explicitly, sourced from the streaming state's `lastBucketRef.bucketSeq` at both propose sites (fisherman and main), so the consumed bucket survives a final-block build failure. A `checkpoint_proposal_job.test.ts` regression 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`).

The shared cross-chain e2e helper (`message_test_helpers.ts`) evaluated `isL1ToL2MessageReady` against the default `'latest'` tip, while these suites anchor the PXE to `syncChainTip: '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 failed `l1_to_l2.test.ts` from this branch up. The helper now gates on the configured PXE sync tip via a new `CrossChainMessagingTest.pxeSyncChainTip` getter (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`).

The archiver's checkpoint reconstruction (`retrievedToPublishedCheckpoint` in `data_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 as `cross_chain_public_message` and `token_bridge` failing (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 own `l1ToL2MessageRoot` from the blob. A `data_retrieval.test.ts` unit 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/src/test/integration.test.ts` 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→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). `setPrefilled` now registers a genesis sentinel plus one bucket per message-carrying checkpoint (cumulative `totalMsgCount` = 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.

- Removed the inert `isFirstBlock` parameter from the blob-data test fixtures (`makeBlockEndBlobData` / `makeBlockBlobData` and the archiver `makeBlockBlobDataFromBody` helper). 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.

Replaces #24789.
spalladino added a commit that referenced this pull request Aug 18, 2026
…usion (A-1385)

Test-only coverage for the Fast Inbox (AZIP-22) streaming L1→L2 message path (issue A-1385, FI-15) — the behaviours the legacy suite could not express, because pre-flip every message entered at the first block of the *next* checkpoint.

Stacked on the node/flip phase: #24784 (A-1379) → #24785 (A-1380) → #24786 (A-1381) → #24787 (A-1382) → #24788 (A-1383) → #24789 (A-1384). Base is `spl/a-1384-flip-streaming-inbox`.

New `single-node/cross-chain/streaming_inbox.test.ts`, on the proven `CrossChainMessagingTest` fixture (production pipelining sequencer) with a widened slot (36s, 6s blocks → up to ~4 blocks per checkpoint) and `minTxsPerBlock: 0`:

- **Mid-checkpoint inclusion** — times a send so the message ages past `INBOX_LAG_SECONDS` partway through a checkpoint's build, then asserts the inserting block has `indexWithinCheckpoint > 0` and that the immediately preceding block did not yet carry the message. Retries with fresh messages if a message happens to age exactly at a checkpoint boundary.
- **Latency bound** — asserts, slot-denominated, that `includingBlockTimestamp − messageL1Timestamp ≤ INBOX_LAG_SECONDS + 2·slotDuration` (derived from the deployed constants, not hardcoded), and that it is positive. Wall-clock latency is logged only (A-1178 style), never asserted.
- **Message-only block** — on a drained pool, asserts the block that consumes the message carries zero tx effects (the FI-05 zero-tx / non-empty-bundle shape) and that the chain keeps proving past it.
- **Send-then-consume on the streaming path** — consumes a streaming-inserted message with a public tx by its compact leaf index and asserts a second consume reverts (double-spend protection); the insert/consume block relationship is logged.

New `checkpoint-sub-tree-orchestrator` test for a checkpoint whose L1→L2 messages span multiple blocks (a non-first block carries a bundle). Asserts per-block start/end L1→L2 tree-snapshot continuity and slice partitioning (no gap/overlap; block slice = `[prevBlockLeafCount, blockLeafCount)`) and the `isFirstBlock` flag. Adds `TestContext.makeCheckpointWithMessagesPerBlock` to distribute a bundle across a checkpoint's blocks (the single-block-per-checkpoint `makeCheckpoint` puts every message in the first block).

- The e2e suite and the `prover-client` orchestrator test **could not be run locally** — the local build is broken in `noir-protocol-circuits-types`/`simulator`/`prover-client` from stale circuit artifacts, and `end-to-end` sits downstream. CI validates both. Every helper, fixture, and API call is grounded on an existing green suite (`l1_to_l2.test.ts`, `cross_chain_public_message.test.ts`) and verified against the current interfaces. No packages that build locally (stdlib, blob-lib, ethereum, foundation) are touched.
- Reviewed by codex (gpt-5.6-terra): it caught an incorrect message-sponge-continuity assumption in the prover test (non-first block roots inherit the checkpoint-wide sponge, not the prior block's end sponge); those sponge assertions were removed, leaving the correct L1→L2 tree-snapshot partitioning checks.

The multi-block-slice orchestrator test now also asserts per-block message-sponge continuity (start empty, each block absorbs exactly its slice, last end equals the InboxParity sponge) and turns its last block into a zero-tx message-only block, exercising the msgs-only block-root wiring fixed in #24789. A stray `async` in the e2e suite was dropped (lint).

Replaces #24790.
spalladino added a commit that referenced this pull request Aug 18, 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.

- 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).

- 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.

- 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.

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.

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.
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