@aztec/sequencer-client 0.0.1-commit.ffe5b04ea → 0.0.1-commit.fff30aa

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 (66) hide show
  1. package/dest/client/sequencer-client.d.ts +5 -12
  2. package/dest/client/sequencer-client.d.ts.map +1 -1
  3. package/dest/client/sequencer-client.js +22 -72
  4. package/dest/config.d.ts +2 -1
  5. package/dest/config.d.ts.map +1 -1
  6. package/dest/config.js +7 -1
  7. package/dest/global_variable_builder/global_builder.d.ts +13 -7
  8. package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
  9. package/dest/global_variable_builder/global_builder.js +22 -21
  10. package/dest/global_variable_builder/index.d.ts +2 -2
  11. package/dest/global_variable_builder/index.d.ts.map +1 -1
  12. package/dest/publisher/config.d.ts +1 -5
  13. package/dest/publisher/config.d.ts.map +1 -1
  14. package/dest/publisher/config.js +1 -6
  15. package/dest/publisher/index.d.ts +1 -2
  16. package/dest/publisher/index.d.ts.map +1 -1
  17. package/dest/publisher/sequencer-publisher-factory.d.ts +1 -1
  18. package/dest/publisher/sequencer-publisher-factory.d.ts.map +1 -1
  19. package/dest/publisher/sequencer-publisher-factory.js +0 -14
  20. package/dest/publisher/sequencer-publisher.d.ts +15 -18
  21. package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
  22. package/dest/publisher/sequencer-publisher.js +70 -290
  23. package/dest/sequencer/checkpoint_proposal_job.d.ts +3 -1
  24. package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -1
  25. package/dest/sequencer/checkpoint_proposal_job.js +66 -63
  26. package/dest/sequencer/checkpoint_voter.d.ts +1 -2
  27. package/dest/sequencer/checkpoint_voter.d.ts.map +1 -1
  28. package/dest/sequencer/checkpoint_voter.js +2 -5
  29. package/dest/sequencer/sequencer.d.ts +6 -3
  30. package/dest/sequencer/sequencer.d.ts.map +1 -1
  31. package/dest/sequencer/sequencer.js +17 -16
  32. package/dest/sequencer/types.d.ts +2 -5
  33. package/dest/sequencer/types.d.ts.map +1 -1
  34. package/dest/test/mock_checkpoint_builder.d.ts +4 -4
  35. package/dest/test/mock_checkpoint_builder.d.ts.map +1 -1
  36. package/dest/test/mock_checkpoint_builder.js +2 -0
  37. package/package.json +28 -28
  38. package/src/client/sequencer-client.ts +34 -94
  39. package/src/config.ts +8 -1
  40. package/src/global_variable_builder/global_builder.ts +22 -23
  41. package/src/global_variable_builder/index.ts +1 -1
  42. package/src/publisher/config.ts +0 -9
  43. package/src/publisher/index.ts +0 -3
  44. package/src/publisher/sequencer-publisher-factory.ts +0 -15
  45. package/src/publisher/sequencer-publisher.ts +71 -276
  46. package/src/sequencer/checkpoint_proposal_job.ts +93 -77
  47. package/src/sequencer/checkpoint_voter.ts +1 -12
  48. package/src/sequencer/sequencer.ts +20 -17
  49. package/src/sequencer/types.ts +2 -5
  50. package/src/test/mock_checkpoint_builder.ts +5 -3
  51. package/dest/publisher/l1_tx_failed_store/factory.d.ts +0 -11
  52. package/dest/publisher/l1_tx_failed_store/factory.d.ts.map +0 -1
  53. package/dest/publisher/l1_tx_failed_store/factory.js +0 -22
  54. package/dest/publisher/l1_tx_failed_store/failed_tx_store.d.ts +0 -59
  55. package/dest/publisher/l1_tx_failed_store/failed_tx_store.d.ts.map +0 -1
  56. package/dest/publisher/l1_tx_failed_store/failed_tx_store.js +0 -1
  57. package/dest/publisher/l1_tx_failed_store/file_store_failed_tx_store.d.ts +0 -15
  58. package/dest/publisher/l1_tx_failed_store/file_store_failed_tx_store.d.ts.map +0 -1
  59. package/dest/publisher/l1_tx_failed_store/file_store_failed_tx_store.js +0 -34
  60. package/dest/publisher/l1_tx_failed_store/index.d.ts +0 -4
  61. package/dest/publisher/l1_tx_failed_store/index.d.ts.map +0 -1
  62. package/dest/publisher/l1_tx_failed_store/index.js +0 -2
  63. package/src/publisher/l1_tx_failed_store/factory.ts +0 -32
  64. package/src/publisher/l1_tx_failed_store/failed_tx_store.ts +0 -55
  65. package/src/publisher/l1_tx_failed_store/file_store_failed_tx_store.ts +0 -46
  66. package/src/publisher/l1_tx_failed_store/index.ts +0 -3
@@ -34,13 +34,18 @@ import { type Checkpoint, validateCheckpoint } from '@aztec/stdlib/checkpoint';
34
34
  import { getSlotStartBuildTimestamp } from '@aztec/stdlib/epoch-helpers';
35
35
  import { Gas } from '@aztec/stdlib/gas';
36
36
  import {
37
- NoValidTxsError,
38
- type PublicProcessorLimits,
37
+ type BlockBuilderOptions,
38
+ InsufficientValidTxsError,
39
39
  type ResolvedSequencerConfig,
40
40
  type WorldStateSynchronizer,
41
41
  } from '@aztec/stdlib/interfaces/server';
42
42
  import { type L1ToL2MessageSource, computeInHashFromL1ToL2Messages } from '@aztec/stdlib/messaging';
43
- import type { BlockProposalOptions, CheckpointProposal, CheckpointProposalOptions } from '@aztec/stdlib/p2p';
43
+ import type {
44
+ BlockProposal,
45
+ BlockProposalOptions,
46
+ CheckpointProposal,
47
+ CheckpointProposalOptions,
48
+ } from '@aztec/stdlib/p2p';
44
49
  import { orderAttestations, trimAttestations } from '@aztec/stdlib/p2p';
45
50
  import type { L2BlockBuiltStats } from '@aztec/stdlib/stats';
46
51
  import { type FailedTx, Tx } from '@aztec/stdlib/tx';
@@ -265,7 +270,8 @@ export class CheckpointProposalJob implements Traceable {
265
270
  this.setStateFn(SequencerState.ASSEMBLING_CHECKPOINT, this.slot);
266
271
  const checkpoint = await checkpointBuilder.completeCheckpoint();
267
272
 
268
- // Final validation round for the checkpoint before we propose it, just for safety
273
+ // Final validation: per-block limits are only checked if the operator set them explicitly.
274
+ // Otherwise, checkpoint-level budgets were already enforced by the redistribution logic.
269
275
  try {
270
276
  validateCheckpoint(checkpoint, {
271
277
  rollupManaLimit: this.l1Constants.rollupManaLimit,
@@ -402,6 +408,7 @@ export class CheckpointProposalJob implements Traceable {
402
408
  const blocksInCheckpoint: L2Block[] = [];
403
409
  const txHashesAlreadyIncluded = new Set<string>();
404
410
  const initialBlockNumber = BlockNumber(this.syncedToBlockNumber + 1);
411
+ const slot = this.slot;
405
412
 
406
413
  // Last block in the checkpoint will usually be flagged as pending broadcast, so we send it along with the checkpoint proposal
407
414
  let blockPendingBroadcast: { block: L2Block; txs: Tx[] } | undefined = undefined;
@@ -415,11 +422,7 @@ export class CheckpointProposalJob implements Traceable {
415
422
  const timingInfo = this.timetable.canStartNextBlock(secondsIntoSlot);
416
423
 
417
424
  if (!timingInfo.canStart) {
418
- this.log.debug(`Not enough time left in slot to start another block`, {
419
- slot: this.slot,
420
- blocksBuilt,
421
- secondsIntoSlot,
422
- });
425
+ this.log.debug(`Not enough time left in slot to start another block`, { slot, blocksBuilt, secondsIntoSlot });
423
426
  break;
424
427
  }
425
428
 
@@ -451,53 +454,37 @@ export class CheckpointProposalJob implements Traceable {
451
454
  } else if ('error' in buildResult) {
452
455
  // If there was an error building the block, just exit the loop and give up the rest of the slot
453
456
  if (!(buildResult.error instanceof SequencerInterruptedError)) {
454
- this.log.warn(`Halting block building for slot ${this.slot}`, {
455
- slot: this.slot,
456
- blocksBuilt,
457
- error: buildResult.error,
458
- });
457
+ this.log.warn(`Halting block building for slot ${slot}`, { slot, blocksBuilt, error: buildResult.error });
459
458
  }
460
459
  break;
461
460
  }
462
461
 
463
462
  const { block, usedTxs } = buildResult;
464
463
  blocksInCheckpoint.push(block);
465
-
466
- // Sync the proposed block to the archiver to make it available
467
- // Note that the checkpoint builder uses its own fork so it should not need to wait for this syncing
468
- // Eventually we should refactor the checkpoint builder to not need a separate long-lived fork
469
- // Fire and forget - don't block the critical path, but log errors
470
- this.syncProposedBlockToArchiver(block).catch(err => {
471
- this.log.error(`Failed to sync proposed block ${block.number} to archiver`, { blockNumber: block.number, err });
472
- });
473
-
474
464
  usedTxs.forEach(tx => txHashesAlreadyIncluded.add(tx.txHash.toString()));
475
465
 
476
- // If this is the last block, exit the loop now so we start collecting attestations
466
+ // If this is the last block, send the proposed block to the archiver,
467
+ // and exit the loop now so we can build the checkpoint and start collecting attestations.
477
468
  if (timingInfo.isLastBlock) {
478
- this.log.verbose(`Completed final block ${blockNumber} for slot ${this.slot}`, {
479
- slot: this.slot,
480
- blockNumber,
481
- blocksBuilt,
482
- });
469
+ await this.syncProposedBlockToArchiver(block);
470
+ this.log.verbose(`Completed final block ${blockNumber} for slot ${slot}`, { slot, blockNumber, blocksBuilt });
483
471
  blockPendingBroadcast = { block, txs: usedTxs };
484
472
  break;
485
473
  }
486
474
 
487
- // For non-last blocks, broadcast the block proposal (unless we're in fisherman mode)
488
- // If the block is the last one, we'll broadcast it along with the checkpoint at the end of the loop
489
- if (!this.config.fishermanMode) {
490
- const proposal = await this.validatorClient.createBlockProposal(
491
- block.header,
492
- block.indexWithinCheckpoint,
493
- inHash,
494
- block.archive.root,
495
- usedTxs,
496
- this.proposer,
497
- blockProposalOptions,
498
- );
499
- await this.p2pClient.broadcastProposal(proposal);
500
- }
475
+ // Broadcast the block proposal (unless we're in fisherman mode) unless the block is the last one,
476
+ // in which case we'll broadcast it along with the checkpoint at the end of the loop.
477
+ // Note that we only send the block to the archiver if we manage to create the proposal, so if there's
478
+ // a HA error we don't pollute our archiver with a block that won't make it to the chain.
479
+ const proposal = await this.createBlockProposal(block, inHash, usedTxs, blockProposalOptions);
480
+
481
+ // Sync the proposed block to the archiver to make it available, only after we've managed to sign the proposal.
482
+ // We wait for the sync to succeed, as this helps catch consistency errors, even if it means we lose some time for block-building.
483
+ // If this throws, we abort the entire checkpoint.
484
+ await this.syncProposedBlockToArchiver(block);
485
+
486
+ // Once we have a signed proposal and the archiver agreed with our proposed block, then we broadcast it.
487
+ proposal && (await this.p2pClient.broadcastProposal(proposal));
501
488
 
502
489
  // Wait until the next block's start time
503
490
  await this.waitUntilNextSubslot(timingInfo.deadline);
@@ -511,6 +498,28 @@ export class CheckpointProposalJob implements Traceable {
511
498
  return { blocksInCheckpoint, blockPendingBroadcast };
512
499
  }
513
500
 
501
+ /** Creates a block proposal for a given block via the validator client (unless in fisherman mode) */
502
+ private createBlockProposal(
503
+ block: L2Block,
504
+ inHash: Fr,
505
+ usedTxs: Tx[],
506
+ blockProposalOptions: BlockProposalOptions,
507
+ ): Promise<BlockProposal | undefined> {
508
+ if (this.config.fishermanMode) {
509
+ this.log.info(`Skipping block proposal for block ${block.number} in fisherman mode`);
510
+ return Promise.resolve(undefined);
511
+ }
512
+ return this.validatorClient.createBlockProposal(
513
+ block.header,
514
+ block.indexWithinCheckpoint,
515
+ inHash,
516
+ block.archive.root,
517
+ usedTxs,
518
+ this.proposer,
519
+ blockProposalOptions,
520
+ );
521
+ }
522
+
514
523
  /** Sleeps until it is time to produce the next block in the slot */
515
524
  @trackSpan('CheckpointProposalJob.waitUntilNextSubslot')
516
525
  private async waitUntilNextSubslot(nextSubslotStart: number) {
@@ -542,8 +551,7 @@ export class CheckpointProposalJob implements Traceable {
542
551
 
543
552
  try {
544
553
  // Wait until we have enough txs to build the block
545
- const minTxs = this.config.minTxsPerBlock;
546
- const { availableTxs, canStartBuilding } = await this.waitForMinTxs(opts);
554
+ const { availableTxs, canStartBuilding, minTxs } = await this.waitForMinTxs(opts);
547
555
  if (!canStartBuilding) {
548
556
  this.log.warn(
549
557
  `Not enough txs to build block ${blockNumber} at index ${indexWithinCheckpoint} in slot ${this.slot} (got ${availableTxs} txs but needs ${minTxs})`,
@@ -567,9 +575,11 @@ export class CheckpointProposalJob implements Traceable {
567
575
  );
568
576
  this.setStateFn(SequencerState.CREATING_BLOCK, this.slot);
569
577
 
570
- // Per-block limits derived at startup by computeBlockLimits(), further capped
578
+ // Per-block limits are operator overrides (from SEQ_MAX_L2_BLOCK_GAS etc.) further capped
571
579
  // by remaining checkpoint-level budgets inside CheckpointBuilder before each block is built.
572
- const blockBuilderOptions: PublicProcessorLimits = {
580
+ // minValidTxs is passed into the builder so it can reject the block *before* updating state.
581
+ const minValidTxs = forceCreate ? 0 : (this.config.minValidTxsPerBlock ?? minTxs);
582
+ const blockBuilderOptions: BlockBuilderOptions = {
573
583
  maxTransactions: this.config.maxTxsPerBlock,
574
584
  maxBlockGas:
575
585
  this.config.maxL2BlockGas !== undefined || this.config.maxDABlockGas !== undefined
@@ -577,9 +587,14 @@ export class CheckpointProposalJob implements Traceable {
577
587
  : undefined,
578
588
  deadline: buildDeadline,
579
589
  isBuildingProposal: true,
590
+ minValidTxs,
591
+ maxBlocksPerCheckpoint: this.timetable.maxNumberOfBlocks,
592
+ perBlockAllocationMultiplier: this.config.perBlockAllocationMultiplier,
580
593
  };
581
594
 
582
- // Actually build the block by executing txs
595
+ // Actually build the block by executing txs. The builder throws InsufficientValidTxsError
596
+ // if the number of successfully processed txs is below minValidTxs, ensuring state is not
597
+ // updated for blocks that will be discarded.
583
598
  const buildResult = await this.buildSingleBlockWithCheckpointBuilder(
584
599
  checkpointBuilder,
585
600
  pendingTxs,
@@ -591,14 +606,16 @@ export class CheckpointProposalJob implements Traceable {
591
606
  // If any txs failed during execution, drop them from the mempool so we don't pick them up again
592
607
  await this.dropFailedTxsFromP2P(buildResult.failedTxs);
593
608
 
594
- // Check if we have created a block with enough txs. If there were invalid txs in the pool, or if execution took
595
- // too long, then we may not get to minTxsPerBlock after executing public functions.
596
- const minValidTxs = this.config.minValidTxsPerBlock ?? minTxs;
597
- const numTxs = buildResult.status === 'no-valid-txs' ? 0 : buildResult.numTxs;
598
- if (buildResult.status === 'no-valid-txs' || (!forceCreate && numTxs < minValidTxs)) {
609
+ if (buildResult.status === 'insufficient-valid-txs') {
599
610
  this.log.warn(
600
611
  `Block ${blockNumber} at index ${indexWithinCheckpoint} on slot ${this.slot} has too few valid txs to be proposed`,
601
- { slot: this.slot, blockNumber, numTxs, indexWithinCheckpoint, minValidTxs, buildResult: buildResult.status },
612
+ {
613
+ slot: this.slot,
614
+ blockNumber,
615
+ numTxs: buildResult.processedCount,
616
+ indexWithinCheckpoint,
617
+ minValidTxs,
618
+ },
602
619
  );
603
620
  this.eventEmitter.emit('block-build-failed', { reason: `Insufficient valid txs`, slot: this.slot });
604
621
  this.metrics.recordBlockProposalFailed('insufficient_valid_txs');
@@ -606,7 +623,7 @@ export class CheckpointProposalJob implements Traceable {
606
623
  }
607
624
 
608
625
  // Block creation succeeded, emit stats and metrics
609
- const { block, publicProcessorDuration, usedTxs, blockBuildDuration } = buildResult;
626
+ const { block, publicProcessorDuration, usedTxs, blockBuildDuration, numTxs } = buildResult;
610
627
 
611
628
  const blockStats = {
612
629
  eventName: 'l2-block-built',
@@ -637,13 +654,13 @@ export class CheckpointProposalJob implements Traceable {
637
654
  }
638
655
  }
639
656
 
640
- /** Uses the checkpoint builder to build a block, catching specific txs */
657
+ /** Uses the checkpoint builder to build a block, catching InsufficientValidTxsError. */
641
658
  private async buildSingleBlockWithCheckpointBuilder(
642
659
  checkpointBuilder: CheckpointBuilder,
643
660
  pendingTxs: AsyncIterable<Tx>,
644
661
  blockNumber: BlockNumber,
645
662
  blockTimestamp: bigint,
646
- blockBuilderOptions: PublicProcessorLimits,
663
+ blockBuilderOptions: BlockBuilderOptions,
647
664
  ) {
648
665
  try {
649
666
  const workTimer = new Timer();
@@ -651,8 +668,12 @@ export class CheckpointProposalJob implements Traceable {
651
668
  const blockBuildDuration = workTimer.ms();
652
669
  return { ...result, blockBuildDuration, status: 'success' as const };
653
670
  } catch (err: unknown) {
654
- if (isErrorClass(err, NoValidTxsError)) {
655
- return { failedTxs: err.failedTxs, status: 'no-valid-txs' as const };
671
+ if (isErrorClass(err, InsufficientValidTxsError)) {
672
+ return {
673
+ failedTxs: err.failedTxs,
674
+ processedCount: err.processedCount,
675
+ status: 'insufficient-valid-txs' as const,
676
+ };
656
677
  }
657
678
  throw err;
658
679
  }
@@ -665,7 +686,7 @@ export class CheckpointProposalJob implements Traceable {
665
686
  blockNumber: BlockNumber;
666
687
  indexWithinCheckpoint: IndexWithinCheckpoint;
667
688
  buildDeadline: Date | undefined;
668
- }): Promise<{ canStartBuilding: boolean; availableTxs: number }> {
689
+ }): Promise<{ canStartBuilding: boolean; availableTxs: number; minTxs: number }> {
669
690
  const { indexWithinCheckpoint, blockNumber, buildDeadline, forceCreate } = opts;
670
691
 
671
692
  // We only allow a block with 0 txs in the first block of the checkpoint
@@ -682,7 +703,7 @@ export class CheckpointProposalJob implements Traceable {
682
703
  // If we're past deadline, or we have no deadline, give up
683
704
  const now = this.dateProvider.nowAsDate();
684
705
  if (startBuildingDeadline === undefined || now >= startBuildingDeadline) {
685
- return { canStartBuilding: false, availableTxs: availableTxs };
706
+ return { canStartBuilding: false, availableTxs, minTxs };
686
707
  }
687
708
 
688
709
  // Wait a bit before checking again
@@ -695,7 +716,7 @@ export class CheckpointProposalJob implements Traceable {
695
716
  availableTxs = await this.p2pClient.getPendingTxCount();
696
717
  }
697
718
 
698
- return { canStartBuilding: true, availableTxs };
719
+ return { canStartBuilding: true, availableTxs, minTxs };
699
720
  }
700
721
 
701
722
  /**
@@ -833,20 +854,11 @@ export class CheckpointProposalJob implements Traceable {
833
854
  this.log.warn(`Shuffling attestation ordering in checkpoint for slot ${slotNumber} (proposer #${proposerIndex})`);
834
855
 
835
856
  const shuffled = [...attestations];
836
-
837
- // Find two non-proposer positions that both have non-empty signatures to swap.
838
- // This ensures the bitmap doesn't change, so the MaliciousCommitteeAttestationsAndSigners
839
- // signers array stays correctly aligned with L1's committee reconstruction.
840
- const swappable: number[] = [];
841
- for (let k = 0; k < shuffled.length; k++) {
842
- if (!shuffled[k].signature.isEmpty() && k !== proposerIndex) {
843
- swappable.push(k);
844
- }
845
- }
846
- if (swappable.length >= 2) {
847
- const [i, j] = [swappable[0], swappable[1]];
848
- [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
849
- }
857
+ const [i, j] = [(proposerIndex + 1) % shuffled.length, (proposerIndex + 2) % shuffled.length];
858
+ const valueI = shuffled[i];
859
+ const valueJ = shuffled[j];
860
+ shuffled[i] = valueJ;
861
+ shuffled[j] = valueI;
850
862
 
851
863
  const signers = new CommitteeAttestationsAndSigners(attestations).getSigners();
852
864
  return new MaliciousCommitteeAttestationsAndSigners(shuffled, signers);
@@ -869,9 +881,13 @@ export class CheckpointProposalJob implements Traceable {
869
881
  * Adds the proposed block to the archiver so it's available via P2P.
870
882
  * Gossip doesn't echo messages back to the sender, so the proposer's archiver/world-state
871
883
  * would never receive its own block without this explicit sync.
884
+ *
885
+ * In fisherman mode we skip this push: the fisherman builds blocks locally for validation
886
+ * and fee analysis only, and pushing them to the archiver causes spurious reorg cascades
887
+ * whenever the real proposer's block arrives from L1.
872
888
  */
873
889
  private async syncProposedBlockToArchiver(block: L2Block): Promise<void> {
874
- if (this.config.skipPushProposedBlocksToArchiver !== false) {
890
+ if (this.config.skipPushProposedBlocksToArchiver !== false || this.config.fishermanMode) {
875
891
  this.log.warn(`Skipping push of proposed block ${block.number} to archiver`, {
876
892
  blockNumber: block.number,
877
893
  slot: block.header.globalVariables.slotNumber,
@@ -2,7 +2,6 @@ import type { SlotNumber } from '@aztec/foundation/branded-types';
2
2
  import type { EthAddress } from '@aztec/foundation/eth-address';
3
3
  import type { Logger } from '@aztec/foundation/log';
4
4
  import type { SlasherClientInterface } from '@aztec/slasher';
5
- import { getTimestampForSlot } from '@aztec/stdlib/epoch-helpers';
6
5
  import type { ResolvedSequencerConfig } from '@aztec/stdlib/interfaces/server';
7
6
  import type { ValidatorClient } from '@aztec/validator-client';
8
7
  import { DutyAlreadySignedError } from '@aztec/validator-ha-signer/errors';
@@ -18,7 +17,6 @@ import type { SequencerRollupConstants } from './types.js';
18
17
  * Handles governance and slashing voting for a given slot.
19
18
  */
20
19
  export class CheckpointVoter {
21
- private slotTimestamp: bigint;
22
20
  private governanceSigner: (msg: TypedDataDefinition) => Promise<`0x${string}`>;
23
21
  private slashingSigner: (msg: TypedDataDefinition) => Promise<`0x${string}`>;
24
22
 
@@ -33,8 +31,6 @@ export class CheckpointVoter {
33
31
  private readonly metrics: SequencerMetrics,
34
32
  private readonly log: Logger,
35
33
  ) {
36
- this.slotTimestamp = getTimestampForSlot(this.slot, this.l1Constants);
37
-
38
34
  // Create separate signers with appropriate duty contexts for governance and slashing votes
39
35
  // These use HA protection to ensure only one node signs per slot/duty
40
36
  const governanceContext: SigningContext = { slot: this.slot, dutyType: DutyType.GOVERNANCE_VOTE };
@@ -77,7 +73,6 @@ export class CheckpointVoter {
77
73
  return await this.publisher.enqueueGovernanceCastSignal(
78
74
  governanceProposerPayload,
79
75
  this.slot,
80
- this.slotTimestamp,
81
76
  this.attestorAddress,
82
77
  this.governanceSigner,
83
78
  );
@@ -108,13 +103,7 @@ export class CheckpointVoter {
108
103
 
109
104
  this.metrics.recordSlashingAttempt(actions.length);
110
105
 
111
- return await this.publisher.enqueueSlashingActions(
112
- actions,
113
- this.slot,
114
- this.slotTimestamp,
115
- this.attestorAddress,
116
- this.slashingSigner,
117
- );
106
+ return await this.publisher.enqueueSlashingActions(actions, this.slot, this.attestorAddress, this.slashingSigner);
118
107
  } catch (err) {
119
108
  if (err instanceof DutyAlreadySignedError) {
120
109
  this.log.info(`Slashing vote already signed by another node`, {
@@ -14,7 +14,7 @@ import type { P2P } from '@aztec/p2p';
14
14
  import type { SlasherClientInterface } from '@aztec/slasher';
15
15
  import type { BlockData, L2BlockSink, L2BlockSource, ValidateCheckpointResult } from '@aztec/stdlib/block';
16
16
  import type { Checkpoint } from '@aztec/stdlib/checkpoint';
17
- import { getSlotAtTimestamp, getSlotStartBuildTimestamp } from '@aztec/stdlib/epoch-helpers';
17
+ import { getSlotStartBuildTimestamp } from '@aztec/stdlib/epoch-helpers';
18
18
  import {
19
19
  type ResolvedSequencerConfig,
20
20
  type SequencerConfig,
@@ -143,6 +143,11 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
143
143
  this.log.info('Started sequencer');
144
144
  }
145
145
 
146
+ /** Triggers an immediate run of the sequencer, bypassing the polling interval. */
147
+ public trigger() {
148
+ return this.runningPromise?.trigger();
149
+ }
150
+
146
151
  /** Stops the sequencer from building blocks and moves to STOPPED state. */
147
152
  public async stop(): Promise<void> {
148
153
  this.log.info(`Stopping sequencer`);
@@ -281,8 +286,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
281
286
 
282
287
  const logCtx = {
283
288
  now,
284
- syncedToL1Ts: syncedTo.l1Timestamp,
285
- syncedToL2Slot: getSlotAtTimestamp(syncedTo.l1Timestamp, this.l1Constants),
289
+ syncedToL2Slot: syncedTo.syncedL2Slot,
286
290
  slot,
287
291
  slotTs: ts,
288
292
  checkpointNumber,
@@ -328,7 +332,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
328
332
 
329
333
  // Check with the rollup contract if we can indeed propose at the next L2 slot. This check should not fail
330
334
  // if all the previous checks are good, but we do it just in case.
331
- const canProposeCheck = await publisher.canProposeAtNextEthBlock(
335
+ const canProposeCheck = await publisher.canProposeAt(
332
336
  syncedTo.archive,
333
337
  proposer ?? EthAddress.ZERO,
334
338
  invalidateCheckpoint,
@@ -475,16 +479,15 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
475
479
  * We don't check against the previous block submitted since it may have been reorg'd out.
476
480
  */
477
481
  protected async checkSync(args: { ts: bigint; slot: SlotNumber }): Promise<SequencerSyncCheckResult | undefined> {
478
- // Check that the archiver and dependencies have synced to the previous L1 slot at least
479
- // TODO(#14766): Archiver reports L1 timestamp based on L1 blocks seen, which means that a missed L1 block will
480
- // cause the archiver L1 timestamp to fall behind, and cause this sequencer to start processing one L1 slot later.
481
- const l1Timestamp = await this.l2BlockSource.getL1Timestamp();
482
- const { slot, ts } = args;
483
- if (l1Timestamp === undefined || l1Timestamp + BigInt(this.l1Constants.ethereumSlotDuration) < ts) {
482
+ // Check that the archiver has fully synced the L2 slot before the one we want to propose in.
483
+ // The archiver reports sync progress via L1 block timestamps and synced checkpoint slots.
484
+ // See getSyncedL2SlotNumber for how missed L1 blocks are handled.
485
+ const syncedL2Slot = await this.l2BlockSource.getSyncedL2SlotNumber();
486
+ const { slot } = args;
487
+ if (syncedL2Slot === undefined || syncedL2Slot + 1 < slot) {
484
488
  this.log.debug(`Cannot propose block at next L2 slot ${slot} due to pending sync from L1`, {
485
489
  slot,
486
- ts,
487
- l1Timestamp,
490
+ syncedL2Slot,
488
491
  });
489
492
  return undefined;
490
493
  }
@@ -524,7 +527,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
524
527
  checkpointNumber: CheckpointNumber.ZERO,
525
528
  blockNumber: BlockNumber.ZERO,
526
529
  archive,
527
- l1Timestamp,
530
+ syncedL2Slot,
528
531
  pendingChainValidationStatus,
529
532
  };
530
533
  }
@@ -541,7 +544,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
541
544
  blockNumber: blockData.header.getBlockNumber(),
542
545
  checkpointNumber: blockData.checkpointNumber,
543
546
  archive: blockData.archive.root,
544
- l1Timestamp,
547
+ syncedL2Slot,
545
548
  pendingChainValidationStatus,
546
549
  };
547
550
  }
@@ -720,7 +723,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
720
723
  syncedTo: SequencerSyncCheckResult,
721
724
  currentSlot: SlotNumber,
722
725
  ): Promise<void> {
723
- const { pendingChainValidationStatus, l1Timestamp } = syncedTo;
726
+ const { pendingChainValidationStatus, syncedL2Slot } = syncedTo;
724
727
  if (pendingChainValidationStatus.valid) {
725
728
  return;
726
729
  }
@@ -735,7 +738,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
735
738
 
736
739
  const logData = {
737
740
  invalidL1Timestamp: invalidCheckpointTimestamp,
738
- l1Timestamp,
741
+ syncedL2Slot,
739
742
  invalidCheckpoint: pendingChainValidationStatus.checkpoint,
740
743
  secondsBeforeInvalidatingBlockAsCommitteeMember,
741
744
  secondsBeforeInvalidatingBlockAsNonCommitteeMember,
@@ -882,6 +885,6 @@ type SequencerSyncCheckResult = {
882
885
  checkpointNumber: CheckpointNumber;
883
886
  blockNumber: BlockNumber;
884
887
  archive: Fr;
885
- l1Timestamp: bigint;
888
+ syncedL2Slot: SlotNumber;
886
889
  pendingChainValidationStatus: ValidateCheckpointResult;
887
890
  };
@@ -2,8 +2,5 @@ import type { L1RollupConstants } from '@aztec/stdlib/epoch-helpers';
2
2
 
3
3
  export type SequencerRollupConstants = Pick<
4
4
  L1RollupConstants,
5
- 'ethereumSlotDuration' | 'l1GenesisTime' | 'slotDuration'
6
- > & {
7
- /** Total L2 gas (mana) allowed per checkpoint. Fetched from L1 getManaLimit(). */
8
- rollupManaLimit: number;
9
- };
5
+ 'ethereumSlotDuration' | 'l1GenesisTime' | 'slotDuration' | 'rollupManaLimit'
6
+ >;
@@ -4,11 +4,11 @@ import { unfreeze } from '@aztec/foundation/types';
4
4
  import { L2Block } from '@aztec/stdlib/block';
5
5
  import { Checkpoint } from '@aztec/stdlib/checkpoint';
6
6
  import type {
7
+ BlockBuilderOptions,
7
8
  FullNodeBlockBuilderConfig,
8
9
  ICheckpointBlockBuilder,
9
10
  ICheckpointsBuilder,
10
11
  MerkleTreeWriteOperations,
11
- PublicProcessorLimits,
12
12
  } from '@aztec/stdlib/interfaces/server';
13
13
  import { CheckpointHeader } from '@aztec/stdlib/rollup';
14
14
  import { makeAppendOnlyTreeSnapshot } from '@aztec/stdlib/testing';
@@ -32,7 +32,7 @@ export class MockCheckpointBuilder implements ICheckpointBlockBuilder {
32
32
  public buildBlockCalls: Array<{
33
33
  blockNumber: BlockNumber;
34
34
  timestamp: bigint;
35
- opts: PublicProcessorLimits;
35
+ opts: BlockBuilderOptions;
36
36
  }> = [];
37
37
  /** Track all consumed transaction hashes across buildBlock calls */
38
38
  public consumedTxHashes: Set<string> = new Set();
@@ -74,7 +74,7 @@ export class MockCheckpointBuilder implements ICheckpointBlockBuilder {
74
74
  pendingTxs: Iterable<Tx> | AsyncIterable<Tx>,
75
75
  blockNumber: BlockNumber,
76
76
  timestamp: bigint,
77
- opts: PublicProcessorLimits,
77
+ opts: BlockBuilderOptions,
78
78
  ): Promise<BuildBlockInCheckpointResult> {
79
79
  this.buildBlockCalls.push({ blockNumber, timestamp, opts });
80
80
 
@@ -304,6 +304,8 @@ export class MockCheckpointsBuilder implements ICheckpointsBuilder {
304
304
  if (!this.checkpointBuilder) {
305
305
  // Auto-create a builder if none was set
306
306
  this.checkpointBuilder = new MockCheckpointBuilder(constants, checkpointNumber);
307
+ } else {
308
+ this.checkpointBuilder.resetCheckpointState();
307
309
  }
308
310
 
309
311
  return Promise.resolve(this.checkpointBuilder);
@@ -1,11 +0,0 @@
1
- import { type Logger } from '@aztec/foundation/log';
2
- import type { L1TxFailedStore } from './failed_tx_store.js';
3
- /**
4
- * Creates an L1TxFailedStore from a config string.
5
- * Supports any backend that FileStore supports (GCS, S3, R2, local filesystem).
6
- * @param config - Config string (e.g., 'gs://bucket/path', 's3://bucket/path', 'file:///path'). If undefined, returns undefined.
7
- * @param logger - Optional logger.
8
- * @returns The store instance, or undefined if config is not provided.
9
- */
10
- export declare function createL1TxFailedStore(config: string | undefined, logger?: Logger): Promise<L1TxFailedStore | undefined>;
11
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZmFjdG9yeS5kLnRzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vc3JjL3B1Ymxpc2hlci9sMV90eF9mYWlsZWRfc3RvcmUvZmFjdG9yeS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsS0FBSyxNQUFNLEVBQWdCLE1BQU0sdUJBQXVCLENBQUM7QUFHbEUsT0FBTyxLQUFLLEVBQUUsZUFBZSxFQUFFLE1BQU0sc0JBQXNCLENBQUM7QUFHNUQ7Ozs7OztHQU1HO0FBQ0gsd0JBQXNCLHFCQUFxQixDQUN6QyxNQUFNLEVBQUUsTUFBTSxHQUFHLFNBQVMsRUFDMUIsTUFBTSxHQUFFLE1BQXFELEdBQzVELE9BQU8sQ0FBQyxlQUFlLEdBQUcsU0FBUyxDQUFDLENBZXRDIn0=
@@ -1 +0,0 @@
1
- {"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../../src/publisher/l1_tx_failed_store/factory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,MAAM,EAAgB,MAAM,uBAAuB,CAAC;AAGlE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAG5D;;;;;;GAMG;AACH,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,MAAM,GAAE,MAAqD,GAC5D,OAAO,CAAC,eAAe,GAAG,SAAS,CAAC,CAetC"}
@@ -1,22 +0,0 @@
1
- import { createLogger } from '@aztec/foundation/log';
2
- import { createFileStore } from '@aztec/stdlib/file-store';
3
- import { FileStoreL1TxFailedStore } from './file_store_failed_tx_store.js';
4
- /**
5
- * Creates an L1TxFailedStore from a config string.
6
- * Supports any backend that FileStore supports (GCS, S3, R2, local filesystem).
7
- * @param config - Config string (e.g., 'gs://bucket/path', 's3://bucket/path', 'file:///path'). If undefined, returns undefined.
8
- * @param logger - Optional logger.
9
- * @returns The store instance, or undefined if config is not provided.
10
- */ export async function createL1TxFailedStore(config, logger = createLogger('sequencer:l1-tx-failed-store')) {
11
- if (!config) {
12
- return undefined;
13
- }
14
- const fileStore = await createFileStore(config, logger);
15
- if (!fileStore) {
16
- throw new Error(`Failed to create file store from config: '${config}'. ` + `Supported formats: 'gs://bucket/path', 's3://bucket/path', 'file:///path'.`);
17
- }
18
- logger.info(`Created L1 tx failed store`, {
19
- config
20
- });
21
- return new FileStoreL1TxFailedStore(fileStore, logger);
22
- }
@@ -1,59 +0,0 @@
1
- import type { Hex } from 'viem';
2
- /** URI pointing to a stored failed L1 transaction. */
3
- export type FailedL1TxUri = string & {
4
- __brand: 'FailedL1TxUri';
5
- };
6
- /** A failed L1 transaction captured for debugging and replay. */
7
- export type FailedL1Tx = {
8
- /** Tx hash (for reverts) or keccak256(request.data) (for simulation/send failures). */
9
- id: Hex;
10
- /** Unix timestamp (ms) when failure occurred. */
11
- timestamp: number;
12
- /** Whether the failure was during simulation or after sending. */
13
- failureType: 'simulation' | 'revert' | 'send-error';
14
- /** The actual L1 transaction for replay (multicall-encoded for bundled txs). */
15
- request: {
16
- to: Hex;
17
- data: Hex;
18
- value?: string;
19
- };
20
- /** Raw blob data as hex for replay. */
21
- blobData?: Hex[];
22
- /** L1 block number at time of failure (simulation target or receipt block). */
23
- l1BlockNumber: string;
24
- /** Receipt info (present only for on-chain reverts). */
25
- receipt?: {
26
- transactionHash: Hex;
27
- blockNumber: string;
28
- gasUsed: string;
29
- status: 'reverted';
30
- };
31
- /** Error information. */
32
- error: {
33
- message: string;
34
- /** Decoded error name (e.g., 'Rollup__InvalidProposer'). */
35
- name?: string;
36
- };
37
- /** Context metadata. */
38
- context: {
39
- /** Actions involved (e.g., ['propose', 'governance-signal']). */
40
- actions: string[];
41
- /** Individual request data for each action (metadata, not used for replay). */
42
- requests?: Array<{
43
- action: string;
44
- to: Hex;
45
- data: Hex;
46
- }>;
47
- checkpointNumber?: number;
48
- slot?: number;
49
- sender: Hex;
50
- };
51
- };
52
- /** Store for failed L1 transactions for debugging purposes. */
53
- export interface L1TxFailedStore {
54
- /** Saves a failed transaction and returns its URI. */
55
- saveFailedTx(tx: FailedL1Tx): Promise<FailedL1TxUri>;
56
- /** Retrieves a failed transaction by its URI. */
57
- getFailedTx(uri: FailedL1TxUri): Promise<FailedL1Tx>;
58
- }
59
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZmFpbGVkX3R4X3N0b3JlLmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvcHVibGlzaGVyL2wxX3R4X2ZhaWxlZF9zdG9yZS9mYWlsZWRfdHhfc3RvcmUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLEVBQUUsR0FBRyxFQUFFLE1BQU0sTUFBTSxDQUFDO0FBRWhDLHNEQUFzRDtBQUN0RCxNQUFNLE1BQU0sYUFBYSxHQUFHLE1BQU0sR0FBRztJQUFFLE9BQU8sRUFBRSxlQUFlLENBQUE7Q0FBRSxDQUFDO0FBRWxFLGlFQUFpRTtBQUNqRSxNQUFNLE1BQU0sVUFBVSxHQUFHO0lBQ3ZCLHVGQUF1RjtJQUN2RixFQUFFLEVBQUUsR0FBRyxDQUFDO0lBQ1IsaURBQWlEO0lBQ2pELFNBQVMsRUFBRSxNQUFNLENBQUM7SUFDbEIsa0VBQWtFO0lBQ2xFLFdBQVcsRUFBRSxZQUFZLEdBQUcsUUFBUSxHQUFHLFlBQVksQ0FBQztJQUNwRCxnRkFBZ0Y7SUFDaEYsT0FBTyxFQUFFO1FBQ1AsRUFBRSxFQUFFLEdBQUcsQ0FBQztRQUNSLElBQUksRUFBRSxHQUFHLENBQUM7UUFDVixLQUFLLENBQUMsRUFBRSxNQUFNLENBQUM7S0FDaEIsQ0FBQztJQUNGLHVDQUF1QztJQUN2QyxRQUFRLENBQUMsRUFBRSxHQUFHLEVBQUUsQ0FBQztJQUNqQiwrRUFBK0U7SUFDL0UsYUFBYSxFQUFFLE1BQU0sQ0FBQztJQUN0Qix3REFBd0Q7SUFDeEQsT0FBTyxDQUFDLEVBQUU7UUFDUixlQUFlLEVBQUUsR0FBRyxDQUFDO1FBQ3JCLFdBQVcsRUFBRSxNQUFNLENBQUM7UUFDcEIsT0FBTyxFQUFFLE1BQU0sQ0FBQztRQUNoQixNQUFNLEVBQUUsVUFBVSxDQUFDO0tBQ3BCLENBQUM7SUFDRix5QkFBeUI7SUFDekIsS0FBSyxFQUFFO1FBQ0wsT0FBTyxFQUFFLE1BQU0sQ0FBQztRQUNoQiw0REFBNEQ7UUFDNUQsSUFBSSxDQUFDLEVBQUUsTUFBTSxDQUFDO0tBQ2YsQ0FBQztJQUNGLHdCQUF3QjtJQUN4QixPQUFPLEVBQUU7UUFDUCxpRUFBaUU7UUFDakUsT0FBTyxFQUFFLE1BQU0sRUFBRSxDQUFDO1FBQ2xCLCtFQUErRTtRQUMvRSxRQUFRLENBQUMsRUFBRSxLQUFLLENBQUM7WUFBRSxNQUFNLEVBQUUsTUFBTSxDQUFDO1lBQUMsRUFBRSxFQUFFLEdBQUcsQ0FBQztZQUFDLElBQUksRUFBRSxHQUFHLENBQUE7U0FBRSxDQUFDLENBQUM7UUFDekQsZ0JBQWdCLENBQUMsRUFBRSxNQUFNLENBQUM7UUFDMUIsSUFBSSxDQUFDLEVBQUUsTUFBTSxDQUFDO1FBQ2QsTUFBTSxFQUFFLEdBQUcsQ0FBQztLQUNiLENBQUM7Q0FDSCxDQUFDO0FBRUYsK0RBQStEO0FBQy9ELE1BQU0sV0FBVyxlQUFlO0lBQzlCLHNEQUFzRDtJQUN0RCxZQUFZLENBQUMsRUFBRSxFQUFFLFVBQVUsR0FBRyxPQUFPLENBQUMsYUFBYSxDQUFDLENBQUM7SUFDckQsaURBQWlEO0lBQ2pELFdBQVcsQ0FBQyxHQUFHLEVBQUUsYUFBYSxHQUFHLE9BQU8sQ0FBQyxVQUFVLENBQUMsQ0FBQztDQUN0RCJ9
@@ -1 +0,0 @@
1
- {"version":3,"file":"failed_tx_store.d.ts","sourceRoot":"","sources":["../../../src/publisher/l1_tx_failed_store/failed_tx_store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,MAAM,CAAC;AAEhC,sDAAsD;AACtD,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG;IAAE,OAAO,EAAE,eAAe,CAAA;CAAE,CAAC;AAElE,iEAAiE;AACjE,MAAM,MAAM,UAAU,GAAG;IACvB,uFAAuF;IACvF,EAAE,EAAE,GAAG,CAAC;IACR,iDAAiD;IACjD,SAAS,EAAE,MAAM,CAAC;IAClB,kEAAkE;IAClE,WAAW,EAAE,YAAY,GAAG,QAAQ,GAAG,YAAY,CAAC;IACpD,gFAAgF;IAChF,OAAO,EAAE;QACP,EAAE,EAAE,GAAG,CAAC;QACR,IAAI,EAAE,GAAG,CAAC;QACV,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC;IACF,uCAAuC;IACvC,QAAQ,CAAC,EAAE,GAAG,EAAE,CAAC;IACjB,+EAA+E;IAC/E,aAAa,EAAE,MAAM,CAAC;IACtB,wDAAwD;IACxD,OAAO,CAAC,EAAE;QACR,eAAe,EAAE,GAAG,CAAC;QACrB,WAAW,EAAE,MAAM,CAAC;QACpB,OAAO,EAAE,MAAM,CAAC;QAChB,MAAM,EAAE,UAAU,CAAC;KACpB,CAAC;IACF,yBAAyB;IACzB,KAAK,EAAE;QACL,OAAO,EAAE,MAAM,CAAC;QAChB,4DAA4D;QAC5D,IAAI,CAAC,EAAE,MAAM,CAAC;KACf,CAAC;IACF,wBAAwB;IACxB,OAAO,EAAE;QACP,iEAAiE;QACjE,OAAO,EAAE,MAAM,EAAE,CAAC;QAClB,+EAA+E;QAC/E,QAAQ,CAAC,EAAE,KAAK,CAAC;YAAE,MAAM,EAAE,MAAM,CAAC;YAAC,EAAE,EAAE,GAAG,CAAC;YAAC,IAAI,EAAE,GAAG,CAAA;SAAE,CAAC,CAAC;QACzD,gBAAgB,CAAC,EAAE,MAAM,CAAC;QAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,GAAG,CAAC;KACb,CAAC;CACH,CAAC;AAEF,+DAA+D;AAC/D,MAAM,WAAW,eAAe;IAC9B,sDAAsD;IACtD,YAAY,CAAC,EAAE,EAAE,UAAU,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;IACrD,iDAAiD;IACjD,WAAW,CAAC,GAAG,EAAE,aAAa,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;CACtD"}
@@ -1 +0,0 @@
1
- /** Store for failed L1 transactions for debugging purposes. */ export { };