@aztec/sequencer-client 0.0.1-commit.993d240 → 0.0.1-commit.9a89641

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/README.md +40 -41
  2. package/dest/client/sequencer-client.d.ts +16 -3
  3. package/dest/client/sequencer-client.d.ts.map +1 -1
  4. package/dest/client/sequencer-client.js +18 -14
  5. package/dest/config.d.ts +9 -4
  6. package/dest/config.d.ts.map +1 -1
  7. package/dest/config.js +29 -15
  8. package/dest/global_variable_builder/fee_predictor.d.ts +1 -1
  9. package/dest/global_variable_builder/fee_predictor.d.ts.map +1 -1
  10. package/dest/global_variable_builder/fee_predictor.js +11 -1
  11. package/dest/global_variable_builder/fee_provider.d.ts +1 -1
  12. package/dest/global_variable_builder/fee_provider.d.ts.map +1 -1
  13. package/dest/global_variable_builder/fee_provider.js +27 -5
  14. package/dest/global_variable_builder/global_builder.d.ts +3 -16
  15. package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
  16. package/dest/global_variable_builder/global_builder.js +2 -25
  17. package/dest/index.d.ts +2 -2
  18. package/dest/index.d.ts.map +1 -1
  19. package/dest/index.js +1 -1
  20. package/dest/publisher/config.d.ts +5 -1
  21. package/dest/publisher/config.d.ts.map +1 -1
  22. package/dest/publisher/config.js +11 -1
  23. package/dest/publisher/l1_to_l2_messaging.d.ts +21 -0
  24. package/dest/publisher/l1_to_l2_messaging.d.ts.map +1 -0
  25. package/dest/publisher/l1_to_l2_messaging.js +70 -0
  26. package/dest/publisher/l1_tx_failed_store/failed_tx_store.d.ts +48 -8
  27. package/dest/publisher/l1_tx_failed_store/failed_tx_store.d.ts.map +1 -1
  28. package/dest/publisher/l1_tx_failed_store/failed_tx_store.js +68 -1
  29. package/dest/publisher/l1_tx_failed_store/file_store_failed_tx_store.d.ts +2 -2
  30. package/dest/publisher/l1_tx_failed_store/file_store_failed_tx_store.d.ts.map +1 -1
  31. package/dest/publisher/l1_tx_failed_store/file_store_failed_tx_store.js +4 -2
  32. package/dest/publisher/l1_tx_failed_store/index.d.ts +2 -2
  33. package/dest/publisher/l1_tx_failed_store/index.d.ts.map +1 -1
  34. package/dest/publisher/l1_tx_failed_store/index.js +1 -0
  35. package/dest/publisher/sequencer-publisher.d.ts +39 -10
  36. package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
  37. package/dest/publisher/sequencer-publisher.js +403 -75
  38. package/dest/publisher/write_json.d.ts +11 -0
  39. package/dest/publisher/write_json.d.ts.map +1 -0
  40. package/dest/publisher/write_json.js +57 -0
  41. package/dest/sequencer/automine/automine_factory.d.ts +5 -3
  42. package/dest/sequencer/automine/automine_factory.d.ts.map +1 -1
  43. package/dest/sequencer/automine/automine_factory.js +2 -1
  44. package/dest/sequencer/automine/automine_sequencer.d.ts +43 -5
  45. package/dest/sequencer/automine/automine_sequencer.d.ts.map +1 -1
  46. package/dest/sequencer/automine/automine_sequencer.js +209 -19
  47. package/dest/sequencer/automine/index.d.ts +3 -0
  48. package/dest/sequencer/automine/index.d.ts.map +1 -0
  49. package/dest/sequencer/automine/index.js +2 -0
  50. package/dest/sequencer/checkpoint_proposal_job.d.ts +28 -15
  51. package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -1
  52. package/dest/sequencer/checkpoint_proposal_job.js +158 -117
  53. package/dest/sequencer/events.d.ts +16 -4
  54. package/dest/sequencer/events.d.ts.map +1 -1
  55. package/dest/sequencer/index.d.ts +1 -3
  56. package/dest/sequencer/index.d.ts.map +1 -1
  57. package/dest/sequencer/index.js +0 -2
  58. package/dest/sequencer/missing_committee.d.ts +72 -0
  59. package/dest/sequencer/missing_committee.d.ts.map +1 -0
  60. package/dest/sequencer/missing_committee.js +139 -0
  61. package/dest/sequencer/requests_tracker.d.ts +22 -0
  62. package/dest/sequencer/requests_tracker.d.ts.map +1 -0
  63. package/dest/sequencer/requests_tracker.js +33 -0
  64. package/dest/sequencer/sequencer.d.ts +113 -28
  65. package/dest/sequencer/sequencer.d.ts.map +1 -1
  66. package/dest/sequencer/sequencer.js +352 -162
  67. package/dest/test/index.d.ts +3 -3
  68. package/dest/test/index.d.ts.map +1 -1
  69. package/dest/test/utils.d.ts +15 -1
  70. package/dest/test/utils.d.ts.map +1 -1
  71. package/dest/test/utils.js +18 -1
  72. package/package.json +29 -28
  73. package/src/client/sequencer-client.ts +20 -14
  74. package/src/config.ts +32 -16
  75. package/src/global_variable_builder/fee_predictor.ts +11 -1
  76. package/src/global_variable_builder/fee_provider.ts +27 -5
  77. package/src/global_variable_builder/global_builder.ts +2 -34
  78. package/src/index.ts +1 -10
  79. package/src/publisher/config.ts +22 -1
  80. package/src/publisher/l1_to_l2_messaging.ts +85 -0
  81. package/src/publisher/l1_tx_failed_store/failed_tx_store.ts +114 -7
  82. package/src/publisher/l1_tx_failed_store/file_store_failed_tx_store.ts +4 -3
  83. package/src/publisher/l1_tx_failed_store/index.ts +1 -1
  84. package/src/publisher/sequencer-publisher.ts +430 -86
  85. package/src/publisher/write_json.ts +78 -0
  86. package/src/sequencer/README.md +16 -6
  87. package/src/sequencer/automine/README.md +18 -4
  88. package/src/sequencer/automine/automine_factory.ts +8 -1
  89. package/src/sequencer/automine/automine_sequencer.ts +229 -21
  90. package/src/sequencer/automine/index.ts +6 -0
  91. package/src/sequencer/checkpoint_proposal_job.ts +177 -132
  92. package/src/sequencer/events.ts +16 -3
  93. package/src/sequencer/index.ts +0 -2
  94. package/src/sequencer/missing_committee.ts +192 -0
  95. package/src/sequencer/requests_tracker.ts +43 -0
  96. package/src/sequencer/sequencer.ts +382 -176
  97. package/src/test/index.ts +2 -2
  98. package/src/test/utils.ts +33 -0
  99. package/dest/sequencer/chain_state_overrides.d.ts +0 -61
  100. package/dest/sequencer/chain_state_overrides.d.ts.map +0 -1
  101. package/dest/sequencer/chain_state_overrides.js +0 -98
  102. package/dest/sequencer/timetable.d.ts +0 -101
  103. package/dest/sequencer/timetable.d.ts.map +0 -1
  104. package/dest/sequencer/timetable.js +0 -231
  105. package/src/sequencer/chain_state_overrides.ts +0 -162
  106. package/src/sequencer/timetable.ts +0 -295
@@ -1,4 +1,4 @@
1
- import type { EpochCache } from '@aztec/epoch-cache';
1
+ import { type EpochCache, PROPOSER_PIPELINING_SLOT_OFFSET } from '@aztec/epoch-cache';
2
2
  import type { SimulationOverridesPlan } from '@aztec/ethereum/contracts';
3
3
  import {
4
4
  BlockNumber,
@@ -14,12 +14,12 @@ import {
14
14
  generateUnrecoverableSignature,
15
15
  } from '@aztec/foundation/crypto/secp256k1-signer';
16
16
  import { Fr } from '@aztec/foundation/curves/bn254';
17
+ import { InterruptError, TimeoutError } from '@aztec/foundation/error';
17
18
  import { EthAddress } from '@aztec/foundation/eth-address';
18
19
  import { Signature } from '@aztec/foundation/eth-signature';
19
20
  import { filter } from '@aztec/foundation/iterator';
20
21
  import { type Logger, type LoggerBindings, createLogger } from '@aztec/foundation/log';
21
- import { retryUntil } from '@aztec/foundation/retry';
22
- import { sleep, sleepUntil } from '@aztec/foundation/sleep';
22
+ import { InterruptibleSleep } from '@aztec/foundation/sleep';
23
23
  import { type DateProvider, Timer } from '@aztec/foundation/timer';
24
24
  import { type TypedEventEmitter, isErrorClass, unfreeze } from '@aztec/foundation/types';
25
25
  import type { P2P } from '@aztec/p2p';
@@ -31,16 +31,18 @@ import {
31
31
  type L2BlockSink,
32
32
  type L2BlockSource,
33
33
  MaliciousCommitteeAttestationsAndSigners,
34
+ MaliciousYParityCommitteeAttestationsAndSigners,
34
35
  type ProposedCheckpointSink,
35
36
  type ValidateCheckpointResult,
36
37
  } from '@aztec/stdlib/block';
37
38
  import {
38
39
  type Checkpoint,
39
40
  type ProposedCheckpointData,
41
+ buildCheckpointSimulationOverridesPlan,
40
42
  getPreviousCheckpointOutHashes,
41
43
  validateCheckpoint,
42
44
  } from '@aztec/stdlib/checkpoint';
43
- import { computeQuorum, getSlotStartBuildTimestamp, getTimestampForSlot } from '@aztec/stdlib/epoch-helpers';
45
+ import { computeQuorum, getTimestampForSlot } from '@aztec/stdlib/epoch-helpers';
44
46
  import { Gas } from '@aztec/stdlib/gas';
45
47
  import {
46
48
  type BlockBuilderOptions,
@@ -59,6 +61,7 @@ import type {
59
61
  } from '@aztec/stdlib/p2p';
60
62
  import { orderAttestations, trimAttestations } from '@aztec/stdlib/p2p';
61
63
  import type { L2BlockBuiltStats } from '@aztec/stdlib/stats';
64
+ import type { ProposerTimetable } from '@aztec/stdlib/timetable';
62
65
  import { type FailedTx, Tx } from '@aztec/stdlib/tx';
63
66
  import { AttestationTimeoutError } from '@aztec/stdlib/validators';
64
67
  import { Attributes, type Traceable, type Tracer, trackSpan } from '@aztec/telemetry-client';
@@ -67,18 +70,18 @@ import { DutyAlreadySignedError, SlashingProtectionError } from '@aztec/validato
67
70
 
68
71
  import type { GlobalVariableBuilder } from '../global_variable_builder/global_builder.js';
69
72
  import type { InvalidateCheckpointRequest, SequencerPublisher } from '../publisher/sequencer-publisher.js';
70
- import { buildCheckpointSimulationOverridesPlan } from './chain_state_overrides.js';
71
73
  import type { CheckpointProposalJobMetricsRecorder } from './checkpoint_proposal_job_metrics.js';
72
74
  import { CheckpointVoter } from './checkpoint_voter.js';
73
75
  import { SequencerInterruptedError } from './errors.js';
74
76
  import type { SequencerEvents } from './events.js';
75
77
  import type { SequencerMetrics } from './metrics.js';
76
- import type { SequencerTimetable } from './timetable.js';
78
+ import type { RequestsTracker } from './requests_tracker.js';
77
79
  import type { SequencerRollupConstants } from './types.js';
78
80
  import { SequencerState } from './utils.js';
79
81
 
80
82
  /** How much time to sleep while waiting for min transactions to accumulate for a block */
81
83
  const TXS_POLLING_MS = 500;
84
+ const ARCHIVER_SYNC_POLLING_MS = 200;
82
85
 
83
86
  /** Result from proposeCheckpoint when a checkpoint was successfully built and broadcast. */
84
87
  type CheckpointProposalBroadcast = {
@@ -104,8 +107,8 @@ export class CheckpointProposalJob implements Traceable {
104
107
  protected readonly log: Logger;
105
108
  private readonly checkpointEventLog: Logger;
106
109
 
107
- /** Tracks the fire-and-forget L1 submission promise so it can be awaited during shutdown. */
108
- private pendingL1Submission: Promise<void> | undefined;
110
+ private readonly interruptibleSleep = new InterruptibleSleep();
111
+ private interrupted = false;
109
112
 
110
113
  /**
111
114
  * Chain state overrides built once per slot in proposeCheckpoint after the checkpoint is
@@ -120,7 +123,6 @@ export class CheckpointProposalJob implements Traceable {
120
123
  }
121
124
 
122
125
  constructor(
123
- private readonly slotNow: SlotNumber,
124
126
  private readonly targetSlot: SlotNumber,
125
127
  private readonly targetEpoch: EpochNumber,
126
128
  private readonly checkpointNumber: CheckpointNumber,
@@ -142,32 +144,70 @@ export class CheckpointProposalJob implements Traceable {
142
144
  private readonly l1Constants: SequencerRollupConstants,
143
145
  private readonly signatureContext: CoordinationSignatureContext,
144
146
  protected config: ResolvedSequencerConfig,
145
- protected timetable: SequencerTimetable,
147
+ protected timetable: ProposerTimetable,
146
148
  private readonly slasherClient: SlasherClientInterface | undefined,
147
149
  private readonly epochCache: EpochCache,
148
150
  private readonly dateProvider: DateProvider,
149
151
  private readonly metrics: SequencerMetrics,
150
152
  private readonly checkpointMetrics: CheckpointProposalJobMetricsRecorder,
151
153
  protected readonly eventEmitter: TypedEventEmitter<SequencerEvents>,
152
- private readonly setStateFn: (state: SequencerState, slot?: SlotNumber) => void,
154
+ // Shared with the owning sequencer, which drains it during shutdown; the fire-and-forget L1
155
+ // submission this job backgrounds is tracked here rather than in a job-local tracker.
156
+ protected readonly pendingRequests: RequestsTracker,
157
+ private readonly setStateFn: (state: SequencerState, slot: SlotNumber) => void,
153
158
  public readonly tracer: Tracer,
154
159
  bindings?: LoggerBindings,
155
160
  private readonly proposedCheckpointData?: ProposedCheckpointData,
156
161
  ) {
157
162
  this.log = createLogger('sequencer:checkpoint-proposal', {
158
163
  ...bindings,
159
- instanceId: `slot-${this.slotNow}`,
164
+ instanceId: `slot-${this.getBuildSlot()}`,
160
165
  });
161
166
  this.checkpointEventLog = createLogger('sequencer:checkpoint-events', {
162
167
  ...bindings,
163
- instanceId: `slot-${this.slotNow}`,
168
+ instanceId: `slot-${this.getBuildSlot()}`,
164
169
  });
165
170
  }
166
171
 
167
- /** Awaits the pending L1 submission if one is in progress. Call during shutdown. */
168
- public async awaitPendingSubmission(): Promise<void> {
169
- this.log.info('Awaiting pending L1 payload submission');
170
- await this.pendingL1Submission;
172
+ /**
173
+ * The wall-clock slot during which this job builds, i.e. the slot one before {@link targetSlot} under
174
+ * proposer pipelining. Also the slot of the parent checkpoint this job builds on top of.
175
+ */
176
+ private getBuildSlot(): SlotNumber {
177
+ return SlotNumber(this.targetSlot - PROPOSER_PIPELINING_SLOT_OFFSET);
178
+ }
179
+
180
+ /**
181
+ * Sets the sequencer state for this job, reporting the target slot the checkpoint is being proposed for
182
+ * (not the wall-clock build slot). The slot is informational on the event payload/metrics; the job knows
183
+ * its own target slot, so callers only pass the state.
184
+ */
185
+ private setState(state: SequencerState): void {
186
+ this.setStateFn(state, this.targetSlot);
187
+ }
188
+
189
+ /** Interrupts job-owned waits, including the publisher's send-at-slot sleep, so shutdown can finish. */
190
+ public interrupt(): void {
191
+ this.interrupted = true;
192
+ this.interruptibleSleep.interrupt(true);
193
+ this.publisher.interrupt();
194
+ }
195
+
196
+ private async awaitInterruptibleSleep(ms: number): Promise<void> {
197
+ if (this.interrupted) {
198
+ throw new SequencerInterruptedError();
199
+ }
200
+ if (ms <= 0) {
201
+ return;
202
+ }
203
+ try {
204
+ await this.interruptibleSleep.sleep(ms);
205
+ } catch (err) {
206
+ if (err instanceof InterruptError) {
207
+ throw new SequencerInterruptedError();
208
+ }
209
+ throw err;
210
+ }
171
211
  }
172
212
 
173
213
  private logCheckpointEvent(eventName: string, message: string, fields: Record<string, unknown>): void {
@@ -214,7 +254,7 @@ export class CheckpointProposalJob implements Traceable {
214
254
  // signature verification to fail silently inside Multicall3. Delay submission to the
215
255
  // start of `targetSlot` so the tx mines in the slot the vote was signed for.
216
256
  if (!this.config.fishermanMode) {
217
- this.pendingL1Submission = this.publisher.sendRequestsAt(this.targetSlot).then(() => {});
257
+ this.pendingRequests.trackRequest(this.publisher.sendRequestsAt(this.targetSlot), () => this.interrupt());
218
258
  }
219
259
  return undefined;
220
260
  }
@@ -229,7 +269,9 @@ export class CheckpointProposalJob implements Traceable {
229
269
  }
230
270
 
231
271
  // Background the attestation → signing → L1 pipeline so the work loop is unblocked
232
- this.pendingL1Submission = this.waitForAttestationsAndEnqueueSubmissionAsync(broadcast, votesPromises);
272
+ this.pendingRequests.trackRequest(this.waitForAttestationsAndEnqueueSubmissionAsync(broadcast, votesPromises), () =>
273
+ this.interrupt(),
274
+ );
233
275
 
234
276
  // Return the built checkpoint immediately — the work loop is now unblocked
235
277
  return checkpoint;
@@ -237,14 +279,13 @@ export class CheckpointProposalJob implements Traceable {
237
279
 
238
280
  /**
239
281
  * Background pipeline: collects attestations, signs them, enqueues the checkpoint, and submits to L1.
240
- * Runs as a fire-and-forget task stored in `pendingL1Submission` so the work loop is unblocked.
282
+ * Runs as a fire-and-forget task tracked in the sequencer's shared tracker so the work loop is unblocked.
241
283
  */
242
284
  private async waitForAttestationsAndEnqueueSubmissionAsync(
243
285
  broadcast: CheckpointProposalBroadcast,
244
286
  votesPromises: Promise<unknown>[],
245
287
  ): Promise<void> {
246
288
  const { checkpoint } = broadcast;
247
- const isPipelining = this.epochCache.isProposerPipeliningEnabled();
248
289
 
249
290
  try {
250
291
  // Wait for all votes actions, enqueued at the beginning, to resolve
@@ -253,15 +294,14 @@ export class CheckpointProposalJob implements Traceable {
253
294
  // Try to collect attestations from the committee
254
295
  const signedAttestations = await this.getSignedCommitteeAttestations(broadcast);
255
296
 
256
- // If pipelining, wait for the previous checkpoint to land on L1 before submitting,
257
- // so we can check it matches the proposed checkpoint we used as parent, and has valid attestations.
258
- if (signedAttestations && (!isPipelining || (await this.waitForValidParentCheckpointOnL1()))) {
297
+ // Wait for the previous checkpoint to land on L1 before submitting, so we can check it
298
+ // matches the proposed checkpoint we used as parent, and has valid attestations.
299
+ if (signedAttestations && (await this.waitForValidParentCheckpointOnL1())) {
259
300
  await this.enqueueCheckpointForSubmission({ checkpoint, ...signedAttestations });
260
301
  }
261
302
 
262
303
  // If we failed to collect attestations, at least check if we need to issue an invalidation
263
- // Note that if we are not pipelining, we enqueued the invalidation at the beginning
264
- if (!signedAttestations && isPipelining && (await this.waitForSyncedL2SlotNumber(this.slotNow))) {
304
+ if (!signedAttestations && (await this.waitForSyncedL2SlotNumber(this.getBuildSlot()))) {
265
305
  const validationStatus = await this.l2BlockSource.getPendingChainValidationStatus();
266
306
  if (!validationStatus.valid) {
267
307
  this.log.warn(
@@ -305,9 +345,7 @@ export class CheckpointProposalJob implements Traceable {
305
345
  reason: 'propose_action_not_successful',
306
346
  });
307
347
  this.eventEmitter.emit('checkpoint-publish-failed', { ...l1Response, slot: this.targetSlot });
308
- if (isPipelining) {
309
- this.metrics.recordPipelineDiscard();
310
- }
348
+ this.metrics.recordPipelineDiscard();
311
349
  }
312
350
  } catch (err) {
313
351
  if (err instanceof SequencerInterruptedError) {
@@ -324,9 +362,7 @@ export class CheckpointProposalJob implements Traceable {
324
362
  reason: err instanceof Error ? err.message : String(err),
325
363
  });
326
364
  this.eventEmitter.emit('checkpoint-publish-failed', { slot: this.targetSlot });
327
- if (isPipelining) {
328
- this.metrics.recordPipelineDiscard();
329
- }
365
+ this.metrics.recordPipelineDiscard();
330
366
  }
331
367
  }
332
368
 
@@ -334,10 +370,18 @@ export class CheckpointProposalJob implements Traceable {
334
370
  private async enqueueCheckpointForSubmission(result: CheckpointProposalResult): Promise<void> {
335
371
  const { checkpoint, attestations, attestationsSignature } = result;
336
372
 
337
- this.setStateFn(SequencerState.PUBLISHING_CHECKPOINT, this.targetSlot);
338
- const aztecSlotDuration = this.l1Constants.slotDuration;
339
- const submissionSlotStart = Number(getTimestampForSlot(this.targetSlot, this.l1Constants));
340
- const txTimeoutAt = new Date((submissionSlotStart + aztecSlotDuration) * 1000);
373
+ this.setState(SequencerState.PUBLISHING_CHECKPOINT);
374
+ // Latest L1 block the propose can still land in for the target slot: the last Ethereum block inside
375
+ // the target slot (`target_slot_start + S - E`). This is one ethereum slot later than
376
+ // `attestation_deadline` (= last_ethereum_block_in_target_slot - E), which bounds when validators must
377
+ // have signed, not when the proposer must have sent. Using the attestation deadline here is too tight:
378
+ // attestations are collected up to (and, when not enforcing, past) it, so the propose tx would be
379
+ // enqueued already expired and time out before it can mine.
380
+ const lastL1BlockInTargetSlot =
381
+ Number(getTimestampForSlot(this.targetSlot, this.l1Constants)) +
382
+ this.l1Constants.slotDuration -
383
+ this.l1Constants.ethereumSlotDuration;
384
+ const txTimeoutAt = new Date(lastL1BlockInTargetSlot * 1000);
341
385
 
342
386
  // If we have been configured to potentially skip publishing checkpoint then roll the dice here
343
387
  if (
@@ -353,9 +397,7 @@ export class CheckpointProposalJob implements Traceable {
353
397
  }
354
398
  }
355
399
 
356
- await this.publisher.enqueueProposeCheckpoint(checkpoint, attestations, attestationsSignature, {
357
- txTimeoutAt,
358
- });
400
+ await this.publisher.enqueueProposeCheckpoint(checkpoint, attestations, attestationsSignature, { txTimeoutAt });
359
401
  }
360
402
 
361
403
  /**
@@ -370,16 +412,21 @@ export class CheckpointProposalJob implements Traceable {
370
412
  const timeoutSeconds = Math.max(0.1, (targetSlotEndMs + syncDelayTolerance - this.dateProvider.now()) / 1000);
371
413
 
372
414
  try {
373
- return await retryUntil(
374
- async () => {
375
- const syncedSlot = await this.l2BlockSource.getSyncedL2SlotNumber();
376
- return syncedSlot !== undefined && syncedSlot >= waitForSlot;
377
- },
378
- `archiver sync past slot ${waitForSlot}`,
379
- timeoutSeconds,
380
- 0.2,
381
- );
382
- } catch {
415
+ const timer = new Timer();
416
+ while (true) {
417
+ const syncedSlot = await this.l2BlockSource.getSyncedL2SlotNumber();
418
+ if (syncedSlot !== undefined && syncedSlot >= waitForSlot) {
419
+ return true;
420
+ }
421
+ if (timeoutSeconds && timer.s() > timeoutSeconds) {
422
+ throw new TimeoutError(`Timeout awaiting archiver sync past slot ${waitForSlot}`);
423
+ }
424
+ await this.awaitInterruptibleSleep(ARCHIVER_SYNC_POLLING_MS);
425
+ }
426
+ } catch (err) {
427
+ if (err instanceof SequencerInterruptedError) {
428
+ throw err;
429
+ }
383
430
  this.log.warn(
384
431
  `Archiver did not sync L1 past slot ${waitForSlot} before slot ${this.targetSlot} expired, discarding pipelined work`,
385
432
  { checkpointNumber: this.checkpointNumber },
@@ -406,8 +453,8 @@ export class CheckpointProposalJob implements Traceable {
406
453
 
407
454
  const parentCheckpointNumber = CheckpointNumber(this.checkpointNumber - 1);
408
455
 
409
- // Wait until archiver has synced L1 past the parent's slot (slotNow)
410
- if (!(await this.waitForSyncedL2SlotNumber(this.slotNow))) {
456
+ // Wait until archiver has synced L1 past the parent's slot (the build slot, one before targetSlot)
457
+ if (!(await this.waitForSyncedL2SlotNumber(this.getBuildSlot()))) {
411
458
  return false;
412
459
  }
413
460
 
@@ -510,11 +557,11 @@ export class CheckpointProposalJob implements Traceable {
510
557
  private async proposeCheckpoint(): Promise<CheckpointProposalBroadcast | undefined> {
511
558
  try {
512
559
  const now = this.dateProvider.now();
513
- if (this.epochCache.isProposerPipeliningEnabled() && this.proposedCheckpointData) {
560
+ if (this.proposedCheckpointData) {
514
561
  // Measure against the wall-clock slot whose build window we are currently using.
515
562
  // In pipelining mode `targetSlot` is intentionally one slot ahead, which makes the
516
563
  // target-slot boundary a full slot away from the actual build start time.
517
- const slotBoundaryMs = Number(getTimestampForSlot(this.slotNow, this.l1Constants)) * 1000;
564
+ const slotBoundaryMs = Number(getTimestampForSlot(this.getBuildSlot(), this.l1Constants)) * 1000;
518
565
  this.checkpointMetrics.recordPipelinedCheckpointBuildStartOffsetFromSlotBoundary(now - slotBoundaryMs);
519
566
  }
520
567
  this.checkpointMetrics.startCheckpointTiming(now);
@@ -524,13 +571,12 @@ export class CheckpointProposalJob implements Traceable {
524
571
  const feeRecipient = this.validatorClient.getFeeRecipientForAttestor(this.attestorAddress);
525
572
 
526
573
  // Start the checkpoint
527
- this.setStateFn(SequencerState.INITIALIZING_CHECKPOINT, this.targetSlot);
574
+ this.setState(SequencerState.INITIALIZING_CHECKPOINT);
528
575
  this.logCheckpointEvent('slot-started', `Starting checkpoint proposal for slot ${this.targetSlot}`, {
529
- buildSlot: this.slotNow,
576
+ buildSlot: this.getBuildSlot(),
530
577
  submissionSlot: this.targetSlot,
531
578
  slot: this.targetSlot,
532
579
  checkpointNumber: this.checkpointNumber,
533
- pipelining: this.epochCache.isProposerPipeliningEnabled(),
534
580
  proposer: this.proposer?.toString(),
535
581
  attestorAddress: this.attestorAddress.toString(),
536
582
  publisherAddress: this.publisher.getSenderAddress().toString(),
@@ -547,10 +593,9 @@ export class CheckpointProposalJob implements Traceable {
547
593
  // pending/archive/fee-header to "as if the proposed parent had landed", so both the
548
594
  // mana-min-fee simulation (in the globals builder) and the pre-broadcast
549
595
  // validateBlockHeader see the chain tip the eventual L1 send will see.
550
- const isPipelining = this.epochCache.isProposerPipeliningEnabled();
551
596
  this.checkpointSimulationOverridesPlan = await buildCheckpointSimulationOverridesPlan({
552
597
  checkpointNumber: this.checkpointNumber,
553
- proposedCheckpointData: isPipelining ? this.proposedCheckpointData : undefined,
598
+ proposedCheckpointData: this.proposedCheckpointData,
554
599
  invalidateToPendingCheckpointNumber: this.invalidateCheckpoint?.forcePendingCheckpointNumber,
555
600
  checkpointedCheckpointNumber: this.checkpointedCheckpointNumber,
556
601
  rollup: this.publisher.rollupContract,
@@ -579,7 +624,7 @@ export class CheckpointProposalJob implements Traceable {
579
624
  epoch: this.targetEpoch,
580
625
  checkpointNumber: this.checkpointNumber,
581
626
  l1Constants: this.epochCache.getL1Constants(),
582
- pipeliningEnabled: this.epochCache.isProposerPipeliningEnabled(),
627
+ pipeliningEnabled: true,
583
628
  proposedCheckpointData: this.proposedCheckpointData,
584
629
  log: this.log,
585
630
  });
@@ -679,7 +724,7 @@ export class CheckpointProposalJob implements Traceable {
679
724
 
680
725
  // Assemble and broadcast the checkpoint proposal, including the last block that was not
681
726
  // broadcasted yet, and wait to collect the committee attestations.
682
- this.setStateFn(SequencerState.ASSEMBLING_CHECKPOINT, this.targetSlot);
727
+ this.setState(SequencerState.ASSEMBLING_CHECKPOINT);
683
728
  const checkpoint = await checkpointBuilder.completeCheckpoint();
684
729
 
685
730
  // Final validation: per-block limits are only checked if the operator set them explicitly.
@@ -700,7 +745,7 @@ export class CheckpointProposalJob implements Traceable {
700
745
  reason: 'invalid_checkpoint',
701
746
  checkpoint: checkpoint.header.toInspect(),
702
747
  });
703
- this.log.error(`Built an invalid checkpoint at slot ${this.slotNow} (skipping proposal)`, err, {
748
+ this.log.error(`Built an invalid checkpoint at slot ${this.targetSlot} (skipping proposal)`, err, {
704
749
  slot: this.targetSlot,
705
750
  checkpointNumber: this.checkpointNumber,
706
751
  blocksBuilt: blocksInCheckpoint.length,
@@ -719,7 +764,7 @@ export class CheckpointProposalJob implements Traceable {
719
764
  );
720
765
  this.logCheckpointEvent('built', `Checkpoint built for slot ${this.targetSlot}`, {
721
766
  slot: this.targetSlot,
722
- buildSlot: this.slotNow,
767
+ buildSlot: this.getBuildSlot(),
723
768
  checkpointNumber: this.checkpointNumber,
724
769
  proposer: this.proposer?.toString(),
725
770
  attestorAddress: this.attestorAddress.toString(),
@@ -843,14 +888,14 @@ export class CheckpointProposalJob implements Traceable {
843
888
  break;
844
889
  }
845
890
 
846
- const secondsIntoSlot = this.getSecondsIntoSlot();
847
- const timingInfo = this.timetable.canStartNextBlock(secondsIntoSlot);
891
+ const nowSeconds = this.dateProvider.now() / 1000;
892
+ const timingInfo = this.timetable.selectNextSubslot(this.targetSlot, nowSeconds);
848
893
 
849
894
  if (!timingInfo.canStart) {
850
895
  this.log.debug(`Not enough time left in slot to start another block`, {
851
896
  slot: this.targetSlot,
852
897
  blocksBuilt,
853
- secondsIntoSlot,
898
+ nowSeconds,
854
899
  });
855
900
  break;
856
901
  }
@@ -860,10 +905,7 @@ export class CheckpointProposalJob implements Traceable {
860
905
  blockTimestamp: timestamp,
861
906
  // Create an empty block if we haven't already and this is the last one
862
907
  forceCreate: timingInfo.isLastBlock && blocksBuilt === 0 && this.config.buildCheckpointIfEmpty,
863
- // Build deadline is only set if we are enforcing the timetable
864
- buildDeadline: timingInfo.deadline
865
- ? new Date((this.getSlotStartBuildTimestamp() + timingInfo.deadline) * 1000)
866
- : undefined,
908
+ buildDeadline: new Date(timingInfo.deadline * 1000),
867
909
  blockNumber,
868
910
  indexWithinCheckpoint,
869
911
  txHashesAlreadyIncluded,
@@ -871,8 +913,8 @@ export class CheckpointProposalJob implements Traceable {
871
913
 
872
914
  // If we failed to build the block due to insufficient txs, we try again if there is still time left in the slot
873
915
  if ('failure' in buildResult) {
874
- // If this was the last subslot, or we're running with a single block per slot, we're done
875
- if (timingInfo.isLastBlock || timingInfo.deadline === undefined) {
916
+ // If this was the last subslot, we're done.
917
+ if (timingInfo.isLastBlock) {
876
918
  break;
877
919
  }
878
920
  // Otherwise, if there is still time for more blocks, we wait until the next subslot and try again
@@ -968,14 +1010,17 @@ export class CheckpointProposalJob implements Traceable {
968
1010
  );
969
1011
  }
970
1012
 
971
- /** Sleeps until it is time to produce the next block in the slot */
1013
+ /**
1014
+ * Sleeps until it is time to produce the next block in the slot.
1015
+ * @param nextSubslotStart - Absolute wall-clock timestamp in seconds of the previous sub-slot deadline.
1016
+ */
972
1017
  @trackSpan('CheckpointProposalJob.waitUntilNextSubslot')
973
- private async waitUntilNextSubslot(nextSubslotStart: number) {
974
- this.setStateFn(SequencerState.WAITING_UNTIL_NEXT_BLOCK, this.targetSlot);
975
- this.log.verbose(`Waiting until time for the next block at ${nextSubslotStart}s into slot`, {
1018
+ protected async waitUntilNextSubslot(nextSubslotStart: number) {
1019
+ this.setState(SequencerState.WAITING_UNTIL_NEXT_BLOCK);
1020
+ this.log.verbose(`Waiting until time for the next block at ${nextSubslotStart}s`, {
976
1021
  slot: this.targetSlot,
977
1022
  });
978
- await this.waitUntilTimeInSlot(nextSubslotStart);
1023
+ await this.awaitInterruptibleSleep(Math.max(0, nextSubslotStart * 1000 - this.dateProvider.now()));
979
1024
  }
980
1025
 
981
1026
  /** Builds a single block. Called from the main block building loop. */
@@ -1003,7 +1048,7 @@ export class CheckpointProposalJob implements Traceable {
1003
1048
 
1004
1049
  try {
1005
1050
  // Wait until we have enough txs to build the block
1006
- const { availableTxs, canStartBuilding, minTxs } = await this.waitForMinTxs(opts);
1051
+ const { canStartBuilding, minTxs } = await this.waitForMinTxs(opts);
1007
1052
  if (!canStartBuilding) {
1008
1053
  this.logCheckpointEvent('block-build-failed', `Block build failed for slot ${this.targetSlot}`, {
1009
1054
  reason: 'insufficient_txs',
@@ -1011,38 +1056,39 @@ export class CheckpointProposalJob implements Traceable {
1011
1056
  slot: this.targetSlot,
1012
1057
  checkpointNumber: this.checkpointNumber,
1013
1058
  indexWithinCheckpoint,
1014
- availableTxs,
1015
1059
  minTxs,
1016
1060
  });
1017
- this.log.warn(
1018
- `Not enough txs to build block ${blockNumber} at index ${indexWithinCheckpoint} in slot ${this.targetSlot} (got ${availableTxs} txs but needs ${minTxs})`,
1061
+ this.log.verbose(
1062
+ `Not enough age-eligible txs to build block ${blockNumber} at index ${indexWithinCheckpoint} in slot ${this.targetSlot} (needs ${minTxs} eligible)`,
1019
1063
  {
1020
1064
  reason: 'insufficient_txs',
1021
1065
  blockNumber,
1022
1066
  slot: this.targetSlot,
1023
1067
  checkpointNumber: this.checkpointNumber,
1024
1068
  indexWithinCheckpoint,
1025
- availableTxs,
1026
1069
  minTxs,
1027
1070
  },
1028
1071
  );
1029
- this.eventEmitter.emit('block-tx-count-check-failed', { minTxs, availableTxs, slot: this.targetSlot });
1072
+ this.eventEmitter.emit('block-tx-count-check-failed', { minTxs, slot: this.targetSlot });
1030
1073
  this.metrics.recordBlockProposalFailed('insufficient_txs');
1031
1074
  return { failure: 'insufficient-txs' };
1032
1075
  }
1033
1076
 
1034
1077
  // Create iterator to pending txs. We filter out txs already included in previous blocks in the checkpoint
1035
1078
  // just in case p2p failed to sync the provisional block and didn't get to remove those txs from the mempool yet.
1079
+ // Block building only executes txs, so we skip loading their proofs unless these same tx objects get attached
1080
+ // to the broadcasted proposals via publishTxsWithProposals.
1036
1081
  const pendingTxs = filter(
1037
- this.p2pClient.iterateEligiblePendingTxs(),
1082
+ this.p2pClient.iterateEligiblePendingTxs({ includeProof: !!this.config.publishTxsWithProposals }),
1038
1083
  tx => !txHashesAlreadyIncluded.has(tx.txHash.toString()),
1039
1084
  );
1040
1085
 
1041
- this.log.debug(
1042
- `Building block ${blockNumber} at index ${indexWithinCheckpoint} for slot ${this.targetSlot} with ${availableTxs} available txs`,
1043
- { slot: this.targetSlot, blockNumber, indexWithinCheckpoint },
1044
- );
1045
- this.setStateFn(SequencerState.CREATING_BLOCK, this.targetSlot);
1086
+ this.log.debug(`Building block ${blockNumber} at index ${indexWithinCheckpoint} for slot ${this.targetSlot}`, {
1087
+ slot: this.targetSlot,
1088
+ blockNumber,
1089
+ indexWithinCheckpoint,
1090
+ });
1091
+ this.setState(SequencerState.CREATING_BLOCK);
1046
1092
 
1047
1093
  // Per-block limits are operator overrides (from SEQ_MAX_L2_BLOCK_GAS etc.) further capped
1048
1094
  // by remaining checkpoint-level budgets inside CheckpointBuilder before each block is built.
@@ -1057,8 +1103,9 @@ export class CheckpointProposalJob implements Traceable {
1057
1103
  deadline: buildDeadline,
1058
1104
  isBuildingProposal: true,
1059
1105
  minValidTxs,
1060
- maxBlocksPerCheckpoint: this.timetable.maxNumberOfBlocks,
1106
+ maxBlocksPerCheckpoint: this.timetable.getMaxBlocksPerCheckpoint(),
1061
1107
  perBlockAllocationMultiplier: this.config.perBlockAllocationMultiplier,
1108
+ perBlockDAAllocationMultiplier: this.config.perBlockDAAllocationMultiplier,
1062
1109
  };
1063
1110
 
1064
1111
  // Actually build the block by executing txs. The builder throws InsufficientValidTxsError
@@ -1132,7 +1179,7 @@ export class CheckpointProposalJob implements Traceable {
1132
1179
  checkpointNumber: this.checkpointNumber,
1133
1180
  indexWithinCheckpoint: block.indexWithinCheckpoint,
1134
1181
  slot: this.targetSlot,
1135
- buildSlot: this.slotNow,
1182
+ buildSlot: this.getBuildSlot(),
1136
1183
  });
1137
1184
  this.metrics.recordBuiltBlock(blockBuildDuration, block.header.totalManaUsed.toNumberUnsafe(), this.targetSlot);
1138
1185
 
@@ -1192,44 +1239,43 @@ export class CheckpointProposalJob implements Traceable {
1192
1239
  blockNumber: BlockNumber;
1193
1240
  indexWithinCheckpoint: IndexWithinCheckpoint;
1194
1241
  buildDeadline: Date | undefined;
1195
- }): Promise<{ canStartBuilding: boolean; availableTxs: number; minTxs: number }> {
1242
+ }): Promise<{ canStartBuilding: boolean; minTxs: number }> {
1196
1243
  const { indexWithinCheckpoint, blockNumber, buildDeadline, forceCreate } = opts;
1197
1244
 
1198
1245
  // We only allow a block with 0 txs in the first block of the checkpoint
1199
1246
  const minTxs = indexWithinCheckpoint > 0 && this.config.minTxsPerBlock === 0 ? 1 : this.config.minTxsPerBlock;
1200
1247
 
1201
- // Deadline is undefined if we are not enforcing the timetable, meaning we'll exit immediately when out of time
1248
+ // Latest time to keep waiting for txs: wait_for_txs_deadline = block_build_deadline(k) - min_block_duration.
1202
1249
  const startBuildingDeadline = buildDeadline
1203
- ? new Date(buildDeadline.getTime() - this.timetable.minExecutionTime * 1000)
1250
+ ? new Date(buildDeadline.getTime() - this.timetable.minBlockDuration * 1000)
1204
1251
  : undefined;
1205
1252
 
1206
- let availableTxs = await this.p2pClient.getPendingTxCount();
1207
-
1208
- while (!forceCreate && availableTxs < minTxs) {
1253
+ // Gate on age-eligible txs so we don't start building on txs the builder would then filter out for being
1254
+ // too fresh. hasEligiblePendingTxs early-exits once minTxs are eligible instead of counting the whole pool.
1255
+ while (!forceCreate && !(await this.p2pClient.hasEligiblePendingTxs(minTxs))) {
1209
1256
  // If we're past deadline, or we have no deadline, give up
1210
1257
  const now = this.dateProvider.nowAsDate();
1211
1258
  if (startBuildingDeadline === undefined || now >= startBuildingDeadline) {
1212
- return { canStartBuilding: false, availableTxs, minTxs };
1259
+ return { canStartBuilding: false, minTxs };
1213
1260
  }
1214
1261
 
1215
1262
  // Wait a bit before checking again
1216
- this.setStateFn(SequencerState.WAITING_FOR_TXS, this.targetSlot);
1263
+ this.setState(SequencerState.WAITING_FOR_TXS);
1217
1264
  this.log.verbose(
1218
- `Waiting for enough txs to build block ${blockNumber} at index ${indexWithinCheckpoint} in slot ${this.targetSlot} (have ${availableTxs} but need ${minTxs})`,
1219
- { blockNumber, slot: this.targetSlot, indexWithinCheckpoint },
1265
+ `Waiting for ${minTxs} age-eligible txs to build block ${blockNumber} at index ${indexWithinCheckpoint} in slot ${this.targetSlot}`,
1266
+ { blockNumber, slot: this.targetSlot, indexWithinCheckpoint, minTxs },
1220
1267
  );
1221
1268
  await this.waitForTxsPollingInterval();
1222
- availableTxs = await this.p2pClient.getPendingTxCount();
1223
1269
  }
1224
1270
 
1225
- return { canStartBuilding: true, availableTxs, minTxs };
1271
+ return { canStartBuilding: true, minTxs };
1226
1272
  }
1227
1273
 
1228
1274
  private async getSignedCommitteeAttestations(
1229
1275
  broadcast: CheckpointProposalBroadcast,
1230
1276
  ): Promise<{ attestations: CommitteeAttestationsAndSigners; attestationsSignature: Signature } | undefined> {
1231
1277
  const { proposal, blockProposedAt } = broadcast;
1232
- this.setStateFn(SequencerState.COLLECTING_ATTESTATIONS, this.targetSlot);
1278
+ this.setState(SequencerState.COLLECTING_ATTESTATIONS);
1233
1279
  const attestations = await this.waitForAttestations(proposal);
1234
1280
  if (!attestations) {
1235
1281
  return undefined;
@@ -1294,12 +1340,14 @@ export class CheckpointProposalJob implements Traceable {
1294
1340
  );
1295
1341
  }
1296
1342
 
1297
- const attestationTimeAllowed = this.config.enforceTimeTable
1298
- ? this.timetable.getCheckpointAttestationDeadline()
1299
- : this.l1Constants.slotDuration;
1300
- const attestationDeadline = new Date((this.getSlotStartBuildTimestamp() + attestationTimeAllowed) * 1000);
1343
+ // Hard attestation-collection cutoff = the single consensus attestation_deadline (target_slot_start + S - 2E).
1344
+ const attestationDeadlineSeconds = this.timetable.getAttestationDeadline(this.targetSlot);
1345
+ const attestationDeadline = new Date(attestationDeadlineSeconds * 1000);
1301
1346
 
1302
- this.metrics.recordRequiredAttestations(numberOfRequiredAttestations, attestationTimeAllowed);
1347
+ this.metrics.recordRequiredAttestations(
1348
+ numberOfRequiredAttestations,
1349
+ Math.max(0, attestationDeadline.getTime() - this.dateProvider.now()),
1350
+ );
1303
1351
 
1304
1352
  const collectAttestationsTimer = new Timer();
1305
1353
  let collectedAttestationsCount: number = 0;
@@ -1336,6 +1384,7 @@ export class CheckpointProposalJob implements Traceable {
1336
1384
  this.config.injectFakeAttestation ||
1337
1385
  this.config.injectHighSValueAttestation ||
1338
1386
  this.config.injectUnrecoverableSignatureAttestation ||
1387
+ this.config.injectYParityAttestation ||
1339
1388
  this.config.shuffleAttestationOrdering
1340
1389
  ) {
1341
1390
  return this.manipulateAttestations(proposal.slotNumber, epoch, seed, committee, sorted);
@@ -1437,6 +1486,19 @@ export class CheckpointProposalJob implements Traceable {
1437
1486
  return new CommitteeAttestationsAndSigners(attestations, this.getSignatureContext());
1438
1487
  }
1439
1488
 
1489
+ if (this.config.injectYParityAttestation) {
1490
+ // Force every non-proposer signed slot's recovery byte to yParity (v ∈ {0, 1}) form in the packed L1
1491
+ // tuple, after packAttestations has canonicalized it. The proposer's own slot is left canonical so
1492
+ // propose() still passes verifyProposer. Models a malicious proposer landing a checkpoint L1 accepts
1493
+ // but that can never be proven (ECDSA.recover rejects v ∉ {27, 28}).
1494
+ this.log.warn(`Injecting yParity attestations in checkpoint for slot ${slotNumber} (proposer #${proposerIndex})`);
1495
+ return new MaliciousYParityCommitteeAttestationsAndSigners(
1496
+ attestations,
1497
+ proposerIndex,
1498
+ this.getSignatureContext(),
1499
+ );
1500
+ }
1501
+
1440
1502
  if (this.config.shuffleAttestationOrdering) {
1441
1503
  this.log.warn(`Shuffling attestation ordering in checkpoint for slot ${slotNumber} (proposer #${proposerIndex})`);
1442
1504
 
@@ -1469,7 +1531,11 @@ export class CheckpointProposalJob implements Traceable {
1469
1531
  }
1470
1532
  const failedTxData = failedTxs.map(fail => fail.tx);
1471
1533
  const failedTxHashes = failedTxData.map(tx => tx.getTxHash());
1472
- this.log.verbose(`Dropping failed txs ${failedTxHashes.join(', ')}`);
1534
+ const failures = failedTxs.map(fail => ({ txHash: fail.tx.getTxHash().toString(), reason: fail.error.message }));
1535
+ this.log.warn(
1536
+ `Dropping ${failedTxs.length} txs from mempool due to failures during block building for slot ${this.targetSlot}`,
1537
+ { slot: this.targetSlot, checkpointNumber: this.checkpointNumber, failures },
1538
+ );
1473
1539
  await this.p2pClient.handleFailedExecution(failedTxHashes);
1474
1540
  }
1475
1541
 
@@ -1502,8 +1568,7 @@ export class CheckpointProposalJob implements Traceable {
1502
1568
  * tip advances locally. Gossip doesn't echo our own messages back, so without this the proposer
1503
1569
  * would never see its own proposed checkpoint and couldn't pipeline the next slot.
1504
1570
  *
1505
- * Only runs under proposer pipelining (the proposed tip is a pipelining-only concept) and is
1506
- * skipped whenever proposed blocks aren't pushed (`skipPushProposedBlocksToArchiver`, fisherman
1571
+ * Skipped whenever proposed blocks aren't pushed (`skipPushProposedBlocksToArchiver`, fisherman
1507
1572
  * mode): the archiver derives the checkpoint archive from its stored blocks, so without them the
1508
1573
  * push would fail. All blocks were already added (and awaited) during block building, so this
1509
1574
  * needs no retry — they are guaranteed present by the time we get here.
@@ -1516,9 +1581,6 @@ export class CheckpointProposalJob implements Traceable {
1516
1581
  if (this.config.skipPushProposedBlocksToArchiver || this.config.fishermanMode) {
1517
1582
  return;
1518
1583
  }
1519
- if (!this.epochCache.isProposerPipeliningEnabled()) {
1520
- return;
1521
- }
1522
1584
  const startBlock = BlockNumber(this.syncedToBlockNumber + 1);
1523
1585
  this.log.debug(`Syncing proposed checkpoint ${this.checkpointNumber} to archiver`, {
1524
1586
  checkpointNumber: this.checkpointNumber,
@@ -1583,26 +1645,9 @@ export class CheckpointProposalJob implements Traceable {
1583
1645
  return false;
1584
1646
  }
1585
1647
 
1586
- /** Waits until a specific time within the current slot */
1587
- @trackSpan('CheckpointProposalJob.waitUntilTimeInSlot')
1588
- protected async waitUntilTimeInSlot(targetSecondsIntoSlot: number): Promise<void> {
1589
- const slotStartTimestamp = this.getSlotStartBuildTimestamp();
1590
- const targetTimestamp = slotStartTimestamp + targetSecondsIntoSlot;
1591
- await sleepUntil(new Date(targetTimestamp * 1000), this.dateProvider.nowAsDate());
1592
- }
1593
-
1594
1648
  /** Waits the polling interval for transactions. Extracted for test overriding. */
1595
1649
  protected async waitForTxsPollingInterval(): Promise<void> {
1596
- await sleep(TXS_POLLING_MS);
1597
- }
1598
-
1599
- private getSlotStartBuildTimestamp(): number {
1600
- return getSlotStartBuildTimestamp(this.slotNow, this.l1Constants);
1601
- }
1602
-
1603
- private getSecondsIntoSlot(): number {
1604
- const slotStartTimestamp = this.getSlotStartBuildTimestamp();
1605
- return Number((this.dateProvider.now() / 1000 - slotStartTimestamp).toFixed(3));
1650
+ await this.awaitInterruptibleSleep(TXS_POLLING_MS);
1606
1651
  }
1607
1652
 
1608
1653
  public getPublisher() {