@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
@@ -28,9 +28,9 @@ import { FormattedViemError, formatViemError, mergeAbis, tryExtractEvent } from
28
28
  import { sumBigint } from '@aztec/foundation/bigint';
29
29
  import { toHex as toPaddedHex } from '@aztec/foundation/bigint-buffer';
30
30
  import { CheckpointNumber, SlotNumber } from '@aztec/foundation/branded-types';
31
+ import { trimmedBytesLength } from '@aztec/foundation/buffer';
31
32
  import { pick } from '@aztec/foundation/collection';
32
33
  import type { Fr } from '@aztec/foundation/curves/bn254';
33
- import { TimeoutError } from '@aztec/foundation/error';
34
34
  import { EthAddress } from '@aztec/foundation/eth-address';
35
35
  import { Signature, type ViemSignature } from '@aztec/foundation/eth-signature';
36
36
  import { type Logger, createLogger } from '@aztec/foundation/log';
@@ -41,24 +41,15 @@ import { EmpireBaseAbi, ErrorsAbi, RollupAbi } from '@aztec/l1-artifacts';
41
41
  import { type ProposerSlashAction, encodeSlashConsensusVotes } from '@aztec/slasher';
42
42
  import { CommitteeAttestationsAndSigners, type ValidateCheckpointResult } from '@aztec/stdlib/block';
43
43
  import type { Checkpoint } from '@aztec/stdlib/checkpoint';
44
+ import { getLastL1SlotTimestampForL2Slot, getNextL1SlotTimestamp } from '@aztec/stdlib/epoch-helpers';
44
45
  import { SlashFactoryContract } from '@aztec/stdlib/l1-contracts';
45
46
  import type { CheckpointHeader } from '@aztec/stdlib/rollup';
46
47
  import type { L1PublishCheckpointStats } from '@aztec/stdlib/stats';
47
48
  import { type TelemetryClient, type Tracer, getTelemetryClient, trackSpan } from '@aztec/telemetry-client';
48
49
 
49
- import {
50
- type Hex,
51
- type StateOverride,
52
- type TransactionReceipt,
53
- type TypedDataDefinition,
54
- encodeFunctionData,
55
- keccak256,
56
- multicall3Abi,
57
- toHex,
58
- } from 'viem';
50
+ import { type StateOverride, type TransactionReceipt, type TypedDataDefinition, encodeFunctionData, toHex } from 'viem';
59
51
 
60
52
  import type { SequencerPublisherConfig } from './config.js';
61
- import { type FailedL1Tx, type L1TxFailedStore, createL1TxFailedStore } from './l1_tx_failed_store/index.js';
62
53
  import { SequencerPublisherMetrics } from './sequencer-publisher-metrics.js';
63
54
 
64
55
  /** Arguments to the process method of the rollup contract */
@@ -120,7 +111,6 @@ export class SequencerPublisher {
120
111
  private interrupted = false;
121
112
  private metrics: SequencerPublisherMetrics;
122
113
  public epochCache: EpochCache;
123
- private failedTxStore?: Promise<L1TxFailedStore | undefined>;
124
114
 
125
115
  protected governanceLog = createLogger('sequencer:publisher:governance');
126
116
  protected slashingLog = createLogger('sequencer:publisher:slashing');
@@ -132,15 +122,14 @@ export class SequencerPublisher {
132
122
 
133
123
  protected log: Logger;
134
124
  protected ethereumSlotDuration: bigint;
125
+ protected aztecSlotDuration: bigint;
126
+ private dateProvider: DateProvider;
135
127
 
136
128
  private blobClient: BlobClientInterface;
137
129
 
138
130
  /** Address to use for simulations in fisherman mode (actual proposer's address) */
139
131
  private proposerAddressForSimulation?: EthAddress;
140
132
 
141
- /** Optional callback to obtain a replacement publisher when the current one fails to send. */
142
- private getNextPublisher?: (excludeAddresses: EthAddress[]) => Promise<L1TxUtils | undefined>;
143
-
144
133
  /** L1 fee analyzer for fisherman mode */
145
134
  private l1FeeAnalyzer?: L1FeeAnalyzer;
146
135
 
@@ -164,8 +153,8 @@ export class SequencerPublisher {
164
153
  protected requests: RequestWithExpiry[] = [];
165
154
 
166
155
  constructor(
167
- private config: Pick<SequencerPublisherConfig, 'fishermanMode' | 'l1TxFailedStore'> &
168
- Pick<L1ContractsConfig, 'ethereumSlotDuration'> & { l1ChainId: number },
156
+ private config: Pick<SequencerPublisherConfig, 'fishermanMode'> &
157
+ Pick<L1ContractsConfig, 'ethereumSlotDuration' | 'aztecSlotDuration'> & { l1ChainId: number },
169
158
  deps: {
170
159
  telemetry?: TelemetryClient;
171
160
  blobClient: BlobClientInterface;
@@ -179,11 +168,12 @@ export class SequencerPublisher {
179
168
  metrics: SequencerPublisherMetrics;
180
169
  lastActions: Partial<Record<Action, SlotNumber>>;
181
170
  log?: Logger;
182
- getNextPublisher?: (excludeAddresses: EthAddress[]) => Promise<L1TxUtils | undefined>;
183
171
  },
184
172
  ) {
185
173
  this.log = deps.log ?? createLogger('sequencer:publisher');
186
174
  this.ethereumSlotDuration = BigInt(config.ethereumSlotDuration);
175
+ this.aztecSlotDuration = BigInt(config.aztecSlotDuration);
176
+ this.dateProvider = deps.dateProvider;
187
177
  this.epochCache = deps.epochCache;
188
178
  this.lastActions = deps.lastActions;
189
179
 
@@ -193,7 +183,6 @@ export class SequencerPublisher {
193
183
  this.metrics = deps.metrics ?? new SequencerPublisherMetrics(telemetry, 'SequencerPublisher');
194
184
  this.tracer = telemetry.getTracer('SequencerPublisher');
195
185
  this.l1TxUtils = deps.l1TxUtils;
196
- this.getNextPublisher = deps.getNextPublisher;
197
186
 
198
187
  this.rollupContract = deps.rollupContract;
199
188
 
@@ -222,31 +211,6 @@ export class SequencerPublisher {
222
211
  this.rollupContract,
223
212
  createLogger('sequencer:publisher:price-oracle'),
224
213
  );
225
-
226
- // Initialize failed L1 tx store (optional, for test networks)
227
- this.failedTxStore = createL1TxFailedStore(config.l1TxFailedStore, this.log);
228
- }
229
-
230
- /**
231
- * Backs up a failed L1 transaction to the configured store for debugging.
232
- * Does nothing if no store is configured.
233
- */
234
- private backupFailedTx(failedTx: Omit<FailedL1Tx, 'timestamp'>): void {
235
- if (!this.failedTxStore) {
236
- return;
237
- }
238
-
239
- const tx: FailedL1Tx = {
240
- ...failedTx,
241
- timestamp: Date.now(),
242
- };
243
-
244
- // Fire and forget - don't block on backup
245
- void this.failedTxStore
246
- .then(store => store?.saveFailedTx(tx))
247
- .catch(err => {
248
- this.log.warn(`Failed to backup failed L1 tx to store`, err);
249
- });
250
214
  }
251
215
 
252
216
  public getRollupContract(): RollupContract {
@@ -398,8 +362,8 @@ export class SequencerPublisher {
398
362
  // @note - we can only have one blob config per bundle
399
363
  // find requests with gas and blob configs
400
364
  // See https://github.com/AztecProtocol/aztec-packages/issues/11513
401
- const gasConfigs = requestsToProcess.filter(request => request.gasConfig).map(request => request.gasConfig);
402
- const blobConfigs = requestsToProcess.filter(request => request.blobConfig).map(request => request.blobConfig);
365
+ const gasConfigs = validRequests.filter(request => request.gasConfig).map(request => request.gasConfig);
366
+ const blobConfigs = validRequests.filter(request => request.blobConfig).map(request => request.blobConfig);
403
367
 
404
368
  if (blobConfigs.length > 1) {
405
369
  throw new Error('Multiple blob configs found');
@@ -428,36 +392,19 @@ export class SequencerPublisher {
428
392
  validRequests.sort((a, b) => compareActions(a.action, b.action));
429
393
 
430
394
  try {
431
- // Capture context for failed tx backup before sending
432
- const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
433
- const multicallData = encodeFunctionData({
434
- abi: multicall3Abi,
435
- functionName: 'aggregate3',
436
- args: [
437
- validRequests.map(r => ({
438
- target: r.request.to!,
439
- callData: r.request.data!,
440
- allowFailure: true,
441
- })),
442
- ],
443
- });
444
- const blobDataHex = blobConfig?.blobs?.map(b => toHex(b)) as Hex[] | undefined;
445
-
446
- const txContext = { multicallData, blobData: blobDataHex, l1BlockNumber };
447
-
448
395
  this.log.debug('Forwarding transactions', {
449
396
  validRequests: validRequests.map(request => request.action),
450
397
  txConfig,
451
398
  });
452
- const result = await this.forwardWithPublisherRotation(validRequests, txConfig, blobConfig);
453
- if (result === undefined) {
454
- return undefined;
455
- }
456
- const { successfulActions = [], failedActions = [] } = this.callbackBundledTransactions(
457
- validRequests,
458
- result,
459
- txContext,
399
+ const result = await Multicall3.forward(
400
+ validRequests.map(request => request.request),
401
+ this.l1TxUtils,
402
+ txConfig,
403
+ blobConfig,
404
+ this.rollupContract.address,
405
+ this.log,
460
406
  );
407
+ const { successfulActions = [], failedActions = [] } = this.callbackBundledTransactions(validRequests, result);
461
408
  return { result, expiredActions, sentActions: validActions, successfulActions, failedActions };
462
409
  } catch (err) {
463
410
  const viemError = formatViemError(err);
@@ -475,79 +422,25 @@ export class SequencerPublisher {
475
422
  }
476
423
  }
477
424
 
478
- /**
479
- * Forwards transactions via Multicall3, rotating to the next available publisher if a send
480
- * failure occurs (i.e. the tx never reached the chain).
481
- * On-chain reverts and simulation errors are returned as-is without rotation.
482
- */
483
- private async forwardWithPublisherRotation(
484
- validRequests: RequestWithExpiry[],
485
- txConfig: RequestWithExpiry['gasConfig'],
486
- blobConfig: L1BlobInputs | undefined,
487
- ) {
488
- const triedAddresses: EthAddress[] = [];
489
- let currentPublisher = this.l1TxUtils;
490
-
491
- while (true) {
492
- triedAddresses.push(currentPublisher.getSenderAddress());
493
- try {
494
- const result = await Multicall3.forward(
495
- validRequests.map(r => r.request),
496
- currentPublisher,
497
- txConfig,
498
- blobConfig,
499
- this.rollupContract.address,
500
- this.log,
501
- );
502
- this.l1TxUtils = currentPublisher;
503
- return result;
504
- } catch (err) {
505
- if (err instanceof TimeoutError) {
506
- throw err;
507
- }
508
- const viemError = formatViemError(err);
509
- if (!this.getNextPublisher) {
510
- this.log.error('Failed to publish bundled transactions', viemError);
511
- return undefined;
512
- }
513
- this.log.warn(
514
- `Publisher ${currentPublisher.getSenderAddress()} failed to send, rotating to next publisher`,
515
- viemError,
516
- );
517
- const nextPublisher = await this.getNextPublisher([...triedAddresses]);
518
- if (!nextPublisher) {
519
- this.log.error('All available publishers exhausted, failed to publish bundled transactions');
520
- return undefined;
521
- }
522
- currentPublisher = nextPublisher;
523
- }
524
- }
525
- }
526
-
527
425
  private callbackBundledTransactions(
528
426
  requests: RequestWithExpiry[],
529
- result: { receipt: TransactionReceipt; errorMsg?: string } | FormattedViemError | undefined,
530
- txContext: { multicallData: Hex; blobData?: Hex[]; l1BlockNumber: bigint },
427
+ result?: { receipt: TransactionReceipt } | FormattedViemError,
531
428
  ) {
532
429
  const actionsListStr = requests.map(r => r.action).join(', ');
533
430
  if (result instanceof FormattedViemError) {
534
431
  this.log.error(`Failed to publish bundled transactions (${actionsListStr})`, result);
535
- this.backupFailedTx({
536
- id: keccak256(txContext.multicallData),
537
- failureType: 'send-error',
538
- request: { to: MULTI_CALL_3_ADDRESS, data: txContext.multicallData },
539
- blobData: txContext.blobData,
540
- l1BlockNumber: txContext.l1BlockNumber.toString(),
541
- error: { message: result.message, name: result.name },
542
- context: {
543
- actions: requests.map(r => r.action),
544
- requests: requests.map(r => ({ action: r.action, to: r.request.to! as Hex, data: r.request.data! })),
545
- sender: this.getSenderAddress().toString(),
546
- },
547
- });
548
432
  return { failedActions: requests.map(r => r.action) };
549
433
  } else {
550
- this.log.verbose(`Published bundled transactions (${actionsListStr})`, { result, requests });
434
+ this.log.verbose(`Published bundled transactions (${actionsListStr})`, {
435
+ result,
436
+ requests: requests.map(r => ({
437
+ ...r,
438
+ // Avoid logging large blob data
439
+ blobConfig: r.blobConfig
440
+ ? { ...r.blobConfig, blobs: r.blobConfig.blobs.map(b => ({ size: trimmedBytesLength(b) })) }
441
+ : undefined,
442
+ })),
443
+ });
551
444
  const successfulActions: Action[] = [];
552
445
  const failedActions: Action[] = [];
553
446
  for (const request of requests) {
@@ -557,49 +450,29 @@ export class SequencerPublisher {
557
450
  failedActions.push(request.action);
558
451
  }
559
452
  }
560
- // Single backup for the whole reverted tx
561
- if (failedActions.length > 0 && result?.receipt?.status === 'reverted') {
562
- this.backupFailedTx({
563
- id: result.receipt.transactionHash,
564
- failureType: 'revert',
565
- request: { to: MULTI_CALL_3_ADDRESS, data: txContext.multicallData },
566
- blobData: txContext.blobData,
567
- l1BlockNumber: result.receipt.blockNumber.toString(),
568
- receipt: {
569
- transactionHash: result.receipt.transactionHash,
570
- blockNumber: result.receipt.blockNumber.toString(),
571
- gasUsed: (result.receipt.gasUsed ?? 0n).toString(),
572
- status: 'reverted',
573
- },
574
- error: { message: result.errorMsg ?? 'Transaction reverted' },
575
- context: {
576
- actions: failedActions,
577
- requests: requests
578
- .filter(r => failedActions.includes(r.action))
579
- .map(r => ({ action: r.action, to: r.request.to! as Hex, data: r.request.data! })),
580
- sender: this.getSenderAddress().toString(),
581
- },
582
- });
583
- }
584
453
  return { successfulActions, failedActions };
585
454
  }
586
455
  }
587
456
 
588
457
  /**
589
- * @notice Will call `canProposeAtNextEthBlock` to make sure that it is possible to propose
458
+ * @notice Will call `canProposeAt` to make sure that it is possible to propose
590
459
  * @param tipArchive - The archive to check
591
460
  * @returns The slot and block number if it is possible to propose, undefined otherwise
592
461
  */
593
- public canProposeAtNextEthBlock(
462
+ public canProposeAt(
594
463
  tipArchive: Fr,
595
464
  msgSender: EthAddress,
596
- opts: { forcePendingCheckpointNumber?: CheckpointNumber } = {},
465
+ opts: { forcePendingCheckpointNumber?: CheckpointNumber; pipelined?: boolean } = {},
597
466
  ) {
598
467
  // TODO: #14291 - should loop through multiple keys to check if any of them can propose
599
468
  const ignoredErrors = ['SlotAlreadyInChain', 'InvalidProposer', 'InvalidArchive'];
600
469
 
470
+ const pipelined = opts.pipelined ?? false;
471
+ const slotOffset = pipelined ? this.aztecSlotDuration : 0n;
472
+ const nextL1SlotTs = this.getNextL1SlotTimestamp() + slotOffset;
473
+
601
474
  return this.rollupContract
602
- .canProposeAtNextEthBlock(tipArchive.toBuffer(), msgSender.toString(), Number(this.ethereumSlotDuration), {
475
+ .canProposeAt(tipArchive.toBuffer(), msgSender.toString(), nextL1SlotTs, {
603
476
  forcePendingCheckpointNumber: opts.forcePendingCheckpointNumber,
604
477
  })
605
478
  .catch(err => {
@@ -636,7 +509,7 @@ export class SequencerPublisher {
636
509
  flags,
637
510
  ] as const;
638
511
 
639
- const ts = BigInt((await this.l1TxUtils.getBlock()).timestamp + this.ethereumSlotDuration);
512
+ const ts = this.getSimulationTimestamp(header.slotNumber);
640
513
  const stateOverrides = await this.rollupContract.makePendingCheckpointNumberOverride(
641
514
  opts?.forcePendingCheckpointNumber,
642
515
  );
@@ -659,7 +532,7 @@ export class SequencerPublisher {
659
532
  data: encodeFunctionData({ abi: RollupAbi, functionName: 'validateHeaderWithAttestations', args }),
660
533
  from: MULTI_CALL_3_ADDRESS,
661
534
  },
662
- { time: ts + 1n },
535
+ { time: ts },
663
536
  stateOverrides,
664
537
  );
665
538
  this.log.debug(`Simulated validateHeader`);
@@ -692,8 +565,6 @@ export class SequencerPublisher {
692
565
  const request = this.buildInvalidateCheckpointRequest(validationResult);
693
566
  this.log.debug(`Simulating invalidate checkpoint ${checkpointNumber}`, { ...logData, request });
694
567
 
695
- const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
696
-
697
568
  try {
698
569
  const { gasUsed } = await this.l1TxUtils.simulate(
699
570
  request,
@@ -745,18 +616,6 @@ export class SequencerPublisher {
745
616
 
746
617
  // Otherwise, throw. We cannot build the next checkpoint if we cannot invalidate the previous one.
747
618
  this.log.error(`Simulation for invalidate checkpoint ${checkpointNumber} failed`, viemError, logData);
748
- this.backupFailedTx({
749
- id: keccak256(request.data!),
750
- failureType: 'simulation',
751
- request: { to: request.to!, data: request.data!, value: request.value?.toString() },
752
- l1BlockNumber: l1BlockNumber.toString(),
753
- error: { message: viemError.message, name: viemError.name },
754
- context: {
755
- actions: [`invalidate-${reason}`],
756
- checkpointNumber,
757
- sender: this.getSenderAddress().toString(),
758
- },
759
- });
760
619
  throw new Error(`Failed to simulate invalidate checkpoint ${checkpointNumber}`, { cause: viemError });
761
620
  }
762
621
  }
@@ -800,8 +659,7 @@ export class SequencerPublisher {
800
659
  attestationsAndSigners: CommitteeAttestationsAndSigners,
801
660
  attestationsAndSignersSignature: Signature,
802
661
  options: { forcePendingCheckpointNumber?: CheckpointNumber },
803
- ): Promise<bigint> {
804
- const ts = BigInt((await this.l1TxUtils.getBlock()).timestamp + this.ethereumSlotDuration);
662
+ ): Promise<void> {
805
663
  const blobFields = checkpoint.toBlobFields();
806
664
  const blobs = await getBlobsPerL1Block(blobFields);
807
665
  const blobInput = getPrefixedEthBlobCommitments(blobs);
@@ -820,13 +678,11 @@ export class SequencerPublisher {
820
678
  blobInput,
821
679
  ] as const;
822
680
 
823
- await this.simulateProposeTx(args, ts, options);
824
- return ts;
681
+ await this.simulateProposeTx(args, options);
825
682
  }
826
683
 
827
684
  private async enqueueCastSignalHelper(
828
685
  slotNumber: SlotNumber,
829
- timestamp: bigint,
830
686
  signalType: GovernanceSignalAction,
831
687
  payload: EthAddress,
832
688
  base: IEmpireBase,
@@ -904,25 +760,15 @@ export class SequencerPublisher {
904
760
  lastValidL2Slot: slotNumber,
905
761
  });
906
762
 
907
- const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
763
+ const timestamp = this.getSimulationTimestamp(slotNumber);
908
764
 
909
765
  try {
910
766
  await this.l1TxUtils.simulate(request, { time: timestamp }, [], mergeAbis([request.abi ?? [], ErrorsAbi]));
911
767
  this.log.debug(`Simulation for ${action} at slot ${slotNumber} succeeded`, { request });
912
768
  } catch (err) {
913
769
  const viemError = formatViemError(err);
914
- this.log.error(`Failed simulation for ${action} at slot ${slotNumber} (enqueuing the action anyway)`, viemError);
915
- this.backupFailedTx({
916
- id: keccak256(request.data!),
917
- failureType: 'simulation',
918
- request: { to: request.to!, data: request.data!, value: request.value?.toString() },
919
- l1BlockNumber: l1BlockNumber.toString(),
920
- error: { message: viemError.message, name: viemError.name },
921
- context: {
922
- actions: [action],
923
- slot: slotNumber,
924
- sender: this.getSenderAddress().toString(),
925
- },
770
+ this.log.error(`Failed simulation for ${action} at slot ${slotNumber} (enqueuing the action anyway)`, viemError, {
771
+ simulationTimestamp: timestamp,
926
772
  });
927
773
  // Yes, we enqueue the request anyway, in case there was a bug with the simulation itself
928
774
  }
@@ -974,19 +820,16 @@ export class SequencerPublisher {
974
820
  /**
975
821
  * Enqueues a governance castSignal transaction to cast a signal for a given slot number.
976
822
  * @param slotNumber - The slot number to cast a signal for.
977
- * @param timestamp - The timestamp of the slot to cast a signal for.
978
823
  * @returns True if the signal was successfully enqueued, false otherwise.
979
824
  */
980
825
  public enqueueGovernanceCastSignal(
981
826
  governancePayload: EthAddress,
982
827
  slotNumber: SlotNumber,
983
- timestamp: bigint,
984
828
  signerAddress: EthAddress,
985
829
  signer: (msg: TypedDataDefinition) => Promise<`0x${string}`>,
986
830
  ): Promise<boolean> {
987
831
  return this.enqueueCastSignalHelper(
988
832
  slotNumber,
989
- timestamp,
990
833
  'governance-signal',
991
834
  governancePayload,
992
835
  this.govProposerContract,
@@ -999,7 +842,6 @@ export class SequencerPublisher {
999
842
  public async enqueueSlashingActions(
1000
843
  actions: ProposerSlashAction[],
1001
844
  slotNumber: SlotNumber,
1002
- timestamp: bigint,
1003
845
  signerAddress: EthAddress,
1004
846
  signer: (msg: TypedDataDefinition) => Promise<`0x${string}`>,
1005
847
  ): Promise<boolean> {
@@ -1020,7 +862,6 @@ export class SequencerPublisher {
1020
862
  });
1021
863
  await this.enqueueCastSignalHelper(
1022
864
  slotNumber,
1023
- timestamp,
1024
865
  'empire-slashing-signal',
1025
866
  action.payload,
1026
867
  this.slashingProposerContract,
@@ -1039,7 +880,6 @@ export class SequencerPublisher {
1039
880
  (receipt: TransactionReceipt) =>
1040
881
  !!this.slashFactoryContract.tryExtractSlashPayloadCreatedEvent(receipt.logs),
1041
882
  slotNumber,
1042
- timestamp,
1043
883
  );
1044
884
  break;
1045
885
  }
@@ -1057,7 +897,6 @@ export class SequencerPublisher {
1057
897
  request,
1058
898
  (receipt: TransactionReceipt) => !!empireSlashingProposer.tryExtractPayloadSubmittedEvent(receipt.logs),
1059
899
  slotNumber,
1060
- timestamp,
1061
900
  );
1062
901
  break;
1063
902
  }
@@ -1081,7 +920,6 @@ export class SequencerPublisher {
1081
920
  request,
1082
921
  (receipt: TransactionReceipt) => !!tallySlashingProposer.tryExtractVoteCastEvent(receipt.logs),
1083
922
  slotNumber,
1084
- timestamp,
1085
923
  );
1086
924
  break;
1087
925
  }
@@ -1103,7 +941,6 @@ export class SequencerPublisher {
1103
941
  request,
1104
942
  (receipt: TransactionReceipt) => !!tallySlashingProposer.tryExtractRoundExecutedEvent(receipt.logs),
1105
943
  slotNumber,
1106
- timestamp,
1107
944
  );
1108
945
  break;
1109
946
  }
@@ -1139,15 +976,13 @@ export class SequencerPublisher {
1139
976
  feeAssetPriceModifier: checkpoint.feeAssetPriceModifier,
1140
977
  };
1141
978
 
1142
- let ts: bigint;
1143
-
1144
979
  try {
1145
980
  // @note This will make sure that we are passing the checks for our header ASSUMING that the data is also made available
1146
981
  // This means that we can avoid the simulation issues in later checks.
1147
982
  // By simulation issue, I mean the fact that the block.timestamp is equal to the last block, not the next, which
1148
983
  // make time consistency checks break.
1149
984
  // TODO(palla): Check whether we're validating twice, once here and once within addProposeTx, since we call simulateProposeTx in both places.
1150
- ts = await this.validateCheckpointForSubmission(
985
+ await this.validateCheckpointForSubmission(
1151
986
  checkpoint,
1152
987
  attestationsAndSigners,
1153
988
  attestationsAndSignersSignature,
@@ -1163,7 +998,7 @@ export class SequencerPublisher {
1163
998
  }
1164
999
 
1165
1000
  this.log.verbose(`Enqueuing checkpoint propose transaction`, { ...checkpoint.toCheckpointInfo(), ...opts });
1166
- await this.addProposeTx(checkpoint, proposeTxArgs, opts, ts);
1001
+ await this.addProposeTx(checkpoint, proposeTxArgs, opts);
1167
1002
  }
1168
1003
 
1169
1004
  public enqueueInvalidateCheckpoint(
@@ -1206,8 +1041,8 @@ export class SequencerPublisher {
1206
1041
  request: L1TxRequest,
1207
1042
  checkSuccess: (receipt: TransactionReceipt) => boolean | undefined,
1208
1043
  slotNumber: SlotNumber,
1209
- timestamp: bigint,
1210
1044
  ) {
1045
+ const timestamp = this.getSimulationTimestamp(slotNumber);
1211
1046
  const logData = { slotNumber, timestamp, gasLimit: undefined as bigint | undefined };
1212
1047
  if (this.lastActions[action] && this.lastActions[action] === slotNumber) {
1213
1048
  this.log.debug(`Skipping duplicate action ${action} for slot ${slotNumber}`);
@@ -1219,30 +1054,16 @@ export class SequencerPublisher {
1219
1054
 
1220
1055
  this.log.debug(`Simulating ${action} for slot ${slotNumber}`, logData);
1221
1056
 
1222
- const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
1223
-
1224
1057
  let gasUsed: bigint;
1225
1058
  const simulateAbi = mergeAbis([request.abi ?? [], ErrorsAbi]);
1059
+
1226
1060
  try {
1227
- ({ gasUsed } = await this.l1TxUtils.simulate(request, { time: timestamp }, [], simulateAbi)); // TODO(palla/slash): Check the timestamp logic
1061
+ ({ gasUsed } = await this.l1TxUtils.simulate(request, { time: timestamp }, [], simulateAbi));
1228
1062
  this.log.verbose(`Simulation for ${action} succeeded`, { ...logData, request, gasUsed });
1229
1063
  } catch (err) {
1230
1064
  const viemError = formatViemError(err, simulateAbi);
1231
1065
  this.log.error(`Simulation for ${action} at ${slotNumber} failed`, viemError, logData);
1232
1066
 
1233
- this.backupFailedTx({
1234
- id: keccak256(request.data!),
1235
- failureType: 'simulation',
1236
- request: { to: request.to!, data: request.data!, value: request.value?.toString() },
1237
- l1BlockNumber: l1BlockNumber.toString(),
1238
- error: { message: viemError.message, name: viemError.name },
1239
- context: {
1240
- actions: [action],
1241
- slot: slotNumber,
1242
- sender: this.getSenderAddress().toString(),
1243
- },
1244
- });
1245
-
1246
1067
  return false;
1247
1068
  }
1248
1069
 
@@ -1293,7 +1114,6 @@ export class SequencerPublisher {
1293
1114
 
1294
1115
  private async prepareProposeTx(
1295
1116
  encodedData: L1ProcessArgs,
1296
- timestamp: bigint,
1297
1117
  options: { forcePendingCheckpointNumber?: CheckpointNumber },
1298
1118
  ) {
1299
1119
  const kzg = Blob.getViemKzgInstance();
@@ -1326,27 +1146,9 @@ export class SequencerPublisher {
1326
1146
  kzg,
1327
1147
  },
1328
1148
  )
1329
- .catch(async err => {
1330
- const viemError = formatViemError(err);
1331
- this.log.error(`Failed to validate blobs`, viemError.message, { metaMessages: viemError.metaMessages });
1332
- const validateBlobsData = encodeFunctionData({
1333
- abi: RollupAbi,
1334
- functionName: 'validateBlobs',
1335
- args: [blobInput],
1336
- });
1337
- const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
1338
- this.backupFailedTx({
1339
- id: keccak256(validateBlobsData),
1340
- failureType: 'simulation',
1341
- request: { to: this.rollupContract.address as Hex, data: validateBlobsData },
1342
- blobData: encodedData.blobs.map(b => toHex(b.data)) as Hex[],
1343
- l1BlockNumber: l1BlockNumber.toString(),
1344
- error: { message: viemError.message, name: viemError.name },
1345
- context: {
1346
- actions: ['validate-blobs'],
1347
- sender: this.getSenderAddress().toString(),
1348
- },
1349
- });
1149
+ .catch(err => {
1150
+ const { message, metaMessages } = formatViemError(err);
1151
+ this.log.error(`Failed to validate blobs`, message, { metaMessages });
1350
1152
  throw new Error('Failed to validate blobs');
1351
1153
  });
1352
1154
  }
@@ -1366,7 +1168,7 @@ export class SequencerPublisher {
1366
1168
  blobInput,
1367
1169
  ] as const;
1368
1170
 
1369
- const { rollupData, simulationResult } = await this.simulateProposeTx(args, timestamp, options);
1171
+ const { rollupData, simulationResult } = await this.simulateProposeTx(args, options);
1370
1172
 
1371
1173
  return { args, blobEvaluationGas, rollupData, simulationResult };
1372
1174
  }
@@ -1374,7 +1176,6 @@ export class SequencerPublisher {
1374
1176
  /**
1375
1177
  * Simulates the propose tx with eth_simulateV1
1376
1178
  * @param args - The propose tx args
1377
- * @param timestamp - The timestamp to simulate proposal at
1378
1179
  * @returns The simulation result
1379
1180
  */
1380
1181
  private async simulateProposeTx(
@@ -1391,7 +1192,6 @@ export class SequencerPublisher {
1391
1192
  ViemSignature,
1392
1193
  `0x${string}`,
1393
1194
  ],
1394
- timestamp: bigint,
1395
1195
  options: { forcePendingCheckpointNumber?: CheckpointNumber },
1396
1196
  ) {
1397
1197
  const rollupData = encodeFunctionData({
@@ -1425,7 +1225,7 @@ export class SequencerPublisher {
1425
1225
  });
1426
1226
  }
1427
1227
 
1428
- const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
1228
+ const simTs = this.getSimulationTimestamp(SlotNumber.fromBigInt(args[0].header.slotNumber));
1429
1229
 
1430
1230
  const simulationResult = await this.l1TxUtils
1431
1231
  .simulate(
@@ -1436,8 +1236,7 @@ export class SequencerPublisher {
1436
1236
  ...(this.proposerAddressForSimulation && { from: this.proposerAddressForSimulation.toString() }),
1437
1237
  },
1438
1238
  {
1439
- // @note we add 1n to the timestamp because geth implementation doesn't like simulation timestamp to be equal to the current block timestamp
1440
- time: timestamp + 1n,
1239
+ time: simTs,
1441
1240
  // @note reth should have a 30m gas limit per block but throws errors that this tx is beyond limit so we increase here
1442
1241
  gasLimit: MAX_L1_TX_LIMIT * 2n,
1443
1242
  },
@@ -1459,19 +1258,7 @@ export class SequencerPublisher {
1459
1258
  logs: [],
1460
1259
  };
1461
1260
  }
1462
- this.log.error(`Failed to simulate propose tx`, viemError);
1463
- this.backupFailedTx({
1464
- id: keccak256(rollupData),
1465
- failureType: 'simulation',
1466
- request: { to: this.rollupContract.address, data: rollupData },
1467
- l1BlockNumber: l1BlockNumber.toString(),
1468
- error: { message: viemError.message, name: viemError.name },
1469
- context: {
1470
- actions: ['propose'],
1471
- slot: Number(args[0].header.slotNumber),
1472
- sender: this.getSenderAddress().toString(),
1473
- },
1474
- });
1261
+ this.log.error(`Failed to simulate propose tx`, viemError, { simulationTimestamp: simTs });
1475
1262
  throw err;
1476
1263
  });
1477
1264
 
@@ -1482,16 +1269,11 @@ export class SequencerPublisher {
1482
1269
  checkpoint: Checkpoint,
1483
1270
  encodedData: L1ProcessArgs,
1484
1271
  opts: { txTimeoutAt?: Date; forcePendingCheckpointNumber?: CheckpointNumber } = {},
1485
- timestamp: bigint,
1486
1272
  ): Promise<void> {
1487
1273
  const slot = checkpoint.header.slotNumber;
1488
1274
  const timer = new Timer();
1489
1275
  const kzg = Blob.getViemKzgInstance();
1490
- const { rollupData, simulationResult, blobEvaluationGas } = await this.prepareProposeTx(
1491
- encodedData,
1492
- timestamp,
1493
- opts,
1494
- );
1276
+ const { rollupData, simulationResult, blobEvaluationGas } = await this.prepareProposeTx(encodedData, opts);
1495
1277
  const startBlock = await this.l1TxUtils.getBlockNumber();
1496
1278
  const gasLimit = this.l1TxUtils.bumpGasLimit(
1497
1279
  BigInt(Math.ceil((Number(simulationResult.gasUsed) * 64) / 63)) +
@@ -1567,4 +1349,17 @@ export class SequencerPublisher {
1567
1349
  },
1568
1350
  });
1569
1351
  }
1352
+
1353
+ /** Returns the timestamp of the last L1 slot within a given L2 slot. Used as the simulation timestamp
1354
+ * for eth_simulateV1 calls, since it's guaranteed to be greater than any L1 block produced during the slot. */
1355
+ private getSimulationTimestamp(slot: SlotNumber): bigint {
1356
+ const l1Constants = this.epochCache.getL1Constants();
1357
+ return getLastL1SlotTimestampForL2Slot(slot, l1Constants);
1358
+ }
1359
+
1360
+ /** Returns the timestamp of the next L1 slot boundary after now. */
1361
+ private getNextL1SlotTimestamp(): bigint {
1362
+ const l1Constants = this.epochCache.getL1Constants();
1363
+ return getNextL1SlotTimestamp(this.dateProvider.nowInSeconds(), l1Constants);
1364
+ }
1570
1365
  }