@aztec/sequencer-client 0.0.1-commit.f504929 → 0.0.1-commit.f650c0a5c
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 +4 -1
- package/dest/client/sequencer-client.d.ts.map +1 -1
- package/dest/client/sequencer-client.js +46 -27
- package/dest/config.d.ts +25 -5
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +21 -12
- package/dest/global_variable_builder/global_builder.d.ts +15 -9
- 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 +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 +2 -3
- package/dest/publisher/sequencer-publisher.d.ts +58 -32
- package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher.js +132 -89
- 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 +302 -180
- 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 +5 -1
- package/dest/sequencer/metrics.d.ts.map +1 -1
- package/dest/sequencer/metrics.js +11 -0
- package/dest/sequencer/sequencer.d.ts +23 -10
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +130 -71
- 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/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 +56 -28
- package/src/config.ts +28 -14
- package/src/global_variable_builder/global_builder.ts +37 -26
- package/src/global_variable_builder/index.ts +1 -1
- package/src/publisher/config.ts +32 -0
- package/src/publisher/sequencer-publisher-factory.ts +3 -6
- package/src/publisher/sequencer-publisher.ts +205 -127
- package/src/sequencer/README.md +81 -12
- package/src/sequencer/checkpoint_proposal_job.ts +385 -199
- package/src/sequencer/checkpoint_voter.ts +1 -12
- package/src/sequencer/events.ts +1 -1
- package/src/sequencer/metrics.ts +14 -0
- package/src/sequencer/sequencer.ts +186 -80
- package/src/sequencer/timetable.ts +26 -15
- package/src/sequencer/types.ts +1 -1
- package/src/test/mock_checkpoint_builder.ts +51 -48
|
@@ -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,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')){
|
|
@@ -475,7 +476,8 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
|
|
|
475
476
|
l1PublishingTime: this.l1PublishingTime,
|
|
476
477
|
p2pPropagationTime: this.config.attestationPropagationTime,
|
|
477
478
|
blockDurationMs: this.config.blockDurationMs,
|
|
478
|
-
enforce: this.config.enforceTimeTable
|
|
479
|
+
enforce: this.config.enforceTimeTable,
|
|
480
|
+
pipelining: this.epochCache.isProposerPipeliningEnabled()
|
|
479
481
|
}, this.metrics, this.log);
|
|
480
482
|
}
|
|
481
483
|
/** Initializes the sequencer (precomputes tables). Takes about 3s. */ init() {
|
|
@@ -494,8 +496,9 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
|
|
|
494
496
|
this.setState(SequencerState.STOPPING, undefined, {
|
|
495
497
|
force: true
|
|
496
498
|
});
|
|
497
|
-
this.publisherFactory.
|
|
499
|
+
await this.publisherFactory.stopAll();
|
|
498
500
|
await this.runningPromise?.stop();
|
|
501
|
+
await this.lastCheckpointProposalJob?.awaitPendingSubmission();
|
|
499
502
|
this.setState(SequencerState.STOPPED, undefined, {
|
|
500
503
|
force: true
|
|
501
504
|
});
|
|
@@ -539,22 +542,25 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
|
|
|
539
542
|
* - Submit checkpoint
|
|
540
543
|
*/ async work() {
|
|
541
544
|
this.setState(SequencerState.SYNCHRONIZING, undefined);
|
|
542
|
-
const { slot, ts,
|
|
545
|
+
const { slot, ts, nowSeconds, epoch } = this.epochCache.getEpochAndSlotInNextL1Slot();
|
|
546
|
+
const { slot: targetSlot, epoch: targetEpoch } = this.epochCache.getTargetEpochAndSlotInNextL1Slot();
|
|
543
547
|
// 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,
|
|
548
|
+
const checkpointProposalJob = await this.prepareCheckpointProposal(slot, targetSlot, epoch, targetEpoch, ts, nowSeconds);
|
|
545
549
|
if (!checkpointProposalJob) {
|
|
546
550
|
return;
|
|
547
551
|
}
|
|
552
|
+
// Track the job so we can await its pending L1 submission during shutdown
|
|
553
|
+
this.lastCheckpointProposalJob = checkpointProposalJob;
|
|
548
554
|
// Execute the checkpoint proposal job
|
|
549
555
|
const checkpoint = await checkpointProposalJob.execute();
|
|
550
556
|
// Update last checkpoint proposed (currently unused)
|
|
551
557
|
if (checkpoint) {
|
|
552
558
|
this.lastCheckpointProposed = checkpoint;
|
|
553
559
|
}
|
|
554
|
-
// Log fee strategy comparison if on fisherman
|
|
555
|
-
if (this.config.fishermanMode && (this.lastEpochForStrategyComparison === undefined ||
|
|
556
|
-
this.logStrategyComparison(
|
|
557
|
-
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;
|
|
558
564
|
}
|
|
559
565
|
return checkpoint;
|
|
560
566
|
}
|
|
@@ -562,17 +568,17 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
|
|
|
562
568
|
* Prepares the checkpoint proposal by performing all necessary checks and setup.
|
|
563
569
|
* This is the initial step in the main loop.
|
|
564
570
|
* @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)
|
|
571
|
+
*/ async prepareCheckpointProposal(slot, targetSlot, epoch, targetEpoch, ts, nowSeconds) {
|
|
572
|
+
// Check we have not already processed this target slot (cheapest check)
|
|
567
573
|
// We only check this if enforce timetable is set, since we want to keep processing the same slot if we are not
|
|
568
574
|
// running against actual time (eg when we use sandbox-style automining)
|
|
569
|
-
if (this.lastSlotForCheckpointProposalJob && this.lastSlotForCheckpointProposalJob >=
|
|
570
|
-
this.log.trace(`
|
|
575
|
+
if (this.lastSlotForCheckpointProposalJob && this.lastSlotForCheckpointProposalJob >= targetSlot && this.config.enforceTimeTable) {
|
|
576
|
+
this.log.trace(`Target slot ${targetSlot} has already been processed`);
|
|
571
577
|
return undefined;
|
|
572
578
|
}
|
|
573
|
-
// But if we have already proposed for this slot,
|
|
574
|
-
if (this.lastCheckpointProposed && this.lastCheckpointProposed.header.slotNumber >=
|
|
575
|
-
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}`);
|
|
576
582
|
return undefined;
|
|
577
583
|
}
|
|
578
584
|
// Check all components are synced to latest as seen by the archiver (queries all subsystems)
|
|
@@ -583,16 +589,18 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
|
|
|
583
589
|
if (!syncedTo) {
|
|
584
590
|
await this.tryVoteWhenSyncFails({
|
|
585
591
|
slot,
|
|
592
|
+
targetSlot,
|
|
586
593
|
ts
|
|
587
594
|
});
|
|
588
595
|
return undefined;
|
|
589
596
|
}
|
|
590
|
-
// 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.
|
|
591
598
|
// Still perform governance/slashing voting (as proposer) once per slot.
|
|
592
|
-
|
|
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);
|
|
593
601
|
if (isEscapeHatchOpen) {
|
|
594
602
|
this.setState(SequencerState.PROPOSER_CHECK, slot);
|
|
595
|
-
const [canPropose, proposer] = await this.checkCanPropose(
|
|
603
|
+
const [canPropose, proposer] = await this.checkCanPropose(targetSlot);
|
|
596
604
|
if (canPropose) {
|
|
597
605
|
await this.tryVoteWhenEscapeHatchOpen({
|
|
598
606
|
slot,
|
|
@@ -610,25 +618,32 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
|
|
|
610
618
|
// Next checkpoint follows from the last synced one
|
|
611
619
|
const checkpointNumber = CheckpointNumber(syncedTo.checkpointNumber + 1);
|
|
612
620
|
const logCtx = {
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
syncedToL2Slot: getSlotAtTimestamp(syncedTo.l1Timestamp, this.l1Constants),
|
|
621
|
+
nowSeconds,
|
|
622
|
+
syncedToL2Slot: syncedTo.syncedL2Slot,
|
|
616
623
|
slot,
|
|
624
|
+
targetSlot,
|
|
617
625
|
slotTs: ts,
|
|
618
626
|
checkpointNumber,
|
|
619
627
|
isPendingChainValid: pick(syncedTo.pendingChainValidationStatus, 'valid', 'reason', 'invalidIndex')
|
|
620
628
|
};
|
|
621
|
-
// Check that we are a proposer for the
|
|
629
|
+
// Check that we are a proposer for the target slot.
|
|
622
630
|
this.setState(SequencerState.PROPOSER_CHECK, slot);
|
|
623
|
-
const [canPropose, proposer] = await this.checkCanPropose(
|
|
631
|
+
const [canPropose, proposer] = await this.checkCanPropose(targetSlot);
|
|
624
632
|
// If we are not a proposer check if we should invalidate an invalid checkpoint, and bail
|
|
625
633
|
if (!canPropose) {
|
|
626
634
|
await this.considerInvalidatingCheckpoint(syncedTo, slot);
|
|
627
635
|
return undefined;
|
|
628
636
|
}
|
|
629
|
-
//
|
|
630
|
-
|
|
631
|
-
|
|
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}`, {
|
|
632
647
|
...logCtx,
|
|
633
648
|
block: syncedTo.blockData.header.toInspect()
|
|
634
649
|
});
|
|
@@ -648,10 +663,35 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
|
|
|
648
663
|
this.log.debug(`Set proposer address ${proposer} for simulation in fisherman mode`);
|
|
649
664
|
}
|
|
650
665
|
// Prepare invalidation request if the pending chain is invalid (returns undefined if no need)
|
|
651
|
-
|
|
652
|
-
//
|
|
653
|
-
//
|
|
654
|
-
|
|
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);
|
|
655
695
|
if (canProposeCheck === undefined) {
|
|
656
696
|
this.log.warn(`Cannot propose checkpoint ${checkpointNumber} at slot ${slot} due to failed rollup contract check`, logCtx);
|
|
657
697
|
this.emit('proposer-rollup-check-failed', {
|
|
@@ -661,11 +701,11 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
|
|
|
661
701
|
this.metrics.recordCheckpointPrecheckFailed('rollup_contract_check_failed');
|
|
662
702
|
return undefined;
|
|
663
703
|
}
|
|
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 ${
|
|
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}.`, {
|
|
666
706
|
...logCtx,
|
|
667
707
|
rollup: canProposeCheck,
|
|
668
|
-
expectedSlot:
|
|
708
|
+
expectedSlot: targetSlot
|
|
669
709
|
});
|
|
670
710
|
this.emit('proposer-rollup-check-failed', {
|
|
671
711
|
reason: 'Slot mismatch',
|
|
@@ -687,17 +727,18 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
|
|
|
687
727
|
this.metrics.recordCheckpointPrecheckFailed('block_number_mismatch');
|
|
688
728
|
return undefined;
|
|
689
729
|
}
|
|
690
|
-
this.lastSlotForCheckpointProposalJob =
|
|
691
|
-
await this.p2pClient.prepareForSlot(
|
|
692
|
-
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}`, {
|
|
693
733
|
...logCtx,
|
|
694
|
-
proposer
|
|
734
|
+
proposer,
|
|
735
|
+
pipeliningEnabled: this.epochCache.isProposerPipeliningEnabled()
|
|
695
736
|
});
|
|
696
737
|
// Create and return the checkpoint proposal job
|
|
697
|
-
return this.createCheckpointProposalJob(
|
|
738
|
+
return this.createCheckpointProposalJob(slot, targetSlot, targetEpoch, checkpointNumber, syncedTo.blockNumber, proposer, publisher, attestorAddress, invalidateCheckpoint, syncedTo.proposedCheckpointData);
|
|
698
739
|
}
|
|
699
|
-
createCheckpointProposalJob(
|
|
700
|
-
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);
|
|
701
742
|
}
|
|
702
743
|
/**
|
|
703
744
|
* Returns the current sequencer state.
|
|
@@ -744,16 +785,15 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
|
|
|
744
785
|
* Returns whether all dependencies have caught up.
|
|
745
786
|
* We don't check against the previous block submitted since it may have been reorg'd out.
|
|
746
787
|
*/ async checkSync(args) {
|
|
747
|
-
// Check that the archiver
|
|
748
|
-
//
|
|
749
|
-
//
|
|
750
|
-
const
|
|
751
|
-
const { slot
|
|
752
|
-
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) {
|
|
753
794
|
this.log.debug(`Cannot propose block at next L2 slot ${slot} due to pending sync from L1`, {
|
|
754
795
|
slot,
|
|
755
|
-
|
|
756
|
-
l1Timestamp
|
|
796
|
+
syncedL2Slot
|
|
757
797
|
});
|
|
758
798
|
return undefined;
|
|
759
799
|
}
|
|
@@ -762,22 +802,30 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
|
|
|
762
802
|
number: syncSummary.latestBlockNumber,
|
|
763
803
|
hash: syncSummary.latestBlockHash
|
|
764
804
|
})),
|
|
765
|
-
this.l2BlockSource.getL2Tips().then((t)=>
|
|
805
|
+
this.l2BlockSource.getL2Tips().then((t)=>({
|
|
806
|
+
proposed: t.proposed,
|
|
807
|
+
checkpointed: t.checkpointed,
|
|
808
|
+
proposedCheckpoint: t.proposedCheckpoint
|
|
809
|
+
})),
|
|
766
810
|
this.p2pClient.getStatus().then((p2p)=>p2p.syncedToL2Block),
|
|
767
|
-
this.l1ToL2MessageSource.getL2Tips().then((t)=>
|
|
768
|
-
|
|
811
|
+
this.l1ToL2MessageSource.getL2Tips().then((t)=>({
|
|
812
|
+
proposed: t.proposed,
|
|
813
|
+
checkpointed: t.checkpointed
|
|
814
|
+
})),
|
|
815
|
+
this.l2BlockSource.getPendingChainValidationStatus(),
|
|
816
|
+
this.l2BlockSource.getProposedCheckpointOnly()
|
|
769
817
|
]);
|
|
770
|
-
const [worldState,
|
|
818
|
+
const [worldState, l2Tips, p2p, l1ToL2MessageSourceTips, pendingChainValidationStatus, proposedCheckpointData] = syncedBlocks;
|
|
771
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,
|
|
772
820
|
// as the world state can compute the new genesis block hash, but other components use the hardcoded constant.
|
|
773
821
|
// TODO(palla/mbps): Fix the above. All components should be able to handle dynamic genesis block hashes.
|
|
774
|
-
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;
|
|
775
823
|
if (!result) {
|
|
776
824
|
this.log.debug(`Sequencer sync check failed`, {
|
|
777
825
|
worldState,
|
|
778
|
-
l2BlockSource,
|
|
826
|
+
l2BlockSource: l2Tips.proposed,
|
|
779
827
|
p2p,
|
|
780
|
-
|
|
828
|
+
l1ToL2MessageSourceTips
|
|
781
829
|
});
|
|
782
830
|
return undefined;
|
|
783
831
|
}
|
|
@@ -787,9 +835,11 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
|
|
|
787
835
|
const archive = new Fr((await this.worldState.getCommitted().getTreeInfo(MerkleTreeId.ARCHIVE)).root);
|
|
788
836
|
return {
|
|
789
837
|
checkpointNumber: CheckpointNumber.ZERO,
|
|
838
|
+
checkpointedCheckpointNumber: CheckpointNumber.ZERO,
|
|
790
839
|
blockNumber: BlockNumber.ZERO,
|
|
791
840
|
archive,
|
|
792
|
-
|
|
841
|
+
hasProposedCheckpoint: false,
|
|
842
|
+
syncedL2Slot,
|
|
793
843
|
pendingChainValidationStatus
|
|
794
844
|
};
|
|
795
845
|
}
|
|
@@ -799,34 +849,38 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
|
|
|
799
849
|
this.log.error(`Failed to get L2 block data ${blockNumber} from the archiver with all components in sync`);
|
|
800
850
|
return undefined;
|
|
801
851
|
}
|
|
852
|
+
const hasProposedCheckpoint = l2Tips.proposedCheckpoint.checkpoint.number > l2Tips.checkpointed.checkpoint.number;
|
|
802
853
|
return {
|
|
803
854
|
blockData,
|
|
804
855
|
blockNumber: blockData.header.getBlockNumber(),
|
|
805
856
|
checkpointNumber: blockData.checkpointNumber,
|
|
857
|
+
checkpointedCheckpointNumber: l2Tips.checkpointed.checkpoint.number,
|
|
806
858
|
archive: blockData.archive.root,
|
|
807
|
-
|
|
859
|
+
hasProposedCheckpoint,
|
|
860
|
+
proposedCheckpointData,
|
|
861
|
+
syncedL2Slot,
|
|
808
862
|
pendingChainValidationStatus
|
|
809
863
|
};
|
|
810
864
|
}
|
|
811
865
|
/**
|
|
812
866
|
* Checks if we are the proposer for the next slot.
|
|
813
867
|
* @returns True if we can propose, and the proposer address (undefined if anyone can propose)
|
|
814
|
-
*/ async checkCanPropose(
|
|
868
|
+
*/ async checkCanPropose(targetSlot) {
|
|
815
869
|
let proposer;
|
|
816
870
|
try {
|
|
817
|
-
proposer = await this.epochCache.getProposerAttesterAddressInSlot(
|
|
871
|
+
proposer = await this.epochCache.getProposerAttesterAddressInSlot(targetSlot);
|
|
818
872
|
} catch (e) {
|
|
819
873
|
if (e instanceof NoCommitteeError) {
|
|
820
|
-
if (this.lastSlotForNoCommitteeWarning !==
|
|
821
|
-
this.lastSlotForNoCommitteeWarning =
|
|
822
|
-
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`);
|
|
823
877
|
}
|
|
824
878
|
return [
|
|
825
879
|
false,
|
|
826
880
|
undefined
|
|
827
881
|
];
|
|
828
882
|
}
|
|
829
|
-
this.log.error(`Error getting proposer for slot ${
|
|
883
|
+
this.log.error(`Error getting proposer for target slot ${targetSlot}`, e);
|
|
830
884
|
return [
|
|
831
885
|
false,
|
|
832
886
|
undefined
|
|
@@ -849,7 +903,8 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
|
|
|
849
903
|
const validatorAddresses = this.validatorClient.getValidatorAddresses();
|
|
850
904
|
const weAreProposer = validatorAddresses.some((addr)=>addr.equals(proposer));
|
|
851
905
|
if (!weAreProposer) {
|
|
852
|
-
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,
|
|
853
908
|
validatorAddresses,
|
|
854
909
|
proposer
|
|
855
910
|
});
|
|
@@ -858,6 +913,10 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
|
|
|
858
913
|
proposer
|
|
859
914
|
];
|
|
860
915
|
}
|
|
916
|
+
this.log.info(`We are the proposer for pipeline slot ${targetSlot}`, {
|
|
917
|
+
targetSlot,
|
|
918
|
+
proposer
|
|
919
|
+
});
|
|
861
920
|
return [
|
|
862
921
|
true,
|
|
863
922
|
proposer
|
|
@@ -867,7 +926,7 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
|
|
|
867
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.
|
|
868
927
|
* This allows the sequencer to participate in governance/slashing votes even when it cannot build blocks.
|
|
869
928
|
*/ async tryVoteWhenSyncFails(args) {
|
|
870
|
-
const { slot } = args;
|
|
929
|
+
const { slot, targetSlot } = args;
|
|
871
930
|
// Prevent duplicate attempts in the same slot
|
|
872
931
|
if (this.lastSlotForFallbackVote === slot) {
|
|
873
932
|
this.log.trace(`Already attempted to vote in slot ${slot} (skipping)`);
|
|
@@ -890,7 +949,7 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
|
|
|
890
949
|
maxAllowedTime
|
|
891
950
|
});
|
|
892
951
|
// Check if we're a proposer or proposal is open
|
|
893
|
-
const [canPropose, proposer] = await this.checkCanPropose(
|
|
952
|
+
const [canPropose, proposer] = await this.checkCanPropose(targetSlot);
|
|
894
953
|
if (!canPropose) {
|
|
895
954
|
this.log.trace(`Cannot vote in slot ${slot} since we are not a proposer`, {
|
|
896
955
|
slot,
|
|
@@ -906,8 +965,8 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
|
|
|
906
965
|
attestorAddress,
|
|
907
966
|
slot
|
|
908
967
|
});
|
|
909
|
-
// Enqueue governance and slashing votes
|
|
910
|
-
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);
|
|
911
970
|
const votesPromises = voter.enqueueVotes();
|
|
912
971
|
const votes = await Promise.all(votesPromises);
|
|
913
972
|
if (votes.every((p)=>!p)) {
|
|
@@ -954,7 +1013,7 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
|
|
|
954
1013
|
* have the proposer try to invalidate, but if they fail, the sequencers in the committee are expected to try,
|
|
955
1014
|
* and if they fail, any sequencer will try as well.
|
|
956
1015
|
*/ async considerInvalidatingCheckpoint(syncedTo, currentSlot) {
|
|
957
|
-
const { pendingChainValidationStatus,
|
|
1016
|
+
const { pendingChainValidationStatus, syncedL2Slot } = syncedTo;
|
|
958
1017
|
if (pendingChainValidationStatus.valid) {
|
|
959
1018
|
return;
|
|
960
1019
|
}
|
|
@@ -965,7 +1024,7 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
|
|
|
965
1024
|
const { secondsBeforeInvalidatingBlockAsCommitteeMember, secondsBeforeInvalidatingBlockAsNonCommitteeMember } = this.config;
|
|
966
1025
|
const logData = {
|
|
967
1026
|
invalidL1Timestamp: invalidCheckpointTimestamp,
|
|
968
|
-
|
|
1027
|
+
syncedL2Slot,
|
|
969
1028
|
invalidCheckpoint: pendingChainValidationStatus.checkpoint,
|
|
970
1029
|
secondsBeforeInvalidatingBlockAsCommitteeMember,
|
|
971
1030
|
secondsBeforeInvalidatingBlockAsNonCommitteeMember,
|
|
@@ -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"}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { createLogger } from '@aztec/aztec.js/log';
|
|
2
1
|
import { CHECKPOINT_ASSEMBLE_TIME, CHECKPOINT_INITIALIZATION_TIME, DEFAULT_P2P_PROPAGATION_TIME, MIN_EXECUTION_TIME } from '@aztec/stdlib/timetable';
|
|
3
2
|
import { SequencerTooSlowError } from './errors.js';
|
|
4
3
|
import { SequencerState } from './utils.js';
|
|
@@ -38,7 +37,8 @@ export class SequencerTimetable {
|
|
|
38
37
|
/** Whether assertTimeLeft will throw if not enough time. */ enforce;
|
|
39
38
|
/** Duration per block when building multiple blocks per slot (undefined = single block per slot) */ blockDuration;
|
|
40
39
|
/** Maximum number of blocks that can be built in this slot configuration */ maxNumberOfBlocks;
|
|
41
|
-
|
|
40
|
+
/** Whether pipelining is enabled (checkpoint finalization deferred to next slot). */ pipelining;
|
|
41
|
+
constructor(opts, metrics, log){
|
|
42
42
|
this.metrics = metrics;
|
|
43
43
|
this.log = log;
|
|
44
44
|
this.minExecutionTime = MIN_EXECUTION_TIME;
|
|
@@ -50,6 +50,7 @@ export class SequencerTimetable {
|
|
|
50
50
|
this.p2pPropagationTime = opts.p2pPropagationTime ?? DEFAULT_P2P_PROPAGATION_TIME;
|
|
51
51
|
this.blockDuration = opts.blockDurationMs ? opts.blockDurationMs / 1000 : undefined;
|
|
52
52
|
this.enforce = opts.enforce;
|
|
53
|
+
this.pipelining = opts.pipelining ?? false;
|
|
53
54
|
// Assume zero-cost propagation time and faster runs in test environments where L1 slot duration is shortened
|
|
54
55
|
if (this.ethereumSlotDuration < 8) {
|
|
55
56
|
this.p2pPropagationTime = 0;
|
|
@@ -71,17 +72,24 @@ export class SequencerTimetable {
|
|
|
71
72
|
if (!this.blockDuration) {
|
|
72
73
|
this.maxNumberOfBlocks = 1; // Single block per slot
|
|
73
74
|
} else {
|
|
74
|
-
|
|
75
|
-
|
|
75
|
+
// When pipelining, finalization is deferred to the next slot, but we still need
|
|
76
|
+
// a sub-slot for validator re-execution so they can produce attestations.
|
|
77
|
+
let timeReservedAtEnd = this.blockDuration; // Validatior re-execution only
|
|
78
|
+
if (!this.pipelining) {
|
|
79
|
+
timeReservedAtEnd += this.checkpointFinalizationTime;
|
|
80
|
+
}
|
|
76
81
|
const timeAvailableForBlocks = this.aztecSlotDuration - this.initializationOffset - timeReservedAtEnd;
|
|
77
82
|
this.maxNumberOfBlocks = Math.floor(timeAvailableForBlocks / this.blockDuration);
|
|
78
83
|
}
|
|
79
|
-
// Minimum work to do within a slot for building a block with the minimum time for execution and publishing its checkpoint
|
|
80
|
-
|
|
81
|
-
this.
|
|
84
|
+
// Minimum work to do within a slot for building a block with the minimum time for execution and publishing its checkpoint.
|
|
85
|
+
// When pipelining, finalization is deferred, but we still need time for execution and validator re-execution.
|
|
86
|
+
let minWorkToDo = this.initializationOffset + this.minExecutionTime * 2;
|
|
87
|
+
if (!this.pipelining) {
|
|
88
|
+
minWorkToDo += this.checkpointFinalizationTime;
|
|
89
|
+
}
|
|
82
90
|
const initializeDeadline = this.aztecSlotDuration - minWorkToDo;
|
|
83
91
|
this.initializeDeadline = initializeDeadline;
|
|
84
|
-
this.log
|
|
92
|
+
this.log?.info(`Sequencer timetable initialized with ${this.maxNumberOfBlocks} blocks per slot (${this.enforce ? 'enforced' : 'not enforced'})`, {
|
|
85
93
|
ethereumSlotDuration: this.ethereumSlotDuration,
|
|
86
94
|
aztecSlotDuration: this.aztecSlotDuration,
|
|
87
95
|
l1PublishingTime: this.l1PublishingTime,
|
|
@@ -91,6 +99,7 @@ export class SequencerTimetable {
|
|
|
91
99
|
blockAssembleTime: this.checkpointAssembleTime,
|
|
92
100
|
initializeDeadline: this.initializeDeadline,
|
|
93
101
|
enforce: this.enforce,
|
|
102
|
+
pipelining: this.pipelining,
|
|
94
103
|
minWorkToDo,
|
|
95
104
|
blockDuration: this.blockDuration,
|
|
96
105
|
maxNumberOfBlocks: this.maxNumberOfBlocks
|
|
@@ -138,7 +147,7 @@ export class SequencerTimetable {
|
|
|
138
147
|
throw new SequencerTooSlowError(newState, maxAllowedTime, secondsIntoSlot);
|
|
139
148
|
}
|
|
140
149
|
this.metrics?.recordStateTransitionBufferMs(Math.floor(bufferSeconds * 1000), newState);
|
|
141
|
-
this.log
|
|
150
|
+
this.log?.trace(`Enough time to transition to ${newState}`, {
|
|
142
151
|
maxAllowedTime,
|
|
143
152
|
secondsIntoSlot
|
|
144
153
|
});
|
|
@@ -172,7 +181,7 @@ export class SequencerTimetable {
|
|
|
172
181
|
const available = (maxAllowed - secondsIntoSlot) / 2; // Split remaining time: half for execution, half for re-execution
|
|
173
182
|
const canStart = available >= this.minExecutionTime;
|
|
174
183
|
const deadline = secondsIntoSlot + available;
|
|
175
|
-
this.log
|
|
184
|
+
this.log?.verbose(`${canStart ? 'Can' : 'Cannot'} start single-block checkpoint at ${secondsIntoSlot}s into slot`, {
|
|
176
185
|
secondsIntoSlot,
|
|
177
186
|
maxAllowed,
|
|
178
187
|
available,
|
|
@@ -194,7 +203,7 @@ export class SequencerTimetable {
|
|
|
194
203
|
if (timeUntilDeadline >= this.minExecutionTime) {
|
|
195
204
|
// Found an available sub-slot! Is this the last one?
|
|
196
205
|
const isLastBlock = subSlot === this.maxNumberOfBlocks;
|
|
197
|
-
this.log
|
|
206
|
+
this.log?.verbose(`Can start ${isLastBlock ? 'last block' : 'block'} in sub-slot ${subSlot} with deadline ${deadline}s`, {
|
|
198
207
|
secondsIntoSlot,
|
|
199
208
|
deadline,
|
|
200
209
|
timeUntilDeadline,
|
|
@@ -209,7 +218,7 @@ export class SequencerTimetable {
|
|
|
209
218
|
}
|
|
210
219
|
}
|
|
211
220
|
// No sub-slots available with enough time
|
|
212
|
-
this.log
|
|
221
|
+
this.log?.verbose(`No time left to start any more blocks`, {
|
|
213
222
|
secondsIntoSlot,
|
|
214
223
|
maxBlocks: this.maxNumberOfBlocks,
|
|
215
224
|
initializationOffset: this.initializationOffset
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import type { L1RollupConstants } from '@aztec/stdlib/epoch-helpers';
|
|
2
|
-
export type SequencerRollupConstants = Pick<L1RollupConstants, 'ethereumSlotDuration' | 'l1GenesisTime' | 'slotDuration'>;
|
|
3
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
2
|
+
export type SequencerRollupConstants = Pick<L1RollupConstants, 'ethereumSlotDuration' | 'l1GenesisTime' | 'slotDuration' | 'rollupManaLimit'>;
|
|
3
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidHlwZXMuZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9zZXF1ZW5jZXIvdHlwZXMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLEVBQUUsaUJBQWlCLEVBQUUsTUFBTSw2QkFBNkIsQ0FBQztBQUVyRSxNQUFNLE1BQU0sd0JBQXdCLEdBQUcsSUFBSSxDQUN6QyxpQkFBaUIsRUFDakIsc0JBQXNCLEdBQUcsZUFBZSxHQUFHLGNBQWMsR0FBRyxpQkFBaUIsQ0FDOUUsQ0FBQyJ9
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/sequencer/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AAErE,MAAM,MAAM,wBAAwB,GAAG,IAAI,CACzC,iBAAiB,EACjB,sBAAsB,GAAG,eAAe,GAAG,cAAc,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/sequencer/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AAErE,MAAM,MAAM,wBAAwB,GAAG,IAAI,CACzC,iBAAiB,EACjB,sBAAsB,GAAG,eAAe,GAAG,cAAc,GAAG,iBAAiB,CAC9E,CAAC"}
|