@aztec/sequencer-client 0.0.1-commit.e2b2873ed → 0.0.1-commit.e304674f1

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 (85) hide show
  1. package/dest/client/sequencer-client.d.ts +15 -7
  2. package/dest/client/sequencer-client.d.ts.map +1 -1
  3. package/dest/client/sequencer-client.js +60 -30
  4. package/dest/config.d.ts +26 -6
  5. package/dest/config.d.ts.map +1 -1
  6. package/dest/config.js +44 -21
  7. package/dest/global_variable_builder/global_builder.d.ts +15 -11
  8. package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
  9. package/dest/global_variable_builder/global_builder.js +29 -25
  10. package/dest/global_variable_builder/index.d.ts +2 -2
  11. package/dest/global_variable_builder/index.d.ts.map +1 -1
  12. package/dest/publisher/config.d.ts +47 -17
  13. package/dest/publisher/config.d.ts.map +1 -1
  14. package/dest/publisher/config.js +121 -42
  15. package/dest/publisher/index.d.ts +2 -1
  16. package/dest/publisher/index.d.ts.map +1 -1
  17. package/dest/publisher/l1_tx_failed_store/factory.d.ts +11 -0
  18. package/dest/publisher/l1_tx_failed_store/factory.d.ts.map +1 -0
  19. package/dest/publisher/l1_tx_failed_store/factory.js +22 -0
  20. package/dest/publisher/l1_tx_failed_store/failed_tx_store.d.ts +59 -0
  21. package/dest/publisher/l1_tx_failed_store/failed_tx_store.d.ts.map +1 -0
  22. package/dest/publisher/l1_tx_failed_store/failed_tx_store.js +1 -0
  23. package/dest/publisher/l1_tx_failed_store/file_store_failed_tx_store.d.ts +15 -0
  24. package/dest/publisher/l1_tx_failed_store/file_store_failed_tx_store.d.ts.map +1 -0
  25. package/dest/publisher/l1_tx_failed_store/file_store_failed_tx_store.js +34 -0
  26. package/dest/publisher/l1_tx_failed_store/index.d.ts +4 -0
  27. package/dest/publisher/l1_tx_failed_store/index.d.ts.map +1 -0
  28. package/dest/publisher/l1_tx_failed_store/index.js +2 -0
  29. package/dest/publisher/sequencer-publisher-factory.d.ts +11 -5
  30. package/dest/publisher/sequencer-publisher-factory.d.ts.map +1 -1
  31. package/dest/publisher/sequencer-publisher-factory.js +27 -3
  32. package/dest/publisher/sequencer-publisher.d.ts +82 -37
  33. package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
  34. package/dest/publisher/sequencer-publisher.js +430 -118
  35. package/dest/sequencer/checkpoint_proposal_job.d.ts +36 -9
  36. package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -1
  37. package/dest/sequencer/checkpoint_proposal_job.js +361 -192
  38. package/dest/sequencer/checkpoint_voter.d.ts +1 -2
  39. package/dest/sequencer/checkpoint_voter.d.ts.map +1 -1
  40. package/dest/sequencer/checkpoint_voter.js +2 -5
  41. package/dest/sequencer/events.d.ts +2 -1
  42. package/dest/sequencer/events.d.ts.map +1 -1
  43. package/dest/sequencer/metrics.d.ts +21 -5
  44. package/dest/sequencer/metrics.d.ts.map +1 -1
  45. package/dest/sequencer/metrics.js +97 -15
  46. package/dest/sequencer/sequencer.d.ts +40 -17
  47. package/dest/sequencer/sequencer.d.ts.map +1 -1
  48. package/dest/sequencer/sequencer.js +152 -95
  49. package/dest/sequencer/timetable.d.ts +7 -3
  50. package/dest/sequencer/timetable.d.ts.map +1 -1
  51. package/dest/sequencer/timetable.js +21 -12
  52. package/dest/sequencer/types.d.ts +2 -2
  53. package/dest/sequencer/types.d.ts.map +1 -1
  54. package/dest/test/index.d.ts +3 -5
  55. package/dest/test/index.d.ts.map +1 -1
  56. package/dest/test/mock_checkpoint_builder.d.ts +11 -11
  57. package/dest/test/mock_checkpoint_builder.d.ts.map +1 -1
  58. package/dest/test/mock_checkpoint_builder.js +45 -34
  59. package/dest/test/utils.d.ts +3 -3
  60. package/dest/test/utils.d.ts.map +1 -1
  61. package/dest/test/utils.js +5 -4
  62. package/package.json +27 -28
  63. package/src/client/sequencer-client.ts +76 -30
  64. package/src/config.ts +56 -27
  65. package/src/global_variable_builder/global_builder.ts +38 -27
  66. package/src/global_variable_builder/index.ts +1 -1
  67. package/src/publisher/config.ts +153 -43
  68. package/src/publisher/index.ts +3 -0
  69. package/src/publisher/l1_tx_failed_store/factory.ts +32 -0
  70. package/src/publisher/l1_tx_failed_store/failed_tx_store.ts +55 -0
  71. package/src/publisher/l1_tx_failed_store/file_store_failed_tx_store.ts +46 -0
  72. package/src/publisher/l1_tx_failed_store/index.ts +3 -0
  73. package/src/publisher/sequencer-publisher-factory.ts +38 -9
  74. package/src/publisher/sequencer-publisher.ts +503 -168
  75. package/src/sequencer/README.md +81 -12
  76. package/src/sequencer/checkpoint_proposal_job.ts +471 -201
  77. package/src/sequencer/checkpoint_voter.ts +1 -12
  78. package/src/sequencer/events.ts +1 -1
  79. package/src/sequencer/metrics.ts +106 -18
  80. package/src/sequencer/sequencer.ts +216 -109
  81. package/src/sequencer/timetable.ts +26 -15
  82. package/src/sequencer/types.ts +1 -1
  83. package/src/test/index.ts +2 -4
  84. package/src/test/mock_checkpoint_builder.ts +63 -49
  85. package/src/test/utils.ts +5 -2
@@ -380,7 +380,7 @@ 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';
@@ -454,14 +454,8 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
454
454
  /** The last slot for which we triggered a checkpoint proposal job, to prevent duplicate attempts. */ lastSlotForCheckpointProposalJob;
455
455
  /** Last successful checkpoint proposed */ lastCheckpointProposed;
456
456
  /** The last epoch for which we logged strategy comparison in fisherman mode. */ lastEpochForStrategyComparison;
457
+ /** The last checkpoint proposal job, tracked so we can await its pending L1 submission during shutdown. */ lastCheckpointProposalJob;
457
458
  /** 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
459
  /** Config for the sequencer */ config;
466
460
  constructor(publisherFactory, validatorClient, globalsBuilder, p2pClient, worldState, slasherClient, l2BlockSource, l1ToL2MessageSource, checkpointsBuilder, l1Constants, dateProvider, epochCache, rollupContract, config, telemetry = getTelemetryClient(), log = createLogger('sequencer')){
467
461
  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;
@@ -474,7 +468,7 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
474
468
  }
475
469
  /** Updates sequencer config by the defined values and updates the timetable */ updateConfig(config) {
476
470
  const filteredConfig = pickFromSchema(config, SequencerConfigSchema);
477
- this.log.info(`Updated sequencer config`, omit(filteredConfig, 'txPublicSetupAllowList'));
471
+ this.log.info(`Updated sequencer config`, omit(filteredConfig, 'txPublicSetupAllowListExtend'));
478
472
  this.config = merge(this.config, filteredConfig);
479
473
  this.timetable = new SequencerTimetable({
480
474
  ethereumSlotDuration: this.l1Constants.ethereumSlotDuration,
@@ -482,12 +476,12 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
482
476
  l1PublishingTime: this.l1PublishingTime,
483
477
  p2pPropagationTime: this.config.attestationPropagationTime,
484
478
  blockDurationMs: this.config.blockDurationMs,
485
- enforce: this.config.enforceTimeTable
479
+ enforce: this.config.enforceTimeTable,
480
+ pipelining: this.epochCache.isProposerPipeliningEnabled()
486
481
  }, this.metrics, this.log);
487
482
  }
488
- /** Initializes the sequencer (precomputes tables and creates a publisher). Takes about 3s. */ async init() {
483
+ /** Initializes the sequencer (precomputes tables). Takes about 3s. */ init() {
489
484
  getKzg();
490
- this.publisher = (await this.publisherFactory.create(undefined)).publisher;
491
485
  }
492
486
  /** Starts the sequencer and moves to IDLE state. */ start() {
493
487
  this.runningPromise = new RunningPromise(this.safeWork.bind(this), this.log, this.config.sequencerPollingIntervalMS);
@@ -502,8 +496,9 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
502
496
  this.setState(SequencerState.STOPPING, undefined, {
503
497
  force: true
504
498
  });
505
- this.publisher?.interrupt();
499
+ await this.publisherFactory.stopAll();
506
500
  await this.runningPromise?.stop();
501
+ await this.lastCheckpointProposalJob?.awaitPendingSubmission();
507
502
  this.setState(SequencerState.STOPPED, undefined, {
508
503
  force: true
509
504
  });
@@ -517,7 +512,6 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
517
512
  error: err
518
513
  });
519
514
  if (err instanceof SequencerTooSlowError) {
520
- // TODO(palla/mbps): Add missing states
521
515
  // Log as warn only if we had to abort halfway through the block proposal
522
516
  const logLvl = [
523
517
  SequencerState.INITIALIZING_CHECKPOINT,
@@ -548,22 +542,25 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
548
542
  * - Submit checkpoint
549
543
  */ async work() {
550
544
  this.setState(SequencerState.SYNCHRONIZING, undefined);
551
- const { slot, ts, now, epoch } = this.epochCache.getEpochAndSlotInNextL1Slot();
545
+ const { slot, ts, nowSeconds, epoch } = this.epochCache.getEpochAndSlotInNextL1Slot();
546
+ const { slot: targetSlot, epoch: targetEpoch } = this.epochCache.getTargetEpochAndSlotInNextL1Slot();
552
547
  // 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);
548
+ const checkpointProposalJob = await this.prepareCheckpointProposal(slot, targetSlot, epoch, targetEpoch, ts, nowSeconds);
554
549
  if (!checkpointProposalJob) {
555
550
  return;
556
551
  }
552
+ // Track the job so we can await its pending L1 submission during shutdown
553
+ this.lastCheckpointProposalJob = checkpointProposalJob;
557
554
  // Execute the checkpoint proposal job
558
555
  const checkpoint = await checkpointProposalJob.execute();
559
556
  // Update last checkpoint proposed (currently unused)
560
557
  if (checkpoint) {
561
558
  this.lastCheckpointProposed = checkpoint;
562
559
  }
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;
560
+ // Log fee strategy comparison if on fisherman (uses target epoch since we mirror the proposer's perspective)
561
+ if (this.config.fishermanMode && (this.lastEpochForStrategyComparison === undefined || targetEpoch > this.lastEpochForStrategyComparison)) {
562
+ this.logStrategyComparison(targetEpoch, checkpointProposalJob.getPublisher());
563
+ this.lastEpochForStrategyComparison = targetEpoch;
567
564
  }
568
565
  return checkpoint;
569
566
  }
@@ -571,17 +568,17 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
571
568
  * Prepares the checkpoint proposal by performing all necessary checks and setup.
572
569
  * This is the initial step in the main loop.
573
570
  * @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)
571
+ */ async prepareCheckpointProposal(slot, targetSlot, epoch, targetEpoch, ts, nowSeconds) {
572
+ // Check we have not already processed this target slot (cheapest check)
576
573
  // We only check this if enforce timetable is set, since we want to keep processing the same slot if we are not
577
574
  // 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`);
575
+ if (this.lastSlotForCheckpointProposalJob && this.lastSlotForCheckpointProposalJob >= targetSlot && this.config.enforceTimeTable) {
576
+ this.log.trace(`Target slot ${targetSlot} has already been processed`);
580
577
  return undefined;
581
578
  }
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}`);
579
+ // But if we have already proposed for this slot, then we definitely have to skip it, automining or not
580
+ if (this.lastCheckpointProposed && this.lastCheckpointProposed.header.slotNumber >= targetSlot) {
581
+ this.log.trace(`Slot ${targetSlot} has already been published as checkpoint ${this.lastCheckpointProposed.number}`);
585
582
  return undefined;
586
583
  }
587
584
  // Check all components are synced to latest as seen by the archiver (queries all subsystems)
@@ -592,16 +589,18 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
592
589
  if (!syncedTo) {
593
590
  await this.tryVoteWhenSyncFails({
594
591
  slot,
592
+ targetSlot,
595
593
  ts
596
594
  });
597
595
  return undefined;
598
596
  }
599
- // If escape hatch is open for this epoch, do not start checkpoint proposal work and do not attempt invalidations.
597
+ // If escape hatch is open for the target epoch, do not start checkpoint proposal work and do not attempt invalidations.
600
598
  // Still perform governance/slashing voting (as proposer) once per slot.
601
- const isEscapeHatchOpen = await this.epochCache.isEscapeHatchOpen(epoch);
599
+ // When pipelining, we check the target epoch (slot+1's epoch) since that's the epoch we're building for.
600
+ const isEscapeHatchOpen = await this.epochCache.isEscapeHatchOpen(targetEpoch);
602
601
  if (isEscapeHatchOpen) {
603
602
  this.setState(SequencerState.PROPOSER_CHECK, slot);
604
- const [canPropose, proposer] = await this.checkCanPropose(slot);
603
+ const [canPropose, proposer] = await this.checkCanPropose(targetSlot);
605
604
  if (canPropose) {
606
605
  await this.tryVoteWhenEscapeHatchOpen({
607
606
  slot,
@@ -619,29 +618,36 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
619
618
  // Next checkpoint follows from the last synced one
620
619
  const checkpointNumber = CheckpointNumber(syncedTo.checkpointNumber + 1);
621
620
  const logCtx = {
622
- now,
623
- syncedToL1Ts: syncedTo.l1Timestamp,
624
- syncedToL2Slot: getSlotAtTimestamp(syncedTo.l1Timestamp, this.l1Constants),
621
+ nowSeconds,
622
+ syncedToL2Slot: syncedTo.syncedL2Slot,
625
623
  slot,
624
+ targetSlot,
626
625
  slotTs: ts,
627
626
  checkpointNumber,
628
627
  isPendingChainValid: pick(syncedTo.pendingChainValidationStatus, 'valid', 'reason', 'invalidIndex')
629
628
  };
630
- // Check that we are a proposer for the next slot
629
+ // Check that we are a proposer for the target slot.
631
630
  this.setState(SequencerState.PROPOSER_CHECK, slot);
632
- const [canPropose, proposer] = await this.checkCanPropose(slot);
631
+ const [canPropose, proposer] = await this.checkCanPropose(targetSlot);
633
632
  // If we are not a proposer check if we should invalidate an invalid checkpoint, and bail
634
633
  if (!canPropose) {
635
634
  await this.considerInvalidatingCheckpoint(syncedTo, slot);
636
635
  return undefined;
637
636
  }
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}`, {
637
+ // Guard: don't exceed 1-deep pipeline. Without a proposed checkpoint, we can only build
638
+ // confirmed + 1. With a proposed checkpoint, we can build confirmed + 2.
639
+ const confirmedCkpt = syncedTo.checkpointedCheckpointNumber;
640
+ if (checkpointNumber > confirmedCkpt + 2) {
641
+ this.log.verbose(`Skipping slot ${targetSlot}: checkpoint ${checkpointNumber} exceeds max pipeline depth (confirmed=${confirmedCkpt})`);
642
+ return undefined;
643
+ }
644
+ // Check that the target slot is not taken by a block already (should never happen, since only us can propose for this slot)
645
+ if (syncedTo.blockData && syncedTo.blockData.header.getSlot() >= targetSlot) {
646
+ this.log.warn(`Cannot propose block at target slot ${targetSlot} since that slot was taken by block ${syncedTo.blockNumber}`, {
641
647
  ...logCtx,
642
- block: syncedTo.block.header.toInspect()
648
+ block: syncedTo.blockData.header.toInspect()
643
649
  });
644
- this.metrics.recordBlockProposalPrecheckFailed('slot_already_taken');
650
+ this.metrics.recordCheckpointPrecheckFailed('slot_already_taken');
645
651
  return undefined;
646
652
  }
647
653
  // We now need to get ourselves a publisher.
@@ -651,37 +657,61 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
651
657
  const proposerForPublisher = this.config.fishermanMode ? undefined : proposer;
652
658
  const { attestorAddress, publisher } = await this.publisherFactory.create(proposerForPublisher);
653
659
  this.log.verbose(`Created publisher at address ${publisher.getSenderAddress()} for attestor ${attestorAddress}`);
654
- this.publisher = publisher;
655
660
  // In fisherman mode, set the actual proposer's address for simulations
656
661
  if (this.config.fishermanMode && proposer) {
657
662
  publisher.setProposerAddressForSimulation(proposer);
658
663
  this.log.debug(`Set proposer address ${proposer} for simulation in fisherman mode`);
659
664
  }
660
665
  // 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);
666
+ let invalidateCheckpoint = await publisher.simulateInvalidateCheckpoint(syncedTo.pendingChainValidationStatus);
667
+ // Determine the correct archive and L1 state overrides for the canProposeAt check.
668
+ // The L1 contract reads archives[proposedCheckpointNumber] and compares it with the provided archive.
669
+ // When invalidating or pipelining, the local archive may differ from L1's, so we adjust accordingly.
670
+ let archiveForCheck = syncedTo.archive;
671
+ const l1Overrides = {};
672
+ if (this.epochCache.isProposerPipeliningEnabled() && syncedTo.hasProposedCheckpoint) {
673
+ // Parent checkpoint hasn't landed on L1 yet. Override both the proposed checkpoint number
674
+ // and the archive at that checkpoint so L1 simulation sees the correct chain tip.
675
+ const parentCheckpointNumber = CheckpointNumber(checkpointNumber - 1);
676
+ l1Overrides.forcePendingCheckpointNumber = parentCheckpointNumber;
677
+ l1Overrides.forceArchive = {
678
+ checkpointNumber: parentCheckpointNumber,
679
+ archive: syncedTo.archive
680
+ };
681
+ this.metrics.recordPipelineDepth(1);
682
+ this.log.verbose(`Building on top of proposed checkpoint (pending=${syncedTo.proposedCheckpointData?.checkpointNumber})`);
683
+ // Clear the invalidation - the proposed checkpoint should handle it.
684
+ invalidateCheckpoint = undefined;
685
+ } else if (invalidateCheckpoint) {
686
+ // After invalidation, L1 will roll back to checkpoint N-1. The archive at N-1 already
687
+ // exists on L1, so we just pass the matching archive (the lastArchive of the invalid checkpoint).
688
+ archiveForCheck = invalidateCheckpoint.lastArchive;
689
+ l1Overrides.forcePendingCheckpointNumber = invalidateCheckpoint.forcePendingCheckpointNumber;
690
+ this.metrics.recordPipelineDepth(0);
691
+ } else {
692
+ this.metrics.recordPipelineDepth(0);
693
+ }
694
+ const canProposeCheck = await publisher.canProposeAt(archiveForCheck, proposer ?? EthAddress.ZERO, l1Overrides);
665
695
  if (canProposeCheck === undefined) {
666
696
  this.log.warn(`Cannot propose checkpoint ${checkpointNumber} at slot ${slot} due to failed rollup contract check`, logCtx);
667
697
  this.emit('proposer-rollup-check-failed', {
668
698
  reason: 'Rollup contract check failed',
669
699
  slot
670
700
  });
671
- this.metrics.recordBlockProposalPrecheckFailed('rollup_contract_check_failed');
701
+ this.metrics.recordCheckpointPrecheckFailed('rollup_contract_check_failed');
672
702
  return undefined;
673
703
  }
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}.`, {
704
+ if (canProposeCheck.slot !== targetSlot) {
705
+ 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
706
  ...logCtx,
677
707
  rollup: canProposeCheck,
678
- expectedSlot: slot
708
+ expectedSlot: targetSlot
679
709
  });
680
710
  this.emit('proposer-rollup-check-failed', {
681
711
  reason: 'Slot mismatch',
682
712
  slot
683
713
  });
684
- this.metrics.recordBlockProposalPrecheckFailed('slot_mismatch');
714
+ this.metrics.recordCheckpointPrecheckFailed('slot_mismatch');
685
715
  return undefined;
686
716
  }
687
717
  if (canProposeCheck.checkpointNumber !== checkpointNumber) {
@@ -694,20 +724,26 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
694
724
  reason: 'Block mismatch',
695
725
  slot
696
726
  });
697
- this.metrics.recordBlockProposalPrecheckFailed('block_number_mismatch');
727
+ this.metrics.recordCheckpointPrecheckFailed('block_number_mismatch');
698
728
  return undefined;
699
729
  }
700
- this.lastSlotForCheckpointProposalJob = slot;
701
- await this.p2pClient.prepareForSlot(slot);
702
- this.log.info(`Preparing checkpoint proposal ${checkpointNumber} at slot ${slot}`, {
730
+ this.lastSlotForCheckpointProposalJob = targetSlot;
731
+ await this.p2pClient.prepareForSlot(targetSlot);
732
+ this.log.info(`Preparing checkpoint proposal ${checkpointNumber} for target slot ${targetSlot} during wall-clock slot ${slot}`, {
703
733
  ...logCtx,
704
- proposer
734
+ proposer,
735
+ pipeliningEnabled: this.epochCache.isProposerPipeliningEnabled()
705
736
  });
706
737
  // Create and return the checkpoint proposal job
707
- return this.createCheckpointProposalJob(epoch, slot, checkpointNumber, syncedTo.blockNumber, proposer, publisher, attestorAddress, invalidateCheckpoint);
738
+ return this.createCheckpointProposalJob(slot, targetSlot, targetEpoch, checkpointNumber, syncedTo.blockNumber, proposer, publisher, attestorAddress, invalidateCheckpoint, syncedTo.proposedCheckpointData);
708
739
  }
709
- createCheckpointProposalJob(epoch, slot, checkpointNumber, syncedToBlockNumber, proposer, publisher, attestorAddress, invalidateCheckpoint) {
710
- 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());
740
+ createCheckpointProposalJob(slot, targetSlot, targetEpoch, checkpointNumber, syncedToBlockNumber, proposer, publisher, attestorAddress, invalidateCheckpoint, proposedCheckpointData) {
741
+ 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, this.setState.bind(this), this.tracer, this.log.getBindings(), proposedCheckpointData);
742
+ }
743
+ /**
744
+ * Returns the current sequencer state.
745
+ */ getState() {
746
+ return this.state;
711
747
  }
712
748
  /**
713
749
  * Internal helper for setting the sequencer state and checks if we have enough time left in the slot to transition to the new state.
@@ -749,16 +785,15 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
749
785
  * Returns whether all dependencies have caught up.
750
786
  * We don't check against the previous block submitted since it may have been reorg'd out.
751
787
  */ async checkSync(args) {
752
- // Check that the archiver and dependencies have synced to the previous L1 slot at least
753
- // TODO(#14766): Archiver reports L1 timestamp based on L1 blocks seen, which means that a missed L1 block will
754
- // cause the archiver L1 timestamp to fall behind, and cause this sequencer to start processing one L1 slot later.
755
- const l1Timestamp = await this.l2BlockSource.getL1Timestamp();
756
- const { slot, ts } = args;
757
- if (l1Timestamp === undefined || l1Timestamp + BigInt(this.l1Constants.ethereumSlotDuration) < ts) {
788
+ // Check that the archiver has fully synced the L2 slot before the one we want to propose in.
789
+ // The archiver reports sync progress via L1 block timestamps and synced checkpoint slots.
790
+ // See getSyncedL2SlotNumber for how missed L1 blocks are handled.
791
+ const syncedL2Slot = await this.l2BlockSource.getSyncedL2SlotNumber();
792
+ const { slot } = args;
793
+ if (syncedL2Slot === undefined || syncedL2Slot + 1 < slot) {
758
794
  this.log.debug(`Cannot propose block at next L2 slot ${slot} due to pending sync from L1`, {
759
795
  slot,
760
- ts,
761
- l1Timestamp
796
+ syncedL2Slot
762
797
  });
763
798
  return undefined;
764
799
  }
@@ -767,22 +802,30 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
767
802
  number: syncSummary.latestBlockNumber,
768
803
  hash: syncSummary.latestBlockHash
769
804
  })),
770
- this.l2BlockSource.getL2Tips().then((t)=>t.proposed),
805
+ this.l2BlockSource.getL2Tips().then((t)=>({
806
+ proposed: t.proposed,
807
+ checkpointed: t.checkpointed,
808
+ proposedCheckpoint: t.proposedCheckpoint
809
+ })),
771
810
  this.p2pClient.getStatus().then((p2p)=>p2p.syncedToL2Block),
772
- this.l1ToL2MessageSource.getL2Tips().then((t)=>t.proposed),
773
- this.l2BlockSource.getPendingChainValidationStatus()
811
+ this.l1ToL2MessageSource.getL2Tips().then((t)=>({
812
+ proposed: t.proposed,
813
+ checkpointed: t.checkpointed
814
+ })),
815
+ this.l2BlockSource.getPendingChainValidationStatus(),
816
+ this.l2BlockSource.getProposedCheckpointOnly()
774
817
  ]);
775
- const [worldState, l2BlockSource, p2p, l1ToL2MessageSource, pendingChainValidationStatus] = syncedBlocks;
818
+ const [worldState, l2Tips, p2p, l1ToL2MessageSourceTips, pendingChainValidationStatus, proposedCheckpointData] = syncedBlocks;
776
819
  // 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,
777
820
  // as the world state can compute the new genesis block hash, but other components use the hardcoded constant.
778
821
  // TODO(palla/mbps): Fix the above. All components should be able to handle dynamic genesis block hashes.
779
- 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;
822
+ 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;
780
823
  if (!result) {
781
824
  this.log.debug(`Sequencer sync check failed`, {
782
825
  worldState,
783
- l2BlockSource,
826
+ l2BlockSource: l2Tips.proposed,
784
827
  p2p,
785
- l1ToL2MessageSource
828
+ l1ToL2MessageSourceTips
786
829
  });
787
830
  return undefined;
788
831
  }
@@ -792,46 +835,52 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
792
835
  const archive = new Fr((await this.worldState.getCommitted().getTreeInfo(MerkleTreeId.ARCHIVE)).root);
793
836
  return {
794
837
  checkpointNumber: CheckpointNumber.ZERO,
838
+ checkpointedCheckpointNumber: CheckpointNumber.ZERO,
795
839
  blockNumber: BlockNumber.ZERO,
796
840
  archive,
797
- l1Timestamp,
841
+ hasProposedCheckpoint: false,
842
+ syncedL2Slot,
798
843
  pendingChainValidationStatus
799
844
  };
800
845
  }
801
- const block = await this.l2BlockSource.getL2Block(blockNumber);
802
- if (!block) {
846
+ const blockData = await this.l2BlockSource.getBlockData(blockNumber);
847
+ if (!blockData) {
803
848
  // this shouldn't really happen because a moment ago we checked that all components were in sync
804
- this.log.error(`Failed to get L2 block ${blockNumber} from the archiver with all components in sync`);
849
+ this.log.error(`Failed to get L2 block data ${blockNumber} from the archiver with all components in sync`);
805
850
  return undefined;
806
851
  }
852
+ const hasProposedCheckpoint = l2Tips.proposedCheckpoint.checkpoint.number > l2Tips.checkpointed.checkpoint.number;
807
853
  return {
808
- block,
809
- blockNumber: block.number,
810
- checkpointNumber: block.checkpointNumber,
811
- archive: block.archive.root,
812
- l1Timestamp,
854
+ blockData,
855
+ blockNumber: blockData.header.getBlockNumber(),
856
+ checkpointNumber: blockData.checkpointNumber,
857
+ checkpointedCheckpointNumber: l2Tips.checkpointed.checkpoint.number,
858
+ archive: blockData.archive.root,
859
+ hasProposedCheckpoint,
860
+ proposedCheckpointData,
861
+ syncedL2Slot,
813
862
  pendingChainValidationStatus
814
863
  };
815
864
  }
816
865
  /**
817
866
  * Checks if we are the proposer for the next slot.
818
867
  * @returns True if we can propose, and the proposer address (undefined if anyone can propose)
819
- */ async checkCanPropose(slot) {
868
+ */ async checkCanPropose(targetSlot) {
820
869
  let proposer;
821
870
  try {
822
- proposer = await this.epochCache.getProposerAttesterAddressInSlot(slot);
871
+ proposer = await this.epochCache.getProposerAttesterAddressInSlot(targetSlot);
823
872
  } catch (e) {
824
873
  if (e instanceof NoCommitteeError) {
825
- if (this.lastSlotForNoCommitteeWarning !== slot) {
826
- this.lastSlotForNoCommitteeWarning = slot;
827
- this.log.warn(`Cannot propose at next L2 slot ${slot} since the committee does not exist on L1`);
874
+ if (this.lastSlotForNoCommitteeWarning !== targetSlot) {
875
+ this.lastSlotForNoCommitteeWarning = targetSlot;
876
+ this.log.warn(`Cannot propose at target slot ${targetSlot} since the committee does not exist on L1`);
828
877
  }
829
878
  return [
830
879
  false,
831
880
  undefined
832
881
  ];
833
882
  }
834
- this.log.error(`Error getting proposer for slot ${slot}`, e);
883
+ this.log.error(`Error getting proposer for target slot ${targetSlot}`, e);
835
884
  return [
836
885
  false,
837
886
  undefined
@@ -854,7 +903,8 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
854
903
  const validatorAddresses = this.validatorClient.getValidatorAddresses();
855
904
  const weAreProposer = validatorAddresses.some((addr)=>addr.equals(proposer));
856
905
  if (!weAreProposer) {
857
- this.log.debug(`Cannot propose at slot ${slot} since we are not a proposer`, {
906
+ this.log.debug(`Cannot propose at target slot ${targetSlot} since we are not a proposer`, {
907
+ targetSlot,
858
908
  validatorAddresses,
859
909
  proposer
860
910
  });
@@ -863,6 +913,10 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
863
913
  proposer
864
914
  ];
865
915
  }
916
+ this.log.info(`We are the proposer for pipeline slot ${targetSlot}`, {
917
+ targetSlot,
918
+ proposer
919
+ });
866
920
  return [
867
921
  true,
868
922
  proposer
@@ -872,7 +926,7 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
872
926
  * Tries to vote on slashing actions and governance when the sync check fails but we're past the max time for initializing a proposal.
873
927
  * This allows the sequencer to participate in governance/slashing votes even when it cannot build blocks.
874
928
  */ async tryVoteWhenSyncFails(args) {
875
- const { slot } = args;
929
+ const { slot, targetSlot } = args;
876
930
  // Prevent duplicate attempts in the same slot
877
931
  if (this.lastSlotForFallbackVote === slot) {
878
932
  this.log.trace(`Already attempted to vote in slot ${slot} (skipping)`);
@@ -895,7 +949,7 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
895
949
  maxAllowedTime
896
950
  });
897
951
  // Check if we're a proposer or proposal is open
898
- const [canPropose, proposer] = await this.checkCanPropose(slot);
952
+ const [canPropose, proposer] = await this.checkCanPropose(targetSlot);
899
953
  if (!canPropose) {
900
954
  this.log.trace(`Cannot vote in slot ${slot} since we are not a proposer`, {
901
955
  slot,
@@ -911,8 +965,8 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
911
965
  attestorAddress,
912
966
  slot
913
967
  });
914
- // Enqueue governance and slashing votes
915
- const voter = new CheckpointVoter(slot, publisher, attestorAddress, this.validatorClient, this.slasherClient, this.l1Constants, this.config, this.metrics, this.log);
968
+ // Enqueue governance and slashing votes (voter uses the target slot for L1 submission)
969
+ const voter = new CheckpointVoter(targetSlot, publisher, attestorAddress, this.validatorClient, this.slasherClient, this.l1Constants, this.config, this.metrics, this.log);
916
970
  const votesPromises = voter.enqueueVotes();
917
971
  const votes = await Promise.all(votesPromises);
918
972
  if (votes.every((p)=>!p)) {
@@ -959,7 +1013,7 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
959
1013
  * have the proposer try to invalidate, but if they fail, the sequencers in the committee are expected to try,
960
1014
  * and if they fail, any sequencer will try as well.
961
1015
  */ async considerInvalidatingCheckpoint(syncedTo, currentSlot) {
962
- const { pendingChainValidationStatus, l1Timestamp } = syncedTo;
1016
+ const { pendingChainValidationStatus, syncedL2Slot } = syncedTo;
963
1017
  if (pendingChainValidationStatus.valid) {
964
1018
  return;
965
1019
  }
@@ -970,7 +1024,7 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
970
1024
  const { secondsBeforeInvalidatingBlockAsCommitteeMember, secondsBeforeInvalidatingBlockAsNonCommitteeMember } = this.config;
971
1025
  const logData = {
972
1026
  invalidL1Timestamp: invalidCheckpointTimestamp,
973
- l1Timestamp,
1027
+ syncedL2Slot,
974
1028
  invalidCheckpoint: pendingChainValidationStatus.checkpoint,
975
1029
  secondsBeforeInvalidatingBlockAsCommitteeMember,
976
1030
  secondsBeforeInvalidatingBlockAsNonCommitteeMember,
@@ -1061,6 +1115,9 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
1061
1115
  getValidatorAddresses() {
1062
1116
  return this.validatorClient?.getValidatorAddresses();
1063
1117
  }
1118
+ /** Updates the publisher factory's node keystore adapter after a keystore reload. */ updatePublisherNodeKeyStore(adapter) {
1119
+ this.publisherFactory.updateNodeKeyStore(adapter);
1120
+ }
1064
1121
  getConfig() {
1065
1122
  return this.config;
1066
1123
  }
@@ -1,8 +1,9 @@
1
+ import type { Logger } from '@aztec/foundation/log';
1
2
  import type { SequencerMetrics } from './metrics.js';
2
3
  import { SequencerState } from './utils.js';
3
4
  export declare class SequencerTimetable {
4
5
  private readonly metrics?;
5
- private readonly log;
6
+ private readonly log?;
6
7
  /**
7
8
  * How late into the slot can we be to start working. Computed as the total time needed for assembling and publishing a block,
8
9
  * assuming an execution time equal to `minExecutionTime`, subtracted from the slot duration. This means that, if the proposer
@@ -49,6 +50,8 @@ export declare class SequencerTimetable {
49
50
  readonly blockDuration: number | undefined;
50
51
  /** Maximum number of blocks that can be built in this slot configuration */
51
52
  readonly maxNumberOfBlocks: number;
53
+ /** Whether pipelining is enabled (checkpoint finalization deferred to next slot). */
54
+ readonly pipelining: boolean;
52
55
  constructor(opts: {
53
56
  ethereumSlotDuration: number;
54
57
  aztecSlotDuration: number;
@@ -56,7 +59,8 @@ export declare class SequencerTimetable {
56
59
  p2pPropagationTime?: number;
57
60
  blockDurationMs?: number;
58
61
  enforce: boolean;
59
- }, metrics?: SequencerMetrics | undefined, log?: import("@aztec/aztec.js/log").Logger);
62
+ pipelining?: boolean;
63
+ }, metrics?: SequencerMetrics | undefined, log?: Logger | undefined);
60
64
  getMaxAllowedTime(state: Extract<SequencerState, SequencerState.STOPPED | SequencerState.IDLE | SequencerState.SYNCHRONIZING>): undefined;
61
65
  getMaxAllowedTime(state: Exclude<SequencerState, SequencerState.STOPPED | SequencerState.IDLE | SequencerState.SYNCHRONIZING>): number;
62
66
  getMaxAllowedTime(state: SequencerState): number | undefined;
@@ -84,4 +88,4 @@ export declare class SequencerTimetable {
84
88
  isLastBlock: boolean;
85
89
  };
86
90
  }
87
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidGltZXRhYmxlLmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvc2VxdWVuY2VyL3RpbWV0YWJsZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFTQSxPQUFPLEtBQUssRUFBRSxnQkFBZ0IsRUFBRSxNQUFNLGNBQWMsQ0FBQztBQUNyRCxPQUFPLEVBQUUsY0FBYyxFQUFFLE1BQU0sWUFBWSxDQUFDO0FBRTVDLHFCQUFhLGtCQUFrQjtJQXFFM0IsT0FBTyxDQUFDLFFBQVEsQ0FBQyxPQUFPLENBQUM7SUFDekIsT0FBTyxDQUFDLFFBQVEsQ0FBQyxHQUFHO0lBckV0Qjs7OztPQUlHO0lBQ0gsU0FBZ0Isa0JBQWtCLEVBQUUsTUFBTSxDQUFDO0lBRTNDOzs7O09BSUc7SUFDSCxTQUFnQixvQkFBb0IsRUFBRSxNQUFNLENBQUM7SUFFN0M7Ozs7O09BS0c7SUFDSCxTQUFnQiwwQkFBMEIsRUFBRSxNQUFNLENBQUM7SUFFbkQ7Ozs7T0FJRztJQUNILFNBQWdCLGdCQUFnQixFQUFFLE1BQU0sQ0FBQztJQUV6Qzs7O09BR0c7SUFDSCxTQUFnQixnQkFBZ0IsRUFBRSxNQUFNLENBQXNCO0lBRTlELHVEQUF1RDtJQUN2RCxTQUFnQiw0QkFBNEIsRUFBRSxNQUFNLENBQWtDO0lBRXRGLG1HQUFtRztJQUNuRyxTQUFnQixrQkFBa0IsRUFBRSxNQUFNLENBQUM7SUFFM0MsbUZBQW1GO0lBQ25GLFNBQWdCLHNCQUFzQixFQUFFLE1BQU0sQ0FBNEI7SUFFMUUsd0NBQXdDO0lBQ3hDLFNBQWdCLG9CQUFvQixFQUFFLE1BQU0sQ0FBQztJQUU3QyxrRkFBa0Y7SUFDbEYsU0FBZ0IsaUJBQWlCLEVBQUUsTUFBTSxDQUFDO0lBRTFDLDREQUE0RDtJQUM1RCxTQUFnQixPQUFPLEVBQUUsT0FBTyxDQUFDO0lBRWpDLG9HQUFvRztJQUNwRyxTQUFnQixhQUFhLEVBQUUsTUFBTSxHQUFHLFNBQVMsQ0FBQztJQUVsRCw0RUFBNEU7SUFDNUUsU0FBZ0IsaUJBQWlCLEVBQUUsTUFBTSxDQUFDO0lBRTFDLFlBQ0UsSUFBSSxFQUFFO1FBQ0osb0JBQW9CLEVBQUUsTUFBTSxDQUFDO1FBQzdCLGlCQUFpQixFQUFFLE1BQU0sQ0FBQztRQUMxQixnQkFBZ0IsRUFBRSxNQUFNLENBQUM7UUFDekIsa0JBQWtCLENBQUMsRUFBRSxNQUFNLENBQUM7UUFDNUIsZUFBZSxDQUFDLEVBQUUsTUFBTSxDQUFDO1FBQ3pCLE9BQU8sRUFBRSxPQUFPLENBQUM7S0FDbEIsRUFDZ0IsT0FBTyxDQUFDLDhCQUFrQixFQUMxQixHQUFHLHVDQUFzQyxFQTJFM0Q7SUFFTSxpQkFBaUIsQ0FDdEIsS0FBSyxFQUFFLE9BQU8sQ0FBQyxjQUFjLEVBQUUsY0FBYyxDQUFDLE9BQU8sR0FBRyxjQUFjLENBQUMsSUFBSSxHQUFHLGNBQWMsQ0FBQyxhQUFhLENBQUMsR0FDMUcsU0FBUyxDQUFDO0lBQ04saUJBQWlCLENBQ3RCLEtBQUssRUFBRSxPQUFPLENBQUMsY0FBYyxFQUFFLGNBQWMsQ0FBQyxPQUFPLEdBQUcsY0FBYyxDQUFDLElBQUksR0FBRyxjQUFjLENBQUMsYUFBYSxDQUFDLEdBQzFHLE1BQU0sQ0FBQztJQUNILGlCQUFpQixDQUFDLEtBQUssRUFBRSxjQUFjLEdBQUcsTUFBTSxHQUFHLFNBQVMsQ0FBQztJQTJCN0QsY0FBYyxDQUFDLFFBQVEsRUFBRSxjQUFjLEVBQUUsZUFBZSxFQUFFLE1BQU0sUUFpQnRFO0lBRUQ7Ozs7Ozs7O09BUUc7SUFDSSxpQkFBaUIsQ0FDdEIsZUFBZSxFQUFFLE1BQU0sR0FFckI7UUFBRSxRQUFRLEVBQUUsSUFBSSxDQUFDO1FBQUMsUUFBUSxFQUFFLFNBQVMsQ0FBQztRQUFDLFdBQVcsRUFBRSxJQUFJLENBQUE7S0FBRSxHQUMxRDtRQUFFLFFBQVEsRUFBRSxLQUFLLENBQUM7UUFBQyxRQUFRLEVBQUUsU0FBUyxDQUFDO1FBQUMsV0FBVyxFQUFFLEtBQUssQ0FBQTtLQUFFLEdBQzVEO1FBQUUsUUFBUSxFQUFFLE9BQU8sQ0FBQztRQUFDLFFBQVEsRUFBRSxNQUFNLENBQUM7UUFBQyxXQUFXLEVBQUUsT0FBTyxDQUFBO0tBQUUsQ0F3RGhFO0NBQ0YifQ==
91
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidGltZXRhYmxlLmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvc2VxdWVuY2VyL3RpbWV0YWJsZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssRUFBRSxNQUFNLEVBQUUsTUFBTSx1QkFBdUIsQ0FBQztBQVNwRCxPQUFPLEtBQUssRUFBRSxnQkFBZ0IsRUFBRSxNQUFNLGNBQWMsQ0FBQztBQUNyRCxPQUFPLEVBQUUsY0FBYyxFQUFFLE1BQU0sWUFBWSxDQUFDO0FBRTVDLHFCQUFhLGtCQUFrQjtJQXlFM0IsT0FBTyxDQUFDLFFBQVEsQ0FBQyxPQUFPLENBQUM7SUFDekIsT0FBTyxDQUFDLFFBQVEsQ0FBQyxHQUFHLENBQUM7SUF6RXZCOzs7O09BSUc7SUFDSCxTQUFnQixrQkFBa0IsRUFBRSxNQUFNLENBQUM7SUFFM0M7Ozs7T0FJRztJQUNILFNBQWdCLG9CQUFvQixFQUFFLE1BQU0sQ0FBQztJQUU3Qzs7Ozs7T0FLRztJQUNILFNBQWdCLDBCQUEwQixFQUFFLE1BQU0sQ0FBQztJQUVuRDs7OztPQUlHO0lBQ0gsU0FBZ0IsZ0JBQWdCLEVBQUUsTUFBTSxDQUFDO0lBRXpDOzs7T0FHRztJQUNILFNBQWdCLGdCQUFnQixFQUFFLE1BQU0sQ0FBc0I7SUFFOUQsdURBQXVEO0lBQ3ZELFNBQWdCLDRCQUE0QixFQUFFLE1BQU0sQ0FBa0M7SUFFdEYsbUdBQW1HO0lBQ25HLFNBQWdCLGtCQUFrQixFQUFFLE1BQU0sQ0FBQztJQUUzQyxtRkFBbUY7SUFDbkYsU0FBZ0Isc0JBQXNCLEVBQUUsTUFBTSxDQUE0QjtJQUUxRSx3Q0FBd0M7SUFDeEMsU0FBZ0Isb0JBQW9CLEVBQUUsTUFBTSxDQUFDO0lBRTdDLGtGQUFrRjtJQUNsRixTQUFnQixpQkFBaUIsRUFBRSxNQUFNLENBQUM7SUFFMUMsNERBQTREO0lBQzVELFNBQWdCLE9BQU8sRUFBRSxPQUFPLENBQUM7SUFFakMsb0dBQW9HO0lBQ3BHLFNBQWdCLGFBQWEsRUFBRSxNQUFNLEdBQUcsU0FBUyxDQUFDO0lBRWxELDRFQUE0RTtJQUM1RSxTQUFnQixpQkFBaUIsRUFBRSxNQUFNLENBQUM7SUFFMUMscUZBQXFGO0lBQ3JGLFNBQWdCLFVBQVUsRUFBRSxPQUFPLENBQUM7SUFFcEMsWUFDRSxJQUFJLEVBQUU7UUFDSixvQkFBb0IsRUFBRSxNQUFNLENBQUM7UUFDN0IsaUJBQWlCLEVBQUUsTUFBTSxDQUFDO1FBQzFCLGdCQUFnQixFQUFFLE1BQU0sQ0FBQztRQUN6QixrQkFBa0IsQ0FBQyxFQUFFLE1BQU0sQ0FBQztRQUM1QixlQUFlLENBQUMsRUFBRSxNQUFNLENBQUM7UUFDekIsT0FBTyxFQUFFLE9BQU8sQ0FBQztRQUNqQixVQUFVLENBQUMsRUFBRSxPQUFPLENBQUM7S0FDdEIsRUFDZ0IsT0FBTyxDQUFDLDhCQUFrQixFQUMxQixHQUFHLENBQUMsb0JBQVEsRUFrRjlCO0lBRU0saUJBQWlCLENBQ3RCLEtBQUssRUFBRSxPQUFPLENBQUMsY0FBYyxFQUFFLGNBQWMsQ0FBQyxPQUFPLEdBQUcsY0FBYyxDQUFDLElBQUksR0FBRyxjQUFjLENBQUMsYUFBYSxDQUFDLEdBQzFHLFNBQVMsQ0FBQztJQUNOLGlCQUFpQixDQUN0QixLQUFLLEVBQUUsT0FBTyxDQUFDLGNBQWMsRUFBRSxjQUFjLENBQUMsT0FBTyxHQUFHLGNBQWMsQ0FBQyxJQUFJLEdBQUcsY0FBYyxDQUFDLGFBQWEsQ0FBQyxHQUMxRyxNQUFNLENBQUM7SUFDSCxpQkFBaUIsQ0FBQyxLQUFLLEVBQUUsY0FBYyxHQUFHLE1BQU0sR0FBRyxTQUFTLENBQUM7SUEyQjdELGNBQWMsQ0FBQyxRQUFRLEVBQUUsY0FBYyxFQUFFLGVBQWUsRUFBRSxNQUFNLFFBaUJ0RTtJQUVEOzs7Ozs7OztPQVFHO0lBQ0ksaUJBQWlCLENBQ3RCLGVBQWUsRUFBRSxNQUFNLEdBRXJCO1FBQUUsUUFBUSxFQUFFLElBQUksQ0FBQztRQUFDLFFBQVEsRUFBRSxTQUFTLENBQUM7UUFBQyxXQUFXLEVBQUUsSUFBSSxDQUFBO0tBQUUsR0FDMUQ7UUFBRSxRQUFRLEVBQUUsS0FBSyxDQUFDO1FBQUMsUUFBUSxFQUFFLFNBQVMsQ0FBQztRQUFDLFdBQVcsRUFBRSxLQUFLLENBQUE7S0FBRSxHQUM1RDtRQUFFLFFBQVEsRUFBRSxPQUFPLENBQUM7UUFBQyxRQUFRLEVBQUUsTUFBTSxDQUFDO1FBQUMsV0FBVyxFQUFFLE9BQU8sQ0FBQTtLQUFFLENBd0RoRTtDQUNGIn0=
@@ -1 +1 @@
1
- {"version":3,"file":"timetable.d.ts","sourceRoot":"","sources":["../../src/sequencer/timetable.ts"],"names":[],"mappings":"AASA,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;IArEtB;;;;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,uCAAsC,EA2E3D;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":"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;IAyE3B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC;IACzB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC;IAzEvB;;;;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,qFAAqF;IACrF,SAAgB,UAAU,EAAE,OAAO,CAAC;IAEpC,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;QACjB,UAAU,CAAC,EAAE,OAAO,CAAC;KACtB,EACgB,OAAO,CAAC,8BAAkB,EAC1B,GAAG,CAAC,oBAAQ,EAkF9B;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"}