@aztec/sequencer-client 5.0.0-rc.1 → 5.0.0
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 +16 -3
- package/dest/client/sequencer-client.d.ts.map +1 -1
- package/dest/client/sequencer-client.js +17 -3
- package/dest/config.d.ts +2 -1
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +8 -3
- package/dest/global_variable_builder/fee_predictor.d.ts +1 -1
- package/dest/global_variable_builder/fee_predictor.d.ts.map +1 -1
- package/dest/global_variable_builder/fee_predictor.js +11 -1
- package/dest/global_variable_builder/fee_provider.d.ts +1 -1
- package/dest/global_variable_builder/fee_provider.d.ts.map +1 -1
- package/dest/global_variable_builder/fee_provider.js +15 -2
- package/dest/global_variable_builder/global_builder.d.ts +3 -16
- package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
- package/dest/global_variable_builder/global_builder.js +2 -25
- package/dest/publisher/config.d.ts +5 -1
- package/dest/publisher/config.d.ts.map +1 -1
- package/dest/publisher/config.js +11 -1
- package/dest/publisher/l1_to_l2_messaging.d.ts +21 -0
- package/dest/publisher/l1_to_l2_messaging.d.ts.map +1 -0
- package/dest/publisher/l1_to_l2_messaging.js +70 -0
- package/dest/publisher/sequencer-publisher.d.ts +17 -4
- package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher.js +119 -22
- package/dest/publisher/write_json.d.ts +11 -0
- package/dest/publisher/write_json.d.ts.map +1 -0
- package/dest/publisher/write_json.js +57 -0
- package/dest/sequencer/automine/automine_sequencer.d.ts +8 -3
- package/dest/sequencer/automine/automine_sequencer.d.ts.map +1 -1
- package/dest/sequencer/automine/automine_sequencer.js +27 -14
- package/dest/sequencer/checkpoint_proposal_job.d.ts +4 -6
- package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -1
- package/dest/sequencer/checkpoint_proposal_job.js +28 -26
- package/dest/sequencer/events.d.ts +1 -2
- package/dest/sequencer/events.d.ts.map +1 -1
- package/dest/sequencer/requests_tracker.d.ts +22 -0
- package/dest/sequencer/requests_tracker.d.ts.map +1 -0
- package/dest/sequencer/requests_tracker.js +33 -0
- package/dest/sequencer/sequencer.d.ts +47 -10
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +125 -60
- package/dest/test/utils.d.ts +1 -1
- package/dest/test/utils.d.ts.map +1 -1
- package/dest/test/utils.js +2 -1
- package/package.json +27 -27
- package/src/client/sequencer-client.ts +19 -3
- package/src/config.ts +8 -3
- package/src/global_variable_builder/fee_predictor.ts +11 -1
- package/src/global_variable_builder/fee_provider.ts +19 -2
- package/src/global_variable_builder/global_builder.ts +2 -34
- package/src/publisher/config.ts +22 -1
- package/src/publisher/l1_to_l2_messaging.ts +85 -0
- package/src/publisher/sequencer-publisher.ts +116 -23
- package/src/publisher/write_json.ts +78 -0
- package/src/sequencer/automine/automine_sequencer.ts +27 -14
- package/src/sequencer/checkpoint_proposal_job.ts +43 -34
- package/src/sequencer/events.ts +1 -1
- package/src/sequencer/requests_tracker.ts +43 -0
- package/src/sequencer/sequencer.ts +133 -61
- package/src/test/utils.ts +2 -0
- package/dest/sequencer/chain_state_overrides.d.ts +0 -61
- package/dest/sequencer/chain_state_overrides.d.ts.map +0 -1
- package/dest/sequencer/chain_state_overrides.js +0 -98
- package/src/sequencer/chain_state_overrides.ts +0 -169
|
@@ -379,6 +379,7 @@ import { merge, omit, pick } from '@aztec/foundation/collection';
|
|
|
379
379
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
380
380
|
import { createLogger } from '@aztec/foundation/log';
|
|
381
381
|
import { RunningPromise } from '@aztec/foundation/running-promise';
|
|
382
|
+
import { buildCheckpointSimulationOverridesPlan } from '@aztec/stdlib/checkpoint';
|
|
382
383
|
import { getEpochAtSlot } from '@aztec/stdlib/epoch-helpers';
|
|
383
384
|
import { MIN_PER_BLOCK_ALLOCATION_MULTIPLIER, MIN_PER_BLOCK_DA_ALLOCATION_MULTIPLIER, computeNetworkTxGasLimits } from '@aztec/stdlib/gas';
|
|
384
385
|
import { SequencerConfigSchema } from '@aztec/stdlib/interfaces/server';
|
|
@@ -387,15 +388,15 @@ import { buildProposerTimetable } from '@aztec/stdlib/timetable';
|
|
|
387
388
|
import { Attributes, getTelemetryClient, trackSpan } from '@aztec/telemetry-client';
|
|
388
389
|
import EventEmitter from 'node:events';
|
|
389
390
|
import { DefaultSequencerConfig } from '../config.js';
|
|
390
|
-
import { buildCheckpointSimulationOverridesPlan } from './chain_state_overrides.js';
|
|
391
391
|
import { CheckpointProposalJob } from './checkpoint_proposal_job.js';
|
|
392
392
|
import { CheckpointProposalJobMetrics } from './checkpoint_proposal_job_metrics.js';
|
|
393
393
|
import { CheckpointVoter } from './checkpoint_voter.js';
|
|
394
394
|
import { SequencerInterruptedError } from './errors.js';
|
|
395
395
|
import { SequencerMetrics } from './metrics.js';
|
|
396
|
+
import { RequestsTracker } from './requests_tracker.js';
|
|
396
397
|
import { SequencerState } from './utils.js';
|
|
397
398
|
export { SequencerState };
|
|
398
|
-
_dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpointProposal'), _dec2 = trackSpan('Sequencer.
|
|
399
|
+
_dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpointProposal'), _dec2 = trackSpan('Sequencer.tryVoteAndPruneWhenCannotBuild', ({ slot })=>({
|
|
399
400
|
[Attributes.SLOT_NUMBER]: slot
|
|
400
401
|
})), _dec3 = trackSpan('Sequencer.tryVoteWhenEscapeHatchOpen', ({ slot })=>({
|
|
401
402
|
[Attributes.SLOT_NUMBER]: slot
|
|
@@ -438,7 +439,7 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
|
|
|
438
439
|
[
|
|
439
440
|
_dec2,
|
|
440
441
|
2,
|
|
441
|
-
"
|
|
442
|
+
"tryVoteAndPruneWhenCannotBuild"
|
|
442
443
|
],
|
|
443
444
|
[
|
|
444
445
|
_dec3,
|
|
@@ -454,7 +455,7 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
|
|
|
454
455
|
metrics;
|
|
455
456
|
checkpointProposalJobMetrics;
|
|
456
457
|
stateLog;
|
|
457
|
-
/** The last slot for which we attempted to perform our
|
|
458
|
+
/** The last slot for which we attempted to perform our fallback duties (votes and/or prune) with degraded block production */ lastSlotForFallbackAction;
|
|
458
459
|
/** The (checkpoint, slot) of the last invalidation request we successfully simulated, to prevent
|
|
459
460
|
* re-simulating and re-submitting the same invalidation across the many ticks within a single slot. */ lastInvalidationAttempt;
|
|
460
461
|
/** The last slot for which we logged "no committee" warning, to avoid spam */ lastSlotForNoCommitteeWarning;
|
|
@@ -462,11 +463,20 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
|
|
|
462
463
|
/** Last successful checkpoint proposed */ lastCheckpointProposed;
|
|
463
464
|
/** The last epoch for which we logged strategy comparison in fisherman mode. */ lastEpochForStrategyComparison;
|
|
464
465
|
/** The last checkpoint proposal job, tracked so we can await its pending L1 submission during shutdown. */ lastCheckpointProposalJob;
|
|
466
|
+
/**
|
|
467
|
+
* In-flight fire-and-forget requests that {@link stop} interrupts and drains, and {@link pause} awaits
|
|
468
|
+
* untouched: the checkpoint proposal jobs' backgrounded L1 submissions (each job is handed this shared
|
|
469
|
+
* tracker) plus the sequencer's own fallback submissions (votes/prune when we cannot build, or
|
|
470
|
+
* escape-hatch votes). Each fallback send is gated by its wrapper publisher's interruptible target-slot
|
|
471
|
+
* sleep; the tracked interrupt wakes that sleep so the send short-circuits without publishing. A wrapper
|
|
472
|
+
* is created for a single send and is never restarted, so once interrupted its sleeper can never publish
|
|
473
|
+
* a stale-slot tx, even after the pooled publishers are restarted by a later {@link start}.
|
|
474
|
+
*/ pendingRequests;
|
|
465
475
|
/** Proposer schedule and block sub-slot timetable for the sequencer, rebuilt on every config update. */ timetable;
|
|
466
476
|
/** Config for the sequencer */ config;
|
|
467
477
|
signatureContext;
|
|
468
478
|
constructor(publisherFactory, validatorClient, globalsBuilder, p2pClient, worldState, slasherClient, l2BlockSource, l1ToL2MessageSource, checkpointsBuilder, l1Constants, dateProvider, epochCache, rollupContract, config, telemetry = getTelemetryClient(), log = createLogger('sequencer')){
|
|
469
|
-
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;
|
|
479
|
+
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.pendingRequests = new RequestsTracker(), this.config = DefaultSequencerConfig;
|
|
470
480
|
this.stateLog = log.createChild('state');
|
|
471
481
|
this.stateEnteredAtMs = this.dateProvider.now();
|
|
472
482
|
// Add [FISHERMAN] prefix to logger if in fisherman mode
|
|
@@ -580,7 +590,21 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
|
|
|
580
590
|
/** Initializes the sequencer (precomputes tables). Takes about 3s. */ init() {
|
|
581
591
|
getKzg();
|
|
582
592
|
}
|
|
583
|
-
/**
|
|
593
|
+
/**
|
|
594
|
+
* Starts (or restarts) the sequencer and moves it to the IDLE state. Idempotent: a start while already
|
|
595
|
+
* running is a no-op, so it never orphans the previous poll loop. A start while STOPPING throws, since the
|
|
596
|
+
* in-flight stop would mark the fresh loop's state STOPPED and orphan it — silently doing nothing would
|
|
597
|
+
* leave the caller believing the sequencer is running when it is on its way to STOPPED. Safe to call after
|
|
598
|
+
* a previous {@link stop} has resolved; the caller must also restart the publishers (see
|
|
599
|
+
* {@link SequencerClient.start}) so L1 publishing is re-enabled.
|
|
600
|
+
*/ start() {
|
|
601
|
+
if (this.state === SequencerState.STOPPING) {
|
|
602
|
+
throw new Error('Cannot start sequencer while it is stopping');
|
|
603
|
+
}
|
|
604
|
+
if (this.runningPromise?.isRunning()) {
|
|
605
|
+
this.log.warn('Attempted to start sequencer that is already running');
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
584
608
|
this.runningPromise = new RunningPromise(this.safeWork.bind(this), this.log, this.config.sequencerPollingIntervalMS);
|
|
585
609
|
this.setState(SequencerState.IDLE, undefined, {
|
|
586
610
|
force: true
|
|
@@ -591,20 +615,56 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
|
|
|
591
615
|
/** Triggers an immediate run of the sequencer, bypassing the polling interval. */ trigger() {
|
|
592
616
|
return this.runningPromise?.trigger();
|
|
593
617
|
}
|
|
594
|
-
/**
|
|
618
|
+
/**
|
|
619
|
+
* Stops the sequencer from building blocks and moves it to STOPPED state, interrupting the in-flight
|
|
620
|
+
* work() iteration and its pending L1 submissions for a fast shutdown, then draining before returning.
|
|
621
|
+
* Idempotent: a second call while already stopped or stopping is a no-op. Lifecycle calls are expected
|
|
622
|
+
* to be serialized by the caller. For a restartable pause that lets in-flight work finish untouched
|
|
623
|
+
* (e.g. around a test clock warp), use {@link pause} instead.
|
|
624
|
+
*/ async stop() {
|
|
625
|
+
if (this.state === SequencerState.STOPPED || this.state === SequencerState.STOPPING) {
|
|
626
|
+
this.log.debug(`Sequencer already ${this.state.toLowerCase()}, ignoring stop`);
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
595
629
|
this.log.info(`Stopping sequencer`);
|
|
596
630
|
this.setState(SequencerState.STOPPING, undefined, {
|
|
597
631
|
force: true
|
|
598
632
|
});
|
|
599
633
|
this.lastCheckpointProposalJob?.interrupt();
|
|
600
634
|
await this.publisherFactory.stopAll();
|
|
635
|
+
// Stop the poll loop and await the in-flight work() iteration. work() registers its fire-and-forget
|
|
636
|
+
// requests synchronously before returning, so once the loop has stopped, pendingRequests holds every
|
|
637
|
+
// request that will ever exist. Interrupting any earlier could miss a request registered by the last
|
|
638
|
+
// in-flight iteration.
|
|
601
639
|
await this.runningPromise?.stop();
|
|
602
|
-
|
|
640
|
+
this.pendingRequests.interruptRequests();
|
|
641
|
+
await this.pendingRequests.awaitRequests();
|
|
603
642
|
this.setState(SequencerState.STOPPED, undefined, {
|
|
604
643
|
force: true
|
|
605
644
|
});
|
|
606
645
|
this.log.info('Stopped sequencer');
|
|
607
646
|
}
|
|
647
|
+
/**
|
|
648
|
+
* Gracefully pauses block production so the sequencer can later be resumed with {@link start}: halts the
|
|
649
|
+
* poll loop and waits for the in-flight work() iteration and every pending L1 submission / fallback send
|
|
650
|
+
* to finish, without interrupting them. No interrupt lands mid-build, so no spurious checkpoint-error is
|
|
651
|
+
* emitted and no enqueued checkpoint is dropped. Deliberately does not stop inner services (validator/HA
|
|
652
|
+
* signer, publishers), so the slashing-protection store stays open and the sequencer stays restartable.
|
|
653
|
+
* Used by tests that pause sequencers around an L1 clock warp. Idempotent.
|
|
654
|
+
*/ async pause() {
|
|
655
|
+
if (!this.runningPromise?.isRunning()) {
|
|
656
|
+
this.log.debug('Sequencer not running, ignoring pause');
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
659
|
+
this.log.info('Pausing sequencer');
|
|
660
|
+
// Halt the poll loop and let the in-flight iteration finish naturally — no interrupt, and no STOPPING
|
|
661
|
+
// state, since entering STOPPING would make the iteration's own setState calls throw and fail it.
|
|
662
|
+
// work() registers its fire-and-forget requests synchronously before returning, so once the loop has
|
|
663
|
+
// stopped pendingRequests holds every request that will ever exist; awaiting it lets them all finish.
|
|
664
|
+
await this.runningPromise.stop();
|
|
665
|
+
await this.pendingRequests.awaitRequests();
|
|
666
|
+
this.log.info('Paused sequencer');
|
|
667
|
+
}
|
|
608
668
|
/** Main sequencer loop with a try/catch */ async safeWork() {
|
|
609
669
|
try {
|
|
610
670
|
await this.work();
|
|
@@ -731,18 +791,6 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
|
|
|
731
791
|
}
|
|
732
792
|
return undefined;
|
|
733
793
|
}
|
|
734
|
-
// We are the proposer and the escape hatch is closed: now run the full sync check before building.
|
|
735
|
-
const syncedTo = await this.checkSync({
|
|
736
|
-
ts,
|
|
737
|
-
slot
|
|
738
|
-
});
|
|
739
|
-
if (!syncedTo) {
|
|
740
|
-
await this.tryVoteWhenCannotBuild({
|
|
741
|
-
slot,
|
|
742
|
-
targetSlot
|
|
743
|
-
});
|
|
744
|
-
return undefined;
|
|
745
|
-
}
|
|
746
794
|
// Explicit build-loop entry gate: if we are past the latest useful block-building start for the
|
|
747
795
|
// target slot, abandon building for this slot. The proposer prioritizes the ideal L1-publish path
|
|
748
796
|
// and does not plan around the late consensus-handoff path. This is the proposer build path's
|
|
@@ -759,13 +807,22 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
|
|
|
759
807
|
});
|
|
760
808
|
// Mark the slot as attempted so a deadline abort is not retried within the same slot. Vote-only actions
|
|
761
809
|
// still need to run because sync can succeed even when it is too late to start building a checkpoint.
|
|
762
|
-
await this.
|
|
810
|
+
await this.tryVoteAndPruneWhenCannotBuild({
|
|
763
811
|
slot,
|
|
764
812
|
targetSlot
|
|
765
813
|
});
|
|
766
814
|
this.lastSlotForCheckpointProposalJob = targetSlot;
|
|
767
815
|
return undefined;
|
|
768
816
|
}
|
|
817
|
+
// We are the proposer, the escape hatch is closed, and we have time before the build start deadline.
|
|
818
|
+
// Now run the full sync check before building.
|
|
819
|
+
const syncedTo = await this.checkSync({
|
|
820
|
+
ts,
|
|
821
|
+
slot
|
|
822
|
+
});
|
|
823
|
+
if (!syncedTo) {
|
|
824
|
+
return undefined;
|
|
825
|
+
}
|
|
769
826
|
// Next checkpoint follows from the last synced one
|
|
770
827
|
const checkpointNumber = CheckpointNumber(syncedTo.checkpointNumber + 1);
|
|
771
828
|
const logCtx = {
|
|
@@ -926,7 +983,7 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
|
|
|
926
983
|
return this.createCheckpointProposalJob(targetSlot, targetEpoch, checkpointNumber, syncedTo.blockNumber, syncedTo.checkpointedCheckpointNumber, proposer, publisher, attestorAddress, invalidateCheckpoint, syncedTo.proposedCheckpointData);
|
|
927
984
|
}
|
|
928
985
|
createCheckpointProposalJob(targetSlot, targetEpoch, checkpointNumber, syncedToBlockNumber, checkpointedCheckpointNumber, proposer, publisher, attestorAddress, invalidateCheckpoint, proposedCheckpointData) {
|
|
929
|
-
return new CheckpointProposalJob(targetSlot, targetEpoch, checkpointNumber, syncedToBlockNumber, checkpointedCheckpointNumber, proposer, publisher, attestorAddress, invalidateCheckpoint, this.validatorClient, this.globalsBuilder, this.p2pClient, this.worldState, this.l1ToL2MessageSource, this.l2BlockSource, this.checkpointsBuilder, this.l2BlockSource, this.l1Constants, this.signatureContext, 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);
|
|
986
|
+
return new CheckpointProposalJob(targetSlot, targetEpoch, checkpointNumber, syncedToBlockNumber, checkpointedCheckpointNumber, proposer, publisher, attestorAddress, invalidateCheckpoint, this.validatorClient, this.globalsBuilder, this.p2pClient, this.worldState, this.l1ToL2MessageSource, this.l2BlockSource, this.checkpointsBuilder, this.l2BlockSource, this.l1Constants, this.signatureContext, this.config, this.timetable, this.slasherClient, this.epochCache, this.dateProvider, this.metrics, this.checkpointProposalJobMetrics.createRecorder(), this, this.pendingRequests, this.setState.bind(this), this.tracer, this.log.getBindings(), proposedCheckpointData);
|
|
930
987
|
}
|
|
931
988
|
/**
|
|
932
989
|
* Returns the current sequencer state.
|
|
@@ -1008,8 +1065,7 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
|
|
|
1008
1065
|
})),
|
|
1009
1066
|
this.l2BlockSource.getL2Tips().then((t)=>({
|
|
1010
1067
|
proposed: t.proposed,
|
|
1011
|
-
checkpointed: t.checkpointed
|
|
1012
|
-
proposedCheckpoint: t.proposedCheckpoint
|
|
1068
|
+
checkpointed: t.checkpointed
|
|
1013
1069
|
})),
|
|
1014
1070
|
this.p2pClient.getStatus().then((p2p)=>p2p.syncedToL2Block),
|
|
1015
1071
|
this.l1ToL2MessageSource.getL2Tips().then((t)=>({
|
|
@@ -1049,12 +1105,14 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
|
|
|
1049
1105
|
// matching proposed checkpoint (e.g. it crashed before assembling it). Building on this orphan block
|
|
1050
1106
|
// would fork the chain off a tip no other node can follow. The archiver prunes these orphan blocks
|
|
1051
1107
|
// once their build slot ends; this guard is the correctness barrier during the grace window before.
|
|
1052
|
-
|
|
1108
|
+
// `getProposedCheckpointData()` returns the latest proposed checkpoint payload, which is always
|
|
1109
|
+
// the leading one (a proposed entry is only stored beyond the confirmed frontier and is deleted
|
|
1110
|
+
// on confirmation). It carries no tip, so there is no tip-vs-payload split read to reconcile.
|
|
1111
|
+
if (blockData.checkpointNumber > l2Tips.checkpointed.checkpoint.number && proposedCheckpointData?.checkpointNumber !== blockData.checkpointNumber) {
|
|
1053
1112
|
const logCtx = {
|
|
1054
1113
|
blockCheckpointNumber: blockData.checkpointNumber,
|
|
1055
1114
|
checkpointedCheckpointNumber: l2Tips.checkpointed.checkpoint.number,
|
|
1056
|
-
proposedCheckpointTipNumber:
|
|
1057
|
-
proposedCheckpointDataNumber: proposedCheckpointData?.checkpointNumber,
|
|
1115
|
+
proposedCheckpointTipNumber: proposedCheckpointData?.checkpointNumber,
|
|
1058
1116
|
blockNumber: blockData.header.getBlockNumber(),
|
|
1059
1117
|
blockSlot: blockData.header.getSlot(),
|
|
1060
1118
|
syncedL2Slot,
|
|
@@ -1063,22 +1121,7 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
|
|
|
1063
1121
|
this.log.debug(`Waiting for proposed checkpoint to catch up with reexecuted block`, logCtx);
|
|
1064
1122
|
return undefined;
|
|
1065
1123
|
}
|
|
1066
|
-
const hasProposedCheckpoint =
|
|
1067
|
-
// The l2Tips and proposedCheckpointData reads above come from independent archiver snapshots
|
|
1068
|
-
// (a JS-side tips cache vs. a direct store read on `#proposedCheckpoints`). A concurrent archiver
|
|
1069
|
-
// write that mutates both can be observed split, leaving us with `hasProposedCheckpoint=true` but
|
|
1070
|
-
// no proposedCheckpointData (or one whose number doesn't match the tip). Refuse to proceed in that
|
|
1071
|
-
// window — the next checkSync tick will see a coherent snapshot.
|
|
1072
|
-
if (hasProposedCheckpoint && (!proposedCheckpointData || proposedCheckpointData.checkpointNumber !== l2Tips.proposedCheckpoint.checkpoint.number)) {
|
|
1073
|
-
this.log.warn(`Sequencer sync check failed: inconsistent proposed-checkpoint state`, {
|
|
1074
|
-
proposedCheckpointTipNumber: l2Tips.proposedCheckpoint.checkpoint.number,
|
|
1075
|
-
checkpointedTipNumber: l2Tips.checkpointed.checkpoint.number,
|
|
1076
|
-
proposedCheckpointDataNumber: proposedCheckpointData?.checkpointNumber,
|
|
1077
|
-
syncedL2Slot,
|
|
1078
|
-
...args
|
|
1079
|
-
});
|
|
1080
|
-
return undefined;
|
|
1081
|
-
}
|
|
1124
|
+
const hasProposedCheckpoint = proposedCheckpointData !== undefined;
|
|
1082
1125
|
// Check that the proposed checkpoint is indeed the parent of the checkpoint we'll be building
|
|
1083
1126
|
// The checkpoint number to build is derived as blockData.checkpointNumber + 1
|
|
1084
1127
|
if (proposedCheckpointData && proposedCheckpointData.checkpointNumber !== blockData.checkpointNumber) {
|
|
@@ -1163,13 +1206,14 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
|
|
|
1163
1206
|
];
|
|
1164
1207
|
}
|
|
1165
1208
|
/**
|
|
1166
|
-
* Tries to vote on slashing actions and governance when we cannot build and are past the
|
|
1167
|
-
* This allows the sequencer to participate in governance/slashing votes even when it
|
|
1168
|
-
|
|
1209
|
+
* Tries to vote on slashing actions and governance and to prune when we cannot build and are past the
|
|
1210
|
+
* block-building window. This allows the sequencer to participate in governance/slashing votes even when it
|
|
1211
|
+
* cannot build blocks, and to prune the pending chain so it can recover from bad data that is blocking sync.
|
|
1212
|
+
*/ async tryVoteAndPruneWhenCannotBuild(args) {
|
|
1169
1213
|
const { slot, targetSlot } = args;
|
|
1170
1214
|
// Prevent duplicate attempts in the same slot
|
|
1171
|
-
if (this.
|
|
1172
|
-
this.log.trace(`Already attempted
|
|
1215
|
+
if (this.lastSlotForFallbackAction === slot) {
|
|
1216
|
+
this.log.trace(`Already attempted fallback actions in slot ${slot} (skipping)`);
|
|
1173
1217
|
return;
|
|
1174
1218
|
}
|
|
1175
1219
|
// Vote-only actions do not give up the slot: if sync recovers, a later work-loop iteration can still build.
|
|
@@ -1191,7 +1235,7 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
|
|
|
1191
1235
|
return;
|
|
1192
1236
|
}
|
|
1193
1237
|
// Mark this slot as attempted
|
|
1194
|
-
this.
|
|
1238
|
+
this.lastSlotForFallbackAction = slot;
|
|
1195
1239
|
// Get a publisher for voting
|
|
1196
1240
|
const { attestorAddress, publisher } = await this.publisherFactory.create(proposer);
|
|
1197
1241
|
this.log.debug(`Attempting to vote despite sync failure at slot ${slot}`, {
|
|
@@ -1202,22 +1246,41 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
|
|
|
1202
1246
|
const voter = new CheckpointVoter(targetSlot, publisher, attestorAddress, this.validatorClient, this.slasherClient, this.l1Constants, this.config, this.metrics, this.log);
|
|
1203
1247
|
const votesPromises = voter.enqueueVotes();
|
|
1204
1248
|
const votes = await Promise.all(votesPromises);
|
|
1205
|
-
if (
|
|
1206
|
-
|
|
1249
|
+
// Even if we cannot build, try to prune so a stuck pending chain (e.g. bad data blocking sync) can
|
|
1250
|
+
// recover. prune() is permissionless, so it rides the same fallback multicall as the votes.
|
|
1251
|
+
const pruneEnqueued = await this.tryEnqueuePruneIfPrunable(targetSlot, publisher);
|
|
1252
|
+
// Bail if nothing to do
|
|
1253
|
+
if (votes.every((p)=>!p) && !pruneEnqueued) {
|
|
1254
|
+
this.log.debug(`Nothing to enqueue for slot ${slot} (no votes, not prunable)`);
|
|
1207
1255
|
return;
|
|
1208
1256
|
}
|
|
1209
|
-
|
|
1210
|
-
|
|
1257
|
+
const [governanceVoteEnqueued, slashingVoteEnqueued] = votes;
|
|
1258
|
+
this.log.info(`Submitting fallback requests in slot ${slot} despite sync failure`, {
|
|
1259
|
+
slot,
|
|
1260
|
+
pruneEnqueued,
|
|
1261
|
+
governanceVoteEnqueued: !!governanceVoteEnqueued,
|
|
1262
|
+
slashingVoteEnqueued: !!slashingVoteEnqueued
|
|
1211
1263
|
});
|
|
1212
1264
|
// Votes are EIP-712-signed for `targetSlot` (the pipelined slot in which the multicall is
|
|
1213
1265
|
// expected to mine). Delay submission to the start of `targetSlot` so the tx mines in the
|
|
1214
1266
|
// slot the votes were signed for. We fire-and-forget so we don't block the sequencer's
|
|
1215
|
-
// work loop while waiting for the target slot to start.
|
|
1216
|
-
|
|
1217
|
-
this.log.error(`Failed to publish
|
|
1267
|
+
// work loop while waiting for the target slot to start, but track it so stop() can drain it.
|
|
1268
|
+
const send = publisher.sendRequestsAt(targetSlot).catch((err)=>{
|
|
1269
|
+
this.log.error(`Failed to publish fallback requests despite sync failure for slot ${slot}`, err, {
|
|
1218
1270
|
slot
|
|
1219
1271
|
});
|
|
1220
1272
|
});
|
|
1273
|
+
this.pendingRequests.trackRequest(send, ()=>publisher.interrupt());
|
|
1274
|
+
}
|
|
1275
|
+
async tryEnqueuePruneIfPrunable(targetSlot, publisher) {
|
|
1276
|
+
try {
|
|
1277
|
+
return await publisher.enqueuePruneIfPrunable(targetSlot);
|
|
1278
|
+
} catch (err) {
|
|
1279
|
+
this.log.error(`Failed to enqueue rollup prune for slot ${targetSlot}`, err, {
|
|
1280
|
+
targetSlot
|
|
1281
|
+
});
|
|
1282
|
+
return false;
|
|
1283
|
+
}
|
|
1221
1284
|
}
|
|
1222
1285
|
/**
|
|
1223
1286
|
* Tries to vote on slashing actions and governance proposals when escape hatch is open.
|
|
@@ -1225,12 +1288,12 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
|
|
|
1225
1288
|
*/ async tryVoteWhenEscapeHatchOpen(args) {
|
|
1226
1289
|
const { slot, targetSlot, proposer } = args;
|
|
1227
1290
|
// Prevent duplicate attempts in the same slot
|
|
1228
|
-
if (this.
|
|
1291
|
+
if (this.lastSlotForFallbackAction === slot) {
|
|
1229
1292
|
this.log.trace(`Already attempted to vote in slot ${slot} (escape hatch open, skipping)`);
|
|
1230
1293
|
return;
|
|
1231
1294
|
}
|
|
1232
1295
|
// Mark this slot as attempted
|
|
1233
|
-
this.
|
|
1296
|
+
this.lastSlotForFallbackAction = slot;
|
|
1234
1297
|
const { attestorAddress, publisher } = await this.publisherFactory.create(proposer);
|
|
1235
1298
|
this.log.debug(`Escape hatch open for slot ${slot}, attempting vote-only actions`, {
|
|
1236
1299
|
slot,
|
|
@@ -1256,13 +1319,15 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
|
|
|
1256
1319
|
// the multicall mines in the slot the votes were signed for; otherwise the L1 contract reads
|
|
1257
1320
|
// `signaler = getCurrentProposer()` against the wrong slot and signature verification fails
|
|
1258
1321
|
// silently inside Multicall3. Fire-and-forget so we don't block the sequencer's work loop while
|
|
1259
|
-
// waiting for the target slot to start, mirroring
|
|
1260
|
-
|
|
1322
|
+
// waiting for the target slot to start, mirroring tryVoteAndPruneWhenCannotBuild, but tracked so
|
|
1323
|
+
// stop() can drain it.
|
|
1324
|
+
const send = publisher.sendRequestsAt(targetSlot).catch((err)=>{
|
|
1261
1325
|
this.log.error(`Failed to publish escape-hatch votes for slot ${slot}`, err, {
|
|
1262
1326
|
slot,
|
|
1263
1327
|
targetSlot
|
|
1264
1328
|
});
|
|
1265
1329
|
});
|
|
1330
|
+
this.pendingRequests.trackRequest(send, ()=>publisher.interrupt());
|
|
1266
1331
|
}
|
|
1267
1332
|
/**
|
|
1268
1333
|
* Considers invalidating a block if the pending chain is invalid. Depends on how long the invalid block
|
package/dest/test/utils.d.ts
CHANGED
|
@@ -64,4 +64,4 @@ export declare function setupTxsAndBlock(p2p: MockProxy<P2P>, globalVariables: G
|
|
|
64
64
|
txs: Tx[];
|
|
65
65
|
block: L2Block;
|
|
66
66
|
}>;
|
|
67
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
67
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidXRpbHMuZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy90ZXN0L3V0aWxzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUdBLE9BQU8sRUFBRSxlQUFlLEVBQUUsTUFBTSwyQ0FBMkMsQ0FBQztBQUM1RSxPQUFPLEVBQUUsRUFBRSxFQUFFLE1BQU0sZ0NBQWdDLENBQUM7QUFDcEQsT0FBTyxLQUFLLEVBQUUsVUFBVSxFQUFFLE1BQU0sK0JBQStCLENBQUM7QUFDaEUsT0FBTyxFQUFFLFNBQVMsRUFBRSxNQUFNLGlDQUFpQyxDQUFDO0FBQzVELE9BQU8sS0FBSyxFQUFFLEdBQUcsRUFBRSxNQUFNLFlBQVksQ0FBQztBQUV0QyxPQUFPLEVBQUUsb0JBQW9CLEVBQUUsT0FBTyxFQUFFLE1BQU0scUJBQXFCLENBQUM7QUFFcEUsT0FBTyxLQUFLLEVBQUUsaUJBQWlCLEVBQUUsTUFBTSw2QkFBNkIsQ0FBQztBQUNyRSxPQUFPLEVBQUUsYUFBYSxFQUFFLHFCQUFxQixFQUFFLGtCQUFrQixFQUFvQixNQUFNLG1CQUFtQixDQUFDO0FBTy9HLE9BQU8sRUFLTCxpQkFBaUIsRUFDbEIsTUFBTSx5QkFBeUIsQ0FBQztBQUNqQyxPQUFPLEVBQWUsZUFBZSxFQUFFLEtBQUssRUFBRSxFQUFvQyxNQUFNLGtCQUFrQixDQUFDO0FBRTNHLE9BQU8sS0FBSyxFQUFFLFNBQVMsRUFBRSxNQUFNLG9CQUFvQixDQUFDO0FBR3BELE9BQU8sRUFBRSxxQkFBcUIsRUFBRSxzQkFBc0IsRUFBRSxNQUFNLDhCQUE4QixDQUFDO0FBRTdGOzs7O0dBSUc7QUFDSCx3QkFBZ0IscUJBQXFCLENBQUMsSUFBSSxFQUFFO0lBQzFDLFdBQVcsRUFBRSxJQUFJLENBQUMsaUJBQWlCLEVBQUUsZUFBZSxHQUFHLGNBQWMsR0FBRyxzQkFBc0IsQ0FBQyxDQUFDO0lBQ2hHLGVBQWUsQ0FBQyxFQUFFLE1BQU0sQ0FBQztJQUN6QixnQkFBZ0IsQ0FBQyxFQUFFLE1BQU0sQ0FBQztJQUMxQixrQkFBa0IsQ0FBQyxFQUFFLE1BQU0sQ0FBQztJQUM1Qiw2QkFBNkIsQ0FBQyxFQUFFLE1BQU0sQ0FBQztDQUN4QyxHQUFHLGlCQUFpQixDQVNwQjtBQUVEOztHQUVHO0FBQ0gsd0JBQXNCLE1BQU0sQ0FBQyxJQUFJLENBQUMsRUFBRSxNQUFNLEVBQUUsT0FBTyxDQUFDLEVBQUUsRUFBRSxHQUFHLE9BQU8sQ0FBQyxFQUFFLENBQUMsQ0FNckU7QUFFRDs7R0FFRztBQUNILHdCQUFzQixTQUFTLENBQUMsR0FBRyxFQUFFLEVBQUUsRUFBRSxFQUFFLGVBQWUsRUFBRSxlQUFlLEdBQUcsT0FBTyxDQUFDLE9BQU8sQ0FBQyxDQWdCN0Y7QUFFRDs7R0FFRztBQUNILHdCQUFnQixjQUFjLENBQUMsR0FBRyxFQUFFLFNBQVMsQ0FBQyxHQUFHLENBQUMsRUFBRSxHQUFHLEVBQUUsRUFBRSxFQUFFLEdBQUcsSUFBSSxDQUtuRTtBQUVEOztHQUVHO0FBQ0gsd0JBQXVCLGNBQWMsQ0FBQyxHQUFHLEVBQUUsT0FBTyxDQUFDLEVBQUUsRUFBRSxDQUFDLEdBQUcscUJBQXFCLENBQUMsRUFBRSxDQUFDLENBSW5GO0FBRUQ7O0dBRUc7QUFDSCx3QkFBZ0Isb0JBQW9CLENBQUMsTUFBTSxFQUFFLGVBQWUsR0FBRyxvQkFBb0IsRUFBRSxDQUdwRjtBQXdCRDs7R0FFRztBQUNILHdCQUFnQixtQkFBbUIsQ0FBQyxLQUFLLEVBQUUsT0FBTyxFQUFFLFNBQVMsRUFBRSxTQUFTLEdBQUcsYUFBYSxDQVd2RjtBQUVEOztHQUVHO0FBQ0gsd0JBQWdCLHdCQUF3QixDQUN0QyxLQUFLLEVBQUUsT0FBTyxFQUNkLG1CQUFtQixFQUFFLFNBQVMsRUFDOUIsY0FBYyxDQUFDLEVBQUUsU0FBUyxFQUMxQixxQkFBcUIsR0FBRSxNQUFXLEdBQ2pDLGtCQUFrQixDQWdCcEI7QUFFRDs7OztHQUlHO0FBQ0gsd0JBQWdCLDJCQUEyQixDQUN6QyxLQUFLLEVBQUUsT0FBTyxFQUNkLFNBQVMsRUFBRSxTQUFTLEVBQ3BCLE1BQU0sRUFBRSxVQUFVLEVBQ2xCLHFCQUFxQixHQUFFLE1BQVcsR0FDakMscUJBQXFCLENBYXZCO0FBRUQ7OztHQUdHO0FBQ0gsd0JBQXNCLGdCQUFnQixDQUNwQyxHQUFHLEVBQUUsU0FBUyxDQUFDLEdBQUcsQ0FBQyxFQUNuQixlQUFlLEVBQUUsZUFBZSxFQUNoQyxPQUFPLEVBQUUsTUFBTSxFQUNmLE9BQU8sRUFBRSxFQUFFLEdBQ1YsT0FBTyxDQUFDO0lBQUUsR0FBRyxFQUFFLEVBQUUsRUFBRSxDQUFDO0lBQUMsS0FBSyxFQUFFLE9BQU8sQ0FBQTtDQUFFLENBQUMsQ0FLeEMifQ==
|
package/dest/test/utils.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/test/utils.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,eAAe,EAAE,MAAM,2CAA2C,CAAC;AAC5E,OAAO,EAAE,EAAE,EAAE,MAAM,gCAAgC,CAAC;AACpD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAChE,OAAO,EAAE,SAAS,EAAE,MAAM,iCAAiC,CAAC;AAC5D,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;AAEtC,OAAO,EAAE,oBAAoB,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAEpE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AACrE,OAAO,EAAE,aAAa,EAAE,qBAAqB,EAAE,kBAAkB,EAAoB,MAAM,mBAAmB,CAAC;AAO/G,OAAO,EAKL,iBAAiB,EAClB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAe,eAAe,EAAE,KAAK,EAAE,EAAoC,MAAM,kBAAkB,CAAC;AAE3G,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAGpD,OAAO,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,MAAM,8BAA8B,CAAC;AAE7F;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE;IAC1C,WAAW,EAAE,IAAI,CAAC,iBAAiB,EAAE,eAAe,GAAG,cAAc,GAAG,sBAAsB,CAAC,CAAC;IAChG,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,6BAA6B,CAAC,EAAE,MAAM,CAAC;CACxC,GAAG,iBAAiB,CASpB;AAED;;GAEG;AACH,wBAAsB,MAAM,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,EAAE,GAAG,OAAO,CAAC,EAAE,CAAC,CAMrE;AAED;;GAEG;AACH,wBAAsB,SAAS,CAAC,GAAG,EAAE,EAAE,EAAE,EAAE,eAAe,EAAE,eAAe,GAAG,OAAO,CAAC,OAAO,CAAC,CAgB7F;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,SAAS,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE,EAAE,EAAE,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/test/utils.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,eAAe,EAAE,MAAM,2CAA2C,CAAC;AAC5E,OAAO,EAAE,EAAE,EAAE,MAAM,gCAAgC,CAAC;AACpD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAChE,OAAO,EAAE,SAAS,EAAE,MAAM,iCAAiC,CAAC;AAC5D,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;AAEtC,OAAO,EAAE,oBAAoB,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAEpE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AACrE,OAAO,EAAE,aAAa,EAAE,qBAAqB,EAAE,kBAAkB,EAAoB,MAAM,mBAAmB,CAAC;AAO/G,OAAO,EAKL,iBAAiB,EAClB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAe,eAAe,EAAE,KAAK,EAAE,EAAoC,MAAM,kBAAkB,CAAC;AAE3G,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAGpD,OAAO,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,MAAM,8BAA8B,CAAC;AAE7F;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE;IAC1C,WAAW,EAAE,IAAI,CAAC,iBAAiB,EAAE,eAAe,GAAG,cAAc,GAAG,sBAAsB,CAAC,CAAC;IAChG,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,6BAA6B,CAAC,EAAE,MAAM,CAAC;CACxC,GAAG,iBAAiB,CASpB;AAED;;GAEG;AACH,wBAAsB,MAAM,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,EAAE,GAAG,OAAO,CAAC,EAAE,CAAC,CAMrE;AAED;;GAEG;AACH,wBAAsB,SAAS,CAAC,GAAG,EAAE,EAAE,EAAE,EAAE,eAAe,EAAE,eAAe,GAAG,OAAO,CAAC,OAAO,CAAC,CAgB7F;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,SAAS,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE,EAAE,EAAE,GAAG,IAAI,CAKnE;AAED;;GAEG;AACH,wBAAuB,cAAc,CAAC,GAAG,EAAE,OAAO,CAAC,EAAE,EAAE,CAAC,GAAG,qBAAqB,CAAC,EAAE,CAAC,CAInF;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,eAAe,GAAG,oBAAoB,EAAE,CAGpF;AAwBD;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,GAAG,aAAa,CAWvF;AAED;;GAEG;AACH,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,OAAO,EACd,mBAAmB,EAAE,SAAS,EAC9B,cAAc,CAAC,EAAE,SAAS,EAC1B,qBAAqB,GAAE,MAAW,GACjC,kBAAkB,CAgBpB;AAED;;;;GAIG;AACH,wBAAgB,2BAA2B,CACzC,KAAK,EAAE,OAAO,EACd,SAAS,EAAE,SAAS,EACpB,MAAM,EAAE,UAAU,EAClB,qBAAqB,GAAE,MAAW,GACjC,qBAAqB,CAavB;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,SAAS,CAAC,GAAG,CAAC,EACnB,eAAe,EAAE,eAAe,EAChC,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,EAAE,GACV,OAAO,CAAC;IAAE,GAAG,EAAE,EAAE,EAAE,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAAC,CAKxC"}
|
package/dest/test/utils.js
CHANGED
|
@@ -51,6 +51,7 @@ export { MockCheckpointBuilder, MockCheckpointsBuilder } from './mock_checkpoint
|
|
|
51
51
|
* Mocks the P2P client to return specific pending transactions
|
|
52
52
|
*/ export function mockPendingTxs(p2p, txs) {
|
|
53
53
|
p2p.getPendingTxCount.mockResolvedValue(txs.length);
|
|
54
|
+
p2p.hasEligiblePendingTxs.mockImplementation((minCount)=>Promise.resolve(txs.length >= minCount));
|
|
54
55
|
p2p.iteratePendingTxs.mockImplementation(()=>mockTxIterator(Promise.resolve(txs)));
|
|
55
56
|
p2p.iterateEligiblePendingTxs.mockImplementation(()=>mockTxIterator(Promise.resolve(txs)));
|
|
56
57
|
}
|
|
@@ -74,7 +75,7 @@ export { MockCheckpointBuilder, MockCheckpointsBuilder } from './mock_checkpoint
|
|
|
74
75
|
* Uses mock values for blockHeadersHash, blobsHash and inHash since L2Block doesn't have these fields.
|
|
75
76
|
*/ function createCheckpointHeaderFromBlock(block) {
|
|
76
77
|
const gv = block.header.globalVariables;
|
|
77
|
-
return new CheckpointHeader(block.header.lastArchive.root, Fr.random(), Fr.random(), Fr.random(), Fr.random(), gv.slotNumber, gv.timestamp, gv.coinbase, gv.feeRecipient, gv.gasFees, block.header.totalManaUsed);
|
|
78
|
+
return new CheckpointHeader(block.header.lastArchive.root, Fr.random(), Fr.random(), Fr.random(), Fr.random(), gv.slotNumber, gv.timestamp, gv.coinbase, gv.feeRecipient, gv.gasFees, block.header.totalManaUsed, block.header.totalFees);
|
|
78
79
|
}
|
|
79
80
|
/**
|
|
80
81
|
* Creates a block proposal from a block and signature
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aztec/sequencer-client",
|
|
3
|
-
"version": "5.0.0
|
|
3
|
+
"version": "5.0.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": "./dest/index.js",
|
|
@@ -27,37 +27,37 @@
|
|
|
27
27
|
"test:integration:run": "NODE_NO_WARNINGS=1 node --experimental-vm-modules $(yarn bin jest) --no-cache --config jest.integration.config.json"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@aztec/aztec.js": "5.0.0
|
|
31
|
-
"@aztec/bb-prover": "5.0.0
|
|
32
|
-
"@aztec/blob-client": "5.0.0
|
|
33
|
-
"@aztec/blob-lib": "5.0.0
|
|
34
|
-
"@aztec/constants": "5.0.0
|
|
35
|
-
"@aztec/epoch-cache": "5.0.0
|
|
36
|
-
"@aztec/ethereum": "5.0.0
|
|
37
|
-
"@aztec/foundation": "5.0.0
|
|
38
|
-
"@aztec/l1-artifacts": "5.0.0
|
|
39
|
-
"@aztec/node-keystore": "5.0.0
|
|
40
|
-
"@aztec/noir-acvm_js": "5.0.0
|
|
41
|
-
"@aztec/noir-contracts.js": "5.0.0
|
|
42
|
-
"@aztec/noir-protocol-circuits-types": "5.0.0
|
|
43
|
-
"@aztec/noir-types": "5.0.0
|
|
44
|
-
"@aztec/p2p": "5.0.0
|
|
45
|
-
"@aztec/protocol-contracts": "5.0.0
|
|
46
|
-
"@aztec/prover-client": "5.0.0
|
|
47
|
-
"@aztec/simulator": "5.0.0
|
|
48
|
-
"@aztec/slasher": "5.0.0
|
|
49
|
-
"@aztec/stdlib": "5.0.0
|
|
50
|
-
"@aztec/telemetry-client": "5.0.0
|
|
51
|
-
"@aztec/validator-client": "5.0.0
|
|
52
|
-
"@aztec/validator-ha-signer": "5.0.0
|
|
53
|
-
"@aztec/world-state": "5.0.0
|
|
30
|
+
"@aztec/aztec.js": "5.0.0",
|
|
31
|
+
"@aztec/bb-prover": "5.0.0",
|
|
32
|
+
"@aztec/blob-client": "5.0.0",
|
|
33
|
+
"@aztec/blob-lib": "5.0.0",
|
|
34
|
+
"@aztec/constants": "5.0.0",
|
|
35
|
+
"@aztec/epoch-cache": "5.0.0",
|
|
36
|
+
"@aztec/ethereum": "5.0.0",
|
|
37
|
+
"@aztec/foundation": "5.0.0",
|
|
38
|
+
"@aztec/l1-artifacts": "5.0.0",
|
|
39
|
+
"@aztec/node-keystore": "5.0.0",
|
|
40
|
+
"@aztec/noir-acvm_js": "5.0.0",
|
|
41
|
+
"@aztec/noir-contracts.js": "5.0.0",
|
|
42
|
+
"@aztec/noir-protocol-circuits-types": "5.0.0",
|
|
43
|
+
"@aztec/noir-types": "5.0.0",
|
|
44
|
+
"@aztec/p2p": "5.0.0",
|
|
45
|
+
"@aztec/protocol-contracts": "5.0.0",
|
|
46
|
+
"@aztec/prover-client": "5.0.0",
|
|
47
|
+
"@aztec/simulator": "5.0.0",
|
|
48
|
+
"@aztec/slasher": "5.0.0",
|
|
49
|
+
"@aztec/stdlib": "5.0.0",
|
|
50
|
+
"@aztec/telemetry-client": "5.0.0",
|
|
51
|
+
"@aztec/validator-client": "5.0.0",
|
|
52
|
+
"@aztec/validator-ha-signer": "5.0.0",
|
|
53
|
+
"@aztec/world-state": "5.0.0",
|
|
54
54
|
"lodash.chunk": "^4.2.0",
|
|
55
55
|
"tslib": "^2.4.0",
|
|
56
56
|
"viem": "npm:@aztec/viem@2.38.2"
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|
|
59
|
-
"@aztec/archiver": "5.0.0
|
|
60
|
-
"@aztec/kv-store": "5.0.0
|
|
59
|
+
"@aztec/archiver": "5.0.0",
|
|
60
|
+
"@aztec/kv-store": "5.0.0",
|
|
61
61
|
"@electric-sql/pglite": "^0.3.14",
|
|
62
62
|
"@jest/globals": "^30.0.0",
|
|
63
63
|
"@types/jest": "^30.0.0",
|
|
@@ -180,16 +180,23 @@ export class SequencerClient {
|
|
|
180
180
|
this.validatorClient?.updateConfig(config);
|
|
181
181
|
}
|
|
182
182
|
|
|
183
|
-
/**
|
|
183
|
+
/**
|
|
184
|
+
* Starts (or resumes) the sequencer, validator, publishers, and metrics. Each underlying start is
|
|
185
|
+
* idempotent, so this is safe to call after a previous {@link pause} to resume building and publishing.
|
|
186
|
+
* The publisher manager is started before the sequencer's poll loop so publishing is ready before the
|
|
187
|
+
* sequencer first tries to publish to L1.
|
|
188
|
+
*/
|
|
184
189
|
public async start() {
|
|
185
190
|
await this.validatorClient?.start();
|
|
191
|
+
await this.publisherManager.start();
|
|
186
192
|
this.sequencer.start();
|
|
187
193
|
this.l1Metrics?.start();
|
|
188
|
-
await this.publisherManager.start();
|
|
189
194
|
}
|
|
190
195
|
|
|
191
196
|
/**
|
|
192
|
-
* Stops the sequencer
|
|
197
|
+
* Stops the sequencer, validator, publishers, and metrics for good, draining in-flight work. This is the
|
|
198
|
+
* final teardown path: stopping the validator client closes its slashing-protection database. For a
|
|
199
|
+
* restartable pause (e.g. around a test clock warp) use {@link pause} instead.
|
|
193
200
|
*/
|
|
194
201
|
public async stop() {
|
|
195
202
|
await this.sequencer.stop();
|
|
@@ -198,6 +205,15 @@ export class SequencerClient {
|
|
|
198
205
|
this.l1Metrics?.stop();
|
|
199
206
|
}
|
|
200
207
|
|
|
208
|
+
/**
|
|
209
|
+
* Gracefully pauses block production, waiting for in-flight work to finish rather than interrupting it,
|
|
210
|
+
* while leaving the validator, publishers, and metrics running so the sequencer can be resumed with
|
|
211
|
+
* {@link start}. Used by tests that pause sequencers around an L1 clock warp.
|
|
212
|
+
*/
|
|
213
|
+
public async pause() {
|
|
214
|
+
await this.sequencer.pause();
|
|
215
|
+
}
|
|
216
|
+
|
|
201
217
|
/** Triggers an immediate run of the sequencer, bypassing the polling interval. */
|
|
202
218
|
public trigger() {
|
|
203
219
|
return this.sequencer.trigger();
|
package/src/config.ts
CHANGED
|
@@ -62,6 +62,7 @@ export const DefaultSequencerConfig = {
|
|
|
62
62
|
injectFakeAttestation: false,
|
|
63
63
|
injectHighSValueAttestation: false,
|
|
64
64
|
injectUnrecoverableSignatureAttestation: false,
|
|
65
|
+
injectYParityAttestation: false,
|
|
65
66
|
fishermanMode: false,
|
|
66
67
|
shuffleAttestationOrdering: false,
|
|
67
68
|
skipPushProposedBlocksToArchiver: false,
|
|
@@ -129,7 +130,7 @@ export const sequencerConfigMappings: ConfigMappingsType<SequencerConfig> = {
|
|
|
129
130
|
description:
|
|
130
131
|
'Per-block budget multiplier applied to DA gas and blob fields in place of perBlockAllocationMultiplier.' +
|
|
131
132
|
' Defaults higher than the general multiplier so the largest contract class deploy fits a single block.',
|
|
132
|
-
...
|
|
133
|
+
...floatConfigHelper(DefaultSequencerConfig.perBlockDAAllocationMultiplier),
|
|
133
134
|
},
|
|
134
135
|
redistributeCheckpointBudget: {
|
|
135
136
|
env: 'SEQ_REDISTRIBUTE_CHECKPOINT_BUDGET',
|
|
@@ -144,7 +145,7 @@ export const sequencerConfigMappings: ConfigMappingsType<SequencerConfig> = {
|
|
|
144
145
|
},
|
|
145
146
|
feeRecipient: {
|
|
146
147
|
env: 'FEE_RECIPIENT',
|
|
147
|
-
parseEnv: (val: string) => AztecAddress.
|
|
148
|
+
parseEnv: (val: string) => AztecAddress.fromStringUnsafe(val),
|
|
148
149
|
description: 'Address to receive fees.',
|
|
149
150
|
},
|
|
150
151
|
acvmWorkingDirectory: {
|
|
@@ -225,6 +226,10 @@ export const sequencerConfigMappings: ConfigMappingsType<SequencerConfig> = {
|
|
|
225
226
|
description: 'Inject an attestation with an unrecoverable signature (for testing only)',
|
|
226
227
|
...booleanConfigHelper(DefaultSequencerConfig.injectUnrecoverableSignatureAttestation),
|
|
227
228
|
},
|
|
229
|
+
injectYParityAttestation: {
|
|
230
|
+
description: 'Inject a non-proposer attestation slot in yParity form in the packed L1 tuple (for testing only)',
|
|
231
|
+
...booleanConfigHelper(DefaultSequencerConfig.injectYParityAttestation),
|
|
232
|
+
},
|
|
228
233
|
fishermanMode: {
|
|
229
234
|
env: 'FISHERMAN_MODE',
|
|
230
235
|
description:
|
|
@@ -242,7 +247,7 @@ export const sequencerConfigMappings: ConfigMappingsType<SequencerConfig> = {
|
|
|
242
247
|
...booleanConfigHelper(DefaultSequencerConfig.buildCheckpointIfEmpty),
|
|
243
248
|
},
|
|
244
249
|
skipPushProposedBlocksToArchiver: {
|
|
245
|
-
description: 'Skip pushing proposed blocks to archiver (
|
|
250
|
+
description: 'Skip pushing proposed blocks to archiver (test only)',
|
|
246
251
|
...booleanConfigHelper(DefaultSequencerConfig.skipPushProposedBlocksToArchiver),
|
|
247
252
|
},
|
|
248
253
|
minBlocksForCheckpoint: {
|
|
@@ -61,7 +61,17 @@ export class FeePredictor {
|
|
|
61
61
|
const blockNumber = await this.publicClient.getBlockNumber({ cacheTime: 0 });
|
|
62
62
|
if (this.cachedL1BlockNumber === undefined || blockNumber > this.cachedL1BlockNumber) {
|
|
63
63
|
this.cachedL1BlockNumber = blockNumber;
|
|
64
|
-
|
|
64
|
+
// Reset the cached block number on failure so a transient L1 RPC error does not leave a
|
|
65
|
+
// rejected promise cached for this block, which would replay the same rejection on every
|
|
66
|
+
// subsequent call until the next L1 block arrives. Only clear it if it still points at the
|
|
67
|
+
// block this attempt was for, so a stale rejection from an older block cannot wipe a marker
|
|
68
|
+
// a newer call already advanced (which would also defeat the monotonic block-number guard).
|
|
69
|
+
this.cachedState = this.fetchState(blockNumber).catch(err => {
|
|
70
|
+
if (this.cachedL1BlockNumber === blockNumber) {
|
|
71
|
+
this.cachedL1BlockNumber = undefined;
|
|
72
|
+
}
|
|
73
|
+
throw err;
|
|
74
|
+
});
|
|
65
75
|
}
|
|
66
76
|
return this.cachedState!;
|
|
67
77
|
}
|
|
@@ -61,10 +61,27 @@ export class FeeProviderImpl implements FeeProvider {
|
|
|
61
61
|
// Get the current block number
|
|
62
62
|
const blockNumber = await this.publicClient.getBlockNumber({ cacheTime: 0 });
|
|
63
63
|
|
|
64
|
-
// If the L1 block number has changed then chain a new promise to get the current min fees
|
|
64
|
+
// If the L1 block number has changed then chain a new promise to get the current min fees.
|
|
65
|
+
// We chain off the previous promise's settlement (via a swallowing catch) rather than its
|
|
66
|
+
// fulfillment, so a prior rejection does not short-circuit the new computation. If the new
|
|
67
|
+
// computation fails (e.g. a transient L1 RPC error), reset the cached block number so the
|
|
68
|
+
// next call recomputes instead of permanently replaying the rejected promise — otherwise a
|
|
69
|
+
// single transient failure would wedge fee estimation until the next L1 block arrives. Only
|
|
70
|
+
// clear it if it still points at the block this attempt was for, so a stale rejection from an
|
|
71
|
+
// older block cannot wipe a marker a newer call already advanced (which would also defeat the
|
|
72
|
+
// monotonic block-number guard).
|
|
65
73
|
if (this.currentL1BlockNumber === undefined || blockNumber > this.currentL1BlockNumber) {
|
|
66
74
|
this.currentL1BlockNumber = blockNumber;
|
|
67
|
-
this.currentMinFees = this.currentMinFees
|
|
75
|
+
this.currentMinFees = this.currentMinFees
|
|
76
|
+
.catch(() => undefined)
|
|
77
|
+
.then(() =>
|
|
78
|
+
this.computeCurrentMinFees().catch(err => {
|
|
79
|
+
if (this.currentL1BlockNumber === blockNumber) {
|
|
80
|
+
this.currentL1BlockNumber = undefined;
|
|
81
|
+
}
|
|
82
|
+
throw err;
|
|
83
|
+
}),
|
|
84
|
+
);
|
|
68
85
|
}
|
|
69
86
|
return this.currentMinFees;
|
|
70
87
|
}
|