@@ -22,7 +22,12 @@ import { EpochCache } from '@aztec/epoch-cache';
2222import { createEthereumChain } from '@aztec/ethereum/chain' ;
2323import { createExtendedL1Client } from '@aztec/ethereum/client' ;
2424import { type L1ContractsConfig , getL1ContractsConfigEnvVars } from '@aztec/ethereum/config' ;
25- import { GovernanceProposerContract , RollupContract , SimulationOverridesBuilder } from '@aztec/ethereum/contracts' ;
25+ import {
26+ GovernanceProposerContract ,
27+ InboxContract ,
28+ RollupContract ,
29+ SimulationOverridesBuilder ,
30+ } from '@aztec/ethereum/contracts' ;
2631import { type DeployAztecL1ContractsArgs , deployAztecL1Contracts } from '@aztec/ethereum/deploy-aztec-l1-contracts' ;
2732import type { L1ContractAddresses } from '@aztec/ethereum/l1-contract-addresses' ;
2833import { TxUtilsState , createL1TxUtils } from '@aztec/ethereum/l1-tx-utils' ;
@@ -111,7 +116,8 @@ const logger = createLogger('integration_l1_publisher');
111116// depending on @aztec /aztec-node, which would create a sequencer-client <-> aztec-node cycle.
112117const config : SequencerClientConfig & L1ContractsConfig = { ...getL1ContractsConfigEnvVars ( ) , ...getConfigEnvVars ( ) } ;
113118
114- // Must exceed the inbox lag (network default 2) so at least one checkpoint consumes a real L1->L2 message.
119+ // Several consecutive checkpoints, each consuming the L1->L2 messages sent while it was being built, so real
120+ // messages are genuinely consumed and validated on L1 (AZIP-22 Fast Inbox).
115121const numberOfConsecutiveBlocks = 3 ;
116122
117123jest . setTimeout ( 1000000 ) ;
@@ -444,13 +450,16 @@ describe('L1Publisher integration', () => {
444450
445451 /**
446452 * Build a checkpoint with a single block using the LightweightCheckpointBuilder.
447- * This properly computes all checkpoint header fields (blobsHash, blockHeadersHash, inHash, epochOutHash, etc.)
453+ * This properly computes all checkpoint header fields (blobsHash, blockHeadersHash, inboxRollingHash,
454+ * epochOutHash, etc.). `previousInboxRollingHash` is the previous checkpoint's rolling hash (zero at genesis), so
455+ * the header's `inboxRollingHash` continues the on-chain Inbox chain over `l1ToL2Messages`.
448456 */
449457 const buildCheckpoint = async (
450458 globalVariables : GlobalVariables ,
451459 txs : ProcessedTx [ ] ,
452460 l1ToL2Messages : Fr [ ] ,
453461 previousCheckpointOutHashes : Fr [ ] = [ ] ,
462+ previousInboxRollingHash : Fr = Fr . ZERO ,
454463 ) : Promise < Checkpoint > => {
455464 await worldStateSynchronizer . syncImmediate ( ) ;
456465 const tempFork = await worldStateSynchronizer . fork ( BlockNumber ( globalVariables . blockNumber - 1 ) ) ;
@@ -472,7 +481,7 @@ describe('L1Publisher integration', () => {
472481 checkpointConstants ,
473482 l1ToL2Messages ,
474483 previousCheckpointOutHashes ,
475- Fr . ZERO ,
484+ previousInboxRollingHash ,
476485 tempFork ,
477486 ) ;
478487
@@ -486,7 +495,8 @@ describe('L1Publisher integration', () => {
486495 const buildSingleCheckpoint = async (
487496 opts : { l1ToL2Messages ?: Fr [ ] ; blockNumber ?: BlockNumber ; slot ?: SlotNumber } = { } ,
488497 ) => {
489- const l1ToL2Messages = opts . l1ToL2Messages ?? new Array ( MAX_L1_TO_L2_MSGS_PER_CHECKPOINT ) . fill ( Fr . ZERO ) ;
498+ // By default a single checkpoint consumes no Inbox messages (bucketHint 0 against the genesis bucket).
499+ const l1ToL2Messages = opts . l1ToL2Messages ?? [ ] ;
490500
491501 const txs = await Promise . all ( [ makeProcessedTx ( 0x1000 ) , makeProcessedTx ( 0x2000 ) ] ) ;
492502 const ts = ( await l1Client . getBlock ( ) ) . timestamp ;
@@ -516,10 +526,8 @@ describe('L1Publisher integration', () => {
516526
517527 describe ( 'block building' , ( ) => {
518528 beforeEach ( async ( ) => {
519- // This suite proposes consecutive checkpoints and models the inbox lag by hand (a checkpoint
520- // consumes the L1->L2 messages sent inboxLag checkpoints earlier -- see the shift register in
521- // buildAndPublishBlock). It inherits the network default lag and proposes enough checkpoints
522- // that a real message is actually consumed and validated on L1.
529+ // This suite proposes consecutive checkpoints, each consuming the streaming-Inbox messages sent while it was
530+ // being built (AZIP-22 Fast Inbox), so real messages are genuinely consumed and validated on L1.
523531 await setup ( ) ;
524532 } ) ;
525533
@@ -534,11 +542,12 @@ describe('L1Publisher integration', () => {
534542 '0x1647b194c649f5dd01d7c832f89b0f496043c9150797923ea89e93d5ac619a93' ,
535543 ) ;
536544
537- // Legacy per-checkpoint lag model: a message inserted while building checkpoint N was only consumable at
538- // checkpoint N + inboxLag. Modeled with a shift register so a real message is genuinely consumed and validated
539- // on L1. Retained here for the integration flow; the streaming Inbox (AZIP-22) consumes per block instead.
540- const inboxLag = 2 ;
541- const messagesInFlight : Fr [ ] [ ] = Array . from ( { length : inboxLag } , ( ) => [ ] ) ;
545+ // Streaming Inbox consumption (AZIP-22 Fast Inbox): each checkpoint consumes every message sent so far, so
546+ // its header rolling hash continues the previous checkpoint's and matches the Inbox's current bucket. The
547+ // bucket hint is read from the Inbox at propose time; consuming through the newest bucket trivially satisfies
548+ // the mandatory-consumption assert.
549+ const inboxContract = new InboxContract ( l1Client , l1ContractAddresses . inboxAddress . toString ( ) ) ;
550+ let previousInboxRollingHash = Fr . ZERO ;
542551 const blobFieldsPerCheckpoint : Fr [ ] [ ] = [ ] ;
543552 // The below batched blob is used for testing different epochs with 1..numberOfConsecutiveBlocks blocks on L1.
544553 // For real usage, always collect ALL epoch blobs first then call .batch().
@@ -549,15 +558,11 @@ describe('L1Publisher integration', () => {
549558 // and causes a chain prune
550559 const l1ToL2Content = range ( Math . min ( 16 , MAX_L1_TO_L2_MSGS_PER_CHECKPOINT ) , 128 * i + 1 + 0x400 ) . map ( fr ) ;
551560
552- const sentThisCheckpoint : Fr [ ] = [ ] ;
561+ const currentL1ToL2Messages : Fr [ ] = [ ] ;
553562 for ( let j = 0 ; j < l1ToL2Content . length ; j ++ ) {
554- sentThisCheckpoint . push ( await sendToL2 ( l1ToL2Content [ j ] , recipientAddress ) ) ;
563+ currentL1ToL2Messages . push ( await sendToL2 ( l1ToL2Content [ j ] , recipientAddress ) ) ;
555564 }
556565
557- // Consume the messages sent inboxLag checkpoints ago, then enqueue this checkpoint's messages.
558- const currentL1ToL2Messages = messagesInFlight . shift ( ) ! ;
559- messagesInFlight . push ( sentThisCheckpoint ) ;
560-
561566 // Ensure that each transaction has unique (non-intersecting nullifier values)
562567 const totalNullifiersPerBlock = 4 * MAX_NULLIFIERS_PER_TX ;
563568 const txs = await timesParallel ( numTxs , txIndex =>
@@ -579,7 +584,14 @@ describe('L1Publisher integration', () => {
579584 new GasFees ( 0 , await rollup . getManaMinFeeAt ( timestamp , true ) ) ,
580585 ) ;
581586
582- const checkpoint = await buildCheckpoint ( globalVariables , txs , currentL1ToL2Messages ) ;
587+ const checkpoint = await buildCheckpoint (
588+ globalVariables ,
589+ txs ,
590+ currentL1ToL2Messages ,
591+ [ ] ,
592+ previousInboxRollingHash ,
593+ ) ;
594+ previousInboxRollingHash = checkpoint . header . inboxRollingHash ;
583595 const block = checkpoint . blocks [ 0 ] ;
584596
585597 const totalManaUsed = txs . reduce ( ( acc , tx ) => acc . add ( new Fr ( tx . gasUsed . billedGas . l2Gas ) ) , Fr . ZERO ) ;
@@ -609,11 +621,13 @@ describe('L1Publisher integration', () => {
609621 deployerAccount . address ,
610622 ) ;
611623
624+ // The checkpoint consumed everything sent so far, so its consumed bucket is the Inbox's newest.
625+ const bucketHint = await inboxContract . getCurrentBucketSeq ( ) ;
612626 await publisher . enqueueProposeCheckpoint (
613627 checkpoint ,
614628 CommitteeAttestationsAndSigners . empty ( getSignatureContext ( ) ) ,
615629 Signature . empty ( ) ,
616- 0n ,
630+ bucketHint ,
617631 ) ;
618632 // Align chain time so the bundle simulate and the L1 send both run at the header's slot.
619633 await progressToSlot ( BigInt ( checkpoint . header . slotNumber ) ) ;
0 commit comments