@aztec/sequencer-client 0.0.1-commit.fcb71a6 → 0.0.1-commit.ff7989d6c

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 (84) hide show
  1. package/dest/client/sequencer-client.d.ts +14 -10
  2. package/dest/client/sequencer-client.d.ts.map +1 -1
  3. package/dest/client/sequencer-client.js +15 -4
  4. package/dest/config.d.ts +3 -4
  5. package/dest/config.d.ts.map +1 -1
  6. package/dest/config.js +22 -12
  7. package/dest/global_variable_builder/global_builder.d.ts +5 -7
  8. package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
  9. package/dest/global_variable_builder/global_builder.js +13 -13
  10. package/dest/index.d.ts +2 -3
  11. package/dest/index.d.ts.map +1 -1
  12. package/dest/index.js +1 -2
  13. package/dest/publisher/config.d.ts +31 -17
  14. package/dest/publisher/config.d.ts.map +1 -1
  15. package/dest/publisher/config.js +101 -42
  16. package/dest/publisher/sequencer-publisher-factory.d.ts +11 -3
  17. package/dest/publisher/sequencer-publisher-factory.d.ts.map +1 -1
  18. package/dest/publisher/sequencer-publisher-factory.js +13 -2
  19. package/dest/publisher/sequencer-publisher-metrics.d.ts +1 -1
  20. package/dest/publisher/sequencer-publisher-metrics.d.ts.map +1 -1
  21. package/dest/publisher/sequencer-publisher-metrics.js +23 -86
  22. package/dest/publisher/sequencer-publisher.d.ts +32 -23
  23. package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
  24. package/dest/publisher/sequencer-publisher.js +522 -88
  25. package/dest/sequencer/checkpoint_proposal_job.d.ts +40 -12
  26. package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -1
  27. package/dest/sequencer/checkpoint_proposal_job.js +633 -62
  28. package/dest/sequencer/checkpoint_voter.d.ts +3 -2
  29. package/dest/sequencer/checkpoint_voter.d.ts.map +1 -1
  30. package/dest/sequencer/checkpoint_voter.js +34 -10
  31. package/dest/sequencer/index.d.ts +1 -3
  32. package/dest/sequencer/index.d.ts.map +1 -1
  33. package/dest/sequencer/index.js +0 -2
  34. package/dest/sequencer/metrics.d.ts +19 -7
  35. package/dest/sequencer/metrics.d.ts.map +1 -1
  36. package/dest/sequencer/metrics.js +131 -141
  37. package/dest/sequencer/sequencer.d.ts +38 -18
  38. package/dest/sequencer/sequencer.d.ts.map +1 -1
  39. package/dest/sequencer/sequencer.js +513 -66
  40. package/dest/sequencer/timetable.d.ts +1 -4
  41. package/dest/sequencer/timetable.d.ts.map +1 -1
  42. package/dest/sequencer/timetable.js +1 -4
  43. package/dest/test/index.d.ts +4 -7
  44. package/dest/test/index.d.ts.map +1 -1
  45. package/dest/test/mock_checkpoint_builder.d.ts +25 -11
  46. package/dest/test/mock_checkpoint_builder.d.ts.map +1 -1
  47. package/dest/test/mock_checkpoint_builder.js +52 -9
  48. package/dest/test/utils.d.ts +13 -9
  49. package/dest/test/utils.d.ts.map +1 -1
  50. package/dest/test/utils.js +27 -17
  51. package/package.json +30 -28
  52. package/src/client/sequencer-client.ts +28 -11
  53. package/src/config.ts +31 -19
  54. package/src/global_variable_builder/global_builder.ts +14 -14
  55. package/src/index.ts +1 -9
  56. package/src/publisher/config.ts +112 -43
  57. package/src/publisher/sequencer-publisher-factory.ts +23 -6
  58. package/src/publisher/sequencer-publisher-metrics.ts +17 -69
  59. package/src/publisher/sequencer-publisher.ts +180 -118
  60. package/src/sequencer/checkpoint_proposal_job.ts +299 -91
  61. package/src/sequencer/checkpoint_voter.ts +32 -7
  62. package/src/sequencer/index.ts +0 -2
  63. package/src/sequencer/metrics.ts +132 -148
  64. package/src/sequencer/sequencer.ts +159 -68
  65. package/src/sequencer/timetable.ts +6 -5
  66. package/src/test/index.ts +3 -6
  67. package/src/test/mock_checkpoint_builder.ts +102 -29
  68. package/src/test/utils.ts +58 -28
  69. package/dest/sequencer/block_builder.d.ts +0 -26
  70. package/dest/sequencer/block_builder.d.ts.map +0 -1
  71. package/dest/sequencer/block_builder.js +0 -129
  72. package/dest/sequencer/checkpoint_builder.d.ts +0 -63
  73. package/dest/sequencer/checkpoint_builder.d.ts.map +0 -1
  74. package/dest/sequencer/checkpoint_builder.js +0 -131
  75. package/dest/tx_validator/nullifier_cache.d.ts +0 -14
  76. package/dest/tx_validator/nullifier_cache.d.ts.map +0 -1
  77. package/dest/tx_validator/nullifier_cache.js +0 -24
  78. package/dest/tx_validator/tx_validator_factory.d.ts +0 -18
  79. package/dest/tx_validator/tx_validator_factory.d.ts.map +0 -1
  80. package/dest/tx_validator/tx_validator_factory.js +0 -53
  81. package/src/sequencer/block_builder.ts +0 -217
  82. package/src/sequencer/checkpoint_builder.ts +0 -217
  83. package/src/tx_validator/nullifier_cache.ts +0 -30
  84. package/src/tx_validator/tx_validator_factory.ts +0 -133
@@ -12,7 +12,7 @@ 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, L2BlockSource, ValidateBlockResult } 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
17
  import { getSlotAtTimestamp, getSlotStartBuildTimestamp } from '@aztec/stdlib/epoch-helpers';
18
18
  import {
@@ -24,16 +24,15 @@ import {
24
24
  import type { L1ToL2MessageSource } from '@aztec/stdlib/messaging';
25
25
  import { pickFromSchema } from '@aztec/stdlib/schemas';
26
26
  import { MerkleTreeId } from '@aztec/stdlib/trees';
27
- import { type TelemetryClient, type Tracer, getTelemetryClient, trackSpan } from '@aztec/telemetry-client';
28
- import type { ValidatorClient } from '@aztec/validator-client';
27
+ import { Attributes, type TelemetryClient, type Tracer, getTelemetryClient, trackSpan } from '@aztec/telemetry-client';
28
+ import { FullNodeCheckpointsBuilder, NodeKeystoreAdapter, type ValidatorClient } from '@aztec/validator-client';
29
29
 
30
30
  import EventEmitter from 'node:events';
31
31
 
32
32
  import { DefaultSequencerConfig } from '../config.js';
33
33
  import type { GlobalVariableBuilder } from '../global_variable_builder/global_builder.js';
34
34
  import type { SequencerPublisherFactory } from '../publisher/sequencer-publisher-factory.js';
35
- import type { InvalidateBlockRequest, SequencerPublisher } from '../publisher/sequencer-publisher.js';
36
- import { FullNodeCheckpointsBuilder } from './checkpoint_builder.js';
35
+ import type { InvalidateCheckpointRequest, SequencerPublisher } from '../publisher/sequencer-publisher.js';
37
36
  import { CheckpointProposalJob } from './checkpoint_proposal_job.js';
38
37
  import { CheckpointVoter } from './checkpoint_voter.js';
39
38
  import { SequencerInterruptedError, SequencerTooSlowError } from './errors.js';
@@ -58,8 +57,11 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
58
57
  private state = SequencerState.STOPPED;
59
58
  private metrics: SequencerMetrics;
60
59
 
61
- /** The last slot for which we attempted to vote when sync failed, to prevent duplicate attempts. */
62
- 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;
63
65
 
64
66
  /** The last slot for which we triggered a checkpoint proposal job, to prevent duplicate attempts. */
65
67
  private lastSlotForCheckpointProposalJob: SlotNumber | undefined;
@@ -73,14 +75,6 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
73
75
  /** The maximum number of seconds that the sequencer can be into a slot to transition to a particular state. */
74
76
  protected timetable!: SequencerTimetable;
75
77
 
76
- // This shouldn't be here as this gets re-created each time we build/propose a block.
77
- // But we have a number of tests that abuse/rely on this class having a permanent publisher.
78
- // As long as those tests only configure a single publisher they will continue to work.
79
- // This will get re-assigned every time the sequencer goes to build a new block to a publisher that is valid
80
- // for the block proposer.
81
- // TODO(palla/mbps): Remove this field and fix tests
82
- protected publisher: SequencerPublisher | undefined;
83
-
84
78
  /** Config for the sequencer */
85
79
  protected config: ResolvedSequencerConfig = DefaultSequencerConfig;
86
80
 
@@ -91,7 +85,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
91
85
  protected p2pClient: P2P,
92
86
  protected worldState: WorldStateSynchronizer,
93
87
  protected slasherClient: SlasherClientInterface | undefined,
94
- protected l2BlockSource: L2BlockSource,
88
+ protected l2BlockSource: L2BlockSource & L2BlockSink,
95
89
  protected l1ToL2MessageSource: L1ToL2MessageSource,
96
90
  protected checkpointsBuilder: FullNodeCheckpointsBuilder,
97
91
  protected l1Constants: SequencerRollupConstants,
@@ -132,10 +126,9 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
132
126
  );
133
127
  }
134
128
 
135
- /** Initializes the sequencer (precomputes tables and creates a publisher). Takes about 3s. */
136
- public async init() {
129
+ /** Initializes the sequencer (precomputes tables). Takes about 3s. */
130
+ public init() {
137
131
  getKzg();
138
- this.publisher = (await this.publisherFactory.create(undefined)).publisher;
139
132
  }
140
133
 
141
134
  /** Starts the sequencer and moves to IDLE state. */
@@ -154,13 +147,12 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
154
147
  public async stop(): Promise<void> {
155
148
  this.log.info(`Stopping sequencer`);
156
149
  this.setState(SequencerState.STOPPING, undefined, { force: true });
157
- this.publisher?.interrupt();
150
+ this.publisherFactory.interruptAll();
158
151
  await this.runningPromise?.stop();
159
152
  this.setState(SequencerState.STOPPED, undefined, { force: true });
160
153
  this.log.info('Stopped sequencer');
161
154
  }
162
155
 
163
- @trackSpan('Sequencer.work')
164
156
  /** Main sequencer loop with a try/catch */
165
157
  protected async safeWork() {
166
158
  try {
@@ -168,7 +160,6 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
168
160
  } catch (err) {
169
161
  this.emit('checkpoint-error', { error: err as Error });
170
162
  if (err instanceof SequencerTooSlowError) {
171
- // TODO(palla/mbps): Add missing states
172
163
  // Log as warn only if we had to abort halfway through the block proposal
173
164
  const logLvl = [SequencerState.INITIALIZING_CHECKPOINT, SequencerState.PROPOSER_CHECK].includes(
174
165
  err.proposedState,
@@ -198,12 +189,13 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
198
189
  * - Collect attestations for the final block
199
190
  * - Submit checkpoint
200
191
  */
192
+ @trackSpan('Sequencer.work')
201
193
  protected async work() {
202
194
  this.setState(SequencerState.SYNCHRONIZING, undefined);
203
195
  const { slot, ts, now, epoch } = this.epochCache.getEpochAndSlotInNextL1Slot();
204
196
 
205
197
  // Check if we are synced and it's our slot, grab a publisher, check previous block invalidation, etc
206
- const checkpointProposalJob = await this.prepareCheckpointProposal(slot, ts, now);
198
+ const checkpointProposalJob = await this.prepareCheckpointProposal(epoch, slot, ts, now);
207
199
  if (!checkpointProposalJob) {
208
200
  return;
209
201
  }
@@ -233,7 +225,9 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
233
225
  * This is the initial step in the main loop.
234
226
  * @returns CheckpointProposalJob if successful, undefined if we are not yet synced or are not the proposer.
235
227
  */
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,8 +257,27 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
263
257
  return undefined;
264
258
  }
265
259
 
266
- // TODO(palla/mbps): Compute proper checkpoint number
267
- const checkpointNumber = CheckpointNumber.fromBlockNumber(BlockNumber(syncedTo.blockNumber + 1));
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
+
279
+ // Next checkpoint follows from the last synced one
280
+ const checkpointNumber = CheckpointNumber(syncedTo.checkpointNumber + 1);
268
281
 
269
282
  const logCtx = {
270
283
  now,
@@ -280,19 +293,19 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
280
293
  this.setState(SequencerState.PROPOSER_CHECK, slot);
281
294
  const [canPropose, proposer] = await this.checkCanPropose(slot);
282
295
 
283
- // If we are not a proposer check if we should invalidate a invalid block, and bail
296
+ // If we are not a proposer check if we should invalidate an invalid checkpoint, and bail
284
297
  if (!canPropose) {
285
- await this.considerInvalidatingBlock(syncedTo, slot);
298
+ await this.considerInvalidatingCheckpoint(syncedTo, slot);
286
299
  return undefined;
287
300
  }
288
301
 
289
302
  // 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) {
303
+ if (syncedTo.blockData && syncedTo.blockData.header.getSlot() >= slot) {
291
304
  this.log.warn(
292
305
  `Cannot propose block at next L2 slot ${slot} since that slot was taken by block ${syncedTo.blockNumber}`,
293
- { ...logCtx, block: syncedTo.block.header.toInspect() },
306
+ { ...logCtx, block: syncedTo.blockData.header.toInspect() },
294
307
  );
295
- this.metrics.recordBlockProposalPrecheckFailed('slot_already_taken');
308
+ this.metrics.recordCheckpointPrecheckFailed('slot_already_taken');
296
309
  return undefined;
297
310
  }
298
311
 
@@ -303,7 +316,6 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
303
316
  const proposerForPublisher = this.config.fishermanMode ? undefined : proposer;
304
317
  const { attestorAddress, publisher } = await this.publisherFactory.create(proposerForPublisher);
305
318
  this.log.verbose(`Created publisher at address ${publisher.getSenderAddress()} for attestor ${attestorAddress}`);
306
- this.publisher = publisher;
307
319
 
308
320
  // In fisherman mode, set the actual proposer's address for simulations
309
321
  if (this.config.fishermanMode && proposer) {
@@ -312,15 +324,14 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
312
324
  }
313
325
 
314
326
  // Prepare invalidation request if the pending chain is invalid (returns undefined if no need)
315
- // TODO(palla/mbps): We need to invalidate checkpoints, not blocks
316
- const invalidateBlock = await publisher.simulateInvalidateBlock(syncedTo.pendingChainValidationStatus);
327
+ const invalidateCheckpoint = await publisher.simulateInvalidateCheckpoint(syncedTo.pendingChainValidationStatus);
317
328
 
318
329
  // Check with the rollup contract if we can indeed propose at the next L2 slot. This check should not fail
319
330
  // if all the previous checks are good, but we do it just in case.
320
331
  const canProposeCheck = await publisher.canProposeAtNextEthBlock(
321
332
  syncedTo.archive,
322
333
  proposer ?? EthAddress.ZERO,
323
- invalidateBlock,
334
+ invalidateCheckpoint,
324
335
  );
325
336
 
326
337
  if (canProposeCheck === undefined) {
@@ -329,7 +340,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
329
340
  logCtx,
330
341
  );
331
342
  this.emit('proposer-rollup-check-failed', { reason: 'Rollup contract check failed', slot });
332
- this.metrics.recordBlockProposalPrecheckFailed('rollup_contract_check_failed');
343
+ this.metrics.recordCheckpointPrecheckFailed('rollup_contract_check_failed');
333
344
  return undefined;
334
345
  }
335
346
 
@@ -339,7 +350,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
339
350
  { ...logCtx, rollup: canProposeCheck, expectedSlot: slot },
340
351
  );
341
352
  this.emit('proposer-rollup-check-failed', { reason: 'Slot mismatch', slot });
342
- this.metrics.recordBlockProposalPrecheckFailed('slot_mismatch');
353
+ this.metrics.recordCheckpointPrecheckFailed('slot_mismatch');
343
354
  return undefined;
344
355
  }
345
356
 
@@ -349,48 +360,54 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
349
360
  { ...logCtx, rollup: canProposeCheck, expectedSlot: slot },
350
361
  );
351
362
  this.emit('proposer-rollup-check-failed', { reason: 'Block mismatch', slot });
352
- this.metrics.recordBlockProposalPrecheckFailed('block_number_mismatch');
363
+ this.metrics.recordCheckpointPrecheckFailed('block_number_mismatch');
353
364
  return undefined;
354
365
  }
355
366
 
356
367
  this.lastSlotForCheckpointProposalJob = slot;
368
+ await this.p2pClient.prepareForSlot(slot);
357
369
  this.log.info(`Preparing checkpoint proposal ${checkpointNumber} at slot ${slot}`, { ...logCtx, proposer });
358
370
 
359
371
  // Create and return the checkpoint proposal job
360
372
  return this.createCheckpointProposalJob(
373
+ epoch,
361
374
  slot,
362
375
  checkpointNumber,
363
376
  syncedTo.blockNumber,
364
377
  proposer,
365
378
  publisher,
366
379
  attestorAddress,
367
- invalidateBlock,
380
+ invalidateCheckpoint,
368
381
  );
369
382
  }
370
383
 
371
384
  protected createCheckpointProposalJob(
385
+ epoch: EpochNumber,
372
386
  slot: SlotNumber,
373
387
  checkpointNumber: CheckpointNumber,
374
388
  syncedToBlockNumber: BlockNumber,
375
389
  proposer: EthAddress | undefined,
376
390
  publisher: SequencerPublisher,
377
391
  attestorAddress: EthAddress,
378
- invalidateBlock: InvalidateBlockRequest | undefined,
392
+ invalidateCheckpoint: InvalidateCheckpointRequest | undefined,
379
393
  ): CheckpointProposalJob {
380
394
  return new CheckpointProposalJob(
395
+ epoch,
381
396
  slot,
382
397
  checkpointNumber,
383
398
  syncedToBlockNumber,
384
399
  proposer,
385
400
  publisher,
386
401
  attestorAddress,
387
- invalidateBlock,
402
+ invalidateCheckpoint,
388
403
  this.validatorClient,
389
404
  this.globalsBuilder,
390
405
  this.p2pClient,
391
406
  this.worldState,
392
407
  this.l1ToL2MessageSource,
408
+ this.l2BlockSource,
393
409
  this.checkpointsBuilder,
410
+ this.l2BlockSource,
394
411
  this.l1Constants,
395
412
  this.config,
396
413
  this.timetable,
@@ -400,10 +417,18 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
400
417
  this.metrics,
401
418
  this,
402
419
  this.setState.bind(this),
403
- this.log,
420
+ this.tracer,
421
+ this.log.getBindings(),
404
422
  );
405
423
  }
406
424
 
425
+ /**
426
+ * Returns the current sequencer state.
427
+ */
428
+ public getState(): SequencerState {
429
+ return this.state;
430
+ }
431
+
407
432
  /**
408
433
  * Internal helper for setting the sequencer state and checks if we have enough time left in the slot to transition to the new state.
409
434
  * @param proposedState - The new state to transition to.
@@ -469,9 +494,9 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
469
494
  number: syncSummary.latestBlockNumber,
470
495
  hash: syncSummary.latestBlockHash,
471
496
  })),
472
- this.l2BlockSource.getL2Tips().then(t => t.latest),
497
+ this.l2BlockSource.getL2Tips().then(t => t.proposed),
473
498
  this.p2pClient.getStatus().then(p2p => p2p.syncedToL2Block),
474
- this.l1ToL2MessageSource.getL2Tips().then(t => t.latest),
499
+ this.l1ToL2MessageSource.getL2Tips().then(t => t.proposed),
475
500
  this.l2BlockSource.getPendingChainValidationStatus(),
476
501
  ] as const);
477
502
 
@@ -479,6 +504,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
479
504
 
480
505
  // Handle zero as a special case, since the block hash won't match across services if we're changing the prefilled data for the genesis block,
481
506
  // as the world state can compute the new genesis block hash, but other components use the hardcoded constant.
507
+ // TODO(palla/mbps): Fix the above. All components should be able to handle dynamic genesis block hashes.
482
508
  const result =
483
509
  (l2BlockSource.number === 0 && worldState.number === 0 && p2p.number === 0 && l1ToL2MessageSource.number === 0) ||
484
510
  (worldState.hash === l2BlockSource.hash &&
@@ -494,20 +520,27 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
494
520
  const blockNumber = worldState.number;
495
521
  if (blockNumber < INITIAL_L2_BLOCK_NUM) {
496
522
  const archive = new Fr((await this.worldState.getCommitted().getTreeInfo(MerkleTreeId.ARCHIVE)).root);
497
- return { blockNumber: BlockNumber(INITIAL_L2_BLOCK_NUM - 1), archive, l1Timestamp, pendingChainValidationStatus };
523
+ return {
524
+ checkpointNumber: CheckpointNumber.ZERO,
525
+ blockNumber: BlockNumber.ZERO,
526
+ archive,
527
+ l1Timestamp,
528
+ pendingChainValidationStatus,
529
+ };
498
530
  }
499
531
 
500
- const block = await this.l2BlockSource.getL2BlockNew(blockNumber);
501
- if (!block) {
532
+ const blockData = await this.l2BlockSource.getBlockData(blockNumber);
533
+ if (!blockData) {
502
534
  // this shouldn't really happen because a moment ago we checked that all components were in sync
503
- this.log.error(`Failed to get L2 block ${blockNumber} from the archiver with all components in sync`);
535
+ this.log.error(`Failed to get L2 block data ${blockNumber} from the archiver with all components in sync`);
504
536
  return undefined;
505
537
  }
506
538
 
507
539
  return {
508
- block,
509
- blockNumber: block.number,
510
- archive: block.archive.root,
540
+ blockData,
541
+ blockNumber: blockData.header.getBlockNumber(),
542
+ checkpointNumber: blockData.checkpointNumber,
543
+ archive: blockData.archive.root,
511
544
  l1Timestamp,
512
545
  pendingChainValidationStatus,
513
546
  };
@@ -524,7 +557,10 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
524
557
  proposer = await this.epochCache.getProposerAttesterAddressInSlot(slot);
525
558
  } catch (e) {
526
559
  if (e instanceof NoCommitteeError) {
527
- this.log.warn(`Cannot propose at next L2 slot ${slot} since the committee does not exist on L1`);
560
+ if (this.lastSlotForNoCommitteeWarning !== slot) {
561
+ this.lastSlotForNoCommitteeWarning = slot;
562
+ this.log.warn(`Cannot propose at next L2 slot ${slot} since the committee does not exist on L1`);
563
+ }
528
564
  return [false, undefined];
529
565
  }
530
566
  this.log.error(`Error getting proposer for slot ${slot}`, e);
@@ -555,11 +591,12 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
555
591
  * Tries to vote on slashing actions and governance when the sync check fails but we're past the max time for initializing a proposal.
556
592
  * This allows the sequencer to participate in governance/slashing votes even when it cannot build blocks.
557
593
  */
594
+ @trackSpan('Seqeuencer.tryVoteWhenSyncFails', ({ slot }) => ({ [Attributes.SLOT_NUMBER]: slot }))
558
595
  protected async tryVoteWhenSyncFails(args: { slot: SlotNumber; ts: bigint }): Promise<void> {
559
596
  const { slot } = args;
560
597
 
561
598
  // Prevent duplicate attempts in the same slot
562
- if (this.lastSlotForVoteWhenSyncFailed === slot) {
599
+ if (this.lastSlotForFallbackVote === slot) {
563
600
  this.log.trace(`Already attempted to vote in slot ${slot} (skipping)`);
564
601
  return;
565
602
  }
@@ -591,7 +628,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
591
628
  }
592
629
 
593
630
  // Mark this slot as attempted
594
- this.lastSlotForVoteWhenSyncFailed = slot;
631
+ this.lastSlotForFallbackVote = slot;
595
632
 
596
633
  // Get a publisher for voting
597
634
  const { attestorAddress, publisher } = await this.publisherFactory.create(proposer);
@@ -625,13 +662,61 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
625
662
  await publisher.sendRequests();
626
663
  }
627
664
 
665
+ /**
666
+ * Tries to vote on slashing actions and governance proposals when escape hatch is open.
667
+ * This allows the sequencer to participate in voting without performing checkpoint proposal work.
668
+ */
669
+ @trackSpan('Sequencer.tryVoteWhenEscapeHatchOpen', ({ slot }) => ({ [Attributes.SLOT_NUMBER]: slot }))
670
+ protected async tryVoteWhenEscapeHatchOpen(args: {
671
+ slot: SlotNumber;
672
+ proposer: EthAddress | undefined;
673
+ }): Promise<void> {
674
+ const { slot, proposer } = args;
675
+
676
+ // Prevent duplicate attempts in the same slot
677
+ if (this.lastSlotForFallbackVote === slot) {
678
+ this.log.trace(`Already attempted to vote in slot ${slot} (escape hatch open, skipping)`);
679
+ return;
680
+ }
681
+
682
+ // Mark this slot as attempted
683
+ this.lastSlotForFallbackVote = slot;
684
+
685
+ const { attestorAddress, publisher } = await this.publisherFactory.create(proposer);
686
+
687
+ this.log.debug(`Escape hatch open for slot ${slot}, attempting vote-only actions`, { slot, attestorAddress });
688
+
689
+ const voter = new CheckpointVoter(
690
+ slot,
691
+ publisher,
692
+ attestorAddress,
693
+ this.validatorClient,
694
+ this.slasherClient,
695
+ this.l1Constants,
696
+ this.config,
697
+ this.metrics,
698
+ this.log,
699
+ );
700
+
701
+ const votesPromises = voter.enqueueVotes();
702
+ const votes = await Promise.all(votesPromises);
703
+
704
+ if (votes.every(p => !p)) {
705
+ this.log.debug(`No votes to enqueue for slot ${slot} (escape hatch open)`);
706
+ return;
707
+ }
708
+
709
+ this.log.info(`Voting in slot ${slot} (escape hatch open)`, { slot });
710
+ await publisher.sendRequests();
711
+ }
712
+
628
713
  /**
629
714
  * Considers invalidating a block if the pending chain is invalid. Depends on how long the invalid block
630
715
  * has been there without being invalidated and whether the sequencer is in the committee or not. We always
631
716
  * have the proposer try to invalidate, but if they fail, the sequencers in the committee are expected to try,
632
717
  * and if they fail, any sequencer will try as well.
633
718
  */
634
- protected async considerInvalidatingBlock(
719
+ protected async considerInvalidatingCheckpoint(
635
720
  syncedTo: SequencerSyncCheckResult,
636
721
  currentSlot: SlotNumber,
637
722
  ): Promise<void> {
@@ -640,18 +725,18 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
640
725
  return;
641
726
  }
642
727
 
643
- const invalidBlockNumber = pendingChainValidationStatus.block.blockNumber;
644
- const invalidBlockTimestamp = pendingChainValidationStatus.block.timestamp;
645
- const timeSinceChainInvalid = this.dateProvider.nowInSeconds() - Number(invalidBlockTimestamp);
728
+ const invalidCheckpointNumber = pendingChainValidationStatus.checkpoint.checkpointNumber;
729
+ const invalidCheckpointTimestamp = pendingChainValidationStatus.checkpoint.timestamp;
730
+ const timeSinceChainInvalid = this.dateProvider.nowInSeconds() - Number(invalidCheckpointTimestamp);
646
731
  const ourValidatorAddresses = this.validatorClient.getValidatorAddresses();
647
732
 
648
733
  const { secondsBeforeInvalidatingBlockAsCommitteeMember, secondsBeforeInvalidatingBlockAsNonCommitteeMember } =
649
734
  this.config;
650
735
 
651
736
  const logData = {
652
- invalidL1Timestamp: invalidBlockTimestamp,
737
+ invalidL1Timestamp: invalidCheckpointTimestamp,
653
738
  l1Timestamp,
654
- invalidBlock: pendingChainValidationStatus.block,
739
+ invalidCheckpoint: pendingChainValidationStatus.checkpoint,
655
740
  secondsBeforeInvalidatingBlockAsCommitteeMember,
656
741
  secondsBeforeInvalidatingBlockAsNonCommitteeMember,
657
742
  ourValidatorAddresses,
@@ -697,25 +782,25 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
697
782
 
698
783
  const { publisher } = await this.publisherFactory.create(validatorToUse);
699
784
 
700
- const invalidateBlock = await publisher.simulateInvalidateBlock(pendingChainValidationStatus);
701
- if (!invalidateBlock) {
702
- this.log.warn(`Failed to simulate invalidate block`, logData);
785
+ const invalidateCheckpoint = await publisher.simulateInvalidateCheckpoint(pendingChainValidationStatus);
786
+ if (!invalidateCheckpoint) {
787
+ this.log.warn(`Failed to simulate invalidate checkpoint`, logData);
703
788
  return;
704
789
  }
705
790
 
706
791
  this.log.info(
707
792
  invalidateAsCommitteeMember
708
- ? `Invalidating block ${invalidBlockNumber} as committee member`
709
- : `Invalidating block ${invalidBlockNumber} as non-committee member`,
793
+ ? `Invalidating checkpoint ${invalidCheckpointNumber} as committee member`
794
+ : `Invalidating checkpoint ${invalidCheckpointNumber} as non-committee member`,
710
795
  logData,
711
796
  );
712
797
 
713
- publisher.enqueueInvalidateBlock(invalidateBlock);
798
+ publisher.enqueueInvalidateCheckpoint(invalidateCheckpoint);
714
799
 
715
800
  if (!this.config.fishermanMode) {
716
801
  await publisher.sendRequests();
717
802
  } else {
718
- this.log.info('Invalidating block in fisherman mode, clearing pending requests');
803
+ this.log.info('Invalidating checkpoint in fisherman mode, clearing pending requests');
719
804
  publisher.clearPendingRequests();
720
805
  }
721
806
  }
@@ -778,6 +863,11 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
778
863
  return this.validatorClient?.getValidatorAddresses();
779
864
  }
780
865
 
866
+ /** Updates the publisher factory's node keystore adapter after a keystore reload. */
867
+ public updatePublisherNodeKeyStore(adapter: NodeKeystoreAdapter): void {
868
+ this.publisherFactory.updateNodeKeyStore(adapter);
869
+ }
870
+
781
871
  public getConfig() {
782
872
  return this.config;
783
873
  }
@@ -788,9 +878,10 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
788
878
  }
789
879
 
790
880
  type SequencerSyncCheckResult = {
791
- block?: L2BlockNew;
881
+ blockData?: BlockData;
882
+ checkpointNumber: CheckpointNumber;
792
883
  blockNumber: BlockNumber;
793
884
  archive: Fr;
794
885
  l1Timestamp: bigint;
795
- pendingChainValidationStatus: ValidateBlockResult;
886
+ pendingChainValidationStatus: ValidateCheckpointResult;
796
887
  };
@@ -1,14 +1,15 @@
1
1
  import { createLogger } from '@aztec/aztec.js/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,
package/src/test/index.ts CHANGED
@@ -1,19 +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
- import type { ValidatorClient } from '@aztec/validator-client';
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
- import type { FullNodeCheckpointsBuilder } from '../sequencer/checkpoint_builder.js';
10
8
  import { Sequencer } from '../sequencer/sequencer.js';
11
9
  import type { SequencerTimetable } from '../sequencer/timetable.js';
12
10
 
13
11
  class TestSequencer_ extends Sequencer {
14
12
  declare public publicProcessorFactory: PublicProcessorFactory;
15
13
  declare public timetable: SequencerTimetable;
16
- declare public publisher: SequencerPublisher;
17
14
  declare public publisherFactory: SequencerPublisherFactory;
18
15
  declare public validatorClient: ValidatorClient;
19
16
  declare public checkpointsBuilder: FullNodeCheckpointsBuilder;
@@ -23,7 +20,7 @@ export type TestSequencer = TestSequencer_;
23
20
 
24
21
  class TestSequencerClient_ extends SequencerClient {
25
22
  declare public sequencer: TestSequencer;
26
- declare public publisherManager: PublisherManager<L1TxUtilsWithBlobs>;
23
+ declare public publisherManager: PublisherManager<L1TxUtils>;
27
24
  }
28
25
 
29
26
  export type TestSequencerClient = TestSequencerClient_;