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