Skip to content

Commit c4e1914

Browse files
authored
test(e2e): enable pipelining in l1-reorgs and mbps redistribution tests (#23009)
No major changes needed
1 parent b9a3eb5 commit c4e1914

3 files changed

Lines changed: 79 additions & 58 deletions

File tree

yarn-project/end-to-end/src/e2e_epochs/epochs_l1_reorgs.parallel.test.ts

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,6 @@ describe('e2e_epochs/epochs_l1_reorgs', () => {
6060
};
6161

6262
beforeEach(async () => {
63-
// Note: pipelining is NOT enabled for this test because it manipulates L1 state directly
64-
// (reorgs, tx cancellation) with cancelTxOnTimeout: false and maxSpeedUpAttempts: 0,
65-
// which conflicts with pipelining's assumption that previous checkpoints land on L1 promptly.
6663
test = await EpochsTestContext.setup({
6764
numberOfAccounts: 1,
6865
maxSpeedUpAttempts: 0, // Do not speed up l1 txs, we dont want them to land
@@ -78,6 +75,10 @@ describe('e2e_epochs/epochs_l1_reorgs', () => {
7875
aztecProofSubmissionEpochs: 1,
7976
// Use 32 slots/epoch (matching real Ethereum mainnet)
8077
anvilSlotsInAnEpoch: 32,
78+
// Pipelining + multi-blocks-per-slot: 8s blocks fit ~4 blocks per 36s slot, and TX_COUNT=8
79+
// ensures multiple checkpoints have multiple blocks
80+
enableProposerPipelining: true,
81+
inboxLag: 2,
8182
});
8283
({ proverDelayer, sequencerDelayer, context, logger, monitor, L1_BLOCK_TIME_IN_S, L2_SLOT_DURATION_IN_S } = test);
8384
node = context.aztecNode;
@@ -220,12 +221,12 @@ describe('e2e_epochs/epochs_l1_reorgs', () => {
220221
const targetProvenCheckpoint = CheckpointNumber(initialProvenCheckpoint + 1);
221222

222223
// Wait until we have proven something and the nodes have caught up
223-
// Use a longer timeout since we need to wait for the epoch to complete (~288s) plus proving time
224+
// Use a longer timeout since we need to wait for the epoch to complete (~288s) plus proving time.
224225
const epochDurationSeconds = test.constants.epochDuration * test.constants.slotDuration;
225226
logger.warn(`Waiting for initial proof to land`);
226227
const provenCheckpoint = await test.waitUntilProvenCheckpointNumber(
227228
targetProvenCheckpoint,
228-
epochDurationSeconds * 2,
229+
epochDurationSeconds * 4,
229230
);
230231
await retryUntil(() => getProvenCheckpointNumber(node).then(cp => cp >= provenCheckpoint), 'node sync', 10, 0.1);
231232

@@ -270,11 +271,16 @@ describe('e2e_epochs/epochs_l1_reorgs', () => {
270271
proverDelayer.cancelNextTx();
271272

272273
// Expect pending chain to advance, so there's something to be pruned
273-
await retryUntil(() => getCheckpointNumber(node).then(cp => cp > initialCheckpoint), 'node sync', 60, 0.1);
274+
await retryUntil(
275+
() => getCheckpointNumber(node).then(cp => cp > initialCheckpoint),
276+
'node sync',
277+
L2_SLOT_DURATION_IN_S * 4,
278+
0.1,
279+
);
274280

275281
// Wait until the end of the proof submission window for the first unproven epoch
276282
const firstUnprovenCheckpoint = CheckpointNumber(initialProvenCheckpoint + 1);
277-
await test.waitUntilCheckpointNumber(firstUnprovenCheckpoint, 60);
283+
await test.waitUntilCheckpointNumber(firstUnprovenCheckpoint, L2_SLOT_DURATION_IN_S * 4);
278284
const epochToWaitFor = await test.rollup.getEpochNumberForCheckpoint(firstUnprovenCheckpoint);
279285
await test.waitUntilLastSlotOfProofSubmissionWindow(epochToWaitFor);
280286
await monitor.run(true);
@@ -339,18 +345,21 @@ describe('e2e_epochs/epochs_l1_reorgs', () => {
339345

340346
// Wait until CHECKPOINT_NUMBER is mined and node synced, and stop the sequencer
341347
const CHECKPOINT_NUMBER = CheckpointNumber(initialCheckpoint + 3);
342-
await test.waitUntilCheckpointNumber(CHECKPOINT_NUMBER, L2_SLOT_DURATION_IN_S * 7);
348+
await test.waitUntilCheckpointNumber(CHECKPOINT_NUMBER, L2_SLOT_DURATION_IN_S * 10);
343349
expect(monitor.checkpointNumber).toEqual(CHECKPOINT_NUMBER);
350+
// Stop the sequencer immediately so any in-flight pipelined publish for CHECKPOINT_NUMBER+1
351+
// doesn't extend l1BlockNumber before we capture it. setConfig alone is not enough under
352+
// pipelining because already-constructed jobs snapshot the old config.
353+
await context.sequencer!.stop();
354+
logger.warn(`Sequencer stopped`);
344355
const l1BlockNumber = monitor.l1BlockNumber;
345356
// Wait for node to sync to the checkpoint.
346357
await retryUntil(() => getCheckpointNumber(node).then(b => b === CHECKPOINT_NUMBER), 'node sync', 10, 0.1);
358+
logger.warn(`Reached checkpoint ${CHECKPOINT_NUMBER}`);
347359

348360
// Verify multi-block checkpoints were built before we do the reorg
349361
await test.assertMultipleBlocksPerSlot(2);
350362

351-
logger.warn(`Reached checkpoint ${CHECKPOINT_NUMBER}. Stopping block production.`);
352-
await context.aztecNodeAdmin.setConfig({ minTxsPerBlock: 100 });
353-
354363
// Remove the L2 block from L1
355364
const l1BlocksToReorg = monitor.l1BlockNumber - l1BlockNumber + 1;
356365
await context.cheatCodes.eth.reorgWithReplacement(l1BlocksToReorg);
@@ -374,19 +383,22 @@ describe('e2e_epochs/epochs_l1_reorgs', () => {
374383
// Wait until the checkpoint *before* CHECKPOINT_NUMBER is mined and node synced
375384
const CHECKPOINT_NUMBER = CheckpointNumber(initialCheckpoint + 3);
376385
const prevCheckpointNumber = CheckpointNumber(CHECKPOINT_NUMBER - 1);
377-
await test.waitUntilCheckpointNumber(prevCheckpointNumber, L2_SLOT_DURATION_IN_S * 7);
386+
await test.waitUntilCheckpointNumber(prevCheckpointNumber, L2_SLOT_DURATION_IN_S * 10);
378387
expect(monitor.checkpointNumber).toEqual(prevCheckpointNumber);
379388
// Wait for node to sync to the checkpoint
380389
await retryUntil(() => getCheckpointNumber(node).then(b => b === prevCheckpointNumber), 'node sync', 5, 0.1);
381390

382391
// Verify multi-block checkpoints were built before we do the reorg
383392
await test.assertMultipleBlocksPerSlot(2);
384393

385-
// Cancel the next tx to be mined and pause the sequencer
394+
// Cancel the next tx to be mined (the proposal for CHECKPOINT_NUMBER) and pause the sequencer.
395+
// Under pipelining we then stop the sequencer entirely so an in-flight pipelined job for
396+
// CHECKPOINT_NUMBER+1 cannot escape and publish onto L1 before our reorg captures the gap.
386397
sequencerDelayer.cancelNextTx();
387398
await retryUntil(() => sequencerDelayer.getCancelledTxs().length, 'next block', L2_SLOT_DURATION_IN_S * 2, 0.1);
388399
const [l2BlockTx] = sequencerDelayer.getCancelledTxs();
389-
await context.aztecNodeAdmin.setConfig({ minTxsPerBlock: 100 });
400+
await context.sequencer!.stop();
401+
logger.warn(`Sequencer stopped`);
390402

391403
// Save the L1 block number when the L2 block would have been mined
392404
const l1BlockNumber = monitor.l1BlockNumber;
@@ -447,7 +459,7 @@ describe('e2e_epochs/epochs_l1_reorgs', () => {
447459
it('updates L1 to L2 messages changed due to an L1 reorg', async () => {
448460
// Send L2 txs to trigger multi-block checkpoints and wait for them to land in a checkpoint
449461
await sendTransactions(TX_COUNT, 100);
450-
await test.waitUntilCheckpointNumber(CheckpointNumber(2), L2_SLOT_DURATION_IN_S * 4);
462+
await test.waitUntilCheckpointNumber(CheckpointNumber(2), L2_SLOT_DURATION_IN_S * 6);
451463

452464
// Send 3 messages and wait for archiver sync
453465
logger.warn(`Sending 3 cross chain messages`);
@@ -484,7 +496,7 @@ describe('e2e_epochs/epochs_l1_reorgs', () => {
484496
it('handles missed message inserted by an L1 reorg', async () => {
485497
// Send L2 txs to trigger multi-block checkpoints and wait for them to land in a checkpoint
486498
await sendTransactions(TX_COUNT, 200);
487-
await test.waitUntilCheckpointNumber(CheckpointNumber(2), L2_SLOT_DURATION_IN_S * 4);
499+
await test.waitUntilCheckpointNumber(CheckpointNumber(2), L2_SLOT_DURATION_IN_S * 6);
488500

489501
// Send a message and wait for node to sync it
490502
logger.warn(`Sending first cross chain message`);

yarn-project/end-to-end/src/e2e_epochs/epochs_mbps_redistribution.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,8 @@ describe('e2e_epochs/epochs_mbps_redistribution', () => {
9494
test = await EpochsTestContext.setup({
9595
numberOfAccounts: 0,
9696
initialValidators: validators,
97+
enableProposerPipelining: true,
98+
inboxLag: 2,
9799
mockGossipSubNetwork: true,
98100
disableAnvilTestWatcher: true,
99101
startProverNode: true,

yarn-project/end-to-end/src/e2e_epochs/epochs_proof_fails.parallel.test.ts

Lines changed: 49 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@ import { getTimestampRangeForEpoch } from '@aztec/aztec.js/block';
22
import type { Logger } from '@aztec/aztec.js/log';
33
import { BatchedBlob } from '@aztec/blob-lib/types';
44
import { RollupContract } from '@aztec/ethereum/contracts';
5-
import { type Delayer, waitUntilL1Timestamp } from '@aztec/ethereum/l1-tx-utils';
5+
import type { Delayer } from '@aztec/ethereum/l1-tx-utils';
66
import { ChainMonitor } from '@aztec/ethereum/test';
77
import type { ViemClient } from '@aztec/ethereum/types';
8-
import { CheckpointNumber, EpochNumber, SlotNumber } from '@aztec/foundation/branded-types';
8+
import { CheckpointNumber, EpochNumber } from '@aztec/foundation/branded-types';
99
import { promiseWithResolvers } from '@aztec/foundation/promise';
10+
import { retryUntil } from '@aztec/foundation/retry';
1011
import { sleep } from '@aztec/foundation/sleep';
1112
import type { TestProverNode } from '@aztec/prover-node/test';
13+
import type { SequencerEvents } from '@aztec/sequencer-client';
1214
import { type L1RollupConstants, getEpochAtSlot } from '@aztec/stdlib/epoch-helpers';
1315
import { Proof } from '@aztec/stdlib/proofs';
1416
import { RootRollupPublicInputs } from '@aztec/stdlib/rollup';
@@ -27,27 +29,27 @@ describe('e2e_epochs/epochs_proof_fails', () => {
2729
let constants: L1RollupConstants;
2830
let logger: Logger;
2931
let proverDelayer: Delayer;
30-
let sequencerDelayer: Delayer;
3132
let monitor: ChainMonitor;
3233

33-
let L1_BLOCK_TIME_IN_S: number;
3434
let L2_SLOT_DURATION_IN_S: number;
3535

3636
let test: EpochsTestContext;
3737

3838
beforeEach(async () => {
39-
// Note: pipelining is NOT enabled for this test because it deliberately manipulates L1 tx timing
40-
// (via proverDelayer/sequencerDelayer with cancelTxOnTimeout: false and maxSpeedUpAttempts: 0),
41-
// which conflicts with pipelining's assumption that previous checkpoints land on L1 promptly.
4239
test = await EpochsTestContext.setup({
4340
maxSpeedUpAttempts: 0, // No speed ups
4441
startProverNode: false, // Avoid early proving
4542
ethereumSlotDuration: 8,
46-
aztecEpochDuration: 8, // Bump empoch duration so we can land at least one block in epoch 0
43+
aztecEpochDuration: 8, // Bump epoch duration so we can land at least one block in epoch 0
44+
aztecSlotDurationInL1Slots: 2,
45+
blockDurationMs: 3000, // 3s blocks → 2 blocks per checkpoint under pipelining
4746
cancelTxOnTimeout: false,
47+
enableProposerPipelining: true,
48+
enforceTimeTable: true,
49+
inboxLag: 2,
4850
});
49-
({ sequencerDelayer, context, l1Client, rollup, constants, logger, monitor } = test);
50-
({ L1_BLOCK_TIME_IN_S, L2_SLOT_DURATION_IN_S } = test);
51+
({ context, l1Client, rollup, constants, logger, monitor } = test);
52+
({ L2_SLOT_DURATION_IN_S } = test);
5153
});
5254

5355
afterEach(async () => {
@@ -56,13 +58,14 @@ describe('e2e_epochs/epochs_proof_fails', () => {
5658
});
5759

5860
it('does not allow submitting proof after epoch end', async () => {
59-
// Here we cause a re-org by not publishing the proof for epoch 0 until after the end of epoch 1
60-
// The proof will be rejected and a re-org will take place
61-
62-
// Ensure that there was at least one checkpoint mined in epoch 0, otherwise this test fails, since it
63-
// relies on the proof for epoch zero not landing in time, which will never happen if there is
64-
// nothing to prove on epoch zero. We need to wait for the checkpoint L1 tx to be mined, not just
65-
// for the block to appear in the node's world state, since the propose tx may still be in-flight.
61+
// Here we cause a re-org by not publishing the proof for epoch 0 until after the end of epoch 1.
62+
// The proof will be rejected and a re-org will take place via the next post-deadline propose tx.
63+
const publishedEvents: Parameters<SequencerEvents['checkpoint-published']>[0][] = [];
64+
test.context.sequencer!.getSequencer().on('checkpoint-published', args => publishedEvents.push(args));
65+
66+
// Ensure that there was at least one checkpoint mined in epoch 0, otherwise this test fails, since
67+
// it relies on the proof for epoch zero not landing in time, which will never happen if there is
68+
// nothing to prove on epoch zero.
6669
await test.waitUntilCheckpointNumber(CheckpointNumber(1));
6770
const firstCheckpoint = await rollup.getCheckpoint(CheckpointNumber(1));
6871
const firstCheckpointEpoch = getEpochAtSlot(firstCheckpoint.slotNumber, test.constants);
@@ -75,38 +78,42 @@ describe('e2e_epochs/epochs_proof_fails', () => {
7578
// Get the prover delayer from the newly created prover node
7679
proverDelayer = proverNode.getProverNode()!.getDelayer()!;
7780

78-
// Hold off prover tx until end epoch 1
81+
// Hold off the prover tx until epoch 2 starts (i.e. past the proof submission deadline)
7982
const [epoch2Start] = getTimestampRangeForEpoch(EpochNumber(2), constants);
8083
proverDelayer.pauseNextTxUntilTimestamp(epoch2Start);
81-
logger.info(`Delayed prover tx until epoch 2 starts at ${epoch2Start}`);
84+
logger.warn(`Delayed prover tx until epoch 2 starts at ${epoch2Start}`);
8285

83-
// Wait until the start of epoch 1 and grab the checkpoint number
86+
// Wait until the start of epoch 1 and capture the checkpoint number before the rollback
8487
await test.waitUntilEpochStarts(EpochNumber(1));
85-
const checkpointNumberAtEndOfEpoch0 = await rollup.getCheckpointNumber();
86-
logger.info(`Starting epoch 1 after checkpoint ${checkpointNumberAtEndOfEpoch0}`);
87-
88-
// Wait until the last checkpoint of epoch 1 is published and then hold off the sequencer.
89-
await test.waitUntilCheckpointNumber(
90-
CheckpointNumber(checkpointNumberAtEndOfEpoch0 + test.epochDuration),
91-
test.L2_SLOT_DURATION_IN_S * (test.epochDuration + 4),
88+
const checkpointBeforeRollback = await rollup.getCheckpointNumber();
89+
logger.warn(`Starting epoch 1 after checkpoint ${checkpointBeforeRollback}`);
90+
expect(checkpointBeforeRollback).toBeGreaterThan(CheckpointNumber(1));
91+
92+
// Wait for the rollback to land via natural sequencer activity in epoch 2. We poll the
93+
// checkpoint number rather than a fixed timestamp because the exact slot that triggers the
94+
// prune depends on poll timing (see comment above).
95+
await test.waitUntilEpochStarts(EpochNumber(2));
96+
await retryUntil(
97+
async () => (await rollup.getCheckpointNumber()) < checkpointBeforeRollback,
98+
'rollup rolled back',
99+
L2_SLOT_DURATION_IN_S * 4,
100+
0.2,
92101
);
93-
sequencerDelayer.pauseNextTxUntilTimestamp(epoch2Start + BigInt(L1_BLOCK_TIME_IN_S));
94102

95-
// Next sequencer to publish a block should trigger a rollback to block 1
96-
await waitUntilL1Timestamp(l1Client, epoch2Start + BigInt(L1_BLOCK_TIME_IN_S));
97-
expect(await rollup.getCheckpointNumber()).toEqual(CheckpointNumber(1));
98-
expect(await rollup.getSlotNumber()).toEqual(SlotNumber(2 * test.epochDuration));
99-
100-
// The prover tx should have been rejected, and mined strictly before the one that triggered the rollback
103+
// The prover tx should have been rejected as it was submitted past the deadline
101104
const lastProverTxHash = proverDelayer.getSentTxHashes().at(-1);
105+
expect(lastProverTxHash).toBeDefined();
102106
const lastProverTxReceipt = await l1Client.getTransactionReceipt({ hash: lastProverTxHash! });
103107
expect(lastProverTxReceipt.status).toEqual('reverted');
104108

105-
const lastL2BlockTxHash = sequencerDelayer.getSentTxHashes().at(-1);
106-
const lastL2BlockTxReceipt = await l1Client.getTransactionReceipt({ hash: lastL2BlockTxHash! });
107-
expect(lastL2BlockTxReceipt.status).toEqual('success');
108-
expect(lastL2BlockTxReceipt.blockNumber).toBeGreaterThan(lastProverTxReceipt!.blockNumber);
109-
logger.info(`Test succeeded`);
109+
// The post-rollback chain tip should be in epoch 2 (the rollback-triggering propose was made
110+
// during epoch 2, after the deadline)
111+
const checkpointAfterRollback = await rollup.getCheckpointNumber();
112+
expect(checkpointAfterRollback).toBeLessThan(checkpointBeforeRollback);
113+
const latestCheckpoint = await rollup.getCheckpoint(checkpointAfterRollback);
114+
expect(getEpochAtSlot(latestCheckpoint.slotNumber, test.constants)).toEqual(EpochNumber(2));
115+
116+
logger.warn(`Test succeeded`);
110117
});
111118

112119
it('aborts proving if end of next epoch is reached', async () => {
@@ -149,17 +156,17 @@ describe('e2e_epochs/epochs_proof_fails', () => {
149156
context.proverNode = proverNode;
150157

151158
await test.waitUntilEpochStarts(1);
152-
logger.info(`Starting epoch 1`);
159+
logger.warn(`Starting epoch 1`);
153160
const proverTxCount = proverDelayer.getSentTxHashes().length;
154161

155162
await test.waitUntilEpochStarts(2);
156-
logger.info(`Starting epoch 2`);
163+
logger.warn(`Starting epoch 2`);
157164

158165
// No proof for epoch zero should have landed during epoch one
159166
expect(monitor.provenCheckpointNumber).toEqual(CheckpointNumber(0));
160167

161168
// Wait until the prover job finalizes (and a bit more) and check that it aborted and never attempted to submit a tx
162-
logger.info(`Awaiting finalize epoch`);
169+
logger.warn(`Awaiting finalize epoch`);
163170
await finalizeEpochPromise.promise;
164171
await sleep(1000);
165172
expect(proverDelayer.getSentTxHashes().length - proverTxCount).toEqual(0);

0 commit comments

Comments
 (0)