@aztec/sequencer-client 0.0.1-commit.ec5f612 → 0.0.1-commit.ec7ac5448
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 +6 -1
- package/dest/client/sequencer-client.d.ts.map +1 -1
- package/dest/client/sequencer-client.js +49 -27
- package/dest/config.d.ts +25 -5
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +31 -22
- package/dest/global_variable_builder/global_builder.d.ts +15 -8
- package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
- package/dest/global_variable_builder/global_builder.js +26 -26
- 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 +13 -1
- package/dest/publisher/config.d.ts.map +1 -1
- package/dest/publisher/config.js +17 -2
- package/dest/publisher/sequencer-publisher-factory.d.ts +3 -5
- package/dest/publisher/sequencer-publisher-factory.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher-factory.js +16 -3
- package/dest/publisher/sequencer-publisher.d.ts +55 -43
- package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher.js +180 -116
- package/dest/sequencer/chain_state_overrides.d.ts +25 -0
- package/dest/sequencer/chain_state_overrides.d.ts.map +1 -0
- package/dest/sequencer/chain_state_overrides.js +39 -0
- package/dest/sequencer/checkpoint_proposal_job.d.ts +24 -9
- package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -1
- package/dest/sequencer/checkpoint_proposal_job.js +331 -211
- 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 +9 -2
- package/dest/sequencer/metrics.d.ts.map +1 -1
- package/dest/sequencer/metrics.js +23 -1
- package/dest/sequencer/sequencer.d.ts +28 -11
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +134 -74
- package/dest/sequencer/timetable.d.ts +17 -3
- package/dest/sequencer/timetable.d.ts.map +1 -1
- package/dest/sequencer/timetable.js +51 -43
- package/dest/sequencer/types.d.ts +2 -2
- package/dest/sequencer/types.d.ts.map +1 -1
- package/dest/test/mock_checkpoint_builder.d.ts +7 -9
- package/dest/test/mock_checkpoint_builder.d.ts.map +1 -1
- package/dest/test/mock_checkpoint_builder.js +39 -30
- package/package.json +27 -28
- package/src/client/sequencer-client.ts +61 -28
- package/src/config.ts +39 -24
- package/src/global_variable_builder/global_builder.ts +30 -27
- package/src/global_variable_builder/index.ts +1 -1
- package/src/publisher/config.ts +32 -0
- package/src/publisher/sequencer-publisher-factory.ts +18 -6
- package/src/publisher/sequencer-publisher.ts +263 -167
- package/src/sequencer/README.md +83 -13
- package/src/sequencer/chain_state_overrides.ts +87 -0
- package/src/sequencer/checkpoint_proposal_job.ts +429 -239
- package/src/sequencer/checkpoint_voter.ts +1 -12
- package/src/sequencer/events.ts +1 -1
- package/src/sequencer/metrics.ts +29 -1
- package/src/sequencer/sequencer.ts +194 -81
- package/src/sequencer/timetable.ts +64 -52
- package/src/sequencer/types.ts +1 -1
- package/src/test/mock_checkpoint_builder.ts +51 -48
|
@@ -373,14 +373,14 @@ function _apply_decs_2203_r(targetClass, memberDecs, classDecs, parentClass) {
|
|
|
373
373
|
var _dec, _dec1, _dec2, _dec3, _initProto;
|
|
374
374
|
import { getKzg } from '@aztec/blob-lib';
|
|
375
375
|
import { INITIAL_L2_BLOCK_NUM } from '@aztec/constants';
|
|
376
|
-
import { NoCommitteeError } from '@aztec/ethereum/contracts';
|
|
376
|
+
import { NoCommitteeError, SimulationOverridesBuilder } from '@aztec/ethereum/contracts';
|
|
377
377
|
import { BlockNumber, CheckpointNumber } from '@aztec/foundation/branded-types';
|
|
378
378
|
import { merge, omit, pick } from '@aztec/foundation/collection';
|
|
379
379
|
import { Fr } from '@aztec/foundation/curves/bn254';
|
|
380
380
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
381
381
|
import { createLogger } from '@aztec/foundation/log';
|
|
382
382
|
import { RunningPromise } from '@aztec/foundation/running-promise';
|
|
383
|
-
import {
|
|
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,6 +454,7 @@ _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
459
|
/** Config for the sequencer */ config;
|
|
459
460
|
constructor(publisherFactory, validatorClient, globalsBuilder, p2pClient, worldState, slasherClient, l2BlockSource, l1ToL2MessageSource, checkpointsBuilder, l1Constants, dateProvider, epochCache, rollupContract, config, telemetry = getTelemetryClient(), log = createLogger('sequencer')){
|
|
@@ -467,15 +468,17 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
|
|
|
467
468
|
}
|
|
468
469
|
/** Updates sequencer config by the defined values and updates the timetable */ updateConfig(config) {
|
|
469
470
|
const filteredConfig = pickFromSchema(config, SequencerConfigSchema);
|
|
470
|
-
this.log.info(`Updated sequencer config`, omit(filteredConfig, '
|
|
471
|
+
this.log.info(`Updated sequencer config`, omit(filteredConfig, 'txPublicSetupAllowListExtend'));
|
|
471
472
|
this.config = merge(this.config, filteredConfig);
|
|
473
|
+
const p2pPropagationTime = this.config.attestationPropagationTime;
|
|
472
474
|
this.timetable = new SequencerTimetable({
|
|
473
475
|
ethereumSlotDuration: this.l1Constants.ethereumSlotDuration,
|
|
474
476
|
aztecSlotDuration: this.aztecSlotDuration,
|
|
475
477
|
l1PublishingTime: this.l1PublishingTime,
|
|
476
|
-
p2pPropagationTime
|
|
478
|
+
p2pPropagationTime,
|
|
477
479
|
blockDurationMs: this.config.blockDurationMs,
|
|
478
|
-
enforce: this.config.enforceTimeTable
|
|
480
|
+
enforce: this.config.enforceTimeTable,
|
|
481
|
+
pipelining: this.epochCache.isProposerPipeliningEnabled()
|
|
479
482
|
}, this.metrics, this.log);
|
|
480
483
|
}
|
|
481
484
|
/** Initializes the sequencer (precomputes tables). Takes about 3s. */ init() {
|
|
@@ -489,13 +492,17 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
|
|
|
489
492
|
this.runningPromise.start();
|
|
490
493
|
this.log.info('Started sequencer');
|
|
491
494
|
}
|
|
495
|
+
/** Triggers an immediate run of the sequencer, bypassing the polling interval. */ trigger() {
|
|
496
|
+
return this.runningPromise?.trigger();
|
|
497
|
+
}
|
|
492
498
|
/** Stops the sequencer from building blocks and moves to STOPPED state. */ async stop() {
|
|
493
499
|
this.log.info(`Stopping sequencer`);
|
|
494
500
|
this.setState(SequencerState.STOPPING, undefined, {
|
|
495
501
|
force: true
|
|
496
502
|
});
|
|
497
|
-
this.publisherFactory.
|
|
503
|
+
await this.publisherFactory.stopAll();
|
|
498
504
|
await this.runningPromise?.stop();
|
|
505
|
+
await this.lastCheckpointProposalJob?.awaitPendingSubmission();
|
|
499
506
|
this.setState(SequencerState.STOPPED, undefined, {
|
|
500
507
|
force: true
|
|
501
508
|
});
|
|
@@ -539,22 +546,25 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
|
|
|
539
546
|
* - Submit checkpoint
|
|
540
547
|
*/ async work() {
|
|
541
548
|
this.setState(SequencerState.SYNCHRONIZING, undefined);
|
|
542
|
-
const { slot, ts,
|
|
549
|
+
const { slot, ts, nowSeconds, epoch } = this.epochCache.getEpochAndSlotInNextL1Slot();
|
|
550
|
+
const { slot: targetSlot, epoch: targetEpoch } = this.epochCache.getTargetEpochAndSlotInNextL1Slot();
|
|
543
551
|
// Check if we are synced and it's our slot, grab a publisher, check previous block invalidation, etc
|
|
544
|
-
const checkpointProposalJob = await this.prepareCheckpointProposal(epoch,
|
|
552
|
+
const checkpointProposalJob = await this.prepareCheckpointProposal(slot, targetSlot, epoch, targetEpoch, ts, nowSeconds);
|
|
545
553
|
if (!checkpointProposalJob) {
|
|
546
554
|
return;
|
|
547
555
|
}
|
|
556
|
+
// Track the job so we can await its pending L1 submission during shutdown
|
|
557
|
+
this.lastCheckpointProposalJob = checkpointProposalJob;
|
|
548
558
|
// Execute the checkpoint proposal job
|
|
549
559
|
const checkpoint = await checkpointProposalJob.execute();
|
|
550
560
|
// Update last checkpoint proposed (currently unused)
|
|
551
561
|
if (checkpoint) {
|
|
552
562
|
this.lastCheckpointProposed = checkpoint;
|
|
553
563
|
}
|
|
554
|
-
// Log fee strategy comparison if on fisherman
|
|
555
|
-
if (this.config.fishermanMode && (this.lastEpochForStrategyComparison === undefined ||
|
|
556
|
-
this.logStrategyComparison(
|
|
557
|
-
this.lastEpochForStrategyComparison =
|
|
564
|
+
// Log fee strategy comparison if on fisherman (uses target epoch since we mirror the proposer's perspective)
|
|
565
|
+
if (this.config.fishermanMode && (this.lastEpochForStrategyComparison === undefined || targetEpoch > this.lastEpochForStrategyComparison)) {
|
|
566
|
+
this.logStrategyComparison(targetEpoch, checkpointProposalJob.getPublisher());
|
|
567
|
+
this.lastEpochForStrategyComparison = targetEpoch;
|
|
558
568
|
}
|
|
559
569
|
return checkpoint;
|
|
560
570
|
}
|
|
@@ -562,17 +572,17 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
|
|
|
562
572
|
* Prepares the checkpoint proposal by performing all necessary checks and setup.
|
|
563
573
|
* This is the initial step in the main loop.
|
|
564
574
|
* @returns CheckpointProposalJob if successful, undefined if we are not yet synced or are not the proposer.
|
|
565
|
-
*/ async prepareCheckpointProposal(epoch,
|
|
566
|
-
// Check we have not already processed this slot (cheapest check)
|
|
575
|
+
*/ async prepareCheckpointProposal(slot, targetSlot, epoch, targetEpoch, ts, nowSeconds) {
|
|
576
|
+
// Check we have not already processed this target slot (cheapest check)
|
|
567
577
|
// We only check this if enforce timetable is set, since we want to keep processing the same slot if we are not
|
|
568
578
|
// running against actual time (eg when we use sandbox-style automining)
|
|
569
|
-
if (this.lastSlotForCheckpointProposalJob && this.lastSlotForCheckpointProposalJob >=
|
|
570
|
-
this.log.trace(`
|
|
579
|
+
if (this.lastSlotForCheckpointProposalJob && this.lastSlotForCheckpointProposalJob >= targetSlot && this.config.enforceTimeTable) {
|
|
580
|
+
this.log.trace(`Target slot ${targetSlot} has already been processed`);
|
|
571
581
|
return undefined;
|
|
572
582
|
}
|
|
573
|
-
// But if we have already proposed for this slot,
|
|
574
|
-
if (this.lastCheckpointProposed && this.lastCheckpointProposed.header.slotNumber >=
|
|
575
|
-
this.log.trace(`Slot ${
|
|
583
|
+
// But if we have already proposed for this slot, then we definitely have to skip it, automining or not
|
|
584
|
+
if (this.lastCheckpointProposed && this.lastCheckpointProposed.header.slotNumber >= targetSlot) {
|
|
585
|
+
this.log.trace(`Slot ${targetSlot} has already been published as checkpoint ${this.lastCheckpointProposed.number}`);
|
|
576
586
|
return undefined;
|
|
577
587
|
}
|
|
578
588
|
// Check all components are synced to latest as seen by the archiver (queries all subsystems)
|
|
@@ -583,16 +593,18 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
|
|
|
583
593
|
if (!syncedTo) {
|
|
584
594
|
await this.tryVoteWhenSyncFails({
|
|
585
595
|
slot,
|
|
596
|
+
targetSlot,
|
|
586
597
|
ts
|
|
587
598
|
});
|
|
588
599
|
return undefined;
|
|
589
600
|
}
|
|
590
|
-
// If escape hatch is open for
|
|
601
|
+
// If escape hatch is open for the target epoch, do not start checkpoint proposal work and do not attempt invalidations.
|
|
591
602
|
// Still perform governance/slashing voting (as proposer) once per slot.
|
|
592
|
-
|
|
603
|
+
// When pipelining, we check the target epoch (slot+1's epoch) since that's the epoch we're building for.
|
|
604
|
+
const isEscapeHatchOpen = await this.epochCache.isEscapeHatchOpen(targetEpoch);
|
|
593
605
|
if (isEscapeHatchOpen) {
|
|
594
606
|
this.setState(SequencerState.PROPOSER_CHECK, slot);
|
|
595
|
-
const [canPropose, proposer] = await this.checkCanPropose(
|
|
607
|
+
const [canPropose, proposer] = await this.checkCanPropose(targetSlot);
|
|
596
608
|
if (canPropose) {
|
|
597
609
|
await this.tryVoteWhenEscapeHatchOpen({
|
|
598
610
|
slot,
|
|
@@ -610,25 +622,32 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
|
|
|
610
622
|
// Next checkpoint follows from the last synced one
|
|
611
623
|
const checkpointNumber = CheckpointNumber(syncedTo.checkpointNumber + 1);
|
|
612
624
|
const logCtx = {
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
syncedToL2Slot: getSlotAtTimestamp(syncedTo.l1Timestamp, this.l1Constants),
|
|
625
|
+
nowSeconds,
|
|
626
|
+
syncedToL2Slot: syncedTo.syncedL2Slot,
|
|
616
627
|
slot,
|
|
628
|
+
targetSlot,
|
|
617
629
|
slotTs: ts,
|
|
618
630
|
checkpointNumber,
|
|
619
631
|
isPendingChainValid: pick(syncedTo.pendingChainValidationStatus, 'valid', 'reason', 'invalidIndex')
|
|
620
632
|
};
|
|
621
|
-
// Check that we are a proposer for the
|
|
633
|
+
// Check that we are a proposer for the target slot.
|
|
622
634
|
this.setState(SequencerState.PROPOSER_CHECK, slot);
|
|
623
|
-
const [canPropose, proposer] = await this.checkCanPropose(
|
|
635
|
+
const [canPropose, proposer] = await this.checkCanPropose(targetSlot);
|
|
624
636
|
// If we are not a proposer check if we should invalidate an invalid checkpoint, and bail
|
|
625
637
|
if (!canPropose) {
|
|
626
638
|
await this.considerInvalidatingCheckpoint(syncedTo, slot);
|
|
627
639
|
return undefined;
|
|
628
640
|
}
|
|
629
|
-
//
|
|
630
|
-
|
|
631
|
-
|
|
641
|
+
// Guard: don't exceed 1-deep pipeline. Without a proposed checkpoint, we can only build
|
|
642
|
+
// confirmed + 1. With a proposed checkpoint, we can build confirmed + 2.
|
|
643
|
+
const confirmedCkpt = syncedTo.checkpointedCheckpointNumber;
|
|
644
|
+
if (checkpointNumber > confirmedCkpt + 2) {
|
|
645
|
+
this.log.verbose(`Skipping slot ${targetSlot}: checkpoint ${checkpointNumber} exceeds max pipeline depth (confirmed=${confirmedCkpt})`);
|
|
646
|
+
return undefined;
|
|
647
|
+
}
|
|
648
|
+
// Check that the target slot is not taken by a block already (should never happen, since only us can propose for this slot)
|
|
649
|
+
if (syncedTo.blockData && syncedTo.blockData.header.getSlot() >= targetSlot) {
|
|
650
|
+
this.log.warn(`Cannot propose block at target slot ${targetSlot} since that slot was taken by block ${syncedTo.blockNumber}`, {
|
|
632
651
|
...logCtx,
|
|
633
652
|
block: syncedTo.blockData.header.toInspect()
|
|
634
653
|
});
|
|
@@ -648,10 +667,32 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
|
|
|
648
667
|
this.log.debug(`Set proposer address ${proposer} for simulation in fisherman mode`);
|
|
649
668
|
}
|
|
650
669
|
// Prepare invalidation request if the pending chain is invalid (returns undefined if no need)
|
|
651
|
-
|
|
652
|
-
//
|
|
653
|
-
//
|
|
654
|
-
|
|
670
|
+
let invalidateCheckpoint = await publisher.simulateInvalidateCheckpoint(syncedTo.pendingChainValidationStatus);
|
|
671
|
+
// Determine the correct archive and L1 state overrides for the canProposeAt check.
|
|
672
|
+
// The L1 contract reads archives[proposedCheckpointNumber] and compares it with the provided archive.
|
|
673
|
+
// When invalidating or pipelining, the local archive may differ from L1's, so we adjust accordingly.
|
|
674
|
+
let archiveForCheck = syncedTo.archive;
|
|
675
|
+
const l1SimulationOverridesBuilder = new SimulationOverridesBuilder();
|
|
676
|
+
if (this.epochCache.isProposerPipeliningEnabled() && syncedTo.hasProposedCheckpoint) {
|
|
677
|
+
// Parent checkpoint hasn't landed on L1 yet. Override both the proposed checkpoint number
|
|
678
|
+
// and the archive at that checkpoint so L1 simulation sees the correct chain tip.
|
|
679
|
+
const parentCheckpointNumber = CheckpointNumber(checkpointNumber - 1);
|
|
680
|
+
l1SimulationOverridesBuilder.forPendingCheckpoint(parentCheckpointNumber).withPendingArchive(syncedTo.archive);
|
|
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
|
+
l1SimulationOverridesBuilder.forPendingCheckpoint(invalidateCheckpoint.forcePendingCheckpointNumber);
|
|
690
|
+
this.metrics.recordPipelineDepth(0);
|
|
691
|
+
} else {
|
|
692
|
+
this.metrics.recordPipelineDepth(0);
|
|
693
|
+
}
|
|
694
|
+
const simulationOverridesPlan = l1SimulationOverridesBuilder.build();
|
|
695
|
+
const canProposeCheck = await publisher.canProposeAt(archiveForCheck, proposer ?? EthAddress.ZERO, simulationOverridesPlan);
|
|
655
696
|
if (canProposeCheck === undefined) {
|
|
656
697
|
this.log.warn(`Cannot propose checkpoint ${checkpointNumber} at slot ${slot} due to failed rollup contract check`, logCtx);
|
|
657
698
|
this.emit('proposer-rollup-check-failed', {
|
|
@@ -661,11 +702,11 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
|
|
|
661
702
|
this.metrics.recordCheckpointPrecheckFailed('rollup_contract_check_failed');
|
|
662
703
|
return undefined;
|
|
663
704
|
}
|
|
664
|
-
if (canProposeCheck.slot !==
|
|
665
|
-
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 ${
|
|
705
|
+
if (canProposeCheck.slot !== targetSlot) {
|
|
706
|
+
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}.`, {
|
|
666
707
|
...logCtx,
|
|
667
708
|
rollup: canProposeCheck,
|
|
668
|
-
expectedSlot:
|
|
709
|
+
expectedSlot: targetSlot
|
|
669
710
|
});
|
|
670
711
|
this.emit('proposer-rollup-check-failed', {
|
|
671
712
|
reason: 'Slot mismatch',
|
|
@@ -687,17 +728,18 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
|
|
|
687
728
|
this.metrics.recordCheckpointPrecheckFailed('block_number_mismatch');
|
|
688
729
|
return undefined;
|
|
689
730
|
}
|
|
690
|
-
this.lastSlotForCheckpointProposalJob =
|
|
691
|
-
await this.p2pClient.prepareForSlot(
|
|
692
|
-
this.log.info(`Preparing checkpoint proposal ${checkpointNumber}
|
|
731
|
+
this.lastSlotForCheckpointProposalJob = targetSlot;
|
|
732
|
+
await this.p2pClient.prepareForSlot(targetSlot);
|
|
733
|
+
this.log.info(`Preparing checkpoint proposal ${checkpointNumber} for target slot ${targetSlot} during wall-clock slot ${slot}`, {
|
|
693
734
|
...logCtx,
|
|
694
|
-
proposer
|
|
735
|
+
proposer,
|
|
736
|
+
pipeliningEnabled: this.epochCache.isProposerPipeliningEnabled()
|
|
695
737
|
});
|
|
696
738
|
// Create and return the checkpoint proposal job
|
|
697
|
-
return this.createCheckpointProposalJob(
|
|
739
|
+
return this.createCheckpointProposalJob(slot, targetSlot, targetEpoch, checkpointNumber, syncedTo.blockNumber, proposer, publisher, attestorAddress, invalidateCheckpoint, syncedTo.proposedCheckpointData);
|
|
698
740
|
}
|
|
699
|
-
createCheckpointProposalJob(
|
|
700
|
-
return new CheckpointProposalJob(
|
|
741
|
+
createCheckpointProposalJob(slot, targetSlot, targetEpoch, checkpointNumber, syncedToBlockNumber, proposer, publisher, attestorAddress, invalidateCheckpoint, proposedCheckpointData) {
|
|
742
|
+
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);
|
|
701
743
|
}
|
|
702
744
|
/**
|
|
703
745
|
* Returns the current sequencer state.
|
|
@@ -744,16 +786,15 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
|
|
|
744
786
|
* Returns whether all dependencies have caught up.
|
|
745
787
|
* We don't check against the previous block submitted since it may have been reorg'd out.
|
|
746
788
|
*/ async checkSync(args) {
|
|
747
|
-
// Check that the archiver
|
|
748
|
-
//
|
|
749
|
-
//
|
|
750
|
-
const
|
|
751
|
-
const { slot
|
|
752
|
-
if (
|
|
789
|
+
// Check that the archiver has fully synced the L2 slot before the one we want to propose in.
|
|
790
|
+
// The archiver reports sync progress via L1 block timestamps and synced checkpoint slots.
|
|
791
|
+
// See getSyncedL2SlotNumber for how missed L1 blocks are handled.
|
|
792
|
+
const syncedL2Slot = await this.l2BlockSource.getSyncedL2SlotNumber();
|
|
793
|
+
const { slot } = args;
|
|
794
|
+
if (syncedL2Slot === undefined || syncedL2Slot + 1 < slot) {
|
|
753
795
|
this.log.debug(`Cannot propose block at next L2 slot ${slot} due to pending sync from L1`, {
|
|
754
796
|
slot,
|
|
755
|
-
|
|
756
|
-
l1Timestamp
|
|
797
|
+
syncedL2Slot
|
|
757
798
|
});
|
|
758
799
|
return undefined;
|
|
759
800
|
}
|
|
@@ -762,22 +803,30 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
|
|
|
762
803
|
number: syncSummary.latestBlockNumber,
|
|
763
804
|
hash: syncSummary.latestBlockHash
|
|
764
805
|
})),
|
|
765
|
-
this.l2BlockSource.getL2Tips().then((t)=>
|
|
806
|
+
this.l2BlockSource.getL2Tips().then((t)=>({
|
|
807
|
+
proposed: t.proposed,
|
|
808
|
+
checkpointed: t.checkpointed,
|
|
809
|
+
proposedCheckpoint: t.proposedCheckpoint
|
|
810
|
+
})),
|
|
766
811
|
this.p2pClient.getStatus().then((p2p)=>p2p.syncedToL2Block),
|
|
767
|
-
this.l1ToL2MessageSource.getL2Tips().then((t)=>
|
|
768
|
-
|
|
812
|
+
this.l1ToL2MessageSource.getL2Tips().then((t)=>({
|
|
813
|
+
proposed: t.proposed,
|
|
814
|
+
checkpointed: t.checkpointed
|
|
815
|
+
})),
|
|
816
|
+
this.l2BlockSource.getPendingChainValidationStatus(),
|
|
817
|
+
this.l2BlockSource.getProposedCheckpointOnly()
|
|
769
818
|
]);
|
|
770
|
-
const [worldState,
|
|
819
|
+
const [worldState, l2Tips, p2p, l1ToL2MessageSourceTips, pendingChainValidationStatus, proposedCheckpointData] = syncedBlocks;
|
|
771
820
|
// 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,
|
|
772
821
|
// as the world state can compute the new genesis block hash, but other components use the hardcoded constant.
|
|
773
822
|
// TODO(palla/mbps): Fix the above. All components should be able to handle dynamic genesis block hashes.
|
|
774
|
-
const result =
|
|
823
|
+
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;
|
|
775
824
|
if (!result) {
|
|
776
825
|
this.log.debug(`Sequencer sync check failed`, {
|
|
777
826
|
worldState,
|
|
778
|
-
l2BlockSource,
|
|
827
|
+
l2BlockSource: l2Tips.proposed,
|
|
779
828
|
p2p,
|
|
780
|
-
|
|
829
|
+
l1ToL2MessageSourceTips
|
|
781
830
|
});
|
|
782
831
|
return undefined;
|
|
783
832
|
}
|
|
@@ -787,9 +836,11 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
|
|
|
787
836
|
const archive = new Fr((await this.worldState.getCommitted().getTreeInfo(MerkleTreeId.ARCHIVE)).root);
|
|
788
837
|
return {
|
|
789
838
|
checkpointNumber: CheckpointNumber.ZERO,
|
|
839
|
+
checkpointedCheckpointNumber: CheckpointNumber.ZERO,
|
|
790
840
|
blockNumber: BlockNumber.ZERO,
|
|
791
841
|
archive,
|
|
792
|
-
|
|
842
|
+
hasProposedCheckpoint: false,
|
|
843
|
+
syncedL2Slot,
|
|
793
844
|
pendingChainValidationStatus
|
|
794
845
|
};
|
|
795
846
|
}
|
|
@@ -799,34 +850,38 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
|
|
|
799
850
|
this.log.error(`Failed to get L2 block data ${blockNumber} from the archiver with all components in sync`);
|
|
800
851
|
return undefined;
|
|
801
852
|
}
|
|
853
|
+
const hasProposedCheckpoint = l2Tips.proposedCheckpoint.checkpoint.number > l2Tips.checkpointed.checkpoint.number;
|
|
802
854
|
return {
|
|
803
855
|
blockData,
|
|
804
856
|
blockNumber: blockData.header.getBlockNumber(),
|
|
805
857
|
checkpointNumber: blockData.checkpointNumber,
|
|
858
|
+
checkpointedCheckpointNumber: l2Tips.checkpointed.checkpoint.number,
|
|
806
859
|
archive: blockData.archive.root,
|
|
807
|
-
|
|
860
|
+
hasProposedCheckpoint,
|
|
861
|
+
proposedCheckpointData,
|
|
862
|
+
syncedL2Slot,
|
|
808
863
|
pendingChainValidationStatus
|
|
809
864
|
};
|
|
810
865
|
}
|
|
811
866
|
/**
|
|
812
867
|
* Checks if we are the proposer for the next slot.
|
|
813
868
|
* @returns True if we can propose, and the proposer address (undefined if anyone can propose)
|
|
814
|
-
*/ async checkCanPropose(
|
|
869
|
+
*/ async checkCanPropose(targetSlot) {
|
|
815
870
|
let proposer;
|
|
816
871
|
try {
|
|
817
|
-
proposer = await this.epochCache.getProposerAttesterAddressInSlot(
|
|
872
|
+
proposer = await this.epochCache.getProposerAttesterAddressInSlot(targetSlot);
|
|
818
873
|
} catch (e) {
|
|
819
874
|
if (e instanceof NoCommitteeError) {
|
|
820
|
-
if (this.lastSlotForNoCommitteeWarning !==
|
|
821
|
-
this.lastSlotForNoCommitteeWarning =
|
|
822
|
-
this.log.warn(`Cannot propose at
|
|
875
|
+
if (this.lastSlotForNoCommitteeWarning !== targetSlot) {
|
|
876
|
+
this.lastSlotForNoCommitteeWarning = targetSlot;
|
|
877
|
+
this.log.warn(`Cannot propose at target slot ${targetSlot} since the committee does not exist on L1`);
|
|
823
878
|
}
|
|
824
879
|
return [
|
|
825
880
|
false,
|
|
826
881
|
undefined
|
|
827
882
|
];
|
|
828
883
|
}
|
|
829
|
-
this.log.error(`Error getting proposer for slot ${
|
|
884
|
+
this.log.error(`Error getting proposer for target slot ${targetSlot}`, e);
|
|
830
885
|
return [
|
|
831
886
|
false,
|
|
832
887
|
undefined
|
|
@@ -849,7 +904,8 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
|
|
|
849
904
|
const validatorAddresses = this.validatorClient.getValidatorAddresses();
|
|
850
905
|
const weAreProposer = validatorAddresses.some((addr)=>addr.equals(proposer));
|
|
851
906
|
if (!weAreProposer) {
|
|
852
|
-
this.log.debug(`Cannot propose at slot ${
|
|
907
|
+
this.log.debug(`Cannot propose at target slot ${targetSlot} since we are not a proposer`, {
|
|
908
|
+
targetSlot,
|
|
853
909
|
validatorAddresses,
|
|
854
910
|
proposer
|
|
855
911
|
});
|
|
@@ -858,6 +914,10 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
|
|
|
858
914
|
proposer
|
|
859
915
|
];
|
|
860
916
|
}
|
|
917
|
+
this.log.info(`We are the proposer for pipeline slot ${targetSlot}`, {
|
|
918
|
+
targetSlot,
|
|
919
|
+
proposer
|
|
920
|
+
});
|
|
861
921
|
return [
|
|
862
922
|
true,
|
|
863
923
|
proposer
|
|
@@ -867,7 +927,7 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
|
|
|
867
927
|
* Tries to vote on slashing actions and governance when the sync check fails but we're past the max time for initializing a proposal.
|
|
868
928
|
* This allows the sequencer to participate in governance/slashing votes even when it cannot build blocks.
|
|
869
929
|
*/ async tryVoteWhenSyncFails(args) {
|
|
870
|
-
const { slot } = args;
|
|
930
|
+
const { slot, targetSlot } = args;
|
|
871
931
|
// Prevent duplicate attempts in the same slot
|
|
872
932
|
if (this.lastSlotForFallbackVote === slot) {
|
|
873
933
|
this.log.trace(`Already attempted to vote in slot ${slot} (skipping)`);
|
|
@@ -890,7 +950,7 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
|
|
|
890
950
|
maxAllowedTime
|
|
891
951
|
});
|
|
892
952
|
// Check if we're a proposer or proposal is open
|
|
893
|
-
const [canPropose, proposer] = await this.checkCanPropose(
|
|
953
|
+
const [canPropose, proposer] = await this.checkCanPropose(targetSlot);
|
|
894
954
|
if (!canPropose) {
|
|
895
955
|
this.log.trace(`Cannot vote in slot ${slot} since we are not a proposer`, {
|
|
896
956
|
slot,
|
|
@@ -906,8 +966,8 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
|
|
|
906
966
|
attestorAddress,
|
|
907
967
|
slot
|
|
908
968
|
});
|
|
909
|
-
// Enqueue governance and slashing votes
|
|
910
|
-
const voter = new CheckpointVoter(
|
|
969
|
+
// Enqueue governance and slashing votes (voter uses the target slot for L1 submission)
|
|
970
|
+
const voter = new CheckpointVoter(targetSlot, publisher, attestorAddress, this.validatorClient, this.slasherClient, this.l1Constants, this.config, this.metrics, this.log);
|
|
911
971
|
const votesPromises = voter.enqueueVotes();
|
|
912
972
|
const votes = await Promise.all(votesPromises);
|
|
913
973
|
if (votes.every((p)=>!p)) {
|
|
@@ -954,7 +1014,7 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
|
|
|
954
1014
|
* have the proposer try to invalidate, but if they fail, the sequencers in the committee are expected to try,
|
|
955
1015
|
* and if they fail, any sequencer will try as well.
|
|
956
1016
|
*/ async considerInvalidatingCheckpoint(syncedTo, currentSlot) {
|
|
957
|
-
const { pendingChainValidationStatus,
|
|
1017
|
+
const { pendingChainValidationStatus, syncedL2Slot } = syncedTo;
|
|
958
1018
|
if (pendingChainValidationStatus.valid) {
|
|
959
1019
|
return;
|
|
960
1020
|
}
|
|
@@ -965,7 +1025,7 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
|
|
|
965
1025
|
const { secondsBeforeInvalidatingBlockAsCommitteeMember, secondsBeforeInvalidatingBlockAsNonCommitteeMember } = this.config;
|
|
966
1026
|
const logData = {
|
|
967
1027
|
invalidL1Timestamp: invalidCheckpointTimestamp,
|
|
968
|
-
|
|
1028
|
+
syncedL2Slot,
|
|
969
1029
|
invalidCheckpoint: pendingChainValidationStatus.checkpoint,
|
|
970
1030
|
secondsBeforeInvalidatingBlockAsCommitteeMember,
|
|
971
1031
|
secondsBeforeInvalidatingBlockAsNonCommitteeMember,
|
|
@@ -1,8 +1,10 @@
|
|
|
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?;
|
|
7
|
+
private readonly checkpointTiming;
|
|
6
8
|
/**
|
|
7
9
|
* How late into the slot can we be to start working. Computed as the total time needed for assembling and publishing a block,
|
|
8
10
|
* assuming an execution time equal to `minExecutionTime`, subtracted from the slot duration. This means that, if the proposer
|
|
@@ -49,6 +51,13 @@ export declare class SequencerTimetable {
|
|
|
49
51
|
readonly blockDuration: number | undefined;
|
|
50
52
|
/** Maximum number of blocks that can be built in this slot configuration */
|
|
51
53
|
readonly maxNumberOfBlocks: number;
|
|
54
|
+
/** Whether pipelining is enabled (checkpoint finalization deferred to next slot). */
|
|
55
|
+
readonly pipelining: boolean;
|
|
56
|
+
/**
|
|
57
|
+
* How far into the target slot attestation collection can extend when pipelining.
|
|
58
|
+
* Covers validator re-execution (one block duration) plus one-way attestation return.
|
|
59
|
+
*/
|
|
60
|
+
readonly pipeliningAttestationGracePeriod: number;
|
|
52
61
|
constructor(opts: {
|
|
53
62
|
ethereumSlotDuration: number;
|
|
54
63
|
aztecSlotDuration: number;
|
|
@@ -56,7 +65,12 @@ export declare class SequencerTimetable {
|
|
|
56
65
|
p2pPropagationTime?: number;
|
|
57
66
|
blockDurationMs?: number;
|
|
58
67
|
enforce: boolean;
|
|
59
|
-
|
|
68
|
+
pipelining?: boolean;
|
|
69
|
+
}, metrics?: SequencerMetrics | undefined, log?: Logger | undefined);
|
|
70
|
+
getCheckpointAssemblyDeadline(): number;
|
|
71
|
+
getCheckpointAttestationDeadline(): number;
|
|
72
|
+
getCheckpointAttestationStartDeadline(): number;
|
|
73
|
+
getCheckpointPublishingDeadline(): number;
|
|
60
74
|
getMaxAllowedTime(state: Extract<SequencerState, SequencerState.STOPPED | SequencerState.IDLE | SequencerState.SYNCHRONIZING>): undefined;
|
|
61
75
|
getMaxAllowedTime(state: Exclude<SequencerState, SequencerState.STOPPED | SequencerState.IDLE | SequencerState.SYNCHRONIZING>): number;
|
|
62
76
|
getMaxAllowedTime(state: SequencerState): number | undefined;
|
|
@@ -84,4 +98,4 @@ export declare class SequencerTimetable {
|
|
|
84
98
|
isLastBlock: boolean;
|
|
85
99
|
};
|
|
86
100
|
}
|
|
87
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
101
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidGltZXRhYmxlLmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvc2VxdWVuY2VyL3RpbWV0YWJsZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssRUFBRSxNQUFNLEVBQUUsTUFBTSx1QkFBdUIsQ0FBQztBQVdwRCxPQUFPLEtBQUssRUFBRSxnQkFBZ0IsRUFBRSxNQUFNLGNBQWMsQ0FBQztBQUNyRCxPQUFPLEVBQUUsY0FBYyxFQUFFLE1BQU0sWUFBWSxDQUFDO0FBRTVDLHFCQUFhLGtCQUFrQjtJQWlGM0IsT0FBTyxDQUFDLFFBQVEsQ0FBQyxPQUFPLENBQUM7SUFDekIsT0FBTyxDQUFDLFFBQVEsQ0FBQyxHQUFHLENBQUM7SUFqRnZCLE9BQU8sQ0FBQyxRQUFRLENBQUMsZ0JBQWdCLENBQW1CO0lBRXBEOzs7O09BSUc7SUFDSCxTQUFnQixrQkFBa0IsRUFBRSxNQUFNLENBQUM7SUFFM0M7Ozs7T0FJRztJQUNILFNBQWdCLG9CQUFvQixFQUFFLE1BQU0sQ0FBQztJQUU3Qzs7Ozs7T0FLRztJQUNILFNBQWdCLDBCQUEwQixFQUFFLE1BQU0sQ0FBQztJQUVuRDs7OztPQUlHO0lBQ0gsU0FBZ0IsZ0JBQWdCLEVBQUUsTUFBTSxDQUFDO0lBRXpDOzs7T0FHRztJQUNILFNBQWdCLGdCQUFnQixFQUFFLE1BQU0sQ0FBc0I7SUFFOUQsdURBQXVEO0lBQ3ZELFNBQWdCLDRCQUE0QixFQUFFLE1BQU0sQ0FBa0M7SUFFdEYsbUdBQW1HO0lBQ25HLFNBQWdCLGtCQUFrQixFQUFFLE1BQU0sQ0FBQztJQUUzQyxtRkFBbUY7SUFDbkYsU0FBZ0Isc0JBQXNCLEVBQUUsTUFBTSxDQUE0QjtJQUUxRSx3Q0FBd0M7SUFDeEMsU0FBZ0Isb0JBQW9CLEVBQUUsTUFBTSxDQUFDO0lBRTdDLGtGQUFrRjtJQUNsRixTQUFnQixpQkFBaUIsRUFBRSxNQUFNLENBQUM7SUFFMUMsNERBQTREO0lBQzVELFNBQWdCLE9BQU8sRUFBRSxPQUFPLENBQUM7SUFFakMsb0dBQW9HO0lBQ3BHLFNBQWdCLGFBQWEsRUFBRSxNQUFNLEdBQUcsU0FBUyxDQUFDO0lBRWxELDRFQUE0RTtJQUM1RSxTQUFnQixpQkFBaUIsRUFBRSxNQUFNLENBQUM7SUFFMUMscUZBQXFGO0lBQ3JGLFNBQWdCLFVBQVUsRUFBRSxPQUFPLENBQUM7SUFFcEM7OztPQUdHO0lBQ0gsU0FBZ0IsZ0NBQWdDLEVBQUUsTUFBTSxDQUFDO0lBRXpELFlBQ0UsSUFBSSxFQUFFO1FBQ0osb0JBQW9CLEVBQUUsTUFBTSxDQUFDO1FBQzdCLGlCQUFpQixFQUFFLE1BQU0sQ0FBQztRQUMxQixnQkFBZ0IsRUFBRSxNQUFNLENBQUM7UUFDekIsa0JBQWtCLENBQUMsRUFBRSxNQUFNLENBQUM7UUFDNUIsZUFBZSxDQUFDLEVBQUUsTUFBTSxDQUFDO1FBQ3pCLE9BQU8sRUFBRSxPQUFPLENBQUM7UUFDakIsVUFBVSxDQUFDLEVBQUUsT0FBTyxDQUFDO0tBQ3RCLEVBQ2dCLE9BQU8sQ0FBQyw4QkFBa0IsRUFDMUIsR0FBRyxDQUFDLG9CQUFRLEVBd0Q5QjtJQUVNLDZCQUE2QixJQUFJLE1BQU0sQ0FFN0M7SUFFTSxnQ0FBZ0MsSUFBSSxNQUFNLENBRWhEO0lBRU0scUNBQXFDLElBQUksTUFBTSxDQUVyRDtJQUVNLCtCQUErQixJQUFJLE1BQU0sQ0FFL0M7SUFFTSxpQkFBaUIsQ0FDdEIsS0FBSyxFQUFFLE9BQU8sQ0FBQyxjQUFjLEVBQUUsY0FBYyxDQUFDLE9BQU8sR0FBRyxjQUFjLENBQUMsSUFBSSxHQUFHLGNBQWMsQ0FBQyxhQUFhLENBQUMsR0FDMUcsU0FBUyxDQUFDO0lBQ04saUJBQWlCLENBQ3RCLEtBQUssRUFBRSxPQUFPLENBQUMsY0FBYyxFQUFFLGNBQWMsQ0FBQyxPQUFPLEdBQUcsY0FBYyxDQUFDLElBQUksR0FBRyxjQUFjLENBQUMsYUFBYSxDQUFDLEdBQzFHLE1BQU0sQ0FBQztJQUNILGlCQUFpQixDQUFDLEtBQUssRUFBRSxjQUFjLEdBQUcsTUFBTSxHQUFHLFNBQVMsQ0FBQztJQTRCN0QsY0FBYyxDQUFDLFFBQVEsRUFBRSxjQUFjLEVBQUUsZUFBZSxFQUFFLE1BQU0sUUFpQnRFO0lBRUQ7Ozs7Ozs7O09BUUc7SUFDSSxpQkFBaUIsQ0FDdEIsZUFBZSxFQUFFLE1BQU0sR0FFckI7UUFBRSxRQUFRLEVBQUUsSUFBSSxDQUFDO1FBQUMsUUFBUSxFQUFFLFNBQVMsQ0FBQztRQUFDLFdBQVcsRUFBRSxJQUFJLENBQUE7S0FBRSxHQUMxRDtRQUFFLFFBQVEsRUFBRSxLQUFLLENBQUM7UUFBQyxRQUFRLEVBQUUsU0FBUyxDQUFDO1FBQUMsV0FBVyxFQUFFLEtBQUssQ0FBQTtLQUFFLEdBQzVEO1FBQUUsUUFBUSxFQUFFLE9BQU8sQ0FBQztRQUFDLFFBQVEsRUFBRSxNQUFNLENBQUM7UUFBQyxXQUFXLEVBQUUsT0FBTyxDQUFBO0tBQUUsQ0F3RGhFO0NBQ0YifQ==
|
|
@@ -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":"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"}
|