Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions yarn-project/aztec/src/cli/aztec_start_action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,11 @@ export async function aztecStart(options: any, userLog: LogFn, debugLogger: Logg
l1RpcUrls: options.l1RpcUrls,
testAccounts: localNetwork.testAccounts,
realProofs: false,
// Setting the epoch duration to 4 by default for local network. This allows the epoch to be "proven" faster, so
// Setting the epoch duration to 2 by default for local network. This allows the epoch to be "proven" faster, so
// the users can consume out hash without having to wait for a long time.
// Note: We are not proving anything in the local network (realProofs == false). But in `createLocalNetwork`,
// the EpochTestSettler will set the out hash to the outbox when an epoch is complete.
aztecEpochDuration: 4,
aztecEpochDuration: 2,
},
userLog,
);
Expand Down
16 changes: 16 additions & 0 deletions yarn-project/aztec/src/local-network/local-network.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import type { LogFn } from '@aztec/foundation/log';
import { DateProvider, TestDateProvider } from '@aztec/foundation/timer';
import { getVKTreeRoot } from '@aztec/noir-protocol-circuits-types/vk-tree';
import { protocolContractsHash } from '@aztec/protocol-contracts';
import { SequencerState } from '@aztec/sequencer-client';
import type { ProvingJobBroker } from '@aztec/stdlib/interfaces/server';
import type { PublicDataTreeLeaf } from '@aztec/stdlib/trees';
import {
Expand Down Expand Up @@ -181,6 +182,21 @@ export async function createLocalNetwork(config: Partial<LocalNetworkConfig> = {
const blobClient = createBlobClient();
const node = await createAztecNode(aztecNodeConfig, { telemetry, blobClient, dateProvider }, { prefilledPublicData });

// Now that the node is up, let the watcher check for pending txs so it can skip unfilled slots faster when
// transactions are waiting in the mempool. Also let it check if the sequencer is actively building, to avoid
// warping time out from under an in-progress block.
watcher?.setGetPendingTxCount(() => node.getPendingTxCount());
const sequencer = node.getSequencer()?.getSequencer();
if (sequencer) {
const idleStates: Set<string> = new Set([
SequencerState.STOPPED,
SequencerState.STOPPING,
SequencerState.IDLE,
SequencerState.SYNCHRONIZING,
]);
watcher?.setIsSequencerBuilding(() => !idleStates.has(sequencer.getState()));
}

let epochTestSettler: EpochTestSettler | undefined;
if (!aztecNodeConfig.p2pEnabled) {
epochTestSettler = new EpochTestSettler(
Expand Down
74 changes: 59 additions & 15 deletions yarn-project/aztec/src/testing/anvil_test_watcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,15 @@ export class AnvilTestWatcher {

private isMarkingAsProven = true;

// Optional callback to check if there are pending txs in the mempool.
private getPendingTxCount?: () => Promise<number>;

// Optional callback to check if the sequencer is actively building a block.
private isSequencerBuilding?: () => boolean;

// Tracks when we first observed the current unfilled slot with pending txs (real wall time).
private unfilledSlotFirstSeen?: { slot: number; realTime: number };

constructor(
private cheatcodes: EthCheatCodes,
rollupAddress: EthAddress,
Expand Down Expand Up @@ -59,6 +68,16 @@ export class AnvilTestWatcher {
this.isLocalNetwork = isLocalNetwork;
}

/** Sets a callback to check for pending txs, used to skip unfilled slots faster when txs are waiting. */
setGetPendingTxCount(fn: () => Promise<number>) {
this.getPendingTxCount = fn;
}

/** Sets a callback to check if the sequencer is actively building, to avoid warping while it works. */
setIsSequencerBuilding(fn: () => boolean) {
this.isSequencerBuilding = fn;
}

async start() {
if (this.filledRunningPromise) {
throw new Error('Watcher already watching for filled slot');
Expand Down Expand Up @@ -131,15 +150,8 @@ export class AnvilTestWatcher {
const nextSlotTimestamp = Number(await this.rollup.read.getTimestampForSlot([BigInt(nextSlot)]));

if (BigInt(currentSlot) === checkpointLog.slotNumber) {
// We should jump to the next slot
try {
await this.cheatcodes.warp(nextSlotTimestamp, {
resetBlockInterval: true,
});
} catch (e) {
this.logger.error(`Failed to warp to timestamp ${nextSlotTimestamp}: ${e}`);
}

// The current slot has been filled, we should jump to the next slot.
await this.warpToTimestamp(nextSlotTimestamp);
this.logger.info(`Slot ${currentSlot} was filled, jumped to next slot`);
return;
}
Expand All @@ -149,18 +161,50 @@ export class AnvilTestWatcher {
return;
}

const currentTimestamp = this.dateProvider?.now() ?? Date.now();
if (currentTimestamp > nextSlotTimestamp * 1000) {
try {
await this.cheatcodes.warp(nextSlotTimestamp, { resetBlockInterval: true });
} catch (e) {
this.logger.error(`Failed to warp to timestamp ${nextSlotTimestamp}: ${e}`);
// If there are pending txs and the sequencer missed them, warp quickly (after a 2s real-time debounce) so the
// sequencer can retry in the next slot. Without this, we'd have to wait a full real-time slot duration (~36s) for
// the dateProvider to catch up to the next slot timestamp. We skip the warp if the sequencer is actively building
// to avoid invalidating its in-progress work.
if (this.getPendingTxCount) {
const pendingTxs = await this.getPendingTxCount();
if (pendingTxs > 0) {
if (this.isSequencerBuilding?.()) {
this.unfilledSlotFirstSeen = undefined;
return;
}

const realNow = Date.now();
if (!this.unfilledSlotFirstSeen || this.unfilledSlotFirstSeen.slot !== currentSlot) {
this.unfilledSlotFirstSeen = { slot: currentSlot, realTime: realNow };
return;
}

if (realNow - this.unfilledSlotFirstSeen.realTime > 2000) {
await this.warpToTimestamp(nextSlotTimestamp);
this.unfilledSlotFirstSeen = undefined;
this.logger.info(`Slot ${currentSlot} was missed with pending txs, jumped to next slot`);
}

return;
}
}

// Fallback: warp when the dateProvider time has passed the next slot timestamp.
const currentTimestamp = this.dateProvider?.now() ?? Date.now();
if (currentTimestamp > nextSlotTimestamp * 1000) {
await this.warpToTimestamp(nextSlotTimestamp);
this.logger.info(`Slot ${currentSlot} was missed, jumped to next slot`);
}
} catch {
this.logger.error('mineIfSlotFilled failed');
}
}

private async warpToTimestamp(timestamp: number) {
try {
await this.cheatcodes.warp(timestamp, { resetBlockInterval: true });
} catch (e) {
this.logger.error(`Failed to warp to timestamp ${timestamp}: ${e}`);
}
}
}
7 changes: 7 additions & 0 deletions yarn-project/sequencer-client/src/sequencer/sequencer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,13 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
);
}

/**
* Returns the current sequencer state.
*/
public getState(): SequencerState {
return this.state;
}

/**
* Internal helper for setting the sequencer state and checks if we have enough time left in the slot to transition to the new state.
* @param proposedState - The new state to transition to.
Expand Down
Loading