@@ -31,6 +31,15 @@ export class AnvilTestWatcher {
3131
3232 private isMarkingAsProven = true ;
3333
34+ // Optional callback to check if there are pending txs in the mempool.
35+ private getPendingTxCount ?: ( ) => Promise < number > ;
36+
37+ // Optional callback to check if the sequencer is actively building a block.
38+ private isSequencerBuilding ?: ( ) => boolean ;
39+
40+ // Tracks when we first observed the current unfilled slot with pending txs (real wall time).
41+ private unfilledSlotFirstSeen ?: { slot : number ; realTime : number } ;
42+
3443 constructor (
3544 private cheatcodes : EthCheatCodes ,
3645 rollupAddress : EthAddress ,
@@ -59,6 +68,16 @@ export class AnvilTestWatcher {
5968 this . isLocalNetwork = isLocalNetwork ;
6069 }
6170
71+ /** Sets a callback to check for pending txs, used to skip unfilled slots faster when txs are waiting. */
72+ setGetPendingTxCount ( fn : ( ) => Promise < number > ) {
73+ this . getPendingTxCount = fn ;
74+ }
75+
76+ /** Sets a callback to check if the sequencer is actively building, to avoid warping while it works. */
77+ setIsSequencerBuilding ( fn : ( ) => boolean ) {
78+ this . isSequencerBuilding = fn ;
79+ }
80+
6281 async start ( ) {
6382 if ( this . filledRunningPromise ) {
6483 throw new Error ( 'Watcher already watching for filled slot' ) ;
@@ -131,15 +150,8 @@ export class AnvilTestWatcher {
131150 const nextSlotTimestamp = Number ( await this . rollup . read . getTimestampForSlot ( [ BigInt ( nextSlot ) ] ) ) ;
132151
133152 if ( BigInt ( currentSlot ) === checkpointLog . slotNumber ) {
134- // We should jump to the next slot
135- try {
136- await this . cheatcodes . warp ( nextSlotTimestamp , {
137- resetBlockInterval : true ,
138- } ) ;
139- } catch ( e ) {
140- this . logger . error ( `Failed to warp to timestamp ${ nextSlotTimestamp } : ${ e } ` ) ;
141- }
142-
153+ // The current slot has been filled, we should jump to the next slot.
154+ await this . warpToTimestamp ( nextSlotTimestamp ) ;
143155 this . logger . info ( `Slot ${ currentSlot } was filled, jumped to next slot` ) ;
144156 return ;
145157 }
@@ -149,18 +161,50 @@ export class AnvilTestWatcher {
149161 return ;
150162 }
151163
152- const currentTimestamp = this . dateProvider ?. now ( ) ?? Date . now ( ) ;
153- if ( currentTimestamp > nextSlotTimestamp * 1000 ) {
154- try {
155- await this . cheatcodes . warp ( nextSlotTimestamp , { resetBlockInterval : true } ) ;
156- } catch ( e ) {
157- this . logger . error ( `Failed to warp to timestamp ${ nextSlotTimestamp } : ${ e } ` ) ;
164+ // If there are pending txs and the sequencer missed them, warp quickly (after a 2s real-time debounce) so the
165+ // sequencer can retry in the next slot. Without this, we'd have to wait a full real-time slot duration (~36s) for
166+ // the dateProvider to catch up to the next slot timestamp. We skip the warp if the sequencer is actively building
167+ // to avoid invalidating its in-progress work.
168+ if ( this . getPendingTxCount ) {
169+ const pendingTxs = await this . getPendingTxCount ( ) ;
170+ if ( pendingTxs > 0 ) {
171+ if ( this . isSequencerBuilding ?.( ) ) {
172+ this . unfilledSlotFirstSeen = undefined ;
173+ return ;
174+ }
175+
176+ const realNow = Date . now ( ) ;
177+ if ( ! this . unfilledSlotFirstSeen || this . unfilledSlotFirstSeen . slot !== currentSlot ) {
178+ this . unfilledSlotFirstSeen = { slot : currentSlot , realTime : realNow } ;
179+ return ;
180+ }
181+
182+ if ( realNow - this . unfilledSlotFirstSeen . realTime > 2000 ) {
183+ await this . warpToTimestamp ( nextSlotTimestamp ) ;
184+ this . unfilledSlotFirstSeen = undefined ;
185+ this . logger . info ( `Slot ${ currentSlot } was missed with pending txs, jumped to next slot` ) ;
186+ }
187+
188+ return ;
158189 }
190+ }
159191
192+ // Fallback: warp when the dateProvider time has passed the next slot timestamp.
193+ const currentTimestamp = this . dateProvider ?. now ( ) ?? Date . now ( ) ;
194+ if ( currentTimestamp > nextSlotTimestamp * 1000 ) {
195+ await this . warpToTimestamp ( nextSlotTimestamp ) ;
160196 this . logger . info ( `Slot ${ currentSlot } was missed, jumped to next slot` ) ;
161197 }
162198 } catch {
163199 this . logger . error ( 'mineIfSlotFilled failed' ) ;
164200 }
165201 }
202+
203+ private async warpToTimestamp ( timestamp : number ) {
204+ try {
205+ await this . cheatcodes . warp ( timestamp , { resetBlockInterval : true } ) ;
206+ } catch ( e ) {
207+ this . logger . error ( `Failed to warp to timestamp ${ timestamp } : ${ e } ` ) ;
208+ }
209+ }
166210}
0 commit comments