@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
@@ -379,8 +379,8 @@ import { FormattedViemError, formatViemError, mergeAbis, tryExtractEvent } from
379
379
  import { sumBigint } from '@aztec/foundation/bigint';
380
380
  import { toHex as toPaddedHex } from '@aztec/foundation/bigint-buffer';
381
381
  import { CheckpointNumber, SlotNumber } from '@aztec/foundation/branded-types';
382
+ import { trimmedBytesLength } from '@aztec/foundation/buffer';
382
383
  import { pick } from '@aztec/foundation/collection';
383
- import { TimeoutError } from '@aztec/foundation/error';
384
384
  import { EthAddress } from '@aztec/foundation/eth-address';
385
385
  import { Signature } from '@aztec/foundation/eth-signature';
386
386
  import { createLogger } from '@aztec/foundation/log';
@@ -390,9 +390,9 @@ import { Timer } from '@aztec/foundation/timer';
390
390
  import { EmpireBaseAbi, ErrorsAbi, RollupAbi } from '@aztec/l1-artifacts';
391
391
  import { encodeSlashConsensusVotes } from '@aztec/slasher';
392
392
  import { CommitteeAttestationsAndSigners } from '@aztec/stdlib/block';
393
+ import { getLastL1SlotTimestampForL2Slot, getNextL1SlotTimestamp } from '@aztec/stdlib/epoch-helpers';
393
394
  import { getTelemetryClient, trackSpan } from '@aztec/telemetry-client';
394
- import { encodeFunctionData, keccak256, multicall3Abi, toHex } from 'viem';
395
- import { createL1TxFailedStore } from './l1_tx_failed_store/index.js';
395
+ import { encodeFunctionData, toHex } from 'viem';
396
396
  import { SequencerPublisherMetrics } from './sequencer-publisher-metrics.js';
397
397
  export const Actions = [
398
398
  'invalidate-by-invalid-attestation',
@@ -432,7 +432,6 @@ export class SequencerPublisher {
432
432
  interrupted;
433
433
  metrics;
434
434
  epochCache;
435
- failedTxStore;
436
435
  governanceLog;
437
436
  slashingLog;
438
437
  lastActions;
@@ -440,9 +439,10 @@ export class SequencerPublisher {
440
439
  payloadProposedCache;
441
440
  log;
442
441
  ethereumSlotDuration;
442
+ aztecSlotDuration;
443
+ dateProvider;
443
444
  blobClient;
444
445
  /** Address to use for simulations in fisherman mode (actual proposer's address) */ proposerAddressForSimulation;
445
- /** Optional callback to obtain a replacement publisher when the current one fails to send. */ getNextPublisher;
446
446
  /** L1 fee analyzer for fisherman mode */ l1FeeAnalyzer;
447
447
  /** Fee asset price oracle for computing price modifiers from Uniswap V4 */ feeAssetPriceOracle;
448
448
  // A CALL to a cold address is 2700 gas
@@ -467,6 +467,8 @@ export class SequencerPublisher {
467
467
  this.requests = [];
468
468
  this.log = deps.log ?? createLogger('sequencer:publisher');
469
469
  this.ethereumSlotDuration = BigInt(config.ethereumSlotDuration);
470
+ this.aztecSlotDuration = BigInt(config.aztecSlotDuration);
471
+ this.dateProvider = deps.dateProvider;
470
472
  this.epochCache = deps.epochCache;
471
473
  this.lastActions = deps.lastActions;
472
474
  this.blobClient = deps.blobClient;
@@ -474,7 +476,6 @@ export class SequencerPublisher {
474
476
  this.metrics = deps.metrics ?? new SequencerPublisherMetrics(telemetry, 'SequencerPublisher');
475
477
  this.tracer = telemetry.getTracer('SequencerPublisher');
476
478
  this.l1TxUtils = deps.l1TxUtils;
477
- this.getNextPublisher = deps.getNextPublisher;
478
479
  this.rollupContract = deps.rollupContract;
479
480
  this.govProposerContract = deps.governanceProposerContract;
480
481
  this.slashingProposerContract = deps.slashingProposerContract;
@@ -490,24 +491,6 @@ export class SequencerPublisher {
490
491
  }
491
492
  // Initialize fee asset price oracle
492
493
  this.feeAssetPriceOracle = new FeeAssetPriceOracle(this.l1TxUtils.client, this.rollupContract, createLogger('sequencer:publisher:price-oracle'));
493
- // Initialize failed L1 tx store (optional, for test networks)
494
- this.failedTxStore = createL1TxFailedStore(config.l1TxFailedStore, this.log);
495
- }
496
- /**
497
- * Backs up a failed L1 transaction to the configured store for debugging.
498
- * Does nothing if no store is configured.
499
- */ backupFailedTx(failedTx) {
500
- if (!this.failedTxStore) {
501
- return;
502
- }
503
- const tx = {
504
- ...failedTx,
505
- timestamp: Date.now()
506
- };
507
- // Fire and forget - don't block on backup
508
- void this.failedTxStore.then((store)=>store?.saveFailedTx(tx)).catch((err)=>{
509
- this.log.warn(`Failed to backup failed L1 tx to store`, err);
510
- });
511
494
  }
512
495
  getRollupContract() {
513
496
  return this.rollupContract;
@@ -625,8 +608,8 @@ export class SequencerPublisher {
625
608
  // @note - we can only have one blob config per bundle
626
609
  // find requests with gas and blob configs
627
610
  // See https://github.com/AztecProtocol/aztec-packages/issues/11513
628
- const gasConfigs = requestsToProcess.filter((request)=>request.gasConfig).map((request)=>request.gasConfig);
629
- const blobConfigs = requestsToProcess.filter((request)=>request.blobConfig).map((request)=>request.blobConfig);
611
+ const gasConfigs = validRequests.filter((request)=>request.gasConfig).map((request)=>request.gasConfig);
612
+ const blobConfigs = validRequests.filter((request)=>request.blobConfig).map((request)=>request.blobConfig);
630
613
  if (blobConfigs.length > 1) {
631
614
  throw new Error('Multiple blob configs found');
632
615
  }
@@ -653,34 +636,12 @@ export class SequencerPublisher {
653
636
  // This ensures the committee gets precomputed correctly
654
637
  validRequests.sort((a, b)=>compareActions(a.action, b.action));
655
638
  try {
656
- // Capture context for failed tx backup before sending
657
- const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
658
- const multicallData = encodeFunctionData({
659
- abi: multicall3Abi,
660
- functionName: 'aggregate3',
661
- args: [
662
- validRequests.map((r)=>({
663
- target: r.request.to,
664
- callData: r.request.data,
665
- allowFailure: true
666
- }))
667
- ]
668
- });
669
- const blobDataHex = blobConfig?.blobs?.map((b)=>toHex(b));
670
- const txContext = {
671
- multicallData,
672
- blobData: blobDataHex,
673
- l1BlockNumber
674
- };
675
639
  this.log.debug('Forwarding transactions', {
676
640
  validRequests: validRequests.map((request)=>request.action),
677
641
  txConfig
678
642
  });
679
- const result = await this.forwardWithPublisherRotation(validRequests, txConfig, blobConfig);
680
- if (result === undefined) {
681
- return undefined;
682
- }
683
- const { successfulActions = [], failedActions = [] } = this.callbackBundledTransactions(validRequests, result, txContext);
643
+ const result = await Multicall3.forward(validRequests.map((request)=>request.request), this.l1TxUtils, txConfig, blobConfig, this.rollupContract.address, this.log);
644
+ const { successfulActions = [], failedActions = [] } = this.callbackBundledTransactions(validRequests, result);
684
645
  return {
685
646
  result,
686
647
  expiredActions,
@@ -700,74 +661,26 @@ export class SequencerPublisher {
700
661
  }
701
662
  }
702
663
  }
703
- /**
704
- * Forwards transactions via Multicall3, rotating to the next available publisher if a send
705
- * failure occurs (i.e. the tx never reached the chain).
706
- * On-chain reverts and simulation errors are returned as-is without rotation.
707
- */ async forwardWithPublisherRotation(validRequests, txConfig, blobConfig) {
708
- const triedAddresses = [];
709
- let currentPublisher = this.l1TxUtils;
710
- while(true){
711
- triedAddresses.push(currentPublisher.getSenderAddress());
712
- try {
713
- const result = await Multicall3.forward(validRequests.map((r)=>r.request), currentPublisher, txConfig, blobConfig, this.rollupContract.address, this.log);
714
- this.l1TxUtils = currentPublisher;
715
- return result;
716
- } catch (err) {
717
- if (err instanceof TimeoutError) {
718
- throw err;
719
- }
720
- const viemError = formatViemError(err);
721
- if (!this.getNextPublisher) {
722
- this.log.error('Failed to publish bundled transactions', viemError);
723
- return undefined;
724
- }
725
- this.log.warn(`Publisher ${currentPublisher.getSenderAddress()} failed to send, rotating to next publisher`, viemError);
726
- const nextPublisher = await this.getNextPublisher([
727
- ...triedAddresses
728
- ]);
729
- if (!nextPublisher) {
730
- this.log.error('All available publishers exhausted, failed to publish bundled transactions');
731
- return undefined;
732
- }
733
- currentPublisher = nextPublisher;
734
- }
735
- }
736
- }
737
- callbackBundledTransactions(requests, result, txContext) {
664
+ callbackBundledTransactions(requests, result) {
738
665
  const actionsListStr = requests.map((r)=>r.action).join(', ');
739
666
  if (result instanceof FormattedViemError) {
740
667
  this.log.error(`Failed to publish bundled transactions (${actionsListStr})`, result);
741
- this.backupFailedTx({
742
- id: keccak256(txContext.multicallData),
743
- failureType: 'send-error',
744
- request: {
745
- to: MULTI_CALL_3_ADDRESS,
746
- data: txContext.multicallData
747
- },
748
- blobData: txContext.blobData,
749
- l1BlockNumber: txContext.l1BlockNumber.toString(),
750
- error: {
751
- message: result.message,
752
- name: result.name
753
- },
754
- context: {
755
- actions: requests.map((r)=>r.action),
756
- requests: requests.map((r)=>({
757
- action: r.action,
758
- to: r.request.to,
759
- data: r.request.data
760
- })),
761
- sender: this.getSenderAddress().toString()
762
- }
763
- });
764
668
  return {
765
669
  failedActions: requests.map((r)=>r.action)
766
670
  };
767
671
  } else {
768
672
  this.log.verbose(`Published bundled transactions (${actionsListStr})`, {
769
673
  result,
770
- requests
674
+ requests: requests.map((r)=>({
675
+ ...r,
676
+ // Avoid logging large blob data
677
+ blobConfig: r.blobConfig ? {
678
+ ...r.blobConfig,
679
+ blobs: r.blobConfig.blobs.map((b)=>({
680
+ size: trimmedBytesLength(b)
681
+ }))
682
+ } : undefined
683
+ }))
771
684
  });
772
685
  const successfulActions = [];
773
686
  const failedActions = [];
@@ -778,37 +691,6 @@ export class SequencerPublisher {
778
691
  failedActions.push(request.action);
779
692
  }
780
693
  }
781
- // Single backup for the whole reverted tx
782
- if (failedActions.length > 0 && result?.receipt?.status === 'reverted') {
783
- this.backupFailedTx({
784
- id: result.receipt.transactionHash,
785
- failureType: 'revert',
786
- request: {
787
- to: MULTI_CALL_3_ADDRESS,
788
- data: txContext.multicallData
789
- },
790
- blobData: txContext.blobData,
791
- l1BlockNumber: result.receipt.blockNumber.toString(),
792
- receipt: {
793
- transactionHash: result.receipt.transactionHash,
794
- blockNumber: result.receipt.blockNumber.toString(),
795
- gasUsed: (result.receipt.gasUsed ?? 0n).toString(),
796
- status: 'reverted'
797
- },
798
- error: {
799
- message: result.errorMsg ?? 'Transaction reverted'
800
- },
801
- context: {
802
- actions: failedActions,
803
- requests: requests.filter((r)=>failedActions.includes(r.action)).map((r)=>({
804
- action: r.action,
805
- to: r.request.to,
806
- data: r.request.data
807
- })),
808
- sender: this.getSenderAddress().toString()
809
- }
810
- });
811
- }
812
694
  return {
813
695
  successfulActions,
814
696
  failedActions
@@ -816,17 +698,20 @@ export class SequencerPublisher {
816
698
  }
817
699
  }
818
700
  /**
819
- * @notice Will call `canProposeAtNextEthBlock` to make sure that it is possible to propose
701
+ * @notice Will call `canProposeAt` to make sure that it is possible to propose
820
702
  * @param tipArchive - The archive to check
821
703
  * @returns The slot and block number if it is possible to propose, undefined otherwise
822
- */ canProposeAtNextEthBlock(tipArchive, msgSender, opts = {}) {
704
+ */ canProposeAt(tipArchive, msgSender, opts = {}) {
823
705
  // TODO: #14291 - should loop through multiple keys to check if any of them can propose
824
706
  const ignoredErrors = [
825
707
  'SlotAlreadyInChain',
826
708
  'InvalidProposer',
827
709
  'InvalidArchive'
828
710
  ];
829
- return this.rollupContract.canProposeAtNextEthBlock(tipArchive.toBuffer(), msgSender.toString(), Number(this.ethereumSlotDuration), {
711
+ const pipelined = opts.pipelined ?? false;
712
+ const slotOffset = pipelined ? this.aztecSlotDuration : 0n;
713
+ const nextL1SlotTs = this.getNextL1SlotTimestamp() + slotOffset;
714
+ return this.rollupContract.canProposeAt(tipArchive.toBuffer(), msgSender.toString(), nextL1SlotTs, {
830
715
  forcePendingCheckpointNumber: opts.forcePendingCheckpointNumber
831
716
  }).catch((err)=>{
832
717
  if (err instanceof FormattedViemError && ignoredErrors.find((e)=>err.message.includes(e))) {
@@ -858,7 +743,7 @@ export class SequencerPublisher {
858
743
  header.blobsHash.toString(),
859
744
  flags
860
745
  ];
861
- const ts = BigInt((await this.l1TxUtils.getBlock()).timestamp + this.ethereumSlotDuration);
746
+ const ts = this.getSimulationTimestamp(header.slotNumber);
862
747
  const stateOverrides = await this.rollupContract.makePendingCheckpointNumberOverride(opts?.forcePendingCheckpointNumber);
863
748
  let balance = 0n;
864
749
  if (this.config.fishermanMode) {
@@ -881,7 +766,7 @@ export class SequencerPublisher {
881
766
  }),
882
767
  from: MULTI_CALL_3_ADDRESS
883
768
  }, {
884
- time: ts + 1n
769
+ time: ts
885
770
  }, stateOverrides);
886
771
  this.log.debug(`Simulated validateHeader`);
887
772
  }
@@ -911,7 +796,6 @@ export class SequencerPublisher {
911
796
  ...logData,
912
797
  request
913
798
  });
914
- const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
915
799
  try {
916
800
  const { gasUsed } = await this.l1TxUtils.simulate(request, undefined, undefined, mergeAbis([
917
801
  request.abi ?? [],
@@ -954,27 +838,6 @@ export class SequencerPublisher {
954
838
  }
955
839
  // Otherwise, throw. We cannot build the next checkpoint if we cannot invalidate the previous one.
956
840
  this.log.error(`Simulation for invalidate checkpoint ${checkpointNumber} failed`, viemError, logData);
957
- this.backupFailedTx({
958
- id: keccak256(request.data),
959
- failureType: 'simulation',
960
- request: {
961
- to: request.to,
962
- data: request.data,
963
- value: request.value?.toString()
964
- },
965
- l1BlockNumber: l1BlockNumber.toString(),
966
- error: {
967
- message: viemError.message,
968
- name: viemError.name
969
- },
970
- context: {
971
- actions: [
972
- `invalidate-${reason}`
973
- ],
974
- checkpointNumber,
975
- sender: this.getSenderAddress().toString()
976
- }
977
- });
978
841
  throw new Error(`Failed to simulate invalidate checkpoint ${checkpointNumber}`, {
979
842
  cause: viemError
980
843
  });
@@ -1001,7 +864,6 @@ export class SequencerPublisher {
1001
864
  }
1002
865
  }
1003
866
  /** Simulates `propose` to make sure that the checkpoint is valid for submission */ async validateCheckpointForSubmission(checkpoint, attestationsAndSigners, attestationsAndSignersSignature, options) {
1004
- const ts = BigInt((await this.l1TxUtils.getBlock()).timestamp + this.ethereumSlotDuration);
1005
867
  const blobFields = checkpoint.toBlobFields();
1006
868
  const blobs = await getBlobsPerL1Block(blobFields);
1007
869
  const blobInput = getPrefixedEthBlobCommitments(blobs);
@@ -1018,10 +880,9 @@ export class SequencerPublisher {
1018
880
  attestationsAndSignersSignature.toViemSignature(),
1019
881
  blobInput
1020
882
  ];
1021
- await this.simulateProposeTx(args, ts, options);
1022
- return ts;
883
+ await this.simulateProposeTx(args, options);
1023
884
  }
1024
- async enqueueCastSignalHelper(slotNumber, timestamp, signalType, payload, base, signerAddress, signer) {
885
+ async enqueueCastSignalHelper(slotNumber, signalType, payload, base, signerAddress, signer) {
1025
886
  if (this.lastActions[signalType] && this.lastActions[signalType] === slotNumber) {
1026
887
  this.log.debug(`Skipping duplicate vote cast signal ${signalType} for slot ${slotNumber}`);
1027
888
  return false;
@@ -1077,7 +938,7 @@ export class SequencerPublisher {
1077
938
  signer: this.l1TxUtils.client.account?.address,
1078
939
  lastValidL2Slot: slotNumber
1079
940
  });
1080
- const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
941
+ const timestamp = this.getSimulationTimestamp(slotNumber);
1081
942
  try {
1082
943
  await this.l1TxUtils.simulate(request, {
1083
944
  time: timestamp
@@ -1090,27 +951,8 @@ export class SequencerPublisher {
1090
951
  });
1091
952
  } catch (err) {
1092
953
  const viemError = formatViemError(err);
1093
- this.log.error(`Failed simulation for ${action} at slot ${slotNumber} (enqueuing the action anyway)`, viemError);
1094
- this.backupFailedTx({
1095
- id: keccak256(request.data),
1096
- failureType: 'simulation',
1097
- request: {
1098
- to: request.to,
1099
- data: request.data,
1100
- value: request.value?.toString()
1101
- },
1102
- l1BlockNumber: l1BlockNumber.toString(),
1103
- error: {
1104
- message: viemError.message,
1105
- name: viemError.name
1106
- },
1107
- context: {
1108
- actions: [
1109
- action
1110
- ],
1111
- slot: slotNumber,
1112
- sender: this.getSenderAddress().toString()
1113
- }
954
+ this.log.error(`Failed simulation for ${action} at slot ${slotNumber} (enqueuing the action anyway)`, viemError, {
955
+ simulationTimestamp: timestamp
1114
956
  });
1115
957
  // Yes, we enqueue the request anyway, in case there was a bug with the simulation itself
1116
958
  }
@@ -1155,12 +997,11 @@ export class SequencerPublisher {
1155
997
  /**
1156
998
  * Enqueues a governance castSignal transaction to cast a signal for a given slot number.
1157
999
  * @param slotNumber - The slot number to cast a signal for.
1158
- * @param timestamp - The timestamp of the slot to cast a signal for.
1159
1000
  * @returns True if the signal was successfully enqueued, false otherwise.
1160
- */ enqueueGovernanceCastSignal(governancePayload, slotNumber, timestamp, signerAddress, signer) {
1161
- return this.enqueueCastSignalHelper(slotNumber, timestamp, 'governance-signal', governancePayload, this.govProposerContract, signerAddress, signer);
1001
+ */ enqueueGovernanceCastSignal(governancePayload, slotNumber, signerAddress, signer) {
1002
+ return this.enqueueCastSignalHelper(slotNumber, 'governance-signal', governancePayload, this.govProposerContract, signerAddress, signer);
1162
1003
  }
1163
- /** Enqueues all slashing actions as returned by the slasher client. */ async enqueueSlashingActions(actions, slotNumber, timestamp, signerAddress, signer) {
1004
+ /** Enqueues all slashing actions as returned by the slasher client. */ async enqueueSlashingActions(actions, slotNumber, signerAddress, signer) {
1164
1005
  if (actions.length === 0) {
1165
1006
  this.log.debug(`No slashing actions to enqueue for slot ${slotNumber}`);
1166
1007
  return false;
@@ -1176,7 +1017,7 @@ export class SequencerPublisher {
1176
1017
  this.log.debug(`Enqueuing slashing vote for payload ${action.payload} at slot ${slotNumber}`, {
1177
1018
  signerAddress
1178
1019
  });
1179
- await this.enqueueCastSignalHelper(slotNumber, timestamp, 'empire-slashing-signal', action.payload, this.slashingProposerContract, signerAddress, signer);
1020
+ await this.enqueueCastSignalHelper(slotNumber, 'empire-slashing-signal', action.payload, this.slashingProposerContract, signerAddress, signer);
1180
1021
  break;
1181
1022
  }
1182
1023
  case 'create-empire-payload':
@@ -1186,7 +1027,7 @@ export class SequencerPublisher {
1186
1027
  signerAddress
1187
1028
  });
1188
1029
  const request = this.slashFactoryContract.buildCreatePayloadRequest(action.data);
1189
- await this.simulateAndEnqueueRequest('create-empire-payload', request, (receipt)=>!!this.slashFactoryContract.tryExtractSlashPayloadCreatedEvent(receipt.logs), slotNumber, timestamp);
1030
+ await this.simulateAndEnqueueRequest('create-empire-payload', request, (receipt)=>!!this.slashFactoryContract.tryExtractSlashPayloadCreatedEvent(receipt.logs), slotNumber);
1190
1031
  break;
1191
1032
  }
1192
1033
  case 'execute-empire-payload':
@@ -1201,7 +1042,7 @@ export class SequencerPublisher {
1201
1042
  }
1202
1043
  const empireSlashingProposer = this.slashingProposerContract;
1203
1044
  const request = empireSlashingProposer.buildExecuteRoundRequest(action.round);
1204
- await this.simulateAndEnqueueRequest('execute-empire-payload', request, (receipt)=>!!empireSlashingProposer.tryExtractPayloadSubmittedEvent(receipt.logs), slotNumber, timestamp);
1045
+ await this.simulateAndEnqueueRequest('execute-empire-payload', request, (receipt)=>!!empireSlashingProposer.tryExtractPayloadSubmittedEvent(receipt.logs), slotNumber);
1205
1046
  break;
1206
1047
  }
1207
1048
  case 'vote-offenses':
@@ -1219,7 +1060,7 @@ export class SequencerPublisher {
1219
1060
  const tallySlashingProposer = this.slashingProposerContract;
1220
1061
  const votes = bufferToHex(encodeSlashConsensusVotes(action.votes));
1221
1062
  const request = await tallySlashingProposer.buildVoteRequestFromSigner(votes, slotNumber, signer);
1222
- await this.simulateAndEnqueueRequest('vote-offenses', request, (receipt)=>!!tallySlashingProposer.tryExtractVoteCastEvent(receipt.logs), slotNumber, timestamp);
1063
+ await this.simulateAndEnqueueRequest('vote-offenses', request, (receipt)=>!!tallySlashingProposer.tryExtractVoteCastEvent(receipt.logs), slotNumber);
1223
1064
  break;
1224
1065
  }
1225
1066
  case 'execute-slash':
@@ -1235,7 +1076,7 @@ export class SequencerPublisher {
1235
1076
  }
1236
1077
  const tallySlashingProposer = this.slashingProposerContract;
1237
1078
  const request = tallySlashingProposer.buildExecuteRoundRequest(action.round, action.committees);
1238
- await this.simulateAndEnqueueRequest('execute-slash', request, (receipt)=>!!tallySlashingProposer.tryExtractRoundExecutedEvent(receipt.logs), slotNumber, timestamp);
1079
+ await this.simulateAndEnqueueRequest('execute-slash', request, (receipt)=>!!tallySlashingProposer.tryExtractRoundExecutedEvent(receipt.logs), slotNumber);
1239
1080
  break;
1240
1081
  }
1241
1082
  default:
@@ -1259,14 +1100,13 @@ export class SequencerPublisher {
1259
1100
  attestationsAndSignersSignature,
1260
1101
  feeAssetPriceModifier: checkpoint.feeAssetPriceModifier
1261
1102
  };
1262
- let ts;
1263
1103
  try {
1264
1104
  // @note This will make sure that we are passing the checks for our header ASSUMING that the data is also made available
1265
1105
  // This means that we can avoid the simulation issues in later checks.
1266
1106
  // By simulation issue, I mean the fact that the block.timestamp is equal to the last block, not the next, which
1267
1107
  // make time consistency checks break.
1268
1108
  // TODO(palla): Check whether we're validating twice, once here and once within addProposeTx, since we call simulateProposeTx in both places.
1269
- ts = await this.validateCheckpointForSubmission(checkpoint, attestationsAndSigners, attestationsAndSignersSignature, opts);
1109
+ await this.validateCheckpointForSubmission(checkpoint, attestationsAndSigners, attestationsAndSignersSignature, opts);
1270
1110
  } catch (err) {
1271
1111
  this.log.error(`Checkpoint validation failed. ${err instanceof Error ? err.message : 'No error message'}`, err, {
1272
1112
  ...checkpoint.getStats(),
@@ -1279,7 +1119,7 @@ export class SequencerPublisher {
1279
1119
  ...checkpoint.toCheckpointInfo(),
1280
1120
  ...opts
1281
1121
  });
1282
- await this.addProposeTx(checkpoint, proposeTxArgs, opts, ts);
1122
+ await this.addProposeTx(checkpoint, proposeTxArgs, opts);
1283
1123
  }
1284
1124
  enqueueInvalidateCheckpoint(request, opts = {}) {
1285
1125
  if (!request) {
@@ -1320,7 +1160,8 @@ export class SequencerPublisher {
1320
1160
  }
1321
1161
  });
1322
1162
  }
1323
- async simulateAndEnqueueRequest(action, request, checkSuccess, slotNumber, timestamp) {
1163
+ async simulateAndEnqueueRequest(action, request, checkSuccess, slotNumber) {
1164
+ const timestamp = this.getSimulationTimestamp(slotNumber);
1324
1165
  const logData = {
1325
1166
  slotNumber,
1326
1167
  timestamp,
@@ -1333,7 +1174,6 @@ export class SequencerPublisher {
1333
1174
  const cachedLastActionSlot = this.lastActions[action];
1334
1175
  this.lastActions[action] = slotNumber;
1335
1176
  this.log.debug(`Simulating ${action} for slot ${slotNumber}`, logData);
1336
- const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
1337
1177
  let gasUsed;
1338
1178
  const simulateAbi = mergeAbis([
1339
1179
  request.abi ?? [],
@@ -1342,7 +1182,7 @@ export class SequencerPublisher {
1342
1182
  try {
1343
1183
  ({ gasUsed } = await this.l1TxUtils.simulate(request, {
1344
1184
  time: timestamp
1345
- }, [], simulateAbi)); // TODO(palla/slash): Check the timestamp logic
1185
+ }, [], simulateAbi));
1346
1186
  this.log.verbose(`Simulation for ${action} succeeded`, {
1347
1187
  ...logData,
1348
1188
  request,
@@ -1351,27 +1191,6 @@ export class SequencerPublisher {
1351
1191
  } catch (err) {
1352
1192
  const viemError = formatViemError(err, simulateAbi);
1353
1193
  this.log.error(`Simulation for ${action} at ${slotNumber} failed`, viemError, logData);
1354
- this.backupFailedTx({
1355
- id: keccak256(request.data),
1356
- failureType: 'simulation',
1357
- request: {
1358
- to: request.to,
1359
- data: request.data,
1360
- value: request.value?.toString()
1361
- },
1362
- l1BlockNumber: l1BlockNumber.toString(),
1363
- error: {
1364
- message: viemError.message,
1365
- name: viemError.name
1366
- },
1367
- context: {
1368
- actions: [
1369
- action
1370
- ],
1371
- slot: slotNumber,
1372
- sender: this.getSenderAddress().toString()
1373
- }
1374
- });
1375
1194
  return false;
1376
1195
  }
1377
1196
  // We issued the simulation against the rollup contract, so we need to account for the overhead of the multicall3
@@ -1423,7 +1242,7 @@ export class SequencerPublisher {
1423
1242
  this.interrupted = false;
1424
1243
  this.l1TxUtils.restart();
1425
1244
  }
1426
- async prepareProposeTx(encodedData, timestamp, options) {
1245
+ async prepareProposeTx(encodedData, options) {
1427
1246
  const kzg = Blob.getViemKzgInstance();
1428
1247
  const blobInput = getPrefixedEthBlobCommitments(encodedData.blobs);
1429
1248
  this.log.debug('Validating blob input', {
@@ -1450,38 +1269,10 @@ export class SequencerPublisher {
1450
1269
  }, {}, {
1451
1270
  blobs: encodedData.blobs.map((b)=>b.data),
1452
1271
  kzg
1453
- }).catch(async (err)=>{
1454
- const viemError = formatViemError(err);
1455
- this.log.error(`Failed to validate blobs`, viemError.message, {
1456
- metaMessages: viemError.metaMessages
1457
- });
1458
- const validateBlobsData = encodeFunctionData({
1459
- abi: RollupAbi,
1460
- functionName: 'validateBlobs',
1461
- args: [
1462
- blobInput
1463
- ]
1464
- });
1465
- const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
1466
- this.backupFailedTx({
1467
- id: keccak256(validateBlobsData),
1468
- failureType: 'simulation',
1469
- request: {
1470
- to: this.rollupContract.address,
1471
- data: validateBlobsData
1472
- },
1473
- blobData: encodedData.blobs.map((b)=>toHex(b.data)),
1474
- l1BlockNumber: l1BlockNumber.toString(),
1475
- error: {
1476
- message: viemError.message,
1477
- name: viemError.name
1478
- },
1479
- context: {
1480
- actions: [
1481
- 'validate-blobs'
1482
- ],
1483
- sender: this.getSenderAddress().toString()
1484
- }
1272
+ }).catch((err)=>{
1273
+ const { message, metaMessages } = formatViemError(err);
1274
+ this.log.error(`Failed to validate blobs`, message, {
1275
+ metaMessages
1485
1276
  });
1486
1277
  throw new Error('Failed to validate blobs');
1487
1278
  });
@@ -1500,7 +1291,7 @@ export class SequencerPublisher {
1500
1291
  encodedData.attestationsAndSignersSignature.toViemSignature(),
1501
1292
  blobInput
1502
1293
  ];
1503
- const { rollupData, simulationResult } = await this.simulateProposeTx(args, timestamp, options);
1294
+ const { rollupData, simulationResult } = await this.simulateProposeTx(args, options);
1504
1295
  return {
1505
1296
  args,
1506
1297
  blobEvaluationGas,
@@ -1511,9 +1302,8 @@ export class SequencerPublisher {
1511
1302
  /**
1512
1303
  * Simulates the propose tx with eth_simulateV1
1513
1304
  * @param args - The propose tx args
1514
- * @param timestamp - The timestamp to simulate proposal at
1515
1305
  * @returns The simulation result
1516
- */ async simulateProposeTx(args, timestamp, options) {
1306
+ */ async simulateProposeTx(args, options) {
1517
1307
  const rollupData = encodeFunctionData({
1518
1308
  abi: RollupAbi,
1519
1309
  functionName: 'propose',
@@ -1541,7 +1331,7 @@ export class SequencerPublisher {
1541
1331
  balance: 10n * WEI_CONST * WEI_CONST
1542
1332
  });
1543
1333
  }
1544
- const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
1334
+ const simTs = this.getSimulationTimestamp(SlotNumber.fromBigInt(args[0].header.slotNumber));
1545
1335
  const simulationResult = await this.l1TxUtils.simulate({
1546
1336
  to: this.rollupContract.address,
1547
1337
  data: rollupData,
@@ -1550,8 +1340,7 @@ export class SequencerPublisher {
1550
1340
  from: this.proposerAddressForSimulation.toString()
1551
1341
  }
1552
1342
  }, {
1553
- // @note we add 1n to the timestamp because geth implementation doesn't like simulation timestamp to be equal to the current block timestamp
1554
- time: timestamp + 1n,
1343
+ time: simTs,
1555
1344
  // @note reth should have a 30m gas limit per block but throws errors that this tx is beyond limit so we increase here
1556
1345
  gasLimit: MAX_L1_TX_LIMIT * 2n
1557
1346
  }, stateOverrides, RollupAbi, {
@@ -1568,26 +1357,8 @@ export class SequencerPublisher {
1568
1357
  logs: []
1569
1358
  };
1570
1359
  }
1571
- this.log.error(`Failed to simulate propose tx`, viemError);
1572
- this.backupFailedTx({
1573
- id: keccak256(rollupData),
1574
- failureType: 'simulation',
1575
- request: {
1576
- to: this.rollupContract.address,
1577
- data: rollupData
1578
- },
1579
- l1BlockNumber: l1BlockNumber.toString(),
1580
- error: {
1581
- message: viemError.message,
1582
- name: viemError.name
1583
- },
1584
- context: {
1585
- actions: [
1586
- 'propose'
1587
- ],
1588
- slot: Number(args[0].header.slotNumber),
1589
- sender: this.getSenderAddress().toString()
1590
- }
1360
+ this.log.error(`Failed to simulate propose tx`, viemError, {
1361
+ simulationTimestamp: simTs
1591
1362
  });
1592
1363
  throw err;
1593
1364
  });
@@ -1596,11 +1367,11 @@ export class SequencerPublisher {
1596
1367
  simulationResult
1597
1368
  };
1598
1369
  }
1599
- async addProposeTx(checkpoint, encodedData, opts = {}, timestamp) {
1370
+ async addProposeTx(checkpoint, encodedData, opts = {}) {
1600
1371
  const slot = checkpoint.header.slotNumber;
1601
1372
  const timer = new Timer();
1602
1373
  const kzg = Blob.getViemKzgInstance();
1603
- const { rollupData, simulationResult, blobEvaluationGas } = await this.prepareProposeTx(encodedData, timestamp, opts);
1374
+ const { rollupData, simulationResult, blobEvaluationGas } = await this.prepareProposeTx(encodedData, opts);
1604
1375
  const startBlock = await this.l1TxUtils.getBlockNumber();
1605
1376
  const gasLimit = this.l1TxUtils.bumpGasLimit(BigInt(Math.ceil(Number(simulationResult.gasUsed) * 64 / 63)) + blobEvaluationGas + SequencerPublisher.MULTICALL_OVERHEAD_GAS_GUESS);
1606
1377
  // Send the blobs to the blob client preemptively. This helps in tests where the sequencer mistakingly thinks that the propose
@@ -1665,4 +1436,13 @@ export class SequencerPublisher {
1665
1436
  }
1666
1437
  });
1667
1438
  }
1439
+ /** Returns the timestamp of the last L1 slot within a given L2 slot. Used as the simulation timestamp
1440
+ * for eth_simulateV1 calls, since it's guaranteed to be greater than any L1 block produced during the slot. */ getSimulationTimestamp(slot) {
1441
+ const l1Constants = this.epochCache.getL1Constants();
1442
+ return getLastL1SlotTimestampForL2Slot(slot, l1Constants);
1443
+ }
1444
+ /** Returns the timestamp of the next L1 slot boundary after now. */ getNextL1SlotTimestamp() {
1445
+ const l1Constants = this.epochCache.getL1Constants();
1446
+ return getNextL1SlotTimestamp(this.dateProvider.nowInSeconds(), l1Constants);
1447
+ }
1668
1448
  }