@aztec/sequencer-client 0.0.1-commit.3d8f95d → 0.0.1-commit.3f296a7d2
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 +15 -7
- package/dest/client/sequencer-client.d.ts.map +1 -1
- package/dest/client/sequencer-client.js +60 -26
- package/dest/config.d.ts +26 -6
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +44 -21
- package/dest/global_variable_builder/global_builder.d.ts +14 -10
- package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
- package/dest/global_variable_builder/global_builder.js +22 -21
- 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 -3
- package/dest/publisher/sequencer-publisher-factory.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher-factory.js +27 -2
- package/dest/publisher/sequencer-publisher.d.ts +33 -9
- package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher.js +332 -39
- package/dest/sequencer/checkpoint_proposal_job.d.ts +13 -7
- package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -1
- package/dest/sequencer/checkpoint_proposal_job.js +235 -137
- package/dest/sequencer/events.d.ts +2 -1
- package/dest/sequencer/events.d.ts.map +1 -1
- package/dest/sequencer/metrics.d.ts +21 -5
- package/dest/sequencer/metrics.d.ts.map +1 -1
- package/dest/sequencer/metrics.js +97 -15
- package/dest/sequencer/sequencer.d.ts +28 -15
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +93 -85
- package/dest/sequencer/timetable.d.ts +4 -3
- package/dest/sequencer/timetable.d.ts.map +1 -1
- package/dest/sequencer/timetable.js +6 -7
- 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 +11 -11
- package/dest/test/mock_checkpoint_builder.d.ts.map +1 -1
- package/dest/test/mock_checkpoint_builder.js +45 -34
- 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 +76 -23
- package/src/config.ts +56 -27
- package/src/global_variable_builder/global_builder.ts +23 -24
- 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 -6
- package/src/publisher/sequencer-publisher.ts +338 -53
- package/src/sequencer/checkpoint_proposal_job.ts +320 -148
- package/src/sequencer/events.ts +1 -1
- package/src/sequencer/metrics.ts +106 -18
- package/src/sequencer/sequencer.ts +127 -97
- package/src/sequencer/timetable.ts +7 -7
- package/src/sequencer/types.ts +1 -1
- package/src/test/index.ts +2 -4
- package/src/test/mock_checkpoint_builder.ts +63 -49
- package/src/test/utils.ts +5 -2
|
@@ -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 {
|
|
15
|
+
import type { BlockData, L2BlockSink, L2BlockSource, ValidateCheckpointResult } from '@aztec/stdlib/block';
|
|
16
16
|
import type { Checkpoint } from '@aztec/stdlib/checkpoint';
|
|
17
|
-
import {
|
|
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
|
|
|
@@ -75,14 +75,6 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
75
75
|
/** The maximum number of seconds that the sequencer can be into a slot to transition to a particular state. */
|
|
76
76
|
protected timetable!: SequencerTimetable;
|
|
77
77
|
|
|
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
78
|
/** Config for the sequencer */
|
|
87
79
|
protected config: ResolvedSequencerConfig = DefaultSequencerConfig;
|
|
88
80
|
|
|
@@ -118,7 +110,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
118
110
|
/** Updates sequencer config by the defined values and updates the timetable */
|
|
119
111
|
public updateConfig(config: Partial<SequencerConfig>) {
|
|
120
112
|
const filteredConfig = pickFromSchema(config, SequencerConfigSchema);
|
|
121
|
-
this.log.info(`Updated sequencer config`, omit(filteredConfig, '
|
|
113
|
+
this.log.info(`Updated sequencer config`, omit(filteredConfig, 'txPublicSetupAllowListExtend'));
|
|
122
114
|
this.config = merge(this.config, filteredConfig);
|
|
123
115
|
this.timetable = new SequencerTimetable(
|
|
124
116
|
{
|
|
@@ -134,10 +126,9 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
134
126
|
);
|
|
135
127
|
}
|
|
136
128
|
|
|
137
|
-
/** Initializes the sequencer (precomputes tables
|
|
138
|
-
public
|
|
129
|
+
/** Initializes the sequencer (precomputes tables). Takes about 3s. */
|
|
130
|
+
public init() {
|
|
139
131
|
getKzg();
|
|
140
|
-
this.publisher = (await this.publisherFactory.create(undefined)).publisher;
|
|
141
132
|
}
|
|
142
133
|
|
|
143
134
|
/** Starts the sequencer and moves to IDLE state. */
|
|
@@ -156,7 +147,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
156
147
|
public async stop(): Promise<void> {
|
|
157
148
|
this.log.info(`Stopping sequencer`);
|
|
158
149
|
this.setState(SequencerState.STOPPING, undefined, { force: true });
|
|
159
|
-
this.
|
|
150
|
+
await this.publisherFactory.stopAll();
|
|
160
151
|
await this.runningPromise?.stop();
|
|
161
152
|
this.setState(SequencerState.STOPPED, undefined, { force: true });
|
|
162
153
|
this.log.info('Stopped sequencer');
|
|
@@ -169,7 +160,6 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
169
160
|
} catch (err) {
|
|
170
161
|
this.emit('checkpoint-error', { error: err as Error });
|
|
171
162
|
if (err instanceof SequencerTooSlowError) {
|
|
172
|
-
// TODO(palla/mbps): Add missing states
|
|
173
163
|
// Log as warn only if we had to abort halfway through the block proposal
|
|
174
164
|
const logLvl = [SequencerState.INITIALIZING_CHECKPOINT, SequencerState.PROPOSER_CHECK].includes(
|
|
175
165
|
err.proposedState,
|
|
@@ -202,10 +192,18 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
202
192
|
@trackSpan('Sequencer.work')
|
|
203
193
|
protected async work() {
|
|
204
194
|
this.setState(SequencerState.SYNCHRONIZING, undefined);
|
|
205
|
-
const { slot, ts,
|
|
195
|
+
const { slot, ts, nowSeconds, epoch } = this.epochCache.getEpochAndSlotInNextL1Slot();
|
|
196
|
+
const { slot: targetSlot, epoch: targetEpoch } = this.epochCache.getTargetEpochAndSlotInNextL1Slot();
|
|
206
197
|
|
|
207
198
|
// Check if we are synced and it's our slot, grab a publisher, check previous block invalidation, etc
|
|
208
|
-
const checkpointProposalJob = await this.prepareCheckpointProposal(
|
|
199
|
+
const checkpointProposalJob = await this.prepareCheckpointProposal(
|
|
200
|
+
slot,
|
|
201
|
+
targetSlot,
|
|
202
|
+
epoch,
|
|
203
|
+
targetEpoch,
|
|
204
|
+
ts,
|
|
205
|
+
nowSeconds,
|
|
206
|
+
);
|
|
209
207
|
if (!checkpointProposalJob) {
|
|
210
208
|
return;
|
|
211
209
|
}
|
|
@@ -218,13 +216,13 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
218
216
|
this.lastCheckpointProposed = checkpoint;
|
|
219
217
|
}
|
|
220
218
|
|
|
221
|
-
// Log fee strategy comparison if on fisherman
|
|
219
|
+
// Log fee strategy comparison if on fisherman (uses target epoch since we mirror the proposer's perspective)
|
|
222
220
|
if (
|
|
223
221
|
this.config.fishermanMode &&
|
|
224
|
-
(this.lastEpochForStrategyComparison === undefined ||
|
|
222
|
+
(this.lastEpochForStrategyComparison === undefined || targetEpoch > this.lastEpochForStrategyComparison)
|
|
225
223
|
) {
|
|
226
|
-
this.logStrategyComparison(
|
|
227
|
-
this.lastEpochForStrategyComparison =
|
|
224
|
+
this.logStrategyComparison(targetEpoch, checkpointProposalJob.getPublisher());
|
|
225
|
+
this.lastEpochForStrategyComparison = targetEpoch;
|
|
228
226
|
}
|
|
229
227
|
|
|
230
228
|
return checkpoint;
|
|
@@ -237,43 +235,48 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
237
235
|
*/
|
|
238
236
|
@trackSpan('Sequencer.prepareCheckpointProposal')
|
|
239
237
|
private async prepareCheckpointProposal(
|
|
240
|
-
epoch: EpochNumber,
|
|
241
238
|
slot: SlotNumber,
|
|
239
|
+
targetSlot: SlotNumber,
|
|
240
|
+
epoch: EpochNumber,
|
|
241
|
+
targetEpoch: EpochNumber,
|
|
242
242
|
ts: bigint,
|
|
243
|
-
|
|
243
|
+
nowSeconds: bigint,
|
|
244
244
|
): Promise<CheckpointProposalJob | undefined> {
|
|
245
|
-
// Check we have not already processed this slot (cheapest check)
|
|
245
|
+
// Check we have not already processed this target slot (cheapest check)
|
|
246
246
|
// We only check this if enforce timetable is set, since we want to keep processing the same slot if we are not
|
|
247
247
|
// running against actual time (eg when we use sandbox-style automining)
|
|
248
248
|
if (
|
|
249
249
|
this.lastSlotForCheckpointProposalJob &&
|
|
250
|
-
this.lastSlotForCheckpointProposalJob >=
|
|
250
|
+
this.lastSlotForCheckpointProposalJob >= targetSlot &&
|
|
251
251
|
this.config.enforceTimeTable
|
|
252
252
|
) {
|
|
253
|
-
this.log.trace(`
|
|
253
|
+
this.log.trace(`Target slot ${targetSlot} has already been processed`);
|
|
254
254
|
return undefined;
|
|
255
255
|
}
|
|
256
256
|
|
|
257
|
-
// But if we have already proposed for this slot,
|
|
258
|
-
if (this.lastCheckpointProposed && this.lastCheckpointProposed.header.slotNumber >=
|
|
259
|
-
this.log.trace(
|
|
257
|
+
// But if we have already proposed for this slot, then we definitely have to skip it, automining or not
|
|
258
|
+
if (this.lastCheckpointProposed && this.lastCheckpointProposed.header.slotNumber >= targetSlot) {
|
|
259
|
+
this.log.trace(
|
|
260
|
+
`Slot ${targetSlot} has already been published as checkpoint ${this.lastCheckpointProposed.number}`,
|
|
261
|
+
);
|
|
260
262
|
return undefined;
|
|
261
263
|
}
|
|
262
264
|
|
|
263
265
|
// Check all components are synced to latest as seen by the archiver (queries all subsystems)
|
|
264
266
|
const syncedTo = await this.checkSync({ ts, slot });
|
|
265
267
|
if (!syncedTo) {
|
|
266
|
-
await this.tryVoteWhenSyncFails({ slot, ts });
|
|
268
|
+
await this.tryVoteWhenSyncFails({ slot, targetSlot, ts });
|
|
267
269
|
return undefined;
|
|
268
270
|
}
|
|
269
271
|
|
|
270
|
-
// If escape hatch is open for
|
|
272
|
+
// If escape hatch is open for the target epoch, do not start checkpoint proposal work and do not attempt invalidations.
|
|
271
273
|
// Still perform governance/slashing voting (as proposer) once per slot.
|
|
272
|
-
|
|
274
|
+
// When pipelining, we check the target epoch (slot+1's epoch) since that's the epoch we're building for.
|
|
275
|
+
const isEscapeHatchOpen = await this.epochCache.isEscapeHatchOpen(targetEpoch);
|
|
273
276
|
|
|
274
277
|
if (isEscapeHatchOpen) {
|
|
275
278
|
this.setState(SequencerState.PROPOSER_CHECK, slot);
|
|
276
|
-
const [canPropose, proposer] = await this.checkCanPropose(
|
|
279
|
+
const [canPropose, proposer] = await this.checkCanPropose(targetSlot);
|
|
277
280
|
if (canPropose) {
|
|
278
281
|
await this.tryVoteWhenEscapeHatchOpen({ slot, proposer });
|
|
279
282
|
} else {
|
|
@@ -290,18 +293,18 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
290
293
|
const checkpointNumber = CheckpointNumber(syncedTo.checkpointNumber + 1);
|
|
291
294
|
|
|
292
295
|
const logCtx = {
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
syncedToL2Slot: getSlotAtTimestamp(syncedTo.l1Timestamp, this.l1Constants),
|
|
296
|
+
nowSeconds,
|
|
297
|
+
syncedToL2Slot: syncedTo.syncedL2Slot,
|
|
296
298
|
slot,
|
|
299
|
+
targetSlot,
|
|
297
300
|
slotTs: ts,
|
|
298
301
|
checkpointNumber,
|
|
299
302
|
isPendingChainValid: pick(syncedTo.pendingChainValidationStatus, 'valid', 'reason', 'invalidIndex'),
|
|
300
303
|
};
|
|
301
304
|
|
|
302
|
-
// Check that we are a proposer for the
|
|
305
|
+
// Check that we are a proposer for the target slot.
|
|
303
306
|
this.setState(SequencerState.PROPOSER_CHECK, slot);
|
|
304
|
-
const [canPropose, proposer] = await this.checkCanPropose(
|
|
307
|
+
const [canPropose, proposer] = await this.checkCanPropose(targetSlot);
|
|
305
308
|
|
|
306
309
|
// If we are not a proposer check if we should invalidate an invalid checkpoint, and bail
|
|
307
310
|
if (!canPropose) {
|
|
@@ -309,13 +312,13 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
309
312
|
return undefined;
|
|
310
313
|
}
|
|
311
314
|
|
|
312
|
-
// Check that the slot is not taken by a block already (should never happen, since only us can propose for this slot)
|
|
313
|
-
if (syncedTo.
|
|
315
|
+
// Check that the target slot is not taken by a block already (should never happen, since only us can propose for this slot)
|
|
316
|
+
if (syncedTo.blockData && syncedTo.blockData.header.getSlot() >= targetSlot) {
|
|
314
317
|
this.log.warn(
|
|
315
|
-
`Cannot propose block at
|
|
316
|
-
{ ...logCtx, block: syncedTo.
|
|
318
|
+
`Cannot propose block at target slot ${targetSlot} since that slot was taken by block ${syncedTo.blockNumber}`,
|
|
319
|
+
{ ...logCtx, block: syncedTo.blockData.header.toInspect() },
|
|
317
320
|
);
|
|
318
|
-
this.metrics.
|
|
321
|
+
this.metrics.recordCheckpointPrecheckFailed('slot_already_taken');
|
|
319
322
|
return undefined;
|
|
320
323
|
}
|
|
321
324
|
|
|
@@ -326,7 +329,6 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
326
329
|
const proposerForPublisher = this.config.fishermanMode ? undefined : proposer;
|
|
327
330
|
const { attestorAddress, publisher } = await this.publisherFactory.create(proposerForPublisher);
|
|
328
331
|
this.log.verbose(`Created publisher at address ${publisher.getSenderAddress()} for attestor ${attestorAddress}`);
|
|
329
|
-
this.publisher = publisher;
|
|
330
332
|
|
|
331
333
|
// In fisherman mode, set the actual proposer's address for simulations
|
|
332
334
|
if (this.config.fishermanMode && proposer) {
|
|
@@ -337,13 +339,11 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
337
339
|
// Prepare invalidation request if the pending chain is invalid (returns undefined if no need)
|
|
338
340
|
const invalidateCheckpoint = await publisher.simulateInvalidateCheckpoint(syncedTo.pendingChainValidationStatus);
|
|
339
341
|
|
|
340
|
-
// Check with the rollup contract if we can indeed propose at the
|
|
342
|
+
// Check with the rollup contract if we can indeed propose at the target slot. This check should not fail
|
|
341
343
|
// if all the previous checks are good, but we do it just in case.
|
|
342
|
-
const canProposeCheck = await publisher.
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
invalidateCheckpoint,
|
|
346
|
-
);
|
|
344
|
+
const canProposeCheck = await publisher.canProposeAt(syncedTo.archive, proposer ?? EthAddress.ZERO, {
|
|
345
|
+
...invalidateCheckpoint,
|
|
346
|
+
});
|
|
347
347
|
|
|
348
348
|
if (canProposeCheck === undefined) {
|
|
349
349
|
this.log.warn(
|
|
@@ -351,17 +351,17 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
351
351
|
logCtx,
|
|
352
352
|
);
|
|
353
353
|
this.emit('proposer-rollup-check-failed', { reason: 'Rollup contract check failed', slot });
|
|
354
|
-
this.metrics.
|
|
354
|
+
this.metrics.recordCheckpointPrecheckFailed('rollup_contract_check_failed');
|
|
355
355
|
return undefined;
|
|
356
356
|
}
|
|
357
357
|
|
|
358
|
-
if (canProposeCheck.slot !==
|
|
358
|
+
if (canProposeCheck.slot !== targetSlot) {
|
|
359
359
|
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:
|
|
360
|
+
`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}.`,
|
|
361
|
+
{ ...logCtx, rollup: canProposeCheck, expectedSlot: targetSlot },
|
|
362
362
|
);
|
|
363
363
|
this.emit('proposer-rollup-check-failed', { reason: 'Slot mismatch', slot });
|
|
364
|
-
this.metrics.
|
|
364
|
+
this.metrics.recordCheckpointPrecheckFailed('slot_mismatch');
|
|
365
365
|
return undefined;
|
|
366
366
|
}
|
|
367
367
|
|
|
@@ -371,18 +371,28 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
371
371
|
{ ...logCtx, rollup: canProposeCheck, expectedSlot: slot },
|
|
372
372
|
);
|
|
373
373
|
this.emit('proposer-rollup-check-failed', { reason: 'Block mismatch', slot });
|
|
374
|
-
this.metrics.
|
|
374
|
+
this.metrics.recordCheckpointPrecheckFailed('block_number_mismatch');
|
|
375
375
|
return undefined;
|
|
376
376
|
}
|
|
377
377
|
|
|
378
|
-
this.lastSlotForCheckpointProposalJob =
|
|
379
|
-
|
|
380
|
-
this.
|
|
378
|
+
this.lastSlotForCheckpointProposalJob = targetSlot;
|
|
379
|
+
|
|
380
|
+
await this.p2pClient.prepareForSlot(targetSlot);
|
|
381
|
+
this.log.info(
|
|
382
|
+
`Preparing checkpoint proposal ${checkpointNumber} for target slot ${targetSlot} during wall-clock slot ${slot}`,
|
|
383
|
+
{
|
|
384
|
+
...logCtx,
|
|
385
|
+
proposer,
|
|
386
|
+
pipeliningEnabled: this.epochCache.isProposerPipeliningEnabled(),
|
|
387
|
+
},
|
|
388
|
+
);
|
|
381
389
|
|
|
382
390
|
// Create and return the checkpoint proposal job
|
|
383
391
|
return this.createCheckpointProposalJob(
|
|
384
|
-
epoch,
|
|
385
392
|
slot,
|
|
393
|
+
targetSlot,
|
|
394
|
+
epoch,
|
|
395
|
+
targetEpoch,
|
|
386
396
|
checkpointNumber,
|
|
387
397
|
syncedTo.blockNumber,
|
|
388
398
|
proposer,
|
|
@@ -393,8 +403,10 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
393
403
|
}
|
|
394
404
|
|
|
395
405
|
protected createCheckpointProposalJob(
|
|
396
|
-
epoch: EpochNumber,
|
|
397
406
|
slot: SlotNumber,
|
|
407
|
+
targetSlot: SlotNumber,
|
|
408
|
+
epoch: EpochNumber,
|
|
409
|
+
targetEpoch: EpochNumber,
|
|
398
410
|
checkpointNumber: CheckpointNumber,
|
|
399
411
|
syncedToBlockNumber: BlockNumber,
|
|
400
412
|
proposer: EthAddress | undefined,
|
|
@@ -403,8 +415,10 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
403
415
|
invalidateCheckpoint: InvalidateCheckpointRequest | undefined,
|
|
404
416
|
): CheckpointProposalJob {
|
|
405
417
|
return new CheckpointProposalJob(
|
|
406
|
-
epoch,
|
|
407
418
|
slot,
|
|
419
|
+
targetSlot,
|
|
420
|
+
epoch,
|
|
421
|
+
targetEpoch,
|
|
408
422
|
checkpointNumber,
|
|
409
423
|
syncedToBlockNumber,
|
|
410
424
|
proposer,
|
|
@@ -433,6 +447,13 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
433
447
|
);
|
|
434
448
|
}
|
|
435
449
|
|
|
450
|
+
/**
|
|
451
|
+
* Returns the current sequencer state.
|
|
452
|
+
*/
|
|
453
|
+
public getState(): SequencerState {
|
|
454
|
+
return this.state;
|
|
455
|
+
}
|
|
456
|
+
|
|
436
457
|
/**
|
|
437
458
|
* 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
459
|
* @param proposedState - The new state to transition to.
|
|
@@ -479,16 +500,15 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
479
500
|
* We don't check against the previous block submitted since it may have been reorg'd out.
|
|
480
501
|
*/
|
|
481
502
|
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 (
|
|
503
|
+
// Check that the archiver has fully synced the L2 slot before the one we want to propose in.
|
|
504
|
+
// The archiver reports sync progress via L1 block timestamps and synced checkpoint slots.
|
|
505
|
+
// See getSyncedL2SlotNumber for how missed L1 blocks are handled.
|
|
506
|
+
const syncedL2Slot = await this.l2BlockSource.getSyncedL2SlotNumber();
|
|
507
|
+
const { slot } = args;
|
|
508
|
+
if (syncedL2Slot === undefined || syncedL2Slot + 1 < slot) {
|
|
488
509
|
this.log.debug(`Cannot propose block at next L2 slot ${slot} due to pending sync from L1`, {
|
|
489
510
|
slot,
|
|
490
|
-
|
|
491
|
-
l1Timestamp,
|
|
511
|
+
syncedL2Slot,
|
|
492
512
|
});
|
|
493
513
|
return undefined;
|
|
494
514
|
}
|
|
@@ -528,24 +548,24 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
528
548
|
checkpointNumber: CheckpointNumber.ZERO,
|
|
529
549
|
blockNumber: BlockNumber.ZERO,
|
|
530
550
|
archive,
|
|
531
|
-
|
|
551
|
+
syncedL2Slot,
|
|
532
552
|
pendingChainValidationStatus,
|
|
533
553
|
};
|
|
534
554
|
}
|
|
535
555
|
|
|
536
|
-
const
|
|
537
|
-
if (!
|
|
556
|
+
const blockData = await this.l2BlockSource.getBlockData(blockNumber);
|
|
557
|
+
if (!blockData) {
|
|
538
558
|
// 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`);
|
|
559
|
+
this.log.error(`Failed to get L2 block data ${blockNumber} from the archiver with all components in sync`);
|
|
540
560
|
return undefined;
|
|
541
561
|
}
|
|
542
562
|
|
|
543
563
|
return {
|
|
544
|
-
|
|
545
|
-
blockNumber:
|
|
546
|
-
checkpointNumber:
|
|
547
|
-
archive:
|
|
548
|
-
|
|
564
|
+
blockData,
|
|
565
|
+
blockNumber: blockData.header.getBlockNumber(),
|
|
566
|
+
checkpointNumber: blockData.checkpointNumber,
|
|
567
|
+
archive: blockData.archive.root,
|
|
568
|
+
syncedL2Slot,
|
|
549
569
|
pendingChainValidationStatus,
|
|
550
570
|
};
|
|
551
571
|
}
|
|
@@ -554,20 +574,20 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
554
574
|
* Checks if we are the proposer for the next slot.
|
|
555
575
|
* @returns True if we can propose, and the proposer address (undefined if anyone can propose)
|
|
556
576
|
*/
|
|
557
|
-
protected async checkCanPropose(
|
|
577
|
+
protected async checkCanPropose(targetSlot: SlotNumber): Promise<[boolean, EthAddress | undefined]> {
|
|
558
578
|
let proposer: EthAddress | undefined;
|
|
559
579
|
|
|
560
580
|
try {
|
|
561
|
-
proposer = await this.epochCache.getProposerAttesterAddressInSlot(
|
|
581
|
+
proposer = await this.epochCache.getProposerAttesterAddressInSlot(targetSlot);
|
|
562
582
|
} catch (e) {
|
|
563
583
|
if (e instanceof NoCommitteeError) {
|
|
564
|
-
if (this.lastSlotForNoCommitteeWarning !==
|
|
565
|
-
this.lastSlotForNoCommitteeWarning =
|
|
566
|
-
this.log.warn(`Cannot propose at
|
|
584
|
+
if (this.lastSlotForNoCommitteeWarning !== targetSlot) {
|
|
585
|
+
this.lastSlotForNoCommitteeWarning = targetSlot;
|
|
586
|
+
this.log.warn(`Cannot propose at target slot ${targetSlot} since the committee does not exist on L1`);
|
|
567
587
|
}
|
|
568
588
|
return [false, undefined];
|
|
569
589
|
}
|
|
570
|
-
this.log.error(`Error getting proposer for slot ${
|
|
590
|
+
this.log.error(`Error getting proposer for target slot ${targetSlot}`, e);
|
|
571
591
|
return [false, undefined];
|
|
572
592
|
}
|
|
573
593
|
|
|
@@ -584,10 +604,15 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
584
604
|
const weAreProposer = validatorAddresses.some(addr => addr.equals(proposer));
|
|
585
605
|
|
|
586
606
|
if (!weAreProposer) {
|
|
587
|
-
this.log.debug(`Cannot propose at slot ${
|
|
607
|
+
this.log.debug(`Cannot propose at target slot ${targetSlot} since we are not a proposer`, {
|
|
608
|
+
targetSlot,
|
|
609
|
+
validatorAddresses,
|
|
610
|
+
proposer,
|
|
611
|
+
});
|
|
588
612
|
return [false, proposer];
|
|
589
613
|
}
|
|
590
614
|
|
|
615
|
+
this.log.debug(`We are the proposer for target slot ${targetSlot}`, { targetSlot, proposer });
|
|
591
616
|
return [true, proposer];
|
|
592
617
|
}
|
|
593
618
|
|
|
@@ -596,8 +621,8 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
596
621
|
* This allows the sequencer to participate in governance/slashing votes even when it cannot build blocks.
|
|
597
622
|
*/
|
|
598
623
|
@trackSpan('Seqeuencer.tryVoteWhenSyncFails', ({ slot }) => ({ [Attributes.SLOT_NUMBER]: slot }))
|
|
599
|
-
protected async tryVoteWhenSyncFails(args: { slot: SlotNumber; ts: bigint }): Promise<void> {
|
|
600
|
-
const { slot } = args;
|
|
624
|
+
protected async tryVoteWhenSyncFails(args: { slot: SlotNumber; targetSlot: SlotNumber; ts: bigint }): Promise<void> {
|
|
625
|
+
const { slot, targetSlot } = args;
|
|
601
626
|
|
|
602
627
|
// Prevent duplicate attempts in the same slot
|
|
603
628
|
if (this.lastSlotForFallbackVote === slot) {
|
|
@@ -625,7 +650,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
625
650
|
});
|
|
626
651
|
|
|
627
652
|
// Check if we're a proposer or proposal is open
|
|
628
|
-
const [canPropose, proposer] = await this.checkCanPropose(
|
|
653
|
+
const [canPropose, proposer] = await this.checkCanPropose(targetSlot);
|
|
629
654
|
if (!canPropose) {
|
|
630
655
|
this.log.trace(`Cannot vote in slot ${slot} since we are not a proposer`, { slot, proposer });
|
|
631
656
|
return;
|
|
@@ -642,9 +667,9 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
642
667
|
slot,
|
|
643
668
|
});
|
|
644
669
|
|
|
645
|
-
// Enqueue governance and slashing votes
|
|
670
|
+
// Enqueue governance and slashing votes (voter uses the target slot for L1 submission)
|
|
646
671
|
const voter = new CheckpointVoter(
|
|
647
|
-
|
|
672
|
+
targetSlot,
|
|
648
673
|
publisher,
|
|
649
674
|
attestorAddress,
|
|
650
675
|
this.validatorClient,
|
|
@@ -724,7 +749,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
724
749
|
syncedTo: SequencerSyncCheckResult,
|
|
725
750
|
currentSlot: SlotNumber,
|
|
726
751
|
): Promise<void> {
|
|
727
|
-
const { pendingChainValidationStatus,
|
|
752
|
+
const { pendingChainValidationStatus, syncedL2Slot } = syncedTo;
|
|
728
753
|
if (pendingChainValidationStatus.valid) {
|
|
729
754
|
return;
|
|
730
755
|
}
|
|
@@ -739,7 +764,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
739
764
|
|
|
740
765
|
const logData = {
|
|
741
766
|
invalidL1Timestamp: invalidCheckpointTimestamp,
|
|
742
|
-
|
|
767
|
+
syncedL2Slot,
|
|
743
768
|
invalidCheckpoint: pendingChainValidationStatus.checkpoint,
|
|
744
769
|
secondsBeforeInvalidatingBlockAsCommitteeMember,
|
|
745
770
|
secondsBeforeInvalidatingBlockAsNonCommitteeMember,
|
|
@@ -867,6 +892,11 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
867
892
|
return this.validatorClient?.getValidatorAddresses();
|
|
868
893
|
}
|
|
869
894
|
|
|
895
|
+
/** Updates the publisher factory's node keystore adapter after a keystore reload. */
|
|
896
|
+
public updatePublisherNodeKeyStore(adapter: NodeKeystoreAdapter): void {
|
|
897
|
+
this.publisherFactory.updateNodeKeyStore(adapter);
|
|
898
|
+
}
|
|
899
|
+
|
|
870
900
|
public getConfig() {
|
|
871
901
|
return this.config;
|
|
872
902
|
}
|
|
@@ -877,10 +907,10 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
877
907
|
}
|
|
878
908
|
|
|
879
909
|
type SequencerSyncCheckResult = {
|
|
880
|
-
|
|
910
|
+
blockData?: BlockData;
|
|
881
911
|
checkpointNumber: CheckpointNumber;
|
|
882
912
|
blockNumber: BlockNumber;
|
|
883
913
|
archive: Fr;
|
|
884
|
-
|
|
914
|
+
syncedL2Slot: SlotNumber;
|
|
885
915
|
pendingChainValidationStatus: ValidateCheckpointResult;
|
|
886
916
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { Logger } from '@aztec/foundation/log';
|
|
2
2
|
import {
|
|
3
3
|
CHECKPOINT_ASSEMBLE_TIME,
|
|
4
4
|
CHECKPOINT_INITIALIZATION_TIME,
|
|
@@ -80,7 +80,7 @@ export class SequencerTimetable {
|
|
|
80
80
|
enforce: boolean;
|
|
81
81
|
},
|
|
82
82
|
private readonly metrics?: SequencerMetrics,
|
|
83
|
-
private readonly log
|
|
83
|
+
private readonly log?: Logger,
|
|
84
84
|
) {
|
|
85
85
|
this.ethereumSlotDuration = opts.ethereumSlotDuration;
|
|
86
86
|
this.aztecSlotDuration = opts.aztecSlotDuration;
|
|
@@ -132,7 +132,7 @@ export class SequencerTimetable {
|
|
|
132
132
|
const initializeDeadline = this.aztecSlotDuration - minWorkToDo;
|
|
133
133
|
this.initializeDeadline = initializeDeadline;
|
|
134
134
|
|
|
135
|
-
this.log
|
|
135
|
+
this.log?.info(
|
|
136
136
|
`Sequencer timetable initialized with ${this.maxNumberOfBlocks} blocks per slot (${this.enforce ? 'enforced' : 'not enforced'})`,
|
|
137
137
|
{
|
|
138
138
|
ethereumSlotDuration: this.ethereumSlotDuration,
|
|
@@ -206,7 +206,7 @@ export class SequencerTimetable {
|
|
|
206
206
|
}
|
|
207
207
|
|
|
208
208
|
this.metrics?.recordStateTransitionBufferMs(Math.floor(bufferSeconds * 1000), newState);
|
|
209
|
-
this.log
|
|
209
|
+
this.log?.trace(`Enough time to transition to ${newState}`, { maxAllowedTime, secondsIntoSlot });
|
|
210
210
|
}
|
|
211
211
|
|
|
212
212
|
/**
|
|
@@ -242,7 +242,7 @@ export class SequencerTimetable {
|
|
|
242
242
|
const canStart = available >= this.minExecutionTime;
|
|
243
243
|
const deadline = secondsIntoSlot + available;
|
|
244
244
|
|
|
245
|
-
this.log
|
|
245
|
+
this.log?.verbose(
|
|
246
246
|
`${canStart ? 'Can' : 'Cannot'} start single-block checkpoint at ${secondsIntoSlot}s into slot`,
|
|
247
247
|
{ secondsIntoSlot, maxAllowed, available, deadline },
|
|
248
248
|
);
|
|
@@ -262,7 +262,7 @@ export class SequencerTimetable {
|
|
|
262
262
|
// Found an available sub-slot! Is this the last one?
|
|
263
263
|
const isLastBlock = subSlot === this.maxNumberOfBlocks;
|
|
264
264
|
|
|
265
|
-
this.log
|
|
265
|
+
this.log?.verbose(
|
|
266
266
|
`Can start ${isLastBlock ? 'last block' : 'block'} in sub-slot ${subSlot} with deadline ${deadline}s`,
|
|
267
267
|
{ secondsIntoSlot, deadline, timeUntilDeadline, subSlot, maxBlocks: this.maxNumberOfBlocks },
|
|
268
268
|
);
|
|
@@ -272,7 +272,7 @@ export class SequencerTimetable {
|
|
|
272
272
|
}
|
|
273
273
|
|
|
274
274
|
// No sub-slots available with enough time
|
|
275
|
-
this.log
|
|
275
|
+
this.log?.verbose(`No time left to start any more blocks`, {
|
|
276
276
|
secondsIntoSlot,
|
|
277
277
|
maxBlocks: this.maxNumberOfBlocks,
|
|
278
278
|
initializationOffset: this.initializationOffset,
|
package/src/sequencer/types.ts
CHANGED
|
@@ -2,5 +2,5 @@ import type { L1RollupConstants } from '@aztec/stdlib/epoch-helpers';
|
|
|
2
2
|
|
|
3
3
|
export type SequencerRollupConstants = Pick<
|
|
4
4
|
L1RollupConstants,
|
|
5
|
-
'ethereumSlotDuration' | 'l1GenesisTime' | 'slotDuration'
|
|
5
|
+
'ethereumSlotDuration' | 'l1GenesisTime' | 'slotDuration' | 'rollupManaLimit'
|
|
6
6
|
>;
|
package/src/test/index.ts
CHANGED
|
@@ -1,18 +1,16 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { L1TxUtils } from '@aztec/ethereum/l1-tx-utils';
|
|
2
2
|
import type { PublisherManager } from '@aztec/ethereum/publisher-manager';
|
|
3
3
|
import type { PublicProcessorFactory } from '@aztec/simulator/server';
|
|
4
4
|
import type { FullNodeCheckpointsBuilder, ValidatorClient } from '@aztec/validator-client';
|
|
5
5
|
|
|
6
6
|
import { SequencerClient } from '../client/sequencer-client.js';
|
|
7
7
|
import type { SequencerPublisherFactory } from '../publisher/sequencer-publisher-factory.js';
|
|
8
|
-
import type { SequencerPublisher } from '../publisher/sequencer-publisher.js';
|
|
9
8
|
import { Sequencer } from '../sequencer/sequencer.js';
|
|
10
9
|
import type { SequencerTimetable } from '../sequencer/timetable.js';
|
|
11
10
|
|
|
12
11
|
class TestSequencer_ extends Sequencer {
|
|
13
12
|
declare public publicProcessorFactory: PublicProcessorFactory;
|
|
14
13
|
declare public timetable: SequencerTimetable;
|
|
15
|
-
declare public publisher: SequencerPublisher;
|
|
16
14
|
declare public publisherFactory: SequencerPublisherFactory;
|
|
17
15
|
declare public validatorClient: ValidatorClient;
|
|
18
16
|
declare public checkpointsBuilder: FullNodeCheckpointsBuilder;
|
|
@@ -22,7 +20,7 @@ export type TestSequencer = TestSequencer_;
|
|
|
22
20
|
|
|
23
21
|
class TestSequencerClient_ extends SequencerClient {
|
|
24
22
|
declare public sequencer: TestSequencer;
|
|
25
|
-
declare public publisherManager: PublisherManager<
|
|
23
|
+
declare public publisherManager: PublisherManager<L1TxUtils>;
|
|
26
24
|
}
|
|
27
25
|
|
|
28
26
|
export type TestSequencerClient = TestSequencerClient_;
|