@aztec/sequencer-client 0.0.1-commit.1142ef1 → 0.0.1-commit.125b3452
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 +24 -7
- package/dest/client/sequencer-client.d.ts.map +1 -1
- package/dest/client/sequencer-client.js +101 -16
- package/dest/config.d.ts +25 -6
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +49 -30
- package/dest/global_variable_builder/global_builder.d.ts +2 -4
- package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
- package/dest/global_variable_builder/global_builder.js +7 -6
- package/dest/index.d.ts +2 -2
- package/dest/index.d.ts.map +1 -1
- package/dest/index.js +1 -1
- package/dest/publisher/config.d.ts +35 -17
- package/dest/publisher/config.d.ts.map +1 -1
- package/dest/publisher/config.js +106 -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-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 +26 -8
- package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher.js +349 -49
- package/dest/sequencer/checkpoint_proposal_job.d.ts +33 -10
- package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -1
- package/dest/sequencer/checkpoint_proposal_job.js +217 -66
- package/dest/sequencer/checkpoint_voter.d.ts +3 -2
- package/dest/sequencer/checkpoint_voter.d.ts.map +1 -1
- package/dest/sequencer/checkpoint_voter.js +34 -10
- package/dest/sequencer/index.d.ts +1 -2
- package/dest/sequencer/index.d.ts.map +1 -1
- package/dest/sequencer/index.js +0 -1
- package/dest/sequencer/metrics.d.ts +17 -5
- package/dest/sequencer/metrics.d.ts.map +1 -1
- package/dest/sequencer/metrics.js +111 -30
- package/dest/sequencer/sequencer.d.ts +42 -19
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +107 -50
- package/dest/sequencer/timetable.d.ts +4 -6
- package/dest/sequencer/timetable.d.ts.map +1 -1
- package/dest/sequencer/timetable.js +7 -11
- 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 +27 -19
- package/dest/test/mock_checkpoint_builder.d.ts.map +1 -1
- package/dest/test/mock_checkpoint_builder.js +67 -38
- package/dest/test/utils.d.ts +8 -8
- package/dest/test/utils.d.ts.map +1 -1
- package/dest/test/utils.js +12 -11
- package/package.json +29 -28
- package/src/client/sequencer-client.ts +135 -18
- package/src/config.ts +65 -41
- package/src/global_variable_builder/global_builder.ts +6 -5
- package/src/index.ts +1 -6
- package/src/publisher/config.ts +121 -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-metrics.ts +7 -3
- package/src/publisher/sequencer-publisher.ts +344 -61
- package/src/sequencer/checkpoint_proposal_job.ts +306 -81
- package/src/sequencer/checkpoint_voter.ts +32 -7
- package/src/sequencer/index.ts +0 -1
- package/src/sequencer/metrics.ts +124 -32
- package/src/sequencer/sequencer.ts +131 -53
- package/src/sequencer/timetable.ts +13 -12
- package/src/sequencer/types.ts +1 -1
- package/src/test/index.ts +2 -4
- package/src/test/mock_checkpoint_builder.ts +122 -78
- package/src/test/utils.ts +24 -14
- package/dest/sequencer/block_builder.d.ts +0 -26
- package/dest/sequencer/block_builder.d.ts.map +0 -1
- package/dest/sequencer/block_builder.js +0 -129
- package/src/sequencer/block_builder.ts +0 -216
|
@@ -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
|
|
|
@@ -57,8 +57,11 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
57
57
|
private state = SequencerState.STOPPED;
|
|
58
58
|
private metrics: SequencerMetrics;
|
|
59
59
|
|
|
60
|
-
/** The last slot for which we attempted to
|
|
61
|
-
private
|
|
60
|
+
/** The last slot for which we attempted to perform our voting duties with degraded block production */
|
|
61
|
+
private lastSlotForFallbackVote: SlotNumber | undefined;
|
|
62
|
+
|
|
63
|
+
/** The last slot for which we logged "no committee" warning, to avoid spam */
|
|
64
|
+
private lastSlotForNoCommitteeWarning: SlotNumber | undefined;
|
|
62
65
|
|
|
63
66
|
/** The last slot for which we triggered a checkpoint proposal job, to prevent duplicate attempts. */
|
|
64
67
|
private lastSlotForCheckpointProposalJob: SlotNumber | undefined;
|
|
@@ -72,14 +75,6 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
72
75
|
/** The maximum number of seconds that the sequencer can be into a slot to transition to a particular state. */
|
|
73
76
|
protected timetable!: SequencerTimetable;
|
|
74
77
|
|
|
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
78
|
/** Config for the sequencer */
|
|
84
79
|
protected config: ResolvedSequencerConfig = DefaultSequencerConfig;
|
|
85
80
|
|
|
@@ -115,7 +110,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
115
110
|
/** Updates sequencer config by the defined values and updates the timetable */
|
|
116
111
|
public updateConfig(config: Partial<SequencerConfig>) {
|
|
117
112
|
const filteredConfig = pickFromSchema(config, SequencerConfigSchema);
|
|
118
|
-
this.log.info(`Updated sequencer config`, omit(filteredConfig, '
|
|
113
|
+
this.log.info(`Updated sequencer config`, omit(filteredConfig, 'txPublicSetupAllowListExtend'));
|
|
119
114
|
this.config = merge(this.config, filteredConfig);
|
|
120
115
|
this.timetable = new SequencerTimetable(
|
|
121
116
|
{
|
|
@@ -131,10 +126,9 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
131
126
|
);
|
|
132
127
|
}
|
|
133
128
|
|
|
134
|
-
/** Initializes the sequencer (precomputes tables
|
|
135
|
-
public
|
|
129
|
+
/** Initializes the sequencer (precomputes tables). Takes about 3s. */
|
|
130
|
+
public init() {
|
|
136
131
|
getKzg();
|
|
137
|
-
this.publisher = (await this.publisherFactory.create(undefined)).publisher;
|
|
138
132
|
}
|
|
139
133
|
|
|
140
134
|
/** Starts the sequencer and moves to IDLE state. */
|
|
@@ -153,7 +147,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
153
147
|
public async stop(): Promise<void> {
|
|
154
148
|
this.log.info(`Stopping sequencer`);
|
|
155
149
|
this.setState(SequencerState.STOPPING, undefined, { force: true });
|
|
156
|
-
this.
|
|
150
|
+
this.publisherFactory.interruptAll();
|
|
157
151
|
await this.runningPromise?.stop();
|
|
158
152
|
this.setState(SequencerState.STOPPED, undefined, { force: true });
|
|
159
153
|
this.log.info('Stopped sequencer');
|
|
@@ -166,7 +160,6 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
166
160
|
} catch (err) {
|
|
167
161
|
this.emit('checkpoint-error', { error: err as Error });
|
|
168
162
|
if (err instanceof SequencerTooSlowError) {
|
|
169
|
-
// TODO(palla/mbps): Add missing states
|
|
170
163
|
// Log as warn only if we had to abort halfway through the block proposal
|
|
171
164
|
const logLvl = [SequencerState.INITIALIZING_CHECKPOINT, SequencerState.PROPOSER_CHECK].includes(
|
|
172
165
|
err.proposedState,
|
|
@@ -202,7 +195,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
202
195
|
const { slot, ts, now, epoch } = this.epochCache.getEpochAndSlotInNextL1Slot();
|
|
203
196
|
|
|
204
197
|
// Check if we are synced and it's our slot, grab a publisher, check previous block invalidation, etc
|
|
205
|
-
const checkpointProposalJob = await this.prepareCheckpointProposal(slot, ts, now);
|
|
198
|
+
const checkpointProposalJob = await this.prepareCheckpointProposal(epoch, slot, ts, now);
|
|
206
199
|
if (!checkpointProposalJob) {
|
|
207
200
|
return;
|
|
208
201
|
}
|
|
@@ -234,6 +227,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
234
227
|
*/
|
|
235
228
|
@trackSpan('Sequencer.prepareCheckpointProposal')
|
|
236
229
|
private async prepareCheckpointProposal(
|
|
230
|
+
epoch: EpochNumber,
|
|
237
231
|
slot: SlotNumber,
|
|
238
232
|
ts: bigint,
|
|
239
233
|
now: bigint,
|
|
@@ -263,13 +257,31 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
263
257
|
return undefined;
|
|
264
258
|
}
|
|
265
259
|
|
|
260
|
+
// If escape hatch is open for this epoch, do not start checkpoint proposal work and do not attempt invalidations.
|
|
261
|
+
// Still perform governance/slashing voting (as proposer) once per slot.
|
|
262
|
+
const isEscapeHatchOpen = await this.epochCache.isEscapeHatchOpen(epoch);
|
|
263
|
+
|
|
264
|
+
if (isEscapeHatchOpen) {
|
|
265
|
+
this.setState(SequencerState.PROPOSER_CHECK, slot);
|
|
266
|
+
const [canPropose, proposer] = await this.checkCanPropose(slot);
|
|
267
|
+
if (canPropose) {
|
|
268
|
+
await this.tryVoteWhenEscapeHatchOpen({ slot, proposer });
|
|
269
|
+
} else {
|
|
270
|
+
this.log.trace(`Escape hatch open but we are not proposer, skipping vote-only actions`, {
|
|
271
|
+
slot,
|
|
272
|
+
epoch,
|
|
273
|
+
proposer,
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
return undefined;
|
|
277
|
+
}
|
|
278
|
+
|
|
266
279
|
// Next checkpoint follows from the last synced one
|
|
267
280
|
const checkpointNumber = CheckpointNumber(syncedTo.checkpointNumber + 1);
|
|
268
281
|
|
|
269
282
|
const logCtx = {
|
|
270
283
|
now,
|
|
271
|
-
|
|
272
|
-
syncedToL2Slot: getSlotAtTimestamp(syncedTo.l1Timestamp, this.l1Constants),
|
|
284
|
+
syncedToL2Slot: syncedTo.syncedL2Slot,
|
|
273
285
|
slot,
|
|
274
286
|
slotTs: ts,
|
|
275
287
|
checkpointNumber,
|
|
@@ -287,12 +299,12 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
287
299
|
}
|
|
288
300
|
|
|
289
301
|
// Check that the slot is not taken by a block already (should never happen, since only us can propose for this slot)
|
|
290
|
-
if (syncedTo.
|
|
302
|
+
if (syncedTo.blockData && syncedTo.blockData.header.getSlot() >= slot) {
|
|
291
303
|
this.log.warn(
|
|
292
304
|
`Cannot propose block at next L2 slot ${slot} since that slot was taken by block ${syncedTo.blockNumber}`,
|
|
293
|
-
{ ...logCtx, block: syncedTo.
|
|
305
|
+
{ ...logCtx, block: syncedTo.blockData.header.toInspect() },
|
|
294
306
|
);
|
|
295
|
-
this.metrics.
|
|
307
|
+
this.metrics.recordCheckpointPrecheckFailed('slot_already_taken');
|
|
296
308
|
return undefined;
|
|
297
309
|
}
|
|
298
310
|
|
|
@@ -303,7 +315,6 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
303
315
|
const proposerForPublisher = this.config.fishermanMode ? undefined : proposer;
|
|
304
316
|
const { attestorAddress, publisher } = await this.publisherFactory.create(proposerForPublisher);
|
|
305
317
|
this.log.verbose(`Created publisher at address ${publisher.getSenderAddress()} for attestor ${attestorAddress}`);
|
|
306
|
-
this.publisher = publisher;
|
|
307
318
|
|
|
308
319
|
// In fisherman mode, set the actual proposer's address for simulations
|
|
309
320
|
if (this.config.fishermanMode && proposer) {
|
|
@@ -328,7 +339,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
328
339
|
logCtx,
|
|
329
340
|
);
|
|
330
341
|
this.emit('proposer-rollup-check-failed', { reason: 'Rollup contract check failed', slot });
|
|
331
|
-
this.metrics.
|
|
342
|
+
this.metrics.recordCheckpointPrecheckFailed('rollup_contract_check_failed');
|
|
332
343
|
return undefined;
|
|
333
344
|
}
|
|
334
345
|
|
|
@@ -338,7 +349,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
338
349
|
{ ...logCtx, rollup: canProposeCheck, expectedSlot: slot },
|
|
339
350
|
);
|
|
340
351
|
this.emit('proposer-rollup-check-failed', { reason: 'Slot mismatch', slot });
|
|
341
|
-
this.metrics.
|
|
352
|
+
this.metrics.recordCheckpointPrecheckFailed('slot_mismatch');
|
|
342
353
|
return undefined;
|
|
343
354
|
}
|
|
344
355
|
|
|
@@ -348,15 +359,17 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
348
359
|
{ ...logCtx, rollup: canProposeCheck, expectedSlot: slot },
|
|
349
360
|
);
|
|
350
361
|
this.emit('proposer-rollup-check-failed', { reason: 'Block mismatch', slot });
|
|
351
|
-
this.metrics.
|
|
362
|
+
this.metrics.recordCheckpointPrecheckFailed('block_number_mismatch');
|
|
352
363
|
return undefined;
|
|
353
364
|
}
|
|
354
365
|
|
|
355
366
|
this.lastSlotForCheckpointProposalJob = slot;
|
|
367
|
+
await this.p2pClient.prepareForSlot(slot);
|
|
356
368
|
this.log.info(`Preparing checkpoint proposal ${checkpointNumber} at slot ${slot}`, { ...logCtx, proposer });
|
|
357
369
|
|
|
358
370
|
// Create and return the checkpoint proposal job
|
|
359
371
|
return this.createCheckpointProposalJob(
|
|
372
|
+
epoch,
|
|
360
373
|
slot,
|
|
361
374
|
checkpointNumber,
|
|
362
375
|
syncedTo.blockNumber,
|
|
@@ -368,6 +381,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
368
381
|
}
|
|
369
382
|
|
|
370
383
|
protected createCheckpointProposalJob(
|
|
384
|
+
epoch: EpochNumber,
|
|
371
385
|
slot: SlotNumber,
|
|
372
386
|
checkpointNumber: CheckpointNumber,
|
|
373
387
|
syncedToBlockNumber: BlockNumber,
|
|
@@ -377,6 +391,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
377
391
|
invalidateCheckpoint: InvalidateCheckpointRequest | undefined,
|
|
378
392
|
): CheckpointProposalJob {
|
|
379
393
|
return new CheckpointProposalJob(
|
|
394
|
+
epoch,
|
|
380
395
|
slot,
|
|
381
396
|
checkpointNumber,
|
|
382
397
|
syncedToBlockNumber,
|
|
@@ -389,6 +404,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
389
404
|
this.p2pClient,
|
|
390
405
|
this.worldState,
|
|
391
406
|
this.l1ToL2MessageSource,
|
|
407
|
+
this.l2BlockSource,
|
|
392
408
|
this.checkpointsBuilder,
|
|
393
409
|
this.l2BlockSource,
|
|
394
410
|
this.l1Constants,
|
|
@@ -400,11 +416,18 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
400
416
|
this.metrics,
|
|
401
417
|
this,
|
|
402
418
|
this.setState.bind(this),
|
|
403
|
-
this.log,
|
|
404
419
|
this.tracer,
|
|
420
|
+
this.log.getBindings(),
|
|
405
421
|
);
|
|
406
422
|
}
|
|
407
423
|
|
|
424
|
+
/**
|
|
425
|
+
* Returns the current sequencer state.
|
|
426
|
+
*/
|
|
427
|
+
public getState(): SequencerState {
|
|
428
|
+
return this.state;
|
|
429
|
+
}
|
|
430
|
+
|
|
408
431
|
/**
|
|
409
432
|
* Internal helper for setting the sequencer state and checks if we have enough time left in the slot to transition to the new state.
|
|
410
433
|
* @param proposedState - The new state to transition to.
|
|
@@ -451,16 +474,15 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
451
474
|
* We don't check against the previous block submitted since it may have been reorg'd out.
|
|
452
475
|
*/
|
|
453
476
|
protected async checkSync(args: { ts: bigint; slot: SlotNumber }): Promise<SequencerSyncCheckResult | undefined> {
|
|
454
|
-
// Check that the archiver
|
|
477
|
+
// Check that the archiver has fully synced the L2 slot before the one we want to propose in.
|
|
455
478
|
// TODO(#14766): Archiver reports L1 timestamp based on L1 blocks seen, which means that a missed L1 block will
|
|
456
479
|
// cause the archiver L1 timestamp to fall behind, and cause this sequencer to start processing one L1 slot later.
|
|
457
|
-
const
|
|
458
|
-
const { slot
|
|
459
|
-
if (
|
|
480
|
+
const syncedL2Slot = await this.l2BlockSource.getSyncedL2SlotNumber();
|
|
481
|
+
const { slot } = args;
|
|
482
|
+
if (syncedL2Slot === undefined || syncedL2Slot + 1 < slot) {
|
|
460
483
|
this.log.debug(`Cannot propose block at next L2 slot ${slot} due to pending sync from L1`, {
|
|
461
484
|
slot,
|
|
462
|
-
|
|
463
|
-
l1Timestamp,
|
|
485
|
+
syncedL2Slot,
|
|
464
486
|
});
|
|
465
487
|
return undefined;
|
|
466
488
|
}
|
|
@@ -500,24 +522,24 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
500
522
|
checkpointNumber: CheckpointNumber.ZERO,
|
|
501
523
|
blockNumber: BlockNumber.ZERO,
|
|
502
524
|
archive,
|
|
503
|
-
|
|
525
|
+
syncedL2Slot,
|
|
504
526
|
pendingChainValidationStatus,
|
|
505
527
|
};
|
|
506
528
|
}
|
|
507
529
|
|
|
508
|
-
const
|
|
509
|
-
if (!
|
|
530
|
+
const blockData = await this.l2BlockSource.getBlockData(blockNumber);
|
|
531
|
+
if (!blockData) {
|
|
510
532
|
// this shouldn't really happen because a moment ago we checked that all components were in sync
|
|
511
|
-
this.log.error(`Failed to get L2 block ${blockNumber} from the archiver with all components in sync`);
|
|
533
|
+
this.log.error(`Failed to get L2 block data ${blockNumber} from the archiver with all components in sync`);
|
|
512
534
|
return undefined;
|
|
513
535
|
}
|
|
514
536
|
|
|
515
537
|
return {
|
|
516
|
-
|
|
517
|
-
blockNumber:
|
|
518
|
-
checkpointNumber:
|
|
519
|
-
archive:
|
|
520
|
-
|
|
538
|
+
blockData,
|
|
539
|
+
blockNumber: blockData.header.getBlockNumber(),
|
|
540
|
+
checkpointNumber: blockData.checkpointNumber,
|
|
541
|
+
archive: blockData.archive.root,
|
|
542
|
+
syncedL2Slot,
|
|
521
543
|
pendingChainValidationStatus,
|
|
522
544
|
};
|
|
523
545
|
}
|
|
@@ -533,7 +555,10 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
533
555
|
proposer = await this.epochCache.getProposerAttesterAddressInSlot(slot);
|
|
534
556
|
} catch (e) {
|
|
535
557
|
if (e instanceof NoCommitteeError) {
|
|
536
|
-
this.
|
|
558
|
+
if (this.lastSlotForNoCommitteeWarning !== slot) {
|
|
559
|
+
this.lastSlotForNoCommitteeWarning = slot;
|
|
560
|
+
this.log.warn(`Cannot propose at next L2 slot ${slot} since the committee does not exist on L1`);
|
|
561
|
+
}
|
|
537
562
|
return [false, undefined];
|
|
538
563
|
}
|
|
539
564
|
this.log.error(`Error getting proposer for slot ${slot}`, e);
|
|
@@ -569,7 +594,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
569
594
|
const { slot } = args;
|
|
570
595
|
|
|
571
596
|
// Prevent duplicate attempts in the same slot
|
|
572
|
-
if (this.
|
|
597
|
+
if (this.lastSlotForFallbackVote === slot) {
|
|
573
598
|
this.log.trace(`Already attempted to vote in slot ${slot} (skipping)`);
|
|
574
599
|
return;
|
|
575
600
|
}
|
|
@@ -601,7 +626,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
601
626
|
}
|
|
602
627
|
|
|
603
628
|
// Mark this slot as attempted
|
|
604
|
-
this.
|
|
629
|
+
this.lastSlotForFallbackVote = slot;
|
|
605
630
|
|
|
606
631
|
// Get a publisher for voting
|
|
607
632
|
const { attestorAddress, publisher } = await this.publisherFactory.create(proposer);
|
|
@@ -636,7 +661,55 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
636
661
|
}
|
|
637
662
|
|
|
638
663
|
/**
|
|
639
|
-
*
|
|
664
|
+
* Tries to vote on slashing actions and governance proposals when escape hatch is open.
|
|
665
|
+
* This allows the sequencer to participate in voting without performing checkpoint proposal work.
|
|
666
|
+
*/
|
|
667
|
+
@trackSpan('Sequencer.tryVoteWhenEscapeHatchOpen', ({ slot }) => ({ [Attributes.SLOT_NUMBER]: slot }))
|
|
668
|
+
protected async tryVoteWhenEscapeHatchOpen(args: {
|
|
669
|
+
slot: SlotNumber;
|
|
670
|
+
proposer: EthAddress | undefined;
|
|
671
|
+
}): Promise<void> {
|
|
672
|
+
const { slot, proposer } = args;
|
|
673
|
+
|
|
674
|
+
// Prevent duplicate attempts in the same slot
|
|
675
|
+
if (this.lastSlotForFallbackVote === slot) {
|
|
676
|
+
this.log.trace(`Already attempted to vote in slot ${slot} (escape hatch open, skipping)`);
|
|
677
|
+
return;
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
// Mark this slot as attempted
|
|
681
|
+
this.lastSlotForFallbackVote = slot;
|
|
682
|
+
|
|
683
|
+
const { attestorAddress, publisher } = await this.publisherFactory.create(proposer);
|
|
684
|
+
|
|
685
|
+
this.log.debug(`Escape hatch open for slot ${slot}, attempting vote-only actions`, { slot, attestorAddress });
|
|
686
|
+
|
|
687
|
+
const voter = new CheckpointVoter(
|
|
688
|
+
slot,
|
|
689
|
+
publisher,
|
|
690
|
+
attestorAddress,
|
|
691
|
+
this.validatorClient,
|
|
692
|
+
this.slasherClient,
|
|
693
|
+
this.l1Constants,
|
|
694
|
+
this.config,
|
|
695
|
+
this.metrics,
|
|
696
|
+
this.log,
|
|
697
|
+
);
|
|
698
|
+
|
|
699
|
+
const votesPromises = voter.enqueueVotes();
|
|
700
|
+
const votes = await Promise.all(votesPromises);
|
|
701
|
+
|
|
702
|
+
if (votes.every(p => !p)) {
|
|
703
|
+
this.log.debug(`No votes to enqueue for slot ${slot} (escape hatch open)`);
|
|
704
|
+
return;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
this.log.info(`Voting in slot ${slot} (escape hatch open)`, { slot });
|
|
708
|
+
await publisher.sendRequests();
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
/**
|
|
712
|
+
* Considers invalidating a block if the pending chain is invalid. Depends on how long the invalid block
|
|
640
713
|
* has been there without being invalidated and whether the sequencer is in the committee or not. We always
|
|
641
714
|
* have the proposer try to invalidate, but if they fail, the sequencers in the committee are expected to try,
|
|
642
715
|
* and if they fail, any sequencer will try as well.
|
|
@@ -645,7 +718,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
645
718
|
syncedTo: SequencerSyncCheckResult,
|
|
646
719
|
currentSlot: SlotNumber,
|
|
647
720
|
): Promise<void> {
|
|
648
|
-
const { pendingChainValidationStatus,
|
|
721
|
+
const { pendingChainValidationStatus, syncedL2Slot } = syncedTo;
|
|
649
722
|
if (pendingChainValidationStatus.valid) {
|
|
650
723
|
return;
|
|
651
724
|
}
|
|
@@ -660,7 +733,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
660
733
|
|
|
661
734
|
const logData = {
|
|
662
735
|
invalidL1Timestamp: invalidCheckpointTimestamp,
|
|
663
|
-
|
|
736
|
+
syncedL2Slot,
|
|
664
737
|
invalidCheckpoint: pendingChainValidationStatus.checkpoint,
|
|
665
738
|
secondsBeforeInvalidatingBlockAsCommitteeMember,
|
|
666
739
|
secondsBeforeInvalidatingBlockAsNonCommitteeMember,
|
|
@@ -788,6 +861,11 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
788
861
|
return this.validatorClient?.getValidatorAddresses();
|
|
789
862
|
}
|
|
790
863
|
|
|
864
|
+
/** Updates the publisher factory's node keystore adapter after a keystore reload. */
|
|
865
|
+
public updatePublisherNodeKeyStore(adapter: NodeKeystoreAdapter): void {
|
|
866
|
+
this.publisherFactory.updateNodeKeyStore(adapter);
|
|
867
|
+
}
|
|
868
|
+
|
|
791
869
|
public getConfig() {
|
|
792
870
|
return this.config;
|
|
793
871
|
}
|
|
@@ -798,10 +876,10 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
798
876
|
}
|
|
799
877
|
|
|
800
878
|
type SequencerSyncCheckResult = {
|
|
801
|
-
|
|
879
|
+
blockData?: BlockData;
|
|
802
880
|
checkpointNumber: CheckpointNumber;
|
|
803
881
|
blockNumber: BlockNumber;
|
|
804
882
|
archive: Fr;
|
|
805
|
-
|
|
883
|
+
syncedL2Slot: SlotNumber;
|
|
806
884
|
pendingChainValidationStatus: ValidateCheckpointResult;
|
|
807
885
|
};
|
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { Logger } from '@aztec/foundation/log';
|
|
2
|
+
import {
|
|
3
|
+
CHECKPOINT_ASSEMBLE_TIME,
|
|
4
|
+
CHECKPOINT_INITIALIZATION_TIME,
|
|
5
|
+
DEFAULT_P2P_PROPAGATION_TIME,
|
|
6
|
+
MIN_EXECUTION_TIME,
|
|
7
|
+
} from '@aztec/stdlib/timetable';
|
|
2
8
|
|
|
3
|
-
import { DEFAULT_ATTESTATION_PROPAGATION_TIME as DEFAULT_P2P_PROPAGATION_TIME } from '../config.js';
|
|
4
9
|
import { SequencerTooSlowError } from './errors.js';
|
|
5
10
|
import type { SequencerMetrics } from './metrics.js';
|
|
6
11
|
import { SequencerState } from './utils.js';
|
|
7
12
|
|
|
8
|
-
export const MIN_EXECUTION_TIME = 2;
|
|
9
|
-
export const CHECKPOINT_INITIALIZATION_TIME = 1;
|
|
10
|
-
export const CHECKPOINT_ASSEMBLE_TIME = 1;
|
|
11
|
-
|
|
12
13
|
export class SequencerTimetable {
|
|
13
14
|
/**
|
|
14
15
|
* How late into the slot can we be to start working. Computed as the total time needed for assembling and publishing a block,
|
|
@@ -79,7 +80,7 @@ export class SequencerTimetable {
|
|
|
79
80
|
enforce: boolean;
|
|
80
81
|
},
|
|
81
82
|
private readonly metrics?: SequencerMetrics,
|
|
82
|
-
private readonly log
|
|
83
|
+
private readonly log?: Logger,
|
|
83
84
|
) {
|
|
84
85
|
this.ethereumSlotDuration = opts.ethereumSlotDuration;
|
|
85
86
|
this.aztecSlotDuration = opts.aztecSlotDuration;
|
|
@@ -131,7 +132,7 @@ export class SequencerTimetable {
|
|
|
131
132
|
const initializeDeadline = this.aztecSlotDuration - minWorkToDo;
|
|
132
133
|
this.initializeDeadline = initializeDeadline;
|
|
133
134
|
|
|
134
|
-
this.log
|
|
135
|
+
this.log?.info(
|
|
135
136
|
`Sequencer timetable initialized with ${this.maxNumberOfBlocks} blocks per slot (${this.enforce ? 'enforced' : 'not enforced'})`,
|
|
136
137
|
{
|
|
137
138
|
ethereumSlotDuration: this.ethereumSlotDuration,
|
|
@@ -205,7 +206,7 @@ export class SequencerTimetable {
|
|
|
205
206
|
}
|
|
206
207
|
|
|
207
208
|
this.metrics?.recordStateTransitionBufferMs(Math.floor(bufferSeconds * 1000), newState);
|
|
208
|
-
this.log
|
|
209
|
+
this.log?.trace(`Enough time to transition to ${newState}`, { maxAllowedTime, secondsIntoSlot });
|
|
209
210
|
}
|
|
210
211
|
|
|
211
212
|
/**
|
|
@@ -241,7 +242,7 @@ export class SequencerTimetable {
|
|
|
241
242
|
const canStart = available >= this.minExecutionTime;
|
|
242
243
|
const deadline = secondsIntoSlot + available;
|
|
243
244
|
|
|
244
|
-
this.log
|
|
245
|
+
this.log?.verbose(
|
|
245
246
|
`${canStart ? 'Can' : 'Cannot'} start single-block checkpoint at ${secondsIntoSlot}s into slot`,
|
|
246
247
|
{ secondsIntoSlot, maxAllowed, available, deadline },
|
|
247
248
|
);
|
|
@@ -261,7 +262,7 @@ export class SequencerTimetable {
|
|
|
261
262
|
// Found an available sub-slot! Is this the last one?
|
|
262
263
|
const isLastBlock = subSlot === this.maxNumberOfBlocks;
|
|
263
264
|
|
|
264
|
-
this.log
|
|
265
|
+
this.log?.verbose(
|
|
265
266
|
`Can start ${isLastBlock ? 'last block' : 'block'} in sub-slot ${subSlot} with deadline ${deadline}s`,
|
|
266
267
|
{ secondsIntoSlot, deadline, timeUntilDeadline, subSlot, maxBlocks: this.maxNumberOfBlocks },
|
|
267
268
|
);
|
|
@@ -271,7 +272,7 @@ export class SequencerTimetable {
|
|
|
271
272
|
}
|
|
272
273
|
|
|
273
274
|
// No sub-slots available with enough time
|
|
274
|
-
this.log
|
|
275
|
+
this.log?.verbose(`No time left to start any more blocks`, {
|
|
275
276
|
secondsIntoSlot,
|
|
276
277
|
maxBlocks: this.maxNumberOfBlocks,
|
|
277
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_;
|