@aztec/sequencer-client 0.0.1-commit.d431d1c → 0.0.1-commit.d939eb5aa
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/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 +53 -13
- package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -1
- package/dest/sequencer/checkpoint_proposal_job.js +431 -235
- package/dest/sequencer/checkpoint_proposal_job_metrics.d.ts +34 -0
- package/dest/sequencer/checkpoint_proposal_job_metrics.d.ts.map +1 -0
- package/dest/sequencer/checkpoint_proposal_job_metrics.js +72 -0
- package/dest/sequencer/checkpoint_voter.d.ts +1 -2
- package/dest/sequencer/checkpoint_voter.d.ts.map +1 -1
- package/dest/sequencer/checkpoint_voter.js +2 -5
- package/dest/sequencer/events.d.ts +2 -1
- package/dest/sequencer/events.d.ts.map +1 -1
- package/dest/sequencer/metrics.d.ts +19 -8
- package/dest/sequencer/metrics.d.ts.map +1 -1
- package/dest/sequencer/metrics.js +120 -36
- package/dest/sequencer/sequencer.d.ts +45 -17
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +160 -94
- 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 +18 -15
- package/dest/test/mock_checkpoint_builder.d.ts.map +1 -1
- package/dest/test/mock_checkpoint_builder.js +63 -40
- package/dest/test/utils.d.ts +8 -8
- package/dest/test/utils.d.ts.map +1 -1
- package/dest/test/utils.js +10 -9
- package/package.json +27 -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/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 +573 -277
- package/src/sequencer/checkpoint_proposal_job_metrics.ts +128 -0
- package/src/sequencer/checkpoint_voter.ts +1 -12
- package/src/sequencer/events.ts +1 -1
- package/src/sequencer/metrics.ts +136 -40
- package/src/sequencer/sequencer.ts +232 -107
- 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 +93 -65
- package/src/test/utils.ts +22 -13
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { getKzg } from '@aztec/blob-lib';
|
|
2
2
|
import { INITIAL_L2_BLOCK_NUM } from '@aztec/constants';
|
|
3
3
|
import type { EpochCache } from '@aztec/epoch-cache';
|
|
4
|
-
import { NoCommitteeError, type RollupContract } from '@aztec/ethereum/contracts';
|
|
4
|
+
import { NoCommitteeError, type RollupContract, SimulationOverridesBuilder } from '@aztec/ethereum/contracts';
|
|
5
5
|
import { BlockNumber, CheckpointNumber, EpochNumber, SlotNumber } from '@aztec/foundation/branded-types';
|
|
6
6
|
import { merge, omit, pick } from '@aztec/foundation/collection';
|
|
7
7
|
import { Fr } from '@aztec/foundation/curves/bn254';
|
|
@@ -12,9 +12,9 @@ import type { DateProvider } from '@aztec/foundation/timer';
|
|
|
12
12
|
import type { TypedEventEmitter } from '@aztec/foundation/types';
|
|
13
13
|
import type { P2P } from '@aztec/p2p';
|
|
14
14
|
import type { SlasherClientInterface } from '@aztec/slasher';
|
|
15
|
-
import type {
|
|
16
|
-
import type { Checkpoint } from '@aztec/stdlib/checkpoint';
|
|
17
|
-
import {
|
|
15
|
+
import type { BlockData, L2BlockSink, L2BlockSource, ValidateCheckpointResult } from '@aztec/stdlib/block';
|
|
16
|
+
import type { Checkpoint, ProposedCheckpointData } from '@aztec/stdlib/checkpoint';
|
|
17
|
+
import { getSlotStartBuildTimestamp } from '@aztec/stdlib/epoch-helpers';
|
|
18
18
|
import {
|
|
19
19
|
type ResolvedSequencerConfig,
|
|
20
20
|
type SequencerConfig,
|
|
@@ -25,7 +25,7 @@ import type { L1ToL2MessageSource } from '@aztec/stdlib/messaging';
|
|
|
25
25
|
import { pickFromSchema } from '@aztec/stdlib/schemas';
|
|
26
26
|
import { MerkleTreeId } from '@aztec/stdlib/trees';
|
|
27
27
|
import { Attributes, type TelemetryClient, type Tracer, getTelemetryClient, trackSpan } from '@aztec/telemetry-client';
|
|
28
|
-
import { FullNodeCheckpointsBuilder, type ValidatorClient } from '@aztec/validator-client';
|
|
28
|
+
import { FullNodeCheckpointsBuilder, NodeKeystoreAdapter, type ValidatorClient } from '@aztec/validator-client';
|
|
29
29
|
|
|
30
30
|
import EventEmitter from 'node:events';
|
|
31
31
|
|
|
@@ -34,6 +34,7 @@ import type { GlobalVariableBuilder } from '../global_variable_builder/global_bu
|
|
|
34
34
|
import type { SequencerPublisherFactory } from '../publisher/sequencer-publisher-factory.js';
|
|
35
35
|
import type { InvalidateCheckpointRequest, SequencerPublisher } from '../publisher/sequencer-publisher.js';
|
|
36
36
|
import { CheckpointProposalJob } from './checkpoint_proposal_job.js';
|
|
37
|
+
import { CheckpointProposalJobMetrics } from './checkpoint_proposal_job_metrics.js';
|
|
37
38
|
import { CheckpointVoter } from './checkpoint_voter.js';
|
|
38
39
|
import { SequencerInterruptedError, SequencerTooSlowError } from './errors.js';
|
|
39
40
|
import type { SequencerEvents } from './events.js';
|
|
@@ -56,10 +57,14 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
56
57
|
private runningPromise?: RunningPromise;
|
|
57
58
|
private state = SequencerState.STOPPED;
|
|
58
59
|
private metrics: SequencerMetrics;
|
|
60
|
+
private checkpointProposalJobMetrics: CheckpointProposalJobMetrics;
|
|
59
61
|
|
|
60
62
|
/** The last slot for which we attempted to perform our voting duties with degraded block production */
|
|
61
63
|
private lastSlotForFallbackVote: SlotNumber | undefined;
|
|
62
64
|
|
|
65
|
+
/** The last slot for which we logged "no committee" warning, to avoid spam */
|
|
66
|
+
private lastSlotForNoCommitteeWarning: SlotNumber | undefined;
|
|
67
|
+
|
|
63
68
|
/** The last slot for which we triggered a checkpoint proposal job, to prevent duplicate attempts. */
|
|
64
69
|
private lastSlotForCheckpointProposalJob: SlotNumber | undefined;
|
|
65
70
|
|
|
@@ -69,17 +74,12 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
69
74
|
/** The last epoch for which we logged strategy comparison in fisherman mode. */
|
|
70
75
|
private lastEpochForStrategyComparison: EpochNumber | undefined;
|
|
71
76
|
|
|
77
|
+
/** The last checkpoint proposal job, tracked so we can await its pending L1 submission during shutdown. */
|
|
78
|
+
protected lastCheckpointProposalJob: CheckpointProposalJob | undefined;
|
|
79
|
+
|
|
72
80
|
/** The maximum number of seconds that the sequencer can be into a slot to transition to a particular state. */
|
|
73
81
|
protected timetable!: SequencerTimetable;
|
|
74
82
|
|
|
75
|
-
// This shouldn't be here as this gets re-created each time we build/propose a block.
|
|
76
|
-
// But we have a number of tests that abuse/rely on this class having a permanent publisher.
|
|
77
|
-
// As long as those tests only configure a single publisher they will continue to work.
|
|
78
|
-
// This will get re-assigned every time the sequencer goes to build a new block to a publisher that is valid
|
|
79
|
-
// for the block proposer.
|
|
80
|
-
// TODO(palla/mbps): Remove this field and fix tests
|
|
81
|
-
protected publisher: SequencerPublisher | undefined;
|
|
82
|
-
|
|
83
83
|
/** Config for the sequencer */
|
|
84
84
|
protected config: ResolvedSequencerConfig = DefaultSequencerConfig;
|
|
85
85
|
|
|
@@ -109,32 +109,34 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
109
109
|
}
|
|
110
110
|
|
|
111
111
|
this.metrics = new SequencerMetrics(telemetry, this.rollupContract, 'Sequencer');
|
|
112
|
+
this.checkpointProposalJobMetrics = new CheckpointProposalJobMetrics(telemetry);
|
|
112
113
|
this.updateConfig(config);
|
|
113
114
|
}
|
|
114
115
|
|
|
115
116
|
/** Updates sequencer config by the defined values and updates the timetable */
|
|
116
117
|
public updateConfig(config: Partial<SequencerConfig>) {
|
|
117
118
|
const filteredConfig = pickFromSchema(config, SequencerConfigSchema);
|
|
118
|
-
this.log.info(`Updated sequencer config`, omit(filteredConfig, '
|
|
119
|
+
this.log.info(`Updated sequencer config`, omit(filteredConfig, 'txPublicSetupAllowListExtend'));
|
|
119
120
|
this.config = merge(this.config, filteredConfig);
|
|
121
|
+
const p2pPropagationTime = this.config.attestationPropagationTime;
|
|
120
122
|
this.timetable = new SequencerTimetable(
|
|
121
123
|
{
|
|
122
124
|
ethereumSlotDuration: this.l1Constants.ethereumSlotDuration,
|
|
123
125
|
aztecSlotDuration: this.aztecSlotDuration,
|
|
124
126
|
l1PublishingTime: this.l1PublishingTime,
|
|
125
|
-
p2pPropagationTime
|
|
127
|
+
p2pPropagationTime,
|
|
126
128
|
blockDurationMs: this.config.blockDurationMs,
|
|
127
129
|
enforce: this.config.enforceTimeTable,
|
|
130
|
+
pipelining: this.epochCache.isProposerPipeliningEnabled(),
|
|
128
131
|
},
|
|
129
132
|
this.metrics,
|
|
130
133
|
this.log,
|
|
131
134
|
);
|
|
132
135
|
}
|
|
133
136
|
|
|
134
|
-
/** Initializes the sequencer (precomputes tables
|
|
135
|
-
public
|
|
137
|
+
/** Initializes the sequencer (precomputes tables). Takes about 3s. */
|
|
138
|
+
public init() {
|
|
136
139
|
getKzg();
|
|
137
|
-
this.publisher = (await this.publisherFactory.create(undefined)).publisher;
|
|
138
140
|
}
|
|
139
141
|
|
|
140
142
|
/** Starts the sequencer and moves to IDLE state. */
|
|
@@ -149,12 +151,18 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
149
151
|
this.log.info('Started sequencer');
|
|
150
152
|
}
|
|
151
153
|
|
|
154
|
+
/** Triggers an immediate run of the sequencer, bypassing the polling interval. */
|
|
155
|
+
public trigger() {
|
|
156
|
+
return this.runningPromise?.trigger();
|
|
157
|
+
}
|
|
158
|
+
|
|
152
159
|
/** Stops the sequencer from building blocks and moves to STOPPED state. */
|
|
153
160
|
public async stop(): Promise<void> {
|
|
154
161
|
this.log.info(`Stopping sequencer`);
|
|
155
162
|
this.setState(SequencerState.STOPPING, undefined, { force: true });
|
|
156
|
-
this.
|
|
163
|
+
await this.publisherFactory.stopAll();
|
|
157
164
|
await this.runningPromise?.stop();
|
|
165
|
+
await this.lastCheckpointProposalJob?.awaitPendingSubmission();
|
|
158
166
|
this.setState(SequencerState.STOPPED, undefined, { force: true });
|
|
159
167
|
this.log.info('Stopped sequencer');
|
|
160
168
|
}
|
|
@@ -166,7 +174,6 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
166
174
|
} catch (err) {
|
|
167
175
|
this.emit('checkpoint-error', { error: err as Error });
|
|
168
176
|
if (err instanceof SequencerTooSlowError) {
|
|
169
|
-
// TODO(palla/mbps): Add missing states
|
|
170
177
|
// Log as warn only if we had to abort halfway through the block proposal
|
|
171
178
|
const logLvl = [SequencerState.INITIALIZING_CHECKPOINT, SequencerState.PROPOSER_CHECK].includes(
|
|
172
179
|
err.proposedState,
|
|
@@ -199,14 +206,25 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
199
206
|
@trackSpan('Sequencer.work')
|
|
200
207
|
protected async work() {
|
|
201
208
|
this.setState(SequencerState.SYNCHRONIZING, undefined);
|
|
202
|
-
const { slot, ts,
|
|
209
|
+
const { slot, ts, nowSeconds, epoch } = this.epochCache.getEpochAndSlotInNextL1Slot();
|
|
210
|
+
const { slot: targetSlot, epoch: targetEpoch } = this.epochCache.getTargetEpochAndSlotInNextL1Slot();
|
|
203
211
|
|
|
204
212
|
// Check if we are synced and it's our slot, grab a publisher, check previous block invalidation, etc
|
|
205
|
-
const checkpointProposalJob = await this.prepareCheckpointProposal(
|
|
213
|
+
const checkpointProposalJob = await this.prepareCheckpointProposal(
|
|
214
|
+
slot,
|
|
215
|
+
targetSlot,
|
|
216
|
+
epoch,
|
|
217
|
+
targetEpoch,
|
|
218
|
+
ts,
|
|
219
|
+
nowSeconds,
|
|
220
|
+
);
|
|
206
221
|
if (!checkpointProposalJob) {
|
|
207
222
|
return;
|
|
208
223
|
}
|
|
209
224
|
|
|
225
|
+
// Track the job so we can await its pending L1 submission during shutdown
|
|
226
|
+
this.lastCheckpointProposalJob = checkpointProposalJob;
|
|
227
|
+
|
|
210
228
|
// Execute the checkpoint proposal job
|
|
211
229
|
const checkpoint = await checkpointProposalJob.execute();
|
|
212
230
|
|
|
@@ -215,13 +233,13 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
215
233
|
this.lastCheckpointProposed = checkpoint;
|
|
216
234
|
}
|
|
217
235
|
|
|
218
|
-
// Log fee strategy comparison if on fisherman
|
|
236
|
+
// Log fee strategy comparison if on fisherman (uses target epoch since we mirror the proposer's perspective)
|
|
219
237
|
if (
|
|
220
238
|
this.config.fishermanMode &&
|
|
221
|
-
(this.lastEpochForStrategyComparison === undefined ||
|
|
239
|
+
(this.lastEpochForStrategyComparison === undefined || targetEpoch > this.lastEpochForStrategyComparison)
|
|
222
240
|
) {
|
|
223
|
-
this.logStrategyComparison(
|
|
224
|
-
this.lastEpochForStrategyComparison =
|
|
241
|
+
this.logStrategyComparison(targetEpoch, checkpointProposalJob.getPublisher());
|
|
242
|
+
this.lastEpochForStrategyComparison = targetEpoch;
|
|
225
243
|
}
|
|
226
244
|
|
|
227
245
|
return checkpoint;
|
|
@@ -233,44 +251,49 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
233
251
|
* @returns CheckpointProposalJob if successful, undefined if we are not yet synced or are not the proposer.
|
|
234
252
|
*/
|
|
235
253
|
@trackSpan('Sequencer.prepareCheckpointProposal')
|
|
236
|
-
|
|
237
|
-
epoch: EpochNumber,
|
|
254
|
+
protected async prepareCheckpointProposal(
|
|
238
255
|
slot: SlotNumber,
|
|
256
|
+
targetSlot: SlotNumber,
|
|
257
|
+
epoch: EpochNumber,
|
|
258
|
+
targetEpoch: EpochNumber,
|
|
239
259
|
ts: bigint,
|
|
240
|
-
|
|
260
|
+
nowSeconds: bigint,
|
|
241
261
|
): Promise<CheckpointProposalJob | undefined> {
|
|
242
|
-
// Check we have not already processed this slot (cheapest check)
|
|
262
|
+
// Check we have not already processed this target slot (cheapest check)
|
|
243
263
|
// We only check this if enforce timetable is set, since we want to keep processing the same slot if we are not
|
|
244
264
|
// running against actual time (eg when we use sandbox-style automining)
|
|
245
265
|
if (
|
|
246
266
|
this.lastSlotForCheckpointProposalJob &&
|
|
247
|
-
this.lastSlotForCheckpointProposalJob >=
|
|
267
|
+
this.lastSlotForCheckpointProposalJob >= targetSlot &&
|
|
248
268
|
this.config.enforceTimeTable
|
|
249
269
|
) {
|
|
250
|
-
this.log.trace(`
|
|
270
|
+
this.log.trace(`Target slot ${targetSlot} has already been processed`);
|
|
251
271
|
return undefined;
|
|
252
272
|
}
|
|
253
273
|
|
|
254
|
-
// But if we have already proposed for this slot,
|
|
255
|
-
if (this.lastCheckpointProposed && this.lastCheckpointProposed.header.slotNumber >=
|
|
256
|
-
this.log.trace(
|
|
274
|
+
// But if we have already proposed for this slot, then we definitely have to skip it, automining or not
|
|
275
|
+
if (this.lastCheckpointProposed && this.lastCheckpointProposed.header.slotNumber >= targetSlot) {
|
|
276
|
+
this.log.trace(
|
|
277
|
+
`Slot ${targetSlot} has already been published as checkpoint ${this.lastCheckpointProposed.number}`,
|
|
278
|
+
);
|
|
257
279
|
return undefined;
|
|
258
280
|
}
|
|
259
281
|
|
|
260
282
|
// Check all components are synced to latest as seen by the archiver (queries all subsystems)
|
|
261
283
|
const syncedTo = await this.checkSync({ ts, slot });
|
|
262
284
|
if (!syncedTo) {
|
|
263
|
-
await this.tryVoteWhenSyncFails({ slot, ts });
|
|
285
|
+
await this.tryVoteWhenSyncFails({ slot, targetSlot, ts });
|
|
264
286
|
return undefined;
|
|
265
287
|
}
|
|
266
288
|
|
|
267
|
-
// If escape hatch is open for
|
|
289
|
+
// If escape hatch is open for the target epoch, do not start checkpoint proposal work and do not attempt invalidations.
|
|
268
290
|
// Still perform governance/slashing voting (as proposer) once per slot.
|
|
269
|
-
|
|
291
|
+
// When pipelining, we check the target epoch (slot+1's epoch) since that's the epoch we're building for.
|
|
292
|
+
const isEscapeHatchOpen = await this.epochCache.isEscapeHatchOpen(targetEpoch);
|
|
270
293
|
|
|
271
294
|
if (isEscapeHatchOpen) {
|
|
272
295
|
this.setState(SequencerState.PROPOSER_CHECK, slot);
|
|
273
|
-
const [canPropose, proposer] = await this.checkCanPropose(
|
|
296
|
+
const [canPropose, proposer] = await this.checkCanPropose(targetSlot);
|
|
274
297
|
if (canPropose) {
|
|
275
298
|
await this.tryVoteWhenEscapeHatchOpen({ slot, proposer });
|
|
276
299
|
} else {
|
|
@@ -287,18 +310,18 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
287
310
|
const checkpointNumber = CheckpointNumber(syncedTo.checkpointNumber + 1);
|
|
288
311
|
|
|
289
312
|
const logCtx = {
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
syncedToL2Slot: getSlotAtTimestamp(syncedTo.l1Timestamp, this.l1Constants),
|
|
313
|
+
nowSeconds,
|
|
314
|
+
syncedToL2Slot: syncedTo.syncedL2Slot,
|
|
293
315
|
slot,
|
|
316
|
+
targetSlot,
|
|
294
317
|
slotTs: ts,
|
|
295
318
|
checkpointNumber,
|
|
296
319
|
isPendingChainValid: pick(syncedTo.pendingChainValidationStatus, 'valid', 'reason', 'invalidIndex'),
|
|
297
320
|
};
|
|
298
321
|
|
|
299
|
-
// Check that we are a proposer for the
|
|
322
|
+
// Check that we are a proposer for the target slot.
|
|
300
323
|
this.setState(SequencerState.PROPOSER_CHECK, slot);
|
|
301
|
-
const [canPropose, proposer] = await this.checkCanPropose(
|
|
324
|
+
const [canPropose, proposer] = await this.checkCanPropose(targetSlot);
|
|
302
325
|
|
|
303
326
|
// If we are not a proposer check if we should invalidate an invalid checkpoint, and bail
|
|
304
327
|
if (!canPropose) {
|
|
@@ -306,13 +329,23 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
306
329
|
return undefined;
|
|
307
330
|
}
|
|
308
331
|
|
|
309
|
-
//
|
|
310
|
-
|
|
332
|
+
// Guard: don't exceed 1-deep pipeline. Without a proposed checkpoint, we can only build
|
|
333
|
+
// confirmed + 1. With a proposed checkpoint, we can build confirmed + 2.
|
|
334
|
+
const confirmedCkpt = syncedTo.checkpointedCheckpointNumber;
|
|
335
|
+
if (checkpointNumber > confirmedCkpt + 2) {
|
|
336
|
+
this.log.verbose(
|
|
337
|
+
`Skipping slot ${targetSlot}: checkpoint ${checkpointNumber} exceeds max pipeline depth (confirmed=${confirmedCkpt})`,
|
|
338
|
+
);
|
|
339
|
+
return undefined;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Check that the target slot is not taken by a block already (should never happen, since only us can propose for this slot)
|
|
343
|
+
if (syncedTo.blockData && syncedTo.blockData.header.getSlot() >= targetSlot) {
|
|
311
344
|
this.log.warn(
|
|
312
|
-
`Cannot propose block at
|
|
313
|
-
{ ...logCtx, block: syncedTo.
|
|
345
|
+
`Cannot propose block at target slot ${targetSlot} since that slot was taken by block ${syncedTo.blockNumber}`,
|
|
346
|
+
{ ...logCtx, block: syncedTo.blockData.header.toInspect() },
|
|
314
347
|
);
|
|
315
|
-
this.metrics.
|
|
348
|
+
this.metrics.recordCheckpointPrecheckFailed('slot_already_taken');
|
|
316
349
|
return undefined;
|
|
317
350
|
}
|
|
318
351
|
|
|
@@ -323,7 +356,6 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
323
356
|
const proposerForPublisher = this.config.fishermanMode ? undefined : proposer;
|
|
324
357
|
const { attestorAddress, publisher } = await this.publisherFactory.create(proposerForPublisher);
|
|
325
358
|
this.log.verbose(`Created publisher at address ${publisher.getSenderAddress()} for attestor ${attestorAddress}`);
|
|
326
|
-
this.publisher = publisher;
|
|
327
359
|
|
|
328
360
|
// In fisherman mode, set the actual proposer's address for simulations
|
|
329
361
|
if (this.config.fishermanMode && proposer) {
|
|
@@ -332,14 +364,41 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
332
364
|
}
|
|
333
365
|
|
|
334
366
|
// Prepare invalidation request if the pending chain is invalid (returns undefined if no need)
|
|
335
|
-
|
|
367
|
+
let invalidateCheckpoint = await publisher.simulateInvalidateCheckpoint(syncedTo.pendingChainValidationStatus);
|
|
368
|
+
|
|
369
|
+
// Determine the correct archive and L1 state overrides for the canProposeAt check.
|
|
370
|
+
// The L1 contract reads archives[proposedCheckpointNumber] and compares it with the provided archive.
|
|
371
|
+
// When invalidating or pipelining, the local archive may differ from L1's, so we adjust accordingly.
|
|
372
|
+
let archiveForCheck = syncedTo.archive;
|
|
373
|
+
const l1SimulationOverridesBuilder = new SimulationOverridesBuilder();
|
|
374
|
+
|
|
375
|
+
if (this.epochCache.isProposerPipeliningEnabled() && syncedTo.hasProposedCheckpoint) {
|
|
376
|
+
// Parent checkpoint hasn't landed on L1 yet. Override both the proposed checkpoint number
|
|
377
|
+
// and the archive at that checkpoint so L1 simulation sees the correct chain tip.
|
|
378
|
+
const parentCheckpointNumber = CheckpointNumber(checkpointNumber - 1);
|
|
379
|
+
l1SimulationOverridesBuilder.forPendingCheckpoint(parentCheckpointNumber).withPendingArchive(syncedTo.archive);
|
|
380
|
+
this.metrics.recordPipelineDepth(1);
|
|
381
|
+
|
|
382
|
+
this.log.verbose(
|
|
383
|
+
`Building on top of proposed checkpoint (pending=${syncedTo.proposedCheckpointData?.checkpointNumber})`,
|
|
384
|
+
);
|
|
385
|
+
// Clear the invalidation - the proposed checkpoint should handle it.
|
|
386
|
+
invalidateCheckpoint = undefined;
|
|
387
|
+
} else if (invalidateCheckpoint) {
|
|
388
|
+
// After invalidation, L1 will roll back to checkpoint N-1. The archive at N-1 already
|
|
389
|
+
// exists on L1, so we just pass the matching archive (the lastArchive of the invalid checkpoint).
|
|
390
|
+
archiveForCheck = invalidateCheckpoint.lastArchive;
|
|
391
|
+
l1SimulationOverridesBuilder.forPendingCheckpoint(invalidateCheckpoint.forcePendingCheckpointNumber);
|
|
392
|
+
this.metrics.recordPipelineDepth(0);
|
|
393
|
+
} else {
|
|
394
|
+
this.metrics.recordPipelineDepth(0);
|
|
395
|
+
}
|
|
336
396
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
syncedTo.archive,
|
|
397
|
+
const simulationOverridesPlan = l1SimulationOverridesBuilder.build();
|
|
398
|
+
const canProposeCheck = await publisher.canProposeAt(
|
|
399
|
+
archiveForCheck,
|
|
341
400
|
proposer ?? EthAddress.ZERO,
|
|
342
|
-
|
|
401
|
+
simulationOverridesPlan,
|
|
343
402
|
);
|
|
344
403
|
|
|
345
404
|
if (canProposeCheck === undefined) {
|
|
@@ -348,17 +407,17 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
348
407
|
logCtx,
|
|
349
408
|
);
|
|
350
409
|
this.emit('proposer-rollup-check-failed', { reason: 'Rollup contract check failed', slot });
|
|
351
|
-
this.metrics.
|
|
410
|
+
this.metrics.recordCheckpointPrecheckFailed('rollup_contract_check_failed');
|
|
352
411
|
return undefined;
|
|
353
412
|
}
|
|
354
413
|
|
|
355
|
-
if (canProposeCheck.slot !==
|
|
414
|
+
if (canProposeCheck.slot !== targetSlot) {
|
|
356
415
|
this.log.warn(
|
|
357
|
-
`Cannot propose block due to slot mismatch with rollup contract (this can be caused by a clock out of sync). Expected slot ${
|
|
358
|
-
{ ...logCtx, rollup: canProposeCheck, expectedSlot:
|
|
416
|
+
`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}.`,
|
|
417
|
+
{ ...logCtx, rollup: canProposeCheck, expectedSlot: targetSlot },
|
|
359
418
|
);
|
|
360
419
|
this.emit('proposer-rollup-check-failed', { reason: 'Slot mismatch', slot });
|
|
361
|
-
this.metrics.
|
|
420
|
+
this.metrics.recordCheckpointPrecheckFailed('slot_mismatch');
|
|
362
421
|
return undefined;
|
|
363
422
|
}
|
|
364
423
|
|
|
@@ -368,39 +427,53 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
368
427
|
{ ...logCtx, rollup: canProposeCheck, expectedSlot: slot },
|
|
369
428
|
);
|
|
370
429
|
this.emit('proposer-rollup-check-failed', { reason: 'Block mismatch', slot });
|
|
371
|
-
this.metrics.
|
|
430
|
+
this.metrics.recordCheckpointPrecheckFailed('block_number_mismatch');
|
|
372
431
|
return undefined;
|
|
373
432
|
}
|
|
374
433
|
|
|
375
|
-
this.lastSlotForCheckpointProposalJob =
|
|
376
|
-
|
|
434
|
+
this.lastSlotForCheckpointProposalJob = targetSlot;
|
|
435
|
+
|
|
436
|
+
await this.p2pClient.prepareForSlot(targetSlot);
|
|
437
|
+
this.log.info(
|
|
438
|
+
`Preparing checkpoint proposal ${checkpointNumber} for target slot ${targetSlot} during wall-clock slot ${slot}`,
|
|
439
|
+
{
|
|
440
|
+
...logCtx,
|
|
441
|
+
proposer,
|
|
442
|
+
pipeliningEnabled: this.epochCache.isProposerPipeliningEnabled(),
|
|
443
|
+
},
|
|
444
|
+
);
|
|
377
445
|
|
|
378
446
|
// Create and return the checkpoint proposal job
|
|
379
447
|
return this.createCheckpointProposalJob(
|
|
380
|
-
epoch,
|
|
381
448
|
slot,
|
|
449
|
+
targetSlot,
|
|
450
|
+
targetEpoch,
|
|
382
451
|
checkpointNumber,
|
|
383
452
|
syncedTo.blockNumber,
|
|
384
453
|
proposer,
|
|
385
454
|
publisher,
|
|
386
455
|
attestorAddress,
|
|
387
456
|
invalidateCheckpoint,
|
|
457
|
+
syncedTo.proposedCheckpointData,
|
|
388
458
|
);
|
|
389
459
|
}
|
|
390
460
|
|
|
391
461
|
protected createCheckpointProposalJob(
|
|
392
|
-
epoch: EpochNumber,
|
|
393
462
|
slot: SlotNumber,
|
|
463
|
+
targetSlot: SlotNumber,
|
|
464
|
+
targetEpoch: EpochNumber,
|
|
394
465
|
checkpointNumber: CheckpointNumber,
|
|
395
466
|
syncedToBlockNumber: BlockNumber,
|
|
396
467
|
proposer: EthAddress | undefined,
|
|
397
468
|
publisher: SequencerPublisher,
|
|
398
469
|
attestorAddress: EthAddress,
|
|
399
470
|
invalidateCheckpoint: InvalidateCheckpointRequest | undefined,
|
|
471
|
+
proposedCheckpointData?: ProposedCheckpointData,
|
|
400
472
|
): CheckpointProposalJob {
|
|
401
473
|
return new CheckpointProposalJob(
|
|
402
|
-
epoch,
|
|
403
474
|
slot,
|
|
475
|
+
targetSlot,
|
|
476
|
+
targetEpoch,
|
|
404
477
|
checkpointNumber,
|
|
405
478
|
syncedToBlockNumber,
|
|
406
479
|
proposer,
|
|
@@ -422,13 +495,22 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
422
495
|
this.epochCache,
|
|
423
496
|
this.dateProvider,
|
|
424
497
|
this.metrics,
|
|
498
|
+
this.checkpointProposalJobMetrics.createRecorder(),
|
|
425
499
|
this,
|
|
426
500
|
this.setState.bind(this),
|
|
427
|
-
this.log,
|
|
428
501
|
this.tracer,
|
|
502
|
+
this.log.getBindings(),
|
|
503
|
+
proposedCheckpointData,
|
|
429
504
|
);
|
|
430
505
|
}
|
|
431
506
|
|
|
507
|
+
/**
|
|
508
|
+
* Returns the current sequencer state.
|
|
509
|
+
*/
|
|
510
|
+
public getState(): SequencerState {
|
|
511
|
+
return this.state;
|
|
512
|
+
}
|
|
513
|
+
|
|
432
514
|
/**
|
|
433
515
|
* Internal helper for setting the sequencer state and checks if we have enough time left in the slot to transition to the new state.
|
|
434
516
|
* @param proposedState - The new state to transition to.
|
|
@@ -475,16 +557,15 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
475
557
|
* We don't check against the previous block submitted since it may have been reorg'd out.
|
|
476
558
|
*/
|
|
477
559
|
protected async checkSync(args: { ts: bigint; slot: SlotNumber }): Promise<SequencerSyncCheckResult | undefined> {
|
|
478
|
-
// Check that the archiver
|
|
479
|
-
//
|
|
480
|
-
//
|
|
481
|
-
const
|
|
482
|
-
const { slot
|
|
483
|
-
if (
|
|
560
|
+
// Check that the archiver has fully synced the L2 slot before the one we want to propose in.
|
|
561
|
+
// The archiver reports sync progress via L1 block timestamps and synced checkpoint slots.
|
|
562
|
+
// See getSyncedL2SlotNumber for how missed L1 blocks are handled.
|
|
563
|
+
const syncedL2Slot = await this.l2BlockSource.getSyncedL2SlotNumber();
|
|
564
|
+
const { slot } = args;
|
|
565
|
+
if (syncedL2Slot === undefined || syncedL2Slot + 1 < slot) {
|
|
484
566
|
this.log.debug(`Cannot propose block at next L2 slot ${slot} due to pending sync from L1`, {
|
|
485
567
|
slot,
|
|
486
|
-
|
|
487
|
-
l1Timestamp,
|
|
568
|
+
syncedL2Slot,
|
|
488
569
|
});
|
|
489
570
|
return undefined;
|
|
490
571
|
}
|
|
@@ -494,25 +575,43 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
494
575
|
number: syncSummary.latestBlockNumber,
|
|
495
576
|
hash: syncSummary.latestBlockHash,
|
|
496
577
|
})),
|
|
497
|
-
this.l2BlockSource
|
|
578
|
+
this.l2BlockSource
|
|
579
|
+
.getL2Tips()
|
|
580
|
+
.then(t => ({ proposed: t.proposed, checkpointed: t.checkpointed, proposedCheckpoint: t.proposedCheckpoint })),
|
|
498
581
|
this.p2pClient.getStatus().then(p2p => p2p.syncedToL2Block),
|
|
499
|
-
this.l1ToL2MessageSource.getL2Tips().then(t => t.proposed),
|
|
582
|
+
this.l1ToL2MessageSource.getL2Tips().then(t => ({ proposed: t.proposed, checkpointed: t.checkpointed })),
|
|
500
583
|
this.l2BlockSource.getPendingChainValidationStatus(),
|
|
584
|
+
this.l2BlockSource.getProposedCheckpointOnly(),
|
|
501
585
|
] as const);
|
|
502
586
|
|
|
503
|
-
const [worldState,
|
|
587
|
+
const [worldState, l2Tips, p2p, l1ToL2MessageSourceTips, pendingChainValidationStatus, proposedCheckpointData] =
|
|
588
|
+
syncedBlocks;
|
|
504
589
|
|
|
505
590
|
// 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,
|
|
506
591
|
// as the world state can compute the new genesis block hash, but other components use the hardcoded constant.
|
|
507
592
|
// TODO(palla/mbps): Fix the above. All components should be able to handle dynamic genesis block hashes.
|
|
508
593
|
const result =
|
|
509
|
-
(
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
594
|
+
(l2Tips.proposed.number === 0 &&
|
|
595
|
+
l2Tips.checkpointed.block.number === 0 &&
|
|
596
|
+
l2Tips.checkpointed.checkpoint.number === 0 &&
|
|
597
|
+
worldState.number === 0 &&
|
|
598
|
+
p2p.number === 0 &&
|
|
599
|
+
l1ToL2MessageSourceTips.proposed.number === 0 &&
|
|
600
|
+
l1ToL2MessageSourceTips.checkpointed.block.number === 0 &&
|
|
601
|
+
l1ToL2MessageSourceTips.checkpointed.checkpoint.number === 0) ||
|
|
602
|
+
(worldState.hash === l2Tips.proposed.hash &&
|
|
603
|
+
p2p.hash === l2Tips.proposed.hash &&
|
|
604
|
+
l1ToL2MessageSourceTips.proposed.hash === l2Tips.proposed.hash &&
|
|
605
|
+
l1ToL2MessageSourceTips.checkpointed.block.hash === l2Tips.checkpointed.block.hash &&
|
|
606
|
+
l1ToL2MessageSourceTips.checkpointed.checkpoint.hash === l2Tips.checkpointed.checkpoint.hash);
|
|
513
607
|
|
|
514
608
|
if (!result) {
|
|
515
|
-
this.log.debug(`Sequencer sync check failed`, {
|
|
609
|
+
this.log.debug(`Sequencer sync check failed`, {
|
|
610
|
+
worldState,
|
|
611
|
+
l2BlockSource: l2Tips.proposed,
|
|
612
|
+
p2p,
|
|
613
|
+
l1ToL2MessageSourceTips,
|
|
614
|
+
});
|
|
516
615
|
return undefined;
|
|
517
616
|
}
|
|
518
617
|
|
|
@@ -522,26 +621,33 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
522
621
|
const archive = new Fr((await this.worldState.getCommitted().getTreeInfo(MerkleTreeId.ARCHIVE)).root);
|
|
523
622
|
return {
|
|
524
623
|
checkpointNumber: CheckpointNumber.ZERO,
|
|
624
|
+
checkpointedCheckpointNumber: CheckpointNumber.ZERO,
|
|
525
625
|
blockNumber: BlockNumber.ZERO,
|
|
526
626
|
archive,
|
|
527
|
-
|
|
627
|
+
hasProposedCheckpoint: false,
|
|
628
|
+
syncedL2Slot,
|
|
528
629
|
pendingChainValidationStatus,
|
|
529
630
|
};
|
|
530
631
|
}
|
|
531
632
|
|
|
532
|
-
const
|
|
533
|
-
if (!
|
|
633
|
+
const blockData = await this.l2BlockSource.getBlockData(blockNumber);
|
|
634
|
+
if (!blockData) {
|
|
534
635
|
// this shouldn't really happen because a moment ago we checked that all components were in sync
|
|
535
|
-
this.log.error(`Failed to get L2 block ${blockNumber} from the archiver with all components in sync`);
|
|
636
|
+
this.log.error(`Failed to get L2 block data ${blockNumber} from the archiver with all components in sync`);
|
|
536
637
|
return undefined;
|
|
537
638
|
}
|
|
538
639
|
|
|
640
|
+
const hasProposedCheckpoint = l2Tips.proposedCheckpoint.checkpoint.number > l2Tips.checkpointed.checkpoint.number;
|
|
641
|
+
|
|
539
642
|
return {
|
|
540
|
-
|
|
541
|
-
blockNumber:
|
|
542
|
-
checkpointNumber:
|
|
543
|
-
|
|
544
|
-
|
|
643
|
+
blockData,
|
|
644
|
+
blockNumber: blockData.header.getBlockNumber(),
|
|
645
|
+
checkpointNumber: blockData.checkpointNumber,
|
|
646
|
+
checkpointedCheckpointNumber: l2Tips.checkpointed.checkpoint.number,
|
|
647
|
+
archive: blockData.archive.root,
|
|
648
|
+
hasProposedCheckpoint,
|
|
649
|
+
proposedCheckpointData,
|
|
650
|
+
syncedL2Slot,
|
|
545
651
|
pendingChainValidationStatus,
|
|
546
652
|
};
|
|
547
653
|
}
|
|
@@ -550,17 +656,20 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
550
656
|
* Checks if we are the proposer for the next slot.
|
|
551
657
|
* @returns True if we can propose, and the proposer address (undefined if anyone can propose)
|
|
552
658
|
*/
|
|
553
|
-
protected async checkCanPropose(
|
|
659
|
+
protected async checkCanPropose(targetSlot: SlotNumber): Promise<[boolean, EthAddress | undefined]> {
|
|
554
660
|
let proposer: EthAddress | undefined;
|
|
555
661
|
|
|
556
662
|
try {
|
|
557
|
-
proposer = await this.epochCache.getProposerAttesterAddressInSlot(
|
|
663
|
+
proposer = await this.epochCache.getProposerAttesterAddressInSlot(targetSlot);
|
|
558
664
|
} catch (e) {
|
|
559
665
|
if (e instanceof NoCommitteeError) {
|
|
560
|
-
this.
|
|
666
|
+
if (this.lastSlotForNoCommitteeWarning !== targetSlot) {
|
|
667
|
+
this.lastSlotForNoCommitteeWarning = targetSlot;
|
|
668
|
+
this.log.warn(`Cannot propose at target slot ${targetSlot} since the committee does not exist on L1`);
|
|
669
|
+
}
|
|
561
670
|
return [false, undefined];
|
|
562
671
|
}
|
|
563
|
-
this.log.error(`Error getting proposer for slot ${
|
|
672
|
+
this.log.error(`Error getting proposer for target slot ${targetSlot}`, e);
|
|
564
673
|
return [false, undefined];
|
|
565
674
|
}
|
|
566
675
|
|
|
@@ -577,10 +686,18 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
577
686
|
const weAreProposer = validatorAddresses.some(addr => addr.equals(proposer));
|
|
578
687
|
|
|
579
688
|
if (!weAreProposer) {
|
|
580
|
-
this.log.debug(`Cannot propose at slot ${
|
|
689
|
+
this.log.debug(`Cannot propose at target slot ${targetSlot} since we are not a proposer`, {
|
|
690
|
+
targetSlot,
|
|
691
|
+
validatorAddresses,
|
|
692
|
+
proposer,
|
|
693
|
+
});
|
|
581
694
|
return [false, proposer];
|
|
582
695
|
}
|
|
583
696
|
|
|
697
|
+
this.log.info(`We are the proposer for pipeline slot ${targetSlot}`, {
|
|
698
|
+
targetSlot,
|
|
699
|
+
proposer,
|
|
700
|
+
});
|
|
584
701
|
return [true, proposer];
|
|
585
702
|
}
|
|
586
703
|
|
|
@@ -589,8 +706,8 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
589
706
|
* This allows the sequencer to participate in governance/slashing votes even when it cannot build blocks.
|
|
590
707
|
*/
|
|
591
708
|
@trackSpan('Seqeuencer.tryVoteWhenSyncFails', ({ slot }) => ({ [Attributes.SLOT_NUMBER]: slot }))
|
|
592
|
-
protected async tryVoteWhenSyncFails(args: { slot: SlotNumber; ts: bigint }): Promise<void> {
|
|
593
|
-
const { slot } = args;
|
|
709
|
+
protected async tryVoteWhenSyncFails(args: { slot: SlotNumber; targetSlot: SlotNumber; ts: bigint }): Promise<void> {
|
|
710
|
+
const { slot, targetSlot } = args;
|
|
594
711
|
|
|
595
712
|
// Prevent duplicate attempts in the same slot
|
|
596
713
|
if (this.lastSlotForFallbackVote === slot) {
|
|
@@ -618,7 +735,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
618
735
|
});
|
|
619
736
|
|
|
620
737
|
// Check if we're a proposer or proposal is open
|
|
621
|
-
const [canPropose, proposer] = await this.checkCanPropose(
|
|
738
|
+
const [canPropose, proposer] = await this.checkCanPropose(targetSlot);
|
|
622
739
|
if (!canPropose) {
|
|
623
740
|
this.log.trace(`Cannot vote in slot ${slot} since we are not a proposer`, { slot, proposer });
|
|
624
741
|
return;
|
|
@@ -635,9 +752,9 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
635
752
|
slot,
|
|
636
753
|
});
|
|
637
754
|
|
|
638
|
-
// Enqueue governance and slashing votes
|
|
755
|
+
// Enqueue governance and slashing votes (voter uses the target slot for L1 submission)
|
|
639
756
|
const voter = new CheckpointVoter(
|
|
640
|
-
|
|
757
|
+
targetSlot,
|
|
641
758
|
publisher,
|
|
642
759
|
attestorAddress,
|
|
643
760
|
this.validatorClient,
|
|
@@ -717,7 +834,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
717
834
|
syncedTo: SequencerSyncCheckResult,
|
|
718
835
|
currentSlot: SlotNumber,
|
|
719
836
|
): Promise<void> {
|
|
720
|
-
const { pendingChainValidationStatus,
|
|
837
|
+
const { pendingChainValidationStatus, syncedL2Slot } = syncedTo;
|
|
721
838
|
if (pendingChainValidationStatus.valid) {
|
|
722
839
|
return;
|
|
723
840
|
}
|
|
@@ -732,7 +849,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
732
849
|
|
|
733
850
|
const logData = {
|
|
734
851
|
invalidL1Timestamp: invalidCheckpointTimestamp,
|
|
735
|
-
|
|
852
|
+
syncedL2Slot,
|
|
736
853
|
invalidCheckpoint: pendingChainValidationStatus.checkpoint,
|
|
737
854
|
secondsBeforeInvalidatingBlockAsCommitteeMember,
|
|
738
855
|
secondsBeforeInvalidatingBlockAsNonCommitteeMember,
|
|
@@ -860,6 +977,11 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
860
977
|
return this.validatorClient?.getValidatorAddresses();
|
|
861
978
|
}
|
|
862
979
|
|
|
980
|
+
/** Updates the publisher factory's node keystore adapter after a keystore reload. */
|
|
981
|
+
public updatePublisherNodeKeyStore(adapter: NodeKeystoreAdapter): void {
|
|
982
|
+
this.publisherFactory.updateNodeKeyStore(adapter);
|
|
983
|
+
}
|
|
984
|
+
|
|
863
985
|
public getConfig() {
|
|
864
986
|
return this.config;
|
|
865
987
|
}
|
|
@@ -870,10 +992,13 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
870
992
|
}
|
|
871
993
|
|
|
872
994
|
type SequencerSyncCheckResult = {
|
|
873
|
-
|
|
995
|
+
blockData?: BlockData;
|
|
874
996
|
checkpointNumber: CheckpointNumber;
|
|
997
|
+
checkpointedCheckpointNumber: CheckpointNumber;
|
|
875
998
|
blockNumber: BlockNumber;
|
|
876
999
|
archive: Fr;
|
|
877
|
-
|
|
1000
|
+
hasProposedCheckpoint: boolean;
|
|
1001
|
+
proposedCheckpointData?: ProposedCheckpointData;
|
|
1002
|
+
syncedL2Slot: SlotNumber;
|
|
878
1003
|
pendingChainValidationStatus: ValidateCheckpointResult;
|
|
879
1004
|
};
|