Skip to content

Commit a0af4d0

Browse files
authored
chore: jump to next slop on local network if there are pending txs (#20654)
Warp to the next slot when there are pending txs so the sequencer can start building blocks with those txs. This speeds up the starting time of the sandbox, which deploys some testing accounts by default. It used to take ~50s to deploy an account, and is reduced to ~5s with this change. Users' txs will also be mined faster.
2 parents 9071ea2 + af11c35 commit a0af4d0

File tree

4 files changed

+84
-17
lines changed

4 files changed

+84
-17
lines changed

yarn-project/aztec/src/cli/aztec_start_action.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,11 @@ export async function aztecStart(options: any, userLog: LogFn, debugLogger: Logg
3737
l1RpcUrls: options.l1RpcUrls,
3838
testAccounts: localNetwork.testAccounts,
3939
realProofs: false,
40-
// Setting the epoch duration to 4 by default for local network. This allows the epoch to be "proven" faster, so
40+
// Setting the epoch duration to 2 by default for local network. This allows the epoch to be "proven" faster, so
4141
// the users can consume out hash without having to wait for a long time.
4242
// Note: We are not proving anything in the local network (realProofs == false). But in `createLocalNetwork`,
4343
// the EpochTestSettler will set the out hash to the outbox when an epoch is complete.
44-
aztecEpochDuration: 4,
44+
aztecEpochDuration: 2,
4545
},
4646
userLog,
4747
);

yarn-project/aztec/src/local-network/local-network.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import type { LogFn } from '@aztec/foundation/log';
1818
import { DateProvider, TestDateProvider } from '@aztec/foundation/timer';
1919
import { getVKTreeRoot } from '@aztec/noir-protocol-circuits-types/vk-tree';
2020
import { protocolContractsHash } from '@aztec/protocol-contracts';
21+
import { SequencerState } from '@aztec/sequencer-client';
2122
import type { ProvingJobBroker } from '@aztec/stdlib/interfaces/server';
2223
import type { PublicDataTreeLeaf } from '@aztec/stdlib/trees';
2324
import {
@@ -181,6 +182,21 @@ export async function createLocalNetwork(config: Partial<LocalNetworkConfig> = {
181182
const blobClient = createBlobClient();
182183
const node = await createAztecNode(aztecNodeConfig, { telemetry, blobClient, dateProvider }, { prefilledPublicData });
183184

185+
// Now that the node is up, let the watcher check for pending txs so it can skip unfilled slots faster when
186+
// transactions are waiting in the mempool. Also let it check if the sequencer is actively building, to avoid
187+
// warping time out from under an in-progress block.
188+
watcher?.setGetPendingTxCount(() => node.getPendingTxCount());
189+
const sequencer = node.getSequencer()?.getSequencer();
190+
if (sequencer) {
191+
const idleStates: Set<string> = new Set([
192+
SequencerState.STOPPED,
193+
SequencerState.STOPPING,
194+
SequencerState.IDLE,
195+
SequencerState.SYNCHRONIZING,
196+
]);
197+
watcher?.setIsSequencerBuilding(() => !idleStates.has(sequencer.getState()));
198+
}
199+
184200
let epochTestSettler: EpochTestSettler | undefined;
185201
if (!aztecNodeConfig.p2pEnabled) {
186202
epochTestSettler = new EpochTestSettler(

yarn-project/aztec/src/testing/anvil_test_watcher.ts

Lines changed: 59 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -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
}

yarn-project/sequencer-client/src/sequencer/sequencer.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -422,6 +422,13 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
422422
);
423423
}
424424

425+
/**
426+
* Returns the current sequencer state.
427+
*/
428+
public getState(): SequencerState {
429+
return this.state;
430+
}
431+
425432
/**
426433
* Internal helper for setting the sequencer state and checks if we have enough time left in the slot to transition to the new state.
427434
* @param proposedState - The new state to transition to.

0 commit comments

Comments
 (0)