@aztec/sequencer-client 0.0.1-commit.fce3e4f → 0.0.1-commit.ff7989d6c

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 (96) hide show
  1. package/dest/client/sequencer-client.d.ts +21 -16
  2. package/dest/client/sequencer-client.d.ts.map +1 -1
  3. package/dest/client/sequencer-client.js +45 -27
  4. package/dest/config.d.ts +14 -8
  5. package/dest/config.d.ts.map +1 -1
  6. package/dest/config.js +83 -35
  7. package/dest/global_variable_builder/global_builder.d.ts +20 -13
  8. package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
  9. package/dest/global_variable_builder/global_builder.js +51 -41
  10. package/dest/index.d.ts +2 -3
  11. package/dest/index.d.ts.map +1 -1
  12. package/dest/index.js +1 -2
  13. package/dest/publisher/config.d.ts +37 -20
  14. package/dest/publisher/config.d.ts.map +1 -1
  15. package/dest/publisher/config.js +104 -39
  16. package/dest/publisher/sequencer-publisher-factory.d.ts +15 -6
  17. package/dest/publisher/sequencer-publisher-factory.d.ts.map +1 -1
  18. package/dest/publisher/sequencer-publisher-factory.js +14 -3
  19. package/dest/publisher/sequencer-publisher-metrics.d.ts +3 -3
  20. package/dest/publisher/sequencer-publisher-metrics.d.ts.map +1 -1
  21. package/dest/publisher/sequencer-publisher-metrics.js +23 -86
  22. package/dest/publisher/sequencer-publisher.d.ts +63 -47
  23. package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
  24. package/dest/publisher/sequencer-publisher.js +630 -137
  25. package/dest/sequencer/checkpoint_proposal_job.d.ts +102 -0
  26. package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -0
  27. package/dest/sequencer/checkpoint_proposal_job.js +1213 -0
  28. package/dest/sequencer/checkpoint_voter.d.ts +35 -0
  29. package/dest/sequencer/checkpoint_voter.d.ts.map +1 -0
  30. package/dest/sequencer/checkpoint_voter.js +109 -0
  31. package/dest/sequencer/config.d.ts +3 -2
  32. package/dest/sequencer/config.d.ts.map +1 -1
  33. package/dest/sequencer/events.d.ts +46 -0
  34. package/dest/sequencer/events.d.ts.map +1 -0
  35. package/dest/sequencer/events.js +1 -0
  36. package/dest/sequencer/index.d.ts +4 -2
  37. package/dest/sequencer/index.d.ts.map +1 -1
  38. package/dest/sequencer/index.js +3 -1
  39. package/dest/sequencer/metrics.d.ts +38 -6
  40. package/dest/sequencer/metrics.d.ts.map +1 -1
  41. package/dest/sequencer/metrics.js +216 -72
  42. package/dest/sequencer/sequencer.d.ts +119 -133
  43. package/dest/sequencer/sequencer.d.ts.map +1 -1
  44. package/dest/sequencer/sequencer.js +717 -625
  45. package/dest/sequencer/timetable.d.ts +51 -14
  46. package/dest/sequencer/timetable.d.ts.map +1 -1
  47. package/dest/sequencer/timetable.js +145 -59
  48. package/dest/sequencer/types.d.ts +3 -0
  49. package/dest/sequencer/types.d.ts.map +1 -0
  50. package/dest/sequencer/types.js +1 -0
  51. package/dest/sequencer/utils.d.ts +14 -8
  52. package/dest/sequencer/utils.d.ts.map +1 -1
  53. package/dest/sequencer/utils.js +7 -4
  54. package/dest/test/index.d.ts +6 -7
  55. package/dest/test/index.d.ts.map +1 -1
  56. package/dest/test/mock_checkpoint_builder.d.ts +97 -0
  57. package/dest/test/mock_checkpoint_builder.d.ts.map +1 -0
  58. package/dest/test/mock_checkpoint_builder.js +222 -0
  59. package/dest/test/utils.d.ts +53 -0
  60. package/dest/test/utils.d.ts.map +1 -0
  61. package/dest/test/utils.js +104 -0
  62. package/package.json +32 -30
  63. package/src/client/sequencer-client.ts +54 -47
  64. package/src/config.ts +95 -44
  65. package/src/global_variable_builder/global_builder.ts +65 -61
  66. package/src/index.ts +1 -7
  67. package/src/publisher/config.ts +122 -50
  68. package/src/publisher/sequencer-publisher-factory.ts +28 -10
  69. package/src/publisher/sequencer-publisher-metrics.ts +19 -71
  70. package/src/publisher/sequencer-publisher.ts +350 -176
  71. package/src/sequencer/README.md +531 -0
  72. package/src/sequencer/checkpoint_proposal_job.ts +914 -0
  73. package/src/sequencer/checkpoint_voter.ts +130 -0
  74. package/src/sequencer/config.ts +2 -1
  75. package/src/sequencer/events.ts +27 -0
  76. package/src/sequencer/index.ts +3 -1
  77. package/src/sequencer/metrics.ts +268 -82
  78. package/src/sequencer/sequencer.ts +464 -831
  79. package/src/sequencer/timetable.ts +175 -80
  80. package/src/sequencer/types.ts +6 -0
  81. package/src/sequencer/utils.ts +18 -9
  82. package/src/test/index.ts +5 -6
  83. package/src/test/mock_checkpoint_builder.ts +320 -0
  84. package/src/test/utils.ts +167 -0
  85. package/dest/sequencer/block_builder.d.ts +0 -27
  86. package/dest/sequencer/block_builder.d.ts.map +0 -1
  87. package/dest/sequencer/block_builder.js +0 -134
  88. package/dest/tx_validator/nullifier_cache.d.ts +0 -14
  89. package/dest/tx_validator/nullifier_cache.d.ts.map +0 -1
  90. package/dest/tx_validator/nullifier_cache.js +0 -24
  91. package/dest/tx_validator/tx_validator_factory.d.ts +0 -17
  92. package/dest/tx_validator/tx_validator_factory.d.ts.map +0 -1
  93. package/dest/tx_validator/tx_validator_factory.js +0 -53
  94. package/src/sequencer/block_builder.ts +0 -222
  95. package/src/tx_validator/nullifier_cache.ts +0 -30
  96. package/src/tx_validator/tx_validator_factory.ts +0 -132
@@ -1,48 +1,53 @@
1
- import { L2Block } from '@aztec/aztec.js/block';
1
+ import type { BlobClientInterface } from '@aztec/blob-client/client';
2
2
  import { Blob, getBlobsPerL1Block, getPrefixedEthBlobCommitments } from '@aztec/blob-lib';
3
- import { type BlobSinkClientInterface, createBlobSinkClient } from '@aztec/blob-sink/client';
4
3
  import type { EpochCache } from '@aztec/epoch-cache';
4
+ import type { L1ContractsConfig } from '@aztec/ethereum/config';
5
5
  import {
6
6
  type EmpireSlashingProposerContract,
7
- FormattedViemError,
7
+ FeeAssetPriceOracle,
8
8
  type GovernanceProposerContract,
9
9
  type IEmpireBase,
10
- type L1BlobInputs,
11
- type L1ContractsConfig,
12
- type L1TxConfig,
13
- type L1TxRequest,
14
10
  MULTI_CALL_3_ADDRESS,
15
11
  Multicall3,
16
12
  RollupContract,
17
13
  type TallySlashingProposerContract,
18
- type TransactionStats,
19
14
  type ViemCommitteeAttestations,
20
15
  type ViemHeader,
16
+ } from '@aztec/ethereum/contracts';
17
+ import { type L1FeeAnalysisResult, L1FeeAnalyzer } from '@aztec/ethereum/l1-fee-analysis';
18
+ import {
19
+ type L1BlobInputs,
20
+ type L1TxConfig,
21
+ type L1TxRequest,
22
+ type L1TxUtils,
23
+ MAX_L1_TX_LIMIT,
24
+ type TransactionStats,
21
25
  WEI_CONST,
22
- formatViemError,
23
- tryExtractEvent,
24
- } from '@aztec/ethereum';
25
- import type { L1TxUtilsWithBlobs } from '@aztec/ethereum/l1-tx-utils-with-blobs';
26
+ } from '@aztec/ethereum/l1-tx-utils';
27
+ import { FormattedViemError, formatViemError, mergeAbis, tryExtractEvent } from '@aztec/ethereum/utils';
26
28
  import { sumBigint } from '@aztec/foundation/bigint';
27
29
  import { toHex as toPaddedHex } from '@aztec/foundation/bigint-buffer';
28
- import { SlotNumber } from '@aztec/foundation/branded-types';
30
+ import { CheckpointNumber, SlotNumber } from '@aztec/foundation/branded-types';
31
+ import { pick } from '@aztec/foundation/collection';
32
+ import type { Fr } from '@aztec/foundation/curves/bn254';
29
33
  import { EthAddress } from '@aztec/foundation/eth-address';
30
34
  import { Signature, type ViemSignature } from '@aztec/foundation/eth-signature';
31
- import type { Fr } from '@aztec/foundation/fields';
32
35
  import { type Logger, createLogger } from '@aztec/foundation/log';
36
+ import { makeBackoff, retry } from '@aztec/foundation/retry';
33
37
  import { bufferToHex } from '@aztec/foundation/string';
34
38
  import { DateProvider, Timer } from '@aztec/foundation/timer';
35
39
  import { EmpireBaseAbi, ErrorsAbi, RollupAbi } from '@aztec/l1-artifacts';
36
40
  import { type ProposerSlashAction, encodeSlashConsensusVotes } from '@aztec/slasher';
37
- import { CommitteeAttestation, CommitteeAttestationsAndSigners, type ValidateBlockResult } from '@aztec/stdlib/block';
41
+ import { CommitteeAttestationsAndSigners, type ValidateCheckpointResult } from '@aztec/stdlib/block';
42
+ import type { Checkpoint } from '@aztec/stdlib/checkpoint';
38
43
  import { SlashFactoryContract } from '@aztec/stdlib/l1-contracts';
39
44
  import type { CheckpointHeader } from '@aztec/stdlib/rollup';
40
- import type { L1PublishBlockStats } from '@aztec/stdlib/stats';
41
- import { type TelemetryClient, getTelemetryClient } from '@aztec/telemetry-client';
45
+ import type { L1PublishCheckpointStats } from '@aztec/stdlib/stats';
46
+ import { type TelemetryClient, type Tracer, getTelemetryClient, trackSpan } from '@aztec/telemetry-client';
42
47
 
43
48
  import { type StateOverride, type TransactionReceipt, type TypedDataDefinition, encodeFunctionData, toHex } from 'viem';
44
49
 
45
- import type { PublisherConfig, TxSenderConfig } from './config.js';
50
+ import type { SequencerPublisherConfig } from './config.js';
46
51
  import { SequencerPublisherMetrics } from './sequencer-publisher-metrics.js';
47
52
 
48
53
  /** Arguments to the process method of the rollup contract */
@@ -57,6 +62,8 @@ type L1ProcessArgs = {
57
62
  attestationsAndSigners: CommitteeAttestationsAndSigners;
58
63
  /** Attestations and signers signature */
59
64
  attestationsAndSignersSignature: Signature;
65
+ /** The fee asset price modifier in basis points (from oracle) */
66
+ feeAssetPriceModifier: bigint;
60
67
  };
61
68
 
62
69
  export const Actions = [
@@ -78,12 +85,12 @@ type GovernanceSignalAction = Extract<Action, 'governance-signal' | 'empire-slas
78
85
  // Sorting for actions such that invalidations go before proposals, and proposals go before votes
79
86
  export const compareActions = (a: Action, b: Action) => Actions.indexOf(a) - Actions.indexOf(b);
80
87
 
81
- export type InvalidateBlockRequest = {
88
+ export type InvalidateCheckpointRequest = {
82
89
  request: L1TxRequest;
83
90
  reason: 'invalid-attestation' | 'insufficient-attestations';
84
91
  gasUsed: bigint;
85
- blockNumber: number;
86
- forcePendingBlockNumber: number;
92
+ checkpointNumber: CheckpointNumber;
93
+ forcePendingCheckpointNumber: CheckpointNumber;
87
94
  };
88
95
 
89
96
  interface RequestWithExpiry {
@@ -108,17 +115,22 @@ export class SequencerPublisher {
108
115
 
109
116
  protected lastActions: Partial<Record<Action, SlotNumber>> = {};
110
117
 
118
+ private isPayloadEmptyCache: Map<string, boolean> = new Map<string, boolean>();
119
+ private payloadProposedCache: Set<string> = new Set<string>();
120
+
111
121
  protected log: Logger;
112
122
  protected ethereumSlotDuration: bigint;
113
123
 
114
- private blobSinkClient: BlobSinkClientInterface;
124
+ private blobClient: BlobClientInterface;
115
125
 
116
126
  /** Address to use for simulations in fisherman mode (actual proposer's address) */
117
127
  private proposerAddressForSimulation?: EthAddress;
118
- // @note - with blobs, the below estimate seems too large.
119
- // Total used for full block from int_l1_pub e2e test: 1m (of which 86k is 1x blob)
120
- // Total used for emptier block from above test: 429k (of which 84k is 1x blob)
121
- public static PROPOSE_GAS_GUESS: bigint = 12_000_000n;
128
+
129
+ /** L1 fee analyzer for fisherman mode */
130
+ private l1FeeAnalyzer?: L1FeeAnalyzer;
131
+
132
+ /** Fee asset price oracle for computing price modifiers from Uniswap V4 */
133
+ private feeAssetPriceOracle: FeeAssetPriceOracle;
122
134
 
123
135
  // A CALL to a cold address is 2700 gas
124
136
  public static MULTICALL_OVERHEAD_GAS_GUESS = 5000n;
@@ -126,20 +138,23 @@ export class SequencerPublisher {
126
138
  // Gas report for VotingWithSigTest shows a max gas of 100k, but we've seen it cost 700k+ in testnet
127
139
  public static VOTE_GAS_GUESS: bigint = 800_000n;
128
140
 
129
- public l1TxUtils: L1TxUtilsWithBlobs;
141
+ public l1TxUtils: L1TxUtils;
130
142
  public rollupContract: RollupContract;
131
143
  public govProposerContract: GovernanceProposerContract;
132
144
  public slashingProposerContract: EmpireSlashingProposerContract | TallySlashingProposerContract | undefined;
133
145
  public slashFactoryContract: SlashFactoryContract;
134
146
 
147
+ public readonly tracer: Tracer;
148
+
135
149
  protected requests: RequestWithExpiry[] = [];
136
150
 
137
151
  constructor(
138
- private config: TxSenderConfig & PublisherConfig & Pick<L1ContractsConfig, 'ethereumSlotDuration'>,
152
+ private config: Pick<SequencerPublisherConfig, 'fishermanMode'> &
153
+ Pick<L1ContractsConfig, 'ethereumSlotDuration'> & { l1ChainId: number },
139
154
  deps: {
140
155
  telemetry?: TelemetryClient;
141
- blobSinkClient?: BlobSinkClientInterface;
142
- l1TxUtils: L1TxUtilsWithBlobs;
156
+ blobClient: BlobClientInterface;
157
+ l1TxUtils: L1TxUtils;
143
158
  rollupContract: RollupContract;
144
159
  slashingProposerContract: EmpireSlashingProposerContract | TallySlashingProposerContract | undefined;
145
160
  governanceProposerContract: GovernanceProposerContract;
@@ -156,11 +171,11 @@ export class SequencerPublisher {
156
171
  this.epochCache = deps.epochCache;
157
172
  this.lastActions = deps.lastActions;
158
173
 
159
- this.blobSinkClient =
160
- deps.blobSinkClient ?? createBlobSinkClient(config, { logger: createLogger('sequencer:blob-sink:client') });
174
+ this.blobClient = deps.blobClient;
161
175
 
162
176
  const telemetry = deps.telemetry ?? getTelemetryClient();
163
177
  this.metrics = deps.metrics ?? new SequencerPublisherMetrics(telemetry, 'SequencerPublisher');
178
+ this.tracer = telemetry.getTracer('SequencerPublisher');
164
179
  this.l1TxUtils = deps.l1TxUtils;
165
180
 
166
181
  this.rollupContract = deps.rollupContract;
@@ -174,16 +189,47 @@ export class SequencerPublisher {
174
189
  this.slashingProposerContract = newSlashingProposer;
175
190
  });
176
191
  this.slashFactoryContract = deps.slashFactoryContract;
192
+
193
+ // Initialize L1 fee analyzer for fisherman mode
194
+ if (config.fishermanMode) {
195
+ this.l1FeeAnalyzer = new L1FeeAnalyzer(
196
+ this.l1TxUtils.client,
197
+ deps.dateProvider,
198
+ createLogger('sequencer:publisher:fee-analyzer'),
199
+ );
200
+ }
201
+
202
+ // Initialize fee asset price oracle
203
+ this.feeAssetPriceOracle = new FeeAssetPriceOracle(
204
+ this.l1TxUtils.client,
205
+ this.rollupContract,
206
+ createLogger('sequencer:publisher:price-oracle'),
207
+ );
177
208
  }
178
209
 
179
210
  public getRollupContract(): RollupContract {
180
211
  return this.rollupContract;
181
212
  }
182
213
 
214
+ /**
215
+ * Gets the fee asset price modifier from the oracle.
216
+ * Returns 0n if the oracle query fails.
217
+ */
218
+ public getFeeAssetPriceModifier(): Promise<bigint> {
219
+ return this.feeAssetPriceOracle.computePriceModifier();
220
+ }
221
+
183
222
  public getSenderAddress() {
184
223
  return this.l1TxUtils.getSenderAddress();
185
224
  }
186
225
 
226
+ /**
227
+ * Gets the L1 fee analyzer instance (only available in fisherman mode)
228
+ */
229
+ public getL1FeeAnalyzer(): L1FeeAnalyzer | undefined {
230
+ return this.l1FeeAnalyzer;
231
+ }
232
+
187
233
  /**
188
234
  * Sets the proposer address to use for simulations in fisherman mode.
189
235
  * @param proposerAddress - The actual proposer's address to use for balance lookups in simulations
@@ -211,6 +257,62 @@ export class SequencerPublisher {
211
257
  }
212
258
  }
213
259
 
260
+ /**
261
+ * Analyzes L1 fees for the pending requests without sending them.
262
+ * This is used in fisherman mode to validate fee calculations.
263
+ * @param l2SlotNumber - The L2 slot number for this analysis
264
+ * @param onComplete - Optional callback to invoke when analysis completes (after block is mined)
265
+ * @returns The analysis result (incomplete until block mines), or undefined if no requests
266
+ */
267
+ public async analyzeL1Fees(
268
+ l2SlotNumber: SlotNumber,
269
+ onComplete?: (analysis: L1FeeAnalysisResult) => void,
270
+ ): Promise<L1FeeAnalysisResult | undefined> {
271
+ if (!this.l1FeeAnalyzer) {
272
+ this.log.warn('L1 fee analyzer not available (not in fisherman mode)');
273
+ return undefined;
274
+ }
275
+
276
+ const requestsToAnalyze = [...this.requests];
277
+ if (requestsToAnalyze.length === 0) {
278
+ this.log.debug('No requests to analyze for L1 fees');
279
+ return undefined;
280
+ }
281
+
282
+ // Extract blob config from requests (if any)
283
+ const blobConfigs = requestsToAnalyze.filter(request => request.blobConfig).map(request => request.blobConfig);
284
+ const blobConfig = blobConfigs[0];
285
+
286
+ // Get gas configs
287
+ const gasConfigs = requestsToAnalyze.filter(request => request.gasConfig).map(request => request.gasConfig);
288
+ const gasLimits = gasConfigs.map(g => g?.gasLimit).filter((g): g is bigint => g !== undefined);
289
+ const gasLimit = gasLimits.length > 0 ? gasLimits.reduce((sum, g) => sum + g, 0n) : 0n;
290
+
291
+ // Get the transaction requests
292
+ const l1Requests = requestsToAnalyze.map(r => r.request);
293
+
294
+ // Start the analysis
295
+ const analysisId = await this.l1FeeAnalyzer.startAnalysis(
296
+ l2SlotNumber,
297
+ gasLimit > 0n ? gasLimit : MAX_L1_TX_LIMIT,
298
+ l1Requests,
299
+ blobConfig,
300
+ onComplete,
301
+ );
302
+
303
+ this.log.info('Started L1 fee analysis', {
304
+ analysisId,
305
+ l2SlotNumber: l2SlotNumber.toString(),
306
+ requestCount: requestsToAnalyze.length,
307
+ hasBlobConfig: !!blobConfig,
308
+ gasLimit: gasLimit.toString(),
309
+ actions: requestsToAnalyze.map(r => r.action),
310
+ });
311
+
312
+ // Return the analysis result (will be incomplete until block mines)
313
+ return this.l1FeeAnalyzer.getAnalysis(analysisId);
314
+ }
315
+
214
316
  /**
215
317
  * Sends all requests that are still valid.
216
318
  * @returns one of:
@@ -218,10 +320,11 @@ export class SequencerPublisher {
218
320
  * - a receipt and errorMsg if it failed on L1
219
321
  * - undefined if no valid requests are found OR the tx failed to send.
220
322
  */
323
+ @trackSpan('SequencerPublisher.sendRequests')
221
324
  public async sendRequests() {
222
325
  const requestsToProcess = [...this.requests];
223
326
  this.requests = [];
224
- if (this.interrupted) {
327
+ if (this.interrupted || requestsToProcess.length === 0) {
225
328
  return undefined;
226
329
  }
227
330
  const currentL2Slot = this.getCurrentL2Slot();
@@ -264,7 +367,16 @@ export class SequencerPublisher {
264
367
 
265
368
  // Merge gasConfigs. Yields the sum of gasLimits, and the earliest txTimeoutAt, or undefined if no gasConfig sets them.
266
369
  const gasLimits = gasConfigs.map(g => g?.gasLimit).filter((g): g is bigint => g !== undefined);
267
- const gasLimit = gasLimits.length > 0 ? sumBigint(gasLimits) : undefined; // sum
370
+ let gasLimit = gasLimits.length > 0 ? sumBigint(gasLimits) : undefined; // sum
371
+ // Cap at L1 block gas limit so the node accepts the tx ("gas limit too high" otherwise).
372
+ const maxGas = MAX_L1_TX_LIMIT;
373
+ if (gasLimit !== undefined && gasLimit > maxGas) {
374
+ this.log.debug('Capping bundled tx gas limit to L1 max', {
375
+ requested: gasLimit,
376
+ capped: maxGas,
377
+ });
378
+ gasLimit = maxGas;
379
+ }
268
380
  const txTimeoutAts = gasConfigs.map(g => g?.txTimeoutAt).filter((g): g is Date => g !== undefined);
269
381
  const txTimeoutAt = txTimeoutAts.length > 0 ? new Date(Math.min(...txTimeoutAts.map(g => g.getTime()))) : undefined; // earliest
270
382
  const txConfig: RequestWithExpiry['gasConfig'] = { gasLimit, txTimeoutAt };
@@ -335,14 +447,14 @@ export class SequencerPublisher {
335
447
  public canProposeAtNextEthBlock(
336
448
  tipArchive: Fr,
337
449
  msgSender: EthAddress,
338
- opts: { forcePendingBlockNumber?: number } = {},
450
+ opts: { forcePendingCheckpointNumber?: CheckpointNumber } = {},
339
451
  ) {
340
452
  // TODO: #14291 - should loop through multiple keys to check if any of them can propose
341
453
  const ignoredErrors = ['SlotAlreadyInChain', 'InvalidProposer', 'InvalidArchive'];
342
454
 
343
455
  return this.rollupContract
344
456
  .canProposeAtNextEthBlock(tipArchive.toBuffer(), msgSender.toString(), Number(this.ethereumSlotDuration), {
345
- forcePendingCheckpointNumber: opts.forcePendingBlockNumber,
457
+ forcePendingCheckpointNumber: opts.forcePendingCheckpointNumber,
346
458
  })
347
459
  .catch(err => {
348
460
  if (err instanceof FormattedViemError && ignoredErrors.find(e => err.message.includes(e))) {
@@ -361,7 +473,11 @@ export class SequencerPublisher {
361
473
  * It will throw if the block header is invalid.
362
474
  * @param header - The block header to validate
363
475
  */
364
- public async validateBlockHeader(header: CheckpointHeader, opts?: { forcePendingBlockNumber: number | undefined }) {
476
+ @trackSpan('SequencerPublisher.validateBlockHeader')
477
+ public async validateBlockHeader(
478
+ header: CheckpointHeader,
479
+ opts?: { forcePendingCheckpointNumber: CheckpointNumber | undefined },
480
+ ): Promise<void> {
365
481
  const flags = { ignoreDA: true, ignoreSignatures: true };
366
482
 
367
483
  const args = [
@@ -370,12 +486,14 @@ export class SequencerPublisher {
370
486
  [], // no signers
371
487
  Signature.empty().toViemSignature(),
372
488
  `0x${'0'.repeat(64)}`, // 32 empty bytes
373
- header.contentCommitment.blobsHash.toString(),
489
+ header.blobsHash.toString(),
374
490
  flags,
375
491
  ] as const;
376
492
 
377
493
  const ts = BigInt((await this.l1TxUtils.getBlock()).timestamp + this.ethereumSlotDuration);
378
- const stateOverrides = await this.rollupContract.makePendingCheckpointNumberOverride(opts?.forcePendingBlockNumber);
494
+ const stateOverrides = await this.rollupContract.makePendingCheckpointNumberOverride(
495
+ opts?.forcePendingCheckpointNumber,
496
+ );
379
497
  let balance = 0n;
380
498
  if (this.config.fishermanMode) {
381
499
  // In fisherman mode, we can't know where the proposer is publishing from
@@ -402,77 +520,95 @@ export class SequencerPublisher {
402
520
  }
403
521
 
404
522
  /**
405
- * Simulate making a call to invalidate a block with invalid attestations. Returns undefined if no need to invalidate.
406
- * @param block - The block to invalidate and the criteria for invalidation (as returned by the archiver)
523
+ * Simulate making a call to invalidate a checkpoint with invalid attestations. Returns undefined if no need to invalidate.
524
+ * @param validationResult - The validation result indicating which checkpoint to invalidate (as returned by the archiver)
407
525
  */
408
- public async simulateInvalidateBlock(
409
- validationResult: ValidateBlockResult,
410
- ): Promise<InvalidateBlockRequest | undefined> {
526
+ public async simulateInvalidateCheckpoint(
527
+ validationResult: ValidateCheckpointResult,
528
+ ): Promise<InvalidateCheckpointRequest | undefined> {
411
529
  if (validationResult.valid) {
412
530
  return undefined;
413
531
  }
414
532
 
415
- const { reason, block } = validationResult;
416
- const blockNumber = block.blockNumber;
417
- const logData = { ...block, reason };
533
+ const { reason, checkpoint } = validationResult;
534
+ const checkpointNumber = checkpoint.checkpointNumber;
535
+ const logData = { ...checkpoint, reason };
418
536
 
419
- const currentBlockNumber = await this.rollupContract.getCheckpointNumber();
420
- if (currentBlockNumber < validationResult.block.blockNumber) {
537
+ const currentCheckpointNumber = await this.rollupContract.getCheckpointNumber();
538
+ if (currentCheckpointNumber < checkpointNumber) {
421
539
  this.log.verbose(
422
- `Skipping block ${blockNumber} invalidation since it has already been removed from the pending chain`,
423
- { currentBlockNumber, ...logData },
540
+ `Skipping checkpoint ${checkpointNumber} invalidation since it has already been removed from the pending chain`,
541
+ { currentCheckpointNumber, ...logData },
424
542
  );
425
543
  return undefined;
426
544
  }
427
545
 
428
- const request = this.buildInvalidateBlockRequest(validationResult);
429
- this.log.debug(`Simulating invalidate block ${blockNumber}`, { ...logData, request });
546
+ const request = this.buildInvalidateCheckpointRequest(validationResult);
547
+ this.log.debug(`Simulating invalidate checkpoint ${checkpointNumber}`, { ...logData, request });
430
548
 
431
549
  try {
432
- const { gasUsed } = await this.l1TxUtils.simulate(request, undefined, undefined, ErrorsAbi);
433
- this.log.verbose(`Simulation for invalidate block ${blockNumber} succeeded`, { ...logData, request, gasUsed });
550
+ const { gasUsed } = await this.l1TxUtils.simulate(
551
+ request,
552
+ undefined,
553
+ undefined,
554
+ mergeAbis([request.abi ?? [], ErrorsAbi]),
555
+ );
556
+ this.log.verbose(`Simulation for invalidate checkpoint ${checkpointNumber} succeeded`, {
557
+ ...logData,
558
+ request,
559
+ gasUsed,
560
+ });
434
561
 
435
- return { request, gasUsed, blockNumber, forcePendingBlockNumber: blockNumber - 1, reason };
562
+ return {
563
+ request,
564
+ gasUsed,
565
+ checkpointNumber,
566
+ forcePendingCheckpointNumber: CheckpointNumber(checkpointNumber - 1),
567
+ reason,
568
+ };
436
569
  } catch (err) {
437
570
  const viemError = formatViemError(err);
438
571
 
439
- // If the error is due to the block not being in the pending chain, and it was indeed removed by someone else,
440
- // we can safely ignore it and return undefined so we go ahead with block building.
441
- if (viemError.message?.includes('Rollup__BlockNotInPendingChain')) {
572
+ // If the error is due to the checkpoint not being in the pending chain, and it was indeed removed by someone else,
573
+ // we can safely ignore it and return undefined so we go ahead with checkpoint building.
574
+ if (viemError.message?.includes('Rollup__CheckpointNotInPendingChain')) {
442
575
  this.log.verbose(
443
- `Simulation for invalidate block ${blockNumber} failed due to block not being in pending chain`,
576
+ `Simulation for invalidate checkpoint ${checkpointNumber} failed due to checkpoint not being in pending chain`,
444
577
  { ...logData, request, error: viemError.message },
445
578
  );
446
- const latestPendingBlockNumber = await this.rollupContract.getCheckpointNumber();
447
- if (latestPendingBlockNumber < blockNumber) {
448
- this.log.verbose(`Block number ${blockNumber} has already been invalidated`, { ...logData });
579
+ const latestPendingCheckpointNumber = await this.rollupContract.getCheckpointNumber();
580
+ if (latestPendingCheckpointNumber < checkpointNumber) {
581
+ this.log.verbose(`Checkpoint ${checkpointNumber} has already been invalidated`, { ...logData });
449
582
  return undefined;
450
583
  } else {
451
584
  this.log.error(
452
- `Simulation for invalidate ${blockNumber} failed and it is still in pending chain`,
585
+ `Simulation for invalidate checkpoint ${checkpointNumber} failed and it is still in pending chain`,
453
586
  viemError,
454
587
  logData,
455
588
  );
456
- throw new Error(`Failed to simulate invalidate block ${blockNumber} while it is still in pending chain`, {
457
- cause: viemError,
458
- });
589
+ throw new Error(
590
+ `Failed to simulate invalidate checkpoint ${checkpointNumber} while it is still in pending chain`,
591
+ {
592
+ cause: viemError,
593
+ },
594
+ );
459
595
  }
460
596
  }
461
597
 
462
- // Otherwise, throw. We cannot build the next block if we cannot invalidate the previous one.
463
- this.log.error(`Simulation for invalidate block ${blockNumber} failed`, viemError, logData);
464
- throw new Error(`Failed to simulate invalidate block ${blockNumber}`, { cause: viemError });
598
+ // Otherwise, throw. We cannot build the next checkpoint if we cannot invalidate the previous one.
599
+ this.log.error(`Simulation for invalidate checkpoint ${checkpointNumber} failed`, viemError, logData);
600
+ throw new Error(`Failed to simulate invalidate checkpoint ${checkpointNumber}`, { cause: viemError });
465
601
  }
466
602
  }
467
603
 
468
- private buildInvalidateBlockRequest(validationResult: ValidateBlockResult) {
604
+ private buildInvalidateCheckpointRequest(validationResult: ValidateCheckpointResult) {
469
605
  if (validationResult.valid) {
470
- throw new Error('Cannot invalidate a valid block');
606
+ throw new Error('Cannot invalidate a valid checkpoint');
471
607
  }
472
608
 
473
- const { block, committee, reason } = validationResult;
474
- const logData = { ...block, reason };
475
- this.log.debug(`Simulating invalidate block ${block.blockNumber}`, logData);
609
+ const { checkpoint, committee, reason } = validationResult;
610
+ const logData = { ...checkpoint, reason };
611
+ this.log.debug(`Building invalidate checkpoint ${checkpoint.checkpointNumber} request`, logData);
476
612
 
477
613
  const attestationsAndSigners = new CommitteeAttestationsAndSigners(
478
614
  validationResult.attestations,
@@ -480,14 +616,14 @@ export class SequencerPublisher {
480
616
 
481
617
  if (reason === 'invalid-attestation') {
482
618
  return this.rollupContract.buildInvalidateBadAttestationRequest(
483
- block.blockNumber,
619
+ checkpoint.checkpointNumber,
484
620
  attestationsAndSigners,
485
621
  committee,
486
622
  validationResult.invalidIndex,
487
623
  );
488
624
  } else if (reason === 'insufficient-attestations') {
489
625
  return this.rollupContract.buildInvalidateInsufficientAttestationsRequest(
490
- block.blockNumber,
626
+ checkpoint.checkpointNumber,
491
627
  attestationsAndSigners,
492
628
  committee,
493
629
  );
@@ -497,47 +633,25 @@ export class SequencerPublisher {
497
633
  }
498
634
  }
499
635
 
500
- /**
501
- * @notice Will simulate `propose` to make sure that the block is valid for submission
502
- *
503
- * @dev Throws if unable to propose
504
- *
505
- * @param block - The block to propose
506
- * @param attestationData - The block's attestation data
507
- *
508
- */
509
- public async validateBlockForSubmission(
510
- block: L2Block,
636
+ /** Simulates `propose` to make sure that the checkpoint is valid for submission */
637
+ @trackSpan('SequencerPublisher.validateCheckpointForSubmission')
638
+ public async validateCheckpointForSubmission(
639
+ checkpoint: Checkpoint,
511
640
  attestationsAndSigners: CommitteeAttestationsAndSigners,
512
641
  attestationsAndSignersSignature: Signature,
513
- options: { forcePendingBlockNumber?: number },
642
+ options: { forcePendingCheckpointNumber?: CheckpointNumber },
514
643
  ): Promise<bigint> {
515
644
  const ts = BigInt((await this.l1TxUtils.getBlock()).timestamp + this.ethereumSlotDuration);
516
-
517
- // If we have no attestations, we still need to provide the empty attestations
518
- // so that the committee is recalculated correctly
519
- const ignoreSignatures = attestationsAndSigners.attestations.length === 0;
520
- if (ignoreSignatures) {
521
- const { committee } = await this.epochCache.getCommittee(block.header.globalVariables.slotNumber);
522
- if (!committee) {
523
- this.log.warn(`No committee found for slot ${block.header.globalVariables.slotNumber}`);
524
- throw new Error(`No committee found for slot ${block.header.globalVariables.slotNumber}`);
525
- }
526
- attestationsAndSigners.attestations = committee.map(committeeMember =>
527
- CommitteeAttestation.fromAddress(committeeMember),
528
- );
529
- }
530
-
531
- const blobFields = block.getCheckpointBlobFields();
532
- const blobs = getBlobsPerL1Block(blobFields);
645
+ const blobFields = checkpoint.toBlobFields();
646
+ const blobs = await getBlobsPerL1Block(blobFields);
533
647
  const blobInput = getPrefixedEthBlobCommitments(blobs);
534
648
 
535
649
  const args = [
536
650
  {
537
- header: block.getCheckpointHeader().toViem(),
538
- archive: toHex(block.archive.root.toBuffer()),
651
+ header: checkpoint.header.toViem(),
652
+ archive: toHex(checkpoint.archive.root.toBuffer()),
539
653
  oracleInput: {
540
- feeAssetPriceModifier: 0n,
654
+ feeAssetPriceModifier: checkpoint.feeAssetPriceModifier,
541
655
  },
542
656
  },
543
657
  attestationsAndSigners.getPackedAttestations(),
@@ -573,10 +687,45 @@ export class SequencerPublisher {
573
687
  const round = await base.computeRound(slotNumber);
574
688
  const roundInfo = await base.getRoundInfo(this.rollupContract.address, round);
575
689
 
690
+ if (roundInfo.quorumReached) {
691
+ return false;
692
+ }
693
+
576
694
  if (roundInfo.lastSignalSlot >= slotNumber) {
577
695
  return false;
578
696
  }
579
697
 
698
+ if (await this.isPayloadEmpty(payload)) {
699
+ this.log.warn(`Skipping vote cast for payload with empty code`);
700
+ return false;
701
+ }
702
+
703
+ // Check if payload was already submitted to governance
704
+ const cacheKey = payload.toString();
705
+ if (!this.payloadProposedCache.has(cacheKey)) {
706
+ try {
707
+ const l1StartBlock = await this.rollupContract.getL1StartBlock();
708
+ const proposed = await retry(
709
+ () => base.hasPayloadBeenProposed(payload.toString(), l1StartBlock),
710
+ 'Check if payload was proposed',
711
+ makeBackoff([0, 1, 2]),
712
+ this.log,
713
+ true,
714
+ );
715
+ if (proposed) {
716
+ this.payloadProposedCache.add(cacheKey);
717
+ }
718
+ } catch (err) {
719
+ this.log.warn(`Failed to check if payload ${payload} was proposed after retries, skipping signal`, err);
720
+ return false;
721
+ }
722
+ }
723
+
724
+ if (this.payloadProposedCache.has(cacheKey)) {
725
+ this.log.info(`Payload ${payload} was already proposed to governance, stopping signals`);
726
+ return false;
727
+ }
728
+
580
729
  const cachedLastVote = this.lastActions[signalType];
581
730
  this.lastActions[signalType] = slotNumber;
582
731
  const action = signalType;
@@ -596,7 +745,7 @@ export class SequencerPublisher {
596
745
  });
597
746
 
598
747
  try {
599
- await this.l1TxUtils.simulate(request, { time: timestamp }, [], ErrorsAbi);
748
+ await this.l1TxUtils.simulate(request, { time: timestamp }, [], mergeAbis([request.abi ?? [], ErrorsAbi]));
600
749
  this.log.debug(`Simulation for ${action} at slot ${slotNumber} succeeded`, { request });
601
750
  } catch (err) {
602
751
  this.log.error(`Failed simulation for ${action} at slot ${slotNumber} (enqueuing the action anyway)`, err);
@@ -619,14 +768,14 @@ export class SequencerPublisher {
619
768
  const logData = { ...result, slotNumber, round, payload: payload.toString() };
620
769
  if (!success) {
621
770
  this.log.error(
622
- `Signaling in [${action}] for ${payload} at slot ${slotNumber} in round ${round} failed`,
771
+ `Signaling in ${action} for ${payload} at slot ${slotNumber} in round ${round} failed`,
623
772
  logData,
624
773
  );
625
774
  this.lastActions[signalType] = cachedLastVote;
626
775
  return false;
627
776
  } else {
628
777
  this.log.info(
629
- `Signaling in [${action}] for ${payload} at slot ${slotNumber} in round ${round} succeeded`,
778
+ `Signaling in ${action} for ${payload} at slot ${slotNumber} in round ${round} succeeded`,
630
779
  logData,
631
780
  );
632
781
  return true;
@@ -636,6 +785,17 @@ export class SequencerPublisher {
636
785
  return true;
637
786
  }
638
787
 
788
+ private async isPayloadEmpty(payload: EthAddress): Promise<boolean> {
789
+ const key = payload.toString();
790
+ const cached = this.isPayloadEmptyCache.get(key);
791
+ if (cached) {
792
+ return cached;
793
+ }
794
+ const isEmpty = !(await this.l1TxUtils.getCode(payload));
795
+ this.isPayloadEmptyCache.set(key, isEmpty);
796
+ return isEmpty;
797
+ }
798
+
639
799
  /**
640
800
  * Enqueues a governance castSignal transaction to cast a signal for a given slot number.
641
801
  * @param slotNumber - The slot number to cast a signal for.
@@ -783,30 +943,25 @@ export class SequencerPublisher {
783
943
  return true;
784
944
  }
785
945
 
786
- /**
787
- * Proposes a L2 block on L1.
788
- *
789
- * @param block - L2 block to propose.
790
- * @returns True if the tx has been enqueued, throws otherwise. See #9315
791
- */
792
- public async enqueueProposeL2Block(
793
- block: L2Block,
946
+ /** Simulates and enqueues a proposal for a checkpoint on L1 */
947
+ public async enqueueProposeCheckpoint(
948
+ checkpoint: Checkpoint,
794
949
  attestationsAndSigners: CommitteeAttestationsAndSigners,
795
950
  attestationsAndSignersSignature: Signature,
796
- opts: { txTimeoutAt?: Date; forcePendingBlockNumber?: number } = {},
797
- ): Promise<boolean> {
798
- const checkpointHeader = block.getCheckpointHeader();
951
+ opts: { txTimeoutAt?: Date; forcePendingCheckpointNumber?: CheckpointNumber } = {},
952
+ ): Promise<void> {
953
+ const checkpointHeader = checkpoint.header;
799
954
 
800
- const blobFields = block.getCheckpointBlobFields();
801
- const blobs = getBlobsPerL1Block(blobFields);
955
+ const blobFields = checkpoint.toBlobFields();
956
+ const blobs = await getBlobsPerL1Block(blobFields);
802
957
 
803
- const proposeTxArgs = {
958
+ const proposeTxArgs: L1ProcessArgs = {
804
959
  header: checkpointHeader,
805
- archive: block.archive.root.toBuffer(),
806
- body: block.body.toBuffer(),
960
+ archive: checkpoint.archive.root.toBuffer(),
807
961
  blobs,
808
962
  attestationsAndSigners,
809
963
  attestationsAndSignersSignature,
964
+ feeAssetPriceModifier: checkpoint.feeAssetPriceModifier,
810
965
  };
811
966
 
812
967
  let ts: bigint;
@@ -817,22 +972,29 @@ export class SequencerPublisher {
817
972
  // By simulation issue, I mean the fact that the block.timestamp is equal to the last block, not the next, which
818
973
  // make time consistency checks break.
819
974
  // TODO(palla): Check whether we're validating twice, once here and once within addProposeTx, since we call simulateProposeTx in both places.
820
- ts = await this.validateBlockForSubmission(block, attestationsAndSigners, attestationsAndSignersSignature, opts);
975
+ ts = await this.validateCheckpointForSubmission(
976
+ checkpoint,
977
+ attestationsAndSigners,
978
+ attestationsAndSignersSignature,
979
+ opts,
980
+ );
821
981
  } catch (err: any) {
822
- this.log.error(`Block validation failed. ${err instanceof Error ? err.message : 'No error message'}`, err, {
823
- ...block.getStats(),
824
- slotNumber: block.header.globalVariables.slotNumber,
825
- forcePendingBlockNumber: opts.forcePendingBlockNumber,
982
+ this.log.error(`Checkpoint validation failed. ${err instanceof Error ? err.message : 'No error message'}`, err, {
983
+ ...checkpoint.getStats(),
984
+ slotNumber: checkpoint.header.slotNumber,
985
+ forcePendingCheckpointNumber: opts.forcePendingCheckpointNumber,
826
986
  });
827
987
  throw err;
828
988
  }
829
989
 
830
- this.log.verbose(`Enqueuing block propose transaction`, { ...block.toBlockInfo(), ...opts });
831
- await this.addProposeTx(block, proposeTxArgs, opts, ts);
832
- return true;
990
+ this.log.verbose(`Enqueuing checkpoint propose transaction`, { ...checkpoint.toCheckpointInfo(), ...opts });
991
+ await this.addProposeTx(checkpoint, proposeTxArgs, opts, ts);
833
992
  }
834
993
 
835
- public enqueueInvalidateBlock(request: InvalidateBlockRequest | undefined, opts: { txTimeoutAt?: Date } = {}) {
994
+ public enqueueInvalidateCheckpoint(
995
+ request: InvalidateCheckpointRequest | undefined,
996
+ opts: { txTimeoutAt?: Date } = {},
997
+ ) {
836
998
  if (!request) {
837
999
  return;
838
1000
  }
@@ -840,9 +1002,9 @@ export class SequencerPublisher {
840
1002
  // We issued the simulation against the rollup contract, so we need to account for the overhead of the multicall3
841
1003
  const gasLimit = this.l1TxUtils.bumpGasLimit(BigInt(Math.ceil((Number(request.gasUsed) * 64) / 63)));
842
1004
 
843
- const { gasUsed, blockNumber } = request;
844
- const logData = { gasUsed, blockNumber, gasLimit, opts };
845
- this.log.verbose(`Enqueuing invalidate block request`, logData);
1005
+ const { gasUsed, checkpointNumber } = request;
1006
+ const logData = { gasUsed, checkpointNumber, gasLimit, opts };
1007
+ this.log.verbose(`Enqueuing invalidate checkpoint request`, logData);
846
1008
  this.addRequest({
847
1009
  action: `invalidate-by-${request.reason}`,
848
1010
  request: request.request,
@@ -855,9 +1017,9 @@ export class SequencerPublisher {
855
1017
  result.receipt.status === 'success' &&
856
1018
  tryExtractEvent(result.receipt.logs, this.rollupContract.address, RollupAbi, 'CheckpointInvalidated');
857
1019
  if (!success) {
858
- this.log.warn(`Invalidate block ${request.blockNumber} failed`, { ...result, ...logData });
1020
+ this.log.warn(`Invalidate checkpoint ${request.checkpointNumber} failed`, { ...result, ...logData });
859
1021
  } else {
860
- this.log.info(`Invalidate block ${request.blockNumber} succeeded`, { ...result, ...logData });
1022
+ this.log.info(`Invalidate checkpoint ${request.checkpointNumber} succeeded`, { ...result, ...logData });
861
1023
  }
862
1024
  return !!success;
863
1025
  },
@@ -883,12 +1045,14 @@ export class SequencerPublisher {
883
1045
  this.log.debug(`Simulating ${action} for slot ${slotNumber}`, logData);
884
1046
 
885
1047
  let gasUsed: bigint;
1048
+ const simulateAbi = mergeAbis([request.abi ?? [], ErrorsAbi]);
886
1049
  try {
887
- ({ gasUsed } = await this.l1TxUtils.simulate(request, { time: timestamp }, [], ErrorsAbi)); // TODO(palla/slash): Check the timestamp logic
1050
+ ({ gasUsed } = await this.l1TxUtils.simulate(request, { time: timestamp }, [], simulateAbi)); // TODO(palla/slash): Check the timestamp logic
888
1051
  this.log.verbose(`Simulation for ${action} succeeded`, { ...logData, request, gasUsed });
889
1052
  } catch (err) {
890
- const viemError = formatViemError(err);
1053
+ const viemError = formatViemError(err, simulateAbi);
891
1054
  this.log.error(`Simulation for ${action} at ${slotNumber} failed`, viemError, logData);
1055
+
892
1056
  return false;
893
1057
  }
894
1058
 
@@ -896,10 +1060,14 @@ export class SequencerPublisher {
896
1060
  const gasLimit = this.l1TxUtils.bumpGasLimit(BigInt(Math.ceil((Number(gasUsed) * 64) / 63)));
897
1061
  logData.gasLimit = gasLimit;
898
1062
 
1063
+ // Store the ABI used for simulation on the request so Multicall3.forward can decode errors
1064
+ // when the tx is sent and a revert is diagnosed via simulation.
1065
+ const requestWithAbi = { ...request, abi: simulateAbi };
1066
+
899
1067
  this.log.debug(`Enqueuing ${action}`, logData);
900
1068
  this.addRequest({
901
1069
  action,
902
- request,
1070
+ request: requestWithAbi,
903
1071
  gasConfig: { gasLimit },
904
1072
  lastValidL2Slot: slotNumber,
905
1073
  checkSuccess: (_req, result) => {
@@ -936,7 +1104,7 @@ export class SequencerPublisher {
936
1104
  private async prepareProposeTx(
937
1105
  encodedData: L1ProcessArgs,
938
1106
  timestamp: bigint,
939
- options: { forcePendingBlockNumber?: number },
1107
+ options: { forcePendingCheckpointNumber?: CheckpointNumber },
940
1108
  ) {
941
1109
  const kzg = Blob.getViemKzgInstance();
942
1110
  const blobInput = getPrefixedEthBlobCommitments(encodedData.blobs);
@@ -981,8 +1149,7 @@ export class SequencerPublisher {
981
1149
  header: encodedData.header.toViem(),
982
1150
  archive: toHex(encodedData.archive),
983
1151
  oracleInput: {
984
- // We are currently not modifying these. See #9963
985
- feeAssetPriceModifier: 0n,
1152
+ feeAssetPriceModifier: encodedData.feeAssetPriceModifier,
986
1153
  },
987
1154
  },
988
1155
  encodedData.attestationsAndSigners.getPackedAttestations(),
@@ -1008,7 +1175,7 @@ export class SequencerPublisher {
1008
1175
  readonly header: ViemHeader;
1009
1176
  readonly archive: `0x${string}`;
1010
1177
  readonly oracleInput: {
1011
- readonly feeAssetPriceModifier: 0n;
1178
+ readonly feeAssetPriceModifier: bigint;
1012
1179
  };
1013
1180
  },
1014
1181
  ViemCommitteeAttestations,
@@ -1017,7 +1184,7 @@ export class SequencerPublisher {
1017
1184
  `0x${string}`,
1018
1185
  ],
1019
1186
  timestamp: bigint,
1020
- options: { forcePendingBlockNumber?: number },
1187
+ options: { forcePendingCheckpointNumber?: CheckpointNumber },
1021
1188
  ) {
1022
1189
  const rollupData = encodeFunctionData({
1023
1190
  abi: RollupAbi,
@@ -1025,10 +1192,10 @@ export class SequencerPublisher {
1025
1192
  args,
1026
1193
  });
1027
1194
 
1028
- // override the pending block number if requested
1029
- const forcePendingBlockNumberStateDiff = (
1030
- options.forcePendingBlockNumber !== undefined
1031
- ? await this.rollupContract.makePendingCheckpointNumberOverride(options.forcePendingBlockNumber)
1195
+ // override the pending checkpoint number if requested
1196
+ const forcePendingCheckpointNumberStateDiff = (
1197
+ options.forcePendingCheckpointNumber !== undefined
1198
+ ? await this.rollupContract.makePendingCheckpointNumberOverride(options.forcePendingCheckpointNumber)
1032
1199
  : []
1033
1200
  ).flatMap(override => override.stateDiff ?? []);
1034
1201
 
@@ -1038,7 +1205,7 @@ export class SequencerPublisher {
1038
1205
  // @note we override checkBlob to false since blobs are not part simulate()
1039
1206
  stateDiff: [
1040
1207
  { slot: toPaddedHex(RollupContract.checkBlobStorageSlot, true), value: toPaddedHex(0n, true) },
1041
- ...forcePendingBlockNumberStateDiff,
1208
+ ...forcePendingCheckpointNumberStateDiff,
1042
1209
  ],
1043
1210
  },
1044
1211
  ];
@@ -1055,20 +1222,20 @@ export class SequencerPublisher {
1055
1222
  {
1056
1223
  to: this.rollupContract.address,
1057
1224
  data: rollupData,
1058
- gas: SequencerPublisher.PROPOSE_GAS_GUESS,
1225
+ gas: MAX_L1_TX_LIMIT,
1059
1226
  ...(this.proposerAddressForSimulation && { from: this.proposerAddressForSimulation.toString() }),
1060
1227
  },
1061
1228
  {
1062
1229
  // @note we add 1n to the timestamp because geth implementation doesn't like simulation timestamp to be equal to the current block timestamp
1063
1230
  time: timestamp + 1n,
1064
1231
  // @note reth should have a 30m gas limit per block but throws errors that this tx is beyond limit so we increase here
1065
- gasLimit: SequencerPublisher.PROPOSE_GAS_GUESS * 2n,
1232
+ gasLimit: MAX_L1_TX_LIMIT * 2n,
1066
1233
  },
1067
1234
  stateOverrides,
1068
1235
  RollupAbi,
1069
1236
  {
1070
1237
  // @note fallback gas estimate to use if the node doesn't support simulation API
1071
- fallbackGasEstimate: SequencerPublisher.PROPOSE_GAS_GUESS,
1238
+ fallbackGasEstimate: MAX_L1_TX_LIMIT,
1072
1239
  },
1073
1240
  )
1074
1241
  .catch(err => {
@@ -1078,7 +1245,7 @@ export class SequencerPublisher {
1078
1245
  this.log.debug(`Ignoring expected ValidatorSelection__MissingProposerSignature error in fisherman mode`);
1079
1246
  // Return a minimal simulation result with the fallback gas estimate
1080
1247
  return {
1081
- gasUsed: SequencerPublisher.PROPOSE_GAS_GUESS,
1248
+ gasUsed: MAX_L1_TX_LIMIT,
1082
1249
  logs: [],
1083
1250
  };
1084
1251
  }
@@ -1090,11 +1257,12 @@ export class SequencerPublisher {
1090
1257
  }
1091
1258
 
1092
1259
  private async addProposeTx(
1093
- block: L2Block,
1260
+ checkpoint: Checkpoint,
1094
1261
  encodedData: L1ProcessArgs,
1095
- opts: { txTimeoutAt?: Date; forcePendingBlockNumber?: number } = {},
1262
+ opts: { txTimeoutAt?: Date; forcePendingCheckpointNumber?: CheckpointNumber } = {},
1096
1263
  timestamp: bigint,
1097
1264
  ): Promise<void> {
1265
+ const slot = checkpoint.header.slotNumber;
1098
1266
  const timer = new Timer();
1099
1267
  const kzg = Blob.getViemKzgInstance();
1100
1268
  const { rollupData, simulationResult, blobEvaluationGas } = await this.prepareProposeTx(
@@ -1109,11 +1277,13 @@ export class SequencerPublisher {
1109
1277
  SequencerPublisher.MULTICALL_OVERHEAD_GAS_GUESS, // We issue the simulation against the rollup contract, so we need to account for the overhead of the multicall3
1110
1278
  );
1111
1279
 
1112
- // Send the blobs to the blob sink preemptively. This helps in tests where the sequencer mistakingly thinks that the propose
1113
- // tx fails but it does get mined. We make sure that the blobs are sent to the blob sink regardless of the tx outcome.
1114
- void this.blobSinkClient.sendBlobsToBlobSink(encodedData.blobs).catch(_err => {
1115
- this.log.error('Failed to send blobs to blob sink');
1116
- });
1280
+ // Send the blobs to the blob client preemptively. This helps in tests where the sequencer mistakingly thinks that the propose
1281
+ // tx fails but it does get mined. We make sure that the blobs are sent to the blob client regardless of the tx outcome.
1282
+ void Promise.resolve().then(() =>
1283
+ this.blobClient.sendBlobsToFilestore(encodedData.blobs).catch(_err => {
1284
+ this.log.error('Failed to send blobs to blob client');
1285
+ }),
1286
+ );
1117
1287
 
1118
1288
  return this.addRequest({
1119
1289
  action: 'propose',
@@ -1121,7 +1291,7 @@ export class SequencerPublisher {
1121
1291
  to: this.rollupContract.address,
1122
1292
  data: rollupData,
1123
1293
  },
1124
- lastValidL2Slot: block.header.globalVariables.slotNumber,
1294
+ lastValidL2Slot: checkpoint.header.slotNumber,
1125
1295
  gasConfig: { ...opts, gasLimit },
1126
1296
  blobConfig: {
1127
1297
  blobs: encodedData.blobs.map(b => b.data),
@@ -1136,11 +1306,12 @@ export class SequencerPublisher {
1136
1306
  receipt &&
1137
1307
  receipt.status === 'success' &&
1138
1308
  tryExtractEvent(receipt.logs, this.rollupContract.address, RollupAbi, 'CheckpointProposed');
1309
+
1139
1310
  if (success) {
1140
1311
  const endBlock = receipt.blockNumber;
1141
1312
  const inclusionBlocks = Number(endBlock - startBlock);
1142
1313
  const { calldataGas, calldataSize, sender } = stats!;
1143
- const publishStats: L1PublishBlockStats = {
1314
+ const publishStats: L1PublishCheckpointStats = {
1144
1315
  gasPrice: receipt.effectiveGasPrice,
1145
1316
  gasUsed: receipt.gasUsed,
1146
1317
  blobGasUsed: receipt.blobGasUsed ?? 0n,
@@ -1149,23 +1320,26 @@ export class SequencerPublisher {
1149
1320
  calldataGas,
1150
1321
  calldataSize,
1151
1322
  sender,
1152
- ...block.getStats(),
1323
+ ...checkpoint.getStats(),
1153
1324
  eventName: 'rollup-published-to-l1',
1154
1325
  blobCount: encodedData.blobs.length,
1155
1326
  inclusionBlocks,
1156
1327
  };
1157
- this.log.info(`Published L2 block to L1 rollup contract`, { ...stats, ...block.getStats(), ...receipt });
1328
+ this.log.info(`Published checkpoint ${checkpoint.number} at slot ${slot} to rollup contract`, {
1329
+ ...stats,
1330
+ ...checkpoint.getStats(),
1331
+ ...pick(receipt, 'transactionHash', 'blockHash'),
1332
+ });
1158
1333
  this.metrics.recordProcessBlockTx(timer.ms(), publishStats);
1159
1334
 
1160
1335
  return true;
1161
1336
  } else {
1162
1337
  this.metrics.recordFailedTx('process');
1163
- this.log.error(`Rollup process tx failed: ${errorMsg ?? 'no error message'}`, undefined, {
1164
- ...block.getStats(),
1165
- receipt,
1166
- txHash: receipt.transactionHash,
1167
- slotNumber: block.header.globalVariables.slotNumber,
1168
- });
1338
+ this.log.error(
1339
+ `Publishing checkpoint at slot ${slot} failed with ${errorMsg ?? 'no error message'}`,
1340
+ undefined,
1341
+ { ...checkpoint.getStats(), ...receipt },
1342
+ );
1169
1343
  return false;
1170
1344
  }
1171
1345
  },