@@ -2,13 +2,15 @@ import { getTimestampRangeForEpoch } from '@aztec/aztec.js/block';
22import type { Logger } from '@aztec/aztec.js/log' ;
33import { BatchedBlob } from '@aztec/blob-lib/types' ;
44import { 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' ;
66import { ChainMonitor } from '@aztec/ethereum/test' ;
77import 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' ;
99import { promiseWithResolvers } from '@aztec/foundation/promise' ;
10+ import { retryUntil } from '@aztec/foundation/retry' ;
1011import { sleep } from '@aztec/foundation/sleep' ;
1112import type { TestProverNode } from '@aztec/prover-node/test' ;
13+ import type { SequencerEvents } from '@aztec/sequencer-client' ;
1214import { type L1RollupConstants , getEpochAtSlot } from '@aztec/stdlib/epoch-helpers' ;
1315import { Proof } from '@aztec/stdlib/proofs' ;
1416import { 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