@aztec/sequencer-client 5.0.0-rc.2 → 5.0.0

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 (52) hide show
  1. package/dest/client/sequencer-client.d.ts +16 -3
  2. package/dest/client/sequencer-client.d.ts.map +1 -1
  3. package/dest/client/sequencer-client.js +17 -3
  4. package/dest/config.d.ts +2 -1
  5. package/dest/config.d.ts.map +1 -1
  6. package/dest/config.js +7 -2
  7. package/dest/global_variable_builder/fee_predictor.d.ts +1 -1
  8. package/dest/global_variable_builder/fee_predictor.d.ts.map +1 -1
  9. package/dest/global_variable_builder/fee_predictor.js +11 -1
  10. package/dest/global_variable_builder/fee_provider.d.ts +1 -1
  11. package/dest/global_variable_builder/fee_provider.d.ts.map +1 -1
  12. package/dest/global_variable_builder/fee_provider.js +15 -2
  13. package/dest/publisher/l1_to_l2_messaging.d.ts +21 -0
  14. package/dest/publisher/l1_to_l2_messaging.d.ts.map +1 -0
  15. package/dest/publisher/l1_to_l2_messaging.js +70 -0
  16. package/dest/publisher/sequencer-publisher.d.ts +1 -1
  17. package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
  18. package/dest/publisher/sequencer-publisher.js +6 -2
  19. package/dest/publisher/write_json.d.ts +11 -0
  20. package/dest/publisher/write_json.d.ts.map +1 -0
  21. package/dest/publisher/write_json.js +57 -0
  22. package/dest/sequencer/automine/automine_sequencer.d.ts +1 -1
  23. package/dest/sequencer/automine/automine_sequencer.d.ts.map +1 -1
  24. package/dest/sequencer/automine/automine_sequencer.js +11 -10
  25. package/dest/sequencer/checkpoint_proposal_job.d.ts +4 -6
  26. package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -1
  27. package/dest/sequencer/checkpoint_proposal_job.js +27 -24
  28. package/dest/sequencer/events.d.ts +1 -2
  29. package/dest/sequencer/events.d.ts.map +1 -1
  30. package/dest/sequencer/requests_tracker.d.ts +22 -0
  31. package/dest/sequencer/requests_tracker.d.ts.map +1 -0
  32. package/dest/sequencer/requests_tracker.js +33 -0
  33. package/dest/sequencer/sequencer.d.ts +39 -4
  34. package/dest/sequencer/sequencer.d.ts.map +1 -1
  35. package/dest/sequencer/sequencer.js +81 -21
  36. package/dest/test/utils.d.ts +1 -1
  37. package/dest/test/utils.d.ts.map +1 -1
  38. package/dest/test/utils.js +1 -0
  39. package/package.json +27 -27
  40. package/src/client/sequencer-client.ts +19 -3
  41. package/src/config.ts +7 -2
  42. package/src/global_variable_builder/fee_predictor.ts +11 -1
  43. package/src/global_variable_builder/fee_provider.ts +19 -2
  44. package/src/publisher/l1_to_l2_messaging.ts +85 -0
  45. package/src/publisher/sequencer-publisher.ts +8 -2
  46. package/src/publisher/write_json.ts +78 -0
  47. package/src/sequencer/automine/automine_sequencer.ts +11 -10
  48. package/src/sequencer/checkpoint_proposal_job.ts +40 -29
  49. package/src/sequencer/events.ts +1 -1
  50. package/src/sequencer/requests_tracker.ts +43 -0
  51. package/src/sequencer/sequencer.ts +83 -15
  52. package/src/test/utils.ts +1 -0
@@ -393,6 +393,7 @@ import { CheckpointProposalJobMetrics } from './checkpoint_proposal_job_metrics.
393
393
  import { CheckpointVoter } from './checkpoint_voter.js';
394
394
  import { SequencerInterruptedError } from './errors.js';
395
395
  import { SequencerMetrics } from './metrics.js';
396
+ import { RequestsTracker } from './requests_tracker.js';
396
397
  import { SequencerState } from './utils.js';
397
398
  export { SequencerState };
398
399
  _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpointProposal'), _dec2 = trackSpan('Sequencer.tryVoteAndPruneWhenCannotBuild', ({ slot })=>({
@@ -462,11 +463,20 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
462
463
  /** Last successful checkpoint proposed */ lastCheckpointProposed;
463
464
  /** The last epoch for which we logged strategy comparison in fisherman mode. */ lastEpochForStrategyComparison;
464
465
  /** The last checkpoint proposal job, tracked so we can await its pending L1 submission during shutdown. */ lastCheckpointProposalJob;
466
+ /**
467
+ * In-flight fire-and-forget requests that {@link stop} interrupts and drains, and {@link pause} awaits
468
+ * untouched: the checkpoint proposal jobs' backgrounded L1 submissions (each job is handed this shared
469
+ * tracker) plus the sequencer's own fallback submissions (votes/prune when we cannot build, or
470
+ * escape-hatch votes). Each fallback send is gated by its wrapper publisher's interruptible target-slot
471
+ * sleep; the tracked interrupt wakes that sleep so the send short-circuits without publishing. A wrapper
472
+ * is created for a single send and is never restarted, so once interrupted its sleeper can never publish
473
+ * a stale-slot tx, even after the pooled publishers are restarted by a later {@link start}.
474
+ */ pendingRequests;
465
475
  /** Proposer schedule and block sub-slot timetable for the sequencer, rebuilt on every config update. */ timetable;
466
476
  /** Config for the sequencer */ config;
467
477
  signatureContext;
468
478
  constructor(publisherFactory, validatorClient, globalsBuilder, p2pClient, worldState, slasherClient, l2BlockSource, l1ToL2MessageSource, checkpointsBuilder, l1Constants, dateProvider, epochCache, rollupContract, config, telemetry = getTelemetryClient(), log = createLogger('sequencer')){
469
- super(), this.publisherFactory = publisherFactory, this.validatorClient = validatorClient, this.globalsBuilder = globalsBuilder, this.p2pClient = p2pClient, this.worldState = worldState, this.slasherClient = slasherClient, this.l2BlockSource = l2BlockSource, this.l1ToL2MessageSource = l1ToL2MessageSource, this.checkpointsBuilder = checkpointsBuilder, this.l1Constants = l1Constants, this.dateProvider = dateProvider, this.epochCache = epochCache, this.rollupContract = rollupContract, this.telemetry = telemetry, this.log = log, this.state = (_initProto(this), SequencerState.STOPPED), this.config = DefaultSequencerConfig;
479
+ super(), this.publisherFactory = publisherFactory, this.validatorClient = validatorClient, this.globalsBuilder = globalsBuilder, this.p2pClient = p2pClient, this.worldState = worldState, this.slasherClient = slasherClient, this.l2BlockSource = l2BlockSource, this.l1ToL2MessageSource = l1ToL2MessageSource, this.checkpointsBuilder = checkpointsBuilder, this.l1Constants = l1Constants, this.dateProvider = dateProvider, this.epochCache = epochCache, this.rollupContract = rollupContract, this.telemetry = telemetry, this.log = log, this.state = (_initProto(this), SequencerState.STOPPED), this.pendingRequests = new RequestsTracker(), this.config = DefaultSequencerConfig;
470
480
  this.stateLog = log.createChild('state');
471
481
  this.stateEnteredAtMs = this.dateProvider.now();
472
482
  // Add [FISHERMAN] prefix to logger if in fisherman mode
@@ -580,7 +590,21 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
580
590
  /** Initializes the sequencer (precomputes tables). Takes about 3s. */ init() {
581
591
  getKzg();
582
592
  }
583
- /** Starts the sequencer and moves to IDLE state. */ start() {
593
+ /**
594
+ * Starts (or restarts) the sequencer and moves it to the IDLE state. Idempotent: a start while already
595
+ * running is a no-op, so it never orphans the previous poll loop. A start while STOPPING throws, since the
596
+ * in-flight stop would mark the fresh loop's state STOPPED and orphan it — silently doing nothing would
597
+ * leave the caller believing the sequencer is running when it is on its way to STOPPED. Safe to call after
598
+ * a previous {@link stop} has resolved; the caller must also restart the publishers (see
599
+ * {@link SequencerClient.start}) so L1 publishing is re-enabled.
600
+ */ start() {
601
+ if (this.state === SequencerState.STOPPING) {
602
+ throw new Error('Cannot start sequencer while it is stopping');
603
+ }
604
+ if (this.runningPromise?.isRunning()) {
605
+ this.log.warn('Attempted to start sequencer that is already running');
606
+ return;
607
+ }
584
608
  this.runningPromise = new RunningPromise(this.safeWork.bind(this), this.log, this.config.sequencerPollingIntervalMS);
585
609
  this.setState(SequencerState.IDLE, undefined, {
586
610
  force: true
@@ -591,20 +615,56 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
591
615
  /** Triggers an immediate run of the sequencer, bypassing the polling interval. */ trigger() {
592
616
  return this.runningPromise?.trigger();
593
617
  }
594
- /** Stops the sequencer from building blocks and moves to STOPPED state. */ async stop() {
618
+ /**
619
+ * Stops the sequencer from building blocks and moves it to STOPPED state, interrupting the in-flight
620
+ * work() iteration and its pending L1 submissions for a fast shutdown, then draining before returning.
621
+ * Idempotent: a second call while already stopped or stopping is a no-op. Lifecycle calls are expected
622
+ * to be serialized by the caller. For a restartable pause that lets in-flight work finish untouched
623
+ * (e.g. around a test clock warp), use {@link pause} instead.
624
+ */ async stop() {
625
+ if (this.state === SequencerState.STOPPED || this.state === SequencerState.STOPPING) {
626
+ this.log.debug(`Sequencer already ${this.state.toLowerCase()}, ignoring stop`);
627
+ return;
628
+ }
595
629
  this.log.info(`Stopping sequencer`);
596
630
  this.setState(SequencerState.STOPPING, undefined, {
597
631
  force: true
598
632
  });
599
633
  this.lastCheckpointProposalJob?.interrupt();
600
634
  await this.publisherFactory.stopAll();
635
+ // Stop the poll loop and await the in-flight work() iteration. work() registers its fire-and-forget
636
+ // requests synchronously before returning, so once the loop has stopped, pendingRequests holds every
637
+ // request that will ever exist. Interrupting any earlier could miss a request registered by the last
638
+ // in-flight iteration.
601
639
  await this.runningPromise?.stop();
602
- await this.lastCheckpointProposalJob?.awaitPendingSubmission();
640
+ this.pendingRequests.interruptRequests();
641
+ await this.pendingRequests.awaitRequests();
603
642
  this.setState(SequencerState.STOPPED, undefined, {
604
643
  force: true
605
644
  });
606
645
  this.log.info('Stopped sequencer');
607
646
  }
647
+ /**
648
+ * Gracefully pauses block production so the sequencer can later be resumed with {@link start}: halts the
649
+ * poll loop and waits for the in-flight work() iteration and every pending L1 submission / fallback send
650
+ * to finish, without interrupting them. No interrupt lands mid-build, so no spurious checkpoint-error is
651
+ * emitted and no enqueued checkpoint is dropped. Deliberately does not stop inner services (validator/HA
652
+ * signer, publishers), so the slashing-protection store stays open and the sequencer stays restartable.
653
+ * Used by tests that pause sequencers around an L1 clock warp. Idempotent.
654
+ */ async pause() {
655
+ if (!this.runningPromise?.isRunning()) {
656
+ this.log.debug('Sequencer not running, ignoring pause');
657
+ return;
658
+ }
659
+ this.log.info('Pausing sequencer');
660
+ // Halt the poll loop and let the in-flight iteration finish naturally — no interrupt, and no STOPPING
661
+ // state, since entering STOPPING would make the iteration's own setState calls throw and fail it.
662
+ // work() registers its fire-and-forget requests synchronously before returning, so once the loop has
663
+ // stopped pendingRequests holds every request that will ever exist; awaiting it lets them all finish.
664
+ await this.runningPromise.stop();
665
+ await this.pendingRequests.awaitRequests();
666
+ this.log.info('Paused sequencer');
667
+ }
608
668
  /** Main sequencer loop with a try/catch */ async safeWork() {
609
669
  try {
610
670
  await this.work();
@@ -731,18 +791,6 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
731
791
  }
732
792
  return undefined;
733
793
  }
734
- // We are the proposer and the escape hatch is closed: now run the full sync check before building.
735
- const syncedTo = await this.checkSync({
736
- ts,
737
- slot
738
- });
739
- if (!syncedTo) {
740
- await this.tryVoteAndPruneWhenCannotBuild({
741
- slot,
742
- targetSlot
743
- });
744
- return undefined;
745
- }
746
794
  // Explicit build-loop entry gate: if we are past the latest useful block-building start for the
747
795
  // target slot, abandon building for this slot. The proposer prioritizes the ideal L1-publish path
748
796
  // and does not plan around the late consensus-handoff path. This is the proposer build path's
@@ -766,6 +814,15 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
766
814
  this.lastSlotForCheckpointProposalJob = targetSlot;
767
815
  return undefined;
768
816
  }
817
+ // We are the proposer, the escape hatch is closed, and we have time before the build start deadline.
818
+ // Now run the full sync check before building.
819
+ const syncedTo = await this.checkSync({
820
+ ts,
821
+ slot
822
+ });
823
+ if (!syncedTo) {
824
+ return undefined;
825
+ }
769
826
  // Next checkpoint follows from the last synced one
770
827
  const checkpointNumber = CheckpointNumber(syncedTo.checkpointNumber + 1);
771
828
  const logCtx = {
@@ -926,7 +983,7 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
926
983
  return this.createCheckpointProposalJob(targetSlot, targetEpoch, checkpointNumber, syncedTo.blockNumber, syncedTo.checkpointedCheckpointNumber, proposer, publisher, attestorAddress, invalidateCheckpoint, syncedTo.proposedCheckpointData);
927
984
  }
928
985
  createCheckpointProposalJob(targetSlot, targetEpoch, checkpointNumber, syncedToBlockNumber, checkpointedCheckpointNumber, proposer, publisher, attestorAddress, invalidateCheckpoint, proposedCheckpointData) {
929
- return new CheckpointProposalJob(targetSlot, targetEpoch, checkpointNumber, syncedToBlockNumber, checkpointedCheckpointNumber, proposer, publisher, attestorAddress, invalidateCheckpoint, this.validatorClient, this.globalsBuilder, this.p2pClient, this.worldState, this.l1ToL2MessageSource, this.l2BlockSource, this.checkpointsBuilder, this.l2BlockSource, this.l1Constants, this.signatureContext, this.config, this.timetable, this.slasherClient, this.epochCache, this.dateProvider, this.metrics, this.checkpointProposalJobMetrics.createRecorder(), this, this.setState.bind(this), this.tracer, this.log.getBindings(), proposedCheckpointData);
986
+ return new CheckpointProposalJob(targetSlot, targetEpoch, checkpointNumber, syncedToBlockNumber, checkpointedCheckpointNumber, proposer, publisher, attestorAddress, invalidateCheckpoint, this.validatorClient, this.globalsBuilder, this.p2pClient, this.worldState, this.l1ToL2MessageSource, this.l2BlockSource, this.checkpointsBuilder, this.l2BlockSource, this.l1Constants, this.signatureContext, this.config, this.timetable, this.slasherClient, this.epochCache, this.dateProvider, this.metrics, this.checkpointProposalJobMetrics.createRecorder(), this, this.pendingRequests, this.setState.bind(this), this.tracer, this.log.getBindings(), proposedCheckpointData);
930
987
  }
931
988
  /**
932
989
  * Returns the current sequencer state.
@@ -1207,12 +1264,13 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
1207
1264
  // Votes are EIP-712-signed for `targetSlot` (the pipelined slot in which the multicall is
1208
1265
  // expected to mine). Delay submission to the start of `targetSlot` so the tx mines in the
1209
1266
  // slot the votes were signed for. We fire-and-forget so we don't block the sequencer's
1210
- // work loop while waiting for the target slot to start.
1211
- void publisher.sendRequestsAt(targetSlot).catch((err)=>{
1267
+ // work loop while waiting for the target slot to start, but track it so stop() can drain it.
1268
+ const send = publisher.sendRequestsAt(targetSlot).catch((err)=>{
1212
1269
  this.log.error(`Failed to publish fallback requests despite sync failure for slot ${slot}`, err, {
1213
1270
  slot
1214
1271
  });
1215
1272
  });
1273
+ this.pendingRequests.trackRequest(send, ()=>publisher.interrupt());
1216
1274
  }
1217
1275
  async tryEnqueuePruneIfPrunable(targetSlot, publisher) {
1218
1276
  try {
@@ -1261,13 +1319,15 @@ _dec = trackSpan('Sequencer.work'), _dec1 = trackSpan('Sequencer.prepareCheckpoi
1261
1319
  // the multicall mines in the slot the votes were signed for; otherwise the L1 contract reads
1262
1320
  // `signaler = getCurrentProposer()` against the wrong slot and signature verification fails
1263
1321
  // silently inside Multicall3. Fire-and-forget so we don't block the sequencer's work loop while
1264
- // waiting for the target slot to start, mirroring tryVoteAndPruneWhenCannotBuild.
1265
- void publisher.sendRequestsAt(targetSlot).catch((err)=>{
1322
+ // waiting for the target slot to start, mirroring tryVoteAndPruneWhenCannotBuild, but tracked so
1323
+ // stop() can drain it.
1324
+ const send = publisher.sendRequestsAt(targetSlot).catch((err)=>{
1266
1325
  this.log.error(`Failed to publish escape-hatch votes for slot ${slot}`, err, {
1267
1326
  slot,
1268
1327
  targetSlot
1269
1328
  });
1270
1329
  });
1330
+ this.pendingRequests.trackRequest(send, ()=>publisher.interrupt());
1271
1331
  }
1272
1332
  /**
1273
1333
  * Considers invalidating a block if the pending chain is invalid. Depends on how long the invalid block
@@ -64,4 +64,4 @@ export declare function setupTxsAndBlock(p2p: MockProxy<P2P>, globalVariables: G
64
64
  txs: Tx[];
65
65
  block: L2Block;
66
66
  }>;
67
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidXRpbHMuZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy90ZXN0L3V0aWxzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUdBLE9BQU8sRUFBRSxlQUFlLEVBQUUsTUFBTSwyQ0FBMkMsQ0FBQztBQUM1RSxPQUFPLEVBQUUsRUFBRSxFQUFFLE1BQU0sZ0NBQWdDLENBQUM7QUFDcEQsT0FBTyxLQUFLLEVBQUUsVUFBVSxFQUFFLE1BQU0sK0JBQStCLENBQUM7QUFDaEUsT0FBTyxFQUFFLFNBQVMsRUFBRSxNQUFNLGlDQUFpQyxDQUFDO0FBQzVELE9BQU8sS0FBSyxFQUFFLEdBQUcsRUFBRSxNQUFNLFlBQVksQ0FBQztBQUV0QyxPQUFPLEVBQUUsb0JBQW9CLEVBQUUsT0FBTyxFQUFFLE1BQU0scUJBQXFCLENBQUM7QUFFcEUsT0FBTyxLQUFLLEVBQUUsaUJBQWlCLEVBQUUsTUFBTSw2QkFBNkIsQ0FBQztBQUNyRSxPQUFPLEVBQUUsYUFBYSxFQUFFLHFCQUFxQixFQUFFLGtCQUFrQixFQUFvQixNQUFNLG1CQUFtQixDQUFDO0FBTy9HLE9BQU8sRUFLTCxpQkFBaUIsRUFDbEIsTUFBTSx5QkFBeUIsQ0FBQztBQUNqQyxPQUFPLEVBQWUsZUFBZSxFQUFFLEtBQUssRUFBRSxFQUFvQyxNQUFNLGtCQUFrQixDQUFDO0FBRTNHLE9BQU8sS0FBSyxFQUFFLFNBQVMsRUFBRSxNQUFNLG9CQUFvQixDQUFDO0FBR3BELE9BQU8sRUFBRSxxQkFBcUIsRUFBRSxzQkFBc0IsRUFBRSxNQUFNLDhCQUE4QixDQUFDO0FBRTdGOzs7O0dBSUc7QUFDSCx3QkFBZ0IscUJBQXFCLENBQUMsSUFBSSxFQUFFO0lBQzFDLFdBQVcsRUFBRSxJQUFJLENBQUMsaUJBQWlCLEVBQUUsZUFBZSxHQUFHLGNBQWMsR0FBRyxzQkFBc0IsQ0FBQyxDQUFDO0lBQ2hHLGVBQWUsQ0FBQyxFQUFFLE1BQU0sQ0FBQztJQUN6QixnQkFBZ0IsQ0FBQyxFQUFFLE1BQU0sQ0FBQztJQUMxQixrQkFBa0IsQ0FBQyxFQUFFLE1BQU0sQ0FBQztJQUM1Qiw2QkFBNkIsQ0FBQyxFQUFFLE1BQU0sQ0FBQztDQUN4QyxHQUFHLGlCQUFpQixDQVNwQjtBQUVEOztHQUVHO0FBQ0gsd0JBQXNCLE1BQU0sQ0FBQyxJQUFJLENBQUMsRUFBRSxNQUFNLEVBQUUsT0FBTyxDQUFDLEVBQUUsRUFBRSxHQUFHLE9BQU8sQ0FBQyxFQUFFLENBQUMsQ0FNckU7QUFFRDs7R0FFRztBQUNILHdCQUFzQixTQUFTLENBQUMsR0FBRyxFQUFFLEVBQUUsRUFBRSxFQUFFLGVBQWUsRUFBRSxlQUFlLEdBQUcsT0FBTyxDQUFDLE9BQU8sQ0FBQyxDQWdCN0Y7QUFFRDs7R0FFRztBQUNILHdCQUFnQixjQUFjLENBQUMsR0FBRyxFQUFFLFNBQVMsQ0FBQyxHQUFHLENBQUMsRUFBRSxHQUFHLEVBQUUsRUFBRSxFQUFFLEdBQUcsSUFBSSxDQUluRTtBQUVEOztHQUVHO0FBQ0gsd0JBQXVCLGNBQWMsQ0FBQyxHQUFHLEVBQUUsT0FBTyxDQUFDLEVBQUUsRUFBRSxDQUFDLEdBQUcscUJBQXFCLENBQUMsRUFBRSxDQUFDLENBSW5GO0FBRUQ7O0dBRUc7QUFDSCx3QkFBZ0Isb0JBQW9CLENBQUMsTUFBTSxFQUFFLGVBQWUsR0FBRyxvQkFBb0IsRUFBRSxDQUdwRjtBQXdCRDs7R0FFRztBQUNILHdCQUFnQixtQkFBbUIsQ0FBQyxLQUFLLEVBQUUsT0FBTyxFQUFFLFNBQVMsRUFBRSxTQUFTLEdBQUcsYUFBYSxDQVd2RjtBQUVEOztHQUVHO0FBQ0gsd0JBQWdCLHdCQUF3QixDQUN0QyxLQUFLLEVBQUUsT0FBTyxFQUNkLG1CQUFtQixFQUFFLFNBQVMsRUFDOUIsY0FBYyxDQUFDLEVBQUUsU0FBUyxFQUMxQixxQkFBcUIsR0FBRSxNQUFXLEdBQ2pDLGtCQUFrQixDQWdCcEI7QUFFRDs7OztHQUlHO0FBQ0gsd0JBQWdCLDJCQUEyQixDQUN6QyxLQUFLLEVBQUUsT0FBTyxFQUNkLFNBQVMsRUFBRSxTQUFTLEVBQ3BCLE1BQU0sRUFBRSxVQUFVLEVBQ2xCLHFCQUFxQixHQUFFLE1BQVcsR0FDakMscUJBQXFCLENBYXZCO0FBRUQ7OztHQUdHO0FBQ0gsd0JBQXNCLGdCQUFnQixDQUNwQyxHQUFHLEVBQUUsU0FBUyxDQUFDLEdBQUcsQ0FBQyxFQUNuQixlQUFlLEVBQUUsZUFBZSxFQUNoQyxPQUFPLEVBQUUsTUFBTSxFQUNmLE9BQU8sRUFBRSxFQUFFLEdBQ1YsT0FBTyxDQUFDO0lBQUUsR0FBRyxFQUFFLEVBQUUsRUFBRSxDQUFDO0lBQUMsS0FBSyxFQUFFLE9BQU8sQ0FBQTtDQUFFLENBQUMsQ0FLeEMifQ==
67
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidXRpbHMuZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy90ZXN0L3V0aWxzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUdBLE9BQU8sRUFBRSxlQUFlLEVBQUUsTUFBTSwyQ0FBMkMsQ0FBQztBQUM1RSxPQUFPLEVBQUUsRUFBRSxFQUFFLE1BQU0sZ0NBQWdDLENBQUM7QUFDcEQsT0FBTyxLQUFLLEVBQUUsVUFBVSxFQUFFLE1BQU0sK0JBQStCLENBQUM7QUFDaEUsT0FBTyxFQUFFLFNBQVMsRUFBRSxNQUFNLGlDQUFpQyxDQUFDO0FBQzVELE9BQU8sS0FBSyxFQUFFLEdBQUcsRUFBRSxNQUFNLFlBQVksQ0FBQztBQUV0QyxPQUFPLEVBQUUsb0JBQW9CLEVBQUUsT0FBTyxFQUFFLE1BQU0scUJBQXFCLENBQUM7QUFFcEUsT0FBTyxLQUFLLEVBQUUsaUJBQWlCLEVBQUUsTUFBTSw2QkFBNkIsQ0FBQztBQUNyRSxPQUFPLEVBQUUsYUFBYSxFQUFFLHFCQUFxQixFQUFFLGtCQUFrQixFQUFvQixNQUFNLG1CQUFtQixDQUFDO0FBTy9HLE9BQU8sRUFLTCxpQkFBaUIsRUFDbEIsTUFBTSx5QkFBeUIsQ0FBQztBQUNqQyxPQUFPLEVBQWUsZUFBZSxFQUFFLEtBQUssRUFBRSxFQUFvQyxNQUFNLGtCQUFrQixDQUFDO0FBRTNHLE9BQU8sS0FBSyxFQUFFLFNBQVMsRUFBRSxNQUFNLG9CQUFvQixDQUFDO0FBR3BELE9BQU8sRUFBRSxxQkFBcUIsRUFBRSxzQkFBc0IsRUFBRSxNQUFNLDhCQUE4QixDQUFDO0FBRTdGOzs7O0dBSUc7QUFDSCx3QkFBZ0IscUJBQXFCLENBQUMsSUFBSSxFQUFFO0lBQzFDLFdBQVcsRUFBRSxJQUFJLENBQUMsaUJBQWlCLEVBQUUsZUFBZSxHQUFHLGNBQWMsR0FBRyxzQkFBc0IsQ0FBQyxDQUFDO0lBQ2hHLGVBQWUsQ0FBQyxFQUFFLE1BQU0sQ0FBQztJQUN6QixnQkFBZ0IsQ0FBQyxFQUFFLE1BQU0sQ0FBQztJQUMxQixrQkFBa0IsQ0FBQyxFQUFFLE1BQU0sQ0FBQztJQUM1Qiw2QkFBNkIsQ0FBQyxFQUFFLE1BQU0sQ0FBQztDQUN4QyxHQUFHLGlCQUFpQixDQVNwQjtBQUVEOztHQUVHO0FBQ0gsd0JBQXNCLE1BQU0sQ0FBQyxJQUFJLENBQUMsRUFBRSxNQUFNLEVBQUUsT0FBTyxDQUFDLEVBQUUsRUFBRSxHQUFHLE9BQU8sQ0FBQyxFQUFFLENBQUMsQ0FNckU7QUFFRDs7R0FFRztBQUNILHdCQUFzQixTQUFTLENBQUMsR0FBRyxFQUFFLEVBQUUsRUFBRSxFQUFFLGVBQWUsRUFBRSxlQUFlLEdBQUcsT0FBTyxDQUFDLE9BQU8sQ0FBQyxDQWdCN0Y7QUFFRDs7R0FFRztBQUNILHdCQUFnQixjQUFjLENBQUMsR0FBRyxFQUFFLFNBQVMsQ0FBQyxHQUFHLENBQUMsRUFBRSxHQUFHLEVBQUUsRUFBRSxFQUFFLEdBQUcsSUFBSSxDQUtuRTtBQUVEOztHQUVHO0FBQ0gsd0JBQXVCLGNBQWMsQ0FBQyxHQUFHLEVBQUUsT0FBTyxDQUFDLEVBQUUsRUFBRSxDQUFDLEdBQUcscUJBQXFCLENBQUMsRUFBRSxDQUFDLENBSW5GO0FBRUQ7O0dBRUc7QUFDSCx3QkFBZ0Isb0JBQW9CLENBQUMsTUFBTSxFQUFFLGVBQWUsR0FBRyxvQkFBb0IsRUFBRSxDQUdwRjtBQXdCRDs7R0FFRztBQUNILHdCQUFnQixtQkFBbUIsQ0FBQyxLQUFLLEVBQUUsT0FBTyxFQUFFLFNBQVMsRUFBRSxTQUFTLEdBQUcsYUFBYSxDQVd2RjtBQUVEOztHQUVHO0FBQ0gsd0JBQWdCLHdCQUF3QixDQUN0QyxLQUFLLEVBQUUsT0FBTyxFQUNkLG1CQUFtQixFQUFFLFNBQVMsRUFDOUIsY0FBYyxDQUFDLEVBQUUsU0FBUyxFQUMxQixxQkFBcUIsR0FBRSxNQUFXLEdBQ2pDLGtCQUFrQixDQWdCcEI7QUFFRDs7OztHQUlHO0FBQ0gsd0JBQWdCLDJCQUEyQixDQUN6QyxLQUFLLEVBQUUsT0FBTyxFQUNkLFNBQVMsRUFBRSxTQUFTLEVBQ3BCLE1BQU0sRUFBRSxVQUFVLEVBQ2xCLHFCQUFxQixHQUFFLE1BQVcsR0FDakMscUJBQXFCLENBYXZCO0FBRUQ7OztHQUdHO0FBQ0gsd0JBQXNCLGdCQUFnQixDQUNwQyxHQUFHLEVBQUUsU0FBUyxDQUFDLEdBQUcsQ0FBQyxFQUNuQixlQUFlLEVBQUUsZUFBZSxFQUNoQyxPQUFPLEVBQUUsTUFBTSxFQUNmLE9BQU8sRUFBRSxFQUFFLEdBQ1YsT0FBTyxDQUFDO0lBQUUsR0FBRyxFQUFFLEVBQUUsRUFBRSxDQUFDO0lBQUMsS0FBSyxFQUFFLE9BQU8sQ0FBQTtDQUFFLENBQUMsQ0FLeEMifQ==
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/test/utils.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,eAAe,EAAE,MAAM,2CAA2C,CAAC;AAC5E,OAAO,EAAE,EAAE,EAAE,MAAM,gCAAgC,CAAC;AACpD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAChE,OAAO,EAAE,SAAS,EAAE,MAAM,iCAAiC,CAAC;AAC5D,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;AAEtC,OAAO,EAAE,oBAAoB,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAEpE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AACrE,OAAO,EAAE,aAAa,EAAE,qBAAqB,EAAE,kBAAkB,EAAoB,MAAM,mBAAmB,CAAC;AAO/G,OAAO,EAKL,iBAAiB,EAClB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAe,eAAe,EAAE,KAAK,EAAE,EAAoC,MAAM,kBAAkB,CAAC;AAE3G,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAGpD,OAAO,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,MAAM,8BAA8B,CAAC;AAE7F;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE;IAC1C,WAAW,EAAE,IAAI,CAAC,iBAAiB,EAAE,eAAe,GAAG,cAAc,GAAG,sBAAsB,CAAC,CAAC;IAChG,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,6BAA6B,CAAC,EAAE,MAAM,CAAC;CACxC,GAAG,iBAAiB,CASpB;AAED;;GAEG;AACH,wBAAsB,MAAM,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,EAAE,GAAG,OAAO,CAAC,EAAE,CAAC,CAMrE;AAED;;GAEG;AACH,wBAAsB,SAAS,CAAC,GAAG,EAAE,EAAE,EAAE,EAAE,eAAe,EAAE,eAAe,GAAG,OAAO,CAAC,OAAO,CAAC,CAgB7F;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,SAAS,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE,EAAE,EAAE,GAAG,IAAI,CAInE;AAED;;GAEG;AACH,wBAAuB,cAAc,CAAC,GAAG,EAAE,OAAO,CAAC,EAAE,EAAE,CAAC,GAAG,qBAAqB,CAAC,EAAE,CAAC,CAInF;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,eAAe,GAAG,oBAAoB,EAAE,CAGpF;AAwBD;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,GAAG,aAAa,CAWvF;AAED;;GAEG;AACH,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,OAAO,EACd,mBAAmB,EAAE,SAAS,EAC9B,cAAc,CAAC,EAAE,SAAS,EAC1B,qBAAqB,GAAE,MAAW,GACjC,kBAAkB,CAgBpB;AAED;;;;GAIG;AACH,wBAAgB,2BAA2B,CACzC,KAAK,EAAE,OAAO,EACd,SAAS,EAAE,SAAS,EACpB,MAAM,EAAE,UAAU,EAClB,qBAAqB,GAAE,MAAW,GACjC,qBAAqB,CAavB;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,SAAS,CAAC,GAAG,CAAC,EACnB,eAAe,EAAE,eAAe,EAChC,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,EAAE,GACV,OAAO,CAAC;IAAE,GAAG,EAAE,EAAE,EAAE,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAAC,CAKxC"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/test/utils.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,eAAe,EAAE,MAAM,2CAA2C,CAAC;AAC5E,OAAO,EAAE,EAAE,EAAE,MAAM,gCAAgC,CAAC;AACpD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAChE,OAAO,EAAE,SAAS,EAAE,MAAM,iCAAiC,CAAC;AAC5D,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;AAEtC,OAAO,EAAE,oBAAoB,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAEpE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AACrE,OAAO,EAAE,aAAa,EAAE,qBAAqB,EAAE,kBAAkB,EAAoB,MAAM,mBAAmB,CAAC;AAO/G,OAAO,EAKL,iBAAiB,EAClB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAe,eAAe,EAAE,KAAK,EAAE,EAAoC,MAAM,kBAAkB,CAAC;AAE3G,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAGpD,OAAO,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,MAAM,8BAA8B,CAAC;AAE7F;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE;IAC1C,WAAW,EAAE,IAAI,CAAC,iBAAiB,EAAE,eAAe,GAAG,cAAc,GAAG,sBAAsB,CAAC,CAAC;IAChG,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,6BAA6B,CAAC,EAAE,MAAM,CAAC;CACxC,GAAG,iBAAiB,CASpB;AAED;;GAEG;AACH,wBAAsB,MAAM,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,EAAE,GAAG,OAAO,CAAC,EAAE,CAAC,CAMrE;AAED;;GAEG;AACH,wBAAsB,SAAS,CAAC,GAAG,EAAE,EAAE,EAAE,EAAE,eAAe,EAAE,eAAe,GAAG,OAAO,CAAC,OAAO,CAAC,CAgB7F;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,SAAS,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE,EAAE,EAAE,GAAG,IAAI,CAKnE;AAED;;GAEG;AACH,wBAAuB,cAAc,CAAC,GAAG,EAAE,OAAO,CAAC,EAAE,EAAE,CAAC,GAAG,qBAAqB,CAAC,EAAE,CAAC,CAInF;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,eAAe,GAAG,oBAAoB,EAAE,CAGpF;AAwBD;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,GAAG,aAAa,CAWvF;AAED;;GAEG;AACH,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,OAAO,EACd,mBAAmB,EAAE,SAAS,EAC9B,cAAc,CAAC,EAAE,SAAS,EAC1B,qBAAqB,GAAE,MAAW,GACjC,kBAAkB,CAgBpB;AAED;;;;GAIG;AACH,wBAAgB,2BAA2B,CACzC,KAAK,EAAE,OAAO,EACd,SAAS,EAAE,SAAS,EACpB,MAAM,EAAE,UAAU,EAClB,qBAAqB,GAAE,MAAW,GACjC,qBAAqB,CAavB;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,SAAS,CAAC,GAAG,CAAC,EACnB,eAAe,EAAE,eAAe,EAChC,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,EAAE,GACV,OAAO,CAAC;IAAE,GAAG,EAAE,EAAE,EAAE,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAAC,CAKxC"}
@@ -51,6 +51,7 @@ export { MockCheckpointBuilder, MockCheckpointsBuilder } from './mock_checkpoint
51
51
  * Mocks the P2P client to return specific pending transactions
52
52
  */ export function mockPendingTxs(p2p, txs) {
53
53
  p2p.getPendingTxCount.mockResolvedValue(txs.length);
54
+ p2p.hasEligiblePendingTxs.mockImplementation((minCount)=>Promise.resolve(txs.length >= minCount));
54
55
  p2p.iteratePendingTxs.mockImplementation(()=>mockTxIterator(Promise.resolve(txs)));
55
56
  p2p.iterateEligiblePendingTxs.mockImplementation(()=>mockTxIterator(Promise.resolve(txs)));
56
57
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aztec/sequencer-client",
3
- "version": "5.0.0-rc.2",
3
+ "version": "5.0.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./dest/index.js",
@@ -27,37 +27,37 @@
27
27
  "test:integration:run": "NODE_NO_WARNINGS=1 node --experimental-vm-modules $(yarn bin jest) --no-cache --config jest.integration.config.json"
28
28
  },
29
29
  "dependencies": {
30
- "@aztec/aztec.js": "5.0.0-rc.2",
31
- "@aztec/bb-prover": "5.0.0-rc.2",
32
- "@aztec/blob-client": "5.0.0-rc.2",
33
- "@aztec/blob-lib": "5.0.0-rc.2",
34
- "@aztec/constants": "5.0.0-rc.2",
35
- "@aztec/epoch-cache": "5.0.0-rc.2",
36
- "@aztec/ethereum": "5.0.0-rc.2",
37
- "@aztec/foundation": "5.0.0-rc.2",
38
- "@aztec/l1-artifacts": "5.0.0-rc.2",
39
- "@aztec/node-keystore": "5.0.0-rc.2",
40
- "@aztec/noir-acvm_js": "5.0.0-rc.2",
41
- "@aztec/noir-contracts.js": "5.0.0-rc.2",
42
- "@aztec/noir-protocol-circuits-types": "5.0.0-rc.2",
43
- "@aztec/noir-types": "5.0.0-rc.2",
44
- "@aztec/p2p": "5.0.0-rc.2",
45
- "@aztec/protocol-contracts": "5.0.0-rc.2",
46
- "@aztec/prover-client": "5.0.0-rc.2",
47
- "@aztec/simulator": "5.0.0-rc.2",
48
- "@aztec/slasher": "5.0.0-rc.2",
49
- "@aztec/stdlib": "5.0.0-rc.2",
50
- "@aztec/telemetry-client": "5.0.0-rc.2",
51
- "@aztec/validator-client": "5.0.0-rc.2",
52
- "@aztec/validator-ha-signer": "5.0.0-rc.2",
53
- "@aztec/world-state": "5.0.0-rc.2",
30
+ "@aztec/aztec.js": "5.0.0",
31
+ "@aztec/bb-prover": "5.0.0",
32
+ "@aztec/blob-client": "5.0.0",
33
+ "@aztec/blob-lib": "5.0.0",
34
+ "@aztec/constants": "5.0.0",
35
+ "@aztec/epoch-cache": "5.0.0",
36
+ "@aztec/ethereum": "5.0.0",
37
+ "@aztec/foundation": "5.0.0",
38
+ "@aztec/l1-artifacts": "5.0.0",
39
+ "@aztec/node-keystore": "5.0.0",
40
+ "@aztec/noir-acvm_js": "5.0.0",
41
+ "@aztec/noir-contracts.js": "5.0.0",
42
+ "@aztec/noir-protocol-circuits-types": "5.0.0",
43
+ "@aztec/noir-types": "5.0.0",
44
+ "@aztec/p2p": "5.0.0",
45
+ "@aztec/protocol-contracts": "5.0.0",
46
+ "@aztec/prover-client": "5.0.0",
47
+ "@aztec/simulator": "5.0.0",
48
+ "@aztec/slasher": "5.0.0",
49
+ "@aztec/stdlib": "5.0.0",
50
+ "@aztec/telemetry-client": "5.0.0",
51
+ "@aztec/validator-client": "5.0.0",
52
+ "@aztec/validator-ha-signer": "5.0.0",
53
+ "@aztec/world-state": "5.0.0",
54
54
  "lodash.chunk": "^4.2.0",
55
55
  "tslib": "^2.4.0",
56
56
  "viem": "npm:@aztec/viem@2.38.2"
57
57
  },
58
58
  "devDependencies": {
59
- "@aztec/archiver": "5.0.0-rc.2",
60
- "@aztec/kv-store": "5.0.0-rc.2",
59
+ "@aztec/archiver": "5.0.0",
60
+ "@aztec/kv-store": "5.0.0",
61
61
  "@electric-sql/pglite": "^0.3.14",
62
62
  "@jest/globals": "^30.0.0",
63
63
  "@types/jest": "^30.0.0",
@@ -180,16 +180,23 @@ export class SequencerClient {
180
180
  this.validatorClient?.updateConfig(config);
181
181
  }
182
182
 
183
- /** Starts the sequencer. */
183
+ /**
184
+ * Starts (or resumes) the sequencer, validator, publishers, and metrics. Each underlying start is
185
+ * idempotent, so this is safe to call after a previous {@link pause} to resume building and publishing.
186
+ * The publisher manager is started before the sequencer's poll loop so publishing is ready before the
187
+ * sequencer first tries to publish to L1.
188
+ */
184
189
  public async start() {
185
190
  await this.validatorClient?.start();
191
+ await this.publisherManager.start();
186
192
  this.sequencer.start();
187
193
  this.l1Metrics?.start();
188
- await this.publisherManager.start();
189
194
  }
190
195
 
191
196
  /**
192
- * Stops the sequencer from processing new txs.
197
+ * Stops the sequencer, validator, publishers, and metrics for good, draining in-flight work. This is the
198
+ * final teardown path: stopping the validator client closes its slashing-protection database. For a
199
+ * restartable pause (e.g. around a test clock warp) use {@link pause} instead.
193
200
  */
194
201
  public async stop() {
195
202
  await this.sequencer.stop();
@@ -198,6 +205,15 @@ export class SequencerClient {
198
205
  this.l1Metrics?.stop();
199
206
  }
200
207
 
208
+ /**
209
+ * Gracefully pauses block production, waiting for in-flight work to finish rather than interrupting it,
210
+ * while leaving the validator, publishers, and metrics running so the sequencer can be resumed with
211
+ * {@link start}. Used by tests that pause sequencers around an L1 clock warp.
212
+ */
213
+ public async pause() {
214
+ await this.sequencer.pause();
215
+ }
216
+
201
217
  /** Triggers an immediate run of the sequencer, bypassing the polling interval. */
202
218
  public trigger() {
203
219
  return this.sequencer.trigger();
package/src/config.ts CHANGED
@@ -62,6 +62,7 @@ export const DefaultSequencerConfig = {
62
62
  injectFakeAttestation: false,
63
63
  injectHighSValueAttestation: false,
64
64
  injectUnrecoverableSignatureAttestation: false,
65
+ injectYParityAttestation: false,
65
66
  fishermanMode: false,
66
67
  shuffleAttestationOrdering: false,
67
68
  skipPushProposedBlocksToArchiver: false,
@@ -129,7 +130,7 @@ export const sequencerConfigMappings: ConfigMappingsType<SequencerConfig> = {
129
130
  description:
130
131
  'Per-block budget multiplier applied to DA gas and blob fields in place of perBlockAllocationMultiplier.' +
131
132
  ' Defaults higher than the general multiplier so the largest contract class deploy fits a single block.',
132
- ...numberConfigHelper(DefaultSequencerConfig.perBlockDAAllocationMultiplier),
133
+ ...floatConfigHelper(DefaultSequencerConfig.perBlockDAAllocationMultiplier),
133
134
  },
134
135
  redistributeCheckpointBudget: {
135
136
  env: 'SEQ_REDISTRIBUTE_CHECKPOINT_BUDGET',
@@ -225,6 +226,10 @@ export const sequencerConfigMappings: ConfigMappingsType<SequencerConfig> = {
225
226
  description: 'Inject an attestation with an unrecoverable signature (for testing only)',
226
227
  ...booleanConfigHelper(DefaultSequencerConfig.injectUnrecoverableSignatureAttestation),
227
228
  },
229
+ injectYParityAttestation: {
230
+ description: 'Inject a non-proposer attestation slot in yParity form in the packed L1 tuple (for testing only)',
231
+ ...booleanConfigHelper(DefaultSequencerConfig.injectYParityAttestation),
232
+ },
228
233
  fishermanMode: {
229
234
  env: 'FISHERMAN_MODE',
230
235
  description:
@@ -242,7 +247,7 @@ export const sequencerConfigMappings: ConfigMappingsType<SequencerConfig> = {
242
247
  ...booleanConfigHelper(DefaultSequencerConfig.buildCheckpointIfEmpty),
243
248
  },
244
249
  skipPushProposedBlocksToArchiver: {
245
- description: 'Skip pushing proposed blocks to archiver (default: true)',
250
+ description: 'Skip pushing proposed blocks to archiver (test only)',
246
251
  ...booleanConfigHelper(DefaultSequencerConfig.skipPushProposedBlocksToArchiver),
247
252
  },
248
253
  minBlocksForCheckpoint: {
@@ -61,7 +61,17 @@ export class FeePredictor {
61
61
  const blockNumber = await this.publicClient.getBlockNumber({ cacheTime: 0 });
62
62
  if (this.cachedL1BlockNumber === undefined || blockNumber > this.cachedL1BlockNumber) {
63
63
  this.cachedL1BlockNumber = blockNumber;
64
- this.cachedState = this.fetchState(blockNumber);
64
+ // Reset the cached block number on failure so a transient L1 RPC error does not leave a
65
+ // rejected promise cached for this block, which would replay the same rejection on every
66
+ // subsequent call until the next L1 block arrives. Only clear it if it still points at the
67
+ // block this attempt was for, so a stale rejection from an older block cannot wipe a marker
68
+ // a newer call already advanced (which would also defeat the monotonic block-number guard).
69
+ this.cachedState = this.fetchState(blockNumber).catch(err => {
70
+ if (this.cachedL1BlockNumber === blockNumber) {
71
+ this.cachedL1BlockNumber = undefined;
72
+ }
73
+ throw err;
74
+ });
65
75
  }
66
76
  return this.cachedState!;
67
77
  }
@@ -61,10 +61,27 @@ export class FeeProviderImpl implements FeeProvider {
61
61
  // Get the current block number
62
62
  const blockNumber = await this.publicClient.getBlockNumber({ cacheTime: 0 });
63
63
 
64
- // If the L1 block number has changed then chain a new promise to get the current min fees
64
+ // If the L1 block number has changed then chain a new promise to get the current min fees.
65
+ // We chain off the previous promise's settlement (via a swallowing catch) rather than its
66
+ // fulfillment, so a prior rejection does not short-circuit the new computation. If the new
67
+ // computation fails (e.g. a transient L1 RPC error), reset the cached block number so the
68
+ // next call recomputes instead of permanently replaying the rejected promise — otherwise a
69
+ // single transient failure would wedge fee estimation until the next L1 block arrives. Only
70
+ // clear it if it still points at the block this attempt was for, so a stale rejection from an
71
+ // older block cannot wipe a marker a newer call already advanced (which would also defeat the
72
+ // monotonic block-number guard).
65
73
  if (this.currentL1BlockNumber === undefined || blockNumber > this.currentL1BlockNumber) {
66
74
  this.currentL1BlockNumber = blockNumber;
67
- this.currentMinFees = this.currentMinFees.then(() => this.computeCurrentMinFees());
75
+ this.currentMinFees = this.currentMinFees
76
+ .catch(() => undefined)
77
+ .then(() =>
78
+ this.computeCurrentMinFees().catch(err => {
79
+ if (this.currentL1BlockNumber === blockNumber) {
80
+ this.currentL1BlockNumber = undefined;
81
+ }
82
+ throw err;
83
+ }),
84
+ );
68
85
  }
69
86
  return this.currentMinFees;
70
87
  }
@@ -0,0 +1,85 @@
1
+ import { RollupContract } from '@aztec/ethereum/contracts';
2
+ import type { L1ContractAddresses } from '@aztec/ethereum/l1-contract-addresses';
3
+ import type { ExtendedViemWalletClient } from '@aztec/ethereum/types';
4
+ import { Fr } from '@aztec/foundation/curves/bn254';
5
+ import { tryJsonStringify } from '@aztec/foundation/json-rpc';
6
+ import { createLogger } from '@aztec/foundation/log';
7
+ import { InboxAbi } from '@aztec/l1-artifacts';
8
+ import type { AztecAddress } from '@aztec/stdlib/aztec-address';
9
+
10
+ import { decodeEventLog, getContract } from 'viem';
11
+
12
+ /**
13
+ * Sends an L1-to-L2 message via the Inbox contract and returns its hash and global leaf index.
14
+ * Test helper copied from the end-to-end fixtures so this integration test can live in sequencer-client.
15
+ */
16
+ export async function sendL1ToL2Message(
17
+ message: { recipient: AztecAddress; content: Fr; secretHash: Fr },
18
+ ctx: {
19
+ l1Client: ExtendedViemWalletClient;
20
+ l1ContractAddresses: Pick<L1ContractAddresses, 'inboxAddress' | 'rollupAddress'>;
21
+ },
22
+ ) {
23
+ const logger = createLogger('sequencer-client:l1_to_l2_messaging');
24
+ const inbox = getContract({
25
+ address: ctx.l1ContractAddresses.inboxAddress.toString(),
26
+ abi: InboxAbi,
27
+ client: ctx.l1Client,
28
+ });
29
+
30
+ const { recipient, content, secretHash } = message;
31
+
32
+ const version = await new RollupContract(ctx.l1Client, ctx.l1ContractAddresses.rollupAddress.toString()).getVersion();
33
+
34
+ // We inject the message to Inbox
35
+ const txHash = await inbox.write.sendL2Message(
36
+ [{ actor: recipient.toString(), version: BigInt(version) }, content.toString(), secretHash.toString()],
37
+ {
38
+ gas: 1_000_000n,
39
+ },
40
+ );
41
+ logger.info(`L1 to L2 message sent in tx ${txHash}`);
42
+
43
+ // We check that the message was correctly injected by checking the emitted event
44
+ const txReceipt = await ctx.l1Client.waitForTransactionReceipt({ hash: txHash });
45
+
46
+ if (txReceipt.status !== 'success') {
47
+ throw new Error(`L1 to L2 message failed to be sent in tx ${txHash}. Status: ${txReceipt.status}`);
48
+ }
49
+
50
+ logger.info(`L1 to L2 message receipt retrieved for tx ${txReceipt.transactionHash}`, txReceipt);
51
+
52
+ if (txReceipt.transactionHash !== txHash) {
53
+ throw new Error(`Receipt transaction hash mismatch: ${txReceipt.transactionHash} !== ${txHash}`);
54
+ }
55
+
56
+ // Filter for MessageSent events from the Inbox contract by trying to decode each log
57
+ const messageSentLogs = txReceipt.logs
58
+ .filter(log => log.address.toLowerCase() === ctx.l1ContractAddresses.inboxAddress.toString().toLowerCase())
59
+ .map(log => {
60
+ try {
61
+ const decoded = decodeEventLog({
62
+ abi: InboxAbi,
63
+ data: log.data,
64
+ topics: log.topics,
65
+ });
66
+ return { log, decoded };
67
+ } catch {
68
+ return null; // Not a decodable event from this ABI
69
+ }
70
+ })
71
+ .filter((item): item is { log: any; decoded: any } => item !== null && item.decoded.eventName === 'MessageSent');
72
+
73
+ if (messageSentLogs.length !== 1) {
74
+ throw new Error(
75
+ `Wrong number of MessageSent logs found in ${txHash} transaction (got ${messageSentLogs.length} expected 1)\n${tryJsonStringify(messageSentLogs.map(item => item.log))}`,
76
+ );
77
+ }
78
+
79
+ // We already have the decoded event
80
+ const topics = messageSentLogs[0].decoded;
81
+ const receivedMsgHash = topics.args.hash;
82
+ const receivedGlobalLeafIndex = topics.args.index;
83
+
84
+ return { msgHash: Fr.fromHexString(receivedMsgHash), globalLeafIndex: new Fr(receivedGlobalLeafIndex), txReceipt };
85
+ }
@@ -469,7 +469,9 @@ export class SequencerPublisher {
469
469
 
470
470
  if (bundleResult.kind === 'aborted') {
471
471
  this.logDroppedInSim(bundleResult.droppedRequests);
472
- void this.backupDroppedInSim(bundleResult.droppedRequests);
472
+ void this.backupDroppedInSim(bundleResult.droppedRequests).catch(err =>
473
+ this.log.error(`Failed to backup requests dropped in simulation`, err),
474
+ );
473
475
  return undefined;
474
476
  }
475
477
 
@@ -912,7 +914,11 @@ export class SequencerPublisher {
912
914
  const logData = { ...checkpoint, reason };
913
915
  this.log.debug(`Building invalidate checkpoint ${checkpoint.checkpointNumber} request`, logData);
914
916
 
915
- const attestationsAndSigners = CommitteeAttestationsAndSigners.packAttestations(validationResult.attestations);
917
+ // Use the exact packed tuple posted to L1 verbatim. A repack via `packAttestations` is not a
918
+ // byte-faithful inverse of `fromPacked` (a canonicalized yParity byte or an all-zero signature slot
919
+ // round-trips differently), so it would diverge from the stored `attestationsHash` and revert the
920
+ // invalidation.
921
+ const attestationsAndSigners = validationResult.verbatimAttestations;
916
922
 
917
923
  if (reason === 'invalid-attestation') {
918
924
  return this.rollupContract.buildInvalidateBadAttestationRequest(
@@ -0,0 +1,78 @@
1
+ import { AztecAddress } from '@aztec/aztec.js/addresses';
2
+ import { Fr } from '@aztec/aztec.js/fields';
3
+ import { BatchedBlob, Blob, getEthBlobEvaluationInputs, getPrefixedEthBlobCommitments } from '@aztec/blob-lib';
4
+ import { EthAddress } from '@aztec/foundation/eth-address';
5
+ import { L2Block } from '@aztec/stdlib/block';
6
+ import { CheckpointHeader } from '@aztec/stdlib/rollup';
7
+
8
+ import { writeFile } from 'fs/promises';
9
+
10
+ const AZTEC_GENERATE_TEST_DATA = !!process.env.AZTEC_GENERATE_TEST_DATA;
11
+
12
+ /**
13
+ * Creates a json object that can be used to test the solidity contract.
14
+ * The json object must be put into
15
+ */
16
+ export async function writeJson(
17
+ fileName: string,
18
+ checkpointHeader: CheckpointHeader,
19
+ block: L2Block,
20
+ l1ToL2Content: Fr[],
21
+ blobs: Blob[],
22
+ batchedBlob: BatchedBlob,
23
+ recipientAddress: AztecAddress,
24
+ deployerAddress: `0x${string}`,
25
+ ): Promise<void> {
26
+ if (!AZTEC_GENERATE_TEST_DATA) {
27
+ return;
28
+ }
29
+ // Path relative to the package.json in the end-to-end folder
30
+ const path = `../../l1-contracts/test/fixtures/${fileName}.json`;
31
+
32
+ const asHex = (value: Fr | Buffer | EthAddress | AztecAddress, size = 64) => {
33
+ const buffer = Buffer.isBuffer(value) ? value : value.toBuffer();
34
+ return `0x${buffer.toString('hex').padStart(size, '0')}`;
35
+ };
36
+
37
+ const jsonObject = {
38
+ populate: {
39
+ l1ToL2Content: l1ToL2Content.map(value => asHex(value)),
40
+ recipient: asHex(recipientAddress.toField()),
41
+ sender: deployerAddress,
42
+ },
43
+ messages: {
44
+ l2ToL1Messages: block.body.txEffects.flatMap(txEffect => txEffect.l2ToL1Msgs).map(value => asHex(value)),
45
+ },
46
+ checkpoint: {
47
+ // The json formatting in forge is a bit brittle, so we convert Fr to a number in the few values below.
48
+ // This should not be a problem for testing as long as the values are not larger than u32.
49
+ archive: asHex(block.archive.root),
50
+ blobCommitments: getPrefixedEthBlobCommitments(blobs),
51
+ batchedBlobInputs: getEthBlobEvaluationInputs(batchedBlob),
52
+ checkpointNumber: block.number,
53
+ body: `0x${block.body.toBuffer().toString('hex')}`,
54
+ header: {
55
+ lastArchiveRoot: asHex(checkpointHeader.lastArchiveRoot),
56
+ blockHeadersHash: asHex(checkpointHeader.blockHeadersHash),
57
+ blobsHash: asHex(checkpointHeader.blobsHash),
58
+ inHash: asHex(checkpointHeader.inHash),
59
+ outHash: asHex(checkpointHeader.epochOutHash),
60
+ slotNumber: Number(checkpointHeader.slotNumber),
61
+ timestamp: Number(checkpointHeader.timestamp),
62
+ coinbase: asHex(checkpointHeader.coinbase, 40),
63
+ feeRecipient: asHex(checkpointHeader.feeRecipient),
64
+ gasFees: {
65
+ feePerDaGas: Number(checkpointHeader.gasFees.feePerDaGas),
66
+ feePerL2Gas: Number(checkpointHeader.gasFees.feePerL2Gas),
67
+ },
68
+ totalManaUsed: checkpointHeader.totalManaUsed.toNumber(),
69
+ accumulatedFees: checkpointHeader.accumulatedFees.toNumber(),
70
+ },
71
+ headerHash: asHex(checkpointHeader.hash()),
72
+ numTxs: block.body.txEffects.length,
73
+ },
74
+ };
75
+
76
+ const output = JSON.stringify(jsonObject, null, 2);
77
+ await writeFile(path, output, 'utf8');
78
+ }