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