@aztec/sequencer-client 0.0.1-commit.f295ac2 → 0.0.1-commit.fc805bf
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.js +1 -1
- package/dest/config.d.ts +1 -1
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +1 -3
- package/dest/global_variable_builder/global_builder.js +2 -2
- package/dest/publisher/sequencer-publisher-metrics.d.ts +1 -1
- package/dest/publisher/sequencer-publisher-metrics.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher-metrics.js +12 -4
- package/dest/publisher/sequencer-publisher.d.ts +1 -2
- package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher.js +39 -18
- package/dest/sequencer/checkpoint_proposal_job.d.ts +26 -9
- package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -1
- package/dest/sequencer/checkpoint_proposal_job.js +59 -22
- package/dest/sequencer/metrics.d.ts +2 -2
- package/dest/sequencer/metrics.d.ts.map +1 -1
- package/dest/sequencer/metrics.js +27 -17
- package/dest/sequencer/sequencer.d.ts +3 -3
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +2 -2
- package/dest/test/mock_checkpoint_builder.d.ts +11 -8
- package/dest/test/mock_checkpoint_builder.d.ts.map +1 -1
- package/dest/test/mock_checkpoint_builder.js +18 -6
- package/dest/test/utils.d.ts +8 -8
- package/dest/test/utils.d.ts.map +1 -1
- package/dest/test/utils.js +5 -5
- package/package.json +28 -28
- package/src/client/sequencer-client.ts +1 -1
- package/src/config.ts +1 -3
- package/src/global_variable_builder/global_builder.ts +2 -2
- package/src/publisher/sequencer-publisher-metrics.ts +7 -3
- package/src/publisher/sequencer-publisher.ts +34 -18
- package/src/sequencer/checkpoint_proposal_job.ts +86 -48
- package/src/sequencer/metrics.ts +36 -18
- package/src/sequencer/sequencer.ts +4 -4
- package/src/test/mock_checkpoint_builder.ts +32 -18
- package/src/test/utils.ts +17 -11
|
@@ -1,22 +1,28 @@
|
|
|
1
1
|
import { NUM_CHECKPOINT_END_MARKER_FIELDS, getNumBlockEndBlobFields } from '@aztec/blob-lib/encoding';
|
|
2
2
|
import { BLOBS_PER_CHECKPOINT, FIELDS_PER_BLOB } from '@aztec/constants';
|
|
3
3
|
import type { EpochCache } from '@aztec/epoch-cache';
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
BlockNumber,
|
|
6
|
+
CheckpointNumber,
|
|
7
|
+
EpochNumber,
|
|
8
|
+
IndexWithinCheckpoint,
|
|
9
|
+
SlotNumber,
|
|
10
|
+
} from '@aztec/foundation/branded-types';
|
|
5
11
|
import { randomInt } from '@aztec/foundation/crypto/random';
|
|
6
12
|
import { Fr } from '@aztec/foundation/curves/bn254';
|
|
7
13
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
8
14
|
import { Signature } from '@aztec/foundation/eth-signature';
|
|
9
15
|
import { filter } from '@aztec/foundation/iterator';
|
|
10
|
-
import type
|
|
16
|
+
import { type Logger, type LoggerBindings, createLogger } from '@aztec/foundation/log';
|
|
11
17
|
import { sleep, sleepUntil } from '@aztec/foundation/sleep';
|
|
12
18
|
import { type DateProvider, Timer } from '@aztec/foundation/timer';
|
|
13
|
-
import { type TypedEventEmitter, unfreeze } from '@aztec/foundation/types';
|
|
19
|
+
import { type TypedEventEmitter, isErrorClass, unfreeze } from '@aztec/foundation/types';
|
|
14
20
|
import type { P2P } from '@aztec/p2p';
|
|
15
21
|
import type { SlasherClientInterface } from '@aztec/slasher';
|
|
16
22
|
import {
|
|
17
23
|
CommitteeAttestation,
|
|
18
24
|
CommitteeAttestationsAndSigners,
|
|
19
|
-
|
|
25
|
+
L2Block,
|
|
20
26
|
type L2BlockSink,
|
|
21
27
|
type L2BlockSource,
|
|
22
28
|
MaliciousCommitteeAttestationsAndSigners,
|
|
@@ -24,10 +30,11 @@ import {
|
|
|
24
30
|
import type { Checkpoint } from '@aztec/stdlib/checkpoint';
|
|
25
31
|
import { getSlotStartBuildTimestamp } from '@aztec/stdlib/epoch-helpers';
|
|
26
32
|
import { Gas } from '@aztec/stdlib/gas';
|
|
27
|
-
import
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
33
|
+
import {
|
|
34
|
+
NoValidTxsError,
|
|
35
|
+
type PublicProcessorLimits,
|
|
36
|
+
type ResolvedSequencerConfig,
|
|
37
|
+
type WorldStateSynchronizer,
|
|
31
38
|
} from '@aztec/stdlib/interfaces/server';
|
|
32
39
|
import { type L1ToL2MessageSource, computeInHashFromL1ToL2Messages } from '@aztec/stdlib/messaging';
|
|
33
40
|
import type { BlockProposalOptions, CheckpointProposal, CheckpointProposalOptions } from '@aztec/stdlib/p2p';
|
|
@@ -59,6 +66,8 @@ const TXS_POLLING_MS = 500;
|
|
|
59
66
|
* the Sequencer once the check for being the proposer for the slot has succeeded.
|
|
60
67
|
*/
|
|
61
68
|
export class CheckpointProposalJob implements Traceable {
|
|
69
|
+
protected readonly log: Logger;
|
|
70
|
+
|
|
62
71
|
constructor(
|
|
63
72
|
private readonly epoch: EpochNumber,
|
|
64
73
|
private readonly slot: SlotNumber,
|
|
@@ -86,9 +95,11 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
86
95
|
private readonly metrics: SequencerMetrics,
|
|
87
96
|
private readonly eventEmitter: TypedEventEmitter<SequencerEvents>,
|
|
88
97
|
private readonly setStateFn: (state: SequencerState, slot?: SlotNumber) => void,
|
|
89
|
-
protected readonly log: Logger,
|
|
90
98
|
public readonly tracer: Tracer,
|
|
91
|
-
|
|
99
|
+
bindings?: LoggerBindings,
|
|
100
|
+
) {
|
|
101
|
+
this.log = createLogger('sequencer:checkpoint-proposal', { ...bindings, instanceId: `slot-${slot}` });
|
|
102
|
+
}
|
|
92
103
|
|
|
93
104
|
/**
|
|
94
105
|
* Executes the checkpoint proposal job.
|
|
@@ -190,6 +201,7 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
190
201
|
l1ToL2Messages,
|
|
191
202
|
previousCheckpointOutHashes,
|
|
192
203
|
fork,
|
|
204
|
+
this.log.getBindings(),
|
|
193
205
|
);
|
|
194
206
|
|
|
195
207
|
// Options for the validator client when creating block and checkpoint proposals
|
|
@@ -203,8 +215,8 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
203
215
|
broadcastInvalidCheckpointProposal: this.config.broadcastInvalidBlockProposal,
|
|
204
216
|
};
|
|
205
217
|
|
|
206
|
-
let blocksInCheckpoint:
|
|
207
|
-
let blockPendingBroadcast: { block:
|
|
218
|
+
let blocksInCheckpoint: L2Block[] = [];
|
|
219
|
+
let blockPendingBroadcast: { block: L2Block; txs: Tx[] } | undefined = undefined;
|
|
208
220
|
|
|
209
221
|
try {
|
|
210
222
|
// Main loop: build blocks for the checkpoint
|
|
@@ -332,6 +344,11 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
332
344
|
|
|
333
345
|
return checkpoint;
|
|
334
346
|
} catch (err) {
|
|
347
|
+
if (err && (err instanceof DutyAlreadySignedError || err instanceof SlashingProtectionError)) {
|
|
348
|
+
// swallow this error. It's already been logged by a function deeper in the stack
|
|
349
|
+
return undefined;
|
|
350
|
+
}
|
|
351
|
+
|
|
335
352
|
this.log.error(`Error building checkpoint at slot ${this.slot}`, err);
|
|
336
353
|
return undefined;
|
|
337
354
|
}
|
|
@@ -347,10 +364,10 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
347
364
|
inHash: Fr,
|
|
348
365
|
blockProposalOptions: BlockProposalOptions,
|
|
349
366
|
): Promise<{
|
|
350
|
-
blocksInCheckpoint:
|
|
351
|
-
blockPendingBroadcast: { block:
|
|
367
|
+
blocksInCheckpoint: L2Block[];
|
|
368
|
+
blockPendingBroadcast: { block: L2Block; txs: Tx[] } | undefined;
|
|
352
369
|
}> {
|
|
353
|
-
const blocksInCheckpoint:
|
|
370
|
+
const blocksInCheckpoint: L2Block[] = [];
|
|
354
371
|
const txHashesAlreadyIncluded = new Set<string>();
|
|
355
372
|
const initialBlockNumber = BlockNumber(this.syncedToBlockNumber + 1);
|
|
356
373
|
|
|
@@ -358,11 +375,11 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
358
375
|
let remainingBlobFields = BLOBS_PER_CHECKPOINT * FIELDS_PER_BLOB - NUM_CHECKPOINT_END_MARKER_FIELDS;
|
|
359
376
|
|
|
360
377
|
// Last block in the checkpoint will usually be flagged as pending broadcast, so we send it along with the checkpoint proposal
|
|
361
|
-
let blockPendingBroadcast: { block:
|
|
378
|
+
let blockPendingBroadcast: { block: L2Block; txs: Tx[] } | undefined = undefined;
|
|
362
379
|
|
|
363
380
|
while (true) {
|
|
364
381
|
const blocksBuilt = blocksInCheckpoint.length;
|
|
365
|
-
const indexWithinCheckpoint = blocksBuilt;
|
|
382
|
+
const indexWithinCheckpoint = IndexWithinCheckpoint(blocksBuilt);
|
|
366
383
|
const blockNumber = BlockNumber(initialBlockNumber + blocksBuilt);
|
|
367
384
|
|
|
368
385
|
const secondsIntoSlot = this.getSecondsIntoSlot();
|
|
@@ -392,6 +409,7 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
392
409
|
remainingBlobFields,
|
|
393
410
|
});
|
|
394
411
|
|
|
412
|
+
// TODO(palla/mbps): Review these conditions. We may want to keep trying in some scenarios.
|
|
395
413
|
if (!buildResult && timingInfo.isLastBlock) {
|
|
396
414
|
// If no block was produced due to not enough txs and this was the last subslot, exit
|
|
397
415
|
break;
|
|
@@ -423,7 +441,12 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
423
441
|
// Sync the proposed block to the archiver to make it available
|
|
424
442
|
// Note that the checkpoint builder uses its own fork so it should not need to wait for this syncing
|
|
425
443
|
// Eventually we should refactor the checkpoint builder to not need a separate long-lived fork
|
|
426
|
-
|
|
444
|
+
// Fire and forget - don't block the critical path, but log errors
|
|
445
|
+
this.syncProposedBlockToArchiver(block).catch(err => {
|
|
446
|
+
this.log.error(`Failed to sync proposed block ${block.number} to archiver`, { blockNumber: block.number, err });
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
usedTxs.forEach(tx => txHashesAlreadyIncluded.add(tx.txHash.toString()));
|
|
427
450
|
|
|
428
451
|
// If this is the last block, exit the loop now so we start collecting attestations
|
|
429
452
|
if (timingInfo.isLastBlock) {
|
|
@@ -473,18 +496,18 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
473
496
|
|
|
474
497
|
/** Builds a single block. Called from the main block building loop. */
|
|
475
498
|
@trackSpan('CheckpointProposalJob.buildSingleBlock')
|
|
476
|
-
|
|
499
|
+
protected async buildSingleBlock(
|
|
477
500
|
checkpointBuilder: CheckpointBuilder,
|
|
478
501
|
opts: {
|
|
479
502
|
forceCreate?: boolean;
|
|
480
503
|
blockTimestamp: bigint;
|
|
481
504
|
blockNumber: BlockNumber;
|
|
482
|
-
indexWithinCheckpoint:
|
|
505
|
+
indexWithinCheckpoint: IndexWithinCheckpoint;
|
|
483
506
|
buildDeadline: Date | undefined;
|
|
484
507
|
txHashesAlreadyIncluded: Set<string>;
|
|
485
508
|
remainingBlobFields: number;
|
|
486
509
|
},
|
|
487
|
-
): Promise<{ block:
|
|
510
|
+
): Promise<{ block: L2Block; usedTxs: Tx[]; remainingBlobFields: number } | { error: Error } | undefined> {
|
|
488
511
|
const {
|
|
489
512
|
blockTimestamp,
|
|
490
513
|
forceCreate,
|
|
@@ -540,45 +563,38 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
540
563
|
};
|
|
541
564
|
|
|
542
565
|
// Actually build the block by executing txs
|
|
543
|
-
const
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
usedTxs,
|
|
551
|
-
failedTxs,
|
|
552
|
-
usedTxBlobFields,
|
|
553
|
-
} = await checkpointBuilder.buildBlock(pendingTxs, blockNumber, blockTimestamp, blockBuilderOptions);
|
|
554
|
-
const blockBuildDuration = workTimer.ms();
|
|
566
|
+
const buildResult = await this.buildSingleBlockWithCheckpointBuilder(
|
|
567
|
+
checkpointBuilder,
|
|
568
|
+
pendingTxs,
|
|
569
|
+
blockNumber,
|
|
570
|
+
blockTimestamp,
|
|
571
|
+
blockBuilderOptions,
|
|
572
|
+
);
|
|
555
573
|
|
|
556
574
|
// If any txs failed during execution, drop them from the mempool so we don't pick them up again
|
|
557
|
-
await this.dropFailedTxsFromP2P(failedTxs);
|
|
575
|
+
await this.dropFailedTxsFromP2P(buildResult.failedTxs);
|
|
558
576
|
|
|
559
577
|
// Check if we have created a block with enough txs. If there were invalid txs in the pool, or if execution took
|
|
560
578
|
// too long, then we may not get to minTxsPerBlock after executing public functions.
|
|
561
579
|
const minValidTxs = this.config.minValidTxsPerBlock ?? minTxs;
|
|
562
|
-
|
|
580
|
+
const numTxs = buildResult.status === 'no-valid-txs' ? 0 : buildResult.numTxs;
|
|
581
|
+
if (buildResult.status === 'no-valid-txs' || (!forceCreate && numTxs < minValidTxs)) {
|
|
563
582
|
this.log.warn(
|
|
564
|
-
`Block ${blockNumber} at index ${indexWithinCheckpoint} on slot ${this.slot} has too few valid txs to be proposed
|
|
565
|
-
{ slot: this.slot, blockNumber, numTxs, indexWithinCheckpoint },
|
|
583
|
+
`Block ${blockNumber} at index ${indexWithinCheckpoint} on slot ${this.slot} has too few valid txs to be proposed`,
|
|
584
|
+
{ slot: this.slot, blockNumber, numTxs, indexWithinCheckpoint, minValidTxs, buildResult: buildResult.status },
|
|
566
585
|
);
|
|
567
|
-
this.eventEmitter.emit('block-
|
|
568
|
-
minTxs: minValidTxs,
|
|
569
|
-
availableTxs: numTxs,
|
|
570
|
-
slot: this.slot,
|
|
571
|
-
});
|
|
586
|
+
this.eventEmitter.emit('block-build-failed', { reason: `Insufficient valid txs`, slot: this.slot });
|
|
572
587
|
this.metrics.recordBlockProposalFailed('insufficient_valid_txs');
|
|
573
588
|
return undefined;
|
|
574
589
|
}
|
|
575
590
|
|
|
576
591
|
// Block creation succeeded, emit stats and metrics
|
|
592
|
+
const { publicGas, block, publicProcessorDuration, usedTxs, usedTxBlobFields, blockBuildDuration } = buildResult;
|
|
593
|
+
|
|
577
594
|
const blockStats = {
|
|
578
595
|
eventName: 'l2-block-built',
|
|
579
596
|
duration: blockBuildDuration,
|
|
580
597
|
publicProcessDuration: publicProcessorDuration,
|
|
581
|
-
rollupCircuitsDuration: blockBuildingTimer.ms(),
|
|
582
598
|
...block.getStats(),
|
|
583
599
|
} satisfies L2BlockBuiltStats;
|
|
584
600
|
|
|
@@ -604,17 +620,40 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
604
620
|
}
|
|
605
621
|
}
|
|
606
622
|
|
|
623
|
+
/** Uses the checkpoint builder to build a block, catching specific txs */
|
|
624
|
+
private async buildSingleBlockWithCheckpointBuilder(
|
|
625
|
+
checkpointBuilder: CheckpointBuilder,
|
|
626
|
+
pendingTxs: AsyncIterable<Tx>,
|
|
627
|
+
blockNumber: BlockNumber,
|
|
628
|
+
blockTimestamp: bigint,
|
|
629
|
+
blockBuilderOptions: PublicProcessorLimits,
|
|
630
|
+
) {
|
|
631
|
+
try {
|
|
632
|
+
const workTimer = new Timer();
|
|
633
|
+
const result = await checkpointBuilder.buildBlock(pendingTxs, blockNumber, blockTimestamp, blockBuilderOptions);
|
|
634
|
+
const blockBuildDuration = workTimer.ms();
|
|
635
|
+
return { ...result, blockBuildDuration, status: 'success' as const };
|
|
636
|
+
} catch (err: unknown) {
|
|
637
|
+
if (isErrorClass(err, NoValidTxsError)) {
|
|
638
|
+
return { failedTxs: err.failedTxs, status: 'no-valid-txs' as const };
|
|
639
|
+
}
|
|
640
|
+
throw err;
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
|
|
607
644
|
/** Waits until minTxs are available on the pool for building a block. */
|
|
608
645
|
@trackSpan('CheckpointProposalJob.waitForMinTxs')
|
|
609
646
|
private async waitForMinTxs(opts: {
|
|
610
647
|
forceCreate?: boolean;
|
|
611
648
|
blockNumber: BlockNumber;
|
|
612
|
-
indexWithinCheckpoint:
|
|
649
|
+
indexWithinCheckpoint: IndexWithinCheckpoint;
|
|
613
650
|
buildDeadline: Date | undefined;
|
|
614
651
|
}): Promise<{ canStartBuilding: boolean; availableTxs: number }> {
|
|
615
|
-
const minTxs = this.config.minTxsPerBlock;
|
|
616
652
|
const { indexWithinCheckpoint, blockNumber, buildDeadline, forceCreate } = opts;
|
|
617
653
|
|
|
654
|
+
// We only allow a block with 0 txs in the first block of the checkpoint
|
|
655
|
+
const minTxs = indexWithinCheckpoint > 0 && this.config.minTxsPerBlock === 0 ? 1 : this.config.minTxsPerBlock;
|
|
656
|
+
|
|
618
657
|
// Deadline is undefined if we are not enforcing the timetable, meaning we'll exit immediately when out of time
|
|
619
658
|
const startBuildingDeadline = buildDeadline
|
|
620
659
|
? new Date(buildDeadline.getTime() - this.timetable.minExecutionTime * 1000)
|
|
@@ -676,7 +715,7 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
676
715
|
const attestationTimeAllowed = this.config.enforceTimeTable
|
|
677
716
|
? this.timetable.getMaxAllowedTime(SequencerState.PUBLISHING_CHECKPOINT)!
|
|
678
717
|
: this.l1Constants.slotDuration;
|
|
679
|
-
const attestationDeadline = new Date(this.
|
|
718
|
+
const attestationDeadline = new Date((this.getSlotStartBuildTimestamp() + attestationTimeAllowed) * 1000);
|
|
680
719
|
|
|
681
720
|
this.metrics.recordRequiredAttestations(numberOfRequiredAttestations, attestationTimeAllowed);
|
|
682
721
|
|
|
@@ -772,8 +811,7 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
772
811
|
* Gossip doesn't echo messages back to the sender, so the proposer's archiver/world-state
|
|
773
812
|
* would never receive its own block without this explicit sync.
|
|
774
813
|
*/
|
|
775
|
-
private async syncProposedBlockToArchiver(block:
|
|
776
|
-
// TODO(palla/mbps): Change default to false once block sync is stable.
|
|
814
|
+
private async syncProposedBlockToArchiver(block: L2Block): Promise<void> {
|
|
777
815
|
if (this.config.skipPushProposedBlocksToArchiver !== false) {
|
|
778
816
|
this.log.warn(`Skipping push of proposed block ${block.number} to archiver`, {
|
|
779
817
|
blockNumber: block.number,
|
package/src/sequencer/metrics.ts
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
type TelemetryClient,
|
|
12
12
|
type Tracer,
|
|
13
13
|
type UpDownCounter,
|
|
14
|
+
createUpDownCounterWithDefault,
|
|
14
15
|
} from '@aztec/telemetry-client';
|
|
15
16
|
|
|
16
17
|
import { type Hex, formatUnits } from 'viem';
|
|
@@ -67,7 +68,9 @@ export class SequencerMetrics {
|
|
|
67
68
|
this.meter = client.getMeter(name);
|
|
68
69
|
this.tracer = client.getTracer(name);
|
|
69
70
|
|
|
70
|
-
this.blockCounter = this.meter
|
|
71
|
+
this.blockCounter = createUpDownCounterWithDefault(this.meter, Metrics.SEQUENCER_BLOCK_COUNT, {
|
|
72
|
+
[Attributes.STATUS]: ['failed', 'built'],
|
|
73
|
+
});
|
|
71
74
|
|
|
72
75
|
this.blockBuildDuration = this.meter.createHistogram(Metrics.SEQUENCER_BLOCK_BUILD_DURATION);
|
|
73
76
|
|
|
@@ -77,23 +80,15 @@ export class SequencerMetrics {
|
|
|
77
80
|
|
|
78
81
|
this.checkpointAttestationDelay = this.meter.createHistogram(Metrics.SEQUENCER_CHECKPOINT_ATTESTATION_DELAY);
|
|
79
82
|
|
|
80
|
-
// Init gauges and counters
|
|
81
|
-
this.blockCounter.add(0, {
|
|
82
|
-
[Attributes.STATUS]: 'failed',
|
|
83
|
-
});
|
|
84
|
-
this.blockCounter.add(0, {
|
|
85
|
-
[Attributes.STATUS]: 'built',
|
|
86
|
-
});
|
|
87
|
-
|
|
88
83
|
this.rewards = this.meter.createGauge(Metrics.SEQUENCER_CURRENT_BLOCK_REWARDS);
|
|
89
84
|
|
|
90
|
-
this.slots = this.meter
|
|
85
|
+
this.slots = createUpDownCounterWithDefault(this.meter, Metrics.SEQUENCER_SLOT_COUNT);
|
|
91
86
|
|
|
92
87
|
/**
|
|
93
88
|
* NOTE: we do not track missed slots as a separate metric. That would be difficult to determine
|
|
94
89
|
* Instead, use a computed metric, `slots - filledSlots` to get the number of slots a sequencer has missed.
|
|
95
90
|
*/
|
|
96
|
-
this.filledSlots = this.meter
|
|
91
|
+
this.filledSlots = createUpDownCounterWithDefault(this.meter, Metrics.SEQUENCER_FILLED_SLOT_COUNT);
|
|
97
92
|
|
|
98
93
|
this.timeToCollectAttestations = this.meter.createGauge(Metrics.SEQUENCER_COLLECT_ATTESTATIONS_DURATION);
|
|
99
94
|
|
|
@@ -103,20 +98,41 @@ export class SequencerMetrics {
|
|
|
103
98
|
|
|
104
99
|
this.collectedAttestions = this.meter.createGauge(Metrics.SEQUENCER_COLLECTED_ATTESTATIONS_COUNT);
|
|
105
100
|
|
|
106
|
-
this.blockProposalFailed =
|
|
101
|
+
this.blockProposalFailed = createUpDownCounterWithDefault(
|
|
102
|
+
this.meter,
|
|
103
|
+
Metrics.SEQUENCER_BLOCK_PROPOSAL_FAILED_COUNT,
|
|
104
|
+
);
|
|
107
105
|
|
|
108
|
-
this.blockProposalSuccess =
|
|
106
|
+
this.blockProposalSuccess = createUpDownCounterWithDefault(
|
|
107
|
+
this.meter,
|
|
108
|
+
Metrics.SEQUENCER_BLOCK_PROPOSAL_SUCCESS_COUNT,
|
|
109
|
+
);
|
|
109
110
|
|
|
110
|
-
this.checkpointSuccess = this.meter
|
|
111
|
+
this.checkpointSuccess = createUpDownCounterWithDefault(this.meter, Metrics.SEQUENCER_CHECKPOINT_SUCCESS_COUNT);
|
|
111
112
|
|
|
112
|
-
this.blockProposalPrecheckFailed =
|
|
113
|
+
this.blockProposalPrecheckFailed = createUpDownCounterWithDefault(
|
|
114
|
+
this.meter,
|
|
113
115
|
Metrics.SEQUENCER_BLOCK_PROPOSAL_PRECHECK_FAILED_COUNT,
|
|
116
|
+
{
|
|
117
|
+
[Attributes.ERROR_TYPE]: [
|
|
118
|
+
'slot_already_taken',
|
|
119
|
+
'rollup_contract_check_failed',
|
|
120
|
+
'slot_mismatch',
|
|
121
|
+
'block_number_mismatch',
|
|
122
|
+
],
|
|
123
|
+
},
|
|
114
124
|
);
|
|
115
125
|
|
|
116
|
-
this.slashingAttempts = this.meter
|
|
126
|
+
this.slashingAttempts = createUpDownCounterWithDefault(this.meter, Metrics.SEQUENCER_SLASHING_ATTEMPTS_COUNT);
|
|
117
127
|
|
|
118
128
|
// Fisherman fee analysis metrics
|
|
119
|
-
this.fishermanWouldBeIncluded =
|
|
129
|
+
this.fishermanWouldBeIncluded = createUpDownCounterWithDefault(
|
|
130
|
+
this.meter,
|
|
131
|
+
Metrics.FISHERMAN_FEE_ANALYSIS_WOULD_BE_INCLUDED,
|
|
132
|
+
{
|
|
133
|
+
[Attributes.OK]: [true, false],
|
|
134
|
+
},
|
|
135
|
+
);
|
|
120
136
|
|
|
121
137
|
this.fishermanTimeBeforeBlock = this.meter.createHistogram(Metrics.FISHERMAN_FEE_ANALYSIS_TIME_BEFORE_BLOCK);
|
|
122
138
|
|
|
@@ -231,7 +247,9 @@ export class SequencerMetrics {
|
|
|
231
247
|
this.blockProposalSuccess.add(1);
|
|
232
248
|
}
|
|
233
249
|
|
|
234
|
-
recordBlockProposalPrecheckFailed(
|
|
250
|
+
recordBlockProposalPrecheckFailed(
|
|
251
|
+
checkType: 'slot_already_taken' | 'rollup_contract_check_failed' | 'slot_mismatch' | 'block_number_mismatch',
|
|
252
|
+
) {
|
|
235
253
|
this.blockProposalPrecheckFailed.add(1, {
|
|
236
254
|
[Attributes.ERROR_TYPE]: checkType,
|
|
237
255
|
});
|
|
@@ -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 {
|
|
@@ -424,8 +424,8 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
424
424
|
this.metrics,
|
|
425
425
|
this,
|
|
426
426
|
this.setState.bind(this),
|
|
427
|
-
this.log,
|
|
428
427
|
this.tracer,
|
|
428
|
+
this.log.getBindings(),
|
|
429
429
|
);
|
|
430
430
|
}
|
|
431
431
|
|
|
@@ -529,7 +529,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
529
529
|
};
|
|
530
530
|
}
|
|
531
531
|
|
|
532
|
-
const block = await this.l2BlockSource.
|
|
532
|
+
const block = await this.l2BlockSource.getL2Block(blockNumber);
|
|
533
533
|
if (!block) {
|
|
534
534
|
// this shouldn't really happen because a moment ago we checked that all components were in sync
|
|
535
535
|
this.log.error(`Failed to get L2 block ${blockNumber} from the archiver with all components in sync`);
|
|
@@ -870,7 +870,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
870
870
|
}
|
|
871
871
|
|
|
872
872
|
type SequencerSyncCheckResult = {
|
|
873
|
-
block?:
|
|
873
|
+
block?: L2Block;
|
|
874
874
|
checkpointNumber: CheckpointNumber;
|
|
875
875
|
blockNumber: BlockNumber;
|
|
876
876
|
archive: Fr;
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import { type BlockNumber, CheckpointNumber } from '@aztec/foundation/branded-types';
|
|
2
2
|
import { Fr } from '@aztec/foundation/curves/bn254';
|
|
3
|
-
import {
|
|
4
|
-
import { L2BlockNew } from '@aztec/stdlib/block';
|
|
3
|
+
import { L2Block } from '@aztec/stdlib/block';
|
|
5
4
|
import { Checkpoint } from '@aztec/stdlib/checkpoint';
|
|
6
5
|
import { Gas } from '@aztec/stdlib/gas';
|
|
7
6
|
import type {
|
|
8
|
-
BuildBlockInCheckpointResult,
|
|
9
7
|
FullNodeBlockBuilderConfig,
|
|
10
8
|
ICheckpointBlockBuilder,
|
|
11
9
|
ICheckpointsBuilder,
|
|
@@ -15,19 +13,20 @@ import type {
|
|
|
15
13
|
import { CheckpointHeader } from '@aztec/stdlib/rollup';
|
|
16
14
|
import { makeAppendOnlyTreeSnapshot } from '@aztec/stdlib/testing';
|
|
17
15
|
import type { CheckpointGlobalVariables, Tx } from '@aztec/stdlib/tx';
|
|
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
22
|
export class MockCheckpointBuilder implements ICheckpointBlockBuilder {
|
|
24
|
-
private blocks:
|
|
25
|
-
private builtBlocks:
|
|
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 ICheckpointBlockBuilder {
|
|
|
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 ICheckpointBlockBuilder {
|
|
|
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 ICheckpointBlockBuilder {
|
|
|
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 ICheckpointBlockBuilder {
|
|
|
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 ICheckpointBlockBuilder {
|
|
|
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,16 +98,28 @@ export class MockCheckpointBuilder implements ICheckpointBlockBuilder {
|
|
|
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,
|
|
109
|
-
}
|
|
122
|
+
};
|
|
110
123
|
}
|
|
111
124
|
|
|
112
125
|
completeCheckpoint(): Promise<Checkpoint> {
|
|
@@ -148,7 +161,7 @@ export class MockCheckpointBuilder implements ICheckpointBlockBuilder {
|
|
|
148
161
|
* Creates a CheckpointHeader from a block's header for testing.
|
|
149
162
|
* This is a simplified version that creates a minimal CheckpointHeader.
|
|
150
163
|
*/
|
|
151
|
-
private createCheckpointHeader(block:
|
|
164
|
+
private createCheckpointHeader(block: L2Block): CheckpointHeader {
|
|
152
165
|
const header = block.header;
|
|
153
166
|
const gv = header.globalVariables;
|
|
154
167
|
return CheckpointHeader.empty({
|
|
@@ -170,6 +183,7 @@ export class MockCheckpointBuilder implements ICheckpointBlockBuilder {
|
|
|
170
183
|
this.usedTxsPerBlock = [];
|
|
171
184
|
this.blockIndex = 0;
|
|
172
185
|
this.buildBlockCalls = [];
|
|
186
|
+
this.consumedTxHashes.clear();
|
|
173
187
|
this.completeCheckpointCalled = false;
|
|
174
188
|
this.getCheckpointCalled = false;
|
|
175
189
|
this.errorOnBuild = undefined;
|
|
@@ -197,7 +211,7 @@ export class MockCheckpointsBuilder implements ICheckpointsBuilder {
|
|
|
197
211
|
constants: CheckpointGlobalVariables;
|
|
198
212
|
l1ToL2Messages: Fr[];
|
|
199
213
|
previousCheckpointOutHashes: Fr[];
|
|
200
|
-
existingBlocks:
|
|
214
|
+
existingBlocks: L2Block[];
|
|
201
215
|
}> = [];
|
|
202
216
|
public updateConfigCalls: Array<Partial<FullNodeBlockBuilderConfig>> = [];
|
|
203
217
|
|
|
@@ -263,7 +277,7 @@ export class MockCheckpointsBuilder implements ICheckpointsBuilder {
|
|
|
263
277
|
l1ToL2Messages: Fr[],
|
|
264
278
|
previousCheckpointOutHashes: Fr[],
|
|
265
279
|
_fork: MerkleTreeWriteOperations,
|
|
266
|
-
existingBlocks:
|
|
280
|
+
existingBlocks: L2Block[] = [],
|
|
267
281
|
): Promise<ICheckpointBlockBuilder> {
|
|
268
282
|
this.openCheckpointCalls.push({
|
|
269
283
|
checkpointNumber,
|
package/src/test/utils.ts
CHANGED
|
@@ -7,7 +7,7 @@ import type { EthAddress } from '@aztec/foundation/eth-address';
|
|
|
7
7
|
import { Signature } from '@aztec/foundation/eth-signature';
|
|
8
8
|
import type { P2P } from '@aztec/p2p';
|
|
9
9
|
import { PublicDataWrite } from '@aztec/stdlib/avm';
|
|
10
|
-
import { CommitteeAttestation,
|
|
10
|
+
import { CommitteeAttestation, L2Block } from '@aztec/stdlib/block';
|
|
11
11
|
import { BlockProposal, CheckpointAttestation, CheckpointProposal, ConsensusPayload } from '@aztec/stdlib/p2p';
|
|
12
12
|
import { CheckpointHeader } from '@aztec/stdlib/rollup';
|
|
13
13
|
import { makeAppendOnlyTreeSnapshot, mockTxForRollup } from '@aztec/stdlib/testing';
|
|
@@ -30,9 +30,9 @@ export async function makeTx(seed?: number, chainId?: Fr): Promise<Tx> {
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
/**
|
|
33
|
-
* Creates an
|
|
33
|
+
* Creates an L2Block from transactions and global variables
|
|
34
34
|
*/
|
|
35
|
-
export async function makeBlock(txs: Tx[], globalVariables: GlobalVariables): Promise<
|
|
35
|
+
export async function makeBlock(txs: Tx[], globalVariables: GlobalVariables): Promise<L2Block> {
|
|
36
36
|
const processedTxs = await Promise.all(
|
|
37
37
|
txs.map(tx =>
|
|
38
38
|
makeProcessedTxFromPrivateOnlyTx(tx, Fr.ZERO, new PublicDataWrite(Fr.random(), Fr.random()), globalVariables),
|
|
@@ -41,7 +41,13 @@ export async function makeBlock(txs: Tx[], globalVariables: GlobalVariables): Pr
|
|
|
41
41
|
const body = new Body(processedTxs.map(tx => tx.txEffect));
|
|
42
42
|
const header = BlockHeader.empty({ globalVariables });
|
|
43
43
|
const archive = makeAppendOnlyTreeSnapshot(globalVariables.blockNumber + 1);
|
|
44
|
-
return new
|
|
44
|
+
return new L2Block(
|
|
45
|
+
archive,
|
|
46
|
+
header,
|
|
47
|
+
body,
|
|
48
|
+
CheckpointNumber.fromBlockNumber(globalVariables.blockNumber),
|
|
49
|
+
IndexWithinCheckpoint(0),
|
|
50
|
+
);
|
|
45
51
|
}
|
|
46
52
|
|
|
47
53
|
/**
|
|
@@ -70,10 +76,10 @@ export function createMockSignatures(signer: Secp256k1Signer): CommitteeAttestat
|
|
|
70
76
|
}
|
|
71
77
|
|
|
72
78
|
/**
|
|
73
|
-
* Creates a CheckpointHeader from an
|
|
74
|
-
* Uses mock values for blockHeadersHash, blobsHash and inHash since
|
|
79
|
+
* Creates a CheckpointHeader from an L2Block for testing purposes.
|
|
80
|
+
* Uses mock values for blockHeadersHash, blobsHash and inHash since L2Block doesn't have these fields.
|
|
75
81
|
*/
|
|
76
|
-
function createCheckpointHeaderFromBlock(block:
|
|
82
|
+
function createCheckpointHeaderFromBlock(block: L2Block): CheckpointHeader {
|
|
77
83
|
const gv = block.header.globalVariables;
|
|
78
84
|
return new CheckpointHeader(
|
|
79
85
|
block.header.lastArchive.root,
|
|
@@ -93,7 +99,7 @@ function createCheckpointHeaderFromBlock(block: L2BlockNew): CheckpointHeader {
|
|
|
93
99
|
/**
|
|
94
100
|
* Creates a block proposal from a block and signature
|
|
95
101
|
*/
|
|
96
|
-
export function createBlockProposal(block:
|
|
102
|
+
export function createBlockProposal(block: L2Block, signature: Signature): BlockProposal {
|
|
97
103
|
const txHashes = block.body.txEffects.map(tx => tx.txHash);
|
|
98
104
|
return new BlockProposal(
|
|
99
105
|
block.header,
|
|
@@ -109,7 +115,7 @@ export function createBlockProposal(block: L2BlockNew, signature: Signature): Bl
|
|
|
109
115
|
* Creates a checkpoint proposal from a block and signature
|
|
110
116
|
*/
|
|
111
117
|
export function createCheckpointProposal(
|
|
112
|
-
block:
|
|
118
|
+
block: L2Block,
|
|
113
119
|
checkpointSignature: Signature,
|
|
114
120
|
blockSignature?: Signature,
|
|
115
121
|
): CheckpointProposal {
|
|
@@ -129,7 +135,7 @@ export function createCheckpointProposal(
|
|
|
129
135
|
* In production, the sender is recovered from the signature.
|
|
130
136
|
*/
|
|
131
137
|
export function createCheckpointAttestation(
|
|
132
|
-
block:
|
|
138
|
+
block: L2Block,
|
|
133
139
|
signature: Signature,
|
|
134
140
|
sender: EthAddress,
|
|
135
141
|
): CheckpointAttestation {
|
|
@@ -150,7 +156,7 @@ export async function setupTxsAndBlock(
|
|
|
150
156
|
globalVariables: GlobalVariables,
|
|
151
157
|
txCount: number,
|
|
152
158
|
chainId: Fr,
|
|
153
|
-
): Promise<{ txs: Tx[]; block:
|
|
159
|
+
): Promise<{ txs: Tx[]; block: L2Block }> {
|
|
154
160
|
const txs = await Promise.all(times(txCount, i => makeTx(i + 1, chainId)));
|
|
155
161
|
const block = await makeBlock(txs, globalVariables);
|
|
156
162
|
mockPendingTxs(p2p, txs);
|