@aztec/sequencer-client 4.0.0-devnet.2-patch.3 → 4.0.0-devnet.3-patch.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/dest/client/sequencer-client.d.ts +3 -1
  2. package/dest/client/sequencer-client.d.ts.map +1 -1
  3. package/dest/client/sequencer-client.js +38 -19
  4. package/dest/config.d.ts +24 -4
  5. package/dest/config.d.ts.map +1 -1
  6. package/dest/config.js +29 -16
  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/sequencer-publisher.d.ts +15 -8
  13. package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
  14. package/dest/publisher/sequencer-publisher.js +65 -36
  15. package/dest/sequencer/checkpoint_proposal_job.d.ts +4 -4
  16. package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -1
  17. package/dest/sequencer/checkpoint_proposal_job.js +98 -68
  18. package/dest/sequencer/checkpoint_voter.d.ts +1 -2
  19. package/dest/sequencer/checkpoint_voter.d.ts.map +1 -1
  20. package/dest/sequencer/checkpoint_voter.js +2 -5
  21. package/dest/sequencer/sequencer.d.ts +12 -7
  22. package/dest/sequencer/sequencer.d.ts.map +1 -1
  23. package/dest/sequencer/sequencer.js +15 -17
  24. package/dest/sequencer/timetable.d.ts +4 -3
  25. package/dest/sequencer/timetable.d.ts.map +1 -1
  26. package/dest/sequencer/timetable.js +6 -7
  27. package/dest/sequencer/types.d.ts +2 -2
  28. package/dest/sequencer/types.d.ts.map +1 -1
  29. package/dest/test/mock_checkpoint_builder.d.ts +7 -9
  30. package/dest/test/mock_checkpoint_builder.d.ts.map +1 -1
  31. package/dest/test/mock_checkpoint_builder.js +41 -30
  32. package/package.json +28 -28
  33. package/src/client/sequencer-client.ts +48 -14
  34. package/src/config.ts +35 -19
  35. package/src/global_variable_builder/global_builder.ts +22 -23
  36. package/src/global_variable_builder/index.ts +1 -1
  37. package/src/publisher/sequencer-publisher.ts +61 -44
  38. package/src/sequencer/checkpoint_proposal_job.ts +156 -98
  39. package/src/sequencer/checkpoint_voter.ts +1 -12
  40. package/src/sequencer/sequencer.ts +16 -18
  41. package/src/sequencer/timetable.ts +7 -7
  42. package/src/sequencer/types.ts +1 -1
  43. package/src/test/mock_checkpoint_builder.ts +53 -48
package/src/config.ts CHANGED
@@ -35,15 +35,13 @@ export type { SequencerConfig };
35
35
  * Default values for SequencerConfig.
36
36
  * Centralized location for all sequencer configuration defaults.
37
37
  */
38
- export const DefaultSequencerConfig: ResolvedSequencerConfig = {
38
+ export const DefaultSequencerConfig = {
39
39
  sequencerPollingIntervalMS: 500,
40
- maxTxsPerBlock: 32,
41
40
  minTxsPerBlock: 1,
42
41
  buildCheckpointIfEmpty: false,
43
42
  publishTxsWithProposals: false,
44
- maxL2BlockGas: 10e9,
45
- maxDABlockGas: 10e9,
46
- maxBlockSizeInBytes: 1024 * 1024,
43
+ perBlockAllocationMultiplier: 1.2,
44
+ redistributeCheckpointBudget: true,
47
45
  enforceTimeTable: true,
48
46
  attestationPropagationTime: DEFAULT_P2P_PROPAGATION_TIME,
49
47
  secondsBeforeInvalidatingBlockAsCommitteeMember: 144, // 12 L1 blocks
@@ -52,11 +50,13 @@ export const DefaultSequencerConfig: ResolvedSequencerConfig = {
52
50
  skipInvalidateBlockAsProposer: false,
53
51
  broadcastInvalidBlockProposal: false,
54
52
  injectFakeAttestation: false,
53
+ injectHighSValueAttestation: false,
54
+ injectUnrecoverableSignatureAttestation: false,
55
55
  fishermanMode: false,
56
56
  shuffleAttestationOrdering: false,
57
57
  skipPushProposedBlocksToArchiver: false,
58
58
  skipPublishingCheckpointsPercent: 0,
59
- };
59
+ } satisfies ResolvedSequencerConfig;
60
60
 
61
61
  /**
62
62
  * Configuration settings for the SequencerClient.
@@ -68,7 +68,7 @@ export type SequencerClientConfig = SequencerPublisherConfig &
68
68
  SequencerConfig &
69
69
  L1ReaderConfig &
70
70
  ChainConfig &
71
- Pick<P2PConfig, 'txPublicSetupAllowList'> &
71
+ Pick<P2PConfig, 'txPublicSetupAllowListExtend'> &
72
72
  Pick<L1ContractsConfig, 'ethereumSlotDuration' | 'aztecSlotDuration' | 'aztecEpochDuration'>;
73
73
 
74
74
  export const sequencerConfigMappings: ConfigMappingsType<SequencerConfig> = {
@@ -77,10 +77,10 @@ export const sequencerConfigMappings: ConfigMappingsType<SequencerConfig> = {
77
77
  description: 'The number of ms to wait between polling for checking to build on the next slot.',
78
78
  ...numberConfigHelper(DefaultSequencerConfig.sequencerPollingIntervalMS),
79
79
  },
80
- maxTxsPerBlock: {
81
- env: 'SEQ_MAX_TX_PER_BLOCK',
82
- description: 'The maximum number of txs to include in a block.',
83
- ...numberConfigHelper(DefaultSequencerConfig.maxTxsPerBlock),
80
+ maxTxsPerCheckpoint: {
81
+ env: 'SEQ_MAX_TX_PER_CHECKPOINT',
82
+ description: 'The maximum number of txs across all blocks in a checkpoint.',
83
+ parseEnv: (val: string) => (val ? parseInt(val, 10) : undefined),
84
84
  },
85
85
  minTxsPerBlock: {
86
86
  env: 'SEQ_MIN_TX_PER_BLOCK',
@@ -99,12 +99,25 @@ export const sequencerConfigMappings: ConfigMappingsType<SequencerConfig> = {
99
99
  maxL2BlockGas: {
100
100
  env: 'SEQ_MAX_L2_BLOCK_GAS',
101
101
  description: 'The maximum L2 block gas.',
102
- ...numberConfigHelper(DefaultSequencerConfig.maxL2BlockGas),
102
+ parseEnv: (val: string) => (val ? parseInt(val, 10) : undefined),
103
103
  },
104
104
  maxDABlockGas: {
105
105
  env: 'SEQ_MAX_DA_BLOCK_GAS',
106
106
  description: 'The maximum DA block gas.',
107
- ...numberConfigHelper(DefaultSequencerConfig.maxDABlockGas),
107
+ parseEnv: (val: string) => (val ? parseInt(val, 10) : undefined),
108
+ },
109
+ perBlockAllocationMultiplier: {
110
+ env: 'SEQ_PER_BLOCK_ALLOCATION_MULTIPLIER',
111
+ description:
112
+ 'Per-block gas budget multiplier for both L2 and DA gas. Budget per block is (checkpointLimit / maxBlocks) * multiplier.' +
113
+ ' Values greater than one allow early blocks to use more than their even share, relying on checkpoint-level capping for later blocks.',
114
+ ...numberConfigHelper(DefaultSequencerConfig.perBlockAllocationMultiplier),
115
+ },
116
+ redistributeCheckpointBudget: {
117
+ env: 'SEQ_REDISTRIBUTE_CHECKPOINT_BUDGET',
118
+ description:
119
+ 'Redistribute remaining checkpoint budget evenly across remaining blocks instead of allowing a single block to consume the entire remaining budget.',
120
+ ...booleanConfigHelper(DefaultSequencerConfig.redistributeCheckpointBudget),
108
121
  },
109
122
  coinbase: {
110
123
  env: 'COINBASE',
@@ -124,11 +137,6 @@ export const sequencerConfigMappings: ConfigMappingsType<SequencerConfig> = {
124
137
  env: 'ACVM_BINARY_PATH',
125
138
  description: 'The path to the ACVM binary',
126
139
  },
127
- maxBlockSizeInBytes: {
128
- env: 'SEQ_MAX_BLOCK_SIZE_IN_BYTES',
129
- description: 'Max block size',
130
- ...numberConfigHelper(DefaultSequencerConfig.maxBlockSizeInBytes),
131
- },
132
140
  enforceTimeTable: {
133
141
  env: 'SEQ_ENFORCE_TIME_TABLE',
134
142
  description: 'Whether to enforce the time table when building blocks',
@@ -186,6 +194,14 @@ export const sequencerConfigMappings: ConfigMappingsType<SequencerConfig> = {
186
194
  description: 'Inject a fake attestation (for testing only)',
187
195
  ...booleanConfigHelper(DefaultSequencerConfig.injectFakeAttestation),
188
196
  },
197
+ injectHighSValueAttestation: {
198
+ description: 'Inject a malleable attestation with a high-s value (for testing only)',
199
+ ...booleanConfigHelper(DefaultSequencerConfig.injectHighSValueAttestation),
200
+ },
201
+ injectUnrecoverableSignatureAttestation: {
202
+ description: 'Inject an attestation with an unrecoverable signature (for testing only)',
203
+ ...booleanConfigHelper(DefaultSequencerConfig.injectUnrecoverableSignatureAttestation),
204
+ },
189
205
  fishermanMode: {
190
206
  env: 'FISHERMAN_MODE',
191
207
  description:
@@ -214,7 +230,7 @@ export const sequencerConfigMappings: ConfigMappingsType<SequencerConfig> = {
214
230
  description: 'Percent probability (0 - 100) of sequencer skipping checkpoint publishing (testing only)',
215
231
  ...numberConfigHelper(DefaultSequencerConfig.skipPublishingCheckpointsPercent),
216
232
  },
217
- ...pickConfigMappings(p2pConfigMappings, ['txPublicSetupAllowList']),
233
+ ...pickConfigMappings(p2pConfigMappings, ['txPublicSetupAllowListExtend']),
218
234
  };
219
235
 
220
236
  export const sequencerClientConfigMappings: ConfigMappingsType<SequencerClientConfig> = {
@@ -1,14 +1,13 @@
1
- import { createEthereumChain } from '@aztec/ethereum/chain';
2
- import type { L1ContractsConfig } from '@aztec/ethereum/config';
3
1
  import { RollupContract } from '@aztec/ethereum/contracts';
4
- import type { L1ReaderConfig } from '@aztec/ethereum/l1-reader';
2
+ import type { L1ContractAddresses } from '@aztec/ethereum/l1-contract-addresses';
5
3
  import type { ViemPublicClient } from '@aztec/ethereum/types';
6
4
  import { BlockNumber, SlotNumber } from '@aztec/foundation/branded-types';
7
5
  import { Fr } from '@aztec/foundation/curves/bn254';
8
6
  import type { EthAddress } from '@aztec/foundation/eth-address';
9
7
  import { createLogger } from '@aztec/foundation/log';
8
+ import type { DateProvider } from '@aztec/foundation/timer';
10
9
  import type { AztecAddress } from '@aztec/stdlib/aztec-address';
11
- import { type L1RollupConstants, getTimestampForSlot } from '@aztec/stdlib/epoch-helpers';
10
+ import { type L1RollupConstants, getNextL1SlotTimestamp, getTimestampForSlot } from '@aztec/stdlib/epoch-helpers';
12
11
  import { GasFees } from '@aztec/stdlib/gas';
13
12
  import type {
14
13
  CheckpointGlobalVariables,
@@ -16,7 +15,12 @@ import type {
16
15
  } from '@aztec/stdlib/tx';
17
16
  import { GlobalVariables } from '@aztec/stdlib/tx';
18
17
 
19
- import { createPublicClient, fallback, http } from 'viem';
18
+ /** Configuration for the GlobalVariableBuilder (excludes L1 client config). */
19
+ export type GlobalVariableBuilderConfig = {
20
+ l1Contracts: Pick<L1ContractAddresses, 'rollupAddress'>;
21
+ ethereumSlotDuration: number;
22
+ rollupVersion: bigint;
23
+ } & Pick<L1RollupConstants, 'slotDuration' | 'l1GenesisTime'>;
20
24
 
21
25
  /**
22
26
  * Simple global variables builder.
@@ -27,7 +31,6 @@ export class GlobalVariableBuilder implements GlobalVariableBuilderInterface {
27
31
  private currentL1BlockNumber: bigint | undefined = undefined;
28
32
 
29
33
  private readonly rollupContract: RollupContract;
30
- private readonly publicClient: ViemPublicClient;
31
34
  private readonly ethereumSlotDuration: number;
32
35
  private readonly aztecSlotDuration: number;
33
36
  private readonly l1GenesisTime: bigint;
@@ -36,28 +39,18 @@ export class GlobalVariableBuilder implements GlobalVariableBuilderInterface {
36
39
  private version: Fr;
37
40
 
38
41
  constructor(
39
- config: L1ReaderConfig &
40
- Pick<L1ContractsConfig, 'ethereumSlotDuration'> &
41
- Pick<L1RollupConstants, 'slotDuration' | 'l1GenesisTime'> & { rollupVersion: bigint },
42
+ private readonly dateProvider: DateProvider,
43
+ private readonly publicClient: ViemPublicClient,
44
+ config: GlobalVariableBuilderConfig,
42
45
  ) {
43
- const { l1RpcUrls, l1ChainId: chainId, l1Contracts } = config;
44
-
45
- const chain = createEthereumChain(l1RpcUrls, chainId);
46
-
47
46
  this.version = new Fr(config.rollupVersion);
48
- this.chainId = new Fr(chainId);
47
+ this.chainId = new Fr(this.publicClient.chain!.id);
49
48
 
50
49
  this.ethereumSlotDuration = config.ethereumSlotDuration;
51
50
  this.aztecSlotDuration = config.slotDuration;
52
51
  this.l1GenesisTime = config.l1GenesisTime;
53
52
 
54
- this.publicClient = createPublicClient({
55
- chain: chain.chainInfo,
56
- transport: fallback(chain.rpcUrls.map(url => http(url, { batch: false }))),
57
- pollingInterval: config.viemPollingIntervalMS,
58
- });
59
-
60
- this.rollupContract = new RollupContract(this.publicClient, l1Contracts.rollupAddress);
53
+ this.rollupContract = new RollupContract(this.publicClient, config.l1Contracts.rollupAddress);
61
54
  }
62
55
 
63
56
  /**
@@ -73,7 +66,10 @@ export class GlobalVariableBuilder implements GlobalVariableBuilderInterface {
73
66
  const earliestTimestamp = await this.rollupContract.getTimestampForSlot(
74
67
  SlotNumber.fromBigInt(BigInt(lastCheckpoint.slotNumber) + 1n),
75
68
  );
76
- const nextEthTimestamp = BigInt((await this.publicClient.getBlock()).timestamp + BigInt(this.ethereumSlotDuration));
69
+ const nextEthTimestamp = getNextL1SlotTimestamp(this.dateProvider.nowInSeconds(), {
70
+ l1GenesisTime: this.l1GenesisTime,
71
+ ethereumSlotDuration: this.ethereumSlotDuration,
72
+ });
77
73
  const timestamp = earliestTimestamp > nextEthTimestamp ? earliestTimestamp : nextEthTimestamp;
78
74
 
79
75
  return new GasFees(0, await this.rollupContract.getManaMinFeeAt(timestamp, true));
@@ -108,7 +104,10 @@ export class GlobalVariableBuilder implements GlobalVariableBuilderInterface {
108
104
  const slot: SlotNumber =
109
105
  maybeSlot ??
110
106
  (await this.rollupContract.getSlotAt(
111
- BigInt((await this.publicClient.getBlock()).timestamp + BigInt(this.ethereumSlotDuration)),
107
+ getNextL1SlotTimestamp(this.dateProvider.nowInSeconds(), {
108
+ l1GenesisTime: this.l1GenesisTime,
109
+ ethereumSlotDuration: this.ethereumSlotDuration,
110
+ }),
112
111
  ));
113
112
 
114
113
  const checkpointGlobalVariables = await this.buildCheckpointGlobalVariables(coinbase, feeRecipient, slot);
@@ -1 +1 @@
1
- export { GlobalVariableBuilder } from './global_builder.js';
1
+ export { GlobalVariableBuilder, type GlobalVariableBuilderConfig } from './global_builder.js';
@@ -28,6 +28,7 @@ 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
34
  import { EthAddress } from '@aztec/foundation/eth-address';
@@ -40,6 +41,7 @@ import { EmpireBaseAbi, ErrorsAbi, RollupAbi } from '@aztec/l1-artifacts';
40
41
  import { type ProposerSlashAction, encodeSlashConsensusVotes } from '@aztec/slasher';
41
42
  import { CommitteeAttestationsAndSigners, type ValidateCheckpointResult } from '@aztec/stdlib/block';
42
43
  import type { Checkpoint } from '@aztec/stdlib/checkpoint';
44
+ import { getLastL1SlotTimestampForL2Slot, getNextL1SlotTimestamp } from '@aztec/stdlib/epoch-helpers';
43
45
  import { SlashFactoryContract } from '@aztec/stdlib/l1-contracts';
44
46
  import type { CheckpointHeader } from '@aztec/stdlib/rollup';
45
47
  import type { L1PublishCheckpointStats } from '@aztec/stdlib/stats';
@@ -120,6 +122,8 @@ export class SequencerPublisher {
120
122
 
121
123
  protected log: Logger;
122
124
  protected ethereumSlotDuration: bigint;
125
+ protected aztecSlotDuration: bigint;
126
+ private dateProvider: DateProvider;
123
127
 
124
128
  private blobClient: BlobClientInterface;
125
129
 
@@ -150,7 +154,7 @@ export class SequencerPublisher {
150
154
 
151
155
  constructor(
152
156
  private config: Pick<SequencerPublisherConfig, 'fishermanMode'> &
153
- Pick<L1ContractsConfig, 'ethereumSlotDuration'> & { l1ChainId: number },
157
+ Pick<L1ContractsConfig, 'ethereumSlotDuration' | 'aztecSlotDuration'> & { l1ChainId: number },
154
158
  deps: {
155
159
  telemetry?: TelemetryClient;
156
160
  blobClient: BlobClientInterface;
@@ -168,6 +172,8 @@ export class SequencerPublisher {
168
172
  ) {
169
173
  this.log = deps.log ?? createLogger('sequencer:publisher');
170
174
  this.ethereumSlotDuration = BigInt(config.ethereumSlotDuration);
175
+ this.aztecSlotDuration = BigInt(config.aztecSlotDuration);
176
+ this.dateProvider = deps.dateProvider;
171
177
  this.epochCache = deps.epochCache;
172
178
  this.lastActions = deps.lastActions;
173
179
 
@@ -356,8 +362,8 @@ export class SequencerPublisher {
356
362
  // @note - we can only have one blob config per bundle
357
363
  // find requests with gas and blob configs
358
364
  // See https://github.com/AztecProtocol/aztec-packages/issues/11513
359
- const gasConfigs = requestsToProcess.filter(request => request.gasConfig).map(request => request.gasConfig);
360
- 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);
361
367
 
362
368
  if (blobConfigs.length > 1) {
363
369
  throw new Error('Multiple blob configs found');
@@ -425,7 +431,16 @@ export class SequencerPublisher {
425
431
  this.log.error(`Failed to publish bundled transactions (${actionsListStr})`, result);
426
432
  return { failedActions: requests.map(r => r.action) };
427
433
  } else {
428
- 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
+ });
429
444
  const successfulActions: Action[] = [];
430
445
  const failedActions: Action[] = [];
431
446
  for (const request of requests) {
@@ -440,20 +455,24 @@ export class SequencerPublisher {
440
455
  }
441
456
 
442
457
  /**
443
- * @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
444
459
  * @param tipArchive - The archive to check
445
460
  * @returns The slot and block number if it is possible to propose, undefined otherwise
446
461
  */
447
- public canProposeAtNextEthBlock(
462
+ public canProposeAt(
448
463
  tipArchive: Fr,
449
464
  msgSender: EthAddress,
450
- opts: { forcePendingCheckpointNumber?: CheckpointNumber } = {},
465
+ opts: { forcePendingCheckpointNumber?: CheckpointNumber; pipelined?: boolean } = {},
451
466
  ) {
452
467
  // TODO: #14291 - should loop through multiple keys to check if any of them can propose
453
468
  const ignoredErrors = ['SlotAlreadyInChain', 'InvalidProposer', 'InvalidArchive'];
454
469
 
470
+ const pipelined = opts.pipelined ?? false;
471
+ const slotOffset = pipelined ? this.aztecSlotDuration : 0n;
472
+ const nextL1SlotTs = this.getNextL1SlotTimestamp() + slotOffset;
473
+
455
474
  return this.rollupContract
456
- .canProposeAtNextEthBlock(tipArchive.toBuffer(), msgSender.toString(), Number(this.ethereumSlotDuration), {
475
+ .canProposeAt(tipArchive.toBuffer(), msgSender.toString(), nextL1SlotTs, {
457
476
  forcePendingCheckpointNumber: opts.forcePendingCheckpointNumber,
458
477
  })
459
478
  .catch(err => {
@@ -490,7 +509,7 @@ export class SequencerPublisher {
490
509
  flags,
491
510
  ] as const;
492
511
 
493
- const ts = BigInt((await this.l1TxUtils.getBlock()).timestamp + this.ethereumSlotDuration);
512
+ const ts = this.getSimulationTimestamp(header.slotNumber);
494
513
  const stateOverrides = await this.rollupContract.makePendingCheckpointNumberOverride(
495
514
  opts?.forcePendingCheckpointNumber,
496
515
  );
@@ -513,7 +532,7 @@ export class SequencerPublisher {
513
532
  data: encodeFunctionData({ abi: RollupAbi, functionName: 'validateHeaderWithAttestations', args }),
514
533
  from: MULTI_CALL_3_ADDRESS,
515
534
  },
516
- { time: ts + 1n },
535
+ { time: ts },
517
536
  stateOverrides,
518
537
  );
519
538
  this.log.debug(`Simulated validateHeader`);
@@ -640,8 +659,7 @@ export class SequencerPublisher {
640
659
  attestationsAndSigners: CommitteeAttestationsAndSigners,
641
660
  attestationsAndSignersSignature: Signature,
642
661
  options: { forcePendingCheckpointNumber?: CheckpointNumber },
643
- ): Promise<bigint> {
644
- const ts = BigInt((await this.l1TxUtils.getBlock()).timestamp + this.ethereumSlotDuration);
662
+ ): Promise<void> {
645
663
  const blobFields = checkpoint.toBlobFields();
646
664
  const blobs = await getBlobsPerL1Block(blobFields);
647
665
  const blobInput = getPrefixedEthBlobCommitments(blobs);
@@ -660,13 +678,11 @@ export class SequencerPublisher {
660
678
  blobInput,
661
679
  ] as const;
662
680
 
663
- await this.simulateProposeTx(args, ts, options);
664
- return ts;
681
+ await this.simulateProposeTx(args, options);
665
682
  }
666
683
 
667
684
  private async enqueueCastSignalHelper(
668
685
  slotNumber: SlotNumber,
669
- timestamp: bigint,
670
686
  signalType: GovernanceSignalAction,
671
687
  payload: EthAddress,
672
688
  base: IEmpireBase,
@@ -744,11 +760,16 @@ export class SequencerPublisher {
744
760
  lastValidL2Slot: slotNumber,
745
761
  });
746
762
 
763
+ const timestamp = this.getSimulationTimestamp(slotNumber);
764
+
747
765
  try {
748
766
  await this.l1TxUtils.simulate(request, { time: timestamp }, [], mergeAbis([request.abi ?? [], ErrorsAbi]));
749
767
  this.log.debug(`Simulation for ${action} at slot ${slotNumber} succeeded`, { request });
750
768
  } catch (err) {
751
- this.log.error(`Failed simulation for ${action} at slot ${slotNumber} (enqueuing the action anyway)`, err);
769
+ const viemError = formatViemError(err);
770
+ this.log.error(`Failed simulation for ${action} at slot ${slotNumber} (enqueuing the action anyway)`, viemError, {
771
+ simulationTimestamp: timestamp,
772
+ });
752
773
  // Yes, we enqueue the request anyway, in case there was a bug with the simulation itself
753
774
  }
754
775
 
@@ -799,19 +820,16 @@ export class SequencerPublisher {
799
820
  /**
800
821
  * Enqueues a governance castSignal transaction to cast a signal for a given slot number.
801
822
  * @param slotNumber - The slot number to cast a signal for.
802
- * @param timestamp - The timestamp of the slot to cast a signal for.
803
823
  * @returns True if the signal was successfully enqueued, false otherwise.
804
824
  */
805
825
  public enqueueGovernanceCastSignal(
806
826
  governancePayload: EthAddress,
807
827
  slotNumber: SlotNumber,
808
- timestamp: bigint,
809
828
  signerAddress: EthAddress,
810
829
  signer: (msg: TypedDataDefinition) => Promise<`0x${string}`>,
811
830
  ): Promise<boolean> {
812
831
  return this.enqueueCastSignalHelper(
813
832
  slotNumber,
814
- timestamp,
815
833
  'governance-signal',
816
834
  governancePayload,
817
835
  this.govProposerContract,
@@ -824,7 +842,6 @@ export class SequencerPublisher {
824
842
  public async enqueueSlashingActions(
825
843
  actions: ProposerSlashAction[],
826
844
  slotNumber: SlotNumber,
827
- timestamp: bigint,
828
845
  signerAddress: EthAddress,
829
846
  signer: (msg: TypedDataDefinition) => Promise<`0x${string}`>,
830
847
  ): Promise<boolean> {
@@ -845,7 +862,6 @@ export class SequencerPublisher {
845
862
  });
846
863
  await this.enqueueCastSignalHelper(
847
864
  slotNumber,
848
- timestamp,
849
865
  'empire-slashing-signal',
850
866
  action.payload,
851
867
  this.slashingProposerContract,
@@ -864,7 +880,6 @@ export class SequencerPublisher {
864
880
  (receipt: TransactionReceipt) =>
865
881
  !!this.slashFactoryContract.tryExtractSlashPayloadCreatedEvent(receipt.logs),
866
882
  slotNumber,
867
- timestamp,
868
883
  );
869
884
  break;
870
885
  }
@@ -882,7 +897,6 @@ export class SequencerPublisher {
882
897
  request,
883
898
  (receipt: TransactionReceipt) => !!empireSlashingProposer.tryExtractPayloadSubmittedEvent(receipt.logs),
884
899
  slotNumber,
885
- timestamp,
886
900
  );
887
901
  break;
888
902
  }
@@ -906,7 +920,6 @@ export class SequencerPublisher {
906
920
  request,
907
921
  (receipt: TransactionReceipt) => !!tallySlashingProposer.tryExtractVoteCastEvent(receipt.logs),
908
922
  slotNumber,
909
- timestamp,
910
923
  );
911
924
  break;
912
925
  }
@@ -928,7 +941,6 @@ export class SequencerPublisher {
928
941
  request,
929
942
  (receipt: TransactionReceipt) => !!tallySlashingProposer.tryExtractRoundExecutedEvent(receipt.logs),
930
943
  slotNumber,
931
- timestamp,
932
944
  );
933
945
  break;
934
946
  }
@@ -964,15 +976,13 @@ export class SequencerPublisher {
964
976
  feeAssetPriceModifier: checkpoint.feeAssetPriceModifier,
965
977
  };
966
978
 
967
- let ts: bigint;
968
-
969
979
  try {
970
980
  // @note This will make sure that we are passing the checks for our header ASSUMING that the data is also made available
971
981
  // This means that we can avoid the simulation issues in later checks.
972
982
  // By simulation issue, I mean the fact that the block.timestamp is equal to the last block, not the next, which
973
983
  // make time consistency checks break.
974
984
  // TODO(palla): Check whether we're validating twice, once here and once within addProposeTx, since we call simulateProposeTx in both places.
975
- ts = await this.validateCheckpointForSubmission(
985
+ await this.validateCheckpointForSubmission(
976
986
  checkpoint,
977
987
  attestationsAndSigners,
978
988
  attestationsAndSignersSignature,
@@ -988,7 +998,7 @@ export class SequencerPublisher {
988
998
  }
989
999
 
990
1000
  this.log.verbose(`Enqueuing checkpoint propose transaction`, { ...checkpoint.toCheckpointInfo(), ...opts });
991
- await this.addProposeTx(checkpoint, proposeTxArgs, opts, ts);
1001
+ await this.addProposeTx(checkpoint, proposeTxArgs, opts);
992
1002
  }
993
1003
 
994
1004
  public enqueueInvalidateCheckpoint(
@@ -1031,8 +1041,8 @@ export class SequencerPublisher {
1031
1041
  request: L1TxRequest,
1032
1042
  checkSuccess: (receipt: TransactionReceipt) => boolean | undefined,
1033
1043
  slotNumber: SlotNumber,
1034
- timestamp: bigint,
1035
1044
  ) {
1045
+ const timestamp = this.getSimulationTimestamp(slotNumber);
1036
1046
  const logData = { slotNumber, timestamp, gasLimit: undefined as bigint | undefined };
1037
1047
  if (this.lastActions[action] && this.lastActions[action] === slotNumber) {
1038
1048
  this.log.debug(`Skipping duplicate action ${action} for slot ${slotNumber}`);
@@ -1046,8 +1056,9 @@ export class SequencerPublisher {
1046
1056
 
1047
1057
  let gasUsed: bigint;
1048
1058
  const simulateAbi = mergeAbis([request.abi ?? [], ErrorsAbi]);
1059
+
1049
1060
  try {
1050
- ({ 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));
1051
1062
  this.log.verbose(`Simulation for ${action} succeeded`, { ...logData, request, gasUsed });
1052
1063
  } catch (err) {
1053
1064
  const viemError = formatViemError(err, simulateAbi);
@@ -1103,7 +1114,6 @@ export class SequencerPublisher {
1103
1114
 
1104
1115
  private async prepareProposeTx(
1105
1116
  encodedData: L1ProcessArgs,
1106
- timestamp: bigint,
1107
1117
  options: { forcePendingCheckpointNumber?: CheckpointNumber },
1108
1118
  ) {
1109
1119
  const kzg = Blob.getViemKzgInstance();
@@ -1158,7 +1168,7 @@ export class SequencerPublisher {
1158
1168
  blobInput,
1159
1169
  ] as const;
1160
1170
 
1161
- const { rollupData, simulationResult } = await this.simulateProposeTx(args, timestamp, options);
1171
+ const { rollupData, simulationResult } = await this.simulateProposeTx(args, options);
1162
1172
 
1163
1173
  return { args, blobEvaluationGas, rollupData, simulationResult };
1164
1174
  }
@@ -1166,7 +1176,6 @@ export class SequencerPublisher {
1166
1176
  /**
1167
1177
  * Simulates the propose tx with eth_simulateV1
1168
1178
  * @param args - The propose tx args
1169
- * @param timestamp - The timestamp to simulate proposal at
1170
1179
  * @returns The simulation result
1171
1180
  */
1172
1181
  private async simulateProposeTx(
@@ -1183,7 +1192,6 @@ export class SequencerPublisher {
1183
1192
  ViemSignature,
1184
1193
  `0x${string}`,
1185
1194
  ],
1186
- timestamp: bigint,
1187
1195
  options: { forcePendingCheckpointNumber?: CheckpointNumber },
1188
1196
  ) {
1189
1197
  const rollupData = encodeFunctionData({
@@ -1217,6 +1225,8 @@ export class SequencerPublisher {
1217
1225
  });
1218
1226
  }
1219
1227
 
1228
+ const simTs = this.getSimulationTimestamp(SlotNumber.fromBigInt(args[0].header.slotNumber));
1229
+
1220
1230
  const simulationResult = await this.l1TxUtils
1221
1231
  .simulate(
1222
1232
  {
@@ -1226,8 +1236,7 @@ export class SequencerPublisher {
1226
1236
  ...(this.proposerAddressForSimulation && { from: this.proposerAddressForSimulation.toString() }),
1227
1237
  },
1228
1238
  {
1229
- // @note we add 1n to the timestamp because geth implementation doesn't like simulation timestamp to be equal to the current block timestamp
1230
- time: timestamp + 1n,
1239
+ time: simTs,
1231
1240
  // @note reth should have a 30m gas limit per block but throws errors that this tx is beyond limit so we increase here
1232
1241
  gasLimit: MAX_L1_TX_LIMIT * 2n,
1233
1242
  },
@@ -1249,7 +1258,7 @@ export class SequencerPublisher {
1249
1258
  logs: [],
1250
1259
  };
1251
1260
  }
1252
- this.log.error(`Failed to simulate propose tx`, viemError);
1261
+ this.log.error(`Failed to simulate propose tx`, viemError, { simulationTimestamp: simTs });
1253
1262
  throw err;
1254
1263
  });
1255
1264
 
@@ -1260,16 +1269,11 @@ export class SequencerPublisher {
1260
1269
  checkpoint: Checkpoint,
1261
1270
  encodedData: L1ProcessArgs,
1262
1271
  opts: { txTimeoutAt?: Date; forcePendingCheckpointNumber?: CheckpointNumber } = {},
1263
- timestamp: bigint,
1264
1272
  ): Promise<void> {
1265
1273
  const slot = checkpoint.header.slotNumber;
1266
1274
  const timer = new Timer();
1267
1275
  const kzg = Blob.getViemKzgInstance();
1268
- const { rollupData, simulationResult, blobEvaluationGas } = await this.prepareProposeTx(
1269
- encodedData,
1270
- timestamp,
1271
- opts,
1272
- );
1276
+ const { rollupData, simulationResult, blobEvaluationGas } = await this.prepareProposeTx(encodedData, opts);
1273
1277
  const startBlock = await this.l1TxUtils.getBlockNumber();
1274
1278
  const gasLimit = this.l1TxUtils.bumpGasLimit(
1275
1279
  BigInt(Math.ceil((Number(simulationResult.gasUsed) * 64) / 63)) +
@@ -1345,4 +1349,17 @@ export class SequencerPublisher {
1345
1349
  },
1346
1350
  });
1347
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
+ }
1348
1365
  }