@aztec/sequencer-client 0.0.1-commit.9d2bcf6d → 0.0.1-commit.9ee6fcc6
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 -7
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +47 -28
- 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 +32 -9
- package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher.js +343 -39
- package/dest/sequencer/checkpoint_proposal_job.d.ts +15 -7
- package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -1
- package/dest/sequencer/checkpoint_proposal_job.js +240 -139
- 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 -84
- 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 +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 +65 -38
- 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 +349 -53
- package/src/sequencer/checkpoint_proposal_job.ts +327 -150
- package/src/sequencer/events.ts +1 -1
- package/src/sequencer/metrics.ts +106 -18
- package/src/sequencer/sequencer.ts +127 -96
- 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 +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,17 +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
|
-
|
|
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
|
+
);
|
|
380
389
|
|
|
381
390
|
// Create and return the checkpoint proposal job
|
|
382
391
|
return this.createCheckpointProposalJob(
|
|
383
|
-
epoch,
|
|
384
392
|
slot,
|
|
393
|
+
targetSlot,
|
|
394
|
+
epoch,
|
|
395
|
+
targetEpoch,
|
|
385
396
|
checkpointNumber,
|
|
386
397
|
syncedTo.blockNumber,
|
|
387
398
|
proposer,
|
|
@@ -392,8 +403,10 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
392
403
|
}
|
|
393
404
|
|
|
394
405
|
protected createCheckpointProposalJob(
|
|
395
|
-
epoch: EpochNumber,
|
|
396
406
|
slot: SlotNumber,
|
|
407
|
+
targetSlot: SlotNumber,
|
|
408
|
+
epoch: EpochNumber,
|
|
409
|
+
targetEpoch: EpochNumber,
|
|
397
410
|
checkpointNumber: CheckpointNumber,
|
|
398
411
|
syncedToBlockNumber: BlockNumber,
|
|
399
412
|
proposer: EthAddress | undefined,
|
|
@@ -402,8 +415,10 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
402
415
|
invalidateCheckpoint: InvalidateCheckpointRequest | undefined,
|
|
403
416
|
): CheckpointProposalJob {
|
|
404
417
|
return new CheckpointProposalJob(
|
|
405
|
-
epoch,
|
|
406
418
|
slot,
|
|
419
|
+
targetSlot,
|
|
420
|
+
epoch,
|
|
421
|
+
targetEpoch,
|
|
407
422
|
checkpointNumber,
|
|
408
423
|
syncedToBlockNumber,
|
|
409
424
|
proposer,
|
|
@@ -432,6 +447,13 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
432
447
|
);
|
|
433
448
|
}
|
|
434
449
|
|
|
450
|
+
/**
|
|
451
|
+
* Returns the current sequencer state.
|
|
452
|
+
*/
|
|
453
|
+
public getState(): SequencerState {
|
|
454
|
+
return this.state;
|
|
455
|
+
}
|
|
456
|
+
|
|
435
457
|
/**
|
|
436
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.
|
|
437
459
|
* @param proposedState - The new state to transition to.
|
|
@@ -478,16 +500,15 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
478
500
|
* We don't check against the previous block submitted since it may have been reorg'd out.
|
|
479
501
|
*/
|
|
480
502
|
protected async checkSync(args: { ts: bigint; slot: SlotNumber }): Promise<SequencerSyncCheckResult | undefined> {
|
|
481
|
-
// Check that the archiver
|
|
482
|
-
//
|
|
483
|
-
//
|
|
484
|
-
const
|
|
485
|
-
const { slot
|
|
486
|
-
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) {
|
|
487
509
|
this.log.debug(`Cannot propose block at next L2 slot ${slot} due to pending sync from L1`, {
|
|
488
510
|
slot,
|
|
489
|
-
|
|
490
|
-
l1Timestamp,
|
|
511
|
+
syncedL2Slot,
|
|
491
512
|
});
|
|
492
513
|
return undefined;
|
|
493
514
|
}
|
|
@@ -527,24 +548,24 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
527
548
|
checkpointNumber: CheckpointNumber.ZERO,
|
|
528
549
|
blockNumber: BlockNumber.ZERO,
|
|
529
550
|
archive,
|
|
530
|
-
|
|
551
|
+
syncedL2Slot,
|
|
531
552
|
pendingChainValidationStatus,
|
|
532
553
|
};
|
|
533
554
|
}
|
|
534
555
|
|
|
535
|
-
const
|
|
536
|
-
if (!
|
|
556
|
+
const blockData = await this.l2BlockSource.getBlockData(blockNumber);
|
|
557
|
+
if (!blockData) {
|
|
537
558
|
// this shouldn't really happen because a moment ago we checked that all components were in sync
|
|
538
|
-
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`);
|
|
539
560
|
return undefined;
|
|
540
561
|
}
|
|
541
562
|
|
|
542
563
|
return {
|
|
543
|
-
|
|
544
|
-
blockNumber:
|
|
545
|
-
checkpointNumber:
|
|
546
|
-
archive:
|
|
547
|
-
|
|
564
|
+
blockData,
|
|
565
|
+
blockNumber: blockData.header.getBlockNumber(),
|
|
566
|
+
checkpointNumber: blockData.checkpointNumber,
|
|
567
|
+
archive: blockData.archive.root,
|
|
568
|
+
syncedL2Slot,
|
|
548
569
|
pendingChainValidationStatus,
|
|
549
570
|
};
|
|
550
571
|
}
|
|
@@ -553,20 +574,20 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
553
574
|
* Checks if we are the proposer for the next slot.
|
|
554
575
|
* @returns True if we can propose, and the proposer address (undefined if anyone can propose)
|
|
555
576
|
*/
|
|
556
|
-
protected async checkCanPropose(
|
|
577
|
+
protected async checkCanPropose(targetSlot: SlotNumber): Promise<[boolean, EthAddress | undefined]> {
|
|
557
578
|
let proposer: EthAddress | undefined;
|
|
558
579
|
|
|
559
580
|
try {
|
|
560
|
-
proposer = await this.epochCache.getProposerAttesterAddressInSlot(
|
|
581
|
+
proposer = await this.epochCache.getProposerAttesterAddressInSlot(targetSlot);
|
|
561
582
|
} catch (e) {
|
|
562
583
|
if (e instanceof NoCommitteeError) {
|
|
563
|
-
if (this.lastSlotForNoCommitteeWarning !==
|
|
564
|
-
this.lastSlotForNoCommitteeWarning =
|
|
565
|
-
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`);
|
|
566
587
|
}
|
|
567
588
|
return [false, undefined];
|
|
568
589
|
}
|
|
569
|
-
this.log.error(`Error getting proposer for slot ${
|
|
590
|
+
this.log.error(`Error getting proposer for target slot ${targetSlot}`, e);
|
|
570
591
|
return [false, undefined];
|
|
571
592
|
}
|
|
572
593
|
|
|
@@ -583,10 +604,15 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
583
604
|
const weAreProposer = validatorAddresses.some(addr => addr.equals(proposer));
|
|
584
605
|
|
|
585
606
|
if (!weAreProposer) {
|
|
586
|
-
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
|
+
});
|
|
587
612
|
return [false, proposer];
|
|
588
613
|
}
|
|
589
614
|
|
|
615
|
+
this.log.debug(`We are the proposer for target slot ${targetSlot}`, { targetSlot, proposer });
|
|
590
616
|
return [true, proposer];
|
|
591
617
|
}
|
|
592
618
|
|
|
@@ -595,8 +621,8 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
595
621
|
* This allows the sequencer to participate in governance/slashing votes even when it cannot build blocks.
|
|
596
622
|
*/
|
|
597
623
|
@trackSpan('Seqeuencer.tryVoteWhenSyncFails', ({ slot }) => ({ [Attributes.SLOT_NUMBER]: slot }))
|
|
598
|
-
protected async tryVoteWhenSyncFails(args: { slot: SlotNumber; ts: bigint }): Promise<void> {
|
|
599
|
-
const { slot } = args;
|
|
624
|
+
protected async tryVoteWhenSyncFails(args: { slot: SlotNumber; targetSlot: SlotNumber; ts: bigint }): Promise<void> {
|
|
625
|
+
const { slot, targetSlot } = args;
|
|
600
626
|
|
|
601
627
|
// Prevent duplicate attempts in the same slot
|
|
602
628
|
if (this.lastSlotForFallbackVote === slot) {
|
|
@@ -624,7 +650,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
624
650
|
});
|
|
625
651
|
|
|
626
652
|
// Check if we're a proposer or proposal is open
|
|
627
|
-
const [canPropose, proposer] = await this.checkCanPropose(
|
|
653
|
+
const [canPropose, proposer] = await this.checkCanPropose(targetSlot);
|
|
628
654
|
if (!canPropose) {
|
|
629
655
|
this.log.trace(`Cannot vote in slot ${slot} since we are not a proposer`, { slot, proposer });
|
|
630
656
|
return;
|
|
@@ -641,9 +667,9 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
641
667
|
slot,
|
|
642
668
|
});
|
|
643
669
|
|
|
644
|
-
// Enqueue governance and slashing votes
|
|
670
|
+
// Enqueue governance and slashing votes (voter uses the target slot for L1 submission)
|
|
645
671
|
const voter = new CheckpointVoter(
|
|
646
|
-
|
|
672
|
+
targetSlot,
|
|
647
673
|
publisher,
|
|
648
674
|
attestorAddress,
|
|
649
675
|
this.validatorClient,
|
|
@@ -723,7 +749,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
723
749
|
syncedTo: SequencerSyncCheckResult,
|
|
724
750
|
currentSlot: SlotNumber,
|
|
725
751
|
): Promise<void> {
|
|
726
|
-
const { pendingChainValidationStatus,
|
|
752
|
+
const { pendingChainValidationStatus, syncedL2Slot } = syncedTo;
|
|
727
753
|
if (pendingChainValidationStatus.valid) {
|
|
728
754
|
return;
|
|
729
755
|
}
|
|
@@ -738,7 +764,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
738
764
|
|
|
739
765
|
const logData = {
|
|
740
766
|
invalidL1Timestamp: invalidCheckpointTimestamp,
|
|
741
|
-
|
|
767
|
+
syncedL2Slot,
|
|
742
768
|
invalidCheckpoint: pendingChainValidationStatus.checkpoint,
|
|
743
769
|
secondsBeforeInvalidatingBlockAsCommitteeMember,
|
|
744
770
|
secondsBeforeInvalidatingBlockAsNonCommitteeMember,
|
|
@@ -866,6 +892,11 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
866
892
|
return this.validatorClient?.getValidatorAddresses();
|
|
867
893
|
}
|
|
868
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
|
+
|
|
869
900
|
public getConfig() {
|
|
870
901
|
return this.config;
|
|
871
902
|
}
|
|
@@ -876,10 +907,10 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
876
907
|
}
|
|
877
908
|
|
|
878
909
|
type SequencerSyncCheckResult = {
|
|
879
|
-
|
|
910
|
+
blockData?: BlockData;
|
|
880
911
|
checkpointNumber: CheckpointNumber;
|
|
881
912
|
blockNumber: BlockNumber;
|
|
882
913
|
archive: Fr;
|
|
883
|
-
|
|
914
|
+
syncedL2Slot: SlotNumber;
|
|
884
915
|
pendingChainValidationStatus: ValidateCheckpointResult;
|
|
885
916
|
};
|
|
@@ -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_;
|