@aztec/sequencer-client 0.0.1-commit.d431d1c → 0.0.1-commit.e310a4c8
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 +55 -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 +81 -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
|
|
@@ -352,10 +364,10 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
352
364
|
inHash: Fr,
|
|
353
365
|
blockProposalOptions: BlockProposalOptions,
|
|
354
366
|
): Promise<{
|
|
355
|
-
blocksInCheckpoint:
|
|
356
|
-
blockPendingBroadcast: { block:
|
|
367
|
+
blocksInCheckpoint: L2Block[];
|
|
368
|
+
blockPendingBroadcast: { block: L2Block; txs: Tx[] } | undefined;
|
|
357
369
|
}> {
|
|
358
|
-
const blocksInCheckpoint:
|
|
370
|
+
const blocksInCheckpoint: L2Block[] = [];
|
|
359
371
|
const txHashesAlreadyIncluded = new Set<string>();
|
|
360
372
|
const initialBlockNumber = BlockNumber(this.syncedToBlockNumber + 1);
|
|
361
373
|
|
|
@@ -363,11 +375,11 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
363
375
|
let remainingBlobFields = BLOBS_PER_CHECKPOINT * FIELDS_PER_BLOB - NUM_CHECKPOINT_END_MARKER_FIELDS;
|
|
364
376
|
|
|
365
377
|
// Last block in the checkpoint will usually be flagged as pending broadcast, so we send it along with the checkpoint proposal
|
|
366
|
-
let blockPendingBroadcast: { block:
|
|
378
|
+
let blockPendingBroadcast: { block: L2Block; txs: Tx[] } | undefined = undefined;
|
|
367
379
|
|
|
368
380
|
while (true) {
|
|
369
381
|
const blocksBuilt = blocksInCheckpoint.length;
|
|
370
|
-
const indexWithinCheckpoint = blocksBuilt;
|
|
382
|
+
const indexWithinCheckpoint = IndexWithinCheckpoint(blocksBuilt);
|
|
371
383
|
const blockNumber = BlockNumber(initialBlockNumber + blocksBuilt);
|
|
372
384
|
|
|
373
385
|
const secondsIntoSlot = this.getSecondsIntoSlot();
|
|
@@ -397,6 +409,7 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
397
409
|
remainingBlobFields,
|
|
398
410
|
});
|
|
399
411
|
|
|
412
|
+
// TODO(palla/mbps): Review these conditions. We may want to keep trying in some scenarios.
|
|
400
413
|
if (!buildResult && timingInfo.isLastBlock) {
|
|
401
414
|
// If no block was produced due to not enough txs and this was the last subslot, exit
|
|
402
415
|
break;
|
|
@@ -428,7 +441,12 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
428
441
|
// Sync the proposed block to the archiver to make it available
|
|
429
442
|
// Note that the checkpoint builder uses its own fork so it should not need to wait for this syncing
|
|
430
443
|
// Eventually we should refactor the checkpoint builder to not need a separate long-lived fork
|
|
431
|
-
|
|
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()));
|
|
432
450
|
|
|
433
451
|
// If this is the last block, exit the loop now so we start collecting attestations
|
|
434
452
|
if (timingInfo.isLastBlock) {
|
|
@@ -478,18 +496,18 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
478
496
|
|
|
479
497
|
/** Builds a single block. Called from the main block building loop. */
|
|
480
498
|
@trackSpan('CheckpointProposalJob.buildSingleBlock')
|
|
481
|
-
|
|
499
|
+
protected async buildSingleBlock(
|
|
482
500
|
checkpointBuilder: CheckpointBuilder,
|
|
483
501
|
opts: {
|
|
484
502
|
forceCreate?: boolean;
|
|
485
503
|
blockTimestamp: bigint;
|
|
486
504
|
blockNumber: BlockNumber;
|
|
487
|
-
indexWithinCheckpoint:
|
|
505
|
+
indexWithinCheckpoint: IndexWithinCheckpoint;
|
|
488
506
|
buildDeadline: Date | undefined;
|
|
489
507
|
txHashesAlreadyIncluded: Set<string>;
|
|
490
508
|
remainingBlobFields: number;
|
|
491
509
|
},
|
|
492
|
-
): Promise<{ block:
|
|
510
|
+
): Promise<{ block: L2Block; usedTxs: Tx[]; remainingBlobFields: number } | { error: Error } | undefined> {
|
|
493
511
|
const {
|
|
494
512
|
blockTimestamp,
|
|
495
513
|
forceCreate,
|
|
@@ -545,45 +563,38 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
545
563
|
};
|
|
546
564
|
|
|
547
565
|
// Actually build the block by executing txs
|
|
548
|
-
const
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
usedTxs,
|
|
556
|
-
failedTxs,
|
|
557
|
-
usedTxBlobFields,
|
|
558
|
-
} = await checkpointBuilder.buildBlock(pendingTxs, blockNumber, blockTimestamp, blockBuilderOptions);
|
|
559
|
-
const blockBuildDuration = workTimer.ms();
|
|
566
|
+
const buildResult = await this.buildSingleBlockWithCheckpointBuilder(
|
|
567
|
+
checkpointBuilder,
|
|
568
|
+
pendingTxs,
|
|
569
|
+
blockNumber,
|
|
570
|
+
blockTimestamp,
|
|
571
|
+
blockBuilderOptions,
|
|
572
|
+
);
|
|
560
573
|
|
|
561
574
|
// If any txs failed during execution, drop them from the mempool so we don't pick them up again
|
|
562
|
-
await this.dropFailedTxsFromP2P(failedTxs);
|
|
575
|
+
await this.dropFailedTxsFromP2P(buildResult.failedTxs);
|
|
563
576
|
|
|
564
577
|
// Check if we have created a block with enough txs. If there were invalid txs in the pool, or if execution took
|
|
565
578
|
// too long, then we may not get to minTxsPerBlock after executing public functions.
|
|
566
579
|
const minValidTxs = this.config.minValidTxsPerBlock ?? minTxs;
|
|
567
|
-
|
|
580
|
+
const numTxs = buildResult.status === 'no-valid-txs' ? 0 : buildResult.numTxs;
|
|
581
|
+
if (buildResult.status === 'no-valid-txs' || (!forceCreate && numTxs < minValidTxs)) {
|
|
568
582
|
this.log.warn(
|
|
569
|
-
`Block ${blockNumber} at index ${indexWithinCheckpoint} on slot ${this.slot} has too few valid txs to be proposed
|
|
570
|
-
{ 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 },
|
|
571
585
|
);
|
|
572
|
-
this.eventEmitter.emit('block-
|
|
573
|
-
minTxs: minValidTxs,
|
|
574
|
-
availableTxs: numTxs,
|
|
575
|
-
slot: this.slot,
|
|
576
|
-
});
|
|
586
|
+
this.eventEmitter.emit('block-build-failed', { reason: `Insufficient valid txs`, slot: this.slot });
|
|
577
587
|
this.metrics.recordBlockProposalFailed('insufficient_valid_txs');
|
|
578
588
|
return undefined;
|
|
579
589
|
}
|
|
580
590
|
|
|
581
591
|
// Block creation succeeded, emit stats and metrics
|
|
592
|
+
const { publicGas, block, publicProcessorDuration, usedTxs, usedTxBlobFields, blockBuildDuration } = buildResult;
|
|
593
|
+
|
|
582
594
|
const blockStats = {
|
|
583
595
|
eventName: 'l2-block-built',
|
|
584
596
|
duration: blockBuildDuration,
|
|
585
597
|
publicProcessDuration: publicProcessorDuration,
|
|
586
|
-
rollupCircuitsDuration: blockBuildingTimer.ms(),
|
|
587
598
|
...block.getStats(),
|
|
588
599
|
} satisfies L2BlockBuiltStats;
|
|
589
600
|
|
|
@@ -609,17 +620,40 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
609
620
|
}
|
|
610
621
|
}
|
|
611
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
|
+
|
|
612
644
|
/** Waits until minTxs are available on the pool for building a block. */
|
|
613
645
|
@trackSpan('CheckpointProposalJob.waitForMinTxs')
|
|
614
646
|
private async waitForMinTxs(opts: {
|
|
615
647
|
forceCreate?: boolean;
|
|
616
648
|
blockNumber: BlockNumber;
|
|
617
|
-
indexWithinCheckpoint:
|
|
649
|
+
indexWithinCheckpoint: IndexWithinCheckpoint;
|
|
618
650
|
buildDeadline: Date | undefined;
|
|
619
651
|
}): Promise<{ canStartBuilding: boolean; availableTxs: number }> {
|
|
620
|
-
const minTxs = this.config.minTxsPerBlock;
|
|
621
652
|
const { indexWithinCheckpoint, blockNumber, buildDeadline, forceCreate } = opts;
|
|
622
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
|
+
|
|
623
657
|
// Deadline is undefined if we are not enforcing the timetable, meaning we'll exit immediately when out of time
|
|
624
658
|
const startBuildingDeadline = buildDeadline
|
|
625
659
|
? new Date(buildDeadline.getTime() - this.timetable.minExecutionTime * 1000)
|
|
@@ -681,7 +715,7 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
681
715
|
const attestationTimeAllowed = this.config.enforceTimeTable
|
|
682
716
|
? this.timetable.getMaxAllowedTime(SequencerState.PUBLISHING_CHECKPOINT)!
|
|
683
717
|
: this.l1Constants.slotDuration;
|
|
684
|
-
const attestationDeadline = new Date(this.
|
|
718
|
+
const attestationDeadline = new Date((this.getSlotStartBuildTimestamp() + attestationTimeAllowed) * 1000);
|
|
685
719
|
|
|
686
720
|
this.metrics.recordRequiredAttestations(numberOfRequiredAttestations, attestationTimeAllowed);
|
|
687
721
|
|
|
@@ -777,8 +811,7 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
777
811
|
* Gossip doesn't echo messages back to the sender, so the proposer's archiver/world-state
|
|
778
812
|
* would never receive its own block without this explicit sync.
|
|
779
813
|
*/
|
|
780
|
-
private async syncProposedBlockToArchiver(block:
|
|
781
|
-
// TODO(palla/mbps): Change default to false once block sync is stable.
|
|
814
|
+
private async syncProposedBlockToArchiver(block: L2Block): Promise<void> {
|
|
782
815
|
if (this.config.skipPushProposedBlocksToArchiver !== false) {
|
|
783
816
|
this.log.warn(`Skipping push of proposed block ${block.number} to archiver`, {
|
|
784
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);
|