@aztec/sequencer-client 0.0.1-commit.1142ef1 → 0.0.1-commit.125b3452

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 (94) hide show
  1. package/dest/client/sequencer-client.d.ts +24 -7
  2. package/dest/client/sequencer-client.d.ts.map +1 -1
  3. package/dest/client/sequencer-client.js +101 -16
  4. package/dest/config.d.ts +25 -6
  5. package/dest/config.d.ts.map +1 -1
  6. package/dest/config.js +49 -30
  7. package/dest/global_variable_builder/global_builder.d.ts +2 -4
  8. package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
  9. package/dest/global_variable_builder/global_builder.js +7 -6
  10. package/dest/index.d.ts +2 -2
  11. package/dest/index.d.ts.map +1 -1
  12. package/dest/index.js +1 -1
  13. package/dest/publisher/config.d.ts +35 -17
  14. package/dest/publisher/config.d.ts.map +1 -1
  15. package/dest/publisher/config.js +106 -42
  16. package/dest/publisher/index.d.ts +2 -1
  17. package/dest/publisher/index.d.ts.map +1 -1
  18. package/dest/publisher/l1_tx_failed_store/factory.d.ts +11 -0
  19. package/dest/publisher/l1_tx_failed_store/factory.d.ts.map +1 -0
  20. package/dest/publisher/l1_tx_failed_store/factory.js +22 -0
  21. package/dest/publisher/l1_tx_failed_store/failed_tx_store.d.ts +59 -0
  22. package/dest/publisher/l1_tx_failed_store/failed_tx_store.d.ts.map +1 -0
  23. package/dest/publisher/l1_tx_failed_store/failed_tx_store.js +1 -0
  24. package/dest/publisher/l1_tx_failed_store/file_store_failed_tx_store.d.ts +15 -0
  25. package/dest/publisher/l1_tx_failed_store/file_store_failed_tx_store.d.ts.map +1 -0
  26. package/dest/publisher/l1_tx_failed_store/file_store_failed_tx_store.js +34 -0
  27. package/dest/publisher/l1_tx_failed_store/index.d.ts +4 -0
  28. package/dest/publisher/l1_tx_failed_store/index.d.ts.map +1 -0
  29. package/dest/publisher/l1_tx_failed_store/index.js +2 -0
  30. package/dest/publisher/sequencer-publisher-factory.d.ts +11 -3
  31. package/dest/publisher/sequencer-publisher-factory.d.ts.map +1 -1
  32. package/dest/publisher/sequencer-publisher-factory.js +27 -2
  33. package/dest/publisher/sequencer-publisher-metrics.d.ts +1 -1
  34. package/dest/publisher/sequencer-publisher-metrics.d.ts.map +1 -1
  35. package/dest/publisher/sequencer-publisher-metrics.js +12 -4
  36. package/dest/publisher/sequencer-publisher.d.ts +26 -8
  37. package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
  38. package/dest/publisher/sequencer-publisher.js +349 -49
  39. package/dest/sequencer/checkpoint_proposal_job.d.ts +33 -10
  40. package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -1
  41. package/dest/sequencer/checkpoint_proposal_job.js +217 -66
  42. package/dest/sequencer/checkpoint_voter.d.ts +3 -2
  43. package/dest/sequencer/checkpoint_voter.d.ts.map +1 -1
  44. package/dest/sequencer/checkpoint_voter.js +34 -10
  45. package/dest/sequencer/index.d.ts +1 -2
  46. package/dest/sequencer/index.d.ts.map +1 -1
  47. package/dest/sequencer/index.js +0 -1
  48. package/dest/sequencer/metrics.d.ts +17 -5
  49. package/dest/sequencer/metrics.d.ts.map +1 -1
  50. package/dest/sequencer/metrics.js +111 -30
  51. package/dest/sequencer/sequencer.d.ts +42 -19
  52. package/dest/sequencer/sequencer.d.ts.map +1 -1
  53. package/dest/sequencer/sequencer.js +107 -50
  54. package/dest/sequencer/timetable.d.ts +4 -6
  55. package/dest/sequencer/timetable.d.ts.map +1 -1
  56. package/dest/sequencer/timetable.js +7 -11
  57. package/dest/sequencer/types.d.ts +2 -2
  58. package/dest/sequencer/types.d.ts.map +1 -1
  59. package/dest/test/index.d.ts +3 -5
  60. package/dest/test/index.d.ts.map +1 -1
  61. package/dest/test/mock_checkpoint_builder.d.ts +27 -19
  62. package/dest/test/mock_checkpoint_builder.d.ts.map +1 -1
  63. package/dest/test/mock_checkpoint_builder.js +67 -38
  64. package/dest/test/utils.d.ts +8 -8
  65. package/dest/test/utils.d.ts.map +1 -1
  66. package/dest/test/utils.js +12 -11
  67. package/package.json +29 -28
  68. package/src/client/sequencer-client.ts +135 -18
  69. package/src/config.ts +65 -41
  70. package/src/global_variable_builder/global_builder.ts +6 -5
  71. package/src/index.ts +1 -6
  72. package/src/publisher/config.ts +121 -43
  73. package/src/publisher/index.ts +3 -0
  74. package/src/publisher/l1_tx_failed_store/factory.ts +32 -0
  75. package/src/publisher/l1_tx_failed_store/failed_tx_store.ts +55 -0
  76. package/src/publisher/l1_tx_failed_store/file_store_failed_tx_store.ts +46 -0
  77. package/src/publisher/l1_tx_failed_store/index.ts +3 -0
  78. package/src/publisher/sequencer-publisher-factory.ts +38 -6
  79. package/src/publisher/sequencer-publisher-metrics.ts +7 -3
  80. package/src/publisher/sequencer-publisher.ts +344 -61
  81. package/src/sequencer/checkpoint_proposal_job.ts +306 -81
  82. package/src/sequencer/checkpoint_voter.ts +32 -7
  83. package/src/sequencer/index.ts +0 -1
  84. package/src/sequencer/metrics.ts +124 -32
  85. package/src/sequencer/sequencer.ts +131 -53
  86. package/src/sequencer/timetable.ts +13 -12
  87. package/src/sequencer/types.ts +1 -1
  88. package/src/test/index.ts +2 -4
  89. package/src/test/mock_checkpoint_builder.ts +122 -78
  90. package/src/test/utils.ts +24 -14
  91. package/dest/sequencer/block_builder.d.ts +0 -26
  92. package/dest/sequencer/block_builder.d.ts.map +0 -1
  93. package/dest/sequencer/block_builder.js +0 -129
  94. package/src/sequencer/block_builder.ts +0 -216
@@ -12,9 +12,9 @@ import type { DateProvider } from '@aztec/foundation/timer';
12
12
  import type { TypedEventEmitter } from '@aztec/foundation/types';
13
13
  import type { P2P } from '@aztec/p2p';
14
14
  import type { SlasherClientInterface } from '@aztec/slasher';
15
- import type { L2BlockNew, L2BlockSink, L2BlockSource, ValidateCheckpointResult } from '@aztec/stdlib/block';
15
+ import type { BlockData, L2BlockSink, L2BlockSource, ValidateCheckpointResult } from '@aztec/stdlib/block';
16
16
  import type { Checkpoint } from '@aztec/stdlib/checkpoint';
17
- import { getSlotAtTimestamp, getSlotStartBuildTimestamp } from '@aztec/stdlib/epoch-helpers';
17
+ import { getSlotStartBuildTimestamp } from '@aztec/stdlib/epoch-helpers';
18
18
  import {
19
19
  type ResolvedSequencerConfig,
20
20
  type SequencerConfig,
@@ -25,7 +25,7 @@ import type { L1ToL2MessageSource } from '@aztec/stdlib/messaging';
25
25
  import { pickFromSchema } from '@aztec/stdlib/schemas';
26
26
  import { MerkleTreeId } from '@aztec/stdlib/trees';
27
27
  import { Attributes, type TelemetryClient, type Tracer, getTelemetryClient, trackSpan } from '@aztec/telemetry-client';
28
- import { FullNodeCheckpointsBuilder, type ValidatorClient } from '@aztec/validator-client';
28
+ import { FullNodeCheckpointsBuilder, NodeKeystoreAdapter, type ValidatorClient } from '@aztec/validator-client';
29
29
 
30
30
  import EventEmitter from 'node:events';
31
31
 
@@ -57,8 +57,11 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
57
57
  private state = SequencerState.STOPPED;
58
58
  private metrics: SequencerMetrics;
59
59
 
60
- /** The last slot for which we attempted to vote when sync failed, to prevent duplicate attempts. */
61
- private lastSlotForVoteWhenSyncFailed: SlotNumber | undefined;
60
+ /** The last slot for which we attempted to perform our voting duties with degraded block production */
61
+ private lastSlotForFallbackVote: SlotNumber | undefined;
62
+
63
+ /** The last slot for which we logged "no committee" warning, to avoid spam */
64
+ private lastSlotForNoCommitteeWarning: SlotNumber | undefined;
62
65
 
63
66
  /** The last slot for which we triggered a checkpoint proposal job, to prevent duplicate attempts. */
64
67
  private lastSlotForCheckpointProposalJob: SlotNumber | undefined;
@@ -72,14 +75,6 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
72
75
  /** The maximum number of seconds that the sequencer can be into a slot to transition to a particular state. */
73
76
  protected timetable!: SequencerTimetable;
74
77
 
75
- // This shouldn't be here as this gets re-created each time we build/propose a block.
76
- // But we have a number of tests that abuse/rely on this class having a permanent publisher.
77
- // As long as those tests only configure a single publisher they will continue to work.
78
- // This will get re-assigned every time the sequencer goes to build a new block to a publisher that is valid
79
- // for the block proposer.
80
- // TODO(palla/mbps): Remove this field and fix tests
81
- protected publisher: SequencerPublisher | undefined;
82
-
83
78
  /** Config for the sequencer */
84
79
  protected config: ResolvedSequencerConfig = DefaultSequencerConfig;
85
80
 
@@ -115,7 +110,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
115
110
  /** Updates sequencer config by the defined values and updates the timetable */
116
111
  public updateConfig(config: Partial<SequencerConfig>) {
117
112
  const filteredConfig = pickFromSchema(config, SequencerConfigSchema);
118
- this.log.info(`Updated sequencer config`, omit(filteredConfig, 'txPublicSetupAllowList'));
113
+ this.log.info(`Updated sequencer config`, omit(filteredConfig, 'txPublicSetupAllowListExtend'));
119
114
  this.config = merge(this.config, filteredConfig);
120
115
  this.timetable = new SequencerTimetable(
121
116
  {
@@ -131,10 +126,9 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
131
126
  );
132
127
  }
133
128
 
134
- /** Initializes the sequencer (precomputes tables and creates a publisher). Takes about 3s. */
135
- public async init() {
129
+ /** Initializes the sequencer (precomputes tables). Takes about 3s. */
130
+ public init() {
136
131
  getKzg();
137
- this.publisher = (await this.publisherFactory.create(undefined)).publisher;
138
132
  }
139
133
 
140
134
  /** Starts the sequencer and moves to IDLE state. */
@@ -153,7 +147,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
153
147
  public async stop(): Promise<void> {
154
148
  this.log.info(`Stopping sequencer`);
155
149
  this.setState(SequencerState.STOPPING, undefined, { force: true });
156
- this.publisher?.interrupt();
150
+ this.publisherFactory.interruptAll();
157
151
  await this.runningPromise?.stop();
158
152
  this.setState(SequencerState.STOPPED, undefined, { force: true });
159
153
  this.log.info('Stopped sequencer');
@@ -166,7 +160,6 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
166
160
  } catch (err) {
167
161
  this.emit('checkpoint-error', { error: err as Error });
168
162
  if (err instanceof SequencerTooSlowError) {
169
- // TODO(palla/mbps): Add missing states
170
163
  // Log as warn only if we had to abort halfway through the block proposal
171
164
  const logLvl = [SequencerState.INITIALIZING_CHECKPOINT, SequencerState.PROPOSER_CHECK].includes(
172
165
  err.proposedState,
@@ -202,7 +195,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
202
195
  const { slot, ts, now, epoch } = this.epochCache.getEpochAndSlotInNextL1Slot();
203
196
 
204
197
  // Check if we are synced and it's our slot, grab a publisher, check previous block invalidation, etc
205
- const checkpointProposalJob = await this.prepareCheckpointProposal(slot, ts, now);
198
+ const checkpointProposalJob = await this.prepareCheckpointProposal(epoch, slot, ts, now);
206
199
  if (!checkpointProposalJob) {
207
200
  return;
208
201
  }
@@ -234,6 +227,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
234
227
  */
235
228
  @trackSpan('Sequencer.prepareCheckpointProposal')
236
229
  private async prepareCheckpointProposal(
230
+ epoch: EpochNumber,
237
231
  slot: SlotNumber,
238
232
  ts: bigint,
239
233
  now: bigint,
@@ -263,13 +257,31 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
263
257
  return undefined;
264
258
  }
265
259
 
260
+ // If escape hatch is open for this epoch, do not start checkpoint proposal work and do not attempt invalidations.
261
+ // Still perform governance/slashing voting (as proposer) once per slot.
262
+ const isEscapeHatchOpen = await this.epochCache.isEscapeHatchOpen(epoch);
263
+
264
+ if (isEscapeHatchOpen) {
265
+ this.setState(SequencerState.PROPOSER_CHECK, slot);
266
+ const [canPropose, proposer] = await this.checkCanPropose(slot);
267
+ if (canPropose) {
268
+ await this.tryVoteWhenEscapeHatchOpen({ slot, proposer });
269
+ } else {
270
+ this.log.trace(`Escape hatch open but we are not proposer, skipping vote-only actions`, {
271
+ slot,
272
+ epoch,
273
+ proposer,
274
+ });
275
+ }
276
+ return undefined;
277
+ }
278
+
266
279
  // Next checkpoint follows from the last synced one
267
280
  const checkpointNumber = CheckpointNumber(syncedTo.checkpointNumber + 1);
268
281
 
269
282
  const logCtx = {
270
283
  now,
271
- syncedToL1Ts: syncedTo.l1Timestamp,
272
- syncedToL2Slot: getSlotAtTimestamp(syncedTo.l1Timestamp, this.l1Constants),
284
+ syncedToL2Slot: syncedTo.syncedL2Slot,
273
285
  slot,
274
286
  slotTs: ts,
275
287
  checkpointNumber,
@@ -287,12 +299,12 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
287
299
  }
288
300
 
289
301
  // Check that the slot is not taken by a block already (should never happen, since only us can propose for this slot)
290
- if (syncedTo.block && syncedTo.block.header.getSlot() >= slot) {
302
+ if (syncedTo.blockData && syncedTo.blockData.header.getSlot() >= slot) {
291
303
  this.log.warn(
292
304
  `Cannot propose block at next L2 slot ${slot} since that slot was taken by block ${syncedTo.blockNumber}`,
293
- { ...logCtx, block: syncedTo.block.header.toInspect() },
305
+ { ...logCtx, block: syncedTo.blockData.header.toInspect() },
294
306
  );
295
- this.metrics.recordBlockProposalPrecheckFailed('slot_already_taken');
307
+ this.metrics.recordCheckpointPrecheckFailed('slot_already_taken');
296
308
  return undefined;
297
309
  }
298
310
 
@@ -303,7 +315,6 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
303
315
  const proposerForPublisher = this.config.fishermanMode ? undefined : proposer;
304
316
  const { attestorAddress, publisher } = await this.publisherFactory.create(proposerForPublisher);
305
317
  this.log.verbose(`Created publisher at address ${publisher.getSenderAddress()} for attestor ${attestorAddress}`);
306
- this.publisher = publisher;
307
318
 
308
319
  // In fisherman mode, set the actual proposer's address for simulations
309
320
  if (this.config.fishermanMode && proposer) {
@@ -328,7 +339,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
328
339
  logCtx,
329
340
  );
330
341
  this.emit('proposer-rollup-check-failed', { reason: 'Rollup contract check failed', slot });
331
- this.metrics.recordBlockProposalPrecheckFailed('rollup_contract_check_failed');
342
+ this.metrics.recordCheckpointPrecheckFailed('rollup_contract_check_failed');
332
343
  return undefined;
333
344
  }
334
345
 
@@ -338,7 +349,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
338
349
  { ...logCtx, rollup: canProposeCheck, expectedSlot: slot },
339
350
  );
340
351
  this.emit('proposer-rollup-check-failed', { reason: 'Slot mismatch', slot });
341
- this.metrics.recordBlockProposalPrecheckFailed('slot_mismatch');
352
+ this.metrics.recordCheckpointPrecheckFailed('slot_mismatch');
342
353
  return undefined;
343
354
  }
344
355
 
@@ -348,15 +359,17 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
348
359
  { ...logCtx, rollup: canProposeCheck, expectedSlot: slot },
349
360
  );
350
361
  this.emit('proposer-rollup-check-failed', { reason: 'Block mismatch', slot });
351
- this.metrics.recordBlockProposalPrecheckFailed('block_number_mismatch');
362
+ this.metrics.recordCheckpointPrecheckFailed('block_number_mismatch');
352
363
  return undefined;
353
364
  }
354
365
 
355
366
  this.lastSlotForCheckpointProposalJob = slot;
367
+ await this.p2pClient.prepareForSlot(slot);
356
368
  this.log.info(`Preparing checkpoint proposal ${checkpointNumber} at slot ${slot}`, { ...logCtx, proposer });
357
369
 
358
370
  // Create and return the checkpoint proposal job
359
371
  return this.createCheckpointProposalJob(
372
+ epoch,
360
373
  slot,
361
374
  checkpointNumber,
362
375
  syncedTo.blockNumber,
@@ -368,6 +381,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
368
381
  }
369
382
 
370
383
  protected createCheckpointProposalJob(
384
+ epoch: EpochNumber,
371
385
  slot: SlotNumber,
372
386
  checkpointNumber: CheckpointNumber,
373
387
  syncedToBlockNumber: BlockNumber,
@@ -377,6 +391,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
377
391
  invalidateCheckpoint: InvalidateCheckpointRequest | undefined,
378
392
  ): CheckpointProposalJob {
379
393
  return new CheckpointProposalJob(
394
+ epoch,
380
395
  slot,
381
396
  checkpointNumber,
382
397
  syncedToBlockNumber,
@@ -389,6 +404,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
389
404
  this.p2pClient,
390
405
  this.worldState,
391
406
  this.l1ToL2MessageSource,
407
+ this.l2BlockSource,
392
408
  this.checkpointsBuilder,
393
409
  this.l2BlockSource,
394
410
  this.l1Constants,
@@ -400,11 +416,18 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
400
416
  this.metrics,
401
417
  this,
402
418
  this.setState.bind(this),
403
- this.log,
404
419
  this.tracer,
420
+ this.log.getBindings(),
405
421
  );
406
422
  }
407
423
 
424
+ /**
425
+ * Returns the current sequencer state.
426
+ */
427
+ public getState(): SequencerState {
428
+ return this.state;
429
+ }
430
+
408
431
  /**
409
432
  * Internal helper for setting the sequencer state and checks if we have enough time left in the slot to transition to the new state.
410
433
  * @param proposedState - The new state to transition to.
@@ -451,16 +474,15 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
451
474
  * We don't check against the previous block submitted since it may have been reorg'd out.
452
475
  */
453
476
  protected async checkSync(args: { ts: bigint; slot: SlotNumber }): Promise<SequencerSyncCheckResult | undefined> {
454
- // Check that the archiver and dependencies have synced to the previous L1 slot at least
477
+ // Check that the archiver has fully synced the L2 slot before the one we want to propose in.
455
478
  // TODO(#14766): Archiver reports L1 timestamp based on L1 blocks seen, which means that a missed L1 block will
456
479
  // cause the archiver L1 timestamp to fall behind, and cause this sequencer to start processing one L1 slot later.
457
- const l1Timestamp = await this.l2BlockSource.getL1Timestamp();
458
- const { slot, ts } = args;
459
- if (l1Timestamp === undefined || l1Timestamp + BigInt(this.l1Constants.ethereumSlotDuration) < ts) {
480
+ const syncedL2Slot = await this.l2BlockSource.getSyncedL2SlotNumber();
481
+ const { slot } = args;
482
+ if (syncedL2Slot === undefined || syncedL2Slot + 1 < slot) {
460
483
  this.log.debug(`Cannot propose block at next L2 slot ${slot} due to pending sync from L1`, {
461
484
  slot,
462
- ts,
463
- l1Timestamp,
485
+ syncedL2Slot,
464
486
  });
465
487
  return undefined;
466
488
  }
@@ -500,24 +522,24 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
500
522
  checkpointNumber: CheckpointNumber.ZERO,
501
523
  blockNumber: BlockNumber.ZERO,
502
524
  archive,
503
- l1Timestamp,
525
+ syncedL2Slot,
504
526
  pendingChainValidationStatus,
505
527
  };
506
528
  }
507
529
 
508
- const block = await this.l2BlockSource.getL2BlockNew(blockNumber);
509
- if (!block) {
530
+ const blockData = await this.l2BlockSource.getBlockData(blockNumber);
531
+ if (!blockData) {
510
532
  // this shouldn't really happen because a moment ago we checked that all components were in sync
511
- this.log.error(`Failed to get L2 block ${blockNumber} from the archiver with all components in sync`);
533
+ this.log.error(`Failed to get L2 block data ${blockNumber} from the archiver with all components in sync`);
512
534
  return undefined;
513
535
  }
514
536
 
515
537
  return {
516
- block,
517
- blockNumber: block.number,
518
- checkpointNumber: block.checkpointNumber,
519
- archive: block.archive.root,
520
- l1Timestamp,
538
+ blockData,
539
+ blockNumber: blockData.header.getBlockNumber(),
540
+ checkpointNumber: blockData.checkpointNumber,
541
+ archive: blockData.archive.root,
542
+ syncedL2Slot,
521
543
  pendingChainValidationStatus,
522
544
  };
523
545
  }
@@ -533,7 +555,10 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
533
555
  proposer = await this.epochCache.getProposerAttesterAddressInSlot(slot);
534
556
  } catch (e) {
535
557
  if (e instanceof NoCommitteeError) {
536
- this.log.warn(`Cannot propose at next L2 slot ${slot} since the committee does not exist on L1`);
558
+ if (this.lastSlotForNoCommitteeWarning !== slot) {
559
+ this.lastSlotForNoCommitteeWarning = slot;
560
+ this.log.warn(`Cannot propose at next L2 slot ${slot} since the committee does not exist on L1`);
561
+ }
537
562
  return [false, undefined];
538
563
  }
539
564
  this.log.error(`Error getting proposer for slot ${slot}`, e);
@@ -569,7 +594,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
569
594
  const { slot } = args;
570
595
 
571
596
  // Prevent duplicate attempts in the same slot
572
- if (this.lastSlotForVoteWhenSyncFailed === slot) {
597
+ if (this.lastSlotForFallbackVote === slot) {
573
598
  this.log.trace(`Already attempted to vote in slot ${slot} (skipping)`);
574
599
  return;
575
600
  }
@@ -601,7 +626,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
601
626
  }
602
627
 
603
628
  // Mark this slot as attempted
604
- this.lastSlotForVoteWhenSyncFailed = slot;
629
+ this.lastSlotForFallbackVote = slot;
605
630
 
606
631
  // Get a publisher for voting
607
632
  const { attestorAddress, publisher } = await this.publisherFactory.create(proposer);
@@ -636,7 +661,55 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
636
661
  }
637
662
 
638
663
  /**
639
- * Considers invalidating a checkpoint if the pending chain is invalid. Depends on how long the invalid checkpoint
664
+ * Tries to vote on slashing actions and governance proposals when escape hatch is open.
665
+ * This allows the sequencer to participate in voting without performing checkpoint proposal work.
666
+ */
667
+ @trackSpan('Sequencer.tryVoteWhenEscapeHatchOpen', ({ slot }) => ({ [Attributes.SLOT_NUMBER]: slot }))
668
+ protected async tryVoteWhenEscapeHatchOpen(args: {
669
+ slot: SlotNumber;
670
+ proposer: EthAddress | undefined;
671
+ }): Promise<void> {
672
+ const { slot, proposer } = args;
673
+
674
+ // Prevent duplicate attempts in the same slot
675
+ if (this.lastSlotForFallbackVote === slot) {
676
+ this.log.trace(`Already attempted to vote in slot ${slot} (escape hatch open, skipping)`);
677
+ return;
678
+ }
679
+
680
+ // Mark this slot as attempted
681
+ this.lastSlotForFallbackVote = slot;
682
+
683
+ const { attestorAddress, publisher } = await this.publisherFactory.create(proposer);
684
+
685
+ this.log.debug(`Escape hatch open for slot ${slot}, attempting vote-only actions`, { slot, attestorAddress });
686
+
687
+ const voter = new CheckpointVoter(
688
+ slot,
689
+ publisher,
690
+ attestorAddress,
691
+ this.validatorClient,
692
+ this.slasherClient,
693
+ this.l1Constants,
694
+ this.config,
695
+ this.metrics,
696
+ this.log,
697
+ );
698
+
699
+ const votesPromises = voter.enqueueVotes();
700
+ const votes = await Promise.all(votesPromises);
701
+
702
+ if (votes.every(p => !p)) {
703
+ this.log.debug(`No votes to enqueue for slot ${slot} (escape hatch open)`);
704
+ return;
705
+ }
706
+
707
+ this.log.info(`Voting in slot ${slot} (escape hatch open)`, { slot });
708
+ await publisher.sendRequests();
709
+ }
710
+
711
+ /**
712
+ * Considers invalidating a block if the pending chain is invalid. Depends on how long the invalid block
640
713
  * has been there without being invalidated and whether the sequencer is in the committee or not. We always
641
714
  * have the proposer try to invalidate, but if they fail, the sequencers in the committee are expected to try,
642
715
  * and if they fail, any sequencer will try as well.
@@ -645,7 +718,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
645
718
  syncedTo: SequencerSyncCheckResult,
646
719
  currentSlot: SlotNumber,
647
720
  ): Promise<void> {
648
- const { pendingChainValidationStatus, l1Timestamp } = syncedTo;
721
+ const { pendingChainValidationStatus, syncedL2Slot } = syncedTo;
649
722
  if (pendingChainValidationStatus.valid) {
650
723
  return;
651
724
  }
@@ -660,7 +733,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
660
733
 
661
734
  const logData = {
662
735
  invalidL1Timestamp: invalidCheckpointTimestamp,
663
- l1Timestamp,
736
+ syncedL2Slot,
664
737
  invalidCheckpoint: pendingChainValidationStatus.checkpoint,
665
738
  secondsBeforeInvalidatingBlockAsCommitteeMember,
666
739
  secondsBeforeInvalidatingBlockAsNonCommitteeMember,
@@ -788,6 +861,11 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
788
861
  return this.validatorClient?.getValidatorAddresses();
789
862
  }
790
863
 
864
+ /** Updates the publisher factory's node keystore adapter after a keystore reload. */
865
+ public updatePublisherNodeKeyStore(adapter: NodeKeystoreAdapter): void {
866
+ this.publisherFactory.updateNodeKeyStore(adapter);
867
+ }
868
+
791
869
  public getConfig() {
792
870
  return this.config;
793
871
  }
@@ -798,10 +876,10 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
798
876
  }
799
877
 
800
878
  type SequencerSyncCheckResult = {
801
- block?: L2BlockNew;
879
+ blockData?: BlockData;
802
880
  checkpointNumber: CheckpointNumber;
803
881
  blockNumber: BlockNumber;
804
882
  archive: Fr;
805
- l1Timestamp: bigint;
883
+ syncedL2Slot: SlotNumber;
806
884
  pendingChainValidationStatus: ValidateCheckpointResult;
807
885
  };
@@ -1,14 +1,15 @@
1
- import { createLogger } from '@aztec/aztec.js/log';
1
+ import type { Logger } from '@aztec/foundation/log';
2
+ import {
3
+ CHECKPOINT_ASSEMBLE_TIME,
4
+ CHECKPOINT_INITIALIZATION_TIME,
5
+ DEFAULT_P2P_PROPAGATION_TIME,
6
+ MIN_EXECUTION_TIME,
7
+ } from '@aztec/stdlib/timetable';
2
8
 
3
- import { DEFAULT_ATTESTATION_PROPAGATION_TIME as DEFAULT_P2P_PROPAGATION_TIME } from '../config.js';
4
9
  import { SequencerTooSlowError } from './errors.js';
5
10
  import type { SequencerMetrics } from './metrics.js';
6
11
  import { SequencerState } from './utils.js';
7
12
 
8
- export const MIN_EXECUTION_TIME = 2;
9
- export const CHECKPOINT_INITIALIZATION_TIME = 1;
10
- export const CHECKPOINT_ASSEMBLE_TIME = 1;
11
-
12
13
  export class SequencerTimetable {
13
14
  /**
14
15
  * How late into the slot can we be to start working. Computed as the total time needed for assembling and publishing a block,
@@ -79,7 +80,7 @@ export class SequencerTimetable {
79
80
  enforce: boolean;
80
81
  },
81
82
  private readonly metrics?: SequencerMetrics,
82
- private readonly log = createLogger('sequencer:timetable'),
83
+ private readonly log?: Logger,
83
84
  ) {
84
85
  this.ethereumSlotDuration = opts.ethereumSlotDuration;
85
86
  this.aztecSlotDuration = opts.aztecSlotDuration;
@@ -131,7 +132,7 @@ export class SequencerTimetable {
131
132
  const initializeDeadline = this.aztecSlotDuration - minWorkToDo;
132
133
  this.initializeDeadline = initializeDeadline;
133
134
 
134
- this.log.verbose(
135
+ this.log?.info(
135
136
  `Sequencer timetable initialized with ${this.maxNumberOfBlocks} blocks per slot (${this.enforce ? 'enforced' : 'not enforced'})`,
136
137
  {
137
138
  ethereumSlotDuration: this.ethereumSlotDuration,
@@ -205,7 +206,7 @@ export class SequencerTimetable {
205
206
  }
206
207
 
207
208
  this.metrics?.recordStateTransitionBufferMs(Math.floor(bufferSeconds * 1000), newState);
208
- this.log.trace(`Enough time to transition to ${newState}`, { maxAllowedTime, secondsIntoSlot });
209
+ this.log?.trace(`Enough time to transition to ${newState}`, { maxAllowedTime, secondsIntoSlot });
209
210
  }
210
211
 
211
212
  /**
@@ -241,7 +242,7 @@ export class SequencerTimetable {
241
242
  const canStart = available >= this.minExecutionTime;
242
243
  const deadline = secondsIntoSlot + available;
243
244
 
244
- this.log.verbose(
245
+ this.log?.verbose(
245
246
  `${canStart ? 'Can' : 'Cannot'} start single-block checkpoint at ${secondsIntoSlot}s into slot`,
246
247
  { secondsIntoSlot, maxAllowed, available, deadline },
247
248
  );
@@ -261,7 +262,7 @@ export class SequencerTimetable {
261
262
  // Found an available sub-slot! Is this the last one?
262
263
  const isLastBlock = subSlot === this.maxNumberOfBlocks;
263
264
 
264
- this.log.verbose(
265
+ this.log?.verbose(
265
266
  `Can start ${isLastBlock ? 'last block' : 'block'} in sub-slot ${subSlot} with deadline ${deadline}s`,
266
267
  { secondsIntoSlot, deadline, timeUntilDeadline, subSlot, maxBlocks: this.maxNumberOfBlocks },
267
268
  );
@@ -271,7 +272,7 @@ export class SequencerTimetable {
271
272
  }
272
273
 
273
274
  // No sub-slots available with enough time
274
- this.log.verbose(`No time left to start any more blocks`, {
275
+ this.log?.verbose(`No time left to start any more blocks`, {
275
276
  secondsIntoSlot,
276
277
  maxBlocks: this.maxNumberOfBlocks,
277
278
  initializationOffset: this.initializationOffset,
@@ -2,5 +2,5 @@ import type { L1RollupConstants } from '@aztec/stdlib/epoch-helpers';
2
2
 
3
3
  export type SequencerRollupConstants = Pick<
4
4
  L1RollupConstants,
5
- 'ethereumSlotDuration' | 'l1GenesisTime' | 'slotDuration'
5
+ 'ethereumSlotDuration' | 'l1GenesisTime' | 'slotDuration' | 'rollupManaLimit'
6
6
  >;
package/src/test/index.ts CHANGED
@@ -1,18 +1,16 @@
1
- import type { L1TxUtilsWithBlobs } from '@aztec/ethereum/l1-tx-utils-with-blobs';
1
+ import type { L1TxUtils } from '@aztec/ethereum/l1-tx-utils';
2
2
  import type { PublisherManager } from '@aztec/ethereum/publisher-manager';
3
3
  import type { PublicProcessorFactory } from '@aztec/simulator/server';
4
4
  import type { FullNodeCheckpointsBuilder, ValidatorClient } from '@aztec/validator-client';
5
5
 
6
6
  import { SequencerClient } from '../client/sequencer-client.js';
7
7
  import type { SequencerPublisherFactory } from '../publisher/sequencer-publisher-factory.js';
8
- import type { SequencerPublisher } from '../publisher/sequencer-publisher.js';
9
8
  import { Sequencer } from '../sequencer/sequencer.js';
10
9
  import type { SequencerTimetable } from '../sequencer/timetable.js';
11
10
 
12
11
  class TestSequencer_ extends Sequencer {
13
12
  declare public publicProcessorFactory: PublicProcessorFactory;
14
13
  declare public timetable: SequencerTimetable;
15
- declare public publisher: SequencerPublisher;
16
14
  declare public publisherFactory: SequencerPublisherFactory;
17
15
  declare public validatorClient: ValidatorClient;
18
16
  declare public checkpointsBuilder: FullNodeCheckpointsBuilder;
@@ -22,7 +20,7 @@ export type TestSequencer = TestSequencer_;
22
20
 
23
21
  class TestSequencerClient_ extends SequencerClient {
24
22
  declare public sequencer: TestSequencer;
25
- declare public publisherManager: PublisherManager<L1TxUtilsWithBlobs>;
23
+ declare public publisherManager: PublisherManager<L1TxUtils>;
26
24
  }
27
25
 
28
26
  export type TestSequencerClient = TestSequencerClient_;