@aztec/sequencer-client 0.0.1-commit.5914bae → 0.0.1-commit.59a0419c6

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 (89) hide show
  1. package/dest/client/sequencer-client.d.ts +2 -1
  2. package/dest/client/sequencer-client.d.ts.map +1 -1
  3. package/dest/client/sequencer-client.js +8 -8
  4. package/dest/config.d.ts +3 -3
  5. package/dest/config.d.ts.map +1 -1
  6. package/dest/config.js +10 -12
  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 +3 -8
  14. package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
  15. package/dest/global_variable_builder/global_builder.js +4 -39
  16. package/dest/global_variable_builder/index.d.ts +3 -1
  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 +17 -1
  20. package/dest/publisher/config.d.ts.map +1 -1
  21. package/dest/publisher/config.js +25 -5
  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 +3 -5
  37. package/dest/publisher/sequencer-publisher-factory.d.ts.map +1 -1
  38. package/dest/publisher/sequencer-publisher-factory.js +16 -3
  39. package/dest/publisher/sequencer-publisher.d.ts +52 -41
  40. package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
  41. package/dest/publisher/sequencer-publisher.js +345 -101
  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 +38 -8
  46. package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -1
  47. package/dest/sequencer/checkpoint_proposal_job.js +427 -171
  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/events.d.ts +7 -1
  52. package/dest/sequencer/events.d.ts.map +1 -1
  53. package/dest/sequencer/metrics.d.ts +11 -10
  54. package/dest/sequencer/metrics.d.ts.map +1 -1
  55. package/dest/sequencer/metrics.js +38 -20
  56. package/dest/sequencer/sequencer.d.ts +17 -5
  57. package/dest/sequencer/sequencer.d.ts.map +1 -1
  58. package/dest/sequencer/sequencer.js +120 -58
  59. package/dest/sequencer/timetable.d.ts +14 -1
  60. package/dest/sequencer/timetable.d.ts.map +1 -1
  61. package/dest/sequencer/timetable.js +45 -36
  62. package/dest/test/mock_checkpoint_builder.d.ts +1 -1
  63. package/dest/test/mock_checkpoint_builder.d.ts.map +1 -1
  64. package/dest/test/mock_checkpoint_builder.js +0 -2
  65. package/package.json +27 -28
  66. package/src/client/sequencer-client.ts +8 -14
  67. package/src/config.ts +12 -11
  68. package/src/global_variable_builder/README.md +44 -0
  69. package/src/global_variable_builder/fee_predictor.ts +172 -0
  70. package/src/global_variable_builder/fee_provider.ts +75 -0
  71. package/src/global_variable_builder/global_builder.ts +8 -43
  72. package/src/global_variable_builder/index.ts +2 -0
  73. package/src/publisher/config.ts +47 -4
  74. package/src/publisher/index.ts +3 -0
  75. package/src/publisher/l1_tx_failed_store/factory.ts +32 -0
  76. package/src/publisher/l1_tx_failed_store/failed_tx_store.ts +57 -0
  77. package/src/publisher/l1_tx_failed_store/file_store_failed_tx_store.ts +46 -0
  78. package/src/publisher/l1_tx_failed_store/index.ts +3 -0
  79. package/src/publisher/sequencer-publisher-factory.ts +18 -6
  80. package/src/publisher/sequencer-publisher.ts +389 -140
  81. package/src/sequencer/README.md +83 -13
  82. package/src/sequencer/chain_state_overrides.ts +87 -0
  83. package/src/sequencer/checkpoint_proposal_job.ts +517 -180
  84. package/src/sequencer/checkpoint_proposal_job_metrics.ts +128 -0
  85. package/src/sequencer/events.ts +6 -1
  86. package/src/sequencer/metrics.ts +49 -25
  87. package/src/sequencer/sequencer.ts +177 -63
  88. package/src/sequencer/timetable.ts +57 -45
  89. package/src/test/mock_checkpoint_builder.ts +0 -2
@@ -373,7 +373,7 @@ 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';
@@ -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';
@@ -449,11 +450,13 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
449
450
  runningPromise;
450
451
  state;
451
452
  metrics;
453
+ checkpointProposalJobMetrics;
452
454
  /** The last slot for which we attempted to perform our voting duties with degraded block production */ lastSlotForFallbackVote;
453
455
  /** The last slot for which we logged "no committee" warning, to avoid spam */ lastSlotForNoCommitteeWarning;
454
456
  /** The last slot for which we triggered a checkpoint proposal job, to prevent duplicate attempts. */ lastSlotForCheckpointProposalJob;
455
457
  /** Last successful checkpoint proposed */ lastCheckpointProposed;
456
458
  /** The last epoch for which we logged strategy comparison in fisherman mode. */ lastEpochForStrategyComparison;
459
+ /** The last checkpoint proposal job, tracked so we can await its pending L1 submission during shutdown. */ lastCheckpointProposalJob;
457
460
  /** The maximum number of seconds that the sequencer can be into a slot to transition to a particular state. */ timetable;
458
461
  /** Config for the sequencer */ config;
459
462
  constructor(publisherFactory, validatorClient, globalsBuilder, p2pClient, worldState, slasherClient, l2BlockSource, l1ToL2MessageSource, checkpointsBuilder, l1Constants, dateProvider, epochCache, rollupContract, config, telemetry = getTelemetryClient(), log = createLogger('sequencer')){
@@ -463,19 +466,22 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
463
466
  this.log = log.createChild('[FISHERMAN]');
464
467
  }
465
468
  this.metrics = new SequencerMetrics(telemetry, this.rollupContract, 'Sequencer');
469
+ this.checkpointProposalJobMetrics = new CheckpointProposalJobMetrics(telemetry);
466
470
  this.updateConfig(config);
467
471
  }
468
472
  /** Updates sequencer config by the defined values and updates the timetable */ updateConfig(config) {
469
473
  const filteredConfig = pickFromSchema(config, SequencerConfigSchema);
470
474
  this.log.info(`Updated sequencer config`, omit(filteredConfig, 'txPublicSetupAllowListExtend'));
471
475
  this.config = merge(this.config, filteredConfig);
476
+ const p2pPropagationTime = this.config.attestationPropagationTime;
472
477
  this.timetable = new SequencerTimetable({
473
478
  ethereumSlotDuration: this.l1Constants.ethereumSlotDuration,
474
479
  aztecSlotDuration: this.aztecSlotDuration,
475
480
  l1PublishingTime: this.l1PublishingTime,
476
- p2pPropagationTime: this.config.attestationPropagationTime,
481
+ p2pPropagationTime,
477
482
  blockDurationMs: this.config.blockDurationMs,
478
- enforce: this.config.enforceTimeTable
483
+ enforce: this.config.enforceTimeTable,
484
+ pipelining: this.epochCache.isProposerPipeliningEnabled()
479
485
  }, this.metrics, this.log);
480
486
  }
481
487
  /** Initializes the sequencer (precomputes tables). Takes about 3s. */ init() {
@@ -497,8 +503,9 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
497
503
  this.setState(SequencerState.STOPPING, undefined, {
498
504
  force: true
499
505
  });
500
- this.publisherFactory.interruptAll();
506
+ await this.publisherFactory.stopAll();
501
507
  await this.runningPromise?.stop();
508
+ await this.lastCheckpointProposalJob?.awaitPendingSubmission();
502
509
  this.setState(SequencerState.STOPPED, undefined, {
503
510
  force: true
504
511
  });
@@ -542,22 +549,25 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
542
549
  * - Submit checkpoint
543
550
  */ async work() {
544
551
  this.setState(SequencerState.SYNCHRONIZING, undefined);
545
- const { slot, ts, now, epoch } = this.epochCache.getEpochAndSlotInNextL1Slot();
552
+ const { slot, ts, nowSeconds, epoch } = this.epochCache.getEpochAndSlotInNextL1Slot();
553
+ const { slot: targetSlot, epoch: targetEpoch } = this.epochCache.getTargetEpochAndSlotInNextL1Slot();
546
554
  // Check if we are synced and it's our slot, grab a publisher, check previous block invalidation, etc
547
- const checkpointProposalJob = await this.prepareCheckpointProposal(epoch, slot, ts, now);
555
+ const checkpointProposalJob = await this.prepareCheckpointProposal(slot, targetSlot, epoch, targetEpoch, ts, nowSeconds);
548
556
  if (!checkpointProposalJob) {
549
557
  return;
550
558
  }
559
+ // Track the job so we can await its pending L1 submission during shutdown
560
+ this.lastCheckpointProposalJob = checkpointProposalJob;
551
561
  // Execute the checkpoint proposal job
552
562
  const checkpoint = await checkpointProposalJob.execute();
553
563
  // Update last checkpoint proposed (currently unused)
554
564
  if (checkpoint) {
555
565
  this.lastCheckpointProposed = checkpoint;
556
566
  }
557
- // Log fee strategy comparison if on fisherman
558
- if (this.config.fishermanMode && (this.lastEpochForStrategyComparison === undefined || epoch > this.lastEpochForStrategyComparison)) {
559
- this.logStrategyComparison(epoch, checkpointProposalJob.getPublisher());
560
- this.lastEpochForStrategyComparison = epoch;
567
+ // Log fee strategy comparison if on fisherman (uses target epoch since we mirror the proposer's perspective)
568
+ if (this.config.fishermanMode && (this.lastEpochForStrategyComparison === undefined || targetEpoch > this.lastEpochForStrategyComparison)) {
569
+ this.logStrategyComparison(targetEpoch, checkpointProposalJob.getPublisher());
570
+ this.lastEpochForStrategyComparison = targetEpoch;
561
571
  }
562
572
  return checkpoint;
563
573
  }
@@ -565,17 +575,17 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
565
575
  * Prepares the checkpoint proposal by performing all necessary checks and setup.
566
576
  * This is the initial step in the main loop.
567
577
  * @returns CheckpointProposalJob if successful, undefined if we are not yet synced or are not the proposer.
568
- */ async prepareCheckpointProposal(epoch, slot, ts, now) {
569
- // Check we have not already processed this slot (cheapest check)
578
+ */ async prepareCheckpointProposal(slot, targetSlot, epoch, targetEpoch, ts, nowSeconds) {
579
+ // Check we have not already processed this target slot (cheapest check)
570
580
  // We only check this if enforce timetable is set, since we want to keep processing the same slot if we are not
571
581
  // running against actual time (eg when we use sandbox-style automining)
572
- if (this.lastSlotForCheckpointProposalJob && this.lastSlotForCheckpointProposalJob >= slot && this.config.enforceTimeTable) {
573
- this.log.trace(`Slot ${slot} has already been processed`);
582
+ if (this.lastSlotForCheckpointProposalJob && this.lastSlotForCheckpointProposalJob >= targetSlot && this.config.enforceTimeTable) {
583
+ this.log.trace(`Target slot ${targetSlot} has already been processed`);
574
584
  return undefined;
575
585
  }
576
- // But if we have already proposed for this slot, the we definitely have to skip it, automining or not
577
- if (this.lastCheckpointProposed && this.lastCheckpointProposed.header.slotNumber >= slot) {
578
- this.log.trace(`Slot ${slot} has already been published as checkpoint ${this.lastCheckpointProposed.number}`);
586
+ // But if we have already proposed for this slot, then we definitely have to skip it, automining or not
587
+ if (this.lastCheckpointProposed && this.lastCheckpointProposed.header.slotNumber >= targetSlot) {
588
+ this.log.trace(`Slot ${targetSlot} has already been published as checkpoint ${this.lastCheckpointProposed.number}`);
579
589
  return undefined;
580
590
  }
581
591
  // Check all components are synced to latest as seen by the archiver (queries all subsystems)
@@ -586,16 +596,18 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
586
596
  if (!syncedTo) {
587
597
  await this.tryVoteWhenSyncFails({
588
598
  slot,
599
+ targetSlot,
589
600
  ts
590
601
  });
591
602
  return undefined;
592
603
  }
593
- // If escape hatch is open for this epoch, do not start checkpoint proposal work and do not attempt invalidations.
604
+ // If escape hatch is open for the target epoch, do not start checkpoint proposal work and do not attempt invalidations.
594
605
  // Still perform governance/slashing voting (as proposer) once per slot.
595
- const isEscapeHatchOpen = await this.epochCache.isEscapeHatchOpen(epoch);
606
+ // When pipelining, we check the target epoch (slot+1's epoch) since that's the epoch we're building for.
607
+ const isEscapeHatchOpen = await this.epochCache.isEscapeHatchOpen(targetEpoch);
596
608
  if (isEscapeHatchOpen) {
597
609
  this.setState(SequencerState.PROPOSER_CHECK, slot);
598
- const [canPropose, proposer] = await this.checkCanPropose(slot);
610
+ const [canPropose, proposer] = await this.checkCanPropose(targetSlot);
599
611
  if (canPropose) {
600
612
  await this.tryVoteWhenEscapeHatchOpen({
601
613
  slot,
@@ -613,24 +625,32 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
613
625
  // Next checkpoint follows from the last synced one
614
626
  const checkpointNumber = CheckpointNumber(syncedTo.checkpointNumber + 1);
615
627
  const logCtx = {
616
- now,
628
+ nowSeconds,
617
629
  syncedToL2Slot: syncedTo.syncedL2Slot,
618
630
  slot,
631
+ targetSlot,
619
632
  slotTs: ts,
620
633
  checkpointNumber,
621
634
  isPendingChainValid: pick(syncedTo.pendingChainValidationStatus, 'valid', 'reason', 'invalidIndex')
622
635
  };
623
- // Check that we are a proposer for the next slot
636
+ // Check that we are a proposer for the target slot.
624
637
  this.setState(SequencerState.PROPOSER_CHECK, slot);
625
- const [canPropose, proposer] = await this.checkCanPropose(slot);
638
+ const [canPropose, proposer] = await this.checkCanPropose(targetSlot);
626
639
  // If we are not a proposer check if we should invalidate an invalid checkpoint, and bail
627
640
  if (!canPropose) {
628
641
  await this.considerInvalidatingCheckpoint(syncedTo, slot);
629
642
  return undefined;
630
643
  }
631
- // Check that the slot is not taken by a block already (should never happen, since only us can propose for this slot)
632
- if (syncedTo.blockData && syncedTo.blockData.header.getSlot() >= slot) {
633
- this.log.warn(`Cannot propose block at next L2 slot ${slot} since that slot was taken by block ${syncedTo.blockNumber}`, {
644
+ // Guard: don't exceed 1-deep pipeline. Without a proposed checkpoint, we can only build
645
+ // confirmed + 1. With a proposed checkpoint, we can build confirmed + 2.
646
+ const confirmedCkpt = syncedTo.checkpointedCheckpointNumber;
647
+ if (checkpointNumber > confirmedCkpt + 2) {
648
+ this.log.verbose(`Skipping slot ${targetSlot}: checkpoint ${checkpointNumber} exceeds max pipeline depth (confirmed=${confirmedCkpt})`);
649
+ return undefined;
650
+ }
651
+ // Check that the target slot is not taken by a block already (should never happen, since only us can propose for this slot)
652
+ if (syncedTo.blockData && syncedTo.blockData.header.getSlot() >= targetSlot) {
653
+ this.log.warn(`Cannot propose block at target slot ${targetSlot} since that slot was taken by block ${syncedTo.blockNumber}`, {
634
654
  ...logCtx,
635
655
  block: syncedTo.blockData.header.toInspect()
636
656
  });
@@ -650,10 +670,32 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
650
670
  this.log.debug(`Set proposer address ${proposer} for simulation in fisherman mode`);
651
671
  }
652
672
  // Prepare invalidation request if the pending chain is invalid (returns undefined if no need)
653
- const invalidateCheckpoint = await publisher.simulateInvalidateCheckpoint(syncedTo.pendingChainValidationStatus);
654
- // Check with the rollup contract if we can indeed propose at the next L2 slot. This check should not fail
655
- // if all the previous checks are good, but we do it just in case.
656
- const canProposeCheck = await publisher.canProposeAt(syncedTo.archive, proposer ?? EthAddress.ZERO, invalidateCheckpoint);
673
+ let invalidateCheckpoint = await publisher.simulateInvalidateCheckpoint(syncedTo.pendingChainValidationStatus);
674
+ // Determine the correct archive and L1 state overrides for the canProposeAt check.
675
+ // The L1 contract reads archives[proposedCheckpointNumber] and compares it with the provided archive.
676
+ // When invalidating or pipelining, the local archive may differ from L1's, so we adjust accordingly.
677
+ let archiveForCheck = syncedTo.archive;
678
+ const l1SimulationOverridesBuilder = new SimulationOverridesBuilder();
679
+ if (this.epochCache.isProposerPipeliningEnabled() && syncedTo.hasProposedCheckpoint) {
680
+ // Parent checkpoint hasn't landed on L1 yet. Override both the proposed checkpoint number
681
+ // and the archive at that checkpoint so L1 simulation sees the correct chain tip.
682
+ const parentCheckpointNumber = CheckpointNumber(checkpointNumber - 1);
683
+ l1SimulationOverridesBuilder.forPendingCheckpoint(parentCheckpointNumber).withPendingArchive(syncedTo.archive);
684
+ this.metrics.recordPipelineDepth(1);
685
+ this.log.verbose(`Building on top of proposed checkpoint (pending=${syncedTo.proposedCheckpointData?.checkpointNumber})`);
686
+ // Clear the invalidation - the proposed checkpoint should handle it.
687
+ invalidateCheckpoint = undefined;
688
+ } else if (invalidateCheckpoint) {
689
+ // After invalidation, L1 will roll back to checkpoint N-1. The archive at N-1 already
690
+ // exists on L1, so we just pass the matching archive (the lastArchive of the invalid checkpoint).
691
+ archiveForCheck = invalidateCheckpoint.lastArchive;
692
+ l1SimulationOverridesBuilder.forPendingCheckpoint(invalidateCheckpoint.forcePendingCheckpointNumber);
693
+ this.metrics.recordPipelineDepth(0);
694
+ } else {
695
+ this.metrics.recordPipelineDepth(0);
696
+ }
697
+ const simulationOverridesPlan = l1SimulationOverridesBuilder.build();
698
+ const canProposeCheck = await publisher.canProposeAt(archiveForCheck, proposer ?? EthAddress.ZERO, simulationOverridesPlan);
657
699
  if (canProposeCheck === undefined) {
658
700
  this.log.warn(`Cannot propose checkpoint ${checkpointNumber} at slot ${slot} due to failed rollup contract check`, logCtx);
659
701
  this.emit('proposer-rollup-check-failed', {
@@ -663,11 +705,11 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
663
705
  this.metrics.recordCheckpointPrecheckFailed('rollup_contract_check_failed');
664
706
  return undefined;
665
707
  }
666
- if (canProposeCheck.slot !== slot) {
667
- 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}.`, {
708
+ if (canProposeCheck.slot !== targetSlot) {
709
+ 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}.`, {
668
710
  ...logCtx,
669
711
  rollup: canProposeCheck,
670
- expectedSlot: slot
712
+ expectedSlot: targetSlot
671
713
  });
672
714
  this.emit('proposer-rollup-check-failed', {
673
715
  reason: 'Slot mismatch',
@@ -689,17 +731,18 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
689
731
  this.metrics.recordCheckpointPrecheckFailed('block_number_mismatch');
690
732
  return undefined;
691
733
  }
692
- this.lastSlotForCheckpointProposalJob = slot;
693
- await this.p2pClient.prepareForSlot(slot);
694
- this.log.info(`Preparing checkpoint proposal ${checkpointNumber} at slot ${slot}`, {
734
+ this.lastSlotForCheckpointProposalJob = targetSlot;
735
+ await this.p2pClient.prepareForSlot(targetSlot);
736
+ this.log.info(`Preparing checkpoint proposal ${checkpointNumber} for target slot ${targetSlot} during wall-clock slot ${slot}`, {
695
737
  ...logCtx,
696
- proposer
738
+ proposer,
739
+ pipeliningEnabled: this.epochCache.isProposerPipeliningEnabled()
697
740
  });
698
741
  // Create and return the checkpoint proposal job
699
- return this.createCheckpointProposalJob(epoch, slot, checkpointNumber, syncedTo.blockNumber, proposer, publisher, attestorAddress, invalidateCheckpoint);
742
+ return this.createCheckpointProposalJob(slot, targetSlot, targetEpoch, checkpointNumber, syncedTo.blockNumber, proposer, publisher, attestorAddress, invalidateCheckpoint, syncedTo.proposedCheckpointData);
700
743
  }
701
- createCheckpointProposalJob(epoch, slot, checkpointNumber, syncedToBlockNumber, proposer, publisher, attestorAddress, invalidateCheckpoint) {
702
- 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());
744
+ createCheckpointProposalJob(slot, targetSlot, targetEpoch, checkpointNumber, syncedToBlockNumber, proposer, publisher, attestorAddress, invalidateCheckpoint, proposedCheckpointData) {
745
+ 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.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);
703
746
  }
704
747
  /**
705
748
  * Returns the current sequencer state.
@@ -763,22 +806,30 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
763
806
  number: syncSummary.latestBlockNumber,
764
807
  hash: syncSummary.latestBlockHash
765
808
  })),
766
- this.l2BlockSource.getL2Tips().then((t)=>t.proposed),
809
+ this.l2BlockSource.getL2Tips().then((t)=>({
810
+ proposed: t.proposed,
811
+ checkpointed: t.checkpointed,
812
+ proposedCheckpoint: t.proposedCheckpoint
813
+ })),
767
814
  this.p2pClient.getStatus().then((p2p)=>p2p.syncedToL2Block),
768
- this.l1ToL2MessageSource.getL2Tips().then((t)=>t.proposed),
769
- this.l2BlockSource.getPendingChainValidationStatus()
815
+ this.l1ToL2MessageSource.getL2Tips().then((t)=>({
816
+ proposed: t.proposed,
817
+ checkpointed: t.checkpointed
818
+ })),
819
+ this.l2BlockSource.getPendingChainValidationStatus(),
820
+ this.l2BlockSource.getProposedCheckpointOnly()
770
821
  ]);
771
- const [worldState, l2BlockSource, p2p, l1ToL2MessageSource, pendingChainValidationStatus] = syncedBlocks;
822
+ const [worldState, l2Tips, p2p, l1ToL2MessageSourceTips, pendingChainValidationStatus, proposedCheckpointData] = syncedBlocks;
772
823
  // 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,
773
824
  // as the world state can compute the new genesis block hash, but other components use the hardcoded constant.
774
825
  // TODO(palla/mbps): Fix the above. All components should be able to handle dynamic genesis block hashes.
775
- 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;
826
+ 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;
776
827
  if (!result) {
777
828
  this.log.debug(`Sequencer sync check failed`, {
778
829
  worldState,
779
- l2BlockSource,
830
+ l2BlockSource: l2Tips.proposed,
780
831
  p2p,
781
- l1ToL2MessageSource
832
+ l1ToL2MessageSourceTips
782
833
  });
783
834
  return undefined;
784
835
  }
@@ -788,8 +839,10 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
788
839
  const archive = new Fr((await this.worldState.getCommitted().getTreeInfo(MerkleTreeId.ARCHIVE)).root);
789
840
  return {
790
841
  checkpointNumber: CheckpointNumber.ZERO,
842
+ checkpointedCheckpointNumber: CheckpointNumber.ZERO,
791
843
  blockNumber: BlockNumber.ZERO,
792
844
  archive,
845
+ hasProposedCheckpoint: false,
793
846
  syncedL2Slot,
794
847
  pendingChainValidationStatus
795
848
  };
@@ -800,11 +853,15 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
800
853
  this.log.error(`Failed to get L2 block data ${blockNumber} from the archiver with all components in sync`);
801
854
  return undefined;
802
855
  }
856
+ const hasProposedCheckpoint = l2Tips.proposedCheckpoint.checkpoint.number > l2Tips.checkpointed.checkpoint.number;
803
857
  return {
804
858
  blockData,
805
859
  blockNumber: blockData.header.getBlockNumber(),
806
860
  checkpointNumber: blockData.checkpointNumber,
861
+ checkpointedCheckpointNumber: l2Tips.checkpointed.checkpoint.number,
807
862
  archive: blockData.archive.root,
863
+ hasProposedCheckpoint,
864
+ proposedCheckpointData,
808
865
  syncedL2Slot,
809
866
  pendingChainValidationStatus
810
867
  };
@@ -812,22 +869,22 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
812
869
  /**
813
870
  * Checks if we are the proposer for the next slot.
814
871
  * @returns True if we can propose, and the proposer address (undefined if anyone can propose)
815
- */ async checkCanPropose(slot) {
872
+ */ async checkCanPropose(targetSlot) {
816
873
  let proposer;
817
874
  try {
818
- proposer = await this.epochCache.getProposerAttesterAddressInSlot(slot);
875
+ proposer = await this.epochCache.getProposerAttesterAddressInSlot(targetSlot);
819
876
  } catch (e) {
820
877
  if (e instanceof NoCommitteeError) {
821
- if (this.lastSlotForNoCommitteeWarning !== slot) {
822
- this.lastSlotForNoCommitteeWarning = slot;
823
- this.log.warn(`Cannot propose at next L2 slot ${slot} since the committee does not exist on L1`);
878
+ if (this.lastSlotForNoCommitteeWarning !== targetSlot) {
879
+ this.lastSlotForNoCommitteeWarning = targetSlot;
880
+ this.log.warn(`Cannot propose at target slot ${targetSlot} since the committee does not exist on L1`);
824
881
  }
825
882
  return [
826
883
  false,
827
884
  undefined
828
885
  ];
829
886
  }
830
- this.log.error(`Error getting proposer for slot ${slot}`, e);
887
+ this.log.error(`Error getting proposer for target slot ${targetSlot}`, e);
831
888
  return [
832
889
  false,
833
890
  undefined
@@ -850,7 +907,8 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
850
907
  const validatorAddresses = this.validatorClient.getValidatorAddresses();
851
908
  const weAreProposer = validatorAddresses.some((addr)=>addr.equals(proposer));
852
909
  if (!weAreProposer) {
853
- this.log.debug(`Cannot propose at slot ${slot} since we are not a proposer`, {
910
+ this.log.debug(`Cannot propose at target slot ${targetSlot} since we are not a proposer`, {
911
+ targetSlot,
854
912
  validatorAddresses,
855
913
  proposer
856
914
  });
@@ -859,6 +917,10 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
859
917
  proposer
860
918
  ];
861
919
  }
920
+ this.log.info(`We are the proposer for pipeline slot ${targetSlot}`, {
921
+ targetSlot,
922
+ proposer
923
+ });
862
924
  return [
863
925
  true,
864
926
  proposer
@@ -868,7 +930,7 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
868
930
  * Tries to vote on slashing actions and governance when the sync check fails but we're past the max time for initializing a proposal.
869
931
  * This allows the sequencer to participate in governance/slashing votes even when it cannot build blocks.
870
932
  */ async tryVoteWhenSyncFails(args) {
871
- const { slot } = args;
933
+ const { slot, targetSlot } = args;
872
934
  // Prevent duplicate attempts in the same slot
873
935
  if (this.lastSlotForFallbackVote === slot) {
874
936
  this.log.trace(`Already attempted to vote in slot ${slot} (skipping)`);
@@ -891,7 +953,7 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
891
953
  maxAllowedTime
892
954
  });
893
955
  // Check if we're a proposer or proposal is open
894
- const [canPropose, proposer] = await this.checkCanPropose(slot);
956
+ const [canPropose, proposer] = await this.checkCanPropose(targetSlot);
895
957
  if (!canPropose) {
896
958
  this.log.trace(`Cannot vote in slot ${slot} since we are not a proposer`, {
897
959
  slot,
@@ -907,8 +969,8 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
907
969
  attestorAddress,
908
970
  slot
909
971
  });
910
- // Enqueue governance and slashing votes
911
- const voter = new CheckpointVoter(slot, publisher, attestorAddress, this.validatorClient, this.slasherClient, this.l1Constants, this.config, this.metrics, this.log);
972
+ // Enqueue governance and slashing votes (voter uses the target slot for L1 submission)
973
+ const voter = new CheckpointVoter(targetSlot, publisher, attestorAddress, this.validatorClient, this.slasherClient, this.l1Constants, this.config, this.metrics, this.log);
912
974
  const votesPromises = voter.enqueueVotes();
913
975
  const votes = await Promise.all(votesPromises);
914
976
  if (votes.every((p)=>!p)) {
@@ -4,6 +4,7 @@ import { SequencerState } from './utils.js';
4
4
  export declare class SequencerTimetable {
5
5
  private readonly metrics?;
6
6
  private readonly log?;
7
+ private readonly checkpointTiming;
7
8
  /**
8
9
  * How late into the slot can we be to start working. Computed as the total time needed for assembling and publishing a block,
9
10
  * assuming an execution time equal to `minExecutionTime`, subtracted from the slot duration. This means that, if the proposer
@@ -50,6 +51,13 @@ export declare class SequencerTimetable {
50
51
  readonly blockDuration: number | undefined;
51
52
  /** Maximum number of blocks that can be built in this slot configuration */
52
53
  readonly maxNumberOfBlocks: number;
54
+ /** Whether pipelining is enabled (checkpoint finalization deferred to next slot). */
55
+ readonly pipelining: boolean;
56
+ /**
57
+ * How far into the target slot attestation collection can extend when pipelining.
58
+ * Covers validator re-execution (one block duration) plus one-way attestation return.
59
+ */
60
+ readonly pipeliningAttestationGracePeriod: number;
53
61
  constructor(opts: {
54
62
  ethereumSlotDuration: number;
55
63
  aztecSlotDuration: number;
@@ -57,7 +65,12 @@ export declare class SequencerTimetable {
57
65
  p2pPropagationTime?: number;
58
66
  blockDurationMs?: number;
59
67
  enforce: boolean;
68
+ pipelining?: boolean;
60
69
  }, metrics?: SequencerMetrics | undefined, log?: Logger | undefined);
70
+ getCheckpointAssemblyDeadline(): number;
71
+ getCheckpointAttestationDeadline(): number;
72
+ getCheckpointAttestationStartDeadline(): number;
73
+ getCheckpointPublishingDeadline(): number;
61
74
  getMaxAllowedTime(state: Extract<SequencerState, SequencerState.STOPPED | SequencerState.IDLE | SequencerState.SYNCHRONIZING>): undefined;
62
75
  getMaxAllowedTime(state: Exclude<SequencerState, SequencerState.STOPPED | SequencerState.IDLE | SequencerState.SYNCHRONIZING>): number;
63
76
  getMaxAllowedTime(state: SequencerState): number | undefined;
@@ -85,4 +98,4 @@ export declare class SequencerTimetable {
85
98
  isLastBlock: boolean;
86
99
  };
87
100
  }
88
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidGltZXRhYmxlLmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvc2VxdWVuY2VyL3RpbWV0YWJsZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssRUFBRSxNQUFNLEVBQUUsTUFBTSx1QkFBdUIsQ0FBQztBQVNwRCxPQUFPLEtBQUssRUFBRSxnQkFBZ0IsRUFBRSxNQUFNLGNBQWMsQ0FBQztBQUNyRCxPQUFPLEVBQUUsY0FBYyxFQUFFLE1BQU0sWUFBWSxDQUFDO0FBRTVDLHFCQUFhLGtCQUFrQjtJQXFFM0IsT0FBTyxDQUFDLFFBQVEsQ0FBQyxPQUFPLENBQUM7SUFDekIsT0FBTyxDQUFDLFFBQVEsQ0FBQyxHQUFHLENBQUM7SUFyRXZCOzs7O09BSUc7SUFDSCxTQUFnQixrQkFBa0IsRUFBRSxNQUFNLENBQUM7SUFFM0M7Ozs7T0FJRztJQUNILFNBQWdCLG9CQUFvQixFQUFFLE1BQU0sQ0FBQztJQUU3Qzs7Ozs7T0FLRztJQUNILFNBQWdCLDBCQUEwQixFQUFFLE1BQU0sQ0FBQztJQUVuRDs7OztPQUlHO0lBQ0gsU0FBZ0IsZ0JBQWdCLEVBQUUsTUFBTSxDQUFDO0lBRXpDOzs7T0FHRztJQUNILFNBQWdCLGdCQUFnQixFQUFFLE1BQU0sQ0FBc0I7SUFFOUQsdURBQXVEO0lBQ3ZELFNBQWdCLDRCQUE0QixFQUFFLE1BQU0sQ0FBa0M7SUFFdEYsbUdBQW1HO0lBQ25HLFNBQWdCLGtCQUFrQixFQUFFLE1BQU0sQ0FBQztJQUUzQyxtRkFBbUY7SUFDbkYsU0FBZ0Isc0JBQXNCLEVBQUUsTUFBTSxDQUE0QjtJQUUxRSx3Q0FBd0M7SUFDeEMsU0FBZ0Isb0JBQW9CLEVBQUUsTUFBTSxDQUFDO0lBRTdDLGtGQUFrRjtJQUNsRixTQUFnQixpQkFBaUIsRUFBRSxNQUFNLENBQUM7SUFFMUMsNERBQTREO0lBQzVELFNBQWdCLE9BQU8sRUFBRSxPQUFPLENBQUM7SUFFakMsb0dBQW9HO0lBQ3BHLFNBQWdCLGFBQWEsRUFBRSxNQUFNLEdBQUcsU0FBUyxDQUFDO0lBRWxELDRFQUE0RTtJQUM1RSxTQUFnQixpQkFBaUIsRUFBRSxNQUFNLENBQUM7SUFFMUMsWUFDRSxJQUFJLEVBQUU7UUFDSixvQkFBb0IsRUFBRSxNQUFNLENBQUM7UUFDN0IsaUJBQWlCLEVBQUUsTUFBTSxDQUFDO1FBQzFCLGdCQUFnQixFQUFFLE1BQU0sQ0FBQztRQUN6QixrQkFBa0IsQ0FBQyxFQUFFLE1BQU0sQ0FBQztRQUM1QixlQUFlLENBQUMsRUFBRSxNQUFNLENBQUM7UUFDekIsT0FBTyxFQUFFLE9BQU8sQ0FBQztLQUNsQixFQUNnQixPQUFPLENBQUMsOEJBQWtCLEVBQzFCLEdBQUcsQ0FBQyxvQkFBUSxFQTJFOUI7SUFFTSxpQkFBaUIsQ0FDdEIsS0FBSyxFQUFFLE9BQU8sQ0FBQyxjQUFjLEVBQUUsY0FBYyxDQUFDLE9BQU8sR0FBRyxjQUFjLENBQUMsSUFBSSxHQUFHLGNBQWMsQ0FBQyxhQUFhLENBQUMsR0FDMUcsU0FBUyxDQUFDO0lBQ04saUJBQWlCLENBQ3RCLEtBQUssRUFBRSxPQUFPLENBQUMsY0FBYyxFQUFFLGNBQWMsQ0FBQyxPQUFPLEdBQUcsY0FBYyxDQUFDLElBQUksR0FBRyxjQUFjLENBQUMsYUFBYSxDQUFDLEdBQzFHLE1BQU0sQ0FBQztJQUNILGlCQUFpQixDQUFDLEtBQUssRUFBRSxjQUFjLEdBQUcsTUFBTSxHQUFHLFNBQVMsQ0FBQztJQTJCN0QsY0FBYyxDQUFDLFFBQVEsRUFBRSxjQUFjLEVBQUUsZUFBZSxFQUFFLE1BQU0sUUFpQnRFO0lBRUQ7Ozs7Ozs7O09BUUc7SUFDSSxpQkFBaUIsQ0FDdEIsZUFBZSxFQUFFLE1BQU0sR0FFckI7UUFBRSxRQUFRLEVBQUUsSUFBSSxDQUFDO1FBQUMsUUFBUSxFQUFFLFNBQVMsQ0FBQztRQUFDLFdBQVcsRUFBRSxJQUFJLENBQUE7S0FBRSxHQUMxRDtRQUFFLFFBQVEsRUFBRSxLQUFLLENBQUM7UUFBQyxRQUFRLEVBQUUsU0FBUyxDQUFDO1FBQUMsV0FBVyxFQUFFLEtBQUssQ0FBQTtLQUFFLEdBQzVEO1FBQUUsUUFBUSxFQUFFLE9BQU8sQ0FBQztRQUFDLFFBQVEsRUFBRSxNQUFNLENBQUM7UUFBQyxXQUFXLEVBQUUsT0FBTyxDQUFBO0tBQUUsQ0F3RGhFO0NBQ0YifQ==
101
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidGltZXRhYmxlLmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvc2VxdWVuY2VyL3RpbWV0YWJsZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssRUFBRSxNQUFNLEVBQUUsTUFBTSx1QkFBdUIsQ0FBQztBQVdwRCxPQUFPLEtBQUssRUFBRSxnQkFBZ0IsRUFBRSxNQUFNLGNBQWMsQ0FBQztBQUNyRCxPQUFPLEVBQUUsY0FBYyxFQUFFLE1BQU0sWUFBWSxDQUFDO0FBRTVDLHFCQUFhLGtCQUFrQjtJQWlGM0IsT0FBTyxDQUFDLFFBQVEsQ0FBQyxPQUFPLENBQUM7SUFDekIsT0FBTyxDQUFDLFFBQVEsQ0FBQyxHQUFHLENBQUM7SUFqRnZCLE9BQU8sQ0FBQyxRQUFRLENBQUMsZ0JBQWdCLENBQW1CO0lBRXBEOzs7O09BSUc7SUFDSCxTQUFnQixrQkFBa0IsRUFBRSxNQUFNLENBQUM7SUFFM0M7Ozs7T0FJRztJQUNILFNBQWdCLG9CQUFvQixFQUFFLE1BQU0sQ0FBQztJQUU3Qzs7Ozs7T0FLRztJQUNILFNBQWdCLDBCQUEwQixFQUFFLE1BQU0sQ0FBQztJQUVuRDs7OztPQUlHO0lBQ0gsU0FBZ0IsZ0JBQWdCLEVBQUUsTUFBTSxDQUFDO0lBRXpDOzs7T0FHRztJQUNILFNBQWdCLGdCQUFnQixFQUFFLE1BQU0sQ0FBc0I7SUFFOUQsdURBQXVEO0lBQ3ZELFNBQWdCLDRCQUE0QixFQUFFLE1BQU0sQ0FBa0M7SUFFdEYsbUdBQW1HO0lBQ25HLFNBQWdCLGtCQUFrQixFQUFFLE1BQU0sQ0FBQztJQUUzQyxtRkFBbUY7SUFDbkYsU0FBZ0Isc0JBQXNCLEVBQUUsTUFBTSxDQUE0QjtJQUUxRSx3Q0FBd0M7SUFDeEMsU0FBZ0Isb0JBQW9CLEVBQUUsTUFBTSxDQUFDO0lBRTdDLGtGQUFrRjtJQUNsRixTQUFnQixpQkFBaUIsRUFBRSxNQUFNLENBQUM7SUFFMUMsNERBQTREO0lBQzVELFNBQWdCLE9BQU8sRUFBRSxPQUFPLENBQUM7SUFFakMsb0dBQW9HO0lBQ3BHLFNBQWdCLGFBQWEsRUFBRSxNQUFNLEdBQUcsU0FBUyxDQUFDO0lBRWxELDRFQUE0RTtJQUM1RSxTQUFnQixpQkFBaUIsRUFBRSxNQUFNLENBQUM7SUFFMUMscUZBQXFGO0lBQ3JGLFNBQWdCLFVBQVUsRUFBRSxPQUFPLENBQUM7SUFFcEM7OztPQUdHO0lBQ0gsU0FBZ0IsZ0NBQWdDLEVBQUUsTUFBTSxDQUFDO0lBRXpELFlBQ0UsSUFBSSxFQUFFO1FBQ0osb0JBQW9CLEVBQUUsTUFBTSxDQUFDO1FBQzdCLGlCQUFpQixFQUFFLE1BQU0sQ0FBQztRQUMxQixnQkFBZ0IsRUFBRSxNQUFNLENBQUM7UUFDekIsa0JBQWtCLENBQUMsRUFBRSxNQUFNLENBQUM7UUFDNUIsZUFBZSxDQUFDLEVBQUUsTUFBTSxDQUFDO1FBQ3pCLE9BQU8sRUFBRSxPQUFPLENBQUM7UUFDakIsVUFBVSxDQUFDLEVBQUUsT0FBTyxDQUFDO0tBQ3RCLEVBQ2dCLE9BQU8sQ0FBQyw4QkFBa0IsRUFDMUIsR0FBRyxDQUFDLG9CQUFRLEVBd0Q5QjtJQUVNLDZCQUE2QixJQUFJLE1BQU0sQ0FFN0M7SUFFTSxnQ0FBZ0MsSUFBSSxNQUFNLENBRWhEO0lBRU0scUNBQXFDLElBQUksTUFBTSxDQUVyRDtJQUVNLCtCQUErQixJQUFJLE1BQU0sQ0FFL0M7SUFFTSxpQkFBaUIsQ0FDdEIsS0FBSyxFQUFFLE9BQU8sQ0FBQyxjQUFjLEVBQUUsY0FBYyxDQUFDLE9BQU8sR0FBRyxjQUFjLENBQUMsSUFBSSxHQUFHLGNBQWMsQ0FBQyxhQUFhLENBQUMsR0FDMUcsU0FBUyxDQUFDO0lBQ04saUJBQWlCLENBQ3RCLEtBQUssRUFBRSxPQUFPLENBQUMsY0FBYyxFQUFFLGNBQWMsQ0FBQyxPQUFPLEdBQUcsY0FBYyxDQUFDLElBQUksR0FBRyxjQUFjLENBQUMsYUFBYSxDQUFDLEdBQzFHLE1BQU0sQ0FBQztJQUNILGlCQUFpQixDQUFDLEtBQUssRUFBRSxjQUFjLEdBQUcsTUFBTSxHQUFHLFNBQVMsQ0FBQztJQTRCN0QsY0FBYyxDQUFDLFFBQVEsRUFBRSxjQUFjLEVBQUUsZUFBZSxFQUFFLE1BQU0sUUFpQnRFO0lBRUQ7Ozs7Ozs7O09BUUc7SUFDSSxpQkFBaUIsQ0FDdEIsZUFBZSxFQUFFLE1BQU0sR0FFckI7UUFBRSxRQUFRLEVBQUUsSUFBSSxDQUFDO1FBQUMsUUFBUSxFQUFFLFNBQVMsQ0FBQztRQUFDLFdBQVcsRUFBRSxJQUFJLENBQUE7S0FBRSxHQUMxRDtRQUFFLFFBQVEsRUFBRSxLQUFLLENBQUM7UUFBQyxRQUFRLEVBQUUsU0FBUyxDQUFDO1FBQUMsV0FBVyxFQUFFLEtBQUssQ0FBQTtLQUFFLEdBQzVEO1FBQUUsUUFBUSxFQUFFLE9BQU8sQ0FBQztRQUFDLFFBQVEsRUFBRSxNQUFNLENBQUM7UUFBQyxXQUFXLEVBQUUsT0FBTyxDQUFBO0tBQUUsQ0F3RGhFO0NBQ0YifQ==
@@ -1 +1 @@
1
- {"version":3,"file":"timetable.d.ts","sourceRoot":"","sources":["../../src/sequencer/timetable.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AASpD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAE5C,qBAAa,kBAAkB;IAqE3B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC;IACzB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC;IArEvB;;;;OAIG;IACH,SAAgB,kBAAkB,EAAE,MAAM,CAAC;IAE3C;;;;OAIG;IACH,SAAgB,oBAAoB,EAAE,MAAM,CAAC;IAE7C;;;;;OAKG;IACH,SAAgB,0BAA0B,EAAE,MAAM,CAAC;IAEnD;;;;OAIG;IACH,SAAgB,gBAAgB,EAAE,MAAM,CAAC;IAEzC;;;OAGG;IACH,SAAgB,gBAAgB,EAAE,MAAM,CAAsB;IAE9D,uDAAuD;IACvD,SAAgB,4BAA4B,EAAE,MAAM,CAAkC;IAEtF,mGAAmG;IACnG,SAAgB,kBAAkB,EAAE,MAAM,CAAC;IAE3C,mFAAmF;IACnF,SAAgB,sBAAsB,EAAE,MAAM,CAA4B;IAE1E,wCAAwC;IACxC,SAAgB,oBAAoB,EAAE,MAAM,CAAC;IAE7C,kFAAkF;IAClF,SAAgB,iBAAiB,EAAE,MAAM,CAAC;IAE1C,4DAA4D;IAC5D,SAAgB,OAAO,EAAE,OAAO,CAAC;IAEjC,oGAAoG;IACpG,SAAgB,aAAa,EAAE,MAAM,GAAG,SAAS,CAAC;IAElD,4EAA4E;IAC5E,SAAgB,iBAAiB,EAAE,MAAM,CAAC;IAE1C,YACE,IAAI,EAAE;QACJ,oBAAoB,EAAE,MAAM,CAAC;QAC7B,iBAAiB,EAAE,MAAM,CAAC;QAC1B,gBAAgB,EAAE,MAAM,CAAC;QACzB,kBAAkB,CAAC,EAAE,MAAM,CAAC;QAC5B,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,OAAO,EAAE,OAAO,CAAC;KAClB,EACgB,OAAO,CAAC,8BAAkB,EAC1B,GAAG,CAAC,oBAAQ,EA2E9B;IAEM,iBAAiB,CACtB,KAAK,EAAE,OAAO,CAAC,cAAc,EAAE,cAAc,CAAC,OAAO,GAAG,cAAc,CAAC,IAAI,GAAG,cAAc,CAAC,aAAa,CAAC,GAC1G,SAAS,CAAC;IACN,iBAAiB,CACtB,KAAK,EAAE,OAAO,CAAC,cAAc,EAAE,cAAc,CAAC,OAAO,GAAG,cAAc,CAAC,IAAI,GAAG,cAAc,CAAC,aAAa,CAAC,GAC1G,MAAM,CAAC;IACH,iBAAiB,CAAC,KAAK,EAAE,cAAc,GAAG,MAAM,GAAG,SAAS,CAAC;IA2B7D,cAAc,CAAC,QAAQ,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,QAiBtE;IAED;;;;;;;;OAQG;IACI,iBAAiB,CACtB,eAAe,EAAE,MAAM,GAErB;QAAE,QAAQ,EAAE,IAAI,CAAC;QAAC,QAAQ,EAAE,SAAS,CAAC;QAAC,WAAW,EAAE,IAAI,CAAA;KAAE,GAC1D;QAAE,QAAQ,EAAE,KAAK,CAAC;QAAC,QAAQ,EAAE,SAAS,CAAC;QAAC,WAAW,EAAE,KAAK,CAAA;KAAE,GAC5D;QAAE,QAAQ,EAAE,OAAO,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,OAAO,CAAA;KAAE,CAwDhE;CACF"}
1
+ {"version":3,"file":"timetable.d.ts","sourceRoot":"","sources":["../../src/sequencer/timetable.ts"],"names":[],"mappings":"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"}
@@ -1,9 +1,10 @@
1
- import { CHECKPOINT_ASSEMBLE_TIME, CHECKPOINT_INITIALIZATION_TIME, DEFAULT_P2P_PROPAGATION_TIME, MIN_EXECUTION_TIME } from '@aztec/stdlib/timetable';
1
+ import { CHECKPOINT_ASSEMBLE_TIME, CHECKPOINT_INITIALIZATION_TIME, DEFAULT_P2P_PROPAGATION_TIME, MIN_EXECUTION_TIME, createCheckpointTimingModel } from '@aztec/stdlib/timetable';
2
2
  import { SequencerTooSlowError } from './errors.js';
3
3
  import { SequencerState } from './utils.js';
4
4
  export class SequencerTimetable {
5
5
  metrics;
6
6
  log;
7
+ checkpointTiming;
7
8
  /**
8
9
  * How late into the slot can we be to start working. Computed as the total time needed for assembling and publishing a block,
9
10
  * assuming an execution time equal to `minExecutionTime`, subtracted from the slot duration. This means that, if the proposer
@@ -37,6 +38,11 @@ export class SequencerTimetable {
37
38
  /** Whether assertTimeLeft will throw if not enough time. */ enforce;
38
39
  /** Duration per block when building multiple blocks per slot (undefined = single block per slot) */ blockDuration;
39
40
  /** Maximum number of blocks that can be built in this slot configuration */ maxNumberOfBlocks;
41
+ /** Whether pipelining is enabled (checkpoint finalization deferred to next slot). */ pipelining;
42
+ /**
43
+ * How far into the target slot attestation collection can extend when pipelining.
44
+ * Covers validator re-execution (one block duration) plus one-way attestation return.
45
+ */ pipeliningAttestationGracePeriod;
40
46
  constructor(opts, metrics, log){
41
47
  this.metrics = metrics;
42
48
  this.log = log;
@@ -46,40 +52,28 @@ export class SequencerTimetable {
46
52
  this.ethereumSlotDuration = opts.ethereumSlotDuration;
47
53
  this.aztecSlotDuration = opts.aztecSlotDuration;
48
54
  this.l1PublishingTime = opts.l1PublishingTime;
49
- this.p2pPropagationTime = opts.p2pPropagationTime ?? DEFAULT_P2P_PROPAGATION_TIME;
50
55
  this.blockDuration = opts.blockDurationMs ? opts.blockDurationMs / 1000 : undefined;
51
56
  this.enforce = opts.enforce;
52
- // Assume zero-cost propagation time and faster runs in test environments where L1 slot duration is shortened
53
- if (this.ethereumSlotDuration < 8) {
54
- this.p2pPropagationTime = 0;
55
- this.checkpointAssembleTime = 0.5;
56
- this.checkpointInitializationTime = 0.5;
57
- this.minExecutionTime = 1;
58
- }
59
- // Min execution time cannot be less than the block duration if set
60
- if (this.blockDuration !== undefined && this.minExecutionTime > this.blockDuration) {
61
- this.minExecutionTime = this.blockDuration;
62
- }
57
+ this.pipelining = opts.pipelining ?? false;
58
+ this.checkpointTiming = createCheckpointTimingModel({
59
+ aztecSlotDuration: this.aztecSlotDuration,
60
+ ethereumSlotDuration: this.ethereumSlotDuration,
61
+ blockDuration: this.blockDuration,
62
+ l1PublishingTime: this.l1PublishingTime,
63
+ p2pPropagationTime: opts.p2pPropagationTime ?? DEFAULT_P2P_PROPAGATION_TIME,
64
+ pipelining: this.pipelining
65
+ });
66
+ this.p2pPropagationTime = this.checkpointTiming.p2pPropagationTime;
67
+ this.checkpointAssembleTime = this.checkpointTiming.checkpointAssembleTime;
68
+ this.checkpointInitializationTime = this.checkpointTiming.checkpointInitializationTime;
69
+ this.minExecutionTime = this.checkpointTiming.minExecutionTime;
63
70
  // Calculate initialization offset - estimate of time needed for sync + proposer check
64
71
  // This is the baseline for all sub-slot deadlines
65
- this.initializationOffset = this.checkpointInitializationTime;
66
- // Calculate total checkpoint finalization time (assembly + attestations + L1 publishing)
67
- this.checkpointFinalizationTime = this.checkpointAssembleTime + this.p2pPropagationTime * 2 + // Round-trip propagation
68
- this.l1PublishingTime; // L1 publishing
69
- // Calculate maximum number of blocks that fit in this slot
70
- if (!this.blockDuration) {
71
- this.maxNumberOfBlocks = 1; // Single block per slot
72
- } else {
73
- const timeReservedAtEnd = this.blockDuration + // Last sub-slot for validator re-execution
74
- this.checkpointFinalizationTime; // Checkpoint finalization
75
- const timeAvailableForBlocks = this.aztecSlotDuration - this.initializationOffset - timeReservedAtEnd;
76
- this.maxNumberOfBlocks = Math.floor(timeAvailableForBlocks / this.blockDuration);
77
- }
78
- // Minimum work to do within a slot for building a block with the minimum time for execution and publishing its checkpoint
79
- const minWorkToDo = this.initializationOffset + this.minExecutionTime * 2 + // Execution and reexecution
80
- this.checkpointFinalizationTime;
81
- const initializeDeadline = this.aztecSlotDuration - minWorkToDo;
82
- this.initializeDeadline = initializeDeadline;
72
+ this.initializationOffset = this.checkpointTiming.checkpointInitializationTime;
73
+ this.checkpointFinalizationTime = this.checkpointTiming.checkpointFinalizationTime;
74
+ this.pipeliningAttestationGracePeriod = this.checkpointTiming.pipeliningAttestationGracePeriod;
75
+ this.maxNumberOfBlocks = this.checkpointTiming.calculateMaxBlocksPerSlot();
76
+ this.initializeDeadline = this.checkpointTiming.initializeDeadline;
83
77
  this.log?.info(`Sequencer timetable initialized with ${this.maxNumberOfBlocks} blocks per slot (${this.enforce ? 'enforced' : 'not enforced'})`, {
84
78
  ethereumSlotDuration: this.ethereumSlotDuration,
85
79
  aztecSlotDuration: this.aztecSlotDuration,
@@ -90,14 +84,28 @@ export class SequencerTimetable {
90
84
  blockAssembleTime: this.checkpointAssembleTime,
91
85
  initializeDeadline: this.initializeDeadline,
92
86
  enforce: this.enforce,
93
- minWorkToDo,
87
+ pipelining: this.pipelining,
88
+ pipeliningAttestationGracePeriod: this.pipeliningAttestationGracePeriod,
89
+ minWorkToDo: this.checkpointTiming.minimumBuildSlotWork,
94
90
  blockDuration: this.blockDuration,
95
91
  maxNumberOfBlocks: this.maxNumberOfBlocks
96
92
  });
97
- if (initializeDeadline <= 0) {
98
- throw new Error(`Block proposal initialize deadline cannot be negative (got ${initializeDeadline} from total time needed ${minWorkToDo} and a slot duration of ${this.aztecSlotDuration}).`);
93
+ if (this.initializeDeadline <= 0) {
94
+ throw new Error(`Block proposal initialize deadline cannot be negative (got ${this.initializeDeadline} from total time needed ${this.checkpointTiming.minimumBuildSlotWork} and a slot duration of ${this.aztecSlotDuration}).`);
99
95
  }
100
96
  }
97
+ getCheckpointAssemblyDeadline() {
98
+ return this.checkpointTiming.checkpointAssemblyDeadline;
99
+ }
100
+ getCheckpointAttestationDeadline() {
101
+ return this.checkpointTiming.checkpointAttestationDeadline;
102
+ }
103
+ getCheckpointAttestationStartDeadline() {
104
+ return this.checkpointTiming.checkpointAttestationStartDeadline;
105
+ }
106
+ getCheckpointPublishingDeadline() {
107
+ return this.checkpointTiming.checkpointPublishingDeadline;
108
+ }
101
109
  getMaxAllowedTime(state) {
102
110
  switch(state){
103
111
  case SequencerState.STOPPED:
@@ -113,10 +121,11 @@ export class SequencerTimetable {
113
121
  case SequencerState.WAITING_UNTIL_NEXT_BLOCK:
114
122
  return this.initializeDeadline + this.checkpointInitializationTime;
115
123
  case SequencerState.ASSEMBLING_CHECKPOINT:
124
+ return this.getCheckpointAssemblyDeadline();
116
125
  case SequencerState.COLLECTING_ATTESTATIONS:
117
- return this.aztecSlotDuration - this.l1PublishingTime - 2 * this.p2pPropagationTime;
126
+ return this.getCheckpointAttestationStartDeadline();
118
127
  case SequencerState.PUBLISHING_CHECKPOINT:
119
- return this.aztecSlotDuration - this.l1PublishingTime;
128
+ return this.getCheckpointPublishingDeadline();
120
129
  default:
121
130
  {
122
131
  const _exhaustiveCheck = state;
@@ -92,4 +92,4 @@ export declare class MockCheckpointsBuilder implements ICheckpointsBuilder {
92
92
  /** Reset for reuse in another test */
93
93
  reset(): void;
94
94
  }
95
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibW9ja19jaGVja3BvaW50X2J1aWxkZXIuZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy90ZXN0L21vY2tfY2hlY2twb2ludF9idWlsZGVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxLQUFLLFdBQVcsRUFBRSxnQkFBZ0IsRUFBeUIsTUFBTSxpQ0FBaUMsQ0FBQztBQUM1RyxPQUFPLEVBQUUsRUFBRSxFQUFFLE1BQU0sZ0NBQWdDLENBQUM7QUFFcEQsT0FBTyxFQUFFLE9BQU8sRUFBRSxNQUFNLHFCQUFxQixDQUFDO0FBQzlDLE9BQU8sRUFBRSxVQUFVLEVBQUUsTUFBTSwwQkFBMEIsQ0FBQztBQUN0RCxPQUFPLEtBQUssRUFDVixtQkFBbUIsRUFDbkIsMEJBQTBCLEVBQzFCLHVCQUF1QixFQUN2QixtQkFBbUIsRUFDbkIseUJBQXlCLEVBQzFCLE1BQU0saUNBQWlDLENBQUM7QUFHekMsT0FBTyxLQUFLLEVBQUUseUJBQXlCLEVBQUUsRUFBRSxFQUFFLE1BQU0sa0JBQWtCLENBQUM7QUFDdEUsT0FBTyxLQUFLLEVBQUUsNEJBQTRCLEVBQUUsTUFBTSx5QkFBeUIsQ0FBQztBQUU1RTs7O0dBR0c7QUFDSCxxQkFBYSxxQkFBc0IsWUFBVyx1QkFBdUI7SUF3QmpFLE9BQU8sQ0FBQyxRQUFRLENBQUMsU0FBUztJQUMxQixPQUFPLENBQUMsUUFBUSxDQUFDLGdCQUFnQjtJQXhCbkMsT0FBTyxDQUFDLE1BQU0sQ0FBaUI7SUFDL0IsT0FBTyxDQUFDLFdBQVcsQ0FBaUI7SUFDcEMsT0FBTyxDQUFDLGVBQWUsQ0FBYztJQUNyQyxPQUFPLENBQUMsVUFBVSxDQUFLO0lBRXZCLHFGQUFxRjtJQUNyRixPQUFPLENBQUMsYUFBYSxDQUEwQztJQUUvRCxpQ0FBaUM7SUFDMUIsZUFBZSxFQUFFLEtBQUssQ0FBQztRQUM1QixXQUFXLEVBQUUsV0FBVyxDQUFDO1FBQ3pCLFNBQVMsRUFBRSxNQUFNLENBQUM7UUFDbEIs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
95
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibW9ja19jaGVja3BvaW50X2J1aWxkZXIuZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy90ZXN0L21vY2tfY2hlY2twb2ludF9idWlsZGVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxLQUFLLFdBQVcsRUFBRSxnQkFBZ0IsRUFBeUIsTUFBTSxpQ0FBaUMsQ0FBQztBQUM1RyxPQUFPLEVBQUUsRUFBRSxFQUFFLE1BQU0sZ0NBQWdDLENBQUM7QUFFcEQsT0FBTyxFQUFFLE9BQU8sRUFBRSxNQUFNLHFCQUFxQixDQUFDO0FBQzlDLE9BQU8sRUFBRSxVQUFVLEVBQUUsTUFBTSwwQkFBMEIsQ0FBQztBQUN0RCxPQUFPLEtBQUssRUFDVixtQkFBbUIsRUFDbkIsMEJBQTBCLEVBQzFCLHVCQUF1QixFQUN2QixtQkFBbUIsRUFDbkIseUJBQXlCLEVBQzFCLE1BQU0saUNBQWlDLENBQUM7QUFHekMsT0FBTyxLQUFLLEVBQUUseUJBQXlCLEVBQUUsRUFBRSxFQUFFLE1BQU0sa0JBQWtCLENBQUM7QUFDdEUsT0FBTyxLQUFLLEVBQUUsNEJBQTRCLEVBQUUsTUFBTSx5QkFBeUIsQ0FBQztBQUU1RTs7O0dBR0c7QUFDSCxxQkFBYSxxQkFBc0IsWUFBVyx1QkFBdUI7SUF3QmpFLE9BQU8sQ0FBQyxRQUFRLENBQUMsU0FBUztJQUMxQixPQUFPLENBQUMsUUFBUSxDQUFDLGdCQUFnQjtJQXhCbkMsT0FBTyxDQUFDLE1BQU0sQ0FBaUI7SUFDL0IsT0FBTyxDQUFDLFdBQVcsQ0FBaUI7SUFDcEMsT0FBTyxDQUFDLGVBQWUsQ0FBYztJQUNyQyxPQUFPLENBQUMsVUFBVSxDQUFLO0lBRXZCLHFGQUFxRjtJQUNyRixPQUFPLENBQUMsYUFBYSxDQUEwQztJQUUvRCxpQ0FBaUM7SUFDMUIsZUFBZSxFQUFFLEtBQUssQ0FBQztRQUM1QixXQUFXLEVBQUUsV0FBVyxDQUFDO1FBQ3pCLFNBQVMsRUFBRSxNQUFNLENBQUM7UUFDbEIs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