Skip to content

Commit 60e0c63

Browse files
committed
test(fast-inbox): rework l1 publisher integration test for streaming inbox consumption (A-1388)
The suite still modeled the legacy per-checkpoint lag (a shift register consuming messages sent two checkpoints earlier) and built every checkpoint header from a zero previous rolling hash, neither of which can pass the streaming-inbox propose validation. Each checkpoint now consumes every message sent while it was built: the previous checkpoint's inboxRollingHash threads into the next header, and the propose bucket hint is read from the Inbox's current bucket, which also satisfies the mandatory-consumption assert. Single-checkpoint helpers default to consuming nothing (bucket hint 0 against the genesis bucket) instead of a padded zero-filled list. The suite cannot run locally (stale circuit artifacts in noir-protocol-circuits-types); CI is the runner of record.
1 parent faafd0f commit 60e0c63

1 file changed

Lines changed: 36 additions & 22 deletions

File tree

yarn-project/sequencer-client/src/publisher/l1_publisher.integration.test.ts

Lines changed: 36 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,12 @@ import { EpochCache } from '@aztec/epoch-cache';
2222
import { createEthereumChain } from '@aztec/ethereum/chain';
2323
import { createExtendedL1Client } from '@aztec/ethereum/client';
2424
import { 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';
2631
import { type DeployAztecL1ContractsArgs, deployAztecL1Contracts } from '@aztec/ethereum/deploy-aztec-l1-contracts';
2732
import type { L1ContractAddresses } from '@aztec/ethereum/l1-contract-addresses';
2833
import { 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.
112117
const 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).
115121
const numberOfConsecutiveBlocks = 3;
116122

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

Comments
 (0)