Skip to content

Commit 4d1705f

Browse files
authored
fix(sequencer): enforce build-frame deadlines and align attestation/publish windows (#23776)
## Summary Fixes timing bugs in block building and validation, now that proposer pipelining is the only production mode. Found via an audit of the sequencer timetable, checkpoint proposal job, validator client, proposal handler, and p2p proposal/attestation validators. ### The frame bug (main fix) Under pipelining the proposer job runs with `slotNow = N-1` (build slot) and `targetSlot = N`. The job passed `targetSlot` to `setState` for build-frame states, so `Sequencer.setState` measured the `assertTimeLeft` deadlines against `getSlotStartBuildTimestamp(targetSlot)` — one full Aztec slot (72s) later than the build frame. The build-frame deadlines (`INITIALIZING_CHECKPOINT`, `CREATING_BLOCK`, `ASSEMBLING_CHECKPOINT`, `COLLECTING_ATTESTATIONS`, `PUBLISHING_CHECKPOINT`, …) were therefore checked ~72s too late and never fired. Now these states are measured against `slotNow`. `targetSlot` is still used for headers, signing, and `sendRequestsAt`. ### Aligning the attestation / publish windows around L1 geometry - The checkpoint attestation/publish deadline and the p2p attestation acceptance window are now derived from `ethereumSlotDuration` — **one Ethereum slot (12s) before the last L1 block of the target slot**, the latest a checkpoint can be submitted and still land on L1 in its slot. Previously the deadline used the configurable `l1PublishingTime` and the p2p window was only `2 * p2pPropagationTime` (~4.5s into the target slot). This also unifies the deadline with the publisher's send lead (`sendRequestsAt` already targets one Ethereum slot before the target slot start). - Validators (in `validateCheckpointProposal`) keep validating/attesting checkpoint proposals until that L1 publish deadline instead of the target-slot start, so attestations stay useful right up to the proposer's real publish cutoff. Block-proposal re-execution deadlines are intentionally left at the target-slot start. ### Why no test caught the frame bug The job timing test built the job with `slotNow === targetSlot` (so the two frames coincided) and stubbed `setStateFn` with a no-op, mocking away the very `assertTimeLeft` enforcement where the frame matters. This PR adds: - A contract test asserting every build-frame state is set against the build slot (`slotNow`), not the target slot. - A behavioral test with a real enforcing `setStateFn`: a checkpoint whose assembly crosses the build-frame deadline is now correctly abandoned. Both fail on the pre-fix code and pass after the fix. - Updated stdlib/timetable, clock-tolerance, attestation-validator, proposal-handler, and validator tests for the realigned windows (including an `l1PublishingTime != ethereumSlotDuration` case proving the deadline is now Ethereum-slot-based). No constants were removed and no broader cleanup was done; that is deferred. ## Test plan - `yarn build` green; touched packages lint/format clean. - `@aztec/stdlib` timetable, `@aztec/sequencer-client` (incl. `timetable`, `checkpoint_proposal_job.timing`, `sequencer-publisher`), `@aztec/validator-client` (incl. `proposal_handler`, `validator`), and `@aztec/p2p` `msg_validators` suites pass.
1 parent cd1d459 commit 4d1705f

14 files changed

Lines changed: 309 additions & 63 deletions

File tree

yarn-project/p2p/src/msg_validators/attestation_validator/attestation_validator.test.ts

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,8 @@ describe('CheckpointAttestationValidator', () => {
9797

9898
it('accepts attestation for current slot inside the straggler window', async () => {
9999
// Attestation is for slot 98 (current wallclock slot), but targetSlot is 99 (pipelining).
100-
// attestationWindowIntoTargetSlot = 2*p2p = 4s ⇒ straggler grace 4s+500ms disparity.
100+
// attestationWindowIntoTargetSlot now spans target-slot start to the L1 publish deadline:
101+
// aztecSlotDuration - 2*ethereumSlotDuration = 72 - 24 = 48s ⇒ straggler grace 48s+500ms.
101102
const header = CheckpointHeader.random({ slotNumber: SlotNumber(98) });
102103
const mockAttestation = makeCheckpointAttestation({
103104
header,
@@ -119,7 +120,39 @@ describe('CheckpointAttestationValidator', () => {
119120
epoch: EpochNumber(1),
120121
slot: SlotNumber(98),
121122
ts: 1000n,
122-
nowMs: 1003000n, // 3000ms elapsed, within 4500ms straggler grace
123+
nowMs: 1003000n, // 3000ms elapsed, within 48500ms straggler grace
124+
});
125+
epochCache.isInCommittee.mockResolvedValue(true);
126+
epochCache.getProposerAttesterAddressInSlot.mockResolvedValue(proposer.address);
127+
128+
const result = await validator.validate(mockAttestation);
129+
expect(result).toEqual({ result: 'accept' });
130+
});
131+
132+
it('accepts attestation arriving well into the target slot (previously rejected past ~4.5s)', async () => {
133+
// 30s into the target slot — past the old 4.5s window, well within the new 48.5s window.
134+
const header = CheckpointHeader.random({ slotNumber: SlotNumber(98) });
135+
const mockAttestation = makeCheckpointAttestation({
136+
header,
137+
attesterSigner: attester,
138+
proposerSigner: proposer,
139+
});
140+
141+
epochCache.getTargetAndNextSlot.mockReturnValue({
142+
targetSlot: SlotNumber(99),
143+
nextSlot: SlotNumber(100),
144+
});
145+
epochCache.getSlotNow.mockReturnValue(SlotNumber(98));
146+
epochCache.getL1Constants.mockReturnValue({
147+
slotDuration: 72,
148+
ethereumSlotDuration: 12,
149+
} as any);
150+
151+
epochCache.getEpochAndSlotNow.mockReturnValue({
152+
epoch: EpochNumber(1),
153+
slot: SlotNumber(98),
154+
ts: 1000n,
155+
nowMs: 1030000n, // 30s elapsed — accepted now, would have been rejected pre-fix
123156
});
124157
epochCache.isInCommittee.mockResolvedValue(true);
125158
epochCache.getProposerAttesterAddressInSlot.mockResolvedValue(proposer.address);
@@ -151,7 +184,7 @@ describe('CheckpointAttestationValidator', () => {
151184
epoch: EpochNumber(1),
152185
slot: SlotNumber(99),
153186
ts: 1000n,
154-
nowMs: 1005000n, // 5000ms elapsed, past 4500ms straggler cutoff
187+
nowMs: 1049000n, // 49s elapsed, past the 48.5s straggler cutoff (48s window + 500ms disparity)
155188
});
156189
epochCache.isInCommittee.mockResolvedValue(true);
157190

yarn-project/p2p/src/msg_validators/clock_tolerance.test.ts

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -260,10 +260,11 @@ describe('clock_tolerance', () => {
260260
});
261261

262262
describe('PipeliningWindow.acceptsAttestation', () => {
263-
// Config: 72s slot, 2s p2p.
264-
// Under early pipelining, attestationWindowIntoTargetSlot = 2*p2p = 4s,
265-
// giving straggler attestations 4s+500ms grace after the receiver rolls
266-
// into the next slot.
263+
// Config: 72s slot, 12s Ethereum slot.
264+
// attestationWindowIntoTargetSlot now spans target-slot start to the L1 publish deadline:
265+
// aztecSlotDuration - 2*ethereumSlotDuration = 72 - 24 = 48s, giving straggler attestations
266+
// 48s + 500ms grace after the receiver rolls into the target slot, so the proposer can keep
267+
// collecting useful attestations until right before the publish deadline.
267268
let epochCache: ReturnType<typeof mock<EpochCacheInterface>>;
268269
let pipeliningWindow: PipeliningWindow;
269270

@@ -282,34 +283,31 @@ describe('clock_tolerance', () => {
282283
epoch: 1 as any,
283284
slot: SlotNumber(100),
284285
ts: 1000n,
285-
nowMs: 1003000n, // 3000ms elapsed, within 4500ms straggler grace
286+
nowMs: 1003000n, // 3000ms elapsed, within 48500ms straggler grace
286287
});
287288

288289
expect(pipeliningWindow.acceptsAttestation(SlotNumber(100))).toBe(true);
289290
});
290291

291-
it('rejects a current-slot attestation past the straggler window', () => {
292+
it('accepts an attestation arriving well into the target slot (previously rejected past ~4.5s)', () => {
292293
epochCache.getEpochAndSlotNow.mockReturnValue({
293294
epoch: 1 as any,
294295
slot: SlotNumber(100),
295296
ts: 1000n,
296-
nowMs: 1005000n, // 5000ms elapsed, past 4500ms cutoff
297+
nowMs: 1030000n, // 30s into the target slot — past the old 4.5s window, within the new 48.5s
297298
});
298299

299-
expect(pipeliningWindow.acceptsAttestation(SlotNumber(100))).toBe(false);
300+
expect(pipeliningWindow.acceptsAttestation(SlotNumber(100))).toBe(true);
300301
});
301302

302-
it('scales the straggler window with p2pPropagationTime', () => {
303-
// With p2pPropagationTime = 4, straggler window = 8s + 500ms disparity.
304-
const longer = new PipeliningWindow(epochCache, { l1PublishingTime: 12, p2pPropagationTime: 4 });
303+
it('rejects a current-slot attestation past the L1 publish deadline window', () => {
305304
epochCache.getEpochAndSlotNow.mockReturnValue({
306305
epoch: 1 as any,
307306
slot: SlotNumber(100),
308307
ts: 1000n,
309-
nowMs: 1007000n,
308+
nowMs: 1049000n, // 49s elapsed, past the 48.5s cutoff (48s window + 500ms disparity)
310309
});
311310

312-
expect(longer.acceptsAttestation(SlotNumber(100))).toBe(true);
313311
expect(pipeliningWindow.acceptsAttestation(SlotNumber(100))).toBe(false);
314312
});
315313
});

yarn-project/sequencer-client/src/sequencer/README.md

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -94,11 +94,17 @@ These constants come from `@aztec/stdlib/timetable` (see `stdlib/src/timetable/i
9494
| `WAITING_FOR_TXS` | `initializeDeadline + checkpointInitializationTime` |
9595
| `CREATING_BLOCK` | same as `WAITING_FOR_TXS` |
9696
| `WAITING_UNTIL_NEXT_BLOCK` | same as `WAITING_FOR_TXS` |
97-
| `ASSEMBLING_CHECKPOINT` | `aztecSlotDuration` (assembly completes by the build-slot boundary) |
97+
| `ASSEMBLING_CHECKPOINT` | `aztecSlotDuration + pipeliningAttestationGracePeriod` |
9898
| `COLLECTING_ATTESTATIONS` | same as `ASSEMBLING_CHECKPOINT` |
99-
| `PUBLISHING_CHECKPOINT` | `2 * aztecSlotDuration - l1PublishingTime` (extends into the target slot) |
99+
| `PUBLISHING_CHECKPOINT` | `2 * aztecSlotDuration - ethereumSlotDuration` (extends into the target slot) |
100100

101-
`ASSEMBLING_CHECKPOINT` and `COLLECTING_ATTESTATIONS` must be *entered* before the build-slot boundary; once entered, attestation collection itself has its own `checkpointAttestationDeadline = 2 * aztecSlotDuration - l1PublishingTime`, so a late attestation arriving after the boundary is still accepted. The publishing deadline extends into the target slot because that is when the L1 tx is actually submitted.
101+
In production-like timing, `pipeliningAttestationGracePeriod` is zero, so `ASSEMBLING_CHECKPOINT` and
102+
`COLLECTING_ATTESTATIONS` must be *entered* before the build-slot boundary. Local networks with
103+
`l1PublishingTime < ethereumSlotDuration` can use the target-slot attestation window as grace while preserving the
104+
L1-geometry publishing cutoff. Once entered, attestation collection itself has its own
105+
`checkpointAttestationDeadline = 2 * aztecSlotDuration - ethereumSlotDuration`, so a late attestation arriving after
106+
the boundary is still accepted. The publishing deadline extends into the target slot because that is when the L1 tx is
107+
actually submitted.
102108

103109
## Example: 72 s slot, 8 s sub-slots
104110

@@ -208,7 +214,9 @@ The current sub-slot is dropped without committing anything. The loop retries on
208214

209215
### Build slot ends before attestations arrive
210216

211-
`assertTimeLeft` will reject `PUBLISHING_CHECKPOINT` if the attestation deadline has passed; the slot is abandoned, and `checkpoint-publish-failed` is emitted. The `PUBLISHING_CHECKPOINT` deadline allows spillover into the target slot (`2 * aztecSlotDuration - l1PublishingTime`) precisely to absorb a small overrun.
217+
`assertTimeLeft` will reject `PUBLISHING_CHECKPOINT` if the attestation deadline has passed; the slot is abandoned, and
218+
`checkpoint-publish-failed` is emitted. The `PUBLISHING_CHECKPOINT` deadline allows spillover into the target slot
219+
(`2 * aztecSlotDuration - ethereumSlotDuration`) precisely to absorb a small overrun.
212220

213221
### Pipelined parent fails on L1
214222

@@ -230,4 +238,6 @@ aztecSlotDuration ≥ checkpointInitializationTime
230238

231239
Block duration should be ≥ `minExecutionTime` (otherwise no sub-slot ever has enough headroom). `p2pPropagationTime` should be measured against the deployment's actual p2p latency: it directly determines how much of each slot is spent on the cooldown.
232240

233-
`l1PublishingTime` should fit inside the Ethereum slot the target slot maps to. The default of 12 s lines up with one Ethereum slot; congested deployments may need to increase it (which only affects the `PUBLISHING_CHECKPOINT` deadline, not the number of blocks built).
241+
`l1PublishingTime` should fit inside the Ethereum slot the target slot maps to. The default of 12 s lines up with one
242+
Ethereum slot; fast local networks may reduce it to use the target-slot attestation window as assembly and attestation
243+
grace.

yarn-project/sequencer-client/src/sequencer/checkpoint_proposal_job.timing.test.ts

Lines changed: 89 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { type P2P, P2PClientState } from '@aztec/p2p';
1111
import type { SlasherClientInterface } from '@aztec/slasher';
1212
import { AztecAddress } from '@aztec/stdlib/aztec-address';
1313
import type { L2Block, L2BlockSink, L2BlockSource, ProposedCheckpointSink } from '@aztec/stdlib/block';
14-
import type { L1RollupConstants } from '@aztec/stdlib/epoch-helpers';
14+
import { type L1RollupConstants, getSlotStartBuildTimestamp } from '@aztec/stdlib/epoch-helpers';
1515
import { GasFees } from '@aztec/stdlib/gas';
1616
import type {
1717
BlockBuilderOptions,
@@ -301,8 +301,9 @@ describe('CheckpointProposalJob Timing Tests', () => {
301301
}
302302

303303
/** Create a TimingTestCheckpointProposalJob with current mocks */
304-
function createJob(): TimingTestCheckpointProposalJob {
305-
const setStateFn = jest.fn();
304+
function createJob(
305+
setStateFn: (state: SequencerState, slot?: SlotNumber, timeReferenceSlot?: SlotNumber) => void = jest.fn(),
306+
): TimingTestCheckpointProposalJob {
306307
const eventEmitter = new EventEmitter() as TypedEventEmitter<SequencerEvents>;
307308

308309
return new TimingTestCheckpointProposalJob(
@@ -1138,4 +1139,89 @@ describe('CheckpointProposalJob Timing Tests', () => {
11381139
expect(publisher.sendRequestsAt).toHaveBeenCalledWith(targetSlot);
11391140
});
11401141
});
1142+
1143+
describe('Build-frame deadline enforcement', () => {
1144+
// Regression coverage for the frame bug: build-frame state deadlines must be measured against
1145+
// the build slot (`slotNow`), not the target slot. Passing `targetSlot` shifted every deadline a
1146+
// full Aztec slot into the future (frame B), so they never fired.
1147+
1148+
// States the job owns and sets while building inside the build frame. Their deadlines must be
1149+
// enforced against `slotNow`.
1150+
const buildFrameStates = [
1151+
SequencerState.INITIALIZING_CHECKPOINT,
1152+
SequencerState.WAITING_FOR_TXS,
1153+
SequencerState.CREATING_BLOCK,
1154+
SequencerState.WAITING_UNTIL_NEXT_BLOCK,
1155+
SequencerState.ASSEMBLING_CHECKPOINT,
1156+
SequencerState.COLLECTING_ATTESTATIONS,
1157+
SequencerState.PUBLISHING_CHECKPOINT,
1158+
];
1159+
1160+
it('passes the target slot to observers and the build slot to deadline checks for build-frame states', async () => {
1161+
const { blocks, txs } = await createTestBlocksAndTxs(2);
1162+
mockP2pWithTxs(txs);
1163+
// Force a single WAITING_FOR_TXS poll before the first block by reporting no pending txs once.
1164+
p2p.getPendingTxCount.mockResolvedValueOnce(0);
1165+
checkpointBuilder.seedBlocks(
1166+
blocks,
1167+
blocks.map((_, i) => [txs[i]]),
1168+
);
1169+
checkpointBuilder.setExecutionDurations([5, 5]);
1170+
1171+
validatorClient.collectAttestations.mockResolvedValue(getAttestations(blocks[1]));
1172+
1173+
setTimeInSlot(1);
1174+
1175+
const observedSlots = new Map<
1176+
SequencerState,
1177+
{ slot: SlotNumber | undefined; timeReferenceSlot: SlotNumber | undefined }
1178+
>();
1179+
const setStateFn = jest.fn((state: SequencerState, slot?: SlotNumber, timeReferenceSlot?: SlotNumber) => {
1180+
observedSlots.set(state, { slot, timeReferenceSlot });
1181+
});
1182+
1183+
const job = createJob(setStateFn);
1184+
await job.execute();
1185+
await job.awaitPendingSubmission();
1186+
1187+
// The build and target slot differ, so the bug would be observable.
1188+
expect(Number(targetSlot)).toBe(Number(slotNumber) + PROPOSER_PIPELINING_SLOT_OFFSET);
1189+
1190+
for (const state of buildFrameStates) {
1191+
expect(observedSlots.has(state)).toBe(true);
1192+
expect(observedSlots.get(state)?.slot).toBe(targetSlot);
1193+
expect(observedSlots.get(state)?.timeReferenceSlot).toBe(slotNumber);
1194+
}
1195+
});
1196+
1197+
it('abandons the slot when a build-frame deadline is missed (assembly past the build-slot boundary)', async () => {
1198+
const { blocks, txs } = await createTestBlocksAndTxs(1);
1199+
mockP2pWithTxs(txs);
1200+
checkpointBuilder.seedBlocks(blocks, [[txs[0]]]);
1201+
// Single block runs 50s -> 80s (in the build frame), so ASSEMBLING_CHECKPOINT is entered at
1202+
// ~80s into the build frame, past its deadline (checkpointAssemblyDeadline = 72s in frame A).
1203+
checkpointBuilder.setExecutionDurations([30]);
1204+
1205+
validatorClient.collectAttestations.mockResolvedValue(getAttestations(blocks[0]));
1206+
1207+
setTimeInSlot(50);
1208+
1209+
// Enforcing setStateFn mirroring Sequencer.setState: measures the deadline against the
1210+
// explicit time reference slot when one is provided.
1211+
const setStateFn = (state: SequencerState, slot?: SlotNumber, timeReferenceSlot?: SlotNumber) => {
1212+
const slotForTiming = timeReferenceSlot ?? slot;
1213+
if (slotForTiming !== undefined) {
1214+
const ref = getSlotStartBuildTimestamp(slotForTiming, l1Constants);
1215+
timetable.assertTimeLeft(state, dateProvider.nowInSeconds() - ref);
1216+
}
1217+
};
1218+
1219+
const job = createJob(setStateFn);
1220+
const checkpoint = await job.execute();
1221+
1222+
// Past the build-frame assembly deadline: the SequencerTooSlowError is caught inside
1223+
// proposeCheckpoint, which returns undefined, so no checkpoint is produced.
1224+
expect(checkpoint).toBeUndefined();
1225+
});
1226+
});
11411227
});

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

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ export class CheckpointProposalJob implements Traceable {
149149
private readonly metrics: SequencerMetrics,
150150
private readonly checkpointMetrics: CheckpointProposalJobMetricsRecorder,
151151
protected readonly eventEmitter: TypedEventEmitter<SequencerEvents>,
152-
private readonly setStateFn: (state: SequencerState, slot?: SlotNumber) => void,
152+
private readonly setStateFn: (state: SequencerState, slot?: SlotNumber, timeReferenceSlot?: SlotNumber) => void,
153153
public readonly tracer: Tracer,
154154
bindings?: LoggerBindings,
155155
private readonly proposedCheckpointData?: ProposedCheckpointData,
@@ -328,7 +328,8 @@ export class CheckpointProposalJob implements Traceable {
328328
private async enqueueCheckpointForSubmission(result: CheckpointProposalResult): Promise<void> {
329329
const { checkpoint, attestations, attestationsSignature } = result;
330330

331-
this.setStateFn(SequencerState.PUBLISHING_CHECKPOINT, this.targetSlot);
331+
// Build-frame deadlines are measured against `slotNow`; observers still see `targetSlot`.
332+
this.setStateFn(SequencerState.PUBLISHING_CHECKPOINT, this.targetSlot, this.slotNow);
332333
const aztecSlotDuration = this.l1Constants.slotDuration;
333334
const submissionSlotStart = Number(getTimestampForSlot(this.targetSlot, this.l1Constants));
334335
const txTimeoutAt = new Date((submissionSlotStart + aztecSlotDuration) * 1000);
@@ -518,7 +519,7 @@ export class CheckpointProposalJob implements Traceable {
518519
const feeRecipient = this.validatorClient.getFeeRecipientForAttestor(this.attestorAddress);
519520

520521
// Start the checkpoint
521-
this.setStateFn(SequencerState.INITIALIZING_CHECKPOINT, this.targetSlot);
522+
this.setStateFn(SequencerState.INITIALIZING_CHECKPOINT, this.targetSlot, this.slotNow);
522523
this.logCheckpointEvent('slot-started', `Starting checkpoint proposal for slot ${this.targetSlot}`, {
523524
buildSlot: this.slotNow,
524525
submissionSlot: this.targetSlot,
@@ -671,7 +672,7 @@ export class CheckpointProposalJob implements Traceable {
671672

672673
// Assemble and broadcast the checkpoint proposal, including the last block that was not
673674
// broadcasted yet, and wait to collect the committee attestations.
674-
this.setStateFn(SequencerState.ASSEMBLING_CHECKPOINT, this.targetSlot);
675+
this.setStateFn(SequencerState.ASSEMBLING_CHECKPOINT, this.targetSlot, this.slotNow);
675676
const checkpoint = await checkpointBuilder.completeCheckpoint();
676677

677678
// Final validation: per-block limits are only checked if the operator set them explicitly.
@@ -963,7 +964,7 @@ export class CheckpointProposalJob implements Traceable {
963964
/** Sleeps until it is time to produce the next block in the slot */
964965
@trackSpan('CheckpointProposalJob.waitUntilNextSubslot')
965966
private async waitUntilNextSubslot(nextSubslotStart: number) {
966-
this.setStateFn(SequencerState.WAITING_UNTIL_NEXT_BLOCK, this.targetSlot);
967+
this.setStateFn(SequencerState.WAITING_UNTIL_NEXT_BLOCK, this.targetSlot, this.slotNow);
967968
this.log.verbose(`Waiting until time for the next block at ${nextSubslotStart}s into slot`, {
968969
slot: this.targetSlot,
969970
});
@@ -1034,7 +1035,7 @@ export class CheckpointProposalJob implements Traceable {
10341035
`Building block ${blockNumber} at index ${indexWithinCheckpoint} for slot ${this.targetSlot} with ${availableTxs} available txs`,
10351036
{ slot: this.targetSlot, blockNumber, indexWithinCheckpoint },
10361037
);
1037-
this.setStateFn(SequencerState.CREATING_BLOCK, this.targetSlot);
1038+
this.setStateFn(SequencerState.CREATING_BLOCK, this.targetSlot, this.slotNow);
10381039

10391040
// Per-block limits are operator overrides (from SEQ_MAX_L2_BLOCK_GAS etc.) further capped
10401041
// by remaining checkpoint-level budgets inside CheckpointBuilder before each block is built.
@@ -1205,7 +1206,7 @@ export class CheckpointProposalJob implements Traceable {
12051206
}
12061207

12071208
// Wait a bit before checking again
1208-
this.setStateFn(SequencerState.WAITING_FOR_TXS, this.targetSlot);
1209+
this.setStateFn(SequencerState.WAITING_FOR_TXS, this.targetSlot, this.slotNow);
12091210
this.log.verbose(
12101211
`Waiting for enough txs to build block ${blockNumber} at index ${indexWithinCheckpoint} in slot ${this.targetSlot} (have ${availableTxs} but need ${minTxs})`,
12111212
{ blockNumber, slot: this.targetSlot, indexWithinCheckpoint },
@@ -1221,7 +1222,7 @@ export class CheckpointProposalJob implements Traceable {
12211222
broadcast: CheckpointProposalBroadcast,
12221223
): Promise<{ attestations: CommitteeAttestationsAndSigners; attestationsSignature: Signature } | undefined> {
12231224
const { proposal, blockProposedAt } = broadcast;
1224-
this.setStateFn(SequencerState.COLLECTING_ATTESTATIONS, this.targetSlot);
1225+
this.setStateFn(SequencerState.COLLECTING_ATTESTATIONS, this.targetSlot, this.slotNow);
12251226
const attestations = await this.waitForAttestations(proposal);
12261227
if (!attestations) {
12271228
return undefined;

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export type SequencerEvents = {
1010
newState: SequencerState;
1111
secondsIntoSlot?: number;
1212
slot?: SlotNumber;
13+
timeReferenceSlot?: SlotNumber;
1314
}) => void;
1415
/**
1516
* Emitted by the sequencer once it has decided it is going to attempt to build a

0 commit comments

Comments
 (0)