@aztec/sequencer-client 0.0.1-commit.ec5f612 → 0.0.1-commit.ec7ac5448

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 (63) hide show
  1. package/dest/client/sequencer-client.d.ts +6 -1
  2. package/dest/client/sequencer-client.d.ts.map +1 -1
  3. package/dest/client/sequencer-client.js +49 -27
  4. package/dest/config.d.ts +25 -5
  5. package/dest/config.d.ts.map +1 -1
  6. package/dest/config.js +31 -22
  7. package/dest/global_variable_builder/global_builder.d.ts +15 -8
  8. package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
  9. package/dest/global_variable_builder/global_builder.js +26 -26
  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 +13 -1
  13. package/dest/publisher/config.d.ts.map +1 -1
  14. package/dest/publisher/config.js +17 -2
  15. package/dest/publisher/sequencer-publisher-factory.d.ts +3 -5
  16. package/dest/publisher/sequencer-publisher-factory.d.ts.map +1 -1
  17. package/dest/publisher/sequencer-publisher-factory.js +16 -3
  18. package/dest/publisher/sequencer-publisher.d.ts +55 -43
  19. package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
  20. package/dest/publisher/sequencer-publisher.js +180 -116
  21. package/dest/sequencer/chain_state_overrides.d.ts +25 -0
  22. package/dest/sequencer/chain_state_overrides.d.ts.map +1 -0
  23. package/dest/sequencer/chain_state_overrides.js +39 -0
  24. package/dest/sequencer/checkpoint_proposal_job.d.ts +24 -9
  25. package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -1
  26. package/dest/sequencer/checkpoint_proposal_job.js +331 -211
  27. package/dest/sequencer/checkpoint_voter.d.ts +1 -2
  28. package/dest/sequencer/checkpoint_voter.d.ts.map +1 -1
  29. package/dest/sequencer/checkpoint_voter.js +2 -5
  30. package/dest/sequencer/events.d.ts +2 -1
  31. package/dest/sequencer/events.d.ts.map +1 -1
  32. package/dest/sequencer/metrics.d.ts +9 -2
  33. package/dest/sequencer/metrics.d.ts.map +1 -1
  34. package/dest/sequencer/metrics.js +23 -1
  35. package/dest/sequencer/sequencer.d.ts +28 -11
  36. package/dest/sequencer/sequencer.d.ts.map +1 -1
  37. package/dest/sequencer/sequencer.js +134 -74
  38. package/dest/sequencer/timetable.d.ts +17 -3
  39. package/dest/sequencer/timetable.d.ts.map +1 -1
  40. package/dest/sequencer/timetable.js +51 -43
  41. package/dest/sequencer/types.d.ts +2 -2
  42. package/dest/sequencer/types.d.ts.map +1 -1
  43. package/dest/test/mock_checkpoint_builder.d.ts +7 -9
  44. package/dest/test/mock_checkpoint_builder.d.ts.map +1 -1
  45. package/dest/test/mock_checkpoint_builder.js +39 -30
  46. package/package.json +27 -28
  47. package/src/client/sequencer-client.ts +61 -28
  48. package/src/config.ts +39 -24
  49. package/src/global_variable_builder/global_builder.ts +30 -27
  50. package/src/global_variable_builder/index.ts +1 -1
  51. package/src/publisher/config.ts +32 -0
  52. package/src/publisher/sequencer-publisher-factory.ts +18 -6
  53. package/src/publisher/sequencer-publisher.ts +263 -167
  54. package/src/sequencer/README.md +83 -13
  55. package/src/sequencer/chain_state_overrides.ts +87 -0
  56. package/src/sequencer/checkpoint_proposal_job.ts +429 -239
  57. package/src/sequencer/checkpoint_voter.ts +1 -12
  58. package/src/sequencer/events.ts +1 -1
  59. package/src/sequencer/metrics.ts +29 -1
  60. package/src/sequencer/sequencer.ts +194 -81
  61. package/src/sequencer/timetable.ts +64 -52
  62. package/src/sequencer/types.ts +1 -1
  63. package/src/test/mock_checkpoint_builder.ts +51 -48
@@ -372,23 +372,26 @@ function _apply_decs_2203_r(targetClass, memberDecs, classDecs, parentClass) {
372
372
  }
373
373
  var _dec, _dec1, _dec2, _initProto;
374
374
  import { Blob, getBlobsPerL1Block, getPrefixedEthBlobCommitments } from '@aztec/blob-lib';
375
- import { FeeAssetPriceOracle, MULTI_CALL_3_ADDRESS, Multicall3, RollupContract } from '@aztec/ethereum/contracts';
375
+ import { FeeAssetPriceOracle, MULTI_CALL_3_ADDRESS, Multicall3, SimulationOverridesBuilder, buildSimulationOverridesStateOverride } from '@aztec/ethereum/contracts';
376
376
  import { L1FeeAnalyzer } from '@aztec/ethereum/l1-fee-analysis';
377
377
  import { MAX_L1_TX_LIMIT, WEI_CONST } from '@aztec/ethereum/l1-tx-utils';
378
378
  import { FormattedViemError, formatViemError, mergeAbis, tryExtractEvent } from '@aztec/ethereum/utils';
379
379
  import { sumBigint } from '@aztec/foundation/bigint';
380
- import { toHex as toPaddedHex } from '@aztec/foundation/bigint-buffer';
381
380
  import { CheckpointNumber, SlotNumber } from '@aztec/foundation/branded-types';
381
+ import { trimmedBytesLength } from '@aztec/foundation/buffer';
382
382
  import { pick } from '@aztec/foundation/collection';
383
+ import { TimeoutError } from '@aztec/foundation/error';
383
384
  import { EthAddress } from '@aztec/foundation/eth-address';
384
385
  import { Signature } from '@aztec/foundation/eth-signature';
385
386
  import { createLogger } from '@aztec/foundation/log';
386
387
  import { makeBackoff, retry } from '@aztec/foundation/retry';
388
+ import { InterruptibleSleep } from '@aztec/foundation/sleep';
387
389
  import { bufferToHex } from '@aztec/foundation/string';
388
390
  import { Timer } from '@aztec/foundation/timer';
389
391
  import { EmpireBaseAbi, ErrorsAbi, RollupAbi } from '@aztec/l1-artifacts';
390
392
  import { encodeSlashConsensusVotes } from '@aztec/slasher';
391
393
  import { CommitteeAttestationsAndSigners } from '@aztec/stdlib/block';
394
+ import { getLastL1SlotTimestampForL2Slot, getNextL1SlotTimestamp } from '@aztec/stdlib/epoch-helpers';
392
395
  import { getTelemetryClient, trackSpan } from '@aztec/telemetry-client';
393
396
  import { encodeFunctionData, keccak256, multicall3Abi, toHex } from 'viem';
394
397
  import { createL1TxFailedStore } from './l1_tx_failed_store/index.js';
@@ -398,9 +401,6 @@ export const Actions = [
398
401
  'invalidate-by-insufficient-attestations',
399
402
  'propose',
400
403
  'governance-signal',
401
- 'empire-slashing-signal',
402
- 'create-empire-payload',
403
- 'execute-empire-payload',
404
404
  'vote-offenses',
405
405
  'execute-slash'
406
406
  ];
@@ -439,10 +439,14 @@ export class SequencerPublisher {
439
439
  payloadProposedCache;
440
440
  log;
441
441
  ethereumSlotDuration;
442
+ aztecSlotDuration;
443
+ /** Date provider for wall-clock time. */ dateProvider;
442
444
  blobClient;
443
445
  /** Address to use for simulations in fisherman mode (actual proposer's address) */ proposerAddressForSimulation;
446
+ /** Optional callback to obtain a replacement publisher when the current one fails to send. */ getNextPublisher;
444
447
  /** L1 fee analyzer for fisherman mode */ l1FeeAnalyzer;
445
448
  /** Fee asset price oracle for computing price modifiers from Uniswap V4 */ feeAssetPriceOracle;
449
+ /** Interruptible sleep used by sendRequestsAt to wait until a target timestamp. */ interruptibleSleep;
446
450
  // A CALL to a cold address is 2700 gas
447
451
  static MULTICALL_OVERHEAD_GAS_GUESS = 5000n;
448
452
  // Gas report for VotingWithSigTest shows a max gas of 100k, but we've seen it cost 700k+ in testnet
@@ -451,7 +455,6 @@ export class SequencerPublisher {
451
455
  rollupContract;
452
456
  govProposerContract;
453
457
  slashingProposerContract;
454
- slashFactoryContract;
455
458
  tracer;
456
459
  requests;
457
460
  constructor(config, deps){
@@ -462,16 +465,21 @@ export class SequencerPublisher {
462
465
  this.lastActions = {};
463
466
  this.isPayloadEmptyCache = new Map();
464
467
  this.payloadProposedCache = new Set();
468
+ this.interruptibleSleep = new InterruptibleSleep();
465
469
  this.requests = [];
466
470
  this.log = deps.log ?? createLogger('sequencer:publisher');
467
471
  this.ethereumSlotDuration = BigInt(config.ethereumSlotDuration);
472
+ this.aztecSlotDuration = BigInt(config.aztecSlotDuration);
473
+ this.dateProvider = deps.dateProvider;
468
474
  this.epochCache = deps.epochCache;
469
475
  this.lastActions = deps.lastActions;
470
476
  this.blobClient = deps.blobClient;
477
+ this.dateProvider = deps.dateProvider;
471
478
  const telemetry = deps.telemetry ?? getTelemetryClient();
472
479
  this.metrics = deps.metrics ?? new SequencerPublisherMetrics(telemetry, 'SequencerPublisher');
473
480
  this.tracer = telemetry.getTracer('SequencerPublisher');
474
481
  this.l1TxUtils = deps.l1TxUtils;
482
+ this.getNextPublisher = deps.getNextPublisher;
475
483
  this.rollupContract = deps.rollupContract;
476
484
  this.govProposerContract = deps.governanceProposerContract;
477
485
  this.slashingProposerContract = deps.slashingProposerContract;
@@ -480,7 +488,6 @@ export class SequencerPublisher {
480
488
  const newSlashingProposer = await this.rollupContract.getSlashingProposer();
481
489
  this.slashingProposerContract = newSlashingProposer;
482
490
  });
483
- this.slashFactoryContract = deps.slashFactoryContract;
484
491
  // Initialize L1 fee analyzer for fisherman mode
485
492
  if (config.fishermanMode) {
486
493
  this.l1FeeAnalyzer = new L1FeeAnalyzer(this.l1TxUtils.client, deps.dateProvider, createLogger('sequencer:publisher:fee-analyzer'));
@@ -533,7 +540,7 @@ export class SequencerPublisher {
533
540
  this.requests.push(request);
534
541
  }
535
542
  getCurrentL2Slot() {
536
- return this.epochCache.getEpochAndSlotNow().slot;
543
+ return this.epochCache.getSlotNow();
537
544
  }
538
545
  /**
539
546
  * Clears all pending requests without sending them.
@@ -622,8 +629,8 @@ export class SequencerPublisher {
622
629
  // @note - we can only have one blob config per bundle
623
630
  // find requests with gas and blob configs
624
631
  // See https://github.com/AztecProtocol/aztec-packages/issues/11513
625
- const gasConfigs = requestsToProcess.filter((request)=>request.gasConfig).map((request)=>request.gasConfig);
626
- const blobConfigs = requestsToProcess.filter((request)=>request.blobConfig).map((request)=>request.blobConfig);
632
+ const gasConfigs = validRequests.filter((request)=>request.gasConfig).map((request)=>request.gasConfig);
633
+ const blobConfigs = validRequests.filter((request)=>request.blobConfig).map((request)=>request.blobConfig);
627
634
  if (blobConfigs.length > 1) {
628
635
  throw new Error('Multiple blob configs found');
629
636
  }
@@ -664,16 +671,19 @@ export class SequencerPublisher {
664
671
  ]
665
672
  });
666
673
  const blobDataHex = blobConfig?.blobs?.map((b)=>toHex(b));
667
- this.log.debug('Forwarding transactions', {
668
- validRequests: validRequests.map((request)=>request.action),
669
- txConfig
670
- });
671
- const result = await Multicall3.forward(validRequests.map((request)=>request.request), this.l1TxUtils, txConfig, blobConfig, this.rollupContract.address, this.log);
672
674
  const txContext = {
673
675
  multicallData,
674
676
  blobData: blobDataHex,
675
677
  l1BlockNumber
676
678
  };
679
+ this.log.debug('Forwarding transactions', {
680
+ validRequests: validRequests.map((request)=>request.action),
681
+ txConfig
682
+ });
683
+ const result = await this.forwardWithPublisherRotation(validRequests, txConfig, blobConfig);
684
+ if (result === undefined) {
685
+ return undefined;
686
+ }
677
687
  const { successfulActions = [], failedActions = [] } = this.callbackBundledTransactions(validRequests, result, txContext);
678
688
  return {
679
689
  result,
@@ -694,6 +704,75 @@ export class SequencerPublisher {
694
704
  }
695
705
  }
696
706
  }
707
+ /**
708
+ * Forwards transactions via Multicall3, rotating to the next available publisher if a send
709
+ * failure occurs (i.e. the tx never reached the chain).
710
+ * On-chain reverts and simulation errors are returned as-is without rotation.
711
+ */ async forwardWithPublisherRotation(validRequests, txConfig, blobConfig) {
712
+ const triedAddresses = [];
713
+ let currentPublisher = this.l1TxUtils;
714
+ while(true){
715
+ triedAddresses.push(currentPublisher.getSenderAddress());
716
+ try {
717
+ const result = await Multicall3.forward(validRequests.map((r)=>r.request), currentPublisher, txConfig, blobConfig, this.rollupContract.address, this.log);
718
+ this.l1TxUtils = currentPublisher;
719
+ return result;
720
+ } catch (err) {
721
+ if (err instanceof TimeoutError) {
722
+ throw err;
723
+ }
724
+ const viemError = formatViemError(err);
725
+ if (!this.getNextPublisher) {
726
+ this.log.error('Failed to publish bundled transactions', viemError);
727
+ return undefined;
728
+ }
729
+ this.log.warn(`Publisher ${currentPublisher.getSenderAddress()} failed to send, rotating to next publisher`, viemError);
730
+ const nextPublisher = await this.getNextPublisher([
731
+ ...triedAddresses
732
+ ]);
733
+ if (!nextPublisher) {
734
+ this.log.error('All available publishers exhausted, failed to publish bundled transactions');
735
+ return undefined;
736
+ }
737
+ currentPublisher = nextPublisher;
738
+ }
739
+ }
740
+ }
741
+ /*
742
+ * Schedules sending all enqueued requests at (or after) the given timestamp.
743
+ * Uses InterruptibleSleep so it can be cancelled via interrupt().
744
+ * Returns the promise for the L1 response (caller should NOT await this in the work loop).
745
+ */ async sendRequestsAt(submitAfter) {
746
+ const ms = submitAfter.getTime() - this.dateProvider.now();
747
+ if (ms > 0) {
748
+ this.log.debug(`Sleeping ${ms}ms before sending requests`, {
749
+ submitAfter
750
+ });
751
+ await this.interruptibleSleep.sleep(ms);
752
+ }
753
+ if (this.interrupted) {
754
+ return undefined;
755
+ }
756
+ // Re-validate enqueued requests after the sleep (state may have changed, e.g. prune or L1 reorg)
757
+ const validRequests = [];
758
+ for (const request of this.requests){
759
+ if (!request.preCheck) {
760
+ validRequests.push(request);
761
+ continue;
762
+ }
763
+ try {
764
+ await request.preCheck();
765
+ validRequests.push(request);
766
+ } catch (err) {
767
+ this.log.warn(`Pre-send validation failed for ${request.action}, discarding request`, err);
768
+ }
769
+ }
770
+ this.requests = validRequests;
771
+ if (this.requests.length === 0) {
772
+ return undefined;
773
+ }
774
+ return this.sendRequests();
775
+ }
697
776
  callbackBundledTransactions(requests, result, txContext) {
698
777
  const actionsListStr = requests.map((r)=>r.action).join(', ');
699
778
  if (result instanceof FormattedViemError) {
@@ -727,7 +806,16 @@ export class SequencerPublisher {
727
806
  } else {
728
807
  this.log.verbose(`Published bundled transactions (${actionsListStr})`, {
729
808
  result,
730
- requests
809
+ requests: requests.map((r)=>({
810
+ ...r,
811
+ // Avoid logging large blob data
812
+ blobConfig: r.blobConfig ? {
813
+ ...r.blobConfig,
814
+ blobs: r.blobConfig.blobs.map((b)=>({
815
+ size: trimmedBytesLength(b)
816
+ }))
817
+ } : undefined
818
+ }))
731
819
  });
732
820
  const successfulActions = [];
733
821
  const failedActions = [];
@@ -776,19 +864,20 @@ export class SequencerPublisher {
776
864
  }
777
865
  }
778
866
  /**
779
- * @notice Will call `canProposeAtNextEthBlock` to make sure that it is possible to propose
867
+ * @notice Will call `canProposeAt` to make sure that it is possible to propose
780
868
  * @param tipArchive - The archive to check
781
869
  * @returns The slot and block number if it is possible to propose, undefined otherwise
782
- */ canProposeAtNextEthBlock(tipArchive, msgSender, opts = {}) {
870
+ */ async canProposeAt(tipArchive, msgSender, simulationOverridesPlan) {
783
871
  // TODO: #14291 - should loop through multiple keys to check if any of them can propose
784
872
  const ignoredErrors = [
785
873
  'SlotAlreadyInChain',
786
874
  'InvalidProposer',
787
875
  'InvalidArchive'
788
876
  ];
789
- return this.rollupContract.canProposeAtNextEthBlock(tipArchive.toBuffer(), msgSender.toString(), Number(this.ethereumSlotDuration), {
790
- forcePendingCheckpointNumber: opts.forcePendingCheckpointNumber
791
- }).catch((err)=>{
877
+ const pipelined = this.epochCache.isProposerPipeliningEnabled();
878
+ const slotOffset = pipelined ? this.aztecSlotDuration : 0n;
879
+ const nextL1SlotTs = this.getNextL1SlotTimestamp() + slotOffset;
880
+ return this.rollupContract.canProposeAt(tipArchive.toBuffer(), msgSender.toString(), nextL1SlotTs, await buildSimulationOverridesStateOverride(this.rollupContract, simulationOverridesPlan)).catch((err)=>{
792
881
  if (err instanceof FormattedViemError && ignoredErrors.find((e)=>err.message.includes(e))) {
793
882
  this.log.warn(`Failed canProposeAtTime check with ${ignoredErrors.find((e)=>err.message.includes(e))}`, {
794
883
  error: err.message
@@ -804,7 +893,7 @@ export class SequencerPublisher {
804
893
  * @dev This is a convenience function that can be used by the sequencer to validate a "partial" header.
805
894
  * It will throw if the block header is invalid.
806
895
  * @param header - The block header to validate
807
- */ async validateBlockHeader(header, opts) {
896
+ */ async validateBlockHeader(header, simulationOverridesPlan) {
808
897
  const flags = {
809
898
  ignoreDA: true,
810
899
  ignoreSignatures: true
@@ -818,8 +907,8 @@ export class SequencerPublisher {
818
907
  header.blobsHash.toString(),
819
908
  flags
820
909
  ];
821
- const ts = BigInt((await this.l1TxUtils.getBlock()).timestamp + this.ethereumSlotDuration);
822
- const stateOverrides = await this.rollupContract.makePendingCheckpointNumberOverride(opts?.forcePendingCheckpointNumber);
910
+ const ts = this.getSimulationTimestamp(header.slotNumber);
911
+ const stateOverrides = await buildSimulationOverridesStateOverride(this.rollupContract, simulationOverridesPlan);
823
912
  let balance = 0n;
824
913
  if (this.config.fishermanMode) {
825
914
  // In fisherman mode, we can't know where the proposer is publishing from
@@ -841,7 +930,7 @@ export class SequencerPublisher {
841
930
  }),
842
931
  from: MULTI_CALL_3_ADDRESS
843
932
  }, {
844
- time: ts + 1n
933
+ time: ts
845
934
  }, stateOverrides);
846
935
  this.log.debug(`Simulated validateHeader`);
847
936
  }
@@ -887,6 +976,7 @@ export class SequencerPublisher {
887
976
  gasUsed,
888
977
  checkpointNumber,
889
978
  forcePendingCheckpointNumber: CheckpointNumber(checkpointNumber - 1),
979
+ lastArchive: validationResult.checkpoint.lastArchive,
890
980
  reason
891
981
  };
892
982
  } catch (err) {
@@ -899,8 +989,8 @@ export class SequencerPublisher {
899
989
  request,
900
990
  error: viemError.message
901
991
  });
902
- const latestPendingCheckpointNumber = await this.rollupContract.getCheckpointNumber();
903
- if (latestPendingCheckpointNumber < checkpointNumber) {
992
+ const latestProposedCheckpointNumber = await this.rollupContract.getCheckpointNumber();
993
+ if (latestProposedCheckpointNumber < checkpointNumber) {
904
994
  this.log.verbose(`Checkpoint ${checkpointNumber} has already been invalidated`, {
905
995
  ...logData
906
996
  });
@@ -960,8 +1050,7 @@ export class SequencerPublisher {
960
1050
  throw new Error(`Unknown reason for invalidation`);
961
1051
  }
962
1052
  }
963
- /** Simulates `propose` to make sure that the checkpoint is valid for submission */ async validateCheckpointForSubmission(checkpoint, attestationsAndSigners, attestationsAndSignersSignature, options) {
964
- const ts = BigInt((await this.l1TxUtils.getBlock()).timestamp + this.ethereumSlotDuration);
1053
+ /** Simulates `propose` to make sure that the checkpoint is valid for submission */ async validateCheckpointForSubmission(checkpoint, attestationsAndSigners, attestationsAndSignersSignature, simulationOverridesPlan) {
965
1054
  const blobFields = checkpoint.toBlobFields();
966
1055
  const blobs = await getBlobsPerL1Block(blobFields);
967
1056
  const blobInput = getPrefixedEthBlobCommitments(blobs);
@@ -978,10 +1067,9 @@ export class SequencerPublisher {
978
1067
  attestationsAndSignersSignature.toViemSignature(),
979
1068
  blobInput
980
1069
  ];
981
- await this.simulateProposeTx(args, ts, options);
982
- return ts;
1070
+ await this.simulateProposeTx(args, simulationOverridesPlan);
983
1071
  }
984
- async enqueueCastSignalHelper(slotNumber, timestamp, signalType, payload, base, signerAddress, signer) {
1072
+ async enqueueCastSignalHelper(slotNumber, signalType, payload, base, signerAddress, signer) {
985
1073
  if (this.lastActions[signalType] && this.lastActions[signalType] === slotNumber) {
986
1074
  this.log.debug(`Skipping duplicate vote cast signal ${signalType} for slot ${slotNumber}`);
987
1075
  return false;
@@ -1038,6 +1126,7 @@ export class SequencerPublisher {
1038
1126
  lastValidL2Slot: slotNumber
1039
1127
  });
1040
1128
  const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
1129
+ const timestamp = this.getSimulationTimestamp(slotNumber);
1041
1130
  try {
1042
1131
  await this.l1TxUtils.simulate(request, {
1043
1132
  time: timestamp
@@ -1050,7 +1139,10 @@ export class SequencerPublisher {
1050
1139
  });
1051
1140
  } catch (err) {
1052
1141
  const viemError = formatViemError(err);
1053
- this.log.error(`Failed simulation for ${action} at slot ${slotNumber} (enqueuing the action anyway)`, viemError);
1142
+ this.log.error(`Failed simulation for ${action} at slot ${slotNumber} (enqueuing the action anyway)`, viemError, {
1143
+ simulationTimestamp: timestamp,
1144
+ l1BlockNumber
1145
+ });
1054
1146
  this.backupFailedTx({
1055
1147
  id: keccak256(request.data),
1056
1148
  failureType: 'simulation',
@@ -1115,55 +1207,17 @@ export class SequencerPublisher {
1115
1207
  /**
1116
1208
  * Enqueues a governance castSignal transaction to cast a signal for a given slot number.
1117
1209
  * @param slotNumber - The slot number to cast a signal for.
1118
- * @param timestamp - The timestamp of the slot to cast a signal for.
1119
1210
  * @returns True if the signal was successfully enqueued, false otherwise.
1120
- */ enqueueGovernanceCastSignal(governancePayload, slotNumber, timestamp, signerAddress, signer) {
1121
- return this.enqueueCastSignalHelper(slotNumber, timestamp, 'governance-signal', governancePayload, this.govProposerContract, signerAddress, signer);
1211
+ */ enqueueGovernanceCastSignal(governancePayload, slotNumber, signerAddress, signer) {
1212
+ return this.enqueueCastSignalHelper(slotNumber, 'governance-signal', governancePayload, this.govProposerContract, signerAddress, signer);
1122
1213
  }
1123
- /** Enqueues all slashing actions as returned by the slasher client. */ async enqueueSlashingActions(actions, slotNumber, timestamp, signerAddress, signer) {
1214
+ /** Enqueues all slashing actions as returned by the slasher client. */ async enqueueSlashingActions(actions, slotNumber, signerAddress, signer) {
1124
1215
  if (actions.length === 0) {
1125
1216
  this.log.debug(`No slashing actions to enqueue for slot ${slotNumber}`);
1126
1217
  return false;
1127
1218
  }
1128
1219
  for (const action of actions){
1129
1220
  switch(action.type){
1130
- case 'vote-empire-payload':
1131
- {
1132
- if (this.slashingProposerContract?.type !== 'empire') {
1133
- this.log.error('Cannot vote for empire payload on non-empire slashing contract');
1134
- break;
1135
- }
1136
- this.log.debug(`Enqueuing slashing vote for payload ${action.payload} at slot ${slotNumber}`, {
1137
- signerAddress
1138
- });
1139
- await this.enqueueCastSignalHelper(slotNumber, timestamp, 'empire-slashing-signal', action.payload, this.slashingProposerContract, signerAddress, signer);
1140
- break;
1141
- }
1142
- case 'create-empire-payload':
1143
- {
1144
- this.log.debug(`Enqueuing slashing create payload at slot ${slotNumber}`, {
1145
- slotNumber,
1146
- signerAddress
1147
- });
1148
- const request = this.slashFactoryContract.buildCreatePayloadRequest(action.data);
1149
- await this.simulateAndEnqueueRequest('create-empire-payload', request, (receipt)=>!!this.slashFactoryContract.tryExtractSlashPayloadCreatedEvent(receipt.logs), slotNumber, timestamp);
1150
- break;
1151
- }
1152
- case 'execute-empire-payload':
1153
- {
1154
- this.log.debug(`Enqueuing slashing execute payload at slot ${slotNumber}`, {
1155
- slotNumber,
1156
- signerAddress
1157
- });
1158
- if (this.slashingProposerContract?.type !== 'empire') {
1159
- this.log.error('Cannot execute slashing payload on non-empire slashing contract');
1160
- return false;
1161
- }
1162
- const empireSlashingProposer = this.slashingProposerContract;
1163
- const request = empireSlashingProposer.buildExecuteRoundRequest(action.round);
1164
- await this.simulateAndEnqueueRequest('execute-empire-payload', request, (receipt)=>!!empireSlashingProposer.tryExtractPayloadSubmittedEvent(receipt.logs), slotNumber, timestamp);
1165
- break;
1166
- }
1167
1221
  case 'vote-offenses':
1168
1222
  {
1169
1223
  this.log.debug(`Enqueuing slashing vote for ${action.votes.length} votes at slot ${slotNumber}`, {
@@ -1172,14 +1226,13 @@ export class SequencerPublisher {
1172
1226
  votesCount: action.votes.length,
1173
1227
  signerAddress
1174
1228
  });
1175
- if (this.slashingProposerContract?.type !== 'tally') {
1176
- this.log.error('Cannot vote for slashing offenses on non-tally slashing contract');
1229
+ if (!this.slashingProposerContract) {
1230
+ this.log.error('No slashing proposer contract available');
1177
1231
  return false;
1178
1232
  }
1179
- const tallySlashingProposer = this.slashingProposerContract;
1180
1233
  const votes = bufferToHex(encodeSlashConsensusVotes(action.votes));
1181
- const request = await tallySlashingProposer.buildVoteRequestFromSigner(votes, slotNumber, signer);
1182
- await this.simulateAndEnqueueRequest('vote-offenses', request, (receipt)=>!!tallySlashingProposer.tryExtractVoteCastEvent(receipt.logs), slotNumber, timestamp);
1234
+ const request = await this.slashingProposerContract.buildVoteRequestFromSigner(votes, slotNumber, signer);
1235
+ await this.simulateAndEnqueueRequest('vote-offenses', request, (receipt)=>!!this.slashingProposerContract.tryExtractVoteCastEvent(receipt.logs), slotNumber);
1183
1236
  break;
1184
1237
  }
1185
1238
  case 'execute-slash':
@@ -1189,13 +1242,12 @@ export class SequencerPublisher {
1189
1242
  round: action.round,
1190
1243
  signerAddress
1191
1244
  });
1192
- if (this.slashingProposerContract?.type !== 'tally') {
1193
- this.log.error('Cannot execute slashing offenses on non-tally slashing contract');
1245
+ if (!this.slashingProposerContract) {
1246
+ this.log.error('No slashing proposer contract available');
1194
1247
  return false;
1195
1248
  }
1196
- const tallySlashingProposer = this.slashingProposerContract;
1197
- const request = tallySlashingProposer.buildExecuteRoundRequest(action.round, action.committees);
1198
- await this.simulateAndEnqueueRequest('execute-slash', request, (receipt)=>!!tallySlashingProposer.tryExtractRoundExecutedEvent(receipt.logs), slotNumber, timestamp);
1249
+ const executeRequest = this.slashingProposerContract.buildExecuteRoundRequest(action.round, action.committees);
1250
+ await this.simulateAndEnqueueRequest('execute-slash', executeRequest, (receipt)=>!!this.slashingProposerContract.tryExtractRoundExecutedEvent(receipt.logs), slotNumber);
1199
1251
  break;
1200
1252
  }
1201
1253
  default:
@@ -1219,27 +1271,40 @@ export class SequencerPublisher {
1219
1271
  attestationsAndSignersSignature,
1220
1272
  feeAssetPriceModifier: checkpoint.feeAssetPriceModifier
1221
1273
  };
1222
- let ts;
1274
+ const simulationOverridesPlan = SimulationOverridesBuilder.from(opts.simulationOverridesPlan).withoutBlobCheck().build();
1223
1275
  try {
1224
1276
  // @note This will make sure that we are passing the checks for our header ASSUMING that the data is also made available
1225
1277
  // This means that we can avoid the simulation issues in later checks.
1226
1278
  // By simulation issue, I mean the fact that the block.timestamp is equal to the last block, not the next, which
1227
1279
  // make time consistency checks break.
1228
1280
  // TODO(palla): Check whether we're validating twice, once here and once within addProposeTx, since we call simulateProposeTx in both places.
1229
- ts = await this.validateCheckpointForSubmission(checkpoint, attestationsAndSigners, attestationsAndSignersSignature, opts);
1281
+ await this.validateCheckpointForSubmission(checkpoint, attestationsAndSigners, attestationsAndSignersSignature, simulationOverridesPlan);
1230
1282
  } catch (err) {
1231
1283
  this.log.error(`Checkpoint validation failed. ${err instanceof Error ? err.message : 'No error message'}`, err, {
1232
1284
  ...checkpoint.getStats(),
1233
1285
  slotNumber: checkpoint.header.slotNumber,
1234
- forcePendingCheckpointNumber: opts.forcePendingCheckpointNumber
1286
+ simulationOverridesPlan
1235
1287
  });
1236
1288
  throw err;
1237
1289
  }
1290
+ // Build a pre-check callback that re-validates the checkpoint before L1 submission.
1291
+ // During pipelining this catches stale proposals due to prunes or L1 reorgs that occur during the pipeline sleep.
1292
+ let preCheck = undefined;
1293
+ if (this.epochCache.isProposerPipeliningEnabled()) {
1294
+ preCheck = async ()=>{
1295
+ this.log.debug(`Re-validating checkpoint ${checkpoint.number} before L1 submission`);
1296
+ await this.validateCheckpointForSubmission(checkpoint, attestationsAndSigners, attestationsAndSignersSignature, simulationOverridesPlan);
1297
+ };
1298
+ }
1238
1299
  this.log.verbose(`Enqueuing checkpoint propose transaction`, {
1239
1300
  ...checkpoint.toCheckpointInfo(),
1240
- ...opts
1301
+ txTimeoutAt: opts.txTimeoutAt,
1302
+ simulationOverridesPlan
1241
1303
  });
1242
- await this.addProposeTx(checkpoint, proposeTxArgs, opts, ts);
1304
+ await this.addProposeTx(checkpoint, proposeTxArgs, {
1305
+ txTimeoutAt: opts.txTimeoutAt,
1306
+ simulationOverridesPlan
1307
+ }, preCheck);
1243
1308
  }
1244
1309
  enqueueInvalidateCheckpoint(request, opts = {}) {
1245
1310
  if (!request) {
@@ -1280,7 +1345,8 @@ export class SequencerPublisher {
1280
1345
  }
1281
1346
  });
1282
1347
  }
1283
- async simulateAndEnqueueRequest(action, request, checkSuccess, slotNumber, timestamp) {
1348
+ async simulateAndEnqueueRequest(action, request, checkSuccess, slotNumber) {
1349
+ const timestamp = this.getSimulationTimestamp(slotNumber);
1284
1350
  const logData = {
1285
1351
  slotNumber,
1286
1352
  timestamp,
@@ -1302,7 +1368,7 @@ export class SequencerPublisher {
1302
1368
  try {
1303
1369
  ({ gasUsed } = await this.l1TxUtils.simulate(request, {
1304
1370
  time: timestamp
1305
- }, [], simulateAbi)); // TODO(palla/slash): Check the timestamp logic
1371
+ }, [], simulateAbi));
1306
1372
  this.log.verbose(`Simulation for ${action} succeeded`, {
1307
1373
  ...logData,
1308
1374
  request,
@@ -1377,13 +1443,14 @@ export class SequencerPublisher {
1377
1443
  * A call to `restart` is required before you can continue publishing.
1378
1444
  */ interrupt() {
1379
1445
  this.interrupted = true;
1446
+ this.interruptibleSleep.interrupt();
1380
1447
  this.l1TxUtils.interrupt();
1381
1448
  }
1382
1449
  /** Restarts the publisher after calling `interrupt`. */ restart() {
1383
1450
  this.interrupted = false;
1384
1451
  this.l1TxUtils.restart();
1385
1452
  }
1386
- async prepareProposeTx(encodedData, timestamp, options) {
1453
+ async prepareProposeTx(encodedData, simulationOverridesPlan) {
1387
1454
  const kzg = Blob.getViemKzgInstance();
1388
1455
  const blobInput = getPrefixedEthBlobCommitments(encodedData.blobs);
1389
1456
  this.log.debug('Validating blob input', {
@@ -1460,7 +1527,7 @@ export class SequencerPublisher {
1460
1527
  encodedData.attestationsAndSignersSignature.toViemSignature(),
1461
1528
  blobInput
1462
1529
  ];
1463
- const { rollupData, simulationResult } = await this.simulateProposeTx(args, timestamp, options);
1530
+ const { rollupData, simulationResult } = await this.simulateProposeTx(args, simulationOverridesPlan);
1464
1531
  return {
1465
1532
  args,
1466
1533
  blobEvaluationGas,
@@ -1471,29 +1538,14 @@ export class SequencerPublisher {
1471
1538
  /**
1472
1539
  * Simulates the propose tx with eth_simulateV1
1473
1540
  * @param args - The propose tx args
1474
- * @param timestamp - The timestamp to simulate proposal at
1475
1541
  * @returns The simulation result
1476
- */ async simulateProposeTx(args, timestamp, options) {
1542
+ */ async simulateProposeTx(args, simulationOverridesPlan) {
1477
1543
  const rollupData = encodeFunctionData({
1478
1544
  abi: RollupAbi,
1479
1545
  functionName: 'propose',
1480
1546
  args
1481
1547
  });
1482
- // override the pending checkpoint number if requested
1483
- const forcePendingCheckpointNumberStateDiff = (options.forcePendingCheckpointNumber !== undefined ? await this.rollupContract.makePendingCheckpointNumberOverride(options.forcePendingCheckpointNumber) : []).flatMap((override)=>override.stateDiff ?? []);
1484
- const stateOverrides = [
1485
- {
1486
- address: this.rollupContract.address,
1487
- // @note we override checkBlob to false since blobs are not part simulate()
1488
- stateDiff: [
1489
- {
1490
- slot: toPaddedHex(RollupContract.checkBlobStorageSlot, true),
1491
- value: toPaddedHex(0n, true)
1492
- },
1493
- ...forcePendingCheckpointNumberStateDiff
1494
- ]
1495
- }
1496
- ];
1548
+ const stateOverrides = await buildSimulationOverridesStateOverride(this.rollupContract, simulationOverridesPlan);
1497
1549
  // In fisherman mode, simulate as the proposer but with sufficient balance
1498
1550
  if (this.proposerAddressForSimulation) {
1499
1551
  stateOverrides.push({
@@ -1502,6 +1554,7 @@ export class SequencerPublisher {
1502
1554
  });
1503
1555
  }
1504
1556
  const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
1557
+ const simTs = this.getSimulationTimestamp(SlotNumber.fromBigInt(args[0].header.slotNumber));
1505
1558
  const simulationResult = await this.l1TxUtils.simulate({
1506
1559
  to: this.rollupContract.address,
1507
1560
  data: rollupData,
@@ -1510,8 +1563,7 @@ export class SequencerPublisher {
1510
1563
  from: this.proposerAddressForSimulation.toString()
1511
1564
  }
1512
1565
  }, {
1513
- // @note we add 1n to the timestamp because geth implementation doesn't like simulation timestamp to be equal to the current block timestamp
1514
- time: timestamp + 1n,
1566
+ time: simTs,
1515
1567
  // @note reth should have a 30m gas limit per block but throws errors that this tx is beyond limit so we increase here
1516
1568
  gasLimit: MAX_L1_TX_LIMIT * 2n
1517
1569
  }, stateOverrides, RollupAbi, {
@@ -1528,7 +1580,9 @@ export class SequencerPublisher {
1528
1580
  logs: []
1529
1581
  };
1530
1582
  }
1531
- this.log.error(`Failed to simulate propose tx`, viemError);
1583
+ this.log.error(`Failed to simulate propose tx`, viemError, {
1584
+ simulationTimestamp: simTs
1585
+ });
1532
1586
  this.backupFailedTx({
1533
1587
  id: keccak256(rollupData),
1534
1588
  failureType: 'simulation',
@@ -1556,11 +1610,11 @@ export class SequencerPublisher {
1556
1610
  simulationResult
1557
1611
  };
1558
1612
  }
1559
- async addProposeTx(checkpoint, encodedData, opts = {}, timestamp) {
1613
+ async addProposeTx(checkpoint, encodedData, opts = {}, preCheck) {
1560
1614
  const slot = checkpoint.header.slotNumber;
1561
1615
  const timer = new Timer();
1562
1616
  const kzg = Blob.getViemKzgInstance();
1563
- const { rollupData, simulationResult, blobEvaluationGas } = await this.prepareProposeTx(encodedData, timestamp, opts);
1617
+ const { rollupData, simulationResult, blobEvaluationGas } = await this.prepareProposeTx(encodedData, opts.simulationOverridesPlan);
1564
1618
  const startBlock = await this.l1TxUtils.getBlockNumber();
1565
1619
  const gasLimit = this.l1TxUtils.bumpGasLimit(BigInt(Math.ceil(Number(simulationResult.gasUsed) * 64 / 63)) + blobEvaluationGas + SequencerPublisher.MULTICALL_OVERHEAD_GAS_GUESS);
1566
1620
  // Send the blobs to the blob client preemptively. This helps in tests where the sequencer mistakingly thinks that the propose
@@ -1576,9 +1630,10 @@ export class SequencerPublisher {
1576
1630
  },
1577
1631
  lastValidL2Slot: checkpoint.header.slotNumber,
1578
1632
  gasConfig: {
1579
- ...opts,
1633
+ txTimeoutAt: opts.txTimeoutAt,
1580
1634
  gasLimit
1581
1635
  },
1636
+ preCheck,
1582
1637
  blobConfig: {
1583
1638
  blobs: encodedData.blobs.map((b)=>b.data),
1584
1639
  kzg
@@ -1625,4 +1680,13 @@ export class SequencerPublisher {
1625
1680
  }
1626
1681
  });
1627
1682
  }
1683
+ /** Returns the timestamp of the last L1 slot within a given L2 slot. Used as the simulation timestamp
1684
+ * for eth_simulateV1 calls, since it's guaranteed to be greater than any L1 block produced during the slot. */ getSimulationTimestamp(slot) {
1685
+ const l1Constants = this.epochCache.getL1Constants();
1686
+ return getLastL1SlotTimestampForL2Slot(slot, l1Constants);
1687
+ }
1688
+ /** Returns the timestamp of the next L1 slot boundary after now. */ getNextL1SlotTimestamp() {
1689
+ const l1Constants = this.epochCache.getL1Constants();
1690
+ return getNextL1SlotTimestamp(this.dateProvider.nowInSeconds(), l1Constants);
1691
+ }
1628
1692
  }
@@ -0,0 +1,25 @@
1
+ import { RollupContract, type SimulationOverridesPlan } from '@aztec/ethereum/contracts';
2
+ import { CheckpointNumber } from '@aztec/foundation/branded-types';
3
+ import type { Fr } from '@aztec/foundation/curves/bn254';
4
+ import type { Logger } from '@aztec/foundation/log';
5
+ import type { ProposedCheckpointData } from '@aztec/stdlib/checkpoint';
6
+ type PipelinedParentSimulationOverridesPlanInput = {
7
+ checkpointNumber: CheckpointNumber;
8
+ proposedCheckpointData?: ProposedCheckpointData;
9
+ rollup: RollupContract;
10
+ log: Logger;
11
+ };
12
+ type SubmissionSimulationOverridesPlanInput = {
13
+ pipelinedParentPlan?: SimulationOverridesPlan;
14
+ invalidateToPendingCheckpointNumber?: CheckpointNumber;
15
+ lastArchiveRoot: Fr;
16
+ pipeliningEnabled: boolean;
17
+ };
18
+ /** Builds the simulated parent checkpoint view used while constructing a pipelined proposal. */
19
+ export declare function buildPipelinedParentSimulationOverridesPlan(input: PipelinedParentSimulationOverridesPlanInput): Promise<SimulationOverridesPlan | undefined>;
20
+ /** Builds the simulated chain view used when validating and enqueueing checkpoint submission. */
21
+ export declare function buildSubmissionSimulationOverridesPlan(input: SubmissionSimulationOverridesPlanInput): SimulationOverridesPlan | undefined;
22
+ /** Derives the pending parent fee header used during pipelined proposal simulation. */
23
+ export declare function computePipelinedParentFeeHeader(input: PipelinedParentSimulationOverridesPlanInput): Promise<import("@aztec/ethereum/contracts").FeeHeader | undefined>;
24
+ export {};
25
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2hhaW5fc3RhdGVfb3ZlcnJpZGVzLmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvc2VxdWVuY2VyL2NoYWluX3N0YXRlX292ZXJyaWRlcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsY0FBYyxFQUE4QixLQUFLLHVCQUF1QixFQUFFLE1BQU0sMkJBQTJCLENBQUM7QUFDckgsT0FBTyxFQUFFLGdCQUFnQixFQUFFLE1BQU0saUNBQWlDLENBQUM7QUFDbkUsT0FBTyxLQUFLLEVBQUUsRUFBRSxFQUFFLE1BQU0sZ0NBQWdDLENBQUM7QUFDekQsT0FBTyxLQUFLLEVBQUUsTUFBTSxFQUFFLE1BQU0sdUJBQXVCLENBQUM7QUFDcEQsT0FBTyxLQUFLLEVBQUUsc0JBQXNCLEVBQUUsTUFBTSwwQkFBMEIsQ0FBQztBQUV2RSxLQUFLLDJDQUEyQyxHQUFHO0lBQ2pELGdCQUFnQixFQUFFLGdCQUFnQixDQUFDO0lBQ25DLHNCQUFzQixDQUFDLEVBQUUsc0JBQXNCLENBQUM7SUFDaEQsTUFBTSxFQUFFLGNBQWMsQ0FBQztJQUN2QixHQUFHLEVBQUUsTUFBTSxDQUFDO0NBQ2IsQ0FBQztBQUVGLEtBQUssc0NBQXNDLEdBQUc7SUFDNUMsbUJBQW1CLENBQUMsRUFBRSx1QkFBdUIsQ0FBQztJQUM5QyxtQ0FBbUMsQ0FBQyxFQUFFLGdCQUFnQixDQUFDO0lBQ3ZELGVBQWUsRUFBRSxFQUFFLENBQUM7SUFDcEIsaUJBQWlCLEVBQUUsT0FBTyxDQUFDO0NBQzVCLENBQUM7QUFFRixnR0FBZ0c7QUFDaEcsd0JBQXNCLDJDQUEyQyxDQUMvRCxLQUFLLEVBQUUsMkNBQTJDLEdBQ2pELE9BQU8sQ0FBQyx1QkFBdUIsR0FBRyxTQUFTLENBQUMsQ0FVOUM7QUFFRCxpR0FBaUc7QUFDakcsd0JBQWdCLHNDQUFzQyxDQUNwRCxLQUFLLEVBQUUsc0NBQXNDLEdBQzVDLHVCQUF1QixHQUFHLFNBQVMsQ0FhckM7QUFFRCx1RkFBdUY7QUFDdkYsd0JBQXNCLCtCQUErQixDQUFDLEtBQUssRUFBRSwyQ0FBMkMsc0VBZ0N2RyJ9
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chain_state_overrides.d.ts","sourceRoot":"","sources":["../../src/sequencer/chain_state_overrides.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAA8B,KAAK,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AACrH,OAAO,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AACnE,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,gCAAgC,CAAC;AACzD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AACpD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAEvE,KAAK,2CAA2C,GAAG;IACjD,gBAAgB,EAAE,gBAAgB,CAAC;IACnC,sBAAsB,CAAC,EAAE,sBAAsB,CAAC;IAChD,MAAM,EAAE,cAAc,CAAC;IACvB,GAAG,EAAE,MAAM,CAAC;CACb,CAAC;AAEF,KAAK,sCAAsC,GAAG;IAC5C,mBAAmB,CAAC,EAAE,uBAAuB,CAAC;IAC9C,mCAAmC,CAAC,EAAE,gBAAgB,CAAC;IACvD,eAAe,EAAE,EAAE,CAAC;IACpB,iBAAiB,EAAE,OAAO,CAAC;CAC5B,CAAC;AAEF,gGAAgG;AAChG,wBAAsB,2CAA2C,CAC/D,KAAK,EAAE,2CAA2C,GACjD,OAAO,CAAC,uBAAuB,GAAG,SAAS,CAAC,CAU9C;AAED,iGAAiG;AACjG,wBAAgB,sCAAsC,CACpD,KAAK,EAAE,sCAAsC,GAC5C,uBAAuB,GAAG,SAAS,CAarC;AAED,uFAAuF;AACvF,wBAAsB,+BAA+B,CAAC,KAAK,EAAE,2CAA2C,sEAgCvG"}