@aztec/sequencer-client 0.0.1-commit.1bb068fb5 → 0.0.1-commit.1dcfe2301
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 -6
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +49 -29
- 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.d.ts +68 -48
- package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher.js +419 -135
- package/dest/sequencer/chain_state_overrides.d.ts +25 -0
- package/dest/sequencer/chain_state_overrides.d.ts.map +1 -0
- package/dest/sequencer/chain_state_overrides.js +39 -0
- package/dest/sequencer/checkpoint_proposal_job.d.ts +26 -9
- package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -1
- package/dest/sequencer/checkpoint_proposal_job.js +372 -210
- 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 +16 -8
- package/dest/sequencer/metrics.d.ts.map +1 -1
- package/dest/sequencer/metrics.js +70 -21
- package/dest/sequencer/sequencer.d.ts +43 -17
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +158 -97
- package/dest/sequencer/timetable.d.ts +17 -3
- package/dest/sequencer/timetable.d.ts.map +1 -1
- package/dest/sequencer/timetable.js +51 -43
- 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 +7 -9
- package/dest/test/mock_checkpoint_builder.d.ts.map +1 -1
- package/dest/test/mock_checkpoint_builder.js +39 -30
- package/package.json +27 -28
- package/src/client/sequencer-client.ts +81 -30
- package/src/config.ts +61 -35
- 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.ts +472 -193
- package/src/sequencer/README.md +83 -13
- package/src/sequencer/chain_state_overrides.ts +87 -0
- package/src/sequencer/checkpoint_proposal_job.ts +486 -231
- 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 +80 -26
- package/src/sequencer/sequencer.ts +227 -109
- package/src/sequencer/timetable.ts +64 -52
- package/src/sequencer/types.ts +1 -1
- package/src/test/index.ts +2 -4
- package/src/test/mock_checkpoint_builder.ts +51 -48
|
@@ -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,6 +57,7 @@ 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;
|
|
@@ -72,17 +74,12 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
72
74
|
/** The last epoch for which we logged strategy comparison in fisherman mode. */
|
|
73
75
|
private lastEpochForStrategyComparison: EpochNumber | undefined;
|
|
74
76
|
|
|
77
|
+
/** The last checkpoint proposal job, tracked so we can await its pending L1 submission during shutdown. */
|
|
78
|
+
protected lastCheckpointProposalJob: CheckpointProposalJob | undefined;
|
|
79
|
+
|
|
75
80
|
/** The maximum number of seconds that the sequencer can be into a slot to transition to a particular state. */
|
|
76
81
|
protected timetable!: SequencerTimetable;
|
|
77
82
|
|
|
78
|
-
// This shouldn't be here as this gets re-created each time we build/propose a block.
|
|
79
|
-
// But we have a number of tests that abuse/rely on this class having a permanent publisher.
|
|
80
|
-
// As long as those tests only configure a single publisher they will continue to work.
|
|
81
|
-
// This will get re-assigned every time the sequencer goes to build a new block to a publisher that is valid
|
|
82
|
-
// for the block proposer.
|
|
83
|
-
// TODO(palla/mbps): Remove this field and fix tests
|
|
84
|
-
protected publisher: SequencerPublisher | undefined;
|
|
85
|
-
|
|
86
83
|
/** Config for the sequencer */
|
|
87
84
|
protected config: ResolvedSequencerConfig = DefaultSequencerConfig;
|
|
88
85
|
|
|
@@ -112,32 +109,34 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
112
109
|
}
|
|
113
110
|
|
|
114
111
|
this.metrics = new SequencerMetrics(telemetry, this.rollupContract, 'Sequencer');
|
|
112
|
+
this.checkpointProposalJobMetrics = new CheckpointProposalJobMetrics(telemetry);
|
|
115
113
|
this.updateConfig(config);
|
|
116
114
|
}
|
|
117
115
|
|
|
118
116
|
/** Updates sequencer config by the defined values and updates the timetable */
|
|
119
117
|
public updateConfig(config: Partial<SequencerConfig>) {
|
|
120
118
|
const filteredConfig = pickFromSchema(config, SequencerConfigSchema);
|
|
121
|
-
this.log.info(`Updated sequencer config`, omit(filteredConfig, '
|
|
119
|
+
this.log.info(`Updated sequencer config`, omit(filteredConfig, 'txPublicSetupAllowListExtend'));
|
|
122
120
|
this.config = merge(this.config, filteredConfig);
|
|
121
|
+
const p2pPropagationTime = this.config.attestationPropagationTime;
|
|
123
122
|
this.timetable = new SequencerTimetable(
|
|
124
123
|
{
|
|
125
124
|
ethereumSlotDuration: this.l1Constants.ethereumSlotDuration,
|
|
126
125
|
aztecSlotDuration: this.aztecSlotDuration,
|
|
127
126
|
l1PublishingTime: this.l1PublishingTime,
|
|
128
|
-
p2pPropagationTime
|
|
127
|
+
p2pPropagationTime,
|
|
129
128
|
blockDurationMs: this.config.blockDurationMs,
|
|
130
129
|
enforce: this.config.enforceTimeTable,
|
|
130
|
+
pipelining: this.epochCache.isProposerPipeliningEnabled(),
|
|
131
131
|
},
|
|
132
132
|
this.metrics,
|
|
133
133
|
this.log,
|
|
134
134
|
);
|
|
135
135
|
}
|
|
136
136
|
|
|
137
|
-
/** Initializes the sequencer (precomputes tables
|
|
138
|
-
public
|
|
137
|
+
/** Initializes the sequencer (precomputes tables). Takes about 3s. */
|
|
138
|
+
public init() {
|
|
139
139
|
getKzg();
|
|
140
|
-
this.publisher = (await this.publisherFactory.create(undefined)).publisher;
|
|
141
140
|
}
|
|
142
141
|
|
|
143
142
|
/** Starts the sequencer and moves to IDLE state. */
|
|
@@ -152,12 +151,18 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
152
151
|
this.log.info('Started sequencer');
|
|
153
152
|
}
|
|
154
153
|
|
|
154
|
+
/** Triggers an immediate run of the sequencer, bypassing the polling interval. */
|
|
155
|
+
public trigger() {
|
|
156
|
+
return this.runningPromise?.trigger();
|
|
157
|
+
}
|
|
158
|
+
|
|
155
159
|
/** Stops the sequencer from building blocks and moves to STOPPED state. */
|
|
156
160
|
public async stop(): Promise<void> {
|
|
157
161
|
this.log.info(`Stopping sequencer`);
|
|
158
162
|
this.setState(SequencerState.STOPPING, undefined, { force: true });
|
|
159
|
-
this.
|
|
163
|
+
await this.publisherFactory.stopAll();
|
|
160
164
|
await this.runningPromise?.stop();
|
|
165
|
+
await this.lastCheckpointProposalJob?.awaitPendingSubmission();
|
|
161
166
|
this.setState(SequencerState.STOPPED, undefined, { force: true });
|
|
162
167
|
this.log.info('Stopped sequencer');
|
|
163
168
|
}
|
|
@@ -169,7 +174,6 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
169
174
|
} catch (err) {
|
|
170
175
|
this.emit('checkpoint-error', { error: err as Error });
|
|
171
176
|
if (err instanceof SequencerTooSlowError) {
|
|
172
|
-
// TODO(palla/mbps): Add missing states
|
|
173
177
|
// Log as warn only if we had to abort halfway through the block proposal
|
|
174
178
|
const logLvl = [SequencerState.INITIALIZING_CHECKPOINT, SequencerState.PROPOSER_CHECK].includes(
|
|
175
179
|
err.proposedState,
|
|
@@ -202,14 +206,25 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
202
206
|
@trackSpan('Sequencer.work')
|
|
203
207
|
protected async work() {
|
|
204
208
|
this.setState(SequencerState.SYNCHRONIZING, undefined);
|
|
205
|
-
const { slot, ts,
|
|
209
|
+
const { slot, ts, nowSeconds, epoch } = this.epochCache.getEpochAndSlotInNextL1Slot();
|
|
210
|
+
const { slot: targetSlot, epoch: targetEpoch } = this.epochCache.getTargetEpochAndSlotInNextL1Slot();
|
|
206
211
|
|
|
207
212
|
// Check if we are synced and it's our slot, grab a publisher, check previous block invalidation, etc
|
|
208
|
-
const checkpointProposalJob = await this.prepareCheckpointProposal(
|
|
213
|
+
const checkpointProposalJob = await this.prepareCheckpointProposal(
|
|
214
|
+
slot,
|
|
215
|
+
targetSlot,
|
|
216
|
+
epoch,
|
|
217
|
+
targetEpoch,
|
|
218
|
+
ts,
|
|
219
|
+
nowSeconds,
|
|
220
|
+
);
|
|
209
221
|
if (!checkpointProposalJob) {
|
|
210
222
|
return;
|
|
211
223
|
}
|
|
212
224
|
|
|
225
|
+
// Track the job so we can await its pending L1 submission during shutdown
|
|
226
|
+
this.lastCheckpointProposalJob = checkpointProposalJob;
|
|
227
|
+
|
|
213
228
|
// Execute the checkpoint proposal job
|
|
214
229
|
const checkpoint = await checkpointProposalJob.execute();
|
|
215
230
|
|
|
@@ -218,13 +233,13 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
218
233
|
this.lastCheckpointProposed = checkpoint;
|
|
219
234
|
}
|
|
220
235
|
|
|
221
|
-
// 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)
|
|
222
237
|
if (
|
|
223
238
|
this.config.fishermanMode &&
|
|
224
|
-
(this.lastEpochForStrategyComparison === undefined ||
|
|
239
|
+
(this.lastEpochForStrategyComparison === undefined || targetEpoch > this.lastEpochForStrategyComparison)
|
|
225
240
|
) {
|
|
226
|
-
this.logStrategyComparison(
|
|
227
|
-
this.lastEpochForStrategyComparison =
|
|
241
|
+
this.logStrategyComparison(targetEpoch, checkpointProposalJob.getPublisher());
|
|
242
|
+
this.lastEpochForStrategyComparison = targetEpoch;
|
|
228
243
|
}
|
|
229
244
|
|
|
230
245
|
return checkpoint;
|
|
@@ -236,44 +251,49 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
236
251
|
* @returns CheckpointProposalJob if successful, undefined if we are not yet synced or are not the proposer.
|
|
237
252
|
*/
|
|
238
253
|
@trackSpan('Sequencer.prepareCheckpointProposal')
|
|
239
|
-
|
|
240
|
-
epoch: EpochNumber,
|
|
254
|
+
protected async prepareCheckpointProposal(
|
|
241
255
|
slot: SlotNumber,
|
|
256
|
+
targetSlot: SlotNumber,
|
|
257
|
+
epoch: EpochNumber,
|
|
258
|
+
targetEpoch: EpochNumber,
|
|
242
259
|
ts: bigint,
|
|
243
|
-
|
|
260
|
+
nowSeconds: bigint,
|
|
244
261
|
): Promise<CheckpointProposalJob | undefined> {
|
|
245
|
-
// Check we have not already processed this slot (cheapest check)
|
|
262
|
+
// Check we have not already processed this target slot (cheapest check)
|
|
246
263
|
// We only check this if enforce timetable is set, since we want to keep processing the same slot if we are not
|
|
247
264
|
// running against actual time (eg when we use sandbox-style automining)
|
|
248
265
|
if (
|
|
249
266
|
this.lastSlotForCheckpointProposalJob &&
|
|
250
|
-
this.lastSlotForCheckpointProposalJob >=
|
|
267
|
+
this.lastSlotForCheckpointProposalJob >= targetSlot &&
|
|
251
268
|
this.config.enforceTimeTable
|
|
252
269
|
) {
|
|
253
|
-
this.log.trace(`
|
|
270
|
+
this.log.trace(`Target slot ${targetSlot} has already been processed`);
|
|
254
271
|
return undefined;
|
|
255
272
|
}
|
|
256
273
|
|
|
257
|
-
// But if we have already proposed for this slot,
|
|
258
|
-
if (this.lastCheckpointProposed && this.lastCheckpointProposed.header.slotNumber >=
|
|
259
|
-
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
|
+
);
|
|
260
279
|
return undefined;
|
|
261
280
|
}
|
|
262
281
|
|
|
263
282
|
// Check all components are synced to latest as seen by the archiver (queries all subsystems)
|
|
264
283
|
const syncedTo = await this.checkSync({ ts, slot });
|
|
265
284
|
if (!syncedTo) {
|
|
266
|
-
await this.tryVoteWhenSyncFails({ slot, ts });
|
|
285
|
+
await this.tryVoteWhenSyncFails({ slot, targetSlot, ts });
|
|
267
286
|
return undefined;
|
|
268
287
|
}
|
|
269
288
|
|
|
270
|
-
// 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.
|
|
271
290
|
// Still perform governance/slashing voting (as proposer) once per slot.
|
|
272
|
-
|
|
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);
|
|
273
293
|
|
|
274
294
|
if (isEscapeHatchOpen) {
|
|
275
295
|
this.setState(SequencerState.PROPOSER_CHECK, slot);
|
|
276
|
-
const [canPropose, proposer] = await this.checkCanPropose(
|
|
296
|
+
const [canPropose, proposer] = await this.checkCanPropose(targetSlot);
|
|
277
297
|
if (canPropose) {
|
|
278
298
|
await this.tryVoteWhenEscapeHatchOpen({ slot, proposer });
|
|
279
299
|
} else {
|
|
@@ -290,18 +310,18 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
290
310
|
const checkpointNumber = CheckpointNumber(syncedTo.checkpointNumber + 1);
|
|
291
311
|
|
|
292
312
|
const logCtx = {
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
syncedToL2Slot: getSlotAtTimestamp(syncedTo.l1Timestamp, this.l1Constants),
|
|
313
|
+
nowSeconds,
|
|
314
|
+
syncedToL2Slot: syncedTo.syncedL2Slot,
|
|
296
315
|
slot,
|
|
316
|
+
targetSlot,
|
|
297
317
|
slotTs: ts,
|
|
298
318
|
checkpointNumber,
|
|
299
319
|
isPendingChainValid: pick(syncedTo.pendingChainValidationStatus, 'valid', 'reason', 'invalidIndex'),
|
|
300
320
|
};
|
|
301
321
|
|
|
302
|
-
// Check that we are a proposer for the
|
|
322
|
+
// Check that we are a proposer for the target slot.
|
|
303
323
|
this.setState(SequencerState.PROPOSER_CHECK, slot);
|
|
304
|
-
const [canPropose, proposer] = await this.checkCanPropose(
|
|
324
|
+
const [canPropose, proposer] = await this.checkCanPropose(targetSlot);
|
|
305
325
|
|
|
306
326
|
// If we are not a proposer check if we should invalidate an invalid checkpoint, and bail
|
|
307
327
|
if (!canPropose) {
|
|
@@ -309,13 +329,23 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
309
329
|
return undefined;
|
|
310
330
|
}
|
|
311
331
|
|
|
312
|
-
//
|
|
313
|
-
|
|
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) {
|
|
314
344
|
this.log.warn(
|
|
315
|
-
`Cannot propose block at
|
|
316
|
-
{ ...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() },
|
|
317
347
|
);
|
|
318
|
-
this.metrics.
|
|
348
|
+
this.metrics.recordCheckpointPrecheckFailed('slot_already_taken');
|
|
319
349
|
return undefined;
|
|
320
350
|
}
|
|
321
351
|
|
|
@@ -326,7 +356,6 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
326
356
|
const proposerForPublisher = this.config.fishermanMode ? undefined : proposer;
|
|
327
357
|
const { attestorAddress, publisher } = await this.publisherFactory.create(proposerForPublisher);
|
|
328
358
|
this.log.verbose(`Created publisher at address ${publisher.getSenderAddress()} for attestor ${attestorAddress}`);
|
|
329
|
-
this.publisher = publisher;
|
|
330
359
|
|
|
331
360
|
// In fisherman mode, set the actual proposer's address for simulations
|
|
332
361
|
if (this.config.fishermanMode && proposer) {
|
|
@@ -335,14 +364,41 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
335
364
|
}
|
|
336
365
|
|
|
337
366
|
// Prepare invalidation request if the pending chain is invalid (returns undefined if no need)
|
|
338
|
-
|
|
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
|
+
}
|
|
339
396
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
syncedTo.archive,
|
|
397
|
+
const simulationOverridesPlan = l1SimulationOverridesBuilder.build();
|
|
398
|
+
const canProposeCheck = await publisher.canProposeAt(
|
|
399
|
+
archiveForCheck,
|
|
344
400
|
proposer ?? EthAddress.ZERO,
|
|
345
|
-
|
|
401
|
+
simulationOverridesPlan,
|
|
346
402
|
);
|
|
347
403
|
|
|
348
404
|
if (canProposeCheck === undefined) {
|
|
@@ -351,17 +407,17 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
351
407
|
logCtx,
|
|
352
408
|
);
|
|
353
409
|
this.emit('proposer-rollup-check-failed', { reason: 'Rollup contract check failed', slot });
|
|
354
|
-
this.metrics.
|
|
410
|
+
this.metrics.recordCheckpointPrecheckFailed('rollup_contract_check_failed');
|
|
355
411
|
return undefined;
|
|
356
412
|
}
|
|
357
413
|
|
|
358
|
-
if (canProposeCheck.slot !==
|
|
414
|
+
if (canProposeCheck.slot !== targetSlot) {
|
|
359
415
|
this.log.warn(
|
|
360
|
-
`Cannot propose block due to slot mismatch with rollup contract (this can be caused by a clock out of sync). Expected slot ${
|
|
361
|
-
{ ...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 },
|
|
362
418
|
);
|
|
363
419
|
this.emit('proposer-rollup-check-failed', { reason: 'Slot mismatch', slot });
|
|
364
|
-
this.metrics.
|
|
420
|
+
this.metrics.recordCheckpointPrecheckFailed('slot_mismatch');
|
|
365
421
|
return undefined;
|
|
366
422
|
}
|
|
367
423
|
|
|
@@ -371,40 +427,53 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
371
427
|
{ ...logCtx, rollup: canProposeCheck, expectedSlot: slot },
|
|
372
428
|
);
|
|
373
429
|
this.emit('proposer-rollup-check-failed', { reason: 'Block mismatch', slot });
|
|
374
|
-
this.metrics.
|
|
430
|
+
this.metrics.recordCheckpointPrecheckFailed('block_number_mismatch');
|
|
375
431
|
return undefined;
|
|
376
432
|
}
|
|
377
433
|
|
|
378
|
-
this.lastSlotForCheckpointProposalJob =
|
|
379
|
-
|
|
380
|
-
this.
|
|
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
|
+
);
|
|
381
445
|
|
|
382
446
|
// Create and return the checkpoint proposal job
|
|
383
447
|
return this.createCheckpointProposalJob(
|
|
384
|
-
epoch,
|
|
385
448
|
slot,
|
|
449
|
+
targetSlot,
|
|
450
|
+
targetEpoch,
|
|
386
451
|
checkpointNumber,
|
|
387
452
|
syncedTo.blockNumber,
|
|
388
453
|
proposer,
|
|
389
454
|
publisher,
|
|
390
455
|
attestorAddress,
|
|
391
456
|
invalidateCheckpoint,
|
|
457
|
+
syncedTo.proposedCheckpointData,
|
|
392
458
|
);
|
|
393
459
|
}
|
|
394
460
|
|
|
395
461
|
protected createCheckpointProposalJob(
|
|
396
|
-
epoch: EpochNumber,
|
|
397
462
|
slot: SlotNumber,
|
|
463
|
+
targetSlot: SlotNumber,
|
|
464
|
+
targetEpoch: EpochNumber,
|
|
398
465
|
checkpointNumber: CheckpointNumber,
|
|
399
466
|
syncedToBlockNumber: BlockNumber,
|
|
400
467
|
proposer: EthAddress | undefined,
|
|
401
468
|
publisher: SequencerPublisher,
|
|
402
469
|
attestorAddress: EthAddress,
|
|
403
470
|
invalidateCheckpoint: InvalidateCheckpointRequest | undefined,
|
|
471
|
+
proposedCheckpointData?: ProposedCheckpointData,
|
|
404
472
|
): CheckpointProposalJob {
|
|
405
473
|
return new CheckpointProposalJob(
|
|
406
|
-
epoch,
|
|
407
474
|
slot,
|
|
475
|
+
targetSlot,
|
|
476
|
+
targetEpoch,
|
|
408
477
|
checkpointNumber,
|
|
409
478
|
syncedToBlockNumber,
|
|
410
479
|
proposer,
|
|
@@ -426,13 +495,22 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
426
495
|
this.epochCache,
|
|
427
496
|
this.dateProvider,
|
|
428
497
|
this.metrics,
|
|
498
|
+
this.checkpointProposalJobMetrics.createRecorder(),
|
|
429
499
|
this,
|
|
430
500
|
this.setState.bind(this),
|
|
431
501
|
this.tracer,
|
|
432
502
|
this.log.getBindings(),
|
|
503
|
+
proposedCheckpointData,
|
|
433
504
|
);
|
|
434
505
|
}
|
|
435
506
|
|
|
507
|
+
/**
|
|
508
|
+
* Returns the current sequencer state.
|
|
509
|
+
*/
|
|
510
|
+
public getState(): SequencerState {
|
|
511
|
+
return this.state;
|
|
512
|
+
}
|
|
513
|
+
|
|
436
514
|
/**
|
|
437
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.
|
|
438
516
|
* @param proposedState - The new state to transition to.
|
|
@@ -479,16 +557,15 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
479
557
|
* We don't check against the previous block submitted since it may have been reorg'd out.
|
|
480
558
|
*/
|
|
481
559
|
protected async checkSync(args: { ts: bigint; slot: SlotNumber }): Promise<SequencerSyncCheckResult | undefined> {
|
|
482
|
-
// Check that the archiver
|
|
483
|
-
//
|
|
484
|
-
//
|
|
485
|
-
const
|
|
486
|
-
const { slot
|
|
487
|
-
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) {
|
|
488
566
|
this.log.debug(`Cannot propose block at next L2 slot ${slot} due to pending sync from L1`, {
|
|
489
567
|
slot,
|
|
490
|
-
|
|
491
|
-
l1Timestamp,
|
|
568
|
+
syncedL2Slot,
|
|
492
569
|
});
|
|
493
570
|
return undefined;
|
|
494
571
|
}
|
|
@@ -498,25 +575,43 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
498
575
|
number: syncSummary.latestBlockNumber,
|
|
499
576
|
hash: syncSummary.latestBlockHash,
|
|
500
577
|
})),
|
|
501
|
-
this.l2BlockSource
|
|
578
|
+
this.l2BlockSource
|
|
579
|
+
.getL2Tips()
|
|
580
|
+
.then(t => ({ proposed: t.proposed, checkpointed: t.checkpointed, proposedCheckpoint: t.proposedCheckpoint })),
|
|
502
581
|
this.p2pClient.getStatus().then(p2p => p2p.syncedToL2Block),
|
|
503
|
-
this.l1ToL2MessageSource.getL2Tips().then(t => t.proposed),
|
|
582
|
+
this.l1ToL2MessageSource.getL2Tips().then(t => ({ proposed: t.proposed, checkpointed: t.checkpointed })),
|
|
504
583
|
this.l2BlockSource.getPendingChainValidationStatus(),
|
|
584
|
+
this.l2BlockSource.getProposedCheckpointOnly(),
|
|
505
585
|
] as const);
|
|
506
586
|
|
|
507
|
-
const [worldState,
|
|
587
|
+
const [worldState, l2Tips, p2p, l1ToL2MessageSourceTips, pendingChainValidationStatus, proposedCheckpointData] =
|
|
588
|
+
syncedBlocks;
|
|
508
589
|
|
|
509
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,
|
|
510
591
|
// as the world state can compute the new genesis block hash, but other components use the hardcoded constant.
|
|
511
592
|
// TODO(palla/mbps): Fix the above. All components should be able to handle dynamic genesis block hashes.
|
|
512
593
|
const result =
|
|
513
|
-
(
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
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);
|
|
517
607
|
|
|
518
608
|
if (!result) {
|
|
519
|
-
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
|
+
});
|
|
520
615
|
return undefined;
|
|
521
616
|
}
|
|
522
617
|
|
|
@@ -526,26 +621,33 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
526
621
|
const archive = new Fr((await this.worldState.getCommitted().getTreeInfo(MerkleTreeId.ARCHIVE)).root);
|
|
527
622
|
return {
|
|
528
623
|
checkpointNumber: CheckpointNumber.ZERO,
|
|
624
|
+
checkpointedCheckpointNumber: CheckpointNumber.ZERO,
|
|
529
625
|
blockNumber: BlockNumber.ZERO,
|
|
530
626
|
archive,
|
|
531
|
-
|
|
627
|
+
hasProposedCheckpoint: false,
|
|
628
|
+
syncedL2Slot,
|
|
532
629
|
pendingChainValidationStatus,
|
|
533
630
|
};
|
|
534
631
|
}
|
|
535
632
|
|
|
536
|
-
const
|
|
537
|
-
if (!
|
|
633
|
+
const blockData = await this.l2BlockSource.getBlockData(blockNumber);
|
|
634
|
+
if (!blockData) {
|
|
538
635
|
// this shouldn't really happen because a moment ago we checked that all components were in sync
|
|
539
|
-
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`);
|
|
540
637
|
return undefined;
|
|
541
638
|
}
|
|
542
639
|
|
|
640
|
+
const hasProposedCheckpoint = l2Tips.proposedCheckpoint.checkpoint.number > l2Tips.checkpointed.checkpoint.number;
|
|
641
|
+
|
|
543
642
|
return {
|
|
544
|
-
|
|
545
|
-
blockNumber:
|
|
546
|
-
checkpointNumber:
|
|
547
|
-
|
|
548
|
-
|
|
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,
|
|
549
651
|
pendingChainValidationStatus,
|
|
550
652
|
};
|
|
551
653
|
}
|
|
@@ -554,20 +656,20 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
554
656
|
* Checks if we are the proposer for the next slot.
|
|
555
657
|
* @returns True if we can propose, and the proposer address (undefined if anyone can propose)
|
|
556
658
|
*/
|
|
557
|
-
protected async checkCanPropose(
|
|
659
|
+
protected async checkCanPropose(targetSlot: SlotNumber): Promise<[boolean, EthAddress | undefined]> {
|
|
558
660
|
let proposer: EthAddress | undefined;
|
|
559
661
|
|
|
560
662
|
try {
|
|
561
|
-
proposer = await this.epochCache.getProposerAttesterAddressInSlot(
|
|
663
|
+
proposer = await this.epochCache.getProposerAttesterAddressInSlot(targetSlot);
|
|
562
664
|
} catch (e) {
|
|
563
665
|
if (e instanceof NoCommitteeError) {
|
|
564
|
-
if (this.lastSlotForNoCommitteeWarning !==
|
|
565
|
-
this.lastSlotForNoCommitteeWarning =
|
|
566
|
-
this.log.warn(`Cannot propose at
|
|
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`);
|
|
567
669
|
}
|
|
568
670
|
return [false, undefined];
|
|
569
671
|
}
|
|
570
|
-
this.log.error(`Error getting proposer for slot ${
|
|
672
|
+
this.log.error(`Error getting proposer for target slot ${targetSlot}`, e);
|
|
571
673
|
return [false, undefined];
|
|
572
674
|
}
|
|
573
675
|
|
|
@@ -584,10 +686,18 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
584
686
|
const weAreProposer = validatorAddresses.some(addr => addr.equals(proposer));
|
|
585
687
|
|
|
586
688
|
if (!weAreProposer) {
|
|
587
|
-
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
|
+
});
|
|
588
694
|
return [false, proposer];
|
|
589
695
|
}
|
|
590
696
|
|
|
697
|
+
this.log.info(`We are the proposer for pipeline slot ${targetSlot}`, {
|
|
698
|
+
targetSlot,
|
|
699
|
+
proposer,
|
|
700
|
+
});
|
|
591
701
|
return [true, proposer];
|
|
592
702
|
}
|
|
593
703
|
|
|
@@ -596,8 +706,8 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
596
706
|
* This allows the sequencer to participate in governance/slashing votes even when it cannot build blocks.
|
|
597
707
|
*/
|
|
598
708
|
@trackSpan('Seqeuencer.tryVoteWhenSyncFails', ({ slot }) => ({ [Attributes.SLOT_NUMBER]: slot }))
|
|
599
|
-
protected async tryVoteWhenSyncFails(args: { slot: SlotNumber; ts: bigint }): Promise<void> {
|
|
600
|
-
const { slot } = args;
|
|
709
|
+
protected async tryVoteWhenSyncFails(args: { slot: SlotNumber; targetSlot: SlotNumber; ts: bigint }): Promise<void> {
|
|
710
|
+
const { slot, targetSlot } = args;
|
|
601
711
|
|
|
602
712
|
// Prevent duplicate attempts in the same slot
|
|
603
713
|
if (this.lastSlotForFallbackVote === slot) {
|
|
@@ -625,7 +735,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
625
735
|
});
|
|
626
736
|
|
|
627
737
|
// Check if we're a proposer or proposal is open
|
|
628
|
-
const [canPropose, proposer] = await this.checkCanPropose(
|
|
738
|
+
const [canPropose, proposer] = await this.checkCanPropose(targetSlot);
|
|
629
739
|
if (!canPropose) {
|
|
630
740
|
this.log.trace(`Cannot vote in slot ${slot} since we are not a proposer`, { slot, proposer });
|
|
631
741
|
return;
|
|
@@ -642,9 +752,9 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
642
752
|
slot,
|
|
643
753
|
});
|
|
644
754
|
|
|
645
|
-
// Enqueue governance and slashing votes
|
|
755
|
+
// Enqueue governance and slashing votes (voter uses the target slot for L1 submission)
|
|
646
756
|
const voter = new CheckpointVoter(
|
|
647
|
-
|
|
757
|
+
targetSlot,
|
|
648
758
|
publisher,
|
|
649
759
|
attestorAddress,
|
|
650
760
|
this.validatorClient,
|
|
@@ -724,7 +834,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
724
834
|
syncedTo: SequencerSyncCheckResult,
|
|
725
835
|
currentSlot: SlotNumber,
|
|
726
836
|
): Promise<void> {
|
|
727
|
-
const { pendingChainValidationStatus,
|
|
837
|
+
const { pendingChainValidationStatus, syncedL2Slot } = syncedTo;
|
|
728
838
|
if (pendingChainValidationStatus.valid) {
|
|
729
839
|
return;
|
|
730
840
|
}
|
|
@@ -739,7 +849,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
739
849
|
|
|
740
850
|
const logData = {
|
|
741
851
|
invalidL1Timestamp: invalidCheckpointTimestamp,
|
|
742
|
-
|
|
852
|
+
syncedL2Slot,
|
|
743
853
|
invalidCheckpoint: pendingChainValidationStatus.checkpoint,
|
|
744
854
|
secondsBeforeInvalidatingBlockAsCommitteeMember,
|
|
745
855
|
secondsBeforeInvalidatingBlockAsNonCommitteeMember,
|
|
@@ -867,6 +977,11 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
867
977
|
return this.validatorClient?.getValidatorAddresses();
|
|
868
978
|
}
|
|
869
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
|
+
|
|
870
985
|
public getConfig() {
|
|
871
986
|
return this.config;
|
|
872
987
|
}
|
|
@@ -877,10 +992,13 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
877
992
|
}
|
|
878
993
|
|
|
879
994
|
type SequencerSyncCheckResult = {
|
|
880
|
-
|
|
995
|
+
blockData?: BlockData;
|
|
881
996
|
checkpointNumber: CheckpointNumber;
|
|
997
|
+
checkpointedCheckpointNumber: CheckpointNumber;
|
|
882
998
|
blockNumber: BlockNumber;
|
|
883
999
|
archive: Fr;
|
|
884
|
-
|
|
1000
|
+
hasProposedCheckpoint: boolean;
|
|
1001
|
+
proposedCheckpointData?: ProposedCheckpointData;
|
|
1002
|
+
syncedL2Slot: SlotNumber;
|
|
885
1003
|
pendingChainValidationStatus: ValidateCheckpointResult;
|
|
886
1004
|
};
|