@aztec/sequencer-client 0.0.1-commit.6c91f13 → 0.0.1-commit.6d63667d
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 +4 -5
- package/dest/client/sequencer-client.d.ts.map +1 -1
- package/dest/client/sequencer-client.js +1 -1
- package/dest/config.d.ts +1 -1
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +6 -1
- package/dest/global_variable_builder/global_builder.d.ts +4 -4
- 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/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 +17 -17
- package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher.js +481 -67
- 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 +605 -56
- 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 +4 -4
- package/dest/sequencer/metrics.d.ts.map +1 -1
- package/dest/sequencer/metrics.js +48 -129
- package/dest/sequencer/sequencer.d.ts +27 -15
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +491 -43
- package/dest/test/index.d.ts +2 -3
- package/dest/test/index.d.ts.map +1 -1
- package/dest/test/mock_checkpoint_builder.d.ts +23 -11
- package/dest/test/mock_checkpoint_builder.d.ts.map +1 -1
- package/dest/test/mock_checkpoint_builder.js +50 -9
- package/dest/test/utils.d.ts +13 -9
- package/dest/test/utils.d.ts.map +1 -1
- package/dest/test/utils.js +26 -17
- package/package.json +30 -28
- package/src/client/sequencer-client.ts +4 -5
- package/src/config.ts +5 -0
- package/src/global_variable_builder/global_builder.ts +13 -13
- package/src/index.ts +1 -9
- package/src/publisher/sequencer-publisher-metrics.ts +17 -69
- package/src/publisher/sequencer-publisher.ts +118 -91
- package/src/sequencer/checkpoint_proposal_job.ts +257 -86
- package/src/sequencer/checkpoint_voter.ts +32 -7
- package/src/sequencer/index.ts +0 -2
- package/src/sequencer/metrics.ts +48 -138
- package/src/sequencer/sequencer.ts +132 -43
- package/src/test/index.ts +1 -2
- package/src/test/mock_checkpoint_builder.ts +91 -29
- package/src/test/utils.ts +55 -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 { L2Block, 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, 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;
|
|
@@ -91,7 +93,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
91
93
|
protected p2pClient: P2P,
|
|
92
94
|
protected worldState: WorldStateSynchronizer,
|
|
93
95
|
protected slasherClient: SlasherClientInterface | undefined,
|
|
94
|
-
protected l2BlockSource: L2BlockSource,
|
|
96
|
+
protected l2BlockSource: L2BlockSource & L2BlockSink,
|
|
95
97
|
protected l1ToL2MessageSource: L1ToL2MessageSource,
|
|
96
98
|
protected checkpointsBuilder: FullNodeCheckpointsBuilder,
|
|
97
99
|
protected l1Constants: SequencerRollupConstants,
|
|
@@ -160,7 +162,6 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
160
162
|
this.log.info('Stopped sequencer');
|
|
161
163
|
}
|
|
162
164
|
|
|
163
|
-
@trackSpan('Sequencer.work')
|
|
164
165
|
/** Main sequencer loop with a try/catch */
|
|
165
166
|
protected async safeWork() {
|
|
166
167
|
try {
|
|
@@ -198,12 +199,13 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
198
199
|
* - Collect attestations for the final block
|
|
199
200
|
* - Submit checkpoint
|
|
200
201
|
*/
|
|
202
|
+
@trackSpan('Sequencer.work')
|
|
201
203
|
protected async work() {
|
|
202
204
|
this.setState(SequencerState.SYNCHRONIZING, undefined);
|
|
203
205
|
const { slot, ts, now, epoch } = this.epochCache.getEpochAndSlotInNextL1Slot();
|
|
204
206
|
|
|
205
207
|
// 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);
|
|
208
|
+
const checkpointProposalJob = await this.prepareCheckpointProposal(epoch, slot, ts, now);
|
|
207
209
|
if (!checkpointProposalJob) {
|
|
208
210
|
return;
|
|
209
211
|
}
|
|
@@ -233,7 +235,9 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
233
235
|
* This is the initial step in the main loop.
|
|
234
236
|
* @returns CheckpointProposalJob if successful, undefined if we are not yet synced or are not the proposer.
|
|
235
237
|
*/
|
|
238
|
+
@trackSpan('Sequencer.prepareCheckpointProposal')
|
|
236
239
|
private async prepareCheckpointProposal(
|
|
240
|
+
epoch: EpochNumber,
|
|
237
241
|
slot: SlotNumber,
|
|
238
242
|
ts: bigint,
|
|
239
243
|
now: bigint,
|
|
@@ -263,8 +267,27 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
263
267
|
return undefined;
|
|
264
268
|
}
|
|
265
269
|
|
|
266
|
-
//
|
|
267
|
-
|
|
270
|
+
// If escape hatch is open for this epoch, do not start checkpoint proposal work and do not attempt invalidations.
|
|
271
|
+
// Still perform governance/slashing voting (as proposer) once per slot.
|
|
272
|
+
const isEscapeHatchOpen = await this.epochCache.isEscapeHatchOpen(epoch);
|
|
273
|
+
|
|
274
|
+
if (isEscapeHatchOpen) {
|
|
275
|
+
this.setState(SequencerState.PROPOSER_CHECK, slot);
|
|
276
|
+
const [canPropose, proposer] = await this.checkCanPropose(slot);
|
|
277
|
+
if (canPropose) {
|
|
278
|
+
await this.tryVoteWhenEscapeHatchOpen({ slot, proposer });
|
|
279
|
+
} else {
|
|
280
|
+
this.log.trace(`Escape hatch open but we are not proposer, skipping vote-only actions`, {
|
|
281
|
+
slot,
|
|
282
|
+
epoch,
|
|
283
|
+
proposer,
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
return undefined;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Next checkpoint follows from the last synced one
|
|
290
|
+
const checkpointNumber = CheckpointNumber(syncedTo.checkpointNumber + 1);
|
|
268
291
|
|
|
269
292
|
const logCtx = {
|
|
270
293
|
now,
|
|
@@ -280,9 +303,9 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
280
303
|
this.setState(SequencerState.PROPOSER_CHECK, slot);
|
|
281
304
|
const [canPropose, proposer] = await this.checkCanPropose(slot);
|
|
282
305
|
|
|
283
|
-
// If we are not a proposer check if we should invalidate
|
|
306
|
+
// If we are not a proposer check if we should invalidate an invalid checkpoint, and bail
|
|
284
307
|
if (!canPropose) {
|
|
285
|
-
await this.
|
|
308
|
+
await this.considerInvalidatingCheckpoint(syncedTo, slot);
|
|
286
309
|
return undefined;
|
|
287
310
|
}
|
|
288
311
|
|
|
@@ -312,15 +335,14 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
312
335
|
}
|
|
313
336
|
|
|
314
337
|
// Prepare invalidation request if the pending chain is invalid (returns undefined if no need)
|
|
315
|
-
|
|
316
|
-
const invalidateBlock = await publisher.simulateInvalidateBlock(syncedTo.pendingChainValidationStatus);
|
|
338
|
+
const invalidateCheckpoint = await publisher.simulateInvalidateCheckpoint(syncedTo.pendingChainValidationStatus);
|
|
317
339
|
|
|
318
340
|
// Check with the rollup contract if we can indeed propose at the next L2 slot. This check should not fail
|
|
319
341
|
// if all the previous checks are good, but we do it just in case.
|
|
320
342
|
const canProposeCheck = await publisher.canProposeAtNextEthBlock(
|
|
321
343
|
syncedTo.archive,
|
|
322
344
|
proposer ?? EthAddress.ZERO,
|
|
323
|
-
|
|
345
|
+
invalidateCheckpoint,
|
|
324
346
|
);
|
|
325
347
|
|
|
326
348
|
if (canProposeCheck === undefined) {
|
|
@@ -358,39 +380,44 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
358
380
|
|
|
359
381
|
// Create and return the checkpoint proposal job
|
|
360
382
|
return this.createCheckpointProposalJob(
|
|
383
|
+
epoch,
|
|
361
384
|
slot,
|
|
362
385
|
checkpointNumber,
|
|
363
386
|
syncedTo.blockNumber,
|
|
364
387
|
proposer,
|
|
365
388
|
publisher,
|
|
366
389
|
attestorAddress,
|
|
367
|
-
|
|
390
|
+
invalidateCheckpoint,
|
|
368
391
|
);
|
|
369
392
|
}
|
|
370
393
|
|
|
371
394
|
protected createCheckpointProposalJob(
|
|
395
|
+
epoch: EpochNumber,
|
|
372
396
|
slot: SlotNumber,
|
|
373
397
|
checkpointNumber: CheckpointNumber,
|
|
374
398
|
syncedToBlockNumber: BlockNumber,
|
|
375
399
|
proposer: EthAddress | undefined,
|
|
376
400
|
publisher: SequencerPublisher,
|
|
377
401
|
attestorAddress: EthAddress,
|
|
378
|
-
|
|
402
|
+
invalidateCheckpoint: InvalidateCheckpointRequest | undefined,
|
|
379
403
|
): CheckpointProposalJob {
|
|
380
404
|
return new CheckpointProposalJob(
|
|
405
|
+
epoch,
|
|
381
406
|
slot,
|
|
382
407
|
checkpointNumber,
|
|
383
408
|
syncedToBlockNumber,
|
|
384
409
|
proposer,
|
|
385
410
|
publisher,
|
|
386
411
|
attestorAddress,
|
|
387
|
-
|
|
412
|
+
invalidateCheckpoint,
|
|
388
413
|
this.validatorClient,
|
|
389
414
|
this.globalsBuilder,
|
|
390
415
|
this.p2pClient,
|
|
391
416
|
this.worldState,
|
|
392
417
|
this.l1ToL2MessageSource,
|
|
418
|
+
this.l2BlockSource,
|
|
393
419
|
this.checkpointsBuilder,
|
|
420
|
+
this.l2BlockSource,
|
|
394
421
|
this.l1Constants,
|
|
395
422
|
this.config,
|
|
396
423
|
this.timetable,
|
|
@@ -400,7 +427,8 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
400
427
|
this.metrics,
|
|
401
428
|
this,
|
|
402
429
|
this.setState.bind(this),
|
|
403
|
-
this.
|
|
430
|
+
this.tracer,
|
|
431
|
+
this.log.getBindings(),
|
|
404
432
|
);
|
|
405
433
|
}
|
|
406
434
|
|
|
@@ -469,9 +497,9 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
469
497
|
number: syncSummary.latestBlockNumber,
|
|
470
498
|
hash: syncSummary.latestBlockHash,
|
|
471
499
|
})),
|
|
472
|
-
this.l2BlockSource.getL2Tips().then(t => t.
|
|
500
|
+
this.l2BlockSource.getL2Tips().then(t => t.proposed),
|
|
473
501
|
this.p2pClient.getStatus().then(p2p => p2p.syncedToL2Block),
|
|
474
|
-
this.l1ToL2MessageSource.getL2Tips().then(t => t.
|
|
502
|
+
this.l1ToL2MessageSource.getL2Tips().then(t => t.proposed),
|
|
475
503
|
this.l2BlockSource.getPendingChainValidationStatus(),
|
|
476
504
|
] as const);
|
|
477
505
|
|
|
@@ -479,6 +507,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
479
507
|
|
|
480
508
|
// 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
509
|
// as the world state can compute the new genesis block hash, but other components use the hardcoded constant.
|
|
510
|
+
// TODO(palla/mbps): Fix the above. All components should be able to handle dynamic genesis block hashes.
|
|
482
511
|
const result =
|
|
483
512
|
(l2BlockSource.number === 0 && worldState.number === 0 && p2p.number === 0 && l1ToL2MessageSource.number === 0) ||
|
|
484
513
|
(worldState.hash === l2BlockSource.hash &&
|
|
@@ -494,10 +523,16 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
494
523
|
const blockNumber = worldState.number;
|
|
495
524
|
if (blockNumber < INITIAL_L2_BLOCK_NUM) {
|
|
496
525
|
const archive = new Fr((await this.worldState.getCommitted().getTreeInfo(MerkleTreeId.ARCHIVE)).root);
|
|
497
|
-
return {
|
|
526
|
+
return {
|
|
527
|
+
checkpointNumber: CheckpointNumber.ZERO,
|
|
528
|
+
blockNumber: BlockNumber.ZERO,
|
|
529
|
+
archive,
|
|
530
|
+
l1Timestamp,
|
|
531
|
+
pendingChainValidationStatus,
|
|
532
|
+
};
|
|
498
533
|
}
|
|
499
534
|
|
|
500
|
-
const block = await this.l2BlockSource.
|
|
535
|
+
const block = await this.l2BlockSource.getL2Block(blockNumber);
|
|
501
536
|
if (!block) {
|
|
502
537
|
// this shouldn't really happen because a moment ago we checked that all components were in sync
|
|
503
538
|
this.log.error(`Failed to get L2 block ${blockNumber} from the archiver with all components in sync`);
|
|
@@ -507,6 +542,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
507
542
|
return {
|
|
508
543
|
block,
|
|
509
544
|
blockNumber: block.number,
|
|
545
|
+
checkpointNumber: block.checkpointNumber,
|
|
510
546
|
archive: block.archive.root,
|
|
511
547
|
l1Timestamp,
|
|
512
548
|
pendingChainValidationStatus,
|
|
@@ -524,7 +560,10 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
524
560
|
proposer = await this.epochCache.getProposerAttesterAddressInSlot(slot);
|
|
525
561
|
} catch (e) {
|
|
526
562
|
if (e instanceof NoCommitteeError) {
|
|
527
|
-
this.
|
|
563
|
+
if (this.lastSlotForNoCommitteeWarning !== slot) {
|
|
564
|
+
this.lastSlotForNoCommitteeWarning = slot;
|
|
565
|
+
this.log.warn(`Cannot propose at next L2 slot ${slot} since the committee does not exist on L1`);
|
|
566
|
+
}
|
|
528
567
|
return [false, undefined];
|
|
529
568
|
}
|
|
530
569
|
this.log.error(`Error getting proposer for slot ${slot}`, e);
|
|
@@ -555,11 +594,12 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
555
594
|
* 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
595
|
* This allows the sequencer to participate in governance/slashing votes even when it cannot build blocks.
|
|
557
596
|
*/
|
|
597
|
+
@trackSpan('Seqeuencer.tryVoteWhenSyncFails', ({ slot }) => ({ [Attributes.SLOT_NUMBER]: slot }))
|
|
558
598
|
protected async tryVoteWhenSyncFails(args: { slot: SlotNumber; ts: bigint }): Promise<void> {
|
|
559
599
|
const { slot } = args;
|
|
560
600
|
|
|
561
601
|
// Prevent duplicate attempts in the same slot
|
|
562
|
-
if (this.
|
|
602
|
+
if (this.lastSlotForFallbackVote === slot) {
|
|
563
603
|
this.log.trace(`Already attempted to vote in slot ${slot} (skipping)`);
|
|
564
604
|
return;
|
|
565
605
|
}
|
|
@@ -591,7 +631,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
591
631
|
}
|
|
592
632
|
|
|
593
633
|
// Mark this slot as attempted
|
|
594
|
-
this.
|
|
634
|
+
this.lastSlotForFallbackVote = slot;
|
|
595
635
|
|
|
596
636
|
// Get a publisher for voting
|
|
597
637
|
const { attestorAddress, publisher } = await this.publisherFactory.create(proposer);
|
|
@@ -625,13 +665,61 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
625
665
|
await publisher.sendRequests();
|
|
626
666
|
}
|
|
627
667
|
|
|
668
|
+
/**
|
|
669
|
+
* Tries to vote on slashing actions and governance proposals when escape hatch is open.
|
|
670
|
+
* This allows the sequencer to participate in voting without performing checkpoint proposal work.
|
|
671
|
+
*/
|
|
672
|
+
@trackSpan('Sequencer.tryVoteWhenEscapeHatchOpen', ({ slot }) => ({ [Attributes.SLOT_NUMBER]: slot }))
|
|
673
|
+
protected async tryVoteWhenEscapeHatchOpen(args: {
|
|
674
|
+
slot: SlotNumber;
|
|
675
|
+
proposer: EthAddress | undefined;
|
|
676
|
+
}): Promise<void> {
|
|
677
|
+
const { slot, proposer } = args;
|
|
678
|
+
|
|
679
|
+
// Prevent duplicate attempts in the same slot
|
|
680
|
+
if (this.lastSlotForFallbackVote === slot) {
|
|
681
|
+
this.log.trace(`Already attempted to vote in slot ${slot} (escape hatch open, skipping)`);
|
|
682
|
+
return;
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
// Mark this slot as attempted
|
|
686
|
+
this.lastSlotForFallbackVote = slot;
|
|
687
|
+
|
|
688
|
+
const { attestorAddress, publisher } = await this.publisherFactory.create(proposer);
|
|
689
|
+
|
|
690
|
+
this.log.debug(`Escape hatch open for slot ${slot}, attempting vote-only actions`, { slot, attestorAddress });
|
|
691
|
+
|
|
692
|
+
const voter = new CheckpointVoter(
|
|
693
|
+
slot,
|
|
694
|
+
publisher,
|
|
695
|
+
attestorAddress,
|
|
696
|
+
this.validatorClient,
|
|
697
|
+
this.slasherClient,
|
|
698
|
+
this.l1Constants,
|
|
699
|
+
this.config,
|
|
700
|
+
this.metrics,
|
|
701
|
+
this.log,
|
|
702
|
+
);
|
|
703
|
+
|
|
704
|
+
const votesPromises = voter.enqueueVotes();
|
|
705
|
+
const votes = await Promise.all(votesPromises);
|
|
706
|
+
|
|
707
|
+
if (votes.every(p => !p)) {
|
|
708
|
+
this.log.debug(`No votes to enqueue for slot ${slot} (escape hatch open)`);
|
|
709
|
+
return;
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
this.log.info(`Voting in slot ${slot} (escape hatch open)`, { slot });
|
|
713
|
+
await publisher.sendRequests();
|
|
714
|
+
}
|
|
715
|
+
|
|
628
716
|
/**
|
|
629
717
|
* Considers invalidating a block if the pending chain is invalid. Depends on how long the invalid block
|
|
630
718
|
* has been there without being invalidated and whether the sequencer is in the committee or not. We always
|
|
631
719
|
* have the proposer try to invalidate, but if they fail, the sequencers in the committee are expected to try,
|
|
632
720
|
* and if they fail, any sequencer will try as well.
|
|
633
721
|
*/
|
|
634
|
-
protected async
|
|
722
|
+
protected async considerInvalidatingCheckpoint(
|
|
635
723
|
syncedTo: SequencerSyncCheckResult,
|
|
636
724
|
currentSlot: SlotNumber,
|
|
637
725
|
): Promise<void> {
|
|
@@ -640,18 +728,18 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
640
728
|
return;
|
|
641
729
|
}
|
|
642
730
|
|
|
643
|
-
const
|
|
644
|
-
const
|
|
645
|
-
const timeSinceChainInvalid = this.dateProvider.nowInSeconds() - Number(
|
|
731
|
+
const invalidCheckpointNumber = pendingChainValidationStatus.checkpoint.checkpointNumber;
|
|
732
|
+
const invalidCheckpointTimestamp = pendingChainValidationStatus.checkpoint.timestamp;
|
|
733
|
+
const timeSinceChainInvalid = this.dateProvider.nowInSeconds() - Number(invalidCheckpointTimestamp);
|
|
646
734
|
const ourValidatorAddresses = this.validatorClient.getValidatorAddresses();
|
|
647
735
|
|
|
648
736
|
const { secondsBeforeInvalidatingBlockAsCommitteeMember, secondsBeforeInvalidatingBlockAsNonCommitteeMember } =
|
|
649
737
|
this.config;
|
|
650
738
|
|
|
651
739
|
const logData = {
|
|
652
|
-
invalidL1Timestamp:
|
|
740
|
+
invalidL1Timestamp: invalidCheckpointTimestamp,
|
|
653
741
|
l1Timestamp,
|
|
654
|
-
|
|
742
|
+
invalidCheckpoint: pendingChainValidationStatus.checkpoint,
|
|
655
743
|
secondsBeforeInvalidatingBlockAsCommitteeMember,
|
|
656
744
|
secondsBeforeInvalidatingBlockAsNonCommitteeMember,
|
|
657
745
|
ourValidatorAddresses,
|
|
@@ -697,25 +785,25 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
697
785
|
|
|
698
786
|
const { publisher } = await this.publisherFactory.create(validatorToUse);
|
|
699
787
|
|
|
700
|
-
const
|
|
701
|
-
if (!
|
|
702
|
-
this.log.warn(`Failed to simulate invalidate
|
|
788
|
+
const invalidateCheckpoint = await publisher.simulateInvalidateCheckpoint(pendingChainValidationStatus);
|
|
789
|
+
if (!invalidateCheckpoint) {
|
|
790
|
+
this.log.warn(`Failed to simulate invalidate checkpoint`, logData);
|
|
703
791
|
return;
|
|
704
792
|
}
|
|
705
793
|
|
|
706
794
|
this.log.info(
|
|
707
795
|
invalidateAsCommitteeMember
|
|
708
|
-
? `Invalidating
|
|
709
|
-
: `Invalidating
|
|
796
|
+
? `Invalidating checkpoint ${invalidCheckpointNumber} as committee member`
|
|
797
|
+
: `Invalidating checkpoint ${invalidCheckpointNumber} as non-committee member`,
|
|
710
798
|
logData,
|
|
711
799
|
);
|
|
712
800
|
|
|
713
|
-
publisher.
|
|
801
|
+
publisher.enqueueInvalidateCheckpoint(invalidateCheckpoint);
|
|
714
802
|
|
|
715
803
|
if (!this.config.fishermanMode) {
|
|
716
804
|
await publisher.sendRequests();
|
|
717
805
|
} else {
|
|
718
|
-
this.log.info('Invalidating
|
|
806
|
+
this.log.info('Invalidating checkpoint in fisherman mode, clearing pending requests');
|
|
719
807
|
publisher.clearPendingRequests();
|
|
720
808
|
}
|
|
721
809
|
}
|
|
@@ -788,9 +876,10 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
788
876
|
}
|
|
789
877
|
|
|
790
878
|
type SequencerSyncCheckResult = {
|
|
791
|
-
block?:
|
|
879
|
+
block?: L2Block;
|
|
880
|
+
checkpointNumber: CheckpointNumber;
|
|
792
881
|
blockNumber: BlockNumber;
|
|
793
882
|
archive: Fr;
|
|
794
883
|
l1Timestamp: bigint;
|
|
795
|
-
pendingChainValidationStatus:
|
|
884
|
+
pendingChainValidationStatus: ValidateCheckpointResult;
|
|
796
885
|
};
|
package/src/test/index.ts
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import type { L1TxUtilsWithBlobs } from '@aztec/ethereum/l1-tx-utils-with-blobs';
|
|
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
8
|
import type { SequencerPublisher } from '../publisher/sequencer-publisher.js';
|
|
9
|
-
import type { FullNodeCheckpointsBuilder } from '../sequencer/checkpoint_builder.js';
|
|
10
9
|
import { Sequencer } from '../sequencer/sequencer.js';
|
|
11
10
|
import type { SequencerTimetable } from '../sequencer/timetable.js';
|
|
12
11
|
|
|
@@ -1,33 +1,32 @@
|
|
|
1
1
|
import { type BlockNumber, CheckpointNumber } from '@aztec/foundation/branded-types';
|
|
2
2
|
import { Fr } from '@aztec/foundation/curves/bn254';
|
|
3
|
-
import {
|
|
4
|
-
import type { FunctionsOf } from '@aztec/foundation/types';
|
|
5
|
-
import { L2BlockNew } from '@aztec/stdlib/block';
|
|
3
|
+
import { L2Block } from '@aztec/stdlib/block';
|
|
6
4
|
import { Checkpoint } from '@aztec/stdlib/checkpoint';
|
|
7
5
|
import { Gas } from '@aztec/stdlib/gas';
|
|
8
|
-
import type {
|
|
6
|
+
import type {
|
|
7
|
+
FullNodeBlockBuilderConfig,
|
|
8
|
+
ICheckpointBlockBuilder,
|
|
9
|
+
ICheckpointsBuilder,
|
|
10
|
+
MerkleTreeWriteOperations,
|
|
11
|
+
PublicProcessorLimits,
|
|
12
|
+
} from '@aztec/stdlib/interfaces/server';
|
|
9
13
|
import { CheckpointHeader } from '@aztec/stdlib/rollup';
|
|
10
14
|
import { makeAppendOnlyTreeSnapshot } from '@aztec/stdlib/testing';
|
|
11
15
|
import type { CheckpointGlobalVariables, Tx } from '@aztec/stdlib/tx';
|
|
12
|
-
|
|
13
|
-
import type {
|
|
14
|
-
BuildBlockInCheckpointResult,
|
|
15
|
-
CheckpointBuilder,
|
|
16
|
-
FullNodeCheckpointsBuilder,
|
|
17
|
-
} from '../sequencer/checkpoint_builder.js';
|
|
16
|
+
import type { BuildBlockInCheckpointResult } from '@aztec/validator-client';
|
|
18
17
|
|
|
19
18
|
/**
|
|
20
19
|
* A fake CheckpointBuilder for testing that implements the same interface as the real one.
|
|
21
20
|
* Can be seeded with blocks to return sequentially on each `buildBlock` call.
|
|
22
21
|
*/
|
|
23
|
-
export class MockCheckpointBuilder implements
|
|
24
|
-
private blocks:
|
|
25
|
-
private builtBlocks:
|
|
22
|
+
export class MockCheckpointBuilder implements ICheckpointBlockBuilder {
|
|
23
|
+
private blocks: L2Block[] = [];
|
|
24
|
+
private builtBlocks: L2Block[] = [];
|
|
26
25
|
private usedTxsPerBlock: Tx[][] = [];
|
|
27
26
|
private blockIndex = 0;
|
|
28
27
|
|
|
29
28
|
/** Optional function to dynamically provide the block (alternative to seedBlocks) */
|
|
30
|
-
private blockProvider: (() =>
|
|
29
|
+
private blockProvider: (() => L2Block) | undefined = undefined;
|
|
31
30
|
|
|
32
31
|
/** Track calls for assertions */
|
|
33
32
|
public buildBlockCalls: Array<{
|
|
@@ -35,6 +34,8 @@ export class MockCheckpointBuilder implements FunctionsOf<CheckpointBuilder> {
|
|
|
35
34
|
timestamp: bigint;
|
|
36
35
|
opts: PublicProcessorLimits;
|
|
37
36
|
}> = [];
|
|
37
|
+
/** Track all consumed transaction hashes across buildBlock calls */
|
|
38
|
+
public consumedTxHashes: Set<string> = new Set();
|
|
38
39
|
public completeCheckpointCalled = false;
|
|
39
40
|
public getCheckpointCalled = false;
|
|
40
41
|
|
|
@@ -47,7 +48,7 @@ export class MockCheckpointBuilder implements FunctionsOf<CheckpointBuilder> {
|
|
|
47
48
|
) {}
|
|
48
49
|
|
|
49
50
|
/** Seed the builder with blocks to return on successive buildBlock calls */
|
|
50
|
-
seedBlocks(blocks:
|
|
51
|
+
seedBlocks(blocks: L2Block[], usedTxsPerBlock?: Tx[][]): this {
|
|
51
52
|
this.blocks = blocks;
|
|
52
53
|
this.usedTxsPerBlock = usedTxsPerBlock ?? blocks.map(() => []);
|
|
53
54
|
this.blockIndex = 0;
|
|
@@ -59,7 +60,7 @@ export class MockCheckpointBuilder implements FunctionsOf<CheckpointBuilder> {
|
|
|
59
60
|
* Set a function that provides blocks dynamically.
|
|
60
61
|
* Useful for tests where the block is determined at call time (e.g., sequencer tests).
|
|
61
62
|
*/
|
|
62
|
-
setBlockProvider(provider: () =>
|
|
63
|
+
setBlockProvider(provider: () => L2Block): this {
|
|
63
64
|
this.blockProvider = provider;
|
|
64
65
|
this.blocks = [];
|
|
65
66
|
return this;
|
|
@@ -69,8 +70,8 @@ export class MockCheckpointBuilder implements FunctionsOf<CheckpointBuilder> {
|
|
|
69
70
|
return this.constants;
|
|
70
71
|
}
|
|
71
72
|
|
|
72
|
-
buildBlock(
|
|
73
|
-
|
|
73
|
+
async buildBlock(
|
|
74
|
+
pendingTxs: Iterable<Tx> | AsyncIterable<Tx>,
|
|
74
75
|
blockNumber: BlockNumber,
|
|
75
76
|
timestamp: bigint,
|
|
76
77
|
opts: PublicProcessorLimits,
|
|
@@ -78,10 +79,10 @@ export class MockCheckpointBuilder implements FunctionsOf<CheckpointBuilder> {
|
|
|
78
79
|
this.buildBlockCalls.push({ blockNumber, timestamp, opts });
|
|
79
80
|
|
|
80
81
|
if (this.errorOnBuild) {
|
|
81
|
-
|
|
82
|
+
throw this.errorOnBuild;
|
|
82
83
|
}
|
|
83
84
|
|
|
84
|
-
let block:
|
|
85
|
+
let block: L2Block;
|
|
85
86
|
let usedTxs: Tx[];
|
|
86
87
|
|
|
87
88
|
if (this.blockProvider) {
|
|
@@ -97,15 +98,28 @@ export class MockCheckpointBuilder implements FunctionsOf<CheckpointBuilder> {
|
|
|
97
98
|
this.builtBlocks.push(block);
|
|
98
99
|
}
|
|
99
100
|
|
|
100
|
-
|
|
101
|
+
// Check that no pending tx has already been consumed
|
|
102
|
+
for await (const tx of pendingTxs) {
|
|
103
|
+
const hash = tx.getTxHash().toString();
|
|
104
|
+
if (this.consumedTxHashes.has(hash)) {
|
|
105
|
+
throw new Error(`Transaction ${hash} was already consumed in a previous block`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Add used txs to consumed set
|
|
110
|
+
for (const tx of usedTxs) {
|
|
111
|
+
this.consumedTxHashes.add(tx.getTxHash().toString());
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return {
|
|
101
115
|
block,
|
|
102
116
|
publicGas: Gas.empty(),
|
|
103
117
|
publicProcessorDuration: 0,
|
|
104
118
|
numTxs: block?.body?.txEffects?.length ?? usedTxs.length,
|
|
105
|
-
blockBuildingTimer: new Timer(),
|
|
106
119
|
usedTxs,
|
|
107
120
|
failedTxs: [],
|
|
108
|
-
|
|
121
|
+
usedTxBlobFields: block?.body?.txEffects?.reduce((sum, tx) => sum + tx.getNumBlobFields(), 0) ?? 0,
|
|
122
|
+
};
|
|
109
123
|
}
|
|
110
124
|
|
|
111
125
|
completeCheckpoint(): Promise<Checkpoint> {
|
|
@@ -147,7 +161,7 @@ export class MockCheckpointBuilder implements FunctionsOf<CheckpointBuilder> {
|
|
|
147
161
|
* Creates a CheckpointHeader from a block's header for testing.
|
|
148
162
|
* This is a simplified version that creates a minimal CheckpointHeader.
|
|
149
163
|
*/
|
|
150
|
-
private createCheckpointHeader(block:
|
|
164
|
+
private createCheckpointHeader(block: L2Block): CheckpointHeader {
|
|
151
165
|
const header = block.header;
|
|
152
166
|
const gv = header.globalVariables;
|
|
153
167
|
return CheckpointHeader.empty({
|
|
@@ -169,6 +183,7 @@ export class MockCheckpointBuilder implements FunctionsOf<CheckpointBuilder> {
|
|
|
169
183
|
this.usedTxsPerBlock = [];
|
|
170
184
|
this.blockIndex = 0;
|
|
171
185
|
this.buildBlockCalls = [];
|
|
186
|
+
this.consumedTxHashes.clear();
|
|
172
187
|
this.completeCheckpointCalled = false;
|
|
173
188
|
this.getCheckpointCalled = false;
|
|
174
189
|
this.errorOnBuild = undefined;
|
|
@@ -181,7 +196,7 @@ export class MockCheckpointBuilder implements FunctionsOf<CheckpointBuilder> {
|
|
|
181
196
|
* as FullNodeCheckpointsBuilder. Returns MockCheckpointBuilder instances.
|
|
182
197
|
* Does NOT use jest mocks - this is a proper test double.
|
|
183
198
|
*/
|
|
184
|
-
export class MockCheckpointsBuilder implements
|
|
199
|
+
export class MockCheckpointsBuilder implements ICheckpointsBuilder {
|
|
185
200
|
private checkpointBuilder: MockCheckpointBuilder | undefined;
|
|
186
201
|
|
|
187
202
|
/** Track calls for assertions */
|
|
@@ -189,6 +204,14 @@ export class MockCheckpointsBuilder implements FunctionsOf<FullNodeCheckpointsBu
|
|
|
189
204
|
checkpointNumber: CheckpointNumber;
|
|
190
205
|
constants: CheckpointGlobalVariables;
|
|
191
206
|
l1ToL2Messages: Fr[];
|
|
207
|
+
previousCheckpointOutHashes: Fr[];
|
|
208
|
+
}> = [];
|
|
209
|
+
public openCheckpointCalls: Array<{
|
|
210
|
+
checkpointNumber: CheckpointNumber;
|
|
211
|
+
constants: CheckpointGlobalVariables;
|
|
212
|
+
l1ToL2Messages: Fr[];
|
|
213
|
+
previousCheckpointOutHashes: Fr[];
|
|
214
|
+
existingBlocks: L2Block[];
|
|
192
215
|
}> = [];
|
|
193
216
|
public updateConfigCalls: Array<Partial<FullNodeBlockBuilderConfig>> = [];
|
|
194
217
|
|
|
@@ -218,6 +241,15 @@ export class MockCheckpointsBuilder implements FunctionsOf<FullNodeCheckpointsBu
|
|
|
218
241
|
return this.checkpointBuilder;
|
|
219
242
|
}
|
|
220
243
|
|
|
244
|
+
getConfig(): FullNodeBlockBuilderConfig {
|
|
245
|
+
return {
|
|
246
|
+
l1GenesisTime: 0n,
|
|
247
|
+
slotDuration: 24,
|
|
248
|
+
l1ChainId: 1,
|
|
249
|
+
rollupVersion: 1,
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
|
|
221
253
|
updateConfig(config: Partial<FullNodeBlockBuilderConfig>): void {
|
|
222
254
|
this.updateConfigCalls.push(config);
|
|
223
255
|
}
|
|
@@ -226,22 +258,52 @@ export class MockCheckpointsBuilder implements FunctionsOf<FullNodeCheckpointsBu
|
|
|
226
258
|
checkpointNumber: CheckpointNumber,
|
|
227
259
|
constants: CheckpointGlobalVariables,
|
|
228
260
|
l1ToL2Messages: Fr[],
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
261
|
+
previousCheckpointOutHashes: Fr[],
|
|
262
|
+
_fork: MerkleTreeWriteOperations,
|
|
263
|
+
): Promise<ICheckpointBlockBuilder> {
|
|
264
|
+
this.startCheckpointCalls.push({ checkpointNumber, constants, l1ToL2Messages, previousCheckpointOutHashes });
|
|
232
265
|
|
|
233
266
|
if (!this.checkpointBuilder) {
|
|
234
267
|
// Auto-create a builder if none was set
|
|
235
268
|
this.checkpointBuilder = new MockCheckpointBuilder(constants, checkpointNumber);
|
|
236
269
|
}
|
|
237
270
|
|
|
238
|
-
return Promise.resolve(this.checkpointBuilder
|
|
271
|
+
return Promise.resolve(this.checkpointBuilder);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
openCheckpoint(
|
|
275
|
+
checkpointNumber: CheckpointNumber,
|
|
276
|
+
constants: CheckpointGlobalVariables,
|
|
277
|
+
l1ToL2Messages: Fr[],
|
|
278
|
+
previousCheckpointOutHashes: Fr[],
|
|
279
|
+
_fork: MerkleTreeWriteOperations,
|
|
280
|
+
existingBlocks: L2Block[] = [],
|
|
281
|
+
): Promise<ICheckpointBlockBuilder> {
|
|
282
|
+
this.openCheckpointCalls.push({
|
|
283
|
+
checkpointNumber,
|
|
284
|
+
constants,
|
|
285
|
+
l1ToL2Messages,
|
|
286
|
+
previousCheckpointOutHashes,
|
|
287
|
+
existingBlocks,
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
if (!this.checkpointBuilder) {
|
|
291
|
+
// Auto-create a builder if none was set
|
|
292
|
+
this.checkpointBuilder = new MockCheckpointBuilder(constants, checkpointNumber);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
return Promise.resolve(this.checkpointBuilder);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
getFork(_blockNumber: BlockNumber): Promise<MerkleTreeWriteOperations> {
|
|
299
|
+
throw new Error('MockCheckpointsBuilder.getFork not implemented');
|
|
239
300
|
}
|
|
240
301
|
|
|
241
302
|
/** Reset for reuse in another test */
|
|
242
303
|
reset(): void {
|
|
243
304
|
this.checkpointBuilder = undefined;
|
|
244
305
|
this.startCheckpointCalls = [];
|
|
306
|
+
this.openCheckpointCalls = [];
|
|
245
307
|
this.updateConfigCalls = [];
|
|
246
308
|
}
|
|
247
309
|
}
|