@aztec/sequencer-client 0.0.1-commit.e558bd1c → 0.0.1-commit.e5a3663dd

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 (103) hide show
  1. package/dest/client/sequencer-client.d.ts +17 -7
  2. package/dest/client/sequencer-client.d.ts.map +1 -1
  3. package/dest/client/sequencer-client.js +63 -30
  4. package/dest/config.d.ts +27 -7
  5. package/dest/config.d.ts.map +1 -1
  6. package/dest/config.js +58 -37
  7. package/dest/global_variable_builder/fee_predictor.d.ts +37 -0
  8. package/dest/global_variable_builder/fee_predictor.d.ts.map +1 -0
  9. package/dest/global_variable_builder/fee_predictor.js +128 -0
  10. package/dest/global_variable_builder/fee_provider.d.ts +21 -0
  11. package/dest/global_variable_builder/fee_provider.d.ts.map +1 -0
  12. package/dest/global_variable_builder/fee_provider.js +58 -0
  13. package/dest/global_variable_builder/global_builder.d.ts +15 -16
  14. package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
  15. package/dest/global_variable_builder/global_builder.js +16 -50
  16. package/dest/global_variable_builder/index.d.ts +4 -2
  17. package/dest/global_variable_builder/index.d.ts.map +1 -1
  18. package/dest/global_variable_builder/index.js +2 -0
  19. package/dest/publisher/config.d.ts +47 -17
  20. package/dest/publisher/config.d.ts.map +1 -1
  21. package/dest/publisher/config.js +121 -42
  22. package/dest/publisher/index.d.ts +2 -1
  23. package/dest/publisher/index.d.ts.map +1 -1
  24. package/dest/publisher/l1_tx_failed_store/factory.d.ts +11 -0
  25. package/dest/publisher/l1_tx_failed_store/factory.d.ts.map +1 -0
  26. package/dest/publisher/l1_tx_failed_store/factory.js +22 -0
  27. package/dest/publisher/l1_tx_failed_store/failed_tx_store.d.ts +58 -0
  28. package/dest/publisher/l1_tx_failed_store/failed_tx_store.d.ts.map +1 -0
  29. package/dest/publisher/l1_tx_failed_store/failed_tx_store.js +1 -0
  30. package/dest/publisher/l1_tx_failed_store/file_store_failed_tx_store.d.ts +15 -0
  31. package/dest/publisher/l1_tx_failed_store/file_store_failed_tx_store.d.ts.map +1 -0
  32. package/dest/publisher/l1_tx_failed_store/file_store_failed_tx_store.js +34 -0
  33. package/dest/publisher/l1_tx_failed_store/index.d.ts +4 -0
  34. package/dest/publisher/l1_tx_failed_store/index.d.ts.map +1 -0
  35. package/dest/publisher/l1_tx_failed_store/index.js +2 -0
  36. package/dest/publisher/sequencer-publisher-factory.d.ts +11 -5
  37. package/dest/publisher/sequencer-publisher-factory.d.ts.map +1 -1
  38. package/dest/publisher/sequencer-publisher-factory.js +27 -3
  39. package/dest/publisher/sequencer-publisher.d.ts +75 -48
  40. package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
  41. package/dest/publisher/sequencer-publisher.js +434 -141
  42. package/dest/sequencer/chain_state_overrides.d.ts +25 -0
  43. package/dest/sequencer/chain_state_overrides.d.ts.map +1 -0
  44. package/dest/sequencer/chain_state_overrides.js +39 -0
  45. package/dest/sequencer/checkpoint_proposal_job.d.ts +46 -10
  46. package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -1
  47. package/dest/sequencer/checkpoint_proposal_job.js +705 -223
  48. package/dest/sequencer/checkpoint_proposal_job_metrics.d.ts +34 -0
  49. package/dest/sequencer/checkpoint_proposal_job_metrics.d.ts.map +1 -0
  50. package/dest/sequencer/checkpoint_proposal_job_metrics.js +72 -0
  51. package/dest/sequencer/checkpoint_voter.d.ts +1 -2
  52. package/dest/sequencer/checkpoint_voter.d.ts.map +1 -1
  53. package/dest/sequencer/checkpoint_voter.js +2 -5
  54. package/dest/sequencer/events.d.ts +7 -1
  55. package/dest/sequencer/events.d.ts.map +1 -1
  56. package/dest/sequencer/metrics.d.ts +23 -8
  57. package/dest/sequencer/metrics.d.ts.map +1 -1
  58. package/dest/sequencer/metrics.js +117 -21
  59. package/dest/sequencer/sequencer.d.ts +53 -19
  60. package/dest/sequencer/sequencer.d.ts.map +1 -1
  61. package/dest/sequencer/sequencer.js +188 -101
  62. package/dest/sequencer/timetable.d.ts +17 -6
  63. package/dest/sequencer/timetable.d.ts.map +1 -1
  64. package/dest/sequencer/timetable.js +51 -46
  65. package/dest/sequencer/types.d.ts +2 -2
  66. package/dest/sequencer/types.d.ts.map +1 -1
  67. package/dest/test/index.d.ts +3 -5
  68. package/dest/test/index.d.ts.map +1 -1
  69. package/dest/test/mock_checkpoint_builder.d.ts +11 -11
  70. package/dest/test/mock_checkpoint_builder.d.ts.map +1 -1
  71. package/dest/test/mock_checkpoint_builder.js +45 -34
  72. package/dest/test/utils.d.ts +3 -3
  73. package/dest/test/utils.d.ts.map +1 -1
  74. package/dest/test/utils.js +10 -8
  75. package/package.json +27 -28
  76. package/src/client/sequencer-client.ts +81 -30
  77. package/src/config.ts +77 -46
  78. package/src/global_variable_builder/README.md +44 -0
  79. package/src/global_variable_builder/fee_predictor.ts +172 -0
  80. package/src/global_variable_builder/fee_provider.ts +75 -0
  81. package/src/global_variable_builder/global_builder.ts +27 -63
  82. package/src/global_variable_builder/index.ts +3 -1
  83. package/src/publisher/config.ts +157 -45
  84. package/src/publisher/index.ts +3 -0
  85. package/src/publisher/l1_tx_failed_store/factory.ts +32 -0
  86. package/src/publisher/l1_tx_failed_store/failed_tx_store.ts +57 -0
  87. package/src/publisher/l1_tx_failed_store/file_store_failed_tx_store.ts +46 -0
  88. package/src/publisher/l1_tx_failed_store/index.ts +3 -0
  89. package/src/publisher/sequencer-publisher-factory.ts +38 -9
  90. package/src/publisher/sequencer-publisher.ts +501 -202
  91. package/src/sequencer/README.md +82 -13
  92. package/src/sequencer/chain_state_overrides.ts +87 -0
  93. package/src/sequencer/checkpoint_proposal_job.ts +873 -243
  94. package/src/sequencer/checkpoint_proposal_job_metrics.ts +128 -0
  95. package/src/sequencer/checkpoint_voter.ts +1 -12
  96. package/src/sequencer/events.ts +6 -1
  97. package/src/sequencer/metrics.ts +132 -25
  98. package/src/sequencer/sequencer.ts +262 -115
  99. package/src/sequencer/timetable.ts +70 -57
  100. package/src/sequencer/types.ts +1 -1
  101. package/src/test/index.ts +2 -4
  102. package/src/test/mock_checkpoint_builder.ts +63 -49
  103. package/src/test/utils.ts +31 -10
@@ -373,14 +373,14 @@ function _apply_decs_2203_r(targetClass, memberDecs, classDecs, parentClass) {
373
373
  var _dec, _dec1, _dec2, _dec3, _initProto;
374
374
  import { getKzg } from '@aztec/blob-lib';
375
375
  import { INITIAL_L2_BLOCK_NUM } from '@aztec/constants';
376
- import { NoCommitteeError } from '@aztec/ethereum/contracts';
376
+ import { NoCommitteeError, SimulationOverridesBuilder } from '@aztec/ethereum/contracts';
377
377
  import { BlockNumber, CheckpointNumber } from '@aztec/foundation/branded-types';
378
378
  import { merge, omit, pick } from '@aztec/foundation/collection';
379
379
  import { Fr } from '@aztec/foundation/curves/bn254';
380
380
  import { EthAddress } from '@aztec/foundation/eth-address';
381
381
  import { createLogger } from '@aztec/foundation/log';
382
382
  import { RunningPromise } from '@aztec/foundation/running-promise';
383
- import { getSlotAtTimestamp, getSlotStartBuildTimestamp } from '@aztec/stdlib/epoch-helpers';
383
+ import { getSlotStartBuildTimestamp } from '@aztec/stdlib/epoch-helpers';
384
384
  import { SequencerConfigSchema } from '@aztec/stdlib/interfaces/server';
385
385
  import { pickFromSchema } from '@aztec/stdlib/schemas';
386
386
  import { MerkleTreeId } from '@aztec/stdlib/trees';
@@ -388,6 +388,7 @@ import { Attributes, getTelemetryClient, trackSpan } from '@aztec/telemetry-clie
388
388
  import EventEmitter from 'node:events';
389
389
  import { DefaultSequencerConfig } from '../config.js';
390
390
  import { CheckpointProposalJob } from './checkpoint_proposal_job.js';
391
+ import { CheckpointProposalJobMetrics } from './checkpoint_proposal_job_metrics.js';
391
392
  import { CheckpointVoter } from './checkpoint_voter.js';
392
393
  import { SequencerInterruptedError, SequencerTooSlowError } from './errors.js';
393
394
  import { SequencerMetrics } from './metrics.js';
@@ -448,46 +449,52 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
448
449
  }
449
450
  runningPromise;
450
451
  state;
452
+ stateSlotNumber;
453
+ stateEnteredAtMs;
451
454
  metrics;
455
+ checkpointProposalJobMetrics;
456
+ stateLog;
452
457
  /** The last slot for which we attempted to perform our voting duties with degraded block production */ lastSlotForFallbackVote;
453
458
  /** The last slot for which we logged "no committee" warning, to avoid spam */ lastSlotForNoCommitteeWarning;
454
459
  /** The last slot for which we triggered a checkpoint proposal job, to prevent duplicate attempts. */ lastSlotForCheckpointProposalJob;
455
460
  /** Last successful checkpoint proposed */ lastCheckpointProposed;
456
461
  /** The last epoch for which we logged strategy comparison in fisherman mode. */ lastEpochForStrategyComparison;
462
+ /** The last checkpoint proposal job, tracked so we can await its pending L1 submission during shutdown. */ lastCheckpointProposalJob;
457
463
  /** The maximum number of seconds that the sequencer can be into a slot to transition to a particular state. */ timetable;
458
- // This shouldn't be here as this gets re-created each time we build/propose a block.
459
- // But we have a number of tests that abuse/rely on this class having a permanent publisher.
460
- // As long as those tests only configure a single publisher they will continue to work.
461
- // This will get re-assigned every time the sequencer goes to build a new block to a publisher that is valid
462
- // for the block proposer.
463
- // TODO(palla/mbps): Remove this field and fix tests
464
- publisher;
465
464
  /** Config for the sequencer */ config;
465
+ signatureContext;
466
466
  constructor(publisherFactory, validatorClient, globalsBuilder, p2pClient, worldState, slasherClient, l2BlockSource, l1ToL2MessageSource, checkpointsBuilder, l1Constants, dateProvider, epochCache, rollupContract, config, telemetry = getTelemetryClient(), log = createLogger('sequencer')){
467
- super(), this.publisherFactory = publisherFactory, this.validatorClient = validatorClient, this.globalsBuilder = globalsBuilder, this.p2pClient = p2pClient, this.worldState = worldState, this.slasherClient = slasherClient, this.l2BlockSource = l2BlockSource, this.l1ToL2MessageSource = l1ToL2MessageSource, this.checkpointsBuilder = checkpointsBuilder, this.l1Constants = l1Constants, this.dateProvider = dateProvider, this.epochCache = epochCache, this.rollupContract = rollupContract, this.telemetry = telemetry, this.log = log, this.state = (_initProto(this), SequencerState.STOPPED), this.config = DefaultSequencerConfig;
467
+ super(), this.publisherFactory = publisherFactory, this.validatorClient = validatorClient, this.globalsBuilder = globalsBuilder, this.p2pClient = p2pClient, this.worldState = worldState, this.slasherClient = slasherClient, this.l2BlockSource = l2BlockSource, this.l1ToL2MessageSource = l1ToL2MessageSource, this.checkpointsBuilder = checkpointsBuilder, this.l1Constants = l1Constants, this.dateProvider = dateProvider, this.epochCache = epochCache, this.rollupContract = rollupContract, this.telemetry = telemetry, this.log = log, this.state = (_initProto(this), SequencerState.STOPPED), this.stateEnteredAtMs = performance.now(), this.config = DefaultSequencerConfig;
468
+ this.stateLog = log.createChild('state');
468
469
  // Add [FISHERMAN] prefix to logger if in fisherman mode
469
470
  if (config.fishermanMode) {
470
471
  this.log = log.createChild('[FISHERMAN]');
471
472
  }
473
+ this.signatureContext = {
474
+ chainId: config.l1ChainId,
475
+ rollupAddress: config.l1Contracts.rollupAddress
476
+ };
472
477
  this.metrics = new SequencerMetrics(telemetry, this.rollupContract, 'Sequencer');
478
+ this.checkpointProposalJobMetrics = new CheckpointProposalJobMetrics(telemetry);
473
479
  this.updateConfig(config);
474
480
  }
475
481
  /** Updates sequencer config by the defined values and updates the timetable */ updateConfig(config) {
476
482
  const filteredConfig = pickFromSchema(config, SequencerConfigSchema);
477
- this.log.info(`Updated sequencer config`, omit(filteredConfig, 'txPublicSetupAllowList'));
483
+ this.log.info(`Updated sequencer config`, omit(filteredConfig, 'txPublicSetupAllowListExtend'));
478
484
  this.config = merge(this.config, filteredConfig);
485
+ const p2pPropagationTime = this.config.attestationPropagationTime;
479
486
  this.timetable = new SequencerTimetable({
480
487
  ethereumSlotDuration: this.l1Constants.ethereumSlotDuration,
481
488
  aztecSlotDuration: this.aztecSlotDuration,
482
489
  l1PublishingTime: this.l1PublishingTime,
483
- p2pPropagationTime: this.config.attestationPropagationTime,
490
+ p2pPropagationTime,
484
491
  blockDurationMs: this.config.blockDurationMs,
485
- enforce: this.config.enforceTimeTable
492
+ enforce: this.config.enforceTimeTable,
493
+ pipelining: this.epochCache.isProposerPipeliningEnabled()
486
494
  }, this.metrics, this.log);
487
495
  }
488
- /** Initializes the sequencer (precomputes tables and creates a publisher). Takes about 3s. */ async init() {
496
+ /** Initializes the sequencer (precomputes tables). Takes about 3s. */ init() {
489
497
  getKzg();
490
- this.publisher = (await this.publisherFactory.create(undefined)).publisher;
491
498
  }
492
499
  /** Starts the sequencer and moves to IDLE state. */ start() {
493
500
  this.runningPromise = new RunningPromise(this.safeWork.bind(this), this.log, this.config.sequencerPollingIntervalMS);
@@ -497,13 +504,17 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
497
504
  this.runningPromise.start();
498
505
  this.log.info('Started sequencer');
499
506
  }
507
+ /** Triggers an immediate run of the sequencer, bypassing the polling interval. */ trigger() {
508
+ return this.runningPromise?.trigger();
509
+ }
500
510
  /** Stops the sequencer from building blocks and moves to STOPPED state. */ async stop() {
501
511
  this.log.info(`Stopping sequencer`);
502
512
  this.setState(SequencerState.STOPPING, undefined, {
503
513
  force: true
504
514
  });
505
- this.publisher?.interrupt();
515
+ await this.publisherFactory.stopAll();
506
516
  await this.runningPromise?.stop();
517
+ await this.lastCheckpointProposalJob?.awaitPendingSubmission();
507
518
  this.setState(SequencerState.STOPPED, undefined, {
508
519
  force: true
509
520
  });
@@ -517,7 +528,6 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
517
528
  error: err
518
529
  });
519
530
  if (err instanceof SequencerTooSlowError) {
520
- // TODO(palla/mbps): Add missing states
521
531
  // Log as warn only if we had to abort halfway through the block proposal
522
532
  const logLvl = [
523
533
  SequencerState.INITIALIZING_CHECKPOINT,
@@ -548,22 +558,25 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
548
558
  * - Submit checkpoint
549
559
  */ async work() {
550
560
  this.setState(SequencerState.SYNCHRONIZING, undefined);
551
- const { slot, ts, now, epoch } = this.epochCache.getEpochAndSlotInNextL1Slot();
561
+ const { slot, ts, nowSeconds, epoch } = this.epochCache.getEpochAndSlotInNextL1Slot();
562
+ const { slot: targetSlot, epoch: targetEpoch } = this.epochCache.getTargetEpochAndSlotInNextL1Slot();
552
563
  // Check if we are synced and it's our slot, grab a publisher, check previous block invalidation, etc
553
- const checkpointProposalJob = await this.prepareCheckpointProposal(epoch, slot, ts, now);
564
+ const checkpointProposalJob = await this.prepareCheckpointProposal(slot, targetSlot, epoch, targetEpoch, ts, nowSeconds);
554
565
  if (!checkpointProposalJob) {
555
566
  return;
556
567
  }
568
+ // Track the job so we can await its pending L1 submission during shutdown
569
+ this.lastCheckpointProposalJob = checkpointProposalJob;
557
570
  // Execute the checkpoint proposal job
558
571
  const checkpoint = await checkpointProposalJob.execute();
559
572
  // Update last checkpoint proposed (currently unused)
560
573
  if (checkpoint) {
561
574
  this.lastCheckpointProposed = checkpoint;
562
575
  }
563
- // Log fee strategy comparison if on fisherman
564
- if (this.config.fishermanMode && (this.lastEpochForStrategyComparison === undefined || epoch > this.lastEpochForStrategyComparison)) {
565
- this.logStrategyComparison(epoch, checkpointProposalJob.getPublisher());
566
- this.lastEpochForStrategyComparison = epoch;
576
+ // Log fee strategy comparison if on fisherman (uses target epoch since we mirror the proposer's perspective)
577
+ if (this.config.fishermanMode && (this.lastEpochForStrategyComparison === undefined || targetEpoch > this.lastEpochForStrategyComparison)) {
578
+ this.logStrategyComparison(targetEpoch, checkpointProposalJob.getPublisher());
579
+ this.lastEpochForStrategyComparison = targetEpoch;
567
580
  }
568
581
  return checkpoint;
569
582
  }
@@ -571,17 +584,17 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
571
584
  * Prepares the checkpoint proposal by performing all necessary checks and setup.
572
585
  * This is the initial step in the main loop.
573
586
  * @returns CheckpointProposalJob if successful, undefined if we are not yet synced or are not the proposer.
574
- */ async prepareCheckpointProposal(epoch, slot, ts, now) {
575
- // Check we have not already processed this slot (cheapest check)
587
+ */ async prepareCheckpointProposal(slot, targetSlot, epoch, targetEpoch, ts, nowSeconds) {
588
+ // Check we have not already processed this target slot (cheapest check)
576
589
  // We only check this if enforce timetable is set, since we want to keep processing the same slot if we are not
577
590
  // running against actual time (eg when we use sandbox-style automining)
578
- if (this.lastSlotForCheckpointProposalJob && this.lastSlotForCheckpointProposalJob >= slot && this.config.enforceTimeTable) {
579
- this.log.trace(`Slot ${slot} has already been processed`);
591
+ if (this.lastSlotForCheckpointProposalJob && this.lastSlotForCheckpointProposalJob >= targetSlot && this.config.enforceTimeTable) {
592
+ this.log.trace(`Target slot ${targetSlot} has already been processed`);
580
593
  return undefined;
581
594
  }
582
- // But if we have already proposed for this slot, the we definitely have to skip it, automining or not
583
- if (this.lastCheckpointProposed && this.lastCheckpointProposed.header.slotNumber >= slot) {
584
- this.log.trace(`Slot ${slot} has already been published as checkpoint ${this.lastCheckpointProposed.number}`);
595
+ // But if we have already proposed for this slot, then we definitely have to skip it, automining or not
596
+ if (this.lastCheckpointProposed && this.lastCheckpointProposed.header.slotNumber >= targetSlot) {
597
+ this.log.trace(`Slot ${targetSlot} has already been published as checkpoint ${this.lastCheckpointProposed.number}`);
585
598
  return undefined;
586
599
  }
587
600
  // Check all components are synced to latest as seen by the archiver (queries all subsystems)
@@ -592,16 +605,18 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
592
605
  if (!syncedTo) {
593
606
  await this.tryVoteWhenSyncFails({
594
607
  slot,
608
+ targetSlot,
595
609
  ts
596
610
  });
597
611
  return undefined;
598
612
  }
599
- // If escape hatch is open for this epoch, do not start checkpoint proposal work and do not attempt invalidations.
613
+ // If escape hatch is open for the target epoch, do not start checkpoint proposal work and do not attempt invalidations.
600
614
  // Still perform governance/slashing voting (as proposer) once per slot.
601
- const isEscapeHatchOpen = await this.epochCache.isEscapeHatchOpen(epoch);
615
+ // When pipelining, we check the target epoch (slot+1's epoch) since that's the epoch we're building for.
616
+ const isEscapeHatchOpen = await this.epochCache.isEscapeHatchOpen(targetEpoch);
602
617
  if (isEscapeHatchOpen) {
603
618
  this.setState(SequencerState.PROPOSER_CHECK, slot);
604
- const [canPropose, proposer] = await this.checkCanPropose(slot);
619
+ const [canPropose, proposer] = await this.checkCanPropose(targetSlot);
605
620
  if (canPropose) {
606
621
  await this.tryVoteWhenEscapeHatchOpen({
607
622
  slot,
@@ -619,29 +634,36 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
619
634
  // Next checkpoint follows from the last synced one
620
635
  const checkpointNumber = CheckpointNumber(syncedTo.checkpointNumber + 1);
621
636
  const logCtx = {
622
- now,
623
- syncedToL1Ts: syncedTo.l1Timestamp,
624
- syncedToL2Slot: getSlotAtTimestamp(syncedTo.l1Timestamp, this.l1Constants),
637
+ nowSeconds,
638
+ syncedToL2Slot: syncedTo.syncedL2Slot,
625
639
  slot,
640
+ targetSlot,
626
641
  slotTs: ts,
627
642
  checkpointNumber,
628
643
  isPendingChainValid: pick(syncedTo.pendingChainValidationStatus, 'valid', 'reason', 'invalidIndex')
629
644
  };
630
- // Check that we are a proposer for the next slot
645
+ // Check that we are a proposer for the target slot.
631
646
  this.setState(SequencerState.PROPOSER_CHECK, slot);
632
- const [canPropose, proposer] = await this.checkCanPropose(slot);
647
+ const [canPropose, proposer] = await this.checkCanPropose(targetSlot);
633
648
  // If we are not a proposer check if we should invalidate an invalid checkpoint, and bail
634
649
  if (!canPropose) {
635
650
  await this.considerInvalidatingCheckpoint(syncedTo, slot);
636
651
  return undefined;
637
652
  }
638
- // Check that the slot is not taken by a block already (should never happen, since only us can propose for this slot)
639
- if (syncedTo.block && syncedTo.block.header.getSlot() >= slot) {
640
- this.log.warn(`Cannot propose block at next L2 slot ${slot} since that slot was taken by block ${syncedTo.blockNumber}`, {
653
+ // Guard: don't exceed 1-deep pipeline. Without a proposed checkpoint, we can only build
654
+ // confirmed + 1. With a proposed checkpoint, we can build confirmed + 2.
655
+ const confirmedCkpt = syncedTo.checkpointedCheckpointNumber;
656
+ if (checkpointNumber > confirmedCkpt + 2) {
657
+ this.log.verbose(`Skipping slot ${targetSlot}: checkpoint ${checkpointNumber} exceeds max pipeline depth (confirmed=${confirmedCkpt})`);
658
+ return undefined;
659
+ }
660
+ // Check that the target slot is not taken by a block already (should never happen, since only us can propose for this slot)
661
+ if (syncedTo.blockData && syncedTo.blockData.header.getSlot() >= targetSlot) {
662
+ this.log.warn(`Cannot propose block at target slot ${targetSlot} since that slot was taken by block ${syncedTo.blockNumber}`, {
641
663
  ...logCtx,
642
- block: syncedTo.block.header.toInspect()
664
+ block: syncedTo.blockData.header.toInspect()
643
665
  });
644
- this.metrics.recordBlockProposalPrecheckFailed('slot_already_taken');
666
+ this.metrics.recordCheckpointPrecheckFailed('slot_already_taken');
645
667
  return undefined;
646
668
  }
647
669
  // We now need to get ourselves a publisher.
@@ -651,37 +673,58 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
651
673
  const proposerForPublisher = this.config.fishermanMode ? undefined : proposer;
652
674
  const { attestorAddress, publisher } = await this.publisherFactory.create(proposerForPublisher);
653
675
  this.log.verbose(`Created publisher at address ${publisher.getSenderAddress()} for attestor ${attestorAddress}`);
654
- this.publisher = publisher;
655
676
  // In fisherman mode, set the actual proposer's address for simulations
656
677
  if (this.config.fishermanMode && proposer) {
657
678
  publisher.setProposerAddressForSimulation(proposer);
658
679
  this.log.debug(`Set proposer address ${proposer} for simulation in fisherman mode`);
659
680
  }
660
681
  // Prepare invalidation request if the pending chain is invalid (returns undefined if no need)
661
- const invalidateCheckpoint = await publisher.simulateInvalidateCheckpoint(syncedTo.pendingChainValidationStatus);
662
- // Check with the rollup contract if we can indeed propose at the next L2 slot. This check should not fail
663
- // if all the previous checks are good, but we do it just in case.
664
- const canProposeCheck = await publisher.canProposeAtNextEthBlock(syncedTo.archive, proposer ?? EthAddress.ZERO, invalidateCheckpoint);
682
+ let invalidateCheckpoint = await publisher.simulateInvalidateCheckpoint(syncedTo.pendingChainValidationStatus);
683
+ // Determine the correct archive and L1 state overrides for the canProposeAt check.
684
+ // The L1 contract reads archives[proposedCheckpointNumber] and compares it with the provided archive.
685
+ // When invalidating or pipelining, the local archive may differ from L1's, so we adjust accordingly.
686
+ let archiveForCheck = syncedTo.archive;
687
+ const l1SimulationOverridesBuilder = new SimulationOverridesBuilder();
688
+ if (this.epochCache.isProposerPipeliningEnabled() && syncedTo.hasProposedCheckpoint) {
689
+ // Parent checkpoint hasn't landed on L1 yet. Override both the proposed checkpoint number
690
+ // and the archive at that checkpoint so L1 simulation sees the correct chain tip.
691
+ const parentCheckpointNumber = CheckpointNumber(checkpointNumber - 1);
692
+ l1SimulationOverridesBuilder.forPendingCheckpoint(parentCheckpointNumber).withPendingArchive(syncedTo.archive);
693
+ this.metrics.recordPipelineDepth(syncedTo.checkpointNumber - syncedTo.checkpointedCheckpointNumber);
694
+ this.log.verbose(`Building on top of proposed checkpoint (pending=${syncedTo.proposedCheckpointData?.checkpointNumber})`);
695
+ // Clear the invalidation - the proposed checkpoint should handle it.
696
+ invalidateCheckpoint = undefined;
697
+ } else if (invalidateCheckpoint) {
698
+ // After invalidation, L1 will roll back to checkpoint N-1. The archive at N-1 already
699
+ // exists on L1, so we just pass the matching archive (the lastArchive of the invalid checkpoint).
700
+ archiveForCheck = invalidateCheckpoint.lastArchive;
701
+ l1SimulationOverridesBuilder.forPendingCheckpoint(invalidateCheckpoint.forcePendingCheckpointNumber);
702
+ this.metrics.recordPipelineDepth(0);
703
+ } else {
704
+ this.metrics.recordPipelineDepth(0);
705
+ }
706
+ const simulationOverridesPlan = l1SimulationOverridesBuilder.build();
707
+ const canProposeCheck = await publisher.canProposeAt(archiveForCheck, proposer ?? EthAddress.ZERO, simulationOverridesPlan);
665
708
  if (canProposeCheck === undefined) {
666
709
  this.log.warn(`Cannot propose checkpoint ${checkpointNumber} at slot ${slot} due to failed rollup contract check`, logCtx);
667
710
  this.emit('proposer-rollup-check-failed', {
668
711
  reason: 'Rollup contract check failed',
669
712
  slot
670
713
  });
671
- this.metrics.recordBlockProposalPrecheckFailed('rollup_contract_check_failed');
714
+ this.metrics.recordCheckpointPrecheckFailed('rollup_contract_check_failed');
672
715
  return undefined;
673
716
  }
674
- if (canProposeCheck.slot !== slot) {
675
- this.log.warn(`Cannot propose block due to slot mismatch with rollup contract (this can be caused by a clock out of sync). Expected slot ${slot} but got ${canProposeCheck.slot}.`, {
717
+ if (canProposeCheck.slot !== targetSlot) {
718
+ this.log.warn(`Cannot propose block due to slot mismatch with rollup contract (this can be caused by a clock out of sync). Expected slot ${targetSlot} but got ${canProposeCheck.slot}.`, {
676
719
  ...logCtx,
677
720
  rollup: canProposeCheck,
678
- expectedSlot: slot
721
+ expectedSlot: targetSlot
679
722
  });
680
723
  this.emit('proposer-rollup-check-failed', {
681
724
  reason: 'Slot mismatch',
682
725
  slot
683
726
  });
684
- this.metrics.recordBlockProposalPrecheckFailed('slot_mismatch');
727
+ this.metrics.recordCheckpointPrecheckFailed('slot_mismatch');
685
728
  return undefined;
686
729
  }
687
730
  if (canProposeCheck.checkpointNumber !== checkpointNumber) {
@@ -694,19 +737,26 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
694
737
  reason: 'Block mismatch',
695
738
  slot
696
739
  });
697
- this.metrics.recordBlockProposalPrecheckFailed('block_number_mismatch');
740
+ this.metrics.recordCheckpointPrecheckFailed('block_number_mismatch');
698
741
  return undefined;
699
742
  }
700
- this.lastSlotForCheckpointProposalJob = slot;
701
- this.log.info(`Preparing checkpoint proposal ${checkpointNumber} at slot ${slot}`, {
743
+ this.lastSlotForCheckpointProposalJob = targetSlot;
744
+ await this.p2pClient.prepareForSlot(targetSlot);
745
+ this.log.info(`Preparing checkpoint proposal ${checkpointNumber} for target slot ${targetSlot} during wall-clock slot ${slot}`, {
702
746
  ...logCtx,
703
- proposer
747
+ proposer,
748
+ pipeliningEnabled: this.epochCache.isProposerPipeliningEnabled()
704
749
  });
705
750
  // Create and return the checkpoint proposal job
706
- return this.createCheckpointProposalJob(epoch, slot, checkpointNumber, syncedTo.blockNumber, proposer, publisher, attestorAddress, invalidateCheckpoint);
751
+ return this.createCheckpointProposalJob(slot, targetSlot, targetEpoch, checkpointNumber, syncedTo.blockNumber, proposer, publisher, attestorAddress, invalidateCheckpoint, syncedTo.proposedCheckpointData);
707
752
  }
708
- createCheckpointProposalJob(epoch, slot, checkpointNumber, syncedToBlockNumber, proposer, publisher, attestorAddress, invalidateCheckpoint) {
709
- return new CheckpointProposalJob(epoch, slot, checkpointNumber, syncedToBlockNumber, proposer, publisher, attestorAddress, invalidateCheckpoint, this.validatorClient, this.globalsBuilder, this.p2pClient, this.worldState, this.l1ToL2MessageSource, this.l2BlockSource, this.checkpointsBuilder, this.l2BlockSource, this.l1Constants, this.config, this.timetable, this.slasherClient, this.epochCache, this.dateProvider, this.metrics, this, this.setState.bind(this), this.tracer, this.log.getBindings());
753
+ createCheckpointProposalJob(slot, targetSlot, targetEpoch, checkpointNumber, syncedToBlockNumber, proposer, publisher, attestorAddress, invalidateCheckpoint, proposedCheckpointData) {
754
+ return new CheckpointProposalJob(slot, targetSlot, targetEpoch, checkpointNumber, syncedToBlockNumber, proposer, publisher, attestorAddress, invalidateCheckpoint, this.validatorClient, this.globalsBuilder, this.p2pClient, this.worldState, this.l1ToL2MessageSource, this.l2BlockSource, this.checkpointsBuilder, this.l2BlockSource, this.l1Constants, this.signatureContext, this.config, this.timetable, this.slasherClient, this.epochCache, this.dateProvider, this.metrics, this.checkpointProposalJobMetrics.createRecorder(), this, this.setState.bind(this), this.tracer, this.log.getBindings(), proposedCheckpointData);
755
+ }
756
+ /**
757
+ * Returns the current sequencer state.
758
+ */ getState() {
759
+ return this.state;
710
760
  }
711
761
  /**
712
762
  * Internal helper for setting the sequencer state and checks if we have enough time left in the slot to transition to the new state.
@@ -727,37 +777,52 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
727
777
  secondsIntoSlot = this.getSecondsIntoSlot(slotNumber);
728
778
  this.timetable.assertTimeLeft(proposedState, secondsIntoSlot);
729
779
  }
780
+ const oldState = this.state;
781
+ const oldStateSlotNumber = this.stateSlotNumber;
782
+ const stateChanged = proposedState !== oldState;
783
+ const transitionAtMs = performance.now();
784
+ const stateDurationMs = transitionAtMs - this.stateEnteredAtMs;
730
785
  const boringStates = [
731
786
  SequencerState.IDLE,
732
787
  SequencerState.SYNCHRONIZING
733
788
  ];
734
- const logLevel = boringStates.includes(proposedState) && boringStates.includes(this.state) ? 'trace' : 'debug';
735
- this.log[logLevel](`Transitioning from ${this.state} to ${proposedState}`, {
789
+ const logLevel = boringStates.includes(proposedState) && boringStates.includes(oldState) ? 'trace' : 'debug';
790
+ this.stateLog[logLevel](`Transitioning from ${oldState} to ${proposedState}`, {
791
+ oldState,
792
+ newState: proposedState,
736
793
  slotNumber,
737
- secondsIntoSlot
794
+ stateSlotNumber: oldStateSlotNumber,
795
+ secondsIntoSlot,
796
+ ...stateChanged && {
797
+ stateDurationMs: Math.ceil(stateDurationMs)
798
+ }
738
799
  });
739
800
  this.emit('state-changed', {
740
- oldState: this.state,
801
+ oldState,
741
802
  newState: proposedState,
742
803
  secondsIntoSlot,
743
804
  slot: slotNumber
744
805
  });
806
+ if (stateChanged) {
807
+ this.metrics.recordStateDuration(stateDurationMs, oldState);
808
+ this.stateEnteredAtMs = transitionAtMs;
809
+ this.stateSlotNumber = slotNumber;
810
+ }
745
811
  this.state = proposedState;
746
812
  }
747
813
  /**
748
814
  * Returns whether all dependencies have caught up.
749
815
  * We don't check against the previous block submitted since it may have been reorg'd out.
750
816
  */ async checkSync(args) {
751
- // Check that the archiver and dependencies have synced to the previous L1 slot at least
752
- // TODO(#14766): Archiver reports L1 timestamp based on L1 blocks seen, which means that a missed L1 block will
753
- // cause the archiver L1 timestamp to fall behind, and cause this sequencer to start processing one L1 slot later.
754
- const l1Timestamp = await this.l2BlockSource.getL1Timestamp();
755
- const { slot, ts } = args;
756
- if (l1Timestamp === undefined || l1Timestamp + BigInt(this.l1Constants.ethereumSlotDuration) < ts) {
817
+ // Check that the archiver has fully synced the L2 slot before the one we want to propose in.
818
+ // The archiver reports sync progress via L1 block timestamps and synced checkpoint slots.
819
+ // See getSyncedL2SlotNumber for how missed L1 blocks are handled.
820
+ const syncedL2Slot = await this.l2BlockSource.getSyncedL2SlotNumber();
821
+ const { slot } = args;
822
+ if (syncedL2Slot === undefined || syncedL2Slot + 1 < slot) {
757
823
  this.log.debug(`Cannot propose block at next L2 slot ${slot} due to pending sync from L1`, {
758
824
  slot,
759
- ts,
760
- l1Timestamp
825
+ syncedL2Slot
761
826
  });
762
827
  return undefined;
763
828
  }
@@ -766,22 +831,30 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
766
831
  number: syncSummary.latestBlockNumber,
767
832
  hash: syncSummary.latestBlockHash
768
833
  })),
769
- this.l2BlockSource.getL2Tips().then((t)=>t.proposed),
834
+ this.l2BlockSource.getL2Tips().then((t)=>({
835
+ proposed: t.proposed,
836
+ checkpointed: t.checkpointed,
837
+ proposedCheckpoint: t.proposedCheckpoint
838
+ })),
770
839
  this.p2pClient.getStatus().then((p2p)=>p2p.syncedToL2Block),
771
- this.l1ToL2MessageSource.getL2Tips().then((t)=>t.proposed),
772
- this.l2BlockSource.getPendingChainValidationStatus()
840
+ this.l1ToL2MessageSource.getL2Tips().then((t)=>({
841
+ proposed: t.proposed,
842
+ checkpointed: t.checkpointed
843
+ })),
844
+ this.l2BlockSource.getPendingChainValidationStatus(),
845
+ this.l2BlockSource.getLastProposedCheckpoint()
773
846
  ]);
774
- const [worldState, l2BlockSource, p2p, l1ToL2MessageSource, pendingChainValidationStatus] = syncedBlocks;
847
+ const [worldState, l2Tips, p2p, l1ToL2MessageSourceTips, pendingChainValidationStatus, proposedCheckpointData] = syncedBlocks;
775
848
  // 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,
776
849
  // as the world state can compute the new genesis block hash, but other components use the hardcoded constant.
777
850
  // TODO(palla/mbps): Fix the above. All components should be able to handle dynamic genesis block hashes.
778
- const result = l2BlockSource.number === 0 && worldState.number === 0 && p2p.number === 0 && l1ToL2MessageSource.number === 0 || worldState.hash === l2BlockSource.hash && p2p.hash === l2BlockSource.hash && l1ToL2MessageSource.hash === l2BlockSource.hash;
851
+ const result = l2Tips.proposed.number === 0 && l2Tips.checkpointed.block.number === 0 && l2Tips.checkpointed.checkpoint.number === 0 && worldState.number === 0 && p2p.number === 0 && l1ToL2MessageSourceTips.proposed.number === 0 && l1ToL2MessageSourceTips.checkpointed.block.number === 0 && l1ToL2MessageSourceTips.checkpointed.checkpoint.number === 0 || worldState.hash === l2Tips.proposed.hash && p2p.hash === l2Tips.proposed.hash && l1ToL2MessageSourceTips.proposed.hash === l2Tips.proposed.hash && l1ToL2MessageSourceTips.checkpointed.block.hash === l2Tips.checkpointed.block.hash && l1ToL2MessageSourceTips.checkpointed.checkpoint.hash === l2Tips.checkpointed.checkpoint.hash;
779
852
  if (!result) {
780
853
  this.log.debug(`Sequencer sync check failed`, {
781
854
  worldState,
782
- l2BlockSource,
855
+ l2BlockSource: l2Tips.proposed,
783
856
  p2p,
784
- l1ToL2MessageSource
857
+ l1ToL2MessageSourceTips
785
858
  });
786
859
  return undefined;
787
860
  }
@@ -791,46 +864,52 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
791
864
  const archive = new Fr((await this.worldState.getCommitted().getTreeInfo(MerkleTreeId.ARCHIVE)).root);
792
865
  return {
793
866
  checkpointNumber: CheckpointNumber.ZERO,
867
+ checkpointedCheckpointNumber: CheckpointNumber.ZERO,
794
868
  blockNumber: BlockNumber.ZERO,
795
869
  archive,
796
- l1Timestamp,
870
+ hasProposedCheckpoint: false,
871
+ syncedL2Slot,
797
872
  pendingChainValidationStatus
798
873
  };
799
874
  }
800
- const block = await this.l2BlockSource.getL2Block(blockNumber);
801
- if (!block) {
875
+ const blockData = await this.l2BlockSource.getBlockData(blockNumber);
876
+ if (!blockData) {
802
877
  // this shouldn't really happen because a moment ago we checked that all components were in sync
803
- this.log.error(`Failed to get L2 block ${blockNumber} from the archiver with all components in sync`);
878
+ this.log.error(`Failed to get L2 block data ${blockNumber} from the archiver with all components in sync`);
804
879
  return undefined;
805
880
  }
881
+ const hasProposedCheckpoint = l2Tips.proposedCheckpoint.checkpoint.number > l2Tips.checkpointed.checkpoint.number;
806
882
  return {
807
- block,
808
- blockNumber: block.number,
809
- checkpointNumber: block.checkpointNumber,
810
- archive: block.archive.root,
811
- l1Timestamp,
883
+ blockData,
884
+ blockNumber: blockData.header.getBlockNumber(),
885
+ checkpointNumber: blockData.checkpointNumber,
886
+ checkpointedCheckpointNumber: l2Tips.checkpointed.checkpoint.number,
887
+ archive: blockData.archive.root,
888
+ hasProposedCheckpoint,
889
+ proposedCheckpointData,
890
+ syncedL2Slot,
812
891
  pendingChainValidationStatus
813
892
  };
814
893
  }
815
894
  /**
816
895
  * Checks if we are the proposer for the next slot.
817
896
  * @returns True if we can propose, and the proposer address (undefined if anyone can propose)
818
- */ async checkCanPropose(slot) {
897
+ */ async checkCanPropose(targetSlot) {
819
898
  let proposer;
820
899
  try {
821
- proposer = await this.epochCache.getProposerAttesterAddressInSlot(slot);
900
+ proposer = await this.epochCache.getProposerAttesterAddressInSlot(targetSlot);
822
901
  } catch (e) {
823
902
  if (e instanceof NoCommitteeError) {
824
- if (this.lastSlotForNoCommitteeWarning !== slot) {
825
- this.lastSlotForNoCommitteeWarning = slot;
826
- this.log.warn(`Cannot propose at next L2 slot ${slot} since the committee does not exist on L1`);
903
+ if (this.lastSlotForNoCommitteeWarning !== targetSlot) {
904
+ this.lastSlotForNoCommitteeWarning = targetSlot;
905
+ this.log.warn(`Cannot propose at target slot ${targetSlot} since the committee does not exist on L1`);
827
906
  }
828
907
  return [
829
908
  false,
830
909
  undefined
831
910
  ];
832
911
  }
833
- this.log.error(`Error getting proposer for slot ${slot}`, e);
912
+ this.log.error(`Error getting proposer for target slot ${targetSlot}`, e);
834
913
  return [
835
914
  false,
836
915
  undefined
@@ -853,7 +932,8 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
853
932
  const validatorAddresses = this.validatorClient.getValidatorAddresses();
854
933
  const weAreProposer = validatorAddresses.some((addr)=>addr.equals(proposer));
855
934
  if (!weAreProposer) {
856
- this.log.debug(`Cannot propose at slot ${slot} since we are not a proposer`, {
935
+ this.log.debug(`Cannot propose at target slot ${targetSlot} since we are not a proposer`, {
936
+ targetSlot,
857
937
  validatorAddresses,
858
938
  proposer
859
939
  });
@@ -862,6 +942,10 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
862
942
  proposer
863
943
  ];
864
944
  }
945
+ this.log.info(`We are the proposer for pipeline slot ${targetSlot}`, {
946
+ targetSlot,
947
+ proposer
948
+ });
865
949
  return [
866
950
  true,
867
951
  proposer
@@ -871,7 +955,7 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
871
955
  * Tries to vote on slashing actions and governance when the sync check fails but we're past the max time for initializing a proposal.
872
956
  * This allows the sequencer to participate in governance/slashing votes even when it cannot build blocks.
873
957
  */ async tryVoteWhenSyncFails(args) {
874
- const { slot } = args;
958
+ const { slot, targetSlot } = args;
875
959
  // Prevent duplicate attempts in the same slot
876
960
  if (this.lastSlotForFallbackVote === slot) {
877
961
  this.log.trace(`Already attempted to vote in slot ${slot} (skipping)`);
@@ -894,7 +978,7 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
894
978
  maxAllowedTime
895
979
  });
896
980
  // Check if we're a proposer or proposal is open
897
- const [canPropose, proposer] = await this.checkCanPropose(slot);
981
+ const [canPropose, proposer] = await this.checkCanPropose(targetSlot);
898
982
  if (!canPropose) {
899
983
  this.log.trace(`Cannot vote in slot ${slot} since we are not a proposer`, {
900
984
  slot,
@@ -910,8 +994,8 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
910
994
  attestorAddress,
911
995
  slot
912
996
  });
913
- // Enqueue governance and slashing votes
914
- const voter = new CheckpointVoter(slot, publisher, attestorAddress, this.validatorClient, this.slasherClient, this.l1Constants, this.config, this.metrics, this.log);
997
+ // Enqueue governance and slashing votes (voter uses the target slot for L1 submission)
998
+ const voter = new CheckpointVoter(targetSlot, publisher, attestorAddress, this.validatorClient, this.slasherClient, this.l1Constants, this.config, this.metrics, this.log);
915
999
  const votesPromises = voter.enqueueVotes();
916
1000
  const votes = await Promise.all(votesPromises);
917
1001
  if (votes.every((p)=>!p)) {
@@ -958,7 +1042,7 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
958
1042
  * have the proposer try to invalidate, but if they fail, the sequencers in the committee are expected to try,
959
1043
  * and if they fail, any sequencer will try as well.
960
1044
  */ async considerInvalidatingCheckpoint(syncedTo, currentSlot) {
961
- const { pendingChainValidationStatus, l1Timestamp } = syncedTo;
1045
+ const { pendingChainValidationStatus, syncedL2Slot } = syncedTo;
962
1046
  if (pendingChainValidationStatus.valid) {
963
1047
  return;
964
1048
  }
@@ -969,7 +1053,7 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
969
1053
  const { secondsBeforeInvalidatingBlockAsCommitteeMember, secondsBeforeInvalidatingBlockAsNonCommitteeMember } = this.config;
970
1054
  const logData = {
971
1055
  invalidL1Timestamp: invalidCheckpointTimestamp,
972
- l1Timestamp,
1056
+ syncedL2Slot,
973
1057
  invalidCheckpoint: pendingChainValidationStatus.checkpoint,
974
1058
  secondsBeforeInvalidatingBlockAsCommitteeMember,
975
1059
  secondsBeforeInvalidatingBlockAsNonCommitteeMember,
@@ -1060,6 +1144,9 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
1060
1144
  getValidatorAddresses() {
1061
1145
  return this.validatorClient?.getValidatorAddresses();
1062
1146
  }
1147
+ /** Updates the publisher factory's node keystore adapter after a keystore reload. */ updatePublisherNodeKeyStore(adapter) {
1148
+ this.publisherFactory.updateNodeKeyStore(adapter);
1149
+ }
1063
1150
  getConfig() {
1064
1151
  return this.config;
1065
1152
  }