@aztec/sequencer-client 0.0.1-commit.d431d1c → 0.0.1-commit.e3c1de76

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.
Files changed (37) hide show
  1. package/dest/client/sequencer-client.js +1 -1
  2. package/dest/config.d.ts +1 -1
  3. package/dest/config.d.ts.map +1 -1
  4. package/dest/config.js +1 -3
  5. package/dest/global_variable_builder/global_builder.js +2 -2
  6. package/dest/publisher/sequencer-publisher-metrics.d.ts +1 -1
  7. package/dest/publisher/sequencer-publisher-metrics.d.ts.map +1 -1
  8. package/dest/publisher/sequencer-publisher-metrics.js +12 -4
  9. package/dest/publisher/sequencer-publisher.d.ts +1 -2
  10. package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
  11. package/dest/publisher/sequencer-publisher.js +39 -18
  12. package/dest/sequencer/checkpoint_proposal_job.d.ts +26 -9
  13. package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -1
  14. package/dest/sequencer/checkpoint_proposal_job.js +55 -22
  15. package/dest/sequencer/metrics.d.ts +2 -2
  16. package/dest/sequencer/metrics.d.ts.map +1 -1
  17. package/dest/sequencer/metrics.js +27 -17
  18. package/dest/sequencer/sequencer.d.ts +3 -3
  19. package/dest/sequencer/sequencer.d.ts.map +1 -1
  20. package/dest/sequencer/sequencer.js +2 -2
  21. package/dest/test/mock_checkpoint_builder.d.ts +11 -8
  22. package/dest/test/mock_checkpoint_builder.d.ts.map +1 -1
  23. package/dest/test/mock_checkpoint_builder.js +18 -6
  24. package/dest/test/utils.d.ts +8 -8
  25. package/dest/test/utils.d.ts.map +1 -1
  26. package/dest/test/utils.js +5 -5
  27. package/package.json +28 -28
  28. package/src/client/sequencer-client.ts +1 -1
  29. package/src/config.ts +1 -3
  30. package/src/global_variable_builder/global_builder.ts +2 -2
  31. package/src/publisher/sequencer-publisher-metrics.ts +7 -3
  32. package/src/publisher/sequencer-publisher.ts +34 -18
  33. package/src/sequencer/checkpoint_proposal_job.ts +81 -48
  34. package/src/sequencer/metrics.ts +36 -18
  35. package/src/sequencer/sequencer.ts +4 -4
  36. package/src/test/mock_checkpoint_builder.ts +32 -18
  37. 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 { BlockNumber, CheckpointNumber, EpochNumber, SlotNumber } from '@aztec/foundation/branded-types';
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 { Logger } from '@aztec/foundation/log';
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
- L2BlockNew,
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 type {
28
- PublicProcessorLimits,
29
- ResolvedSequencerConfig,
30
- WorldStateSynchronizer,
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: L2BlockNew[] = [];
207
- let blockPendingBroadcast: { block: L2BlockNew; txs: Tx[] } | undefined = undefined;
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: L2BlockNew[];
356
- blockPendingBroadcast: { block: L2BlockNew; txs: Tx[] } | undefined;
367
+ blocksInCheckpoint: L2Block[];
368
+ blockPendingBroadcast: { block: L2Block; txs: Tx[] } | undefined;
357
369
  }> {
358
- const blocksInCheckpoint: L2BlockNew[] = [];
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: L2BlockNew; txs: Tx[] } | undefined = undefined;
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
- await this.syncProposedBlockToArchiver(block);
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
- private async buildSingleBlock(
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: number;
505
+ indexWithinCheckpoint: IndexWithinCheckpoint;
488
506
  buildDeadline: Date | undefined;
489
507
  txHashesAlreadyIncluded: Set<string>;
490
508
  remainingBlobFields: number;
491
509
  },
492
- ): Promise<{ block: L2BlockNew; usedTxs: Tx[]; remainingBlobFields: number } | { error: Error } | undefined> {
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 workTimer = new Timer();
549
- const {
550
- publicGas,
551
- block,
552
- publicProcessorDuration,
553
- numTxs,
554
- blockBuildingTimer,
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
- if (!forceCreate && numTxs < minValidTxs) {
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 (got ${numTxs} but required ${minValidTxs})`,
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-tx-count-check-failed', {
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: number;
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.dateProvider.now() + attestationTimeAllowed * 1000);
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: L2BlockNew): Promise<void> {
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,
@@ -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.createUpDownCounter(Metrics.SEQUENCER_BLOCK_COUNT);
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.createUpDownCounter(Metrics.SEQUENCER_SLOT_COUNT);
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.createUpDownCounter(Metrics.SEQUENCER_FILLED_SLOT_COUNT);
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 = this.meter.createUpDownCounter(Metrics.SEQUENCER_BLOCK_PROPOSAL_FAILED_COUNT);
101
+ this.blockProposalFailed = createUpDownCounterWithDefault(
102
+ this.meter,
103
+ Metrics.SEQUENCER_BLOCK_PROPOSAL_FAILED_COUNT,
104
+ );
107
105
 
108
- this.blockProposalSuccess = this.meter.createUpDownCounter(Metrics.SEQUENCER_BLOCK_PROPOSAL_SUCCESS_COUNT);
106
+ this.blockProposalSuccess = createUpDownCounterWithDefault(
107
+ this.meter,
108
+ Metrics.SEQUENCER_BLOCK_PROPOSAL_SUCCESS_COUNT,
109
+ );
109
110
 
110
- this.checkpointSuccess = this.meter.createUpDownCounter(Metrics.SEQUENCER_CHECKPOINT_SUCCESS_COUNT);
111
+ this.checkpointSuccess = createUpDownCounterWithDefault(this.meter, Metrics.SEQUENCER_CHECKPOINT_SUCCESS_COUNT);
111
112
 
112
- this.blockProposalPrecheckFailed = this.meter.createUpDownCounter(
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.createUpDownCounter(Metrics.SEQUENCER_SLASHING_ATTEMPTS_COUNT);
126
+ this.slashingAttempts = createUpDownCounterWithDefault(this.meter, Metrics.SEQUENCER_SLASHING_ATTEMPTS_COUNT);
117
127
 
118
128
  // Fisherman fee analysis metrics
119
- this.fishermanWouldBeIncluded = this.meter.createUpDownCounter(Metrics.FISHERMAN_FEE_ANALYSIS_WOULD_BE_INCLUDED);
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(checkType: string) {
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 { L2BlockNew, L2BlockSink, L2BlockSource, ValidateCheckpointResult } from '@aztec/stdlib/block';
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.getL2BlockNew(blockNumber);
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?: L2BlockNew;
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 { Timer } from '@aztec/foundation/timer';
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: L2BlockNew[] = [];
25
- private builtBlocks: L2BlockNew[] = [];
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: (() => L2BlockNew) | undefined = undefined;
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: L2BlockNew[], usedTxsPerBlock?: Tx[][]): this {
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: () => L2BlockNew): this {
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
- _pendingTxs: Iterable<Tx> | AsyncIterable<Tx>,
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
- return Promise.reject(this.errorOnBuild);
82
+ throw this.errorOnBuild;
82
83
  }
83
84
 
84
- let block: L2BlockNew;
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
- return Promise.resolve({
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: L2BlockNew): CheckpointHeader {
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: L2BlockNew[];
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: L2BlockNew[] = [],
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, L2BlockNew } from '@aztec/stdlib/block';
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 L2BlockNew from transactions and global variables
33
+ * Creates an L2Block from transactions and global variables
34
34
  */
35
- export async function makeBlock(txs: Tx[], globalVariables: GlobalVariables): Promise<L2BlockNew> {
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 L2BlockNew(archive, header, body, CheckpointNumber(globalVariables.blockNumber), IndexWithinCheckpoint(0));
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 L2BlockNew for testing purposes.
74
- * Uses mock values for blockHeadersHash, blobsHash and inHash since L2BlockNew doesn't have these fields.
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: L2BlockNew): CheckpointHeader {
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: L2BlockNew, signature: Signature): BlockProposal {
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: L2BlockNew,
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: L2BlockNew,
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: L2BlockNew }> {
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);