@aztec/sequencer-client 0.0.1-commit.934299a21 → 0.0.1-commit.949a33fd8
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 +6 -1
- package/dest/client/sequencer-client.d.ts.map +1 -1
- package/dest/client/sequencer-client.js +49 -27
- package/dest/config.d.ts +25 -5
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +41 -26
- 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 -14
- 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 +13 -1
- package/dest/publisher/config.d.ts.map +1 -1
- package/dest/publisher/config.js +19 -4
- package/dest/publisher/l1_tx_failed_store/failed_tx_store.d.ts +3 -4
- package/dest/publisher/l1_tx_failed_store/failed_tx_store.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher-factory.d.ts +3 -5
- package/dest/publisher/sequencer-publisher-factory.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher-factory.js +16 -3
- package/dest/publisher/sequencer-publisher.d.ts +55 -43
- package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher.js +182 -118
- 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 +46 -10
- package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -1
- package/dest/sequencer/checkpoint_proposal_job.js +671 -225
- 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 +7 -1
- package/dest/sequencer/events.d.ts.map +1 -1
- package/dest/sequencer/metrics.d.ts +13 -10
- package/dest/sequencer/metrics.d.ts.map +1 -1
- package/dest/sequencer/metrics.js +45 -20
- package/dest/sequencer/sequencer.d.ts +38 -13
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +167 -79
- 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/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/dest/test/utils.d.ts +1 -1
- package/dest/test/utils.d.ts.map +1 -1
- package/dest/test/utils.js +7 -6
- package/package.json +27 -28
- package/src/client/sequencer-client.ts +61 -28
- package/src/config.ts +49 -27
- 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 +26 -62
- package/src/global_variable_builder/index.ts +3 -1
- package/src/publisher/config.ts +38 -4
- package/src/publisher/l1_tx_failed_store/failed_tx_store.ts +3 -1
- package/src/publisher/sequencer-publisher-factory.ts +18 -6
- package/src/publisher/sequencer-publisher.ts +265 -171
- package/src/sequencer/README.md +82 -13
- package/src/sequencer/chain_state_overrides.ts +87 -0
- package/src/sequencer/checkpoint_proposal_job.ts +823 -251
- package/src/sequencer/checkpoint_proposal_job_metrics.ts +128 -0
- package/src/sequencer/checkpoint_voter.ts +1 -12
- package/src/sequencer/events.ts +6 -1
- package/src/sequencer/metrics.ts +57 -24
- package/src/sequencer/sequencer.ts +233 -88
- package/src/sequencer/timetable.ts +64 -52
- package/src/sequencer/types.ts +1 -1
- package/src/test/mock_checkpoint_builder.ts +51 -48
- package/src/test/utils.ts +28 -10
|
@@ -1,20 +1,21 @@
|
|
|
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';
|
|
8
8
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
9
|
-
import { createLogger } from '@aztec/foundation/log';
|
|
9
|
+
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
10
10
|
import { RunningPromise } from '@aztec/foundation/running-promise';
|
|
11
11
|
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
15
|
import type { BlockData, L2BlockSink, L2BlockSource, ValidateCheckpointResult } from '@aztec/stdlib/block';
|
|
16
|
-
import type { Checkpoint } from '@aztec/stdlib/checkpoint';
|
|
17
|
-
import {
|
|
16
|
+
import type { Checkpoint, ProposedCheckpointData } from '@aztec/stdlib/checkpoint';
|
|
17
|
+
import type { ChainConfig } from '@aztec/stdlib/config';
|
|
18
|
+
import { getSlotStartBuildTimestamp } from '@aztec/stdlib/epoch-helpers';
|
|
18
19
|
import {
|
|
19
20
|
type ResolvedSequencerConfig,
|
|
20
21
|
type SequencerConfig,
|
|
@@ -22,6 +23,7 @@ import {
|
|
|
22
23
|
type WorldStateSynchronizer,
|
|
23
24
|
} from '@aztec/stdlib/interfaces/server';
|
|
24
25
|
import type { L1ToL2MessageSource } from '@aztec/stdlib/messaging';
|
|
26
|
+
import type { CoordinationSignatureContext } from '@aztec/stdlib/p2p';
|
|
25
27
|
import { pickFromSchema } from '@aztec/stdlib/schemas';
|
|
26
28
|
import { MerkleTreeId } from '@aztec/stdlib/trees';
|
|
27
29
|
import { Attributes, type TelemetryClient, type Tracer, getTelemetryClient, trackSpan } from '@aztec/telemetry-client';
|
|
@@ -34,6 +36,7 @@ import type { GlobalVariableBuilder } from '../global_variable_builder/global_bu
|
|
|
34
36
|
import type { SequencerPublisherFactory } from '../publisher/sequencer-publisher-factory.js';
|
|
35
37
|
import type { InvalidateCheckpointRequest, SequencerPublisher } from '../publisher/sequencer-publisher.js';
|
|
36
38
|
import { CheckpointProposalJob } from './checkpoint_proposal_job.js';
|
|
39
|
+
import { CheckpointProposalJobMetrics } from './checkpoint_proposal_job_metrics.js';
|
|
37
40
|
import { CheckpointVoter } from './checkpoint_voter.js';
|
|
38
41
|
import { SequencerInterruptedError, SequencerTooSlowError } from './errors.js';
|
|
39
42
|
import type { SequencerEvents } from './events.js';
|
|
@@ -55,7 +58,11 @@ export { SequencerState };
|
|
|
55
58
|
export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<SequencerEvents>) {
|
|
56
59
|
private runningPromise?: RunningPromise;
|
|
57
60
|
private state = SequencerState.STOPPED;
|
|
61
|
+
private stateSlotNumber: SlotNumber | undefined;
|
|
62
|
+
private stateEnteredAtMs = performance.now();
|
|
58
63
|
private metrics: SequencerMetrics;
|
|
64
|
+
private checkpointProposalJobMetrics: CheckpointProposalJobMetrics;
|
|
65
|
+
private readonly stateLog: Logger;
|
|
59
66
|
|
|
60
67
|
/** The last slot for which we attempted to perform our voting duties with degraded block production */
|
|
61
68
|
private lastSlotForFallbackVote: SlotNumber | undefined;
|
|
@@ -72,11 +79,15 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
72
79
|
/** The last epoch for which we logged strategy comparison in fisherman mode. */
|
|
73
80
|
private lastEpochForStrategyComparison: EpochNumber | undefined;
|
|
74
81
|
|
|
82
|
+
/** The last checkpoint proposal job, tracked so we can await its pending L1 submission during shutdown. */
|
|
83
|
+
protected lastCheckpointProposalJob: CheckpointProposalJob | undefined;
|
|
84
|
+
|
|
75
85
|
/** The maximum number of seconds that the sequencer can be into a slot to transition to a particular state. */
|
|
76
86
|
protected timetable!: SequencerTimetable;
|
|
77
87
|
|
|
78
88
|
/** Config for the sequencer */
|
|
79
89
|
protected config: ResolvedSequencerConfig = DefaultSequencerConfig;
|
|
90
|
+
private readonly signatureContext: CoordinationSignatureContext;
|
|
80
91
|
|
|
81
92
|
constructor(
|
|
82
93
|
protected publisherFactory: SequencerPublisherFactory,
|
|
@@ -92,34 +103,42 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
92
103
|
protected dateProvider: DateProvider,
|
|
93
104
|
protected epochCache: EpochCache,
|
|
94
105
|
protected rollupContract: RollupContract,
|
|
95
|
-
config: SequencerConfig,
|
|
106
|
+
config: SequencerConfig & Pick<ChainConfig, 'l1ChainId' | 'l1Contracts'>,
|
|
96
107
|
protected telemetry: TelemetryClient = getTelemetryClient(),
|
|
97
108
|
protected log = createLogger('sequencer'),
|
|
98
109
|
) {
|
|
99
110
|
super();
|
|
111
|
+
this.stateLog = log.createChild('state');
|
|
100
112
|
|
|
101
113
|
// Add [FISHERMAN] prefix to logger if in fisherman mode
|
|
102
114
|
if (config.fishermanMode) {
|
|
103
115
|
this.log = log.createChild('[FISHERMAN]');
|
|
104
116
|
}
|
|
105
117
|
|
|
118
|
+
this.signatureContext = {
|
|
119
|
+
chainId: config.l1ChainId,
|
|
120
|
+
rollupAddress: config.l1Contracts.rollupAddress,
|
|
121
|
+
};
|
|
106
122
|
this.metrics = new SequencerMetrics(telemetry, this.rollupContract, 'Sequencer');
|
|
123
|
+
this.checkpointProposalJobMetrics = new CheckpointProposalJobMetrics(telemetry);
|
|
107
124
|
this.updateConfig(config);
|
|
108
125
|
}
|
|
109
126
|
|
|
110
127
|
/** Updates sequencer config by the defined values and updates the timetable */
|
|
111
128
|
public updateConfig(config: Partial<SequencerConfig>) {
|
|
112
129
|
const filteredConfig = pickFromSchema(config, SequencerConfigSchema);
|
|
113
|
-
this.log.info(`Updated sequencer config`, omit(filteredConfig, '
|
|
130
|
+
this.log.info(`Updated sequencer config`, omit(filteredConfig, 'txPublicSetupAllowListExtend'));
|
|
114
131
|
this.config = merge(this.config, filteredConfig);
|
|
132
|
+
const p2pPropagationTime = this.config.attestationPropagationTime;
|
|
115
133
|
this.timetable = new SequencerTimetable(
|
|
116
134
|
{
|
|
117
135
|
ethereumSlotDuration: this.l1Constants.ethereumSlotDuration,
|
|
118
136
|
aztecSlotDuration: this.aztecSlotDuration,
|
|
119
137
|
l1PublishingTime: this.l1PublishingTime,
|
|
120
|
-
p2pPropagationTime
|
|
138
|
+
p2pPropagationTime,
|
|
121
139
|
blockDurationMs: this.config.blockDurationMs,
|
|
122
140
|
enforce: this.config.enforceTimeTable,
|
|
141
|
+
pipelining: this.epochCache.isProposerPipeliningEnabled(),
|
|
123
142
|
},
|
|
124
143
|
this.metrics,
|
|
125
144
|
this.log,
|
|
@@ -143,12 +162,18 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
143
162
|
this.log.info('Started sequencer');
|
|
144
163
|
}
|
|
145
164
|
|
|
165
|
+
/** Triggers an immediate run of the sequencer, bypassing the polling interval. */
|
|
166
|
+
public trigger() {
|
|
167
|
+
return this.runningPromise?.trigger();
|
|
168
|
+
}
|
|
169
|
+
|
|
146
170
|
/** Stops the sequencer from building blocks and moves to STOPPED state. */
|
|
147
171
|
public async stop(): Promise<void> {
|
|
148
172
|
this.log.info(`Stopping sequencer`);
|
|
149
173
|
this.setState(SequencerState.STOPPING, undefined, { force: true });
|
|
150
|
-
this.publisherFactory.
|
|
174
|
+
await this.publisherFactory.stopAll();
|
|
151
175
|
await this.runningPromise?.stop();
|
|
176
|
+
await this.lastCheckpointProposalJob?.awaitPendingSubmission();
|
|
152
177
|
this.setState(SequencerState.STOPPED, undefined, { force: true });
|
|
153
178
|
this.log.info('Stopped sequencer');
|
|
154
179
|
}
|
|
@@ -192,14 +217,25 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
192
217
|
@trackSpan('Sequencer.work')
|
|
193
218
|
protected async work() {
|
|
194
219
|
this.setState(SequencerState.SYNCHRONIZING, undefined);
|
|
195
|
-
const { slot, ts,
|
|
220
|
+
const { slot, ts, nowSeconds, epoch } = this.epochCache.getEpochAndSlotInNextL1Slot();
|
|
221
|
+
const { slot: targetSlot, epoch: targetEpoch } = this.epochCache.getTargetEpochAndSlotInNextL1Slot();
|
|
196
222
|
|
|
197
223
|
// Check if we are synced and it's our slot, grab a publisher, check previous block invalidation, etc
|
|
198
|
-
const checkpointProposalJob = await this.prepareCheckpointProposal(
|
|
224
|
+
const checkpointProposalJob = await this.prepareCheckpointProposal(
|
|
225
|
+
slot,
|
|
226
|
+
targetSlot,
|
|
227
|
+
epoch,
|
|
228
|
+
targetEpoch,
|
|
229
|
+
ts,
|
|
230
|
+
nowSeconds,
|
|
231
|
+
);
|
|
199
232
|
if (!checkpointProposalJob) {
|
|
200
233
|
return;
|
|
201
234
|
}
|
|
202
235
|
|
|
236
|
+
// Track the job so we can await its pending L1 submission during shutdown
|
|
237
|
+
this.lastCheckpointProposalJob = checkpointProposalJob;
|
|
238
|
+
|
|
203
239
|
// Execute the checkpoint proposal job
|
|
204
240
|
const checkpoint = await checkpointProposalJob.execute();
|
|
205
241
|
|
|
@@ -208,13 +244,13 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
208
244
|
this.lastCheckpointProposed = checkpoint;
|
|
209
245
|
}
|
|
210
246
|
|
|
211
|
-
// Log fee strategy comparison if on fisherman
|
|
247
|
+
// Log fee strategy comparison if on fisherman (uses target epoch since we mirror the proposer's perspective)
|
|
212
248
|
if (
|
|
213
249
|
this.config.fishermanMode &&
|
|
214
|
-
(this.lastEpochForStrategyComparison === undefined ||
|
|
250
|
+
(this.lastEpochForStrategyComparison === undefined || targetEpoch > this.lastEpochForStrategyComparison)
|
|
215
251
|
) {
|
|
216
|
-
this.logStrategyComparison(
|
|
217
|
-
this.lastEpochForStrategyComparison =
|
|
252
|
+
this.logStrategyComparison(targetEpoch, checkpointProposalJob.getPublisher());
|
|
253
|
+
this.lastEpochForStrategyComparison = targetEpoch;
|
|
218
254
|
}
|
|
219
255
|
|
|
220
256
|
return checkpoint;
|
|
@@ -226,44 +262,49 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
226
262
|
* @returns CheckpointProposalJob if successful, undefined if we are not yet synced or are not the proposer.
|
|
227
263
|
*/
|
|
228
264
|
@trackSpan('Sequencer.prepareCheckpointProposal')
|
|
229
|
-
|
|
230
|
-
epoch: EpochNumber,
|
|
265
|
+
protected async prepareCheckpointProposal(
|
|
231
266
|
slot: SlotNumber,
|
|
267
|
+
targetSlot: SlotNumber,
|
|
268
|
+
epoch: EpochNumber,
|
|
269
|
+
targetEpoch: EpochNumber,
|
|
232
270
|
ts: bigint,
|
|
233
|
-
|
|
271
|
+
nowSeconds: bigint,
|
|
234
272
|
): Promise<CheckpointProposalJob | undefined> {
|
|
235
|
-
// Check we have not already processed this slot (cheapest check)
|
|
273
|
+
// Check we have not already processed this target slot (cheapest check)
|
|
236
274
|
// We only check this if enforce timetable is set, since we want to keep processing the same slot if we are not
|
|
237
275
|
// running against actual time (eg when we use sandbox-style automining)
|
|
238
276
|
if (
|
|
239
277
|
this.lastSlotForCheckpointProposalJob &&
|
|
240
|
-
this.lastSlotForCheckpointProposalJob >=
|
|
278
|
+
this.lastSlotForCheckpointProposalJob >= targetSlot &&
|
|
241
279
|
this.config.enforceTimeTable
|
|
242
280
|
) {
|
|
243
|
-
this.log.trace(`
|
|
281
|
+
this.log.trace(`Target slot ${targetSlot} has already been processed`);
|
|
244
282
|
return undefined;
|
|
245
283
|
}
|
|
246
284
|
|
|
247
|
-
// But if we have already proposed for this slot,
|
|
248
|
-
if (this.lastCheckpointProposed && this.lastCheckpointProposed.header.slotNumber >=
|
|
249
|
-
this.log.trace(
|
|
285
|
+
// But if we have already proposed for this slot, then we definitely have to skip it, automining or not
|
|
286
|
+
if (this.lastCheckpointProposed && this.lastCheckpointProposed.header.slotNumber >= targetSlot) {
|
|
287
|
+
this.log.trace(
|
|
288
|
+
`Slot ${targetSlot} has already been published as checkpoint ${this.lastCheckpointProposed.number}`,
|
|
289
|
+
);
|
|
250
290
|
return undefined;
|
|
251
291
|
}
|
|
252
292
|
|
|
253
293
|
// Check all components are synced to latest as seen by the archiver (queries all subsystems)
|
|
254
294
|
const syncedTo = await this.checkSync({ ts, slot });
|
|
255
295
|
if (!syncedTo) {
|
|
256
|
-
await this.tryVoteWhenSyncFails({ slot, ts });
|
|
296
|
+
await this.tryVoteWhenSyncFails({ slot, targetSlot, ts });
|
|
257
297
|
return undefined;
|
|
258
298
|
}
|
|
259
299
|
|
|
260
|
-
// If escape hatch is open for
|
|
300
|
+
// If escape hatch is open for the target epoch, do not start checkpoint proposal work and do not attempt invalidations.
|
|
261
301
|
// Still perform governance/slashing voting (as proposer) once per slot.
|
|
262
|
-
|
|
302
|
+
// When pipelining, we check the target epoch (slot+1's epoch) since that's the epoch we're building for.
|
|
303
|
+
const isEscapeHatchOpen = await this.epochCache.isEscapeHatchOpen(targetEpoch);
|
|
263
304
|
|
|
264
305
|
if (isEscapeHatchOpen) {
|
|
265
306
|
this.setState(SequencerState.PROPOSER_CHECK, slot);
|
|
266
|
-
const [canPropose, proposer] = await this.checkCanPropose(
|
|
307
|
+
const [canPropose, proposer] = await this.checkCanPropose(targetSlot);
|
|
267
308
|
if (canPropose) {
|
|
268
309
|
await this.tryVoteWhenEscapeHatchOpen({ slot, proposer });
|
|
269
310
|
} else {
|
|
@@ -280,18 +321,18 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
280
321
|
const checkpointNumber = CheckpointNumber(syncedTo.checkpointNumber + 1);
|
|
281
322
|
|
|
282
323
|
const logCtx = {
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
syncedToL2Slot: getSlotAtTimestamp(syncedTo.l1Timestamp, this.l1Constants),
|
|
324
|
+
nowSeconds,
|
|
325
|
+
syncedToL2Slot: syncedTo.syncedL2Slot,
|
|
286
326
|
slot,
|
|
327
|
+
targetSlot,
|
|
287
328
|
slotTs: ts,
|
|
288
329
|
checkpointNumber,
|
|
289
330
|
isPendingChainValid: pick(syncedTo.pendingChainValidationStatus, 'valid', 'reason', 'invalidIndex'),
|
|
290
331
|
};
|
|
291
332
|
|
|
292
|
-
// Check that we are a proposer for the
|
|
333
|
+
// Check that we are a proposer for the target slot.
|
|
293
334
|
this.setState(SequencerState.PROPOSER_CHECK, slot);
|
|
294
|
-
const [canPropose, proposer] = await this.checkCanPropose(
|
|
335
|
+
const [canPropose, proposer] = await this.checkCanPropose(targetSlot);
|
|
295
336
|
|
|
296
337
|
// If we are not a proposer check if we should invalidate an invalid checkpoint, and bail
|
|
297
338
|
if (!canPropose) {
|
|
@@ -299,10 +340,20 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
299
340
|
return undefined;
|
|
300
341
|
}
|
|
301
342
|
|
|
302
|
-
//
|
|
303
|
-
|
|
343
|
+
// Guard: don't exceed 1-deep pipeline. Without a proposed checkpoint, we can only build
|
|
344
|
+
// confirmed + 1. With a proposed checkpoint, we can build confirmed + 2.
|
|
345
|
+
const confirmedCkpt = syncedTo.checkpointedCheckpointNumber;
|
|
346
|
+
if (checkpointNumber > confirmedCkpt + 2) {
|
|
347
|
+
this.log.verbose(
|
|
348
|
+
`Skipping slot ${targetSlot}: checkpoint ${checkpointNumber} exceeds max pipeline depth (confirmed=${confirmedCkpt})`,
|
|
349
|
+
);
|
|
350
|
+
return undefined;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Check that the target slot is not taken by a block already (should never happen, since only us can propose for this slot)
|
|
354
|
+
if (syncedTo.blockData && syncedTo.blockData.header.getSlot() >= targetSlot) {
|
|
304
355
|
this.log.warn(
|
|
305
|
-
`Cannot propose block at
|
|
356
|
+
`Cannot propose block at target slot ${targetSlot} since that slot was taken by block ${syncedTo.blockNumber}`,
|
|
306
357
|
{ ...logCtx, block: syncedTo.blockData.header.toInspect() },
|
|
307
358
|
);
|
|
308
359
|
this.metrics.recordCheckpointPrecheckFailed('slot_already_taken');
|
|
@@ -324,14 +375,41 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
324
375
|
}
|
|
325
376
|
|
|
326
377
|
// Prepare invalidation request if the pending chain is invalid (returns undefined if no need)
|
|
327
|
-
|
|
378
|
+
let invalidateCheckpoint = await publisher.simulateInvalidateCheckpoint(syncedTo.pendingChainValidationStatus);
|
|
379
|
+
|
|
380
|
+
// Determine the correct archive and L1 state overrides for the canProposeAt check.
|
|
381
|
+
// The L1 contract reads archives[proposedCheckpointNumber] and compares it with the provided archive.
|
|
382
|
+
// When invalidating or pipelining, the local archive may differ from L1's, so we adjust accordingly.
|
|
383
|
+
let archiveForCheck = syncedTo.archive;
|
|
384
|
+
const l1SimulationOverridesBuilder = new SimulationOverridesBuilder();
|
|
385
|
+
|
|
386
|
+
if (this.epochCache.isProposerPipeliningEnabled() && syncedTo.hasProposedCheckpoint) {
|
|
387
|
+
// Parent checkpoint hasn't landed on L1 yet. Override both the proposed checkpoint number
|
|
388
|
+
// and the archive at that checkpoint so L1 simulation sees the correct chain tip.
|
|
389
|
+
const parentCheckpointNumber = CheckpointNumber(checkpointNumber - 1);
|
|
390
|
+
l1SimulationOverridesBuilder.forPendingCheckpoint(parentCheckpointNumber).withPendingArchive(syncedTo.archive);
|
|
391
|
+
this.metrics.recordPipelineDepth(syncedTo.checkpointNumber - syncedTo.checkpointedCheckpointNumber);
|
|
392
|
+
|
|
393
|
+
this.log.verbose(
|
|
394
|
+
`Building on top of proposed checkpoint (pending=${syncedTo.proposedCheckpointData?.checkpointNumber})`,
|
|
395
|
+
);
|
|
396
|
+
// Clear the invalidation - the proposed checkpoint should handle it.
|
|
397
|
+
invalidateCheckpoint = undefined;
|
|
398
|
+
} else if (invalidateCheckpoint) {
|
|
399
|
+
// After invalidation, L1 will roll back to checkpoint N-1. The archive at N-1 already
|
|
400
|
+
// exists on L1, so we just pass the matching archive (the lastArchive of the invalid checkpoint).
|
|
401
|
+
archiveForCheck = invalidateCheckpoint.lastArchive;
|
|
402
|
+
l1SimulationOverridesBuilder.forPendingCheckpoint(invalidateCheckpoint.forcePendingCheckpointNumber);
|
|
403
|
+
this.metrics.recordPipelineDepth(0);
|
|
404
|
+
} else {
|
|
405
|
+
this.metrics.recordPipelineDepth(0);
|
|
406
|
+
}
|
|
328
407
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
syncedTo.archive,
|
|
408
|
+
const simulationOverridesPlan = l1SimulationOverridesBuilder.build();
|
|
409
|
+
const canProposeCheck = await publisher.canProposeAt(
|
|
410
|
+
archiveForCheck,
|
|
333
411
|
proposer ?? EthAddress.ZERO,
|
|
334
|
-
|
|
412
|
+
simulationOverridesPlan,
|
|
335
413
|
);
|
|
336
414
|
|
|
337
415
|
if (canProposeCheck === undefined) {
|
|
@@ -344,10 +422,10 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
344
422
|
return undefined;
|
|
345
423
|
}
|
|
346
424
|
|
|
347
|
-
if (canProposeCheck.slot !==
|
|
425
|
+
if (canProposeCheck.slot !== targetSlot) {
|
|
348
426
|
this.log.warn(
|
|
349
|
-
`Cannot propose block due to slot mismatch with rollup contract (this can be caused by a clock out of sync). Expected slot ${
|
|
350
|
-
{ ...logCtx, rollup: canProposeCheck, expectedSlot:
|
|
427
|
+
`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}.`,
|
|
428
|
+
{ ...logCtx, rollup: canProposeCheck, expectedSlot: targetSlot },
|
|
351
429
|
);
|
|
352
430
|
this.emit('proposer-rollup-check-failed', { reason: 'Slot mismatch', slot });
|
|
353
431
|
this.metrics.recordCheckpointPrecheckFailed('slot_mismatch');
|
|
@@ -364,36 +442,49 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
364
442
|
return undefined;
|
|
365
443
|
}
|
|
366
444
|
|
|
367
|
-
this.lastSlotForCheckpointProposalJob =
|
|
368
|
-
|
|
369
|
-
this.
|
|
445
|
+
this.lastSlotForCheckpointProposalJob = targetSlot;
|
|
446
|
+
|
|
447
|
+
await this.p2pClient.prepareForSlot(targetSlot);
|
|
448
|
+
this.log.info(
|
|
449
|
+
`Preparing checkpoint proposal ${checkpointNumber} for target slot ${targetSlot} during wall-clock slot ${slot}`,
|
|
450
|
+
{
|
|
451
|
+
...logCtx,
|
|
452
|
+
proposer,
|
|
453
|
+
pipeliningEnabled: this.epochCache.isProposerPipeliningEnabled(),
|
|
454
|
+
},
|
|
455
|
+
);
|
|
370
456
|
|
|
371
457
|
// Create and return the checkpoint proposal job
|
|
372
458
|
return this.createCheckpointProposalJob(
|
|
373
|
-
epoch,
|
|
374
459
|
slot,
|
|
460
|
+
targetSlot,
|
|
461
|
+
targetEpoch,
|
|
375
462
|
checkpointNumber,
|
|
376
463
|
syncedTo.blockNumber,
|
|
377
464
|
proposer,
|
|
378
465
|
publisher,
|
|
379
466
|
attestorAddress,
|
|
380
467
|
invalidateCheckpoint,
|
|
468
|
+
syncedTo.proposedCheckpointData,
|
|
381
469
|
);
|
|
382
470
|
}
|
|
383
471
|
|
|
384
472
|
protected createCheckpointProposalJob(
|
|
385
|
-
epoch: EpochNumber,
|
|
386
473
|
slot: SlotNumber,
|
|
474
|
+
targetSlot: SlotNumber,
|
|
475
|
+
targetEpoch: EpochNumber,
|
|
387
476
|
checkpointNumber: CheckpointNumber,
|
|
388
477
|
syncedToBlockNumber: BlockNumber,
|
|
389
478
|
proposer: EthAddress | undefined,
|
|
390
479
|
publisher: SequencerPublisher,
|
|
391
480
|
attestorAddress: EthAddress,
|
|
392
481
|
invalidateCheckpoint: InvalidateCheckpointRequest | undefined,
|
|
482
|
+
proposedCheckpointData?: ProposedCheckpointData,
|
|
393
483
|
): CheckpointProposalJob {
|
|
394
484
|
return new CheckpointProposalJob(
|
|
395
|
-
epoch,
|
|
396
485
|
slot,
|
|
486
|
+
targetSlot,
|
|
487
|
+
targetEpoch,
|
|
397
488
|
checkpointNumber,
|
|
398
489
|
syncedToBlockNumber,
|
|
399
490
|
proposer,
|
|
@@ -409,16 +500,19 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
409
500
|
this.checkpointsBuilder,
|
|
410
501
|
this.l2BlockSource,
|
|
411
502
|
this.l1Constants,
|
|
503
|
+
this.signatureContext,
|
|
412
504
|
this.config,
|
|
413
505
|
this.timetable,
|
|
414
506
|
this.slasherClient,
|
|
415
507
|
this.epochCache,
|
|
416
508
|
this.dateProvider,
|
|
417
509
|
this.metrics,
|
|
510
|
+
this.checkpointProposalJobMetrics.createRecorder(),
|
|
418
511
|
this,
|
|
419
512
|
this.setState.bind(this),
|
|
420
513
|
this.tracer,
|
|
421
514
|
this.log.getBindings(),
|
|
515
|
+
proposedCheckpointData,
|
|
422
516
|
);
|
|
423
517
|
}
|
|
424
518
|
|
|
@@ -454,19 +548,35 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
454
548
|
this.timetable.assertTimeLeft(proposedState, secondsIntoSlot);
|
|
455
549
|
}
|
|
456
550
|
|
|
551
|
+
const oldState = this.state;
|
|
552
|
+
const oldStateSlotNumber = this.stateSlotNumber;
|
|
553
|
+
const stateChanged = proposedState !== oldState;
|
|
554
|
+
const transitionAtMs = performance.now();
|
|
555
|
+
const stateDurationMs = transitionAtMs - this.stateEnteredAtMs;
|
|
556
|
+
|
|
457
557
|
const boringStates = [SequencerState.IDLE, SequencerState.SYNCHRONIZING];
|
|
458
558
|
const logLevel =
|
|
459
|
-
boringStates.includes(proposedState) && boringStates.includes(
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
559
|
+
boringStates.includes(proposedState) && boringStates.includes(oldState) ? ('trace' as const) : ('debug' as const);
|
|
560
|
+
this.stateLog[logLevel](`Transitioning from ${oldState} to ${proposedState}`, {
|
|
561
|
+
oldState,
|
|
562
|
+
newState: proposedState,
|
|
563
|
+
slotNumber,
|
|
564
|
+
stateSlotNumber: oldStateSlotNumber,
|
|
565
|
+
secondsIntoSlot,
|
|
566
|
+
...(stateChanged && { stateDurationMs: Math.ceil(stateDurationMs) }),
|
|
567
|
+
});
|
|
463
568
|
|
|
464
569
|
this.emit('state-changed', {
|
|
465
|
-
oldState
|
|
570
|
+
oldState,
|
|
466
571
|
newState: proposedState,
|
|
467
572
|
secondsIntoSlot,
|
|
468
573
|
slot: slotNumber,
|
|
469
574
|
});
|
|
575
|
+
if (stateChanged) {
|
|
576
|
+
this.metrics.recordStateDuration(stateDurationMs, oldState);
|
|
577
|
+
this.stateEnteredAtMs = transitionAtMs;
|
|
578
|
+
this.stateSlotNumber = slotNumber;
|
|
579
|
+
}
|
|
470
580
|
this.state = proposedState;
|
|
471
581
|
}
|
|
472
582
|
|
|
@@ -475,16 +585,15 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
475
585
|
* We don't check against the previous block submitted since it may have been reorg'd out.
|
|
476
586
|
*/
|
|
477
587
|
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 (
|
|
588
|
+
// Check that the archiver has fully synced the L2 slot before the one we want to propose in.
|
|
589
|
+
// The archiver reports sync progress via L1 block timestamps and synced checkpoint slots.
|
|
590
|
+
// See getSyncedL2SlotNumber for how missed L1 blocks are handled.
|
|
591
|
+
const syncedL2Slot = await this.l2BlockSource.getSyncedL2SlotNumber();
|
|
592
|
+
const { slot } = args;
|
|
593
|
+
if (syncedL2Slot === undefined || syncedL2Slot + 1 < slot) {
|
|
484
594
|
this.log.debug(`Cannot propose block at next L2 slot ${slot} due to pending sync from L1`, {
|
|
485
595
|
slot,
|
|
486
|
-
|
|
487
|
-
l1Timestamp,
|
|
596
|
+
syncedL2Slot,
|
|
488
597
|
});
|
|
489
598
|
return undefined;
|
|
490
599
|
}
|
|
@@ -494,25 +603,43 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
494
603
|
number: syncSummary.latestBlockNumber,
|
|
495
604
|
hash: syncSummary.latestBlockHash,
|
|
496
605
|
})),
|
|
497
|
-
this.l2BlockSource
|
|
606
|
+
this.l2BlockSource
|
|
607
|
+
.getL2Tips()
|
|
608
|
+
.then(t => ({ proposed: t.proposed, checkpointed: t.checkpointed, proposedCheckpoint: t.proposedCheckpoint })),
|
|
498
609
|
this.p2pClient.getStatus().then(p2p => p2p.syncedToL2Block),
|
|
499
|
-
this.l1ToL2MessageSource.getL2Tips().then(t => t.proposed),
|
|
610
|
+
this.l1ToL2MessageSource.getL2Tips().then(t => ({ proposed: t.proposed, checkpointed: t.checkpointed })),
|
|
500
611
|
this.l2BlockSource.getPendingChainValidationStatus(),
|
|
612
|
+
this.l2BlockSource.getLastProposedCheckpoint(),
|
|
501
613
|
] as const);
|
|
502
614
|
|
|
503
|
-
const [worldState,
|
|
615
|
+
const [worldState, l2Tips, p2p, l1ToL2MessageSourceTips, pendingChainValidationStatus, proposedCheckpointData] =
|
|
616
|
+
syncedBlocks;
|
|
504
617
|
|
|
505
618
|
// 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
619
|
// as the world state can compute the new genesis block hash, but other components use the hardcoded constant.
|
|
507
620
|
// TODO(palla/mbps): Fix the above. All components should be able to handle dynamic genesis block hashes.
|
|
508
621
|
const result =
|
|
509
|
-
(
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
622
|
+
(l2Tips.proposed.number === 0 &&
|
|
623
|
+
l2Tips.checkpointed.block.number === 0 &&
|
|
624
|
+
l2Tips.checkpointed.checkpoint.number === 0 &&
|
|
625
|
+
worldState.number === 0 &&
|
|
626
|
+
p2p.number === 0 &&
|
|
627
|
+
l1ToL2MessageSourceTips.proposed.number === 0 &&
|
|
628
|
+
l1ToL2MessageSourceTips.checkpointed.block.number === 0 &&
|
|
629
|
+
l1ToL2MessageSourceTips.checkpointed.checkpoint.number === 0) ||
|
|
630
|
+
(worldState.hash === l2Tips.proposed.hash &&
|
|
631
|
+
p2p.hash === l2Tips.proposed.hash &&
|
|
632
|
+
l1ToL2MessageSourceTips.proposed.hash === l2Tips.proposed.hash &&
|
|
633
|
+
l1ToL2MessageSourceTips.checkpointed.block.hash === l2Tips.checkpointed.block.hash &&
|
|
634
|
+
l1ToL2MessageSourceTips.checkpointed.checkpoint.hash === l2Tips.checkpointed.checkpoint.hash);
|
|
513
635
|
|
|
514
636
|
if (!result) {
|
|
515
|
-
this.log.debug(`Sequencer sync check failed`, {
|
|
637
|
+
this.log.debug(`Sequencer sync check failed`, {
|
|
638
|
+
worldState,
|
|
639
|
+
l2BlockSource: l2Tips.proposed,
|
|
640
|
+
p2p,
|
|
641
|
+
l1ToL2MessageSourceTips,
|
|
642
|
+
});
|
|
516
643
|
return undefined;
|
|
517
644
|
}
|
|
518
645
|
|
|
@@ -522,9 +649,11 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
522
649
|
const archive = new Fr((await this.worldState.getCommitted().getTreeInfo(MerkleTreeId.ARCHIVE)).root);
|
|
523
650
|
return {
|
|
524
651
|
checkpointNumber: CheckpointNumber.ZERO,
|
|
652
|
+
checkpointedCheckpointNumber: CheckpointNumber.ZERO,
|
|
525
653
|
blockNumber: BlockNumber.ZERO,
|
|
526
654
|
archive,
|
|
527
|
-
|
|
655
|
+
hasProposedCheckpoint: false,
|
|
656
|
+
syncedL2Slot,
|
|
528
657
|
pendingChainValidationStatus,
|
|
529
658
|
};
|
|
530
659
|
}
|
|
@@ -536,12 +665,17 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
536
665
|
return undefined;
|
|
537
666
|
}
|
|
538
667
|
|
|
668
|
+
const hasProposedCheckpoint = l2Tips.proposedCheckpoint.checkpoint.number > l2Tips.checkpointed.checkpoint.number;
|
|
669
|
+
|
|
539
670
|
return {
|
|
540
671
|
blockData,
|
|
541
672
|
blockNumber: blockData.header.getBlockNumber(),
|
|
542
673
|
checkpointNumber: blockData.checkpointNumber,
|
|
674
|
+
checkpointedCheckpointNumber: l2Tips.checkpointed.checkpoint.number,
|
|
543
675
|
archive: blockData.archive.root,
|
|
544
|
-
|
|
676
|
+
hasProposedCheckpoint,
|
|
677
|
+
proposedCheckpointData,
|
|
678
|
+
syncedL2Slot,
|
|
545
679
|
pendingChainValidationStatus,
|
|
546
680
|
};
|
|
547
681
|
}
|
|
@@ -550,20 +684,20 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
550
684
|
* Checks if we are the proposer for the next slot.
|
|
551
685
|
* @returns True if we can propose, and the proposer address (undefined if anyone can propose)
|
|
552
686
|
*/
|
|
553
|
-
protected async checkCanPropose(
|
|
687
|
+
protected async checkCanPropose(targetSlot: SlotNumber): Promise<[boolean, EthAddress | undefined]> {
|
|
554
688
|
let proposer: EthAddress | undefined;
|
|
555
689
|
|
|
556
690
|
try {
|
|
557
|
-
proposer = await this.epochCache.getProposerAttesterAddressInSlot(
|
|
691
|
+
proposer = await this.epochCache.getProposerAttesterAddressInSlot(targetSlot);
|
|
558
692
|
} catch (e) {
|
|
559
693
|
if (e instanceof NoCommitteeError) {
|
|
560
|
-
if (this.lastSlotForNoCommitteeWarning !==
|
|
561
|
-
this.lastSlotForNoCommitteeWarning =
|
|
562
|
-
this.log.warn(`Cannot propose at
|
|
694
|
+
if (this.lastSlotForNoCommitteeWarning !== targetSlot) {
|
|
695
|
+
this.lastSlotForNoCommitteeWarning = targetSlot;
|
|
696
|
+
this.log.warn(`Cannot propose at target slot ${targetSlot} since the committee does not exist on L1`);
|
|
563
697
|
}
|
|
564
698
|
return [false, undefined];
|
|
565
699
|
}
|
|
566
|
-
this.log.error(`Error getting proposer for slot ${
|
|
700
|
+
this.log.error(`Error getting proposer for target slot ${targetSlot}`, e);
|
|
567
701
|
return [false, undefined];
|
|
568
702
|
}
|
|
569
703
|
|
|
@@ -580,10 +714,18 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
580
714
|
const weAreProposer = validatorAddresses.some(addr => addr.equals(proposer));
|
|
581
715
|
|
|
582
716
|
if (!weAreProposer) {
|
|
583
|
-
this.log.debug(`Cannot propose at slot ${
|
|
717
|
+
this.log.debug(`Cannot propose at target slot ${targetSlot} since we are not a proposer`, {
|
|
718
|
+
targetSlot,
|
|
719
|
+
validatorAddresses,
|
|
720
|
+
proposer,
|
|
721
|
+
});
|
|
584
722
|
return [false, proposer];
|
|
585
723
|
}
|
|
586
724
|
|
|
725
|
+
this.log.info(`We are the proposer for pipeline slot ${targetSlot}`, {
|
|
726
|
+
targetSlot,
|
|
727
|
+
proposer,
|
|
728
|
+
});
|
|
587
729
|
return [true, proposer];
|
|
588
730
|
}
|
|
589
731
|
|
|
@@ -592,8 +734,8 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
592
734
|
* This allows the sequencer to participate in governance/slashing votes even when it cannot build blocks.
|
|
593
735
|
*/
|
|
594
736
|
@trackSpan('Seqeuencer.tryVoteWhenSyncFails', ({ slot }) => ({ [Attributes.SLOT_NUMBER]: slot }))
|
|
595
|
-
protected async tryVoteWhenSyncFails(args: { slot: SlotNumber; ts: bigint }): Promise<void> {
|
|
596
|
-
const { slot } = args;
|
|
737
|
+
protected async tryVoteWhenSyncFails(args: { slot: SlotNumber; targetSlot: SlotNumber; ts: bigint }): Promise<void> {
|
|
738
|
+
const { slot, targetSlot } = args;
|
|
597
739
|
|
|
598
740
|
// Prevent duplicate attempts in the same slot
|
|
599
741
|
if (this.lastSlotForFallbackVote === slot) {
|
|
@@ -621,7 +763,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
621
763
|
});
|
|
622
764
|
|
|
623
765
|
// Check if we're a proposer or proposal is open
|
|
624
|
-
const [canPropose, proposer] = await this.checkCanPropose(
|
|
766
|
+
const [canPropose, proposer] = await this.checkCanPropose(targetSlot);
|
|
625
767
|
if (!canPropose) {
|
|
626
768
|
this.log.trace(`Cannot vote in slot ${slot} since we are not a proposer`, { slot, proposer });
|
|
627
769
|
return;
|
|
@@ -638,9 +780,9 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
638
780
|
slot,
|
|
639
781
|
});
|
|
640
782
|
|
|
641
|
-
// Enqueue governance and slashing votes
|
|
783
|
+
// Enqueue governance and slashing votes (voter uses the target slot for L1 submission)
|
|
642
784
|
const voter = new CheckpointVoter(
|
|
643
|
-
|
|
785
|
+
targetSlot,
|
|
644
786
|
publisher,
|
|
645
787
|
attestorAddress,
|
|
646
788
|
this.validatorClient,
|
|
@@ -720,7 +862,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
720
862
|
syncedTo: SequencerSyncCheckResult,
|
|
721
863
|
currentSlot: SlotNumber,
|
|
722
864
|
): Promise<void> {
|
|
723
|
-
const { pendingChainValidationStatus,
|
|
865
|
+
const { pendingChainValidationStatus, syncedL2Slot } = syncedTo;
|
|
724
866
|
if (pendingChainValidationStatus.valid) {
|
|
725
867
|
return;
|
|
726
868
|
}
|
|
@@ -735,7 +877,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
735
877
|
|
|
736
878
|
const logData = {
|
|
737
879
|
invalidL1Timestamp: invalidCheckpointTimestamp,
|
|
738
|
-
|
|
880
|
+
syncedL2Slot,
|
|
739
881
|
invalidCheckpoint: pendingChainValidationStatus.checkpoint,
|
|
740
882
|
secondsBeforeInvalidatingBlockAsCommitteeMember,
|
|
741
883
|
secondsBeforeInvalidatingBlockAsNonCommitteeMember,
|
|
@@ -880,8 +1022,11 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
880
1022
|
type SequencerSyncCheckResult = {
|
|
881
1023
|
blockData?: BlockData;
|
|
882
1024
|
checkpointNumber: CheckpointNumber;
|
|
1025
|
+
checkpointedCheckpointNumber: CheckpointNumber;
|
|
883
1026
|
blockNumber: BlockNumber;
|
|
884
1027
|
archive: Fr;
|
|
885
|
-
|
|
1028
|
+
hasProposedCheckpoint: boolean;
|
|
1029
|
+
proposedCheckpointData?: ProposedCheckpointData;
|
|
1030
|
+
syncedL2Slot: SlotNumber;
|
|
886
1031
|
pendingChainValidationStatus: ValidateCheckpointResult;
|
|
887
1032
|
};
|