Skip to content

Commit d3ac793

Browse files
committed
feat(fast-inbox): flip consensus to streaming inbox (A-1384)
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 - **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. ## L1 (`l1-contracts`) - `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. ## Circuits (`noir-projects`) - `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. ## Node (`yarn-project`) - **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). ## Verification - 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`). ## Node prover + archiver keying (completed in this PR) - **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. ## Left for FI-18 cleanup 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. ## 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`: - 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). ## Bucket-hint carry on final-block failure (CI fix) 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`). ## L1→L2 readiness gate on the PXE sync tip (CI fix) 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`). ## Per-block L1→L2 message tree root in archiver reconstruction (consensus fix) 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 mock Inbox buckets (CI fix, pass-8) `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. ## Leftover sweep (post-review) - 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.
1 parent c732d00 commit d3ac793

132 files changed

Lines changed: 1852 additions & 5353 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

l1-contracts/src/core/Rollup.sol

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import {
4141
Epoch,
4242
Timestamp,
4343
CommitteeAttestations,
44+
EpochProofExtLib,
4445
RollupOperationsExtLib,
4546
ValidatorOperationsExtLib,
4647
EthValue,
@@ -300,7 +301,7 @@ contract Rollup is IStaking, IValidatorSelection, IRollup, RollupCore {
300301
ProposedHeader[] calldata _headers,
301302
bytes calldata _blobPublicInputs
302303
) external view override(IRollup) returns (bytes32[] memory) {
303-
return RollupOperationsExtLib.getEpochProofPublicInputs(_start, _end, _args, _headers, _blobPublicInputs);
304+
return EpochProofExtLib.getEpochProofPublicInputs(_start, _end, _args, _headers, _blobPublicInputs);
304305
}
305306

306307
/**

l1-contracts/src/core/RollupCore.sol

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {IOutbox} from "@aztec/core/interfaces/messagebridge/IOutbox.sol";
1818
import {Constants} from "@aztec/core/libraries/ConstantsGen.sol";
1919
import {CommitteeAttestations} from "@aztec/core/libraries/rollup/AttestationLib.sol";
2020
import {Errors} from "@aztec/core/libraries/Errors.sol";
21+
import {EpochProofExtLib} from "@aztec/core/libraries/rollup/EpochProofExtLib.sol";
2122
import {RollupOperationsExtLib} from "@aztec/core/libraries/rollup/RollupOperationsExtLib.sol";
2223
import {ValidatorOperationsExtLib} from "@aztec/core/libraries/rollup/ValidatorOperationsExtLib.sol";
2324
import {SlasherDeploymentExtLib} from "@aztec/core/libraries/rollup/SlasherDeploymentExtLib.sol";
@@ -466,7 +467,7 @@ contract RollupCore is EIP712("Aztec Rollup", "1"), Ownable, IStakingCore, IVali
466467
* @param _args Contains the epoch range, public inputs, fees, attestations, and the ZK proof
467468
*/
468469
function submitEpochRootProof(SubmitEpochRootProofArgs calldata _args) external override(IRollupCore) {
469-
RollupOperationsExtLib.submitEpochRootProof(_args);
470+
EpochProofExtLib.submitEpochRootProof(_args);
470471
}
471472

472473
/**

l1-contracts/src/core/libraries/Errors.sol

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ library Errors {
6060
error Rollup__InvalidCheckpointNumber(uint256 expected, uint256 actual); // 0xd1ba9bfa
6161
error Rollup__InvalidInHash(bytes32 expected, bytes32 actual); // 0xcd6f4233
6262
error Rollup__InvalidInboxRollingHash(bytes32 expected, bytes32 actual); // 0xed1f7bb5
63+
error Rollup__InvalidPreviousInboxRollingHash(bytes32 expected, bytes32 actual); // 0x2fe7cae5
64+
error Rollup__InvalidEndInboxRollingHash(bytes32 expected, bytes32 actual); // 0x4a9cdb72
6365
error Rollup__InboxBucketStillMutable(uint256 bucketSeq); // 0x7254b980
6466
error Rollup__UnconsumedInboxMessages(uint256 nextBucketSeq); // 0x2bd4bf10
6567
error Rollup__InboxConsumptionBehindParent(uint256 expected, uint256 actual); // 0x54e0c025

l1-contracts/src/core/libraries/compressed-data/CheckpointLog.sol

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,16 @@ struct TempCheckpointLog {
3434
bytes32 attestationsHash;
3535
bytes32 payloadDigest;
3636
Slot slotNumber;
37+
// Streaming Inbox consumption counts (AZIP-22 Fast Inbox). `inboxMsgTotal` is the cumulative Inbox message count
38+
// consumed as of this checkpoint (the child's parent-total origin); `inboxConsumedBucket` is the bucket sequence
39+
// number the header's rolling hash corresponds to. Declared next to the slot number so the three share one storage
40+
// slot (4 + 8 + 8 of 32 bytes): propose writes and reads that slot for the slot-progression check anyway.
41+
uint64 inboxMsgTotal;
42+
uint64 inboxConsumedBucket;
3743
FeeHeader feeHeader;
44+
// The consensus Inbox rolling hash the checkpoint header committed to, in a slot of its own: epoch proofs anchor
45+
// both ends of their consumed chain segment against it.
46+
bytes32 inboxRollingHash;
3847
}
3948

4049
struct CompressedTempCheckpointLog {
@@ -44,7 +53,10 @@ struct CompressedTempCheckpointLog {
4453
bytes32 attestationsHash;
4554
bytes32 payloadDigest;
4655
CompressedSlot slotNumber;
56+
uint64 inboxMsgTotal;
57+
uint64 inboxConsumedBucket;
4758
CompressedFeeHeader feeHeader;
59+
bytes32 inboxRollingHash;
4860
}
4961

5062
library CompressedTempCheckpointLogLib {
@@ -61,7 +73,10 @@ library CompressedTempCheckpointLogLib {
6173
attestationsHash: _checkpoint.attestationsHash,
6274
payloadDigest: _checkpoint.payloadDigest,
6375
slotNumber: _checkpoint.slotNumber.compress(),
64-
feeHeader: _checkpoint.feeHeader.compress()
76+
inboxMsgTotal: _checkpoint.inboxMsgTotal,
77+
inboxConsumedBucket: _checkpoint.inboxConsumedBucket,
78+
feeHeader: _checkpoint.feeHeader.compress(),
79+
inboxRollingHash: _checkpoint.inboxRollingHash
6580
});
6681
}
6782

@@ -77,7 +92,10 @@ library CompressedTempCheckpointLogLib {
7792
attestationsHash: _compressedCheckpoint.attestationsHash,
7893
payloadDigest: _compressedCheckpoint.payloadDigest,
7994
slotNumber: _compressedCheckpoint.slotNumber.decompress(),
80-
feeHeader: _compressedCheckpoint.feeHeader.decompress()
95+
inboxMsgTotal: _compressedCheckpoint.inboxMsgTotal,
96+
inboxConsumedBucket: _compressedCheckpoint.inboxConsumedBucket,
97+
feeHeader: _compressedCheckpoint.feeHeader.decompress(),
98+
inboxRollingHash: _compressedCheckpoint.inboxRollingHash
8199
});
82100
}
83101
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
// Copyright 2024 Aztec Labs.
3+
pragma solidity >=0.8.27;
4+
5+
import {SubmitEpochRootProofArgs, PublicInputArgs} from "@aztec/core/interfaces/IRollup.sol";
6+
import {ProposedHeader} from "@aztec/core/libraries/rollup/ProposedHeaderLib.sol";
7+
import {EpochProofLib} from "./EpochProofLib.sol";
8+
9+
/**
10+
* @title EpochProofExtLib - External Rollup Library (Epoch Proof Functions)
11+
* @author Aztec Labs
12+
* @notice External library containing epoch-proof functions for the Rollup contract to avoid exceeding max
13+
* contract size.
14+
*
15+
* @dev This library serves as an external library for the Rollup contract, splitting off the epoch proof
16+
* submission and public-input computation from RollupOperationsExtLib, which the streaming inbox
17+
* validation pushed over the deployable size limit. The library contains external functions primarily
18+
* focused on:
19+
* - Epoch proof submission and verification
20+
* - Epoch proof public input computation
21+
*/
22+
library EpochProofExtLib {
23+
function submitEpochRootProof(SubmitEpochRootProofArgs calldata _args) external {
24+
EpochProofLib.submitEpochRootProof(_args);
25+
}
26+
27+
function getEpochProofPublicInputs(
28+
uint256 _start,
29+
uint256 _end,
30+
PublicInputArgs calldata _args,
31+
ProposedHeader[] calldata _headers,
32+
bytes calldata _blobPublicInputs
33+
) external view returns (bytes32[] memory) {
34+
return EpochProofLib.getEpochProofPublicInputs(_start, _end, _args, _headers, _blobPublicInputs);
35+
}
36+
}

l1-contracts/src/core/libraries/rollup/EpochProofLib.sol

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,30 @@ library EpochProofLib {
289289
expectedEndArchive == _args.endArchive, Errors.Rollup__InvalidArchive(expectedEndArchive, _args.endArchive)
290290
);
291291
}
292+
293+
// Boundary anchoring for the Inbox rolling-hash chain (AZIP-22 Fast Inbox), mirroring previousArchive/endArchive:
294+
// both ends of the claimed chain segment must match the rolling hashes recorded at propose for checkpoints
295+
// _start - 1 and _end. The start needs this to be sound - the previous checkpoint's header is not among this
296+
// proof's public inputs, so nothing else pins where the segment begins. The end is already pinned transitively
297+
// (the checkpoint root binds end_inbox_rolling_hash to the last checkpoint header, whose hash verifyHeaders ties
298+
// to storage), so checking it here only trades a bare proof-verification failure for a specific error.
299+
{
300+
bytes32 expectedPreviousInboxRollingHash = STFLib.getInboxRollingHash(_start - 1);
301+
require(
302+
expectedPreviousInboxRollingHash == _args.previousInboxRollingHash,
303+
Errors.Rollup__InvalidPreviousInboxRollingHash(
304+
expectedPreviousInboxRollingHash, _args.previousInboxRollingHash
305+
)
306+
);
307+
}
308+
309+
{
310+
bytes32 expectedEndInboxRollingHash = STFLib.getInboxRollingHash(_end);
311+
require(
312+
expectedEndInboxRollingHash == _args.endInboxRollingHash,
313+
Errors.Rollup__InvalidEndInboxRollingHash(expectedEndInboxRollingHash, _args.endInboxRollingHash)
314+
);
315+
}
292316
}
293317

294318
bytes32[] memory publicInputs = new bytes32[](Constants.ROOT_ROLLUP_PUBLIC_INPUTS_LENGTH);
@@ -319,9 +343,9 @@ library EpochProofLib {
319343

320344
publicInputs[2] = _args.outHash;
321345

322-
// Inbox rolling-hash chain segment consumed across the epoch (AZIP-22 Fast Inbox). Deliberately UNVALIDATED
323-
// until the Fast Inbox flip, when they get checked against per-checkpoint records written at propose; for now
324-
// they are only passed through to the proof's public inputs.
346+
// Inbox rolling-hash chain segment consumed across the epoch (AZIP-22 Fast Inbox). The start is validated above
347+
// against the record written at propose for checkpoint _start - 1; the end is pinned transitively through the
348+
// stored checkpoint header hashes (see the anchoring block in assertAcceptable).
325349
publicInputs[3] = _args.previousInboxRollingHash;
326350
publicInputs[4] = _args.endInboxRollingHash;
327351
}

l1-contracts/src/core/libraries/rollup/ProposeLib.sol

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {ValidatorSelectionLib} from "@aztec/core/libraries/rollup/ValidatorSelec
1818
import {Timestamp, Slot, Epoch, TimeLib} from "@aztec/core/libraries/TimeLib.sol";
1919
import {CompressedSlot, CompressedTimeMath} from "@aztec/shared/libraries/CompressedTimeMath.sol";
2020
import {Signature} from "@aztec/shared/libraries/SignatureLib.sol";
21+
import {SafeCast} from "@oz/utils/math/SafeCast.sol";
2122
import {ProposedHeader, ProposedHeaderLib} from "./ProposedHeaderLib.sol";
2223
import {STFLib} from "./STFLib.sol";
2324

@@ -33,6 +34,10 @@ struct ProposeArgs {
3334
bytes32 archive;
3435
OracleInput oracleInput;
3536
ProposedHeader header;
37+
// Sequence number of the Inbox bucket the header's `inboxRollingHash` corresponds to (AZIP-22 Fast Inbox).
38+
// Unsigned lookup aid kept out of the attested payload digest: a wrong hint can only revert, never change what is
39+
// accepted, since integrity comes from the rolling-hash equality check against the committee-signed header.
40+
uint256 bucketHint;
3641
}
3742

3843
struct ProposePayload {
@@ -46,7 +51,9 @@ struct InterimProposeValues {
4651
bytes32[] blobHashes;
4752
bytes32 blobsHashesCommitment;
4853
bytes[] blobCommitments;
49-
bytes32 inHash;
54+
bytes32 blobCommitmentsHash;
55+
FeeHeader feeHeader;
56+
uint256 consumedInboxMsgTotal;
5057
bytes32 headerHash;
5158
bytes32 attestationsHash;
5259
bytes32 payloadDigest;
@@ -121,6 +128,7 @@ library ProposeLib {
121128
using TimeLib for Epoch;
122129
using CompressedTimeMath for CompressedSlot;
123130
using ChainTipsLib for CompressedChainTips;
131+
using SafeCast for uint256;
124132

125133
/**
126134
* @notice Publishes a new checkpoint to the pending chain.
@@ -261,17 +269,31 @@ library ProposeLib {
261269
uint256 checkpointNumber = tips.getPending() + 1;
262270
tips = tips.updatePending(checkpointNumber);
263271

272+
// Validate the streaming Inbox consumption against the parent checkpoint's consumed position (AZIP-22 Fast
273+
// Inbox). The parent is checkpointNumber - 1, always available: checkpoint 0 carries the {0,0,0} genesis base
274+
// case written at initialization. rollupStore.tips is not committed until below, so the parent read still sees
275+
// the parent as the pending tip. The returned cumulative total is stored in this checkpoint's record so its
276+
// child validates against it and, since temp-log records rewind with the pending chain on a prune, the record
277+
// stays prune-consistent.
278+
v.consumedInboxMsgTotal = validateInboxConsumption(
279+
rollupStore.config.inbox,
280+
v.header.inboxRollingHash,
281+
_args.bucketHint,
282+
v.header.slotNumber,
283+
STFLib.getInboxMsgTotal(checkpointNumber - 1)
284+
);
285+
264286
// Calculate accumulated blob commitments hash for this checkpoint
265287
// Blob commitments are collected and proven per root rollup proof (per epoch),
266288
// so we need to know whether we are at the epoch start:
267289
v.isFirstCheckpointOfEpoch =
268290
v.currentEpoch > STFLib.getEpochForCheckpoint(checkpointNumber - 1) || checkpointNumber == 1;
269-
bytes32 blobCommitmentsHash = BlobLib.calculateBlobCommitmentsHash(
291+
v.blobCommitmentsHash = BlobLib.calculateBlobCommitmentsHash(
270292
STFLib.getBlobCommitmentsHash(checkpointNumber - 1), v.blobCommitments, v.isFirstCheckpointOfEpoch
271293
);
272294

273295
// Compute fee header for checkpoint metadata
274-
FeeHeader memory feeHeader = FeeLib.computeFeeHeader(
296+
v.feeHeader = FeeLib.computeFeeHeader(
275297
checkpointNumber,
276298
_args.oracleInput.feeAssetPriceModifier,
277299
v.header.totalManaUsed,
@@ -289,20 +311,18 @@ library ProposeLib {
289311
STFLib.addTempCheckpointLog(
290312
TempCheckpointLog({
291313
headerHash: v.headerHash,
292-
blobCommitmentsHash: blobCommitmentsHash,
314+
blobCommitmentsHash: v.blobCommitmentsHash,
293315
outHash: v.header.outHash,
294316
attestationsHash: v.attestationsHash,
295317
payloadDigest: v.payloadDigest,
296318
slotNumber: v.header.slotNumber,
297-
feeHeader: feeHeader
319+
feeHeader: v.feeHeader,
320+
inboxRollingHash: v.header.inboxRollingHash,
321+
inboxMsgTotal: v.consumedInboxMsgTotal.toUint64(),
322+
inboxConsumedBucket: _args.bucketHint.toUint64()
298323
})
299324
);
300325

301-
// Consume pending L1->L2 messages and validate against header commitment
302-
// @note The checkpoint number here will always be >=1 as the genesis checkpoint is at 0
303-
v.inHash = rollupStore.config.inbox.consume(checkpointNumber);
304-
require(v.header.inHash == v.inHash, Errors.Rollup__InvalidInHash(v.inHash, v.header.inHash));
305-
306326
{
307327
bytes32 archive = _args.archive;
308328
if (v.isEscapeHatch) {

l1-contracts/src/core/libraries/rollup/RollupOperationsExtLib.sol

Lines changed: 4 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,9 @@
44
pragma solidity >=0.8.27;
55

66
import {Errors} from "@aztec/core/libraries/Errors.sol";
7-
import {SubmitEpochRootProofArgs, PublicInputArgs} from "@aztec/core/interfaces/IRollup.sol";
87
import {STFLib} from "@aztec/core/libraries/rollup/STFLib.sol";
98
import {Timestamp, TimeLib, Slot, Epoch} from "@aztec/core/libraries/TimeLib.sol";
109
import {BlobLib} from "@aztec-blob-lib/BlobLib.sol";
11-
import {EpochProofLib} from "./EpochProofLib.sol";
12-
import {ProposedHeader} from "@aztec/core/libraries/rollup/ProposedHeaderLib.sol";
1310
import {AttestationLib} from "@aztec/core/libraries/rollup/AttestationLib.sol";
1411
import {
1512
ProposeLib,
@@ -21,16 +18,16 @@ import {
2118
import {Signature} from "@aztec/shared/libraries/SignatureLib.sol";
2219

2320
/**
24-
* @title RollupOperationsExtLib - External Rollup Library (Proposal and Proof Verification Functions)
21+
* @title RollupOperationsExtLib - External Rollup Library (Proposal Functions)
2522
* @author Aztec Labs
2623
* @notice External library containing proposal-related functions for the Rollup contract to avoid exceeding max
2724
* contract size.
2825
*
2926
* @dev This library serves as an external library for the Rollup contract, splitting off proposal-related
30-
* functionality to keep the main contract within the maximum contract size limit. The library contains
31-
* external functions primarily focused on:
27+
* functionality to keep the main contract within the maximum contract size limit. Epoch-proof functions
28+
* live in EpochProofExtLib to keep this library itself deployable. The library contains external
29+
* functions primarily focused on:
3230
* - Checkpoint proposal submission and validation
33-
* - Epoch proof submission and verification
3431
* - Blob validation and commitment management
3532
* - Chain pruning operations
3633
*/
@@ -39,10 +36,6 @@ library RollupOperationsExtLib {
3936
using TimeLib for Slot;
4037
using AttestationLib for CommitteeAttestations;
4138

42-
function submitEpochRootProof(SubmitEpochRootProofArgs calldata _args) external {
43-
EpochProofLib.submitEpochRootProof(_args);
44-
}
45-
4639
function validateHeaderWithAttestations(
4740
ValidateHeaderArgs calldata _args,
4841
CommitteeAttestations calldata _attestations,
@@ -78,16 +71,6 @@ library RollupOperationsExtLib {
7871
STFLib.prune();
7972
}
8073

81-
function getEpochProofPublicInputs(
82-
uint256 _start,
83-
uint256 _end,
84-
PublicInputArgs calldata _args,
85-
ProposedHeader[] calldata _headers,
86-
bytes calldata _blobPublicInputs
87-
) external view returns (bytes32[] memory) {
88-
return EpochProofLib.getEpochProofPublicInputs(_start, _end, _args, _headers, _blobPublicInputs);
89-
}
90-
9174
function validateBlobs(bytes calldata _blobsInput, bool _checkBlob)
9275
external
9376
view

l1-contracts/src/core/libraries/rollup/STFLib.sol

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,12 @@ library STFLib {
132132
slotNumber: Slot.wrap(0),
133133
feeHeader: FeeHeader({
134134
excessMana: 0, manaUsed: 0, ethPerFeeAsset: _initialEthPerFeeAsset, congestionCost: 0, proverCost: 0
135-
})
135+
}),
136+
// Genesis Inbox consumption base case, matching the Inbox's genesis bucket-0 sentinel {0, 0, 0}, so
137+
// checkpoint 1 validates its consumption against it (AZIP-22 Fast Inbox).
138+
inboxRollingHash: bytes32(0),
139+
inboxMsgTotal: 0,
140+
inboxConsumedBucket: 0
136141
}).compress();
137142
}
138143

@@ -305,6 +310,26 @@ library STFLib {
305310
return getStorageTempCheckpointLog(_checkpointNumber).slotNumber.decompress();
306311
}
307312

313+
/**
314+
* @notice Retrieves the cumulative Inbox message count consumed as of a checkpoint (AZIP-22 Fast Inbox)
315+
* @dev Gas-efficient accessor reading only the streaming-inbox consumed total. Reverts if the checkpoint is stale.
316+
* @param _checkpointNumber The checkpoint number to get the consumed total for
317+
* @return The cumulative Inbox message count consumed as of the checkpoint
318+
*/
319+
function getInboxMsgTotal(uint256 _checkpointNumber) internal view returns (uint64) {
320+
return getStorageTempCheckpointLog(_checkpointNumber).inboxMsgTotal;
321+
}
322+
323+
/**
324+
* @notice Retrieves the Inbox rolling hash a checkpoint committed to (AZIP-22 Fast Inbox)
325+
* @dev Gas-efficient accessor reading only the streaming-inbox rolling hash. Reverts if the checkpoint is stale.
326+
* @param _checkpointNumber The checkpoint number to get the rolling hash for
327+
* @return The consensus Inbox rolling hash recorded for the checkpoint
328+
*/
329+
function getInboxRollingHash(uint256 _checkpointNumber) internal view returns (bytes32) {
330+
return getStorageTempCheckpointLog(_checkpointNumber).inboxRollingHash;
331+
}
332+
308333
/**
309334
* @notice Gets the effective pending checkpoint number based on pruning eligibility
310335
* @dev Returns either the pending checkpoint number or proven checkpoint number depending on

l1-contracts/src/core/messagebridge/Inbox.sol

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -130,10 +130,10 @@ contract Inbox is IInbox {
130130
currentTree = trees[inProgress];
131131
}
132132

133-
// this is the global leaf index and not index in the checkpoint subtree
134-
// such that users can simply use it and don't need access to a node if they are to consume it in public.
135-
// trees are constant size so global index = tree number * size + subtree index
136-
uint256 index = (inProgress - Constants.INITIAL_CHECKPOINT_NUMBER) * SIZE + currentTree.nextIndex;
133+
// Compact cumulative message index (AZIP-22 Fast Inbox): the zero-based position of this message in the Inbox's
134+
// insertion order, equal to the number of messages inserted before it. It is embedded in the leaf preimage and
135+
// matches the streaming L1-to-L2 tree's leaf count, so consumers do not need per-checkpoint tree geometry.
136+
uint256 index = totalMessagesInserted;
137137

138138
// If the sender is the fee asset portal, we use a magic address to simpler have it initialized at genesis.
139139
// We assume that no-one will know the private key for this address and that the precompile won't change to

0 commit comments

Comments
 (0)