@aztec/sequencer-client 0.0.1-commit.88e6f9396 → 0.0.1-commit.8c0b8ff

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 (70) hide show
  1. package/dest/client/sequencer-client.d.ts +5 -1
  2. package/dest/client/sequencer-client.d.ts.map +1 -1
  3. package/dest/client/sequencer-client.js +6 -12
  4. package/dest/config.d.ts +3 -3
  5. package/dest/config.d.ts.map +1 -1
  6. package/dest/config.js +1 -2
  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 -22
  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 +11 -16
  21. package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
  22. package/dest/publisher/sequencer-publisher.js +53 -289
  23. package/dest/sequencer/checkpoint_proposal_job.d.ts +4 -10
  24. package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -1
  25. package/dest/sequencer/checkpoint_proposal_job.js +75 -117
  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/events.d.ts +1 -2
  30. package/dest/sequencer/events.d.ts.map +1 -1
  31. package/dest/sequencer/metrics.d.ts +1 -5
  32. package/dest/sequencer/metrics.d.ts.map +1 -1
  33. package/dest/sequencer/metrics.js +0 -11
  34. package/dest/sequencer/sequencer.d.ts +5 -4
  35. package/dest/sequencer/sequencer.d.ts.map +1 -1
  36. package/dest/sequencer/sequencer.js +50 -59
  37. package/dest/test/mock_checkpoint_builder.d.ts +1 -1
  38. package/dest/test/mock_checkpoint_builder.d.ts.map +1 -1
  39. package/dest/test/mock_checkpoint_builder.js +2 -0
  40. package/package.json +28 -27
  41. package/src/client/sequencer-client.ts +9 -11
  42. package/src/config.ts +0 -4
  43. package/src/global_variable_builder/global_builder.ts +22 -24
  44. package/src/global_variable_builder/index.ts +1 -1
  45. package/src/publisher/config.ts +0 -9
  46. package/src/publisher/index.ts +0 -3
  47. package/src/publisher/sequencer-publisher-factory.ts +0 -15
  48. package/src/publisher/sequencer-publisher.ts +51 -274
  49. package/src/sequencer/checkpoint_proposal_job.ts +66 -139
  50. package/src/sequencer/checkpoint_voter.ts +1 -12
  51. package/src/sequencer/events.ts +1 -1
  52. package/src/sequencer/metrics.ts +0 -14
  53. package/src/sequencer/sequencer.ts +58 -84
  54. package/src/test/mock_checkpoint_builder.ts +2 -0
  55. package/dest/publisher/l1_tx_failed_store/factory.d.ts +0 -11
  56. package/dest/publisher/l1_tx_failed_store/factory.d.ts.map +0 -1
  57. package/dest/publisher/l1_tx_failed_store/factory.js +0 -22
  58. package/dest/publisher/l1_tx_failed_store/failed_tx_store.d.ts +0 -59
  59. package/dest/publisher/l1_tx_failed_store/failed_tx_store.d.ts.map +0 -1
  60. package/dest/publisher/l1_tx_failed_store/failed_tx_store.js +0 -1
  61. package/dest/publisher/l1_tx_failed_store/file_store_failed_tx_store.d.ts +0 -15
  62. package/dest/publisher/l1_tx_failed_store/file_store_failed_tx_store.d.ts.map +0 -1
  63. package/dest/publisher/l1_tx_failed_store/file_store_failed_tx_store.js +0 -34
  64. package/dest/publisher/l1_tx_failed_store/index.d.ts +0 -4
  65. package/dest/publisher/l1_tx_failed_store/index.d.ts.map +0 -1
  66. package/dest/publisher/l1_tx_failed_store/index.js +0 -2
  67. package/src/publisher/l1_tx_failed_store/factory.ts +0 -32
  68. package/src/publisher/l1_tx_failed_store/failed_tx_store.ts +0 -55
  69. package/src/publisher/l1_tx_failed_store/file_store_failed_tx_store.ts +0 -46
  70. package/src/publisher/l1_tx_failed_store/index.ts +0 -3
@@ -31,7 +31,6 @@ import { CheckpointNumber, SlotNumber } from '@aztec/foundation/branded-types';
31
31
  import { trimmedBytesLength } from '@aztec/foundation/buffer';
32
32
  import { pick } from '@aztec/foundation/collection';
33
33
  import type { Fr } from '@aztec/foundation/curves/bn254';
34
- import { TimeoutError } from '@aztec/foundation/error';
35
34
  import { EthAddress } from '@aztec/foundation/eth-address';
36
35
  import { Signature, type ViemSignature } from '@aztec/foundation/eth-signature';
37
36
  import { type Logger, createLogger } from '@aztec/foundation/log';
@@ -42,24 +41,15 @@ import { EmpireBaseAbi, ErrorsAbi, RollupAbi } from '@aztec/l1-artifacts';
42
41
  import { type ProposerSlashAction, encodeSlashConsensusVotes } from '@aztec/slasher';
43
42
  import { CommitteeAttestationsAndSigners, type ValidateCheckpointResult } from '@aztec/stdlib/block';
44
43
  import type { Checkpoint } from '@aztec/stdlib/checkpoint';
44
+ import { getLastL1SlotTimestampForL2Slot, getNextL1SlotTimestamp } from '@aztec/stdlib/epoch-helpers';
45
45
  import { SlashFactoryContract } from '@aztec/stdlib/l1-contracts';
46
46
  import type { CheckpointHeader } from '@aztec/stdlib/rollup';
47
47
  import type { L1PublishCheckpointStats } from '@aztec/stdlib/stats';
48
48
  import { type TelemetryClient, type Tracer, getTelemetryClient, trackSpan } from '@aztec/telemetry-client';
49
49
 
50
- import {
51
- type Hex,
52
- type StateOverride,
53
- type TransactionReceipt,
54
- type TypedDataDefinition,
55
- encodeFunctionData,
56
- keccak256,
57
- multicall3Abi,
58
- toHex,
59
- } from 'viem';
50
+ import { type StateOverride, type TransactionReceipt, type TypedDataDefinition, encodeFunctionData, toHex } from 'viem';
60
51
 
61
52
  import type { SequencerPublisherConfig } from './config.js';
62
- import { type FailedL1Tx, type L1TxFailedStore, createL1TxFailedStore } from './l1_tx_failed_store/index.js';
63
53
  import { SequencerPublisherMetrics } from './sequencer-publisher-metrics.js';
64
54
 
65
55
  /** Arguments to the process method of the rollup contract */
@@ -121,7 +111,6 @@ export class SequencerPublisher {
121
111
  private interrupted = false;
122
112
  private metrics: SequencerPublisherMetrics;
123
113
  public epochCache: EpochCache;
124
- private failedTxStore?: Promise<L1TxFailedStore | undefined>;
125
114
 
126
115
  protected governanceLog = createLogger('sequencer:publisher:governance');
127
116
  protected slashingLog = createLogger('sequencer:publisher:slashing');
@@ -134,15 +123,13 @@ export class SequencerPublisher {
134
123
  protected log: Logger;
135
124
  protected ethereumSlotDuration: bigint;
136
125
  protected aztecSlotDuration: bigint;
126
+ private dateProvider: DateProvider;
137
127
 
138
128
  private blobClient: BlobClientInterface;
139
129
 
140
130
  /** Address to use for simulations in fisherman mode (actual proposer's address) */
141
131
  private proposerAddressForSimulation?: EthAddress;
142
132
 
143
- /** Optional callback to obtain a replacement publisher when the current one fails to send. */
144
- private getNextPublisher?: (excludeAddresses: EthAddress[]) => Promise<L1TxUtils | undefined>;
145
-
146
133
  /** L1 fee analyzer for fisherman mode */
147
134
  private l1FeeAnalyzer?: L1FeeAnalyzer;
148
135
 
@@ -166,7 +153,7 @@ export class SequencerPublisher {
166
153
  protected requests: RequestWithExpiry[] = [];
167
154
 
168
155
  constructor(
169
- private config: Pick<SequencerPublisherConfig, 'fishermanMode' | 'l1TxFailedStore'> &
156
+ private config: Pick<SequencerPublisherConfig, 'fishermanMode'> &
170
157
  Pick<L1ContractsConfig, 'ethereumSlotDuration' | 'aztecSlotDuration'> & { l1ChainId: number },
171
158
  deps: {
172
159
  telemetry?: TelemetryClient;
@@ -181,12 +168,12 @@ export class SequencerPublisher {
181
168
  metrics: SequencerPublisherMetrics;
182
169
  lastActions: Partial<Record<Action, SlotNumber>>;
183
170
  log?: Logger;
184
- getNextPublisher?: (excludeAddresses: EthAddress[]) => Promise<L1TxUtils | undefined>;
185
171
  },
186
172
  ) {
187
173
  this.log = deps.log ?? createLogger('sequencer:publisher');
188
174
  this.ethereumSlotDuration = BigInt(config.ethereumSlotDuration);
189
175
  this.aztecSlotDuration = BigInt(config.aztecSlotDuration);
176
+ this.dateProvider = deps.dateProvider;
190
177
  this.epochCache = deps.epochCache;
191
178
  this.lastActions = deps.lastActions;
192
179
 
@@ -196,7 +183,6 @@ export class SequencerPublisher {
196
183
  this.metrics = deps.metrics ?? new SequencerPublisherMetrics(telemetry, 'SequencerPublisher');
197
184
  this.tracer = telemetry.getTracer('SequencerPublisher');
198
185
  this.l1TxUtils = deps.l1TxUtils;
199
- this.getNextPublisher = deps.getNextPublisher;
200
186
 
201
187
  this.rollupContract = deps.rollupContract;
202
188
 
@@ -225,31 +211,6 @@ export class SequencerPublisher {
225
211
  this.rollupContract,
226
212
  createLogger('sequencer:publisher:price-oracle'),
227
213
  );
228
-
229
- // Initialize failed L1 tx store (optional, for test networks)
230
- this.failedTxStore = createL1TxFailedStore(config.l1TxFailedStore, this.log);
231
- }
232
-
233
- /**
234
- * Backs up a failed L1 transaction to the configured store for debugging.
235
- * Does nothing if no store is configured.
236
- */
237
- private backupFailedTx(failedTx: Omit<FailedL1Tx, 'timestamp'>): void {
238
- if (!this.failedTxStore) {
239
- return;
240
- }
241
-
242
- const tx: FailedL1Tx = {
243
- ...failedTx,
244
- timestamp: Date.now(),
245
- };
246
-
247
- // Fire and forget - don't block on backup
248
- void this.failedTxStore
249
- .then(store => store?.saveFailedTx(tx))
250
- .catch(err => {
251
- this.log.warn(`Failed to backup failed L1 tx to store`, err);
252
- });
253
214
  }
254
215
 
255
216
  public getRollupContract(): RollupContract {
@@ -288,7 +249,7 @@ export class SequencerPublisher {
288
249
  }
289
250
 
290
251
  public getCurrentL2Slot(): SlotNumber {
291
- return this.epochCache.getSlotNow();
252
+ return this.epochCache.getEpochAndSlotNow().slot;
292
253
  }
293
254
 
294
255
  /**
@@ -431,36 +392,19 @@ export class SequencerPublisher {
431
392
  validRequests.sort((a, b) => compareActions(a.action, b.action));
432
393
 
433
394
  try {
434
- // Capture context for failed tx backup before sending
435
- const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
436
- const multicallData = encodeFunctionData({
437
- abi: multicall3Abi,
438
- functionName: 'aggregate3',
439
- args: [
440
- validRequests.map(r => ({
441
- target: r.request.to!,
442
- callData: r.request.data!,
443
- allowFailure: true,
444
- })),
445
- ],
446
- });
447
- const blobDataHex = blobConfig?.blobs?.map(b => toHex(b)) as Hex[] | undefined;
448
-
449
- const txContext = { multicallData, blobData: blobDataHex, l1BlockNumber };
450
-
451
395
  this.log.debug('Forwarding transactions', {
452
396
  validRequests: validRequests.map(request => request.action),
453
397
  txConfig,
454
398
  });
455
- const result = await this.forwardWithPublisherRotation(validRequests, txConfig, blobConfig);
456
- if (result === undefined) {
457
- return undefined;
458
- }
459
- const { successfulActions = [], failedActions = [] } = this.callbackBundledTransactions(
460
- validRequests,
461
- result,
462
- 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,
463
406
  );
407
+ const { successfulActions = [], failedActions = [] } = this.callbackBundledTransactions(validRequests, result);
464
408
  return { result, expiredActions, sentActions: validActions, successfulActions, failedActions };
465
409
  } catch (err) {
466
410
  const viemError = formatViemError(err);
@@ -478,76 +422,13 @@ export class SequencerPublisher {
478
422
  }
479
423
  }
480
424
 
481
- /**
482
- * Forwards transactions via Multicall3, rotating to the next available publisher if a send
483
- * failure occurs (i.e. the tx never reached the chain).
484
- * On-chain reverts and simulation errors are returned as-is without rotation.
485
- */
486
- private async forwardWithPublisherRotation(
487
- validRequests: RequestWithExpiry[],
488
- txConfig: RequestWithExpiry['gasConfig'],
489
- blobConfig: L1BlobInputs | undefined,
490
- ) {
491
- const triedAddresses: EthAddress[] = [];
492
- let currentPublisher = this.l1TxUtils;
493
-
494
- while (true) {
495
- triedAddresses.push(currentPublisher.getSenderAddress());
496
- try {
497
- const result = await Multicall3.forward(
498
- validRequests.map(r => r.request),
499
- currentPublisher,
500
- txConfig,
501
- blobConfig,
502
- this.rollupContract.address,
503
- this.log,
504
- );
505
- this.l1TxUtils = currentPublisher;
506
- return result;
507
- } catch (err) {
508
- if (err instanceof TimeoutError) {
509
- throw err;
510
- }
511
- const viemError = formatViemError(err);
512
- if (!this.getNextPublisher) {
513
- this.log.error('Failed to publish bundled transactions', viemError);
514
- return undefined;
515
- }
516
- this.log.warn(
517
- `Publisher ${currentPublisher.getSenderAddress()} failed to send, rotating to next publisher`,
518
- viemError,
519
- );
520
- const nextPublisher = await this.getNextPublisher([...triedAddresses]);
521
- if (!nextPublisher) {
522
- this.log.error('All available publishers exhausted, failed to publish bundled transactions');
523
- return undefined;
524
- }
525
- currentPublisher = nextPublisher;
526
- }
527
- }
528
- }
529
-
530
425
  private callbackBundledTransactions(
531
426
  requests: RequestWithExpiry[],
532
- result: { receipt: TransactionReceipt; errorMsg?: string } | FormattedViemError | undefined,
533
- txContext: { multicallData: Hex; blobData?: Hex[]; l1BlockNumber: bigint },
427
+ result?: { receipt: TransactionReceipt } | FormattedViemError,
534
428
  ) {
535
429
  const actionsListStr = requests.map(r => r.action).join(', ');
536
430
  if (result instanceof FormattedViemError) {
537
431
  this.log.error(`Failed to publish bundled transactions (${actionsListStr})`, result);
538
- this.backupFailedTx({
539
- id: keccak256(txContext.multicallData),
540
- failureType: 'send-error',
541
- request: { to: MULTI_CALL_3_ADDRESS, data: txContext.multicallData },
542
- blobData: txContext.blobData,
543
- l1BlockNumber: txContext.l1BlockNumber.toString(),
544
- error: { message: result.message, name: result.name },
545
- context: {
546
- actions: requests.map(r => r.action),
547
- requests: requests.map(r => ({ action: r.action, to: r.request.to! as Hex, data: r.request.data! })),
548
- sender: this.getSenderAddress().toString(),
549
- },
550
- });
551
432
  return { failedActions: requests.map(r => r.action) };
552
433
  } else {
553
434
  this.log.verbose(`Published bundled transactions (${actionsListStr})`, {
@@ -569,30 +450,6 @@ export class SequencerPublisher {
569
450
  failedActions.push(request.action);
570
451
  }
571
452
  }
572
- // Single backup for the whole reverted tx
573
- if (failedActions.length > 0 && result?.receipt?.status === 'reverted') {
574
- this.backupFailedTx({
575
- id: result.receipt.transactionHash,
576
- failureType: 'revert',
577
- request: { to: MULTI_CALL_3_ADDRESS, data: txContext.multicallData },
578
- blobData: txContext.blobData,
579
- l1BlockNumber: result.receipt.blockNumber.toString(),
580
- receipt: {
581
- transactionHash: result.receipt.transactionHash,
582
- blockNumber: result.receipt.blockNumber.toString(),
583
- gasUsed: (result.receipt.gasUsed ?? 0n).toString(),
584
- status: 'reverted',
585
- },
586
- error: { message: result.errorMsg ?? 'Transaction reverted' },
587
- context: {
588
- actions: failedActions,
589
- requests: requests
590
- .filter(r => failedActions.includes(r.action))
591
- .map(r => ({ action: r.action, to: r.request.to! as Hex, data: r.request.data! })),
592
- sender: this.getSenderAddress().toString(),
593
- },
594
- });
595
- }
596
453
  return { successfulActions, failedActions };
597
454
  }
598
455
  }
@@ -610,11 +467,12 @@ export class SequencerPublisher {
610
467
  // TODO: #14291 - should loop through multiple keys to check if any of them can propose
611
468
  const ignoredErrors = ['SlotAlreadyInChain', 'InvalidProposer', 'InvalidArchive'];
612
469
 
613
- const pipelined = opts.pipelined ?? this.epochCache.isProposerPipeliningEnabled();
470
+ const pipelined = opts.pipelined ?? false;
614
471
  const slotOffset = pipelined ? this.aztecSlotDuration : 0n;
472
+ const nextL1SlotTs = this.getNextL1SlotTimestamp() + slotOffset;
615
473
 
616
474
  return this.rollupContract
617
- .canProposeAt(tipArchive.toBuffer(), msgSender.toString(), this.ethereumSlotDuration, slotOffset, {
475
+ .canProposeAt(tipArchive.toBuffer(), msgSender.toString(), nextL1SlotTs, {
618
476
  forcePendingCheckpointNumber: opts.forcePendingCheckpointNumber,
619
477
  })
620
478
  .catch(err => {
@@ -628,7 +486,6 @@ export class SequencerPublisher {
628
486
  return undefined;
629
487
  });
630
488
  }
631
-
632
489
  /**
633
490
  * @notice Will simulate `validateHeader` to make sure that the block header is valid
634
491
  * @dev This is a convenience function that can be used by the sequencer to validate a "partial" header.
@@ -652,7 +509,7 @@ export class SequencerPublisher {
652
509
  flags,
653
510
  ] as const;
654
511
 
655
- const ts = BigInt((await this.l1TxUtils.getBlock()).timestamp + this.ethereumSlotDuration);
512
+ const ts = this.getSimulationTimestamp(header.slotNumber);
656
513
  const stateOverrides = await this.rollupContract.makePendingCheckpointNumberOverride(
657
514
  opts?.forcePendingCheckpointNumber,
658
515
  );
@@ -675,7 +532,7 @@ export class SequencerPublisher {
675
532
  data: encodeFunctionData({ abi: RollupAbi, functionName: 'validateHeaderWithAttestations', args }),
676
533
  from: MULTI_CALL_3_ADDRESS,
677
534
  },
678
- { time: ts + 1n },
535
+ { time: ts },
679
536
  stateOverrides,
680
537
  );
681
538
  this.log.debug(`Simulated validateHeader`);
@@ -708,8 +565,6 @@ export class SequencerPublisher {
708
565
  const request = this.buildInvalidateCheckpointRequest(validationResult);
709
566
  this.log.debug(`Simulating invalidate checkpoint ${checkpointNumber}`, { ...logData, request });
710
567
 
711
- const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
712
-
713
568
  try {
714
569
  const { gasUsed } = await this.l1TxUtils.simulate(
715
570
  request,
@@ -761,18 +616,6 @@ export class SequencerPublisher {
761
616
 
762
617
  // Otherwise, throw. We cannot build the next checkpoint if we cannot invalidate the previous one.
763
618
  this.log.error(`Simulation for invalidate checkpoint ${checkpointNumber} failed`, viemError, logData);
764
- this.backupFailedTx({
765
- id: keccak256(request.data!),
766
- failureType: 'simulation',
767
- request: { to: request.to!, data: request.data!, value: request.value?.toString() },
768
- l1BlockNumber: l1BlockNumber.toString(),
769
- error: { message: viemError.message, name: viemError.name },
770
- context: {
771
- actions: [`invalidate-${reason}`],
772
- checkpointNumber,
773
- sender: this.getSenderAddress().toString(),
774
- },
775
- });
776
619
  throw new Error(`Failed to simulate invalidate checkpoint ${checkpointNumber}`, { cause: viemError });
777
620
  }
778
621
  }
@@ -816,10 +659,7 @@ export class SequencerPublisher {
816
659
  attestationsAndSigners: CommitteeAttestationsAndSigners,
817
660
  attestationsAndSignersSignature: Signature,
818
661
  options: { forcePendingCheckpointNumber?: CheckpointNumber },
819
- ): Promise<bigint> {
820
- // Anchor the simulation timestamp to the checkpoint's own slot start time
821
- // rather than the current L1 block timestamp, which may overshoot into the next slot if the build ran late.
822
- const ts = checkpoint.header.timestamp;
662
+ ): Promise<void> {
823
663
  const blobFields = checkpoint.toBlobFields();
824
664
  const blobs = await getBlobsPerL1Block(blobFields);
825
665
  const blobInput = getPrefixedEthBlobCommitments(blobs);
@@ -838,13 +678,11 @@ export class SequencerPublisher {
838
678
  blobInput,
839
679
  ] as const;
840
680
 
841
- await this.simulateProposeTx(args, ts, options);
842
- return ts;
681
+ await this.simulateProposeTx(args, options);
843
682
  }
844
683
 
845
684
  private async enqueueCastSignalHelper(
846
685
  slotNumber: SlotNumber,
847
- timestamp: bigint,
848
686
  signalType: GovernanceSignalAction,
849
687
  payload: EthAddress,
850
688
  base: IEmpireBase,
@@ -922,25 +760,15 @@ export class SequencerPublisher {
922
760
  lastValidL2Slot: slotNumber,
923
761
  });
924
762
 
925
- const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
763
+ const timestamp = this.getSimulationTimestamp(slotNumber);
926
764
 
927
765
  try {
928
766
  await this.l1TxUtils.simulate(request, { time: timestamp }, [], mergeAbis([request.abi ?? [], ErrorsAbi]));
929
767
  this.log.debug(`Simulation for ${action} at slot ${slotNumber} succeeded`, { request });
930
768
  } catch (err) {
931
769
  const viemError = formatViemError(err);
932
- this.log.error(`Failed simulation for ${action} at slot ${slotNumber} (enqueuing the action anyway)`, viemError);
933
- this.backupFailedTx({
934
- id: keccak256(request.data!),
935
- failureType: 'simulation',
936
- request: { to: request.to!, data: request.data!, value: request.value?.toString() },
937
- l1BlockNumber: l1BlockNumber.toString(),
938
- error: { message: viemError.message, name: viemError.name },
939
- context: {
940
- actions: [action],
941
- slot: slotNumber,
942
- sender: this.getSenderAddress().toString(),
943
- },
770
+ this.log.error(`Failed simulation for ${action} at slot ${slotNumber} (enqueuing the action anyway)`, viemError, {
771
+ simulationTimestamp: timestamp,
944
772
  });
945
773
  // Yes, we enqueue the request anyway, in case there was a bug with the simulation itself
946
774
  }
@@ -992,19 +820,16 @@ export class SequencerPublisher {
992
820
  /**
993
821
  * Enqueues a governance castSignal transaction to cast a signal for a given slot number.
994
822
  * @param slotNumber - The slot number to cast a signal for.
995
- * @param timestamp - The timestamp of the slot to cast a signal for.
996
823
  * @returns True if the signal was successfully enqueued, false otherwise.
997
824
  */
998
825
  public enqueueGovernanceCastSignal(
999
826
  governancePayload: EthAddress,
1000
827
  slotNumber: SlotNumber,
1001
- timestamp: bigint,
1002
828
  signerAddress: EthAddress,
1003
829
  signer: (msg: TypedDataDefinition) => Promise<`0x${string}`>,
1004
830
  ): Promise<boolean> {
1005
831
  return this.enqueueCastSignalHelper(
1006
832
  slotNumber,
1007
- timestamp,
1008
833
  'governance-signal',
1009
834
  governancePayload,
1010
835
  this.govProposerContract,
@@ -1017,7 +842,6 @@ export class SequencerPublisher {
1017
842
  public async enqueueSlashingActions(
1018
843
  actions: ProposerSlashAction[],
1019
844
  slotNumber: SlotNumber,
1020
- timestamp: bigint,
1021
845
  signerAddress: EthAddress,
1022
846
  signer: (msg: TypedDataDefinition) => Promise<`0x${string}`>,
1023
847
  ): Promise<boolean> {
@@ -1038,7 +862,6 @@ export class SequencerPublisher {
1038
862
  });
1039
863
  await this.enqueueCastSignalHelper(
1040
864
  slotNumber,
1041
- timestamp,
1042
865
  'empire-slashing-signal',
1043
866
  action.payload,
1044
867
  this.slashingProposerContract,
@@ -1057,7 +880,6 @@ export class SequencerPublisher {
1057
880
  (receipt: TransactionReceipt) =>
1058
881
  !!this.slashFactoryContract.tryExtractSlashPayloadCreatedEvent(receipt.logs),
1059
882
  slotNumber,
1060
- timestamp,
1061
883
  );
1062
884
  break;
1063
885
  }
@@ -1075,7 +897,6 @@ export class SequencerPublisher {
1075
897
  request,
1076
898
  (receipt: TransactionReceipt) => !!empireSlashingProposer.tryExtractPayloadSubmittedEvent(receipt.logs),
1077
899
  slotNumber,
1078
- timestamp,
1079
900
  );
1080
901
  break;
1081
902
  }
@@ -1099,7 +920,6 @@ export class SequencerPublisher {
1099
920
  request,
1100
921
  (receipt: TransactionReceipt) => !!tallySlashingProposer.tryExtractVoteCastEvent(receipt.logs),
1101
922
  slotNumber,
1102
- timestamp,
1103
923
  );
1104
924
  break;
1105
925
  }
@@ -1121,7 +941,6 @@ export class SequencerPublisher {
1121
941
  request,
1122
942
  (receipt: TransactionReceipt) => !!tallySlashingProposer.tryExtractRoundExecutedEvent(receipt.logs),
1123
943
  slotNumber,
1124
- timestamp,
1125
944
  );
1126
945
  break;
1127
946
  }
@@ -1157,15 +976,13 @@ export class SequencerPublisher {
1157
976
  feeAssetPriceModifier: checkpoint.feeAssetPriceModifier,
1158
977
  };
1159
978
 
1160
- let ts: bigint;
1161
-
1162
979
  try {
1163
980
  // @note This will make sure that we are passing the checks for our header ASSUMING that the data is also made available
1164
981
  // This means that we can avoid the simulation issues in later checks.
1165
982
  // By simulation issue, I mean the fact that the block.timestamp is equal to the last block, not the next, which
1166
983
  // make time consistency checks break.
1167
984
  // TODO(palla): Check whether we're validating twice, once here and once within addProposeTx, since we call simulateProposeTx in both places.
1168
- ts = await this.validateCheckpointForSubmission(
985
+ await this.validateCheckpointForSubmission(
1169
986
  checkpoint,
1170
987
  attestationsAndSigners,
1171
988
  attestationsAndSignersSignature,
@@ -1181,7 +998,7 @@ export class SequencerPublisher {
1181
998
  }
1182
999
 
1183
1000
  this.log.verbose(`Enqueuing checkpoint propose transaction`, { ...checkpoint.toCheckpointInfo(), ...opts });
1184
- await this.addProposeTx(checkpoint, proposeTxArgs, opts, ts);
1001
+ await this.addProposeTx(checkpoint, proposeTxArgs, opts);
1185
1002
  }
1186
1003
 
1187
1004
  public enqueueInvalidateCheckpoint(
@@ -1224,8 +1041,8 @@ export class SequencerPublisher {
1224
1041
  request: L1TxRequest,
1225
1042
  checkSuccess: (receipt: TransactionReceipt) => boolean | undefined,
1226
1043
  slotNumber: SlotNumber,
1227
- timestamp: bigint,
1228
1044
  ) {
1045
+ const timestamp = this.getSimulationTimestamp(slotNumber);
1229
1046
  const logData = { slotNumber, timestamp, gasLimit: undefined as bigint | undefined };
1230
1047
  if (this.lastActions[action] && this.lastActions[action] === slotNumber) {
1231
1048
  this.log.debug(`Skipping duplicate action ${action} for slot ${slotNumber}`);
@@ -1237,30 +1054,16 @@ export class SequencerPublisher {
1237
1054
 
1238
1055
  this.log.debug(`Simulating ${action} for slot ${slotNumber}`, logData);
1239
1056
 
1240
- const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
1241
-
1242
1057
  let gasUsed: bigint;
1243
1058
  const simulateAbi = mergeAbis([request.abi ?? [], ErrorsAbi]);
1059
+
1244
1060
  try {
1245
- ({ 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));
1246
1062
  this.log.verbose(`Simulation for ${action} succeeded`, { ...logData, request, gasUsed });
1247
1063
  } catch (err) {
1248
1064
  const viemError = formatViemError(err, simulateAbi);
1249
1065
  this.log.error(`Simulation for ${action} at ${slotNumber} failed`, viemError, logData);
1250
1066
 
1251
- this.backupFailedTx({
1252
- id: keccak256(request.data!),
1253
- failureType: 'simulation',
1254
- request: { to: request.to!, data: request.data!, value: request.value?.toString() },
1255
- l1BlockNumber: l1BlockNumber.toString(),
1256
- error: { message: viemError.message, name: viemError.name },
1257
- context: {
1258
- actions: [action],
1259
- slot: slotNumber,
1260
- sender: this.getSenderAddress().toString(),
1261
- },
1262
- });
1263
-
1264
1067
  return false;
1265
1068
  }
1266
1069
 
@@ -1311,7 +1114,6 @@ export class SequencerPublisher {
1311
1114
 
1312
1115
  private async prepareProposeTx(
1313
1116
  encodedData: L1ProcessArgs,
1314
- timestamp: bigint,
1315
1117
  options: { forcePendingCheckpointNumber?: CheckpointNumber },
1316
1118
  ) {
1317
1119
  const kzg = Blob.getViemKzgInstance();
@@ -1344,27 +1146,9 @@ export class SequencerPublisher {
1344
1146
  kzg,
1345
1147
  },
1346
1148
  )
1347
- .catch(async err => {
1348
- const viemError = formatViemError(err);
1349
- this.log.error(`Failed to validate blobs`, viemError.message, { metaMessages: viemError.metaMessages });
1350
- const validateBlobsData = encodeFunctionData({
1351
- abi: RollupAbi,
1352
- functionName: 'validateBlobs',
1353
- args: [blobInput],
1354
- });
1355
- const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
1356
- this.backupFailedTx({
1357
- id: keccak256(validateBlobsData),
1358
- failureType: 'simulation',
1359
- request: { to: this.rollupContract.address as Hex, data: validateBlobsData },
1360
- blobData: encodedData.blobs.map(b => toHex(b.data)) as Hex[],
1361
- l1BlockNumber: l1BlockNumber.toString(),
1362
- error: { message: viemError.message, name: viemError.name },
1363
- context: {
1364
- actions: ['validate-blobs'],
1365
- sender: this.getSenderAddress().toString(),
1366
- },
1367
- });
1149
+ .catch(err => {
1150
+ const { message, metaMessages } = formatViemError(err);
1151
+ this.log.error(`Failed to validate blobs`, message, { metaMessages });
1368
1152
  throw new Error('Failed to validate blobs');
1369
1153
  });
1370
1154
  }
@@ -1384,7 +1168,7 @@ export class SequencerPublisher {
1384
1168
  blobInput,
1385
1169
  ] as const;
1386
1170
 
1387
- const { rollupData, simulationResult } = await this.simulateProposeTx(args, timestamp, options);
1171
+ const { rollupData, simulationResult } = await this.simulateProposeTx(args, options);
1388
1172
 
1389
1173
  return { args, blobEvaluationGas, rollupData, simulationResult };
1390
1174
  }
@@ -1392,7 +1176,6 @@ export class SequencerPublisher {
1392
1176
  /**
1393
1177
  * Simulates the propose tx with eth_simulateV1
1394
1178
  * @param args - The propose tx args
1395
- * @param timestamp - The timestamp to simulate proposal at
1396
1179
  * @returns The simulation result
1397
1180
  */
1398
1181
  private async simulateProposeTx(
@@ -1409,7 +1192,6 @@ export class SequencerPublisher {
1409
1192
  ViemSignature,
1410
1193
  `0x${string}`,
1411
1194
  ],
1412
- timestamp: bigint,
1413
1195
  options: { forcePendingCheckpointNumber?: CheckpointNumber },
1414
1196
  ) {
1415
1197
  const rollupData = encodeFunctionData({
@@ -1443,7 +1225,7 @@ export class SequencerPublisher {
1443
1225
  });
1444
1226
  }
1445
1227
 
1446
- const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
1228
+ const simTs = this.getSimulationTimestamp(SlotNumber.fromBigInt(args[0].header.slotNumber));
1447
1229
 
1448
1230
  const simulationResult = await this.l1TxUtils
1449
1231
  .simulate(
@@ -1454,8 +1236,7 @@ export class SequencerPublisher {
1454
1236
  ...(this.proposerAddressForSimulation && { from: this.proposerAddressForSimulation.toString() }),
1455
1237
  },
1456
1238
  {
1457
- // @note we add 1n to the timestamp because geth implementation doesn't like simulation timestamp to be equal to the current block timestamp
1458
- time: timestamp + 1n,
1239
+ time: simTs,
1459
1240
  // @note reth should have a 30m gas limit per block but throws errors that this tx is beyond limit so we increase here
1460
1241
  gasLimit: MAX_L1_TX_LIMIT * 2n,
1461
1242
  },
@@ -1477,19 +1258,7 @@ export class SequencerPublisher {
1477
1258
  logs: [],
1478
1259
  };
1479
1260
  }
1480
- this.log.error(`Failed to simulate propose tx`, viemError);
1481
- this.backupFailedTx({
1482
- id: keccak256(rollupData),
1483
- failureType: 'simulation',
1484
- request: { to: this.rollupContract.address, data: rollupData },
1485
- l1BlockNumber: l1BlockNumber.toString(),
1486
- error: { message: viemError.message, name: viemError.name },
1487
- context: {
1488
- actions: ['propose'],
1489
- slot: Number(args[0].header.slotNumber),
1490
- sender: this.getSenderAddress().toString(),
1491
- },
1492
- });
1261
+ this.log.error(`Failed to simulate propose tx`, viemError, { simulationTimestamp: simTs });
1493
1262
  throw err;
1494
1263
  });
1495
1264
 
@@ -1500,16 +1269,11 @@ export class SequencerPublisher {
1500
1269
  checkpoint: Checkpoint,
1501
1270
  encodedData: L1ProcessArgs,
1502
1271
  opts: { txTimeoutAt?: Date; forcePendingCheckpointNumber?: CheckpointNumber } = {},
1503
- timestamp: bigint,
1504
1272
  ): Promise<void> {
1505
1273
  const slot = checkpoint.header.slotNumber;
1506
1274
  const timer = new Timer();
1507
1275
  const kzg = Blob.getViemKzgInstance();
1508
- const { rollupData, simulationResult, blobEvaluationGas } = await this.prepareProposeTx(
1509
- encodedData,
1510
- timestamp,
1511
- opts,
1512
- );
1276
+ const { rollupData, simulationResult, blobEvaluationGas } = await this.prepareProposeTx(encodedData, opts);
1513
1277
  const startBlock = await this.l1TxUtils.getBlockNumber();
1514
1278
  const gasLimit = this.l1TxUtils.bumpGasLimit(
1515
1279
  BigInt(Math.ceil((Number(simulationResult.gasUsed) * 64) / 63)) +
@@ -1585,4 +1349,17 @@ export class SequencerPublisher {
1585
1349
  },
1586
1350
  });
1587
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
+ }
1588
1365
  }