@aztec/sequencer-client 0.0.1-commit.fcb71a6 → 0.0.1-commit.ff7989d6c
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 +14 -10
- package/dest/client/sequencer-client.d.ts.map +1 -1
- package/dest/client/sequencer-client.js +15 -4
- package/dest/config.d.ts +3 -4
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +22 -12
- package/dest/global_variable_builder/global_builder.d.ts +5 -7
- package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
- package/dest/global_variable_builder/global_builder.js +13 -13
- package/dest/index.d.ts +2 -3
- package/dest/index.d.ts.map +1 -1
- package/dest/index.js +1 -2
- package/dest/publisher/config.d.ts +31 -17
- package/dest/publisher/config.d.ts.map +1 -1
- package/dest/publisher/config.js +101 -42
- 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 +13 -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 +23 -86
- package/dest/publisher/sequencer-publisher.d.ts +32 -23
- package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher.js +522 -88
- package/dest/sequencer/checkpoint_proposal_job.d.ts +40 -12
- package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -1
- package/dest/sequencer/checkpoint_proposal_job.js +633 -62
- 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 -3
- package/dest/sequencer/index.d.ts.map +1 -1
- package/dest/sequencer/index.js +0 -2
- package/dest/sequencer/metrics.d.ts +19 -7
- package/dest/sequencer/metrics.d.ts.map +1 -1
- package/dest/sequencer/metrics.js +131 -141
- package/dest/sequencer/sequencer.d.ts +38 -18
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +513 -66
- package/dest/sequencer/timetable.d.ts +1 -4
- package/dest/sequencer/timetable.d.ts.map +1 -1
- package/dest/sequencer/timetable.js +1 -4
- package/dest/test/index.d.ts +4 -7
- package/dest/test/index.d.ts.map +1 -1
- package/dest/test/mock_checkpoint_builder.d.ts +25 -11
- package/dest/test/mock_checkpoint_builder.d.ts.map +1 -1
- package/dest/test/mock_checkpoint_builder.js +52 -9
- package/dest/test/utils.d.ts +13 -9
- package/dest/test/utils.d.ts.map +1 -1
- package/dest/test/utils.js +27 -17
- package/package.json +30 -28
- package/src/client/sequencer-client.ts +28 -11
- package/src/config.ts +31 -19
- package/src/global_variable_builder/global_builder.ts +14 -14
- package/src/index.ts +1 -9
- package/src/publisher/config.ts +112 -43
- package/src/publisher/sequencer-publisher-factory.ts +23 -6
- package/src/publisher/sequencer-publisher-metrics.ts +17 -69
- package/src/publisher/sequencer-publisher.ts +180 -118
- package/src/sequencer/checkpoint_proposal_job.ts +299 -91
- package/src/sequencer/checkpoint_voter.ts +32 -7
- package/src/sequencer/index.ts +0 -2
- package/src/sequencer/metrics.ts +132 -148
- package/src/sequencer/sequencer.ts +159 -68
- package/src/sequencer/timetable.ts +6 -5
- package/src/test/index.ts +3 -6
- package/src/test/mock_checkpoint_builder.ts +102 -29
- package/src/test/utils.ts +58 -28
- 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/dest/sequencer/checkpoint_builder.d.ts +0 -63
- package/dest/sequencer/checkpoint_builder.d.ts.map +0 -1
- package/dest/sequencer/checkpoint_builder.js +0 -131
- package/dest/tx_validator/nullifier_cache.d.ts +0 -14
- package/dest/tx_validator/nullifier_cache.d.ts.map +0 -1
- package/dest/tx_validator/nullifier_cache.js +0 -24
- package/dest/tx_validator/tx_validator_factory.d.ts +0 -18
- package/dest/tx_validator/tx_validator_factory.d.ts.map +0 -1
- package/dest/tx_validator/tx_validator_factory.js +0 -53
- package/src/sequencer/block_builder.ts +0 -217
- package/src/sequencer/checkpoint_builder.ts +0 -217
- package/src/tx_validator/nullifier_cache.ts +0 -30
- package/src/tx_validator/tx_validator_factory.ts +0 -133
|
@@ -12,7 +12,7 @@ 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
17
|
import { getSlotAtTimestamp, getSlotStartBuildTimestamp } from '@aztec/stdlib/epoch-helpers';
|
|
18
18
|
import {
|
|
@@ -24,16 +24,15 @@ import {
|
|
|
24
24
|
import type { L1ToL2MessageSource } from '@aztec/stdlib/messaging';
|
|
25
25
|
import { pickFromSchema } from '@aztec/stdlib/schemas';
|
|
26
26
|
import { MerkleTreeId } from '@aztec/stdlib/trees';
|
|
27
|
-
import { type TelemetryClient, type Tracer, getTelemetryClient, trackSpan } from '@aztec/telemetry-client';
|
|
28
|
-
import type
|
|
27
|
+
import { Attributes, type TelemetryClient, type Tracer, getTelemetryClient, trackSpan } from '@aztec/telemetry-client';
|
|
28
|
+
import { FullNodeCheckpointsBuilder, NodeKeystoreAdapter, type ValidatorClient } from '@aztec/validator-client';
|
|
29
29
|
|
|
30
30
|
import EventEmitter from 'node:events';
|
|
31
31
|
|
|
32
32
|
import { DefaultSequencerConfig } from '../config.js';
|
|
33
33
|
import type { GlobalVariableBuilder } from '../global_variable_builder/global_builder.js';
|
|
34
34
|
import type { SequencerPublisherFactory } from '../publisher/sequencer-publisher-factory.js';
|
|
35
|
-
import type {
|
|
36
|
-
import { FullNodeCheckpointsBuilder } from './checkpoint_builder.js';
|
|
35
|
+
import type { InvalidateCheckpointRequest, SequencerPublisher } from '../publisher/sequencer-publisher.js';
|
|
37
36
|
import { CheckpointProposalJob } from './checkpoint_proposal_job.js';
|
|
38
37
|
import { CheckpointVoter } from './checkpoint_voter.js';
|
|
39
38
|
import { SequencerInterruptedError, SequencerTooSlowError } from './errors.js';
|
|
@@ -58,8 +57,11 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
58
57
|
private state = SequencerState.STOPPED;
|
|
59
58
|
private metrics: SequencerMetrics;
|
|
60
59
|
|
|
61
|
-
/** The last slot for which we attempted to
|
|
62
|
-
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;
|
|
63
65
|
|
|
64
66
|
/** The last slot for which we triggered a checkpoint proposal job, to prevent duplicate attempts. */
|
|
65
67
|
private lastSlotForCheckpointProposalJob: SlotNumber | undefined;
|
|
@@ -73,14 +75,6 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
73
75
|
/** The maximum number of seconds that the sequencer can be into a slot to transition to a particular state. */
|
|
74
76
|
protected timetable!: SequencerTimetable;
|
|
75
77
|
|
|
76
|
-
// This shouldn't be here as this gets re-created each time we build/propose a block.
|
|
77
|
-
// But we have a number of tests that abuse/rely on this class having a permanent publisher.
|
|
78
|
-
// As long as those tests only configure a single publisher they will continue to work.
|
|
79
|
-
// This will get re-assigned every time the sequencer goes to build a new block to a publisher that is valid
|
|
80
|
-
// for the block proposer.
|
|
81
|
-
// TODO(palla/mbps): Remove this field and fix tests
|
|
82
|
-
protected publisher: SequencerPublisher | undefined;
|
|
83
|
-
|
|
84
78
|
/** Config for the sequencer */
|
|
85
79
|
protected config: ResolvedSequencerConfig = DefaultSequencerConfig;
|
|
86
80
|
|
|
@@ -91,7 +85,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
91
85
|
protected p2pClient: P2P,
|
|
92
86
|
protected worldState: WorldStateSynchronizer,
|
|
93
87
|
protected slasherClient: SlasherClientInterface | undefined,
|
|
94
|
-
protected l2BlockSource: L2BlockSource,
|
|
88
|
+
protected l2BlockSource: L2BlockSource & L2BlockSink,
|
|
95
89
|
protected l1ToL2MessageSource: L1ToL2MessageSource,
|
|
96
90
|
protected checkpointsBuilder: FullNodeCheckpointsBuilder,
|
|
97
91
|
protected l1Constants: SequencerRollupConstants,
|
|
@@ -132,10 +126,9 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
132
126
|
);
|
|
133
127
|
}
|
|
134
128
|
|
|
135
|
-
/** Initializes the sequencer (precomputes tables
|
|
136
|
-
public
|
|
129
|
+
/** Initializes the sequencer (precomputes tables). Takes about 3s. */
|
|
130
|
+
public init() {
|
|
137
131
|
getKzg();
|
|
138
|
-
this.publisher = (await this.publisherFactory.create(undefined)).publisher;
|
|
139
132
|
}
|
|
140
133
|
|
|
141
134
|
/** Starts the sequencer and moves to IDLE state. */
|
|
@@ -154,13 +147,12 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
154
147
|
public async stop(): Promise<void> {
|
|
155
148
|
this.log.info(`Stopping sequencer`);
|
|
156
149
|
this.setState(SequencerState.STOPPING, undefined, { force: true });
|
|
157
|
-
this.
|
|
150
|
+
this.publisherFactory.interruptAll();
|
|
158
151
|
await this.runningPromise?.stop();
|
|
159
152
|
this.setState(SequencerState.STOPPED, undefined, { force: true });
|
|
160
153
|
this.log.info('Stopped sequencer');
|
|
161
154
|
}
|
|
162
155
|
|
|
163
|
-
@trackSpan('Sequencer.work')
|
|
164
156
|
/** Main sequencer loop with a try/catch */
|
|
165
157
|
protected async safeWork() {
|
|
166
158
|
try {
|
|
@@ -168,7 +160,6 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
168
160
|
} catch (err) {
|
|
169
161
|
this.emit('checkpoint-error', { error: err as Error });
|
|
170
162
|
if (err instanceof SequencerTooSlowError) {
|
|
171
|
-
// TODO(palla/mbps): Add missing states
|
|
172
163
|
// Log as warn only if we had to abort halfway through the block proposal
|
|
173
164
|
const logLvl = [SequencerState.INITIALIZING_CHECKPOINT, SequencerState.PROPOSER_CHECK].includes(
|
|
174
165
|
err.proposedState,
|
|
@@ -198,12 +189,13 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
198
189
|
* - Collect attestations for the final block
|
|
199
190
|
* - Submit checkpoint
|
|
200
191
|
*/
|
|
192
|
+
@trackSpan('Sequencer.work')
|
|
201
193
|
protected async work() {
|
|
202
194
|
this.setState(SequencerState.SYNCHRONIZING, undefined);
|
|
203
195
|
const { slot, ts, now, epoch } = this.epochCache.getEpochAndSlotInNextL1Slot();
|
|
204
196
|
|
|
205
197
|
// Check if we are synced and it's our slot, grab a publisher, check previous block invalidation, etc
|
|
206
|
-
const checkpointProposalJob = await this.prepareCheckpointProposal(slot, ts, now);
|
|
198
|
+
const checkpointProposalJob = await this.prepareCheckpointProposal(epoch, slot, ts, now);
|
|
207
199
|
if (!checkpointProposalJob) {
|
|
208
200
|
return;
|
|
209
201
|
}
|
|
@@ -233,7 +225,9 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
233
225
|
* This is the initial step in the main loop.
|
|
234
226
|
* @returns CheckpointProposalJob if successful, undefined if we are not yet synced or are not the proposer.
|
|
235
227
|
*/
|
|
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,8 +257,27 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
263
257
|
return undefined;
|
|
264
258
|
}
|
|
265
259
|
|
|
266
|
-
//
|
|
267
|
-
|
|
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
|
+
|
|
279
|
+
// Next checkpoint follows from the last synced one
|
|
280
|
+
const checkpointNumber = CheckpointNumber(syncedTo.checkpointNumber + 1);
|
|
268
281
|
|
|
269
282
|
const logCtx = {
|
|
270
283
|
now,
|
|
@@ -280,19 +293,19 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
280
293
|
this.setState(SequencerState.PROPOSER_CHECK, slot);
|
|
281
294
|
const [canPropose, proposer] = await this.checkCanPropose(slot);
|
|
282
295
|
|
|
283
|
-
// If we are not a proposer check if we should invalidate
|
|
296
|
+
// If we are not a proposer check if we should invalidate an invalid checkpoint, and bail
|
|
284
297
|
if (!canPropose) {
|
|
285
|
-
await this.
|
|
298
|
+
await this.considerInvalidatingCheckpoint(syncedTo, slot);
|
|
286
299
|
return undefined;
|
|
287
300
|
}
|
|
288
301
|
|
|
289
302
|
// 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.
|
|
303
|
+
if (syncedTo.blockData && syncedTo.blockData.header.getSlot() >= slot) {
|
|
291
304
|
this.log.warn(
|
|
292
305
|
`Cannot propose block at next L2 slot ${slot} since that slot was taken by block ${syncedTo.blockNumber}`,
|
|
293
|
-
{ ...logCtx, block: syncedTo.
|
|
306
|
+
{ ...logCtx, block: syncedTo.blockData.header.toInspect() },
|
|
294
307
|
);
|
|
295
|
-
this.metrics.
|
|
308
|
+
this.metrics.recordCheckpointPrecheckFailed('slot_already_taken');
|
|
296
309
|
return undefined;
|
|
297
310
|
}
|
|
298
311
|
|
|
@@ -303,7 +316,6 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
303
316
|
const proposerForPublisher = this.config.fishermanMode ? undefined : proposer;
|
|
304
317
|
const { attestorAddress, publisher } = await this.publisherFactory.create(proposerForPublisher);
|
|
305
318
|
this.log.verbose(`Created publisher at address ${publisher.getSenderAddress()} for attestor ${attestorAddress}`);
|
|
306
|
-
this.publisher = publisher;
|
|
307
319
|
|
|
308
320
|
// In fisherman mode, set the actual proposer's address for simulations
|
|
309
321
|
if (this.config.fishermanMode && proposer) {
|
|
@@ -312,15 +324,14 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
312
324
|
}
|
|
313
325
|
|
|
314
326
|
// Prepare invalidation request if the pending chain is invalid (returns undefined if no need)
|
|
315
|
-
|
|
316
|
-
const invalidateBlock = await publisher.simulateInvalidateBlock(syncedTo.pendingChainValidationStatus);
|
|
327
|
+
const invalidateCheckpoint = await publisher.simulateInvalidateCheckpoint(syncedTo.pendingChainValidationStatus);
|
|
317
328
|
|
|
318
329
|
// Check with the rollup contract if we can indeed propose at the next L2 slot. This check should not fail
|
|
319
330
|
// if all the previous checks are good, but we do it just in case.
|
|
320
331
|
const canProposeCheck = await publisher.canProposeAtNextEthBlock(
|
|
321
332
|
syncedTo.archive,
|
|
322
333
|
proposer ?? EthAddress.ZERO,
|
|
323
|
-
|
|
334
|
+
invalidateCheckpoint,
|
|
324
335
|
);
|
|
325
336
|
|
|
326
337
|
if (canProposeCheck === undefined) {
|
|
@@ -329,7 +340,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
329
340
|
logCtx,
|
|
330
341
|
);
|
|
331
342
|
this.emit('proposer-rollup-check-failed', { reason: 'Rollup contract check failed', slot });
|
|
332
|
-
this.metrics.
|
|
343
|
+
this.metrics.recordCheckpointPrecheckFailed('rollup_contract_check_failed');
|
|
333
344
|
return undefined;
|
|
334
345
|
}
|
|
335
346
|
|
|
@@ -339,7 +350,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
339
350
|
{ ...logCtx, rollup: canProposeCheck, expectedSlot: slot },
|
|
340
351
|
);
|
|
341
352
|
this.emit('proposer-rollup-check-failed', { reason: 'Slot mismatch', slot });
|
|
342
|
-
this.metrics.
|
|
353
|
+
this.metrics.recordCheckpointPrecheckFailed('slot_mismatch');
|
|
343
354
|
return undefined;
|
|
344
355
|
}
|
|
345
356
|
|
|
@@ -349,48 +360,54 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
349
360
|
{ ...logCtx, rollup: canProposeCheck, expectedSlot: slot },
|
|
350
361
|
);
|
|
351
362
|
this.emit('proposer-rollup-check-failed', { reason: 'Block mismatch', slot });
|
|
352
|
-
this.metrics.
|
|
363
|
+
this.metrics.recordCheckpointPrecheckFailed('block_number_mismatch');
|
|
353
364
|
return undefined;
|
|
354
365
|
}
|
|
355
366
|
|
|
356
367
|
this.lastSlotForCheckpointProposalJob = slot;
|
|
368
|
+
await this.p2pClient.prepareForSlot(slot);
|
|
357
369
|
this.log.info(`Preparing checkpoint proposal ${checkpointNumber} at slot ${slot}`, { ...logCtx, proposer });
|
|
358
370
|
|
|
359
371
|
// Create and return the checkpoint proposal job
|
|
360
372
|
return this.createCheckpointProposalJob(
|
|
373
|
+
epoch,
|
|
361
374
|
slot,
|
|
362
375
|
checkpointNumber,
|
|
363
376
|
syncedTo.blockNumber,
|
|
364
377
|
proposer,
|
|
365
378
|
publisher,
|
|
366
379
|
attestorAddress,
|
|
367
|
-
|
|
380
|
+
invalidateCheckpoint,
|
|
368
381
|
);
|
|
369
382
|
}
|
|
370
383
|
|
|
371
384
|
protected createCheckpointProposalJob(
|
|
385
|
+
epoch: EpochNumber,
|
|
372
386
|
slot: SlotNumber,
|
|
373
387
|
checkpointNumber: CheckpointNumber,
|
|
374
388
|
syncedToBlockNumber: BlockNumber,
|
|
375
389
|
proposer: EthAddress | undefined,
|
|
376
390
|
publisher: SequencerPublisher,
|
|
377
391
|
attestorAddress: EthAddress,
|
|
378
|
-
|
|
392
|
+
invalidateCheckpoint: InvalidateCheckpointRequest | undefined,
|
|
379
393
|
): CheckpointProposalJob {
|
|
380
394
|
return new CheckpointProposalJob(
|
|
395
|
+
epoch,
|
|
381
396
|
slot,
|
|
382
397
|
checkpointNumber,
|
|
383
398
|
syncedToBlockNumber,
|
|
384
399
|
proposer,
|
|
385
400
|
publisher,
|
|
386
401
|
attestorAddress,
|
|
387
|
-
|
|
402
|
+
invalidateCheckpoint,
|
|
388
403
|
this.validatorClient,
|
|
389
404
|
this.globalsBuilder,
|
|
390
405
|
this.p2pClient,
|
|
391
406
|
this.worldState,
|
|
392
407
|
this.l1ToL2MessageSource,
|
|
408
|
+
this.l2BlockSource,
|
|
393
409
|
this.checkpointsBuilder,
|
|
410
|
+
this.l2BlockSource,
|
|
394
411
|
this.l1Constants,
|
|
395
412
|
this.config,
|
|
396
413
|
this.timetable,
|
|
@@ -400,10 +417,18 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
400
417
|
this.metrics,
|
|
401
418
|
this,
|
|
402
419
|
this.setState.bind(this),
|
|
403
|
-
this.
|
|
420
|
+
this.tracer,
|
|
421
|
+
this.log.getBindings(),
|
|
404
422
|
);
|
|
405
423
|
}
|
|
406
424
|
|
|
425
|
+
/**
|
|
426
|
+
* Returns the current sequencer state.
|
|
427
|
+
*/
|
|
428
|
+
public getState(): SequencerState {
|
|
429
|
+
return this.state;
|
|
430
|
+
}
|
|
431
|
+
|
|
407
432
|
/**
|
|
408
433
|
* Internal helper for setting the sequencer state and checks if we have enough time left in the slot to transition to the new state.
|
|
409
434
|
* @param proposedState - The new state to transition to.
|
|
@@ -469,9 +494,9 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
469
494
|
number: syncSummary.latestBlockNumber,
|
|
470
495
|
hash: syncSummary.latestBlockHash,
|
|
471
496
|
})),
|
|
472
|
-
this.l2BlockSource.getL2Tips().then(t => t.
|
|
497
|
+
this.l2BlockSource.getL2Tips().then(t => t.proposed),
|
|
473
498
|
this.p2pClient.getStatus().then(p2p => p2p.syncedToL2Block),
|
|
474
|
-
this.l1ToL2MessageSource.getL2Tips().then(t => t.
|
|
499
|
+
this.l1ToL2MessageSource.getL2Tips().then(t => t.proposed),
|
|
475
500
|
this.l2BlockSource.getPendingChainValidationStatus(),
|
|
476
501
|
] as const);
|
|
477
502
|
|
|
@@ -479,6 +504,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
479
504
|
|
|
480
505
|
// 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,
|
|
481
506
|
// as the world state can compute the new genesis block hash, but other components use the hardcoded constant.
|
|
507
|
+
// TODO(palla/mbps): Fix the above. All components should be able to handle dynamic genesis block hashes.
|
|
482
508
|
const result =
|
|
483
509
|
(l2BlockSource.number === 0 && worldState.number === 0 && p2p.number === 0 && l1ToL2MessageSource.number === 0) ||
|
|
484
510
|
(worldState.hash === l2BlockSource.hash &&
|
|
@@ -494,20 +520,27 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
494
520
|
const blockNumber = worldState.number;
|
|
495
521
|
if (blockNumber < INITIAL_L2_BLOCK_NUM) {
|
|
496
522
|
const archive = new Fr((await this.worldState.getCommitted().getTreeInfo(MerkleTreeId.ARCHIVE)).root);
|
|
497
|
-
return {
|
|
523
|
+
return {
|
|
524
|
+
checkpointNumber: CheckpointNumber.ZERO,
|
|
525
|
+
blockNumber: BlockNumber.ZERO,
|
|
526
|
+
archive,
|
|
527
|
+
l1Timestamp,
|
|
528
|
+
pendingChainValidationStatus,
|
|
529
|
+
};
|
|
498
530
|
}
|
|
499
531
|
|
|
500
|
-
const
|
|
501
|
-
if (!
|
|
532
|
+
const blockData = await this.l2BlockSource.getBlockData(blockNumber);
|
|
533
|
+
if (!blockData) {
|
|
502
534
|
// this shouldn't really happen because a moment ago we checked that all components were in sync
|
|
503
|
-
this.log.error(`Failed to get L2 block ${blockNumber} from the archiver with all components in sync`);
|
|
535
|
+
this.log.error(`Failed to get L2 block data ${blockNumber} from the archiver with all components in sync`);
|
|
504
536
|
return undefined;
|
|
505
537
|
}
|
|
506
538
|
|
|
507
539
|
return {
|
|
508
|
-
|
|
509
|
-
blockNumber:
|
|
510
|
-
|
|
540
|
+
blockData,
|
|
541
|
+
blockNumber: blockData.header.getBlockNumber(),
|
|
542
|
+
checkpointNumber: blockData.checkpointNumber,
|
|
543
|
+
archive: blockData.archive.root,
|
|
511
544
|
l1Timestamp,
|
|
512
545
|
pendingChainValidationStatus,
|
|
513
546
|
};
|
|
@@ -524,7 +557,10 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
524
557
|
proposer = await this.epochCache.getProposerAttesterAddressInSlot(slot);
|
|
525
558
|
} catch (e) {
|
|
526
559
|
if (e instanceof NoCommitteeError) {
|
|
527
|
-
this.
|
|
560
|
+
if (this.lastSlotForNoCommitteeWarning !== slot) {
|
|
561
|
+
this.lastSlotForNoCommitteeWarning = slot;
|
|
562
|
+
this.log.warn(`Cannot propose at next L2 slot ${slot} since the committee does not exist on L1`);
|
|
563
|
+
}
|
|
528
564
|
return [false, undefined];
|
|
529
565
|
}
|
|
530
566
|
this.log.error(`Error getting proposer for slot ${slot}`, e);
|
|
@@ -555,11 +591,12 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
555
591
|
* Tries to vote on slashing actions and governance when the sync check fails but we're past the max time for initializing a proposal.
|
|
556
592
|
* This allows the sequencer to participate in governance/slashing votes even when it cannot build blocks.
|
|
557
593
|
*/
|
|
594
|
+
@trackSpan('Seqeuencer.tryVoteWhenSyncFails', ({ slot }) => ({ [Attributes.SLOT_NUMBER]: slot }))
|
|
558
595
|
protected async tryVoteWhenSyncFails(args: { slot: SlotNumber; ts: bigint }): Promise<void> {
|
|
559
596
|
const { slot } = args;
|
|
560
597
|
|
|
561
598
|
// Prevent duplicate attempts in the same slot
|
|
562
|
-
if (this.
|
|
599
|
+
if (this.lastSlotForFallbackVote === slot) {
|
|
563
600
|
this.log.trace(`Already attempted to vote in slot ${slot} (skipping)`);
|
|
564
601
|
return;
|
|
565
602
|
}
|
|
@@ -591,7 +628,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
591
628
|
}
|
|
592
629
|
|
|
593
630
|
// Mark this slot as attempted
|
|
594
|
-
this.
|
|
631
|
+
this.lastSlotForFallbackVote = slot;
|
|
595
632
|
|
|
596
633
|
// Get a publisher for voting
|
|
597
634
|
const { attestorAddress, publisher } = await this.publisherFactory.create(proposer);
|
|
@@ -625,13 +662,61 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
625
662
|
await publisher.sendRequests();
|
|
626
663
|
}
|
|
627
664
|
|
|
665
|
+
/**
|
|
666
|
+
* Tries to vote on slashing actions and governance proposals when escape hatch is open.
|
|
667
|
+
* This allows the sequencer to participate in voting without performing checkpoint proposal work.
|
|
668
|
+
*/
|
|
669
|
+
@trackSpan('Sequencer.tryVoteWhenEscapeHatchOpen', ({ slot }) => ({ [Attributes.SLOT_NUMBER]: slot }))
|
|
670
|
+
protected async tryVoteWhenEscapeHatchOpen(args: {
|
|
671
|
+
slot: SlotNumber;
|
|
672
|
+
proposer: EthAddress | undefined;
|
|
673
|
+
}): Promise<void> {
|
|
674
|
+
const { slot, proposer } = args;
|
|
675
|
+
|
|
676
|
+
// Prevent duplicate attempts in the same slot
|
|
677
|
+
if (this.lastSlotForFallbackVote === slot) {
|
|
678
|
+
this.log.trace(`Already attempted to vote in slot ${slot} (escape hatch open, skipping)`);
|
|
679
|
+
return;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
// Mark this slot as attempted
|
|
683
|
+
this.lastSlotForFallbackVote = slot;
|
|
684
|
+
|
|
685
|
+
const { attestorAddress, publisher } = await this.publisherFactory.create(proposer);
|
|
686
|
+
|
|
687
|
+
this.log.debug(`Escape hatch open for slot ${slot}, attempting vote-only actions`, { slot, attestorAddress });
|
|
688
|
+
|
|
689
|
+
const voter = new CheckpointVoter(
|
|
690
|
+
slot,
|
|
691
|
+
publisher,
|
|
692
|
+
attestorAddress,
|
|
693
|
+
this.validatorClient,
|
|
694
|
+
this.slasherClient,
|
|
695
|
+
this.l1Constants,
|
|
696
|
+
this.config,
|
|
697
|
+
this.metrics,
|
|
698
|
+
this.log,
|
|
699
|
+
);
|
|
700
|
+
|
|
701
|
+
const votesPromises = voter.enqueueVotes();
|
|
702
|
+
const votes = await Promise.all(votesPromises);
|
|
703
|
+
|
|
704
|
+
if (votes.every(p => !p)) {
|
|
705
|
+
this.log.debug(`No votes to enqueue for slot ${slot} (escape hatch open)`);
|
|
706
|
+
return;
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
this.log.info(`Voting in slot ${slot} (escape hatch open)`, { slot });
|
|
710
|
+
await publisher.sendRequests();
|
|
711
|
+
}
|
|
712
|
+
|
|
628
713
|
/**
|
|
629
714
|
* Considers invalidating a block if the pending chain is invalid. Depends on how long the invalid block
|
|
630
715
|
* has been there without being invalidated and whether the sequencer is in the committee or not. We always
|
|
631
716
|
* have the proposer try to invalidate, but if they fail, the sequencers in the committee are expected to try,
|
|
632
717
|
* and if they fail, any sequencer will try as well.
|
|
633
718
|
*/
|
|
634
|
-
protected async
|
|
719
|
+
protected async considerInvalidatingCheckpoint(
|
|
635
720
|
syncedTo: SequencerSyncCheckResult,
|
|
636
721
|
currentSlot: SlotNumber,
|
|
637
722
|
): Promise<void> {
|
|
@@ -640,18 +725,18 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
640
725
|
return;
|
|
641
726
|
}
|
|
642
727
|
|
|
643
|
-
const
|
|
644
|
-
const
|
|
645
|
-
const timeSinceChainInvalid = this.dateProvider.nowInSeconds() - Number(
|
|
728
|
+
const invalidCheckpointNumber = pendingChainValidationStatus.checkpoint.checkpointNumber;
|
|
729
|
+
const invalidCheckpointTimestamp = pendingChainValidationStatus.checkpoint.timestamp;
|
|
730
|
+
const timeSinceChainInvalid = this.dateProvider.nowInSeconds() - Number(invalidCheckpointTimestamp);
|
|
646
731
|
const ourValidatorAddresses = this.validatorClient.getValidatorAddresses();
|
|
647
732
|
|
|
648
733
|
const { secondsBeforeInvalidatingBlockAsCommitteeMember, secondsBeforeInvalidatingBlockAsNonCommitteeMember } =
|
|
649
734
|
this.config;
|
|
650
735
|
|
|
651
736
|
const logData = {
|
|
652
|
-
invalidL1Timestamp:
|
|
737
|
+
invalidL1Timestamp: invalidCheckpointTimestamp,
|
|
653
738
|
l1Timestamp,
|
|
654
|
-
|
|
739
|
+
invalidCheckpoint: pendingChainValidationStatus.checkpoint,
|
|
655
740
|
secondsBeforeInvalidatingBlockAsCommitteeMember,
|
|
656
741
|
secondsBeforeInvalidatingBlockAsNonCommitteeMember,
|
|
657
742
|
ourValidatorAddresses,
|
|
@@ -697,25 +782,25 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
697
782
|
|
|
698
783
|
const { publisher } = await this.publisherFactory.create(validatorToUse);
|
|
699
784
|
|
|
700
|
-
const
|
|
701
|
-
if (!
|
|
702
|
-
this.log.warn(`Failed to simulate invalidate
|
|
785
|
+
const invalidateCheckpoint = await publisher.simulateInvalidateCheckpoint(pendingChainValidationStatus);
|
|
786
|
+
if (!invalidateCheckpoint) {
|
|
787
|
+
this.log.warn(`Failed to simulate invalidate checkpoint`, logData);
|
|
703
788
|
return;
|
|
704
789
|
}
|
|
705
790
|
|
|
706
791
|
this.log.info(
|
|
707
792
|
invalidateAsCommitteeMember
|
|
708
|
-
? `Invalidating
|
|
709
|
-
: `Invalidating
|
|
793
|
+
? `Invalidating checkpoint ${invalidCheckpointNumber} as committee member`
|
|
794
|
+
: `Invalidating checkpoint ${invalidCheckpointNumber} as non-committee member`,
|
|
710
795
|
logData,
|
|
711
796
|
);
|
|
712
797
|
|
|
713
|
-
publisher.
|
|
798
|
+
publisher.enqueueInvalidateCheckpoint(invalidateCheckpoint);
|
|
714
799
|
|
|
715
800
|
if (!this.config.fishermanMode) {
|
|
716
801
|
await publisher.sendRequests();
|
|
717
802
|
} else {
|
|
718
|
-
this.log.info('Invalidating
|
|
803
|
+
this.log.info('Invalidating checkpoint in fisherman mode, clearing pending requests');
|
|
719
804
|
publisher.clearPendingRequests();
|
|
720
805
|
}
|
|
721
806
|
}
|
|
@@ -778,6 +863,11 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
778
863
|
return this.validatorClient?.getValidatorAddresses();
|
|
779
864
|
}
|
|
780
865
|
|
|
866
|
+
/** Updates the publisher factory's node keystore adapter after a keystore reload. */
|
|
867
|
+
public updatePublisherNodeKeyStore(adapter: NodeKeystoreAdapter): void {
|
|
868
|
+
this.publisherFactory.updateNodeKeyStore(adapter);
|
|
869
|
+
}
|
|
870
|
+
|
|
781
871
|
public getConfig() {
|
|
782
872
|
return this.config;
|
|
783
873
|
}
|
|
@@ -788,9 +878,10 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
788
878
|
}
|
|
789
879
|
|
|
790
880
|
type SequencerSyncCheckResult = {
|
|
791
|
-
|
|
881
|
+
blockData?: BlockData;
|
|
882
|
+
checkpointNumber: CheckpointNumber;
|
|
792
883
|
blockNumber: BlockNumber;
|
|
793
884
|
archive: Fr;
|
|
794
885
|
l1Timestamp: bigint;
|
|
795
|
-
pendingChainValidationStatus:
|
|
886
|
+
pendingChainValidationStatus: ValidateCheckpointResult;
|
|
796
887
|
};
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import { createLogger } from '@aztec/aztec.js/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,
|
package/src/test/index.ts
CHANGED
|
@@ -1,19 +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
|
-
import type { ValidatorClient } from '@aztec/validator-client';
|
|
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
|
-
import type { FullNodeCheckpointsBuilder } from '../sequencer/checkpoint_builder.js';
|
|
10
8
|
import { Sequencer } from '../sequencer/sequencer.js';
|
|
11
9
|
import type { SequencerTimetable } from '../sequencer/timetable.js';
|
|
12
10
|
|
|
13
11
|
class TestSequencer_ extends Sequencer {
|
|
14
12
|
declare public publicProcessorFactory: PublicProcessorFactory;
|
|
15
13
|
declare public timetable: SequencerTimetable;
|
|
16
|
-
declare public publisher: SequencerPublisher;
|
|
17
14
|
declare public publisherFactory: SequencerPublisherFactory;
|
|
18
15
|
declare public validatorClient: ValidatorClient;
|
|
19
16
|
declare public checkpointsBuilder: FullNodeCheckpointsBuilder;
|
|
@@ -23,7 +20,7 @@ export type TestSequencer = TestSequencer_;
|
|
|
23
20
|
|
|
24
21
|
class TestSequencerClient_ extends SequencerClient {
|
|
25
22
|
declare public sequencer: TestSequencer;
|
|
26
|
-
declare public publisherManager: PublisherManager<
|
|
23
|
+
declare public publisherManager: PublisherManager<L1TxUtils>;
|
|
27
24
|
}
|
|
28
25
|
|
|
29
26
|
export type TestSequencerClient = TestSequencerClient_;
|