@aztec/sequencer-client 0.0.1-commit.1142ef1 → 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 +17 -7
- package/dest/client/sequencer-client.d.ts.map +1 -1
- package/dest/client/sequencer-client.js +63 -30
- package/dest/config.d.ts +26 -7
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +52 -38
- 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 -16
- 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/index.d.ts +2 -2
- package/dest/index.d.ts.map +1 -1
- package/dest/index.js +1 -1
- package/dest/publisher/config.d.ts +47 -17
- package/dest/publisher/config.d.ts.map +1 -1
- package/dest/publisher/config.js +121 -42
- package/dest/publisher/index.d.ts +2 -1
- package/dest/publisher/index.d.ts.map +1 -1
- package/dest/publisher/l1_tx_failed_store/factory.d.ts +11 -0
- package/dest/publisher/l1_tx_failed_store/factory.d.ts.map +1 -0
- package/dest/publisher/l1_tx_failed_store/factory.js +22 -0
- package/dest/publisher/l1_tx_failed_store/failed_tx_store.d.ts +58 -0
- package/dest/publisher/l1_tx_failed_store/failed_tx_store.d.ts.map +1 -0
- package/dest/publisher/l1_tx_failed_store/failed_tx_store.js +1 -0
- package/dest/publisher/l1_tx_failed_store/file_store_failed_tx_store.d.ts +15 -0
- package/dest/publisher/l1_tx_failed_store/file_store_failed_tx_store.d.ts.map +1 -0
- package/dest/publisher/l1_tx_failed_store/file_store_failed_tx_store.js +34 -0
- package/dest/publisher/l1_tx_failed_store/index.d.ts +4 -0
- package/dest/publisher/l1_tx_failed_store/index.d.ts.map +1 -0
- package/dest/publisher/l1_tx_failed_store/index.js +2 -0
- package/dest/publisher/sequencer-publisher-factory.d.ts +11 -5
- package/dest/publisher/sequencer-publisher-factory.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher-factory.js +27 -3
- package/dest/publisher/sequencer-publisher-metrics.d.ts +1 -1
- package/dest/publisher/sequencer-publisher-metrics.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher-metrics.js +12 -4
- package/dest/publisher/sequencer-publisher.d.ts +75 -49
- package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher.js +470 -156
- 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 +54 -12
- package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -1
- package/dest/sequencer/checkpoint_proposal_job.js +450 -184
- 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 +3 -3
- package/dest/sequencer/checkpoint_voter.d.ts.map +1 -1
- package/dest/sequencer/checkpoint_voter.js +34 -13
- package/dest/sequencer/events.d.ts +2 -1
- package/dest/sequencer/events.d.ts.map +1 -1
- package/dest/sequencer/index.d.ts +1 -2
- package/dest/sequencer/index.d.ts.map +1 -1
- package/dest/sequencer/index.js +0 -1
- package/dest/sequencer/metrics.d.ts +19 -8
- package/dest/sequencer/metrics.d.ts.map +1 -1
- package/dest/sequencer/metrics.js +120 -36
- package/dest/sequencer/sequencer.d.ts +58 -22
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +218 -96
- package/dest/sequencer/timetable.d.ts +17 -6
- package/dest/sequencer/timetable.d.ts.map +1 -1
- package/dest/sequencer/timetable.js +51 -46
- package/dest/sequencer/types.d.ts +2 -2
- package/dest/sequencer/types.d.ts.map +1 -1
- package/dest/test/index.d.ts +3 -5
- package/dest/test/index.d.ts.map +1 -1
- package/dest/test/mock_checkpoint_builder.d.ts +23 -19
- package/dest/test/mock_checkpoint_builder.d.ts.map +1 -1
- package/dest/test/mock_checkpoint_builder.js +67 -38
- package/dest/test/utils.d.ts +8 -8
- package/dest/test/utils.d.ts.map +1 -1
- package/dest/test/utils.js +12 -11
- package/package.json +29 -28
- package/src/client/sequencer-client.ts +81 -30
- package/src/config.ts +71 -49
- 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 +27 -63
- package/src/global_variable_builder/index.ts +3 -1
- package/src/index.ts +1 -6
- package/src/publisher/config.ts +157 -45
- package/src/publisher/index.ts +3 -0
- package/src/publisher/l1_tx_failed_store/factory.ts +32 -0
- package/src/publisher/l1_tx_failed_store/failed_tx_store.ts +57 -0
- package/src/publisher/l1_tx_failed_store/file_store_failed_tx_store.ts +46 -0
- package/src/publisher/l1_tx_failed_store/index.ts +3 -0
- package/src/publisher/sequencer-publisher-factory.ts +38 -9
- package/src/publisher/sequencer-publisher-metrics.ts +7 -3
- package/src/publisher/sequencer-publisher.ts +531 -214
- package/src/sequencer/README.md +83 -13
- package/src/sequencer/chain_state_overrides.ts +87 -0
- package/src/sequencer/checkpoint_proposal_job.ts +601 -206
- package/src/sequencer/checkpoint_proposal_job_metrics.ts +128 -0
- package/src/sequencer/checkpoint_voter.ts +31 -17
- package/src/sequencer/events.ts +1 -1
- package/src/sequencer/index.ts +0 -1
- package/src/sequencer/metrics.ts +136 -40
- package/src/sequencer/sequencer.ts +302 -105
- package/src/sequencer/timetable.ts +70 -57
- package/src/sequencer/types.ts +1 -1
- package/src/test/index.ts +2 -4
- package/src/test/mock_checkpoint_builder.ts +122 -78
- package/src/test/utils.ts +24 -14
- package/dest/sequencer/block_builder.d.ts +0 -26
- package/dest/sequencer/block_builder.d.ts.map +0 -1
- package/dest/sequencer/block_builder.js +0 -129
- package/src/sequencer/block_builder.ts +0 -216
|
@@ -370,17 +370,17 @@ function applyDecs2203RFactory() {
|
|
|
370
370
|
function _apply_decs_2203_r(targetClass, memberDecs, classDecs, parentClass) {
|
|
371
371
|
return (_apply_decs_2203_r = applyDecs2203RFactory())(targetClass, memberDecs, classDecs, parentClass);
|
|
372
372
|
}
|
|
373
|
-
var _dec, _dec1, _dec2, _initProto;
|
|
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';
|
|
@@ -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';
|
|
@@ -396,6 +397,8 @@ import { SequencerState } from './utils.js';
|
|
|
396
397
|
export { SequencerState };
|
|
397
398
|
_dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpointProposal'), _dec2 = trackSpan('Seqeuencer.tryVoteWhenSyncFails', ({ slot })=>({
|
|
398
399
|
[Attributes.SLOT_NUMBER]: slot
|
|
400
|
+
})), _dec3 = trackSpan('Sequencer.tryVoteWhenEscapeHatchOpen', ({ slot })=>({
|
|
401
|
+
[Attributes.SLOT_NUMBER]: slot
|
|
399
402
|
}));
|
|
400
403
|
/**
|
|
401
404
|
* Sequencer client
|
|
@@ -436,24 +439,25 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
|
|
|
436
439
|
_dec2,
|
|
437
440
|
2,
|
|
438
441
|
"tryVoteWhenSyncFails"
|
|
442
|
+
],
|
|
443
|
+
[
|
|
444
|
+
_dec3,
|
|
445
|
+
2,
|
|
446
|
+
"tryVoteWhenEscapeHatchOpen"
|
|
439
447
|
]
|
|
440
448
|
], []));
|
|
441
449
|
}
|
|
442
450
|
runningPromise;
|
|
443
451
|
state;
|
|
444
452
|
metrics;
|
|
445
|
-
|
|
453
|
+
checkpointProposalJobMetrics;
|
|
454
|
+
/** The last slot for which we attempted to perform our voting duties with degraded block production */ lastSlotForFallbackVote;
|
|
455
|
+
/** The last slot for which we logged "no committee" warning, to avoid spam */ lastSlotForNoCommitteeWarning;
|
|
446
456
|
/** The last slot for which we triggered a checkpoint proposal job, to prevent duplicate attempts. */ lastSlotForCheckpointProposalJob;
|
|
447
457
|
/** Last successful checkpoint proposed */ lastCheckpointProposed;
|
|
448
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;
|
|
449
460
|
/** The maximum number of seconds that the sequencer can be into a slot to transition to a particular state. */ timetable;
|
|
450
|
-
// This shouldn't be here as this gets re-created each time we build/propose a block.
|
|
451
|
-
// But we have a number of tests that abuse/rely on this class having a permanent publisher.
|
|
452
|
-
// As long as those tests only configure a single publisher they will continue to work.
|
|
453
|
-
// This will get re-assigned every time the sequencer goes to build a new block to a publisher that is valid
|
|
454
|
-
// for the block proposer.
|
|
455
|
-
// TODO(palla/mbps): Remove this field and fix tests
|
|
456
|
-
publisher;
|
|
457
461
|
/** Config for the sequencer */ config;
|
|
458
462
|
constructor(publisherFactory, validatorClient, globalsBuilder, p2pClient, worldState, slasherClient, l2BlockSource, l1ToL2MessageSource, checkpointsBuilder, l1Constants, dateProvider, epochCache, rollupContract, config, telemetry = getTelemetryClient(), log = createLogger('sequencer')){
|
|
459
463
|
super(), this.publisherFactory = publisherFactory, this.validatorClient = validatorClient, this.globalsBuilder = globalsBuilder, this.p2pClient = p2pClient, this.worldState = worldState, this.slasherClient = slasherClient, this.l2BlockSource = l2BlockSource, this.l1ToL2MessageSource = l1ToL2MessageSource, this.checkpointsBuilder = checkpointsBuilder, this.l1Constants = l1Constants, this.dateProvider = dateProvider, this.epochCache = epochCache, this.rollupContract = rollupContract, this.telemetry = telemetry, this.log = log, this.state = (_initProto(this), SequencerState.STOPPED), this.config = DefaultSequencerConfig;
|
|
@@ -462,24 +466,26 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
|
|
|
462
466
|
this.log = log.createChild('[FISHERMAN]');
|
|
463
467
|
}
|
|
464
468
|
this.metrics = new SequencerMetrics(telemetry, this.rollupContract, 'Sequencer');
|
|
469
|
+
this.checkpointProposalJobMetrics = new CheckpointProposalJobMetrics(telemetry);
|
|
465
470
|
this.updateConfig(config);
|
|
466
471
|
}
|
|
467
472
|
/** Updates sequencer config by the defined values and updates the timetable */ updateConfig(config) {
|
|
468
473
|
const filteredConfig = pickFromSchema(config, SequencerConfigSchema);
|
|
469
|
-
this.log.info(`Updated sequencer config`, omit(filteredConfig, '
|
|
474
|
+
this.log.info(`Updated sequencer config`, omit(filteredConfig, 'txPublicSetupAllowListExtend'));
|
|
470
475
|
this.config = merge(this.config, filteredConfig);
|
|
476
|
+
const p2pPropagationTime = this.config.attestationPropagationTime;
|
|
471
477
|
this.timetable = new SequencerTimetable({
|
|
472
478
|
ethereumSlotDuration: this.l1Constants.ethereumSlotDuration,
|
|
473
479
|
aztecSlotDuration: this.aztecSlotDuration,
|
|
474
480
|
l1PublishingTime: this.l1PublishingTime,
|
|
475
|
-
p2pPropagationTime
|
|
481
|
+
p2pPropagationTime,
|
|
476
482
|
blockDurationMs: this.config.blockDurationMs,
|
|
477
|
-
enforce: this.config.enforceTimeTable
|
|
483
|
+
enforce: this.config.enforceTimeTable,
|
|
484
|
+
pipelining: this.epochCache.isProposerPipeliningEnabled()
|
|
478
485
|
}, this.metrics, this.log);
|
|
479
486
|
}
|
|
480
|
-
/** Initializes the sequencer (precomputes tables
|
|
487
|
+
/** Initializes the sequencer (precomputes tables). Takes about 3s. */ init() {
|
|
481
488
|
getKzg();
|
|
482
|
-
this.publisher = (await this.publisherFactory.create(undefined)).publisher;
|
|
483
489
|
}
|
|
484
490
|
/** Starts the sequencer and moves to IDLE state. */ start() {
|
|
485
491
|
this.runningPromise = new RunningPromise(this.safeWork.bind(this), this.log, this.config.sequencerPollingIntervalMS);
|
|
@@ -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.
|
|
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
|
});
|
|
@@ -509,7 +519,6 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
|
|
|
509
519
|
error: err
|
|
510
520
|
});
|
|
511
521
|
if (err instanceof SequencerTooSlowError) {
|
|
512
|
-
// TODO(palla/mbps): Add missing states
|
|
513
522
|
// Log as warn only if we had to abort halfway through the block proposal
|
|
514
523
|
const logLvl = [
|
|
515
524
|
SequencerState.INITIALIZING_CHECKPOINT,
|
|
@@ -540,22 +549,25 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
|
|
|
540
549
|
* - Submit checkpoint
|
|
541
550
|
*/ async work() {
|
|
542
551
|
this.setState(SequencerState.SYNCHRONIZING, undefined);
|
|
543
|
-
const { slot, ts,
|
|
552
|
+
const { slot, ts, nowSeconds, epoch } = this.epochCache.getEpochAndSlotInNextL1Slot();
|
|
553
|
+
const { slot: targetSlot, epoch: targetEpoch } = this.epochCache.getTargetEpochAndSlotInNextL1Slot();
|
|
544
554
|
// Check if we are synced and it's our slot, grab a publisher, check previous block invalidation, etc
|
|
545
|
-
const checkpointProposalJob = await this.prepareCheckpointProposal(slot, ts,
|
|
555
|
+
const checkpointProposalJob = await this.prepareCheckpointProposal(slot, targetSlot, epoch, targetEpoch, ts, nowSeconds);
|
|
546
556
|
if (!checkpointProposalJob) {
|
|
547
557
|
return;
|
|
548
558
|
}
|
|
559
|
+
// Track the job so we can await its pending L1 submission during shutdown
|
|
560
|
+
this.lastCheckpointProposalJob = checkpointProposalJob;
|
|
549
561
|
// Execute the checkpoint proposal job
|
|
550
562
|
const checkpoint = await checkpointProposalJob.execute();
|
|
551
563
|
// Update last checkpoint proposed (currently unused)
|
|
552
564
|
if (checkpoint) {
|
|
553
565
|
this.lastCheckpointProposed = checkpoint;
|
|
554
566
|
}
|
|
555
|
-
// Log fee strategy comparison if on fisherman
|
|
556
|
-
if (this.config.fishermanMode && (this.lastEpochForStrategyComparison === undefined ||
|
|
557
|
-
this.logStrategyComparison(
|
|
558
|
-
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;
|
|
559
571
|
}
|
|
560
572
|
return checkpoint;
|
|
561
573
|
}
|
|
@@ -563,17 +575,17 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
|
|
|
563
575
|
* Prepares the checkpoint proposal by performing all necessary checks and setup.
|
|
564
576
|
* This is the initial step in the main loop.
|
|
565
577
|
* @returns CheckpointProposalJob if successful, undefined if we are not yet synced or are not the proposer.
|
|
566
|
-
*/ async prepareCheckpointProposal(slot, ts,
|
|
567
|
-
// 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)
|
|
568
580
|
// We only check this if enforce timetable is set, since we want to keep processing the same slot if we are not
|
|
569
581
|
// running against actual time (eg when we use sandbox-style automining)
|
|
570
|
-
if (this.lastSlotForCheckpointProposalJob && this.lastSlotForCheckpointProposalJob >=
|
|
571
|
-
this.log.trace(`
|
|
582
|
+
if (this.lastSlotForCheckpointProposalJob && this.lastSlotForCheckpointProposalJob >= targetSlot && this.config.enforceTimeTable) {
|
|
583
|
+
this.log.trace(`Target slot ${targetSlot} has already been processed`);
|
|
572
584
|
return undefined;
|
|
573
585
|
}
|
|
574
|
-
// But if we have already proposed for this slot,
|
|
575
|
-
if (this.lastCheckpointProposed && this.lastCheckpointProposed.header.slotNumber >=
|
|
576
|
-
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}`);
|
|
577
589
|
return undefined;
|
|
578
590
|
}
|
|
579
591
|
// Check all components are synced to latest as seen by the archiver (queries all subsystems)
|
|
@@ -584,36 +596,65 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
|
|
|
584
596
|
if (!syncedTo) {
|
|
585
597
|
await this.tryVoteWhenSyncFails({
|
|
586
598
|
slot,
|
|
599
|
+
targetSlot,
|
|
587
600
|
ts
|
|
588
601
|
});
|
|
589
602
|
return undefined;
|
|
590
603
|
}
|
|
604
|
+
// If escape hatch is open for the target epoch, do not start checkpoint proposal work and do not attempt invalidations.
|
|
605
|
+
// Still perform governance/slashing voting (as proposer) once per slot.
|
|
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);
|
|
608
|
+
if (isEscapeHatchOpen) {
|
|
609
|
+
this.setState(SequencerState.PROPOSER_CHECK, slot);
|
|
610
|
+
const [canPropose, proposer] = await this.checkCanPropose(targetSlot);
|
|
611
|
+
if (canPropose) {
|
|
612
|
+
await this.tryVoteWhenEscapeHatchOpen({
|
|
613
|
+
slot,
|
|
614
|
+
proposer
|
|
615
|
+
});
|
|
616
|
+
} else {
|
|
617
|
+
this.log.trace(`Escape hatch open but we are not proposer, skipping vote-only actions`, {
|
|
618
|
+
slot,
|
|
619
|
+
epoch,
|
|
620
|
+
proposer
|
|
621
|
+
});
|
|
622
|
+
}
|
|
623
|
+
return undefined;
|
|
624
|
+
}
|
|
591
625
|
// Next checkpoint follows from the last synced one
|
|
592
626
|
const checkpointNumber = CheckpointNumber(syncedTo.checkpointNumber + 1);
|
|
593
627
|
const logCtx = {
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
syncedToL2Slot: getSlotAtTimestamp(syncedTo.l1Timestamp, this.l1Constants),
|
|
628
|
+
nowSeconds,
|
|
629
|
+
syncedToL2Slot: syncedTo.syncedL2Slot,
|
|
597
630
|
slot,
|
|
631
|
+
targetSlot,
|
|
598
632
|
slotTs: ts,
|
|
599
633
|
checkpointNumber,
|
|
600
634
|
isPendingChainValid: pick(syncedTo.pendingChainValidationStatus, 'valid', 'reason', 'invalidIndex')
|
|
601
635
|
};
|
|
602
|
-
// Check that we are a proposer for the
|
|
636
|
+
// Check that we are a proposer for the target slot.
|
|
603
637
|
this.setState(SequencerState.PROPOSER_CHECK, slot);
|
|
604
|
-
const [canPropose, proposer] = await this.checkCanPropose(
|
|
638
|
+
const [canPropose, proposer] = await this.checkCanPropose(targetSlot);
|
|
605
639
|
// If we are not a proposer check if we should invalidate an invalid checkpoint, and bail
|
|
606
640
|
if (!canPropose) {
|
|
607
641
|
await this.considerInvalidatingCheckpoint(syncedTo, slot);
|
|
608
642
|
return undefined;
|
|
609
643
|
}
|
|
610
|
-
//
|
|
611
|
-
|
|
612
|
-
|
|
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}`, {
|
|
613
654
|
...logCtx,
|
|
614
|
-
block: syncedTo.
|
|
655
|
+
block: syncedTo.blockData.header.toInspect()
|
|
615
656
|
});
|
|
616
|
-
this.metrics.
|
|
657
|
+
this.metrics.recordCheckpointPrecheckFailed('slot_already_taken');
|
|
617
658
|
return undefined;
|
|
618
659
|
}
|
|
619
660
|
// We now need to get ourselves a publisher.
|
|
@@ -623,37 +664,58 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
|
|
|
623
664
|
const proposerForPublisher = this.config.fishermanMode ? undefined : proposer;
|
|
624
665
|
const { attestorAddress, publisher } = await this.publisherFactory.create(proposerForPublisher);
|
|
625
666
|
this.log.verbose(`Created publisher at address ${publisher.getSenderAddress()} for attestor ${attestorAddress}`);
|
|
626
|
-
this.publisher = publisher;
|
|
627
667
|
// In fisherman mode, set the actual proposer's address for simulations
|
|
628
668
|
if (this.config.fishermanMode && proposer) {
|
|
629
669
|
publisher.setProposerAddressForSimulation(proposer);
|
|
630
670
|
this.log.debug(`Set proposer address ${proposer} for simulation in fisherman mode`);
|
|
631
671
|
}
|
|
632
672
|
// Prepare invalidation request if the pending chain is invalid (returns undefined if no need)
|
|
633
|
-
|
|
634
|
-
//
|
|
635
|
-
//
|
|
636
|
-
|
|
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);
|
|
637
699
|
if (canProposeCheck === undefined) {
|
|
638
700
|
this.log.warn(`Cannot propose checkpoint ${checkpointNumber} at slot ${slot} due to failed rollup contract check`, logCtx);
|
|
639
701
|
this.emit('proposer-rollup-check-failed', {
|
|
640
702
|
reason: 'Rollup contract check failed',
|
|
641
703
|
slot
|
|
642
704
|
});
|
|
643
|
-
this.metrics.
|
|
705
|
+
this.metrics.recordCheckpointPrecheckFailed('rollup_contract_check_failed');
|
|
644
706
|
return undefined;
|
|
645
707
|
}
|
|
646
|
-
if (canProposeCheck.slot !==
|
|
647
|
-
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}.`, {
|
|
648
710
|
...logCtx,
|
|
649
711
|
rollup: canProposeCheck,
|
|
650
|
-
expectedSlot:
|
|
712
|
+
expectedSlot: targetSlot
|
|
651
713
|
});
|
|
652
714
|
this.emit('proposer-rollup-check-failed', {
|
|
653
715
|
reason: 'Slot mismatch',
|
|
654
716
|
slot
|
|
655
717
|
});
|
|
656
|
-
this.metrics.
|
|
718
|
+
this.metrics.recordCheckpointPrecheckFailed('slot_mismatch');
|
|
657
719
|
return undefined;
|
|
658
720
|
}
|
|
659
721
|
if (canProposeCheck.checkpointNumber !== checkpointNumber) {
|
|
@@ -666,19 +728,26 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
|
|
|
666
728
|
reason: 'Block mismatch',
|
|
667
729
|
slot
|
|
668
730
|
});
|
|
669
|
-
this.metrics.
|
|
731
|
+
this.metrics.recordCheckpointPrecheckFailed('block_number_mismatch');
|
|
670
732
|
return undefined;
|
|
671
733
|
}
|
|
672
|
-
this.lastSlotForCheckpointProposalJob =
|
|
673
|
-
this.
|
|
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}`, {
|
|
674
737
|
...logCtx,
|
|
675
|
-
proposer
|
|
738
|
+
proposer,
|
|
739
|
+
pipeliningEnabled: this.epochCache.isProposerPipeliningEnabled()
|
|
676
740
|
});
|
|
677
741
|
// Create and return the checkpoint proposal job
|
|
678
|
-
return this.createCheckpointProposalJob(slot, checkpointNumber, syncedTo.blockNumber, proposer, publisher, attestorAddress, invalidateCheckpoint);
|
|
742
|
+
return this.createCheckpointProposalJob(slot, targetSlot, targetEpoch, checkpointNumber, syncedTo.blockNumber, proposer, publisher, attestorAddress, invalidateCheckpoint, syncedTo.proposedCheckpointData);
|
|
679
743
|
}
|
|
680
|
-
createCheckpointProposalJob(slot, checkpointNumber, syncedToBlockNumber, proposer, publisher, attestorAddress, invalidateCheckpoint) {
|
|
681
|
-
return new CheckpointProposalJob(slot, checkpointNumber, syncedToBlockNumber, proposer, publisher, attestorAddress, invalidateCheckpoint, this.validatorClient, this.globalsBuilder, this.p2pClient, this.worldState, this.l1ToL2MessageSource, this.checkpointsBuilder, this.l2BlockSource, this.l1Constants, this.config, this.timetable, this.slasherClient, this.epochCache, this.dateProvider, this.metrics, this, this.setState.bind(this), this.
|
|
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);
|
|
746
|
+
}
|
|
747
|
+
/**
|
|
748
|
+
* Returns the current sequencer state.
|
|
749
|
+
*/ getState() {
|
|
750
|
+
return this.state;
|
|
682
751
|
}
|
|
683
752
|
/**
|
|
684
753
|
* Internal helper for setting the sequencer state and checks if we have enough time left in the slot to transition to the new state.
|
|
@@ -720,16 +789,15 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
|
|
|
720
789
|
* Returns whether all dependencies have caught up.
|
|
721
790
|
* We don't check against the previous block submitted since it may have been reorg'd out.
|
|
722
791
|
*/ async checkSync(args) {
|
|
723
|
-
// Check that the archiver
|
|
724
|
-
//
|
|
725
|
-
//
|
|
726
|
-
const
|
|
727
|
-
const { slot
|
|
728
|
-
if (
|
|
792
|
+
// Check that the archiver has fully synced the L2 slot before the one we want to propose in.
|
|
793
|
+
// The archiver reports sync progress via L1 block timestamps and synced checkpoint slots.
|
|
794
|
+
// See getSyncedL2SlotNumber for how missed L1 blocks are handled.
|
|
795
|
+
const syncedL2Slot = await this.l2BlockSource.getSyncedL2SlotNumber();
|
|
796
|
+
const { slot } = args;
|
|
797
|
+
if (syncedL2Slot === undefined || syncedL2Slot + 1 < slot) {
|
|
729
798
|
this.log.debug(`Cannot propose block at next L2 slot ${slot} due to pending sync from L1`, {
|
|
730
799
|
slot,
|
|
731
|
-
|
|
732
|
-
l1Timestamp
|
|
800
|
+
syncedL2Slot
|
|
733
801
|
});
|
|
734
802
|
return undefined;
|
|
735
803
|
}
|
|
@@ -738,22 +806,30 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
|
|
|
738
806
|
number: syncSummary.latestBlockNumber,
|
|
739
807
|
hash: syncSummary.latestBlockHash
|
|
740
808
|
})),
|
|
741
|
-
this.l2BlockSource.getL2Tips().then((t)=>
|
|
809
|
+
this.l2BlockSource.getL2Tips().then((t)=>({
|
|
810
|
+
proposed: t.proposed,
|
|
811
|
+
checkpointed: t.checkpointed,
|
|
812
|
+
proposedCheckpoint: t.proposedCheckpoint
|
|
813
|
+
})),
|
|
742
814
|
this.p2pClient.getStatus().then((p2p)=>p2p.syncedToL2Block),
|
|
743
|
-
this.l1ToL2MessageSource.getL2Tips().then((t)=>
|
|
744
|
-
|
|
815
|
+
this.l1ToL2MessageSource.getL2Tips().then((t)=>({
|
|
816
|
+
proposed: t.proposed,
|
|
817
|
+
checkpointed: t.checkpointed
|
|
818
|
+
})),
|
|
819
|
+
this.l2BlockSource.getPendingChainValidationStatus(),
|
|
820
|
+
this.l2BlockSource.getProposedCheckpointOnly()
|
|
745
821
|
]);
|
|
746
|
-
const [worldState,
|
|
822
|
+
const [worldState, l2Tips, p2p, l1ToL2MessageSourceTips, pendingChainValidationStatus, proposedCheckpointData] = syncedBlocks;
|
|
747
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,
|
|
748
824
|
// as the world state can compute the new genesis block hash, but other components use the hardcoded constant.
|
|
749
825
|
// TODO(palla/mbps): Fix the above. All components should be able to handle dynamic genesis block hashes.
|
|
750
|
-
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;
|
|
751
827
|
if (!result) {
|
|
752
828
|
this.log.debug(`Sequencer sync check failed`, {
|
|
753
829
|
worldState,
|
|
754
|
-
l2BlockSource,
|
|
830
|
+
l2BlockSource: l2Tips.proposed,
|
|
755
831
|
p2p,
|
|
756
|
-
|
|
832
|
+
l1ToL2MessageSourceTips
|
|
757
833
|
});
|
|
758
834
|
return undefined;
|
|
759
835
|
}
|
|
@@ -763,43 +839,52 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
|
|
|
763
839
|
const archive = new Fr((await this.worldState.getCommitted().getTreeInfo(MerkleTreeId.ARCHIVE)).root);
|
|
764
840
|
return {
|
|
765
841
|
checkpointNumber: CheckpointNumber.ZERO,
|
|
842
|
+
checkpointedCheckpointNumber: CheckpointNumber.ZERO,
|
|
766
843
|
blockNumber: BlockNumber.ZERO,
|
|
767
844
|
archive,
|
|
768
|
-
|
|
845
|
+
hasProposedCheckpoint: false,
|
|
846
|
+
syncedL2Slot,
|
|
769
847
|
pendingChainValidationStatus
|
|
770
848
|
};
|
|
771
849
|
}
|
|
772
|
-
const
|
|
773
|
-
if (!
|
|
850
|
+
const blockData = await this.l2BlockSource.getBlockData(blockNumber);
|
|
851
|
+
if (!blockData) {
|
|
774
852
|
// this shouldn't really happen because a moment ago we checked that all components were in sync
|
|
775
|
-
this.log.error(`Failed to get L2 block ${blockNumber} from the archiver with all components in sync`);
|
|
853
|
+
this.log.error(`Failed to get L2 block data ${blockNumber} from the archiver with all components in sync`);
|
|
776
854
|
return undefined;
|
|
777
855
|
}
|
|
856
|
+
const hasProposedCheckpoint = l2Tips.proposedCheckpoint.checkpoint.number > l2Tips.checkpointed.checkpoint.number;
|
|
778
857
|
return {
|
|
779
|
-
|
|
780
|
-
blockNumber:
|
|
781
|
-
checkpointNumber:
|
|
782
|
-
|
|
783
|
-
|
|
858
|
+
blockData,
|
|
859
|
+
blockNumber: blockData.header.getBlockNumber(),
|
|
860
|
+
checkpointNumber: blockData.checkpointNumber,
|
|
861
|
+
checkpointedCheckpointNumber: l2Tips.checkpointed.checkpoint.number,
|
|
862
|
+
archive: blockData.archive.root,
|
|
863
|
+
hasProposedCheckpoint,
|
|
864
|
+
proposedCheckpointData,
|
|
865
|
+
syncedL2Slot,
|
|
784
866
|
pendingChainValidationStatus
|
|
785
867
|
};
|
|
786
868
|
}
|
|
787
869
|
/**
|
|
788
870
|
* Checks if we are the proposer for the next slot.
|
|
789
871
|
* @returns True if we can propose, and the proposer address (undefined if anyone can propose)
|
|
790
|
-
*/ async checkCanPropose(
|
|
872
|
+
*/ async checkCanPropose(targetSlot) {
|
|
791
873
|
let proposer;
|
|
792
874
|
try {
|
|
793
|
-
proposer = await this.epochCache.getProposerAttesterAddressInSlot(
|
|
875
|
+
proposer = await this.epochCache.getProposerAttesterAddressInSlot(targetSlot);
|
|
794
876
|
} catch (e) {
|
|
795
877
|
if (e instanceof NoCommitteeError) {
|
|
796
|
-
this.
|
|
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`);
|
|
881
|
+
}
|
|
797
882
|
return [
|
|
798
883
|
false,
|
|
799
884
|
undefined
|
|
800
885
|
];
|
|
801
886
|
}
|
|
802
|
-
this.log.error(`Error getting proposer for slot ${
|
|
887
|
+
this.log.error(`Error getting proposer for target slot ${targetSlot}`, e);
|
|
803
888
|
return [
|
|
804
889
|
false,
|
|
805
890
|
undefined
|
|
@@ -822,7 +907,8 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
|
|
|
822
907
|
const validatorAddresses = this.validatorClient.getValidatorAddresses();
|
|
823
908
|
const weAreProposer = validatorAddresses.some((addr)=>addr.equals(proposer));
|
|
824
909
|
if (!weAreProposer) {
|
|
825
|
-
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,
|
|
826
912
|
validatorAddresses,
|
|
827
913
|
proposer
|
|
828
914
|
});
|
|
@@ -831,6 +917,10 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
|
|
|
831
917
|
proposer
|
|
832
918
|
];
|
|
833
919
|
}
|
|
920
|
+
this.log.info(`We are the proposer for pipeline slot ${targetSlot}`, {
|
|
921
|
+
targetSlot,
|
|
922
|
+
proposer
|
|
923
|
+
});
|
|
834
924
|
return [
|
|
835
925
|
true,
|
|
836
926
|
proposer
|
|
@@ -840,9 +930,9 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
|
|
|
840
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.
|
|
841
931
|
* This allows the sequencer to participate in governance/slashing votes even when it cannot build blocks.
|
|
842
932
|
*/ async tryVoteWhenSyncFails(args) {
|
|
843
|
-
const { slot } = args;
|
|
933
|
+
const { slot, targetSlot } = args;
|
|
844
934
|
// Prevent duplicate attempts in the same slot
|
|
845
|
-
if (this.
|
|
935
|
+
if (this.lastSlotForFallbackVote === slot) {
|
|
846
936
|
this.log.trace(`Already attempted to vote in slot ${slot} (skipping)`);
|
|
847
937
|
return;
|
|
848
938
|
}
|
|
@@ -863,7 +953,7 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
|
|
|
863
953
|
maxAllowedTime
|
|
864
954
|
});
|
|
865
955
|
// Check if we're a proposer or proposal is open
|
|
866
|
-
const [canPropose, proposer] = await this.checkCanPropose(
|
|
956
|
+
const [canPropose, proposer] = await this.checkCanPropose(targetSlot);
|
|
867
957
|
if (!canPropose) {
|
|
868
958
|
this.log.trace(`Cannot vote in slot ${slot} since we are not a proposer`, {
|
|
869
959
|
slot,
|
|
@@ -872,15 +962,15 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
|
|
|
872
962
|
return;
|
|
873
963
|
}
|
|
874
964
|
// Mark this slot as attempted
|
|
875
|
-
this.
|
|
965
|
+
this.lastSlotForFallbackVote = slot;
|
|
876
966
|
// Get a publisher for voting
|
|
877
967
|
const { attestorAddress, publisher } = await this.publisherFactory.create(proposer);
|
|
878
968
|
this.log.debug(`Attempting to vote despite sync failure at slot ${slot}`, {
|
|
879
969
|
attestorAddress,
|
|
880
970
|
slot
|
|
881
971
|
});
|
|
882
|
-
// Enqueue governance and slashing votes
|
|
883
|
-
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);
|
|
884
974
|
const votesPromises = voter.enqueueVotes();
|
|
885
975
|
const votes = await Promise.all(votesPromises);
|
|
886
976
|
if (votes.every((p)=>!p)) {
|
|
@@ -893,12 +983,41 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
|
|
|
893
983
|
await publisher.sendRequests();
|
|
894
984
|
}
|
|
895
985
|
/**
|
|
896
|
-
*
|
|
986
|
+
* Tries to vote on slashing actions and governance proposals when escape hatch is open.
|
|
987
|
+
* This allows the sequencer to participate in voting without performing checkpoint proposal work.
|
|
988
|
+
*/ async tryVoteWhenEscapeHatchOpen(args) {
|
|
989
|
+
const { slot, proposer } = args;
|
|
990
|
+
// Prevent duplicate attempts in the same slot
|
|
991
|
+
if (this.lastSlotForFallbackVote === slot) {
|
|
992
|
+
this.log.trace(`Already attempted to vote in slot ${slot} (escape hatch open, skipping)`);
|
|
993
|
+
return;
|
|
994
|
+
}
|
|
995
|
+
// Mark this slot as attempted
|
|
996
|
+
this.lastSlotForFallbackVote = slot;
|
|
997
|
+
const { attestorAddress, publisher } = await this.publisherFactory.create(proposer);
|
|
998
|
+
this.log.debug(`Escape hatch open for slot ${slot}, attempting vote-only actions`, {
|
|
999
|
+
slot,
|
|
1000
|
+
attestorAddress
|
|
1001
|
+
});
|
|
1002
|
+
const voter = new CheckpointVoter(slot, publisher, attestorAddress, this.validatorClient, this.slasherClient, this.l1Constants, this.config, this.metrics, this.log);
|
|
1003
|
+
const votesPromises = voter.enqueueVotes();
|
|
1004
|
+
const votes = await Promise.all(votesPromises);
|
|
1005
|
+
if (votes.every((p)=>!p)) {
|
|
1006
|
+
this.log.debug(`No votes to enqueue for slot ${slot} (escape hatch open)`);
|
|
1007
|
+
return;
|
|
1008
|
+
}
|
|
1009
|
+
this.log.info(`Voting in slot ${slot} (escape hatch open)`, {
|
|
1010
|
+
slot
|
|
1011
|
+
});
|
|
1012
|
+
await publisher.sendRequests();
|
|
1013
|
+
}
|
|
1014
|
+
/**
|
|
1015
|
+
* Considers invalidating a block if the pending chain is invalid. Depends on how long the invalid block
|
|
897
1016
|
* has been there without being invalidated and whether the sequencer is in the committee or not. We always
|
|
898
1017
|
* have the proposer try to invalidate, but if they fail, the sequencers in the committee are expected to try,
|
|
899
1018
|
* and if they fail, any sequencer will try as well.
|
|
900
1019
|
*/ async considerInvalidatingCheckpoint(syncedTo, currentSlot) {
|
|
901
|
-
const { pendingChainValidationStatus,
|
|
1020
|
+
const { pendingChainValidationStatus, syncedL2Slot } = syncedTo;
|
|
902
1021
|
if (pendingChainValidationStatus.valid) {
|
|
903
1022
|
return;
|
|
904
1023
|
}
|
|
@@ -909,7 +1028,7 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
|
|
|
909
1028
|
const { secondsBeforeInvalidatingBlockAsCommitteeMember, secondsBeforeInvalidatingBlockAsNonCommitteeMember } = this.config;
|
|
910
1029
|
const logData = {
|
|
911
1030
|
invalidL1Timestamp: invalidCheckpointTimestamp,
|
|
912
|
-
|
|
1031
|
+
syncedL2Slot,
|
|
913
1032
|
invalidCheckpoint: pendingChainValidationStatus.checkpoint,
|
|
914
1033
|
secondsBeforeInvalidatingBlockAsCommitteeMember,
|
|
915
1034
|
secondsBeforeInvalidatingBlockAsNonCommitteeMember,
|
|
@@ -1000,6 +1119,9 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
|
|
|
1000
1119
|
getValidatorAddresses() {
|
|
1001
1120
|
return this.validatorClient?.getValidatorAddresses();
|
|
1002
1121
|
}
|
|
1122
|
+
/** Updates the publisher factory's node keystore adapter after a keystore reload. */ updatePublisherNodeKeyStore(adapter) {
|
|
1123
|
+
this.publisherFactory.updateNodeKeyStore(adapter);
|
|
1124
|
+
}
|
|
1003
1125
|
getConfig() {
|
|
1004
1126
|
return this.config;
|
|
1005
1127
|
}
|