@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.
- package/dest/client/sequencer-client.d.ts +15 -7
- package/dest/client/sequencer-client.d.ts.map +1 -1
- package/dest/client/sequencer-client.js +60 -30
- package/dest/config.d.ts +26 -6
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +44 -21
- package/dest/global_variable_builder/global_builder.d.ts +15 -11
- package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
- package/dest/global_variable_builder/global_builder.js +29 -25
- package/dest/global_variable_builder/index.d.ts +2 -2
- package/dest/global_variable_builder/index.d.ts.map +1 -1
- package/dest/publisher/config.d.ts +47 -17
- package/dest/publisher/config.d.ts.map +1 -1
- package/dest/publisher/config.js +121 -42
- package/dest/publisher/index.d.ts +2 -1
- package/dest/publisher/index.d.ts.map +1 -1
- package/dest/publisher/l1_tx_failed_store/factory.d.ts +11 -0
- package/dest/publisher/l1_tx_failed_store/factory.d.ts.map +1 -0
- package/dest/publisher/l1_tx_failed_store/factory.js +22 -0
- package/dest/publisher/l1_tx_failed_store/failed_tx_store.d.ts +59 -0
- package/dest/publisher/l1_tx_failed_store/failed_tx_store.d.ts.map +1 -0
- package/dest/publisher/l1_tx_failed_store/failed_tx_store.js +1 -0
- package/dest/publisher/l1_tx_failed_store/file_store_failed_tx_store.d.ts +15 -0
- package/dest/publisher/l1_tx_failed_store/file_store_failed_tx_store.d.ts.map +1 -0
- package/dest/publisher/l1_tx_failed_store/file_store_failed_tx_store.js +34 -0
- package/dest/publisher/l1_tx_failed_store/index.d.ts +4 -0
- package/dest/publisher/l1_tx_failed_store/index.d.ts.map +1 -0
- package/dest/publisher/l1_tx_failed_store/index.js +2 -0
- package/dest/publisher/sequencer-publisher-factory.d.ts +11 -5
- package/dest/publisher/sequencer-publisher-factory.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher-factory.js +27 -3
- package/dest/publisher/sequencer-publisher.d.ts +82 -37
- package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher.js +430 -118
- package/dest/sequencer/checkpoint_proposal_job.d.ts +36 -9
- package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -1
- package/dest/sequencer/checkpoint_proposal_job.js +361 -192
- package/dest/sequencer/checkpoint_voter.d.ts +1 -2
- package/dest/sequencer/checkpoint_voter.d.ts.map +1 -1
- package/dest/sequencer/checkpoint_voter.js +2 -5
- package/dest/sequencer/events.d.ts +2 -1
- package/dest/sequencer/events.d.ts.map +1 -1
- package/dest/sequencer/metrics.d.ts +21 -5
- package/dest/sequencer/metrics.d.ts.map +1 -1
- package/dest/sequencer/metrics.js +97 -15
- package/dest/sequencer/sequencer.d.ts +40 -17
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +152 -95
- package/dest/sequencer/timetable.d.ts +7 -3
- package/dest/sequencer/timetable.d.ts.map +1 -1
- package/dest/sequencer/timetable.js +21 -12
- package/dest/sequencer/types.d.ts +2 -2
- package/dest/sequencer/types.d.ts.map +1 -1
- package/dest/test/index.d.ts +3 -5
- package/dest/test/index.d.ts.map +1 -1
- package/dest/test/mock_checkpoint_builder.d.ts +11 -11
- package/dest/test/mock_checkpoint_builder.d.ts.map +1 -1
- package/dest/test/mock_checkpoint_builder.js +45 -34
- package/dest/test/utils.d.ts +3 -3
- package/dest/test/utils.d.ts.map +1 -1
- package/dest/test/utils.js +5 -4
- package/package.json +27 -28
- package/src/client/sequencer-client.ts +76 -30
- package/src/config.ts +56 -27
- package/src/global_variable_builder/global_builder.ts +38 -27
- package/src/global_variable_builder/index.ts +1 -1
- package/src/publisher/config.ts +153 -43
- package/src/publisher/index.ts +3 -0
- package/src/publisher/l1_tx_failed_store/factory.ts +32 -0
- package/src/publisher/l1_tx_failed_store/failed_tx_store.ts +55 -0
- package/src/publisher/l1_tx_failed_store/file_store_failed_tx_store.ts +46 -0
- package/src/publisher/l1_tx_failed_store/index.ts +3 -0
- package/src/publisher/sequencer-publisher-factory.ts +38 -9
- package/src/publisher/sequencer-publisher.ts +503 -168
- package/src/sequencer/README.md +81 -12
- package/src/sequencer/checkpoint_proposal_job.ts +471 -201
- package/src/sequencer/checkpoint_voter.ts +1 -12
- package/src/sequencer/events.ts +1 -1
- package/src/sequencer/metrics.ts +106 -18
- package/src/sequencer/sequencer.ts +216 -109
- package/src/sequencer/timetable.ts +26 -15
- package/src/sequencer/types.ts +1 -1
- package/src/test/index.ts +2 -4
- package/src/test/mock_checkpoint_builder.ts +63 -49
- 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 {
|
|
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, '
|
|
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
|
|
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.
|
|
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,
|
|
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,
|
|
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 ||
|
|
565
|
-
this.logStrategyComparison(
|
|
566
|
-
this.lastEpochForStrategyComparison =
|
|
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,
|
|
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 >=
|
|
579
|
-
this.log.trace(`
|
|
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,
|
|
583
|
-
if (this.lastCheckpointProposed && this.lastCheckpointProposed.header.slotNumber >=
|
|
584
|
-
this.log.trace(`Slot ${
|
|
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
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
623
|
-
|
|
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
|
|
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(
|
|
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
|
-
//
|
|
639
|
-
|
|
640
|
-
|
|
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.
|
|
648
|
+
block: syncedTo.blockData.header.toInspect()
|
|
643
649
|
});
|
|
644
|
-
this.metrics.
|
|
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
|
-
|
|
662
|
-
//
|
|
663
|
-
//
|
|
664
|
-
|
|
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.
|
|
701
|
+
this.metrics.recordCheckpointPrecheckFailed('rollup_contract_check_failed');
|
|
672
702
|
return undefined;
|
|
673
703
|
}
|
|
674
|
-
if (canProposeCheck.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 ${
|
|
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:
|
|
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.
|
|
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.
|
|
727
|
+
this.metrics.recordCheckpointPrecheckFailed('block_number_mismatch');
|
|
698
728
|
return undefined;
|
|
699
729
|
}
|
|
700
|
-
this.lastSlotForCheckpointProposalJob =
|
|
701
|
-
await this.p2pClient.prepareForSlot(
|
|
702
|
-
this.log.info(`Preparing checkpoint proposal ${checkpointNumber}
|
|
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(
|
|
738
|
+
return this.createCheckpointProposalJob(slot, targetSlot, targetEpoch, checkpointNumber, syncedTo.blockNumber, proposer, publisher, attestorAddress, invalidateCheckpoint, syncedTo.proposedCheckpointData);
|
|
708
739
|
}
|
|
709
|
-
createCheckpointProposalJob(
|
|
710
|
-
return new CheckpointProposalJob(
|
|
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
|
|
753
|
-
//
|
|
754
|
-
//
|
|
755
|
-
const
|
|
756
|
-
const { slot
|
|
757
|
-
if (
|
|
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
|
-
|
|
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)=>
|
|
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)=>
|
|
773
|
-
|
|
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,
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
841
|
+
hasProposedCheckpoint: false,
|
|
842
|
+
syncedL2Slot,
|
|
798
843
|
pendingChainValidationStatus
|
|
799
844
|
};
|
|
800
845
|
}
|
|
801
|
-
const
|
|
802
|
-
if (!
|
|
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
|
-
|
|
809
|
-
blockNumber:
|
|
810
|
-
checkpointNumber:
|
|
811
|
-
|
|
812
|
-
|
|
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(
|
|
868
|
+
*/ async checkCanPropose(targetSlot) {
|
|
820
869
|
let proposer;
|
|
821
870
|
try {
|
|
822
|
-
proposer = await this.epochCache.getProposerAttesterAddressInSlot(
|
|
871
|
+
proposer = await this.epochCache.getProposerAttesterAddressInSlot(targetSlot);
|
|
823
872
|
} catch (e) {
|
|
824
873
|
if (e instanceof NoCommitteeError) {
|
|
825
|
-
if (this.lastSlotForNoCommitteeWarning !==
|
|
826
|
-
this.lastSlotForNoCommitteeWarning =
|
|
827
|
-
this.log.warn(`Cannot propose at
|
|
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 ${
|
|
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 ${
|
|
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(
|
|
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(
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
91
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidGltZXRhYmxlLmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvc2VxdWVuY2VyL3RpbWV0YWJsZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssRUFBRSxNQUFNLEVBQUUsTUFBTSx1QkFBdUIsQ0FBQztBQVNwRCxPQUFPLEtBQUssRUFBRSxnQkFBZ0IsRUFBRSxNQUFNLGNBQWMsQ0FBQztBQUNyRCxPQUFPLEVBQUUsY0FBYyxFQUFFLE1BQU0sWUFBWSxDQUFDO0FBRTVDLHFCQUFhLGtCQUFrQjtJQXlFM0IsT0FBTyxDQUFDLFFBQVEsQ0FBQyxPQUFPLENBQUM7SUFDekIsT0FBTyxDQUFDLFFBQVEsQ0FBQyxHQUFHLENBQUM7SUF6RXZCOzs7O09BSUc7SUFDSCxTQUFnQixrQkFBa0IsRUFBRSxNQUFNLENBQUM7SUFFM0M7Ozs7T0FJRztJQUNILFNBQWdCLG9CQUFvQixFQUFFLE1BQU0sQ0FBQztJQUU3Qzs7Ozs7T0FLRztJQUNILFNBQWdCLDBCQUEwQixFQUFFLE1BQU0sQ0FBQztJQUVuRDs7OztPQUlHO0lBQ0gsU0FBZ0IsZ0JBQWdCLEVBQUUsTUFBTSxDQUFDO0lBRXpDOzs7T0FHRztJQUNILFNBQWdCLGdCQUFnQixFQUFFLE1BQU0sQ0FBc0I7SUFFOUQsdURBQXVEO0lBQ3ZELFNBQWdCLDRCQUE0QixFQUFFLE1BQU0sQ0FBa0M7SUFFdEYsbUdBQW1HO0lBQ25HLFNBQWdCLGtCQUFrQixFQUFFLE1BQU0sQ0FBQztJQUUzQyxtRkFBbUY7SUFDbkYsU0FBZ0Isc0JBQXNCLEVBQUUsTUFBTSxDQUE0QjtJQUUxRSx3Q0FBd0M7SUFDeEMsU0FBZ0Isb0JBQW9CLEVBQUUsTUFBTSxDQUFDO0lBRTdDLGtGQUFrRjtJQUNsRixTQUFnQixpQkFBaUIsRUFBRSxNQUFNLENBQUM7SUFFMUMsNERBQTREO0lBQzVELFNBQWdCLE9BQU8sRUFBRSxPQUFPLENBQUM7SUFFakMsb0dBQW9HO0lBQ3BHLFNBQWdCLGFBQWEsRUFBRSxNQUFNLEdBQUcsU0FBUyxDQUFDO0lBRWxELDRFQUE0RTtJQUM1RSxTQUFnQixpQkFBaUIsRUFBRSxNQUFNLENBQUM7SUFFMUMscUZBQXFGO0lBQ3JGLFNBQWdCLFVBQVUsRUFBRSxPQUFPLENBQUM7SUFFcEMsWUFDRSxJQUFJLEVBQUU7UUFDSixvQkFBb0IsRUFBRSxNQUFNLENBQUM7UUFDN0IsaUJBQWlCLEVBQUUsTUFBTSxDQUFDO1FBQzFCLGdCQUFnQixFQUFFLE1BQU0sQ0FBQztRQUN6QixrQkFBa0IsQ0FBQyxFQUFFLE1BQU0sQ0FBQztRQUM1QixlQUFlLENBQUMsRUFBRSxNQUFNLENBQUM7UUFDekIsT0FBTyxFQUFFLE9BQU8sQ0FBQztRQUNqQixVQUFVLENBQUMsRUFBRSxPQUFPLENBQUM7S0FDdEIsRUFDZ0IsT0FBTyxDQUFDLDhCQUFrQixFQUMxQixHQUFHLENBQUMsb0JBQVEsRUFrRjlCO0lBRU0saUJBQWlCLENBQ3RCLEtBQUssRUFBRSxPQUFPLENBQUMsY0FBYyxFQUFFLGNBQWMsQ0FBQyxPQUFPLEdBQUcsY0FBYyxDQUFDLElBQUksR0FBRyxjQUFjLENBQUMsYUFBYSxDQUFDLEdBQzFHLFNBQVMsQ0FBQztJQUNOLGlCQUFpQixDQUN0QixLQUFLLEVBQUUsT0FBTyxDQUFDLGNBQWMsRUFBRSxjQUFjLENBQUMsT0FBTyxHQUFHLGNBQWMsQ0FBQyxJQUFJLEdBQUcsY0FBYyxDQUFDLGFBQWEsQ0FBQyxHQUMxRyxNQUFNLENBQUM7SUFDSCxpQkFBaUIsQ0FBQyxLQUFLLEVBQUUsY0FBYyxHQUFHLE1BQU0sR0FBRyxTQUFTLENBQUM7SUEyQjdELGNBQWMsQ0FBQyxRQUFRLEVBQUUsY0FBYyxFQUFFLGVBQWUsRUFBRSxNQUFNLFFBaUJ0RTtJQUVEOzs7Ozs7OztPQVFHO0lBQ0ksaUJBQWlCLENBQ3RCLGVBQWUsRUFBRSxNQUFNLEdBRXJCO1FBQUUsUUFBUSxFQUFFLElBQUksQ0FBQztRQUFDLFFBQVEsRUFBRSxTQUFTLENBQUM7UUFBQyxXQUFXLEVBQUUsSUFBSSxDQUFBO0tBQUUsR0FDMUQ7UUFBRSxRQUFRLEVBQUUsS0FBSyxDQUFDO1FBQUMsUUFBUSxFQUFFLFNBQVMsQ0FBQztRQUFDLFdBQVcsRUFBRSxLQUFLLENBQUE7S0FBRSxHQUM1RDtRQUFFLFFBQVEsRUFBRSxPQUFPLENBQUM7UUFBQyxRQUFRLEVBQUUsTUFBTSxDQUFDO1FBQUMsV0FBVyxFQUFFLE9BQU8sQ0FBQTtLQUFFLENBd0RoRTtDQUNGIn0=
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"timetable.d.ts","sourceRoot":"","sources":["../../src/sequencer/timetable.ts"],"names":[],"mappings":"
|
|
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"}
|