@aztec/sequencer-client 0.0.1-commit.d3ec352c → 0.0.1-commit.e310a4c8

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 +12 -12
  2. package/dest/client/sequencer-client.d.ts.map +1 -1
  3. package/dest/client/sequencer-client.js +33 -26
  4. package/dest/config.d.ts +12 -5
  5. package/dest/config.d.ts.map +1 -1
  6. package/dest/config.js +73 -30
  7. package/dest/global_variable_builder/global_builder.d.ts +21 -12
  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 +7 -4
  14. package/dest/publisher/config.d.ts.map +1 -1
  15. package/dest/publisher/config.js +9 -3
  16. package/dest/publisher/sequencer-publisher-factory.d.ts +5 -4
  17. package/dest/publisher/sequencer-publisher-factory.d.ts.map +1 -1
  18. package/dest/publisher/sequencer-publisher-factory.js +1 -1
  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 +48 -41
  23. package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
  24. package/dest/publisher/sequencer-publisher.js +600 -129
  25. package/dest/sequencer/checkpoint_proposal_job.d.ts +96 -0
  26. package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -0
  27. package/dest/sequencer/checkpoint_proposal_job.js +1192 -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 +23 -3
  40. package/dest/sequencer/metrics.d.ts.map +1 -1
  41. package/dest/sequencer/metrics.js +143 -70
  42. package/dest/sequencer/sequencer.d.ts +107 -131
  43. package/dest/sequencer/sequencer.d.ts.map +1 -1
  44. package/dest/sequencer/sequencer.js +690 -602
  45. package/dest/sequencer/timetable.d.ts +54 -14
  46. package/dest/sequencer/timetable.d.ts.map +1 -1
  47. package/dest/sequencer/timetable.js +148 -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 +4 -3
  55. package/dest/test/index.d.ts.map +1 -1
  56. package/dest/test/mock_checkpoint_builder.d.ts +95 -0
  57. package/dest/test/mock_checkpoint_builder.d.ts.map +1 -0
  58. package/dest/test/mock_checkpoint_builder.js +220 -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 +103 -0
  62. package/package.json +30 -28
  63. package/src/client/sequencer-client.ts +31 -42
  64. package/src/config.ts +78 -34
  65. package/src/global_variable_builder/global_builder.ts +63 -59
  66. package/src/index.ts +1 -7
  67. package/src/publisher/config.ts +12 -9
  68. package/src/publisher/sequencer-publisher-factory.ts +5 -4
  69. package/src/publisher/sequencer-publisher-metrics.ts +19 -71
  70. package/src/publisher/sequencer-publisher.ts +293 -170
  71. package/src/sequencer/README.md +531 -0
  72. package/src/sequencer/checkpoint_proposal_job.ts +874 -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 +190 -78
  78. package/src/sequencer/sequencer.ts +430 -804
  79. package/src/sequencer/timetable.ts +173 -79
  80. package/src/sequencer/types.ts +6 -0
  81. package/src/sequencer/utils.ts +18 -9
  82. package/src/test/index.ts +3 -2
  83. package/src/test/mock_checkpoint_builder.ts +309 -0
  84. package/src/test/utils.ts +164 -0
  85. package/dest/sequencer/block_builder.d.ts +0 -28
  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 -18
  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 -133
@@ -1,44 +1,47 @@
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,
8
7
  type GovernanceProposerContract,
9
8
  type IEmpireBase,
10
- type L1BlobInputs,
11
- type L1ContractsConfig,
12
- type L1TxConfig,
13
- type L1TxRequest,
14
9
  MULTI_CALL_3_ADDRESS,
15
10
  Multicall3,
16
11
  RollupContract,
17
12
  type TallySlashingProposerContract,
18
- type TransactionStats,
19
13
  type ViemCommitteeAttestations,
20
14
  type ViemHeader,
15
+ } from '@aztec/ethereum/contracts';
16
+ import { type L1FeeAnalysisResult, L1FeeAnalyzer } from '@aztec/ethereum/l1-fee-analysis';
17
+ import {
18
+ type L1BlobInputs,
19
+ type L1TxConfig,
20
+ type L1TxRequest,
21
+ MAX_L1_TX_LIMIT,
22
+ type TransactionStats,
21
23
  WEI_CONST,
22
- formatViemError,
23
- tryExtractEvent,
24
- } from '@aztec/ethereum';
24
+ } from '@aztec/ethereum/l1-tx-utils';
25
25
  import type { L1TxUtilsWithBlobs } from '@aztec/ethereum/l1-tx-utils-with-blobs';
26
+ import { FormattedViemError, formatViemError, mergeAbis, tryExtractEvent } from '@aztec/ethereum/utils';
26
27
  import { sumBigint } from '@aztec/foundation/bigint';
27
28
  import { toHex as toPaddedHex } from '@aztec/foundation/bigint-buffer';
28
- import { BlockNumber, CheckpointNumber, SlotNumber } from '@aztec/foundation/branded-types';
29
+ import { CheckpointNumber, SlotNumber } from '@aztec/foundation/branded-types';
30
+ import { pick } from '@aztec/foundation/collection';
31
+ import type { Fr } from '@aztec/foundation/curves/bn254';
29
32
  import { EthAddress } from '@aztec/foundation/eth-address';
30
33
  import { Signature, type ViemSignature } from '@aztec/foundation/eth-signature';
31
- import type { Fr } from '@aztec/foundation/fields';
32
34
  import { type Logger, createLogger } from '@aztec/foundation/log';
33
35
  import { bufferToHex } from '@aztec/foundation/string';
34
36
  import { DateProvider, Timer } from '@aztec/foundation/timer';
35
37
  import { EmpireBaseAbi, ErrorsAbi, RollupAbi } from '@aztec/l1-artifacts';
36
38
  import { type ProposerSlashAction, encodeSlashConsensusVotes } from '@aztec/slasher';
37
- import { CommitteeAttestation, CommitteeAttestationsAndSigners, type ValidateBlockResult } from '@aztec/stdlib/block';
39
+ import { CommitteeAttestationsAndSigners, type ValidateCheckpointResult } from '@aztec/stdlib/block';
40
+ import type { Checkpoint } from '@aztec/stdlib/checkpoint';
38
41
  import { SlashFactoryContract } from '@aztec/stdlib/l1-contracts';
39
42
  import type { CheckpointHeader } from '@aztec/stdlib/rollup';
40
- import type { L1PublishBlockStats } from '@aztec/stdlib/stats';
41
- import { type TelemetryClient, getTelemetryClient } from '@aztec/telemetry-client';
43
+ import type { L1PublishCheckpointStats } from '@aztec/stdlib/stats';
44
+ import { type TelemetryClient, type Tracer, getTelemetryClient, trackSpan } from '@aztec/telemetry-client';
42
45
 
43
46
  import { type StateOverride, type TransactionReceipt, type TypedDataDefinition, encodeFunctionData, toHex } from 'viem';
44
47
 
@@ -78,12 +81,12 @@ type GovernanceSignalAction = Extract<Action, 'governance-signal' | 'empire-slas
78
81
  // Sorting for actions such that invalidations go before proposals, and proposals go before votes
79
82
  export const compareActions = (a: Action, b: Action) => Actions.indexOf(a) - Actions.indexOf(b);
80
83
 
81
- export type InvalidateBlockRequest = {
84
+ export type InvalidateCheckpointRequest = {
82
85
  request: L1TxRequest;
83
86
  reason: 'invalid-attestation' | 'insufficient-attestations';
84
87
  gasUsed: bigint;
85
- blockNumber: BlockNumber;
86
- forcePendingBlockNumber: BlockNumber;
88
+ checkpointNumber: CheckpointNumber;
89
+ forcePendingCheckpointNumber: CheckpointNumber;
87
90
  };
88
91
 
89
92
  interface RequestWithExpiry {
@@ -108,18 +111,18 @@ export class SequencerPublisher {
108
111
 
109
112
  protected lastActions: Partial<Record<Action, SlotNumber>> = {};
110
113
 
114
+ private isPayloadEmptyCache: Map<string, boolean> = new Map<string, boolean>();
115
+
111
116
  protected log: Logger;
112
117
  protected ethereumSlotDuration: bigint;
113
118
 
114
- private blobSinkClient: BlobSinkClientInterface;
119
+ private blobClient: BlobClientInterface;
115
120
 
116
121
  /** Address to use for simulations in fisherman mode (actual proposer's address) */
117
122
  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;
122
123
 
124
+ /** L1 fee analyzer for fisherman mode */
125
+ private l1FeeAnalyzer?: L1FeeAnalyzer;
123
126
  // A CALL to a cold address is 2700 gas
124
127
  public static MULTICALL_OVERHEAD_GAS_GUESS = 5000n;
125
128
 
@@ -132,13 +135,15 @@ export class SequencerPublisher {
132
135
  public slashingProposerContract: EmpireSlashingProposerContract | TallySlashingProposerContract | undefined;
133
136
  public slashFactoryContract: SlashFactoryContract;
134
137
 
138
+ public readonly tracer: Tracer;
139
+
135
140
  protected requests: RequestWithExpiry[] = [];
136
141
 
137
142
  constructor(
138
143
  private config: TxSenderConfig & PublisherConfig & Pick<L1ContractsConfig, 'ethereumSlotDuration'>,
139
144
  deps: {
140
145
  telemetry?: TelemetryClient;
141
- blobSinkClient?: BlobSinkClientInterface;
146
+ blobClient: BlobClientInterface;
142
147
  l1TxUtils: L1TxUtilsWithBlobs;
143
148
  rollupContract: RollupContract;
144
149
  slashingProposerContract: EmpireSlashingProposerContract | TallySlashingProposerContract | undefined;
@@ -156,11 +161,11 @@ export class SequencerPublisher {
156
161
  this.epochCache = deps.epochCache;
157
162
  this.lastActions = deps.lastActions;
158
163
 
159
- this.blobSinkClient =
160
- deps.blobSinkClient ?? createBlobSinkClient(config, { logger: createLogger('sequencer:blob-sink:client') });
164
+ this.blobClient = deps.blobClient;
161
165
 
162
166
  const telemetry = deps.telemetry ?? getTelemetryClient();
163
167
  this.metrics = deps.metrics ?? new SequencerPublisherMetrics(telemetry, 'SequencerPublisher');
168
+ this.tracer = telemetry.getTracer('SequencerPublisher');
164
169
  this.l1TxUtils = deps.l1TxUtils;
165
170
 
166
171
  this.rollupContract = deps.rollupContract;
@@ -174,6 +179,15 @@ export class SequencerPublisher {
174
179
  this.slashingProposerContract = newSlashingProposer;
175
180
  });
176
181
  this.slashFactoryContract = deps.slashFactoryContract;
182
+
183
+ // Initialize L1 fee analyzer for fisherman mode
184
+ if (config.fishermanMode) {
185
+ this.l1FeeAnalyzer = new L1FeeAnalyzer(
186
+ this.l1TxUtils.client,
187
+ deps.dateProvider,
188
+ createLogger('sequencer:publisher:fee-analyzer'),
189
+ );
190
+ }
177
191
  }
178
192
 
179
193
  public getRollupContract(): RollupContract {
@@ -184,6 +198,13 @@ export class SequencerPublisher {
184
198
  return this.l1TxUtils.getSenderAddress();
185
199
  }
186
200
 
201
+ /**
202
+ * Gets the L1 fee analyzer instance (only available in fisherman mode)
203
+ */
204
+ public getL1FeeAnalyzer(): L1FeeAnalyzer | undefined {
205
+ return this.l1FeeAnalyzer;
206
+ }
207
+
187
208
  /**
188
209
  * Sets the proposer address to use for simulations in fisherman mode.
189
210
  * @param proposerAddress - The actual proposer's address to use for balance lookups in simulations
@@ -211,6 +232,62 @@ export class SequencerPublisher {
211
232
  }
212
233
  }
213
234
 
235
+ /**
236
+ * Analyzes L1 fees for the pending requests without sending them.
237
+ * This is used in fisherman mode to validate fee calculations.
238
+ * @param l2SlotNumber - The L2 slot number for this analysis
239
+ * @param onComplete - Optional callback to invoke when analysis completes (after block is mined)
240
+ * @returns The analysis result (incomplete until block mines), or undefined if no requests
241
+ */
242
+ public async analyzeL1Fees(
243
+ l2SlotNumber: SlotNumber,
244
+ onComplete?: (analysis: L1FeeAnalysisResult) => void,
245
+ ): Promise<L1FeeAnalysisResult | undefined> {
246
+ if (!this.l1FeeAnalyzer) {
247
+ this.log.warn('L1 fee analyzer not available (not in fisherman mode)');
248
+ return undefined;
249
+ }
250
+
251
+ const requestsToAnalyze = [...this.requests];
252
+ if (requestsToAnalyze.length === 0) {
253
+ this.log.debug('No requests to analyze for L1 fees');
254
+ return undefined;
255
+ }
256
+
257
+ // Extract blob config from requests (if any)
258
+ const blobConfigs = requestsToAnalyze.filter(request => request.blobConfig).map(request => request.blobConfig);
259
+ const blobConfig = blobConfigs[0];
260
+
261
+ // Get gas configs
262
+ const gasConfigs = requestsToAnalyze.filter(request => request.gasConfig).map(request => request.gasConfig);
263
+ const gasLimits = gasConfigs.map(g => g?.gasLimit).filter((g): g is bigint => g !== undefined);
264
+ const gasLimit = gasLimits.length > 0 ? gasLimits.reduce((sum, g) => sum + g, 0n) : 0n;
265
+
266
+ // Get the transaction requests
267
+ const l1Requests = requestsToAnalyze.map(r => r.request);
268
+
269
+ // Start the analysis
270
+ const analysisId = await this.l1FeeAnalyzer.startAnalysis(
271
+ l2SlotNumber,
272
+ gasLimit > 0n ? gasLimit : MAX_L1_TX_LIMIT,
273
+ l1Requests,
274
+ blobConfig,
275
+ onComplete,
276
+ );
277
+
278
+ this.log.info('Started L1 fee analysis', {
279
+ analysisId,
280
+ l2SlotNumber: l2SlotNumber.toString(),
281
+ requestCount: requestsToAnalyze.length,
282
+ hasBlobConfig: !!blobConfig,
283
+ gasLimit: gasLimit.toString(),
284
+ actions: requestsToAnalyze.map(r => r.action),
285
+ });
286
+
287
+ // Return the analysis result (will be incomplete until block mines)
288
+ return this.l1FeeAnalyzer.getAnalysis(analysisId);
289
+ }
290
+
214
291
  /**
215
292
  * Sends all requests that are still valid.
216
293
  * @returns one of:
@@ -218,10 +295,11 @@ export class SequencerPublisher {
218
295
  * - a receipt and errorMsg if it failed on L1
219
296
  * - undefined if no valid requests are found OR the tx failed to send.
220
297
  */
298
+ @trackSpan('SequencerPublisher.sendRequests')
221
299
  public async sendRequests() {
222
300
  const requestsToProcess = [...this.requests];
223
301
  this.requests = [];
224
- if (this.interrupted) {
302
+ if (this.interrupted || requestsToProcess.length === 0) {
225
303
  return undefined;
226
304
  }
227
305
  const currentL2Slot = this.getCurrentL2Slot();
@@ -264,7 +342,16 @@ export class SequencerPublisher {
264
342
 
265
343
  // Merge gasConfigs. Yields the sum of gasLimits, and the earliest txTimeoutAt, or undefined if no gasConfig sets them.
266
344
  const gasLimits = gasConfigs.map(g => g?.gasLimit).filter((g): g is bigint => g !== undefined);
267
- const gasLimit = gasLimits.length > 0 ? sumBigint(gasLimits) : undefined; // sum
345
+ let gasLimit = gasLimits.length > 0 ? sumBigint(gasLimits) : undefined; // sum
346
+ // Cap at L1 block gas limit so the node accepts the tx ("gas limit too high" otherwise).
347
+ const maxGas = MAX_L1_TX_LIMIT;
348
+ if (gasLimit !== undefined && gasLimit > maxGas) {
349
+ this.log.debug('Capping bundled tx gas limit to L1 max', {
350
+ requested: gasLimit,
351
+ capped: maxGas,
352
+ });
353
+ gasLimit = maxGas;
354
+ }
268
355
  const txTimeoutAts = gasConfigs.map(g => g?.txTimeoutAt).filter((g): g is Date => g !== undefined);
269
356
  const txTimeoutAt = txTimeoutAts.length > 0 ? new Date(Math.min(...txTimeoutAts.map(g => g.getTime()))) : undefined; // earliest
270
357
  const txConfig: RequestWithExpiry['gasConfig'] = { gasLimit, txTimeoutAt };
@@ -335,17 +422,14 @@ export class SequencerPublisher {
335
422
  public canProposeAtNextEthBlock(
336
423
  tipArchive: Fr,
337
424
  msgSender: EthAddress,
338
- opts: { forcePendingBlockNumber?: BlockNumber } = {},
425
+ opts: { forcePendingCheckpointNumber?: CheckpointNumber } = {},
339
426
  ) {
340
427
  // TODO: #14291 - should loop through multiple keys to check if any of them can propose
341
428
  const ignoredErrors = ['SlotAlreadyInChain', 'InvalidProposer', 'InvalidArchive'];
342
429
 
343
430
  return this.rollupContract
344
431
  .canProposeAtNextEthBlock(tipArchive.toBuffer(), msgSender.toString(), Number(this.ethereumSlotDuration), {
345
- forcePendingCheckpointNumber:
346
- opts.forcePendingBlockNumber !== undefined
347
- ? CheckpointNumber.fromBlockNumber(opts.forcePendingBlockNumber)
348
- : undefined,
432
+ forcePendingCheckpointNumber: opts.forcePendingCheckpointNumber,
349
433
  })
350
434
  .catch(err => {
351
435
  if (err instanceof FormattedViemError && ignoredErrors.find(e => err.message.includes(e))) {
@@ -364,10 +448,11 @@ export class SequencerPublisher {
364
448
  * It will throw if the block header is invalid.
365
449
  * @param header - The block header to validate
366
450
  */
451
+ @trackSpan('SequencerPublisher.validateBlockHeader')
367
452
  public async validateBlockHeader(
368
453
  header: CheckpointHeader,
369
- opts?: { forcePendingBlockNumber: BlockNumber | undefined },
370
- ) {
454
+ opts?: { forcePendingCheckpointNumber: CheckpointNumber | undefined },
455
+ ): Promise<void> {
371
456
  const flags = { ignoreDA: true, ignoreSignatures: true };
372
457
 
373
458
  const args = [
@@ -376,17 +461,13 @@ export class SequencerPublisher {
376
461
  [], // no signers
377
462
  Signature.empty().toViemSignature(),
378
463
  `0x${'0'.repeat(64)}`, // 32 empty bytes
379
- header.contentCommitment.blobsHash.toString(),
464
+ header.blobsHash.toString(),
380
465
  flags,
381
466
  ] as const;
382
467
 
383
468
  const ts = BigInt((await this.l1TxUtils.getBlock()).timestamp + this.ethereumSlotDuration);
384
- const optsForcePendingCheckpointNumber =
385
- opts?.forcePendingBlockNumber !== undefined
386
- ? CheckpointNumber.fromBlockNumber(opts.forcePendingBlockNumber)
387
- : undefined;
388
469
  const stateOverrides = await this.rollupContract.makePendingCheckpointNumberOverride(
389
- optsForcePendingCheckpointNumber,
470
+ opts?.forcePendingCheckpointNumber,
390
471
  );
391
472
  let balance = 0n;
392
473
  if (this.config.fishermanMode) {
@@ -414,77 +495,95 @@ export class SequencerPublisher {
414
495
  }
415
496
 
416
497
  /**
417
- * Simulate making a call to invalidate a block with invalid attestations. Returns undefined if no need to invalidate.
418
- * @param block - The block to invalidate and the criteria for invalidation (as returned by the archiver)
498
+ * Simulate making a call to invalidate a checkpoint with invalid attestations. Returns undefined if no need to invalidate.
499
+ * @param validationResult - The validation result indicating which checkpoint to invalidate (as returned by the archiver)
419
500
  */
420
- public async simulateInvalidateBlock(
421
- validationResult: ValidateBlockResult,
422
- ): Promise<InvalidateBlockRequest | undefined> {
501
+ public async simulateInvalidateCheckpoint(
502
+ validationResult: ValidateCheckpointResult,
503
+ ): Promise<InvalidateCheckpointRequest | undefined> {
423
504
  if (validationResult.valid) {
424
505
  return undefined;
425
506
  }
426
507
 
427
- const { reason, block } = validationResult;
428
- const blockNumber = block.blockNumber;
429
- const logData = { ...block, reason };
508
+ const { reason, checkpoint } = validationResult;
509
+ const checkpointNumber = checkpoint.checkpointNumber;
510
+ const logData = { ...checkpoint, reason };
430
511
 
431
- const currentBlockNumber = await this.rollupContract.getCheckpointNumber();
432
- if (currentBlockNumber < validationResult.block.blockNumber) {
512
+ const currentCheckpointNumber = await this.rollupContract.getCheckpointNumber();
513
+ if (currentCheckpointNumber < checkpointNumber) {
433
514
  this.log.verbose(
434
- `Skipping block ${blockNumber} invalidation since it has already been removed from the pending chain`,
435
- { currentBlockNumber, ...logData },
515
+ `Skipping checkpoint ${checkpointNumber} invalidation since it has already been removed from the pending chain`,
516
+ { currentCheckpointNumber, ...logData },
436
517
  );
437
518
  return undefined;
438
519
  }
439
520
 
440
- const request = this.buildInvalidateBlockRequest(validationResult);
441
- this.log.debug(`Simulating invalidate block ${blockNumber}`, { ...logData, request });
521
+ const request = this.buildInvalidateCheckpointRequest(validationResult);
522
+ this.log.debug(`Simulating invalidate checkpoint ${checkpointNumber}`, { ...logData, request });
442
523
 
443
524
  try {
444
- const { gasUsed } = await this.l1TxUtils.simulate(request, undefined, undefined, ErrorsAbi);
445
- this.log.verbose(`Simulation for invalidate block ${blockNumber} succeeded`, { ...logData, request, gasUsed });
525
+ const { gasUsed } = await this.l1TxUtils.simulate(
526
+ request,
527
+ undefined,
528
+ undefined,
529
+ mergeAbis([request.abi ?? [], ErrorsAbi]),
530
+ );
531
+ this.log.verbose(`Simulation for invalidate checkpoint ${checkpointNumber} succeeded`, {
532
+ ...logData,
533
+ request,
534
+ gasUsed,
535
+ });
446
536
 
447
- return { request, gasUsed, blockNumber, forcePendingBlockNumber: BlockNumber(blockNumber - 1), reason };
537
+ return {
538
+ request,
539
+ gasUsed,
540
+ checkpointNumber,
541
+ forcePendingCheckpointNumber: CheckpointNumber(checkpointNumber - 1),
542
+ reason,
543
+ };
448
544
  } catch (err) {
449
545
  const viemError = formatViemError(err);
450
546
 
451
- // If the error is due to the block not being in the pending chain, and it was indeed removed by someone else,
452
- // we can safely ignore it and return undefined so we go ahead with block building.
453
- if (viemError.message?.includes('Rollup__BlockNotInPendingChain')) {
547
+ // If the error is due to the checkpoint not being in the pending chain, and it was indeed removed by someone else,
548
+ // we can safely ignore it and return undefined so we go ahead with checkpoint building.
549
+ if (viemError.message?.includes('Rollup__CheckpointNotInPendingChain')) {
454
550
  this.log.verbose(
455
- `Simulation for invalidate block ${blockNumber} failed due to block not being in pending chain`,
551
+ `Simulation for invalidate checkpoint ${checkpointNumber} failed due to checkpoint not being in pending chain`,
456
552
  { ...logData, request, error: viemError.message },
457
553
  );
458
- const latestPendingBlockNumber = await this.rollupContract.getCheckpointNumber();
459
- if (latestPendingBlockNumber < blockNumber) {
460
- this.log.verbose(`Block number ${blockNumber} has already been invalidated`, { ...logData });
554
+ const latestPendingCheckpointNumber = await this.rollupContract.getCheckpointNumber();
555
+ if (latestPendingCheckpointNumber < checkpointNumber) {
556
+ this.log.verbose(`Checkpoint ${checkpointNumber} has already been invalidated`, { ...logData });
461
557
  return undefined;
462
558
  } else {
463
559
  this.log.error(
464
- `Simulation for invalidate ${blockNumber} failed and it is still in pending chain`,
560
+ `Simulation for invalidate checkpoint ${checkpointNumber} failed and it is still in pending chain`,
465
561
  viemError,
466
562
  logData,
467
563
  );
468
- throw new Error(`Failed to simulate invalidate block ${blockNumber} while it is still in pending chain`, {
469
- cause: viemError,
470
- });
564
+ throw new Error(
565
+ `Failed to simulate invalidate checkpoint ${checkpointNumber} while it is still in pending chain`,
566
+ {
567
+ cause: viemError,
568
+ },
569
+ );
471
570
  }
472
571
  }
473
572
 
474
- // Otherwise, throw. We cannot build the next block if we cannot invalidate the previous one.
475
- this.log.error(`Simulation for invalidate block ${blockNumber} failed`, viemError, logData);
476
- throw new Error(`Failed to simulate invalidate block ${blockNumber}`, { cause: viemError });
573
+ // Otherwise, throw. We cannot build the next checkpoint if we cannot invalidate the previous one.
574
+ this.log.error(`Simulation for invalidate checkpoint ${checkpointNumber} failed`, viemError, logData);
575
+ throw new Error(`Failed to simulate invalidate checkpoint ${checkpointNumber}`, { cause: viemError });
477
576
  }
478
577
  }
479
578
 
480
- private buildInvalidateBlockRequest(validationResult: ValidateBlockResult) {
579
+ private buildInvalidateCheckpointRequest(validationResult: ValidateCheckpointResult) {
481
580
  if (validationResult.valid) {
482
- throw new Error('Cannot invalidate a valid block');
581
+ throw new Error('Cannot invalidate a valid checkpoint');
483
582
  }
484
583
 
485
- const { block, committee, reason } = validationResult;
486
- const logData = { ...block, reason };
487
- this.log.debug(`Simulating invalidate block ${block.blockNumber}`, logData);
584
+ const { checkpoint, committee, reason } = validationResult;
585
+ const logData = { ...checkpoint, reason };
586
+ this.log.debug(`Building invalidate checkpoint ${checkpoint.checkpointNumber} request`, logData);
488
587
 
489
588
  const attestationsAndSigners = new CommitteeAttestationsAndSigners(
490
589
  validationResult.attestations,
@@ -492,14 +591,14 @@ export class SequencerPublisher {
492
591
 
493
592
  if (reason === 'invalid-attestation') {
494
593
  return this.rollupContract.buildInvalidateBadAttestationRequest(
495
- CheckpointNumber.fromBlockNumber(block.blockNumber),
594
+ checkpoint.checkpointNumber,
496
595
  attestationsAndSigners,
497
596
  committee,
498
597
  validationResult.invalidIndex,
499
598
  );
500
599
  } else if (reason === 'insufficient-attestations') {
501
600
  return this.rollupContract.buildInvalidateInsufficientAttestationsRequest(
502
- CheckpointNumber.fromBlockNumber(block.blockNumber),
601
+ checkpoint.checkpointNumber,
503
602
  attestationsAndSigners,
504
603
  committee,
505
604
  );
@@ -509,45 +608,39 @@ export class SequencerPublisher {
509
608
  }
510
609
  }
511
610
 
512
- /**
513
- * @notice Will simulate `propose` to make sure that the block is valid for submission
514
- *
515
- * @dev Throws if unable to propose
516
- *
517
- * @param block - The block to propose
518
- * @param attestationData - The block's attestation data
519
- *
520
- */
521
- public async validateBlockForSubmission(
522
- block: L2Block,
611
+ /** Simulates `propose` to make sure that the checkpoint is valid for submission */
612
+ @trackSpan('SequencerPublisher.validateCheckpointForSubmission')
613
+ public async validateCheckpointForSubmission(
614
+ checkpoint: Checkpoint,
523
615
  attestationsAndSigners: CommitteeAttestationsAndSigners,
524
616
  attestationsAndSignersSignature: Signature,
525
- options: { forcePendingBlockNumber?: BlockNumber },
617
+ options: { forcePendingCheckpointNumber?: CheckpointNumber },
526
618
  ): Promise<bigint> {
527
619
  const ts = BigInt((await this.l1TxUtils.getBlock()).timestamp + this.ethereumSlotDuration);
528
620
 
621
+ // TODO(palla/mbps): This should not be needed, there's no flow where we propose with zero attestations. Or is there?
529
622
  // If we have no attestations, we still need to provide the empty attestations
530
623
  // so that the committee is recalculated correctly
531
- const ignoreSignatures = attestationsAndSigners.attestations.length === 0;
532
- if (ignoreSignatures) {
533
- const { committee } = await this.epochCache.getCommittee(block.header.globalVariables.slotNumber);
534
- if (!committee) {
535
- this.log.warn(`No committee found for slot ${block.header.globalVariables.slotNumber}`);
536
- throw new Error(`No committee found for slot ${block.header.globalVariables.slotNumber}`);
537
- }
538
- attestationsAndSigners.attestations = committee.map(committeeMember =>
539
- CommitteeAttestation.fromAddress(committeeMember),
540
- );
541
- }
542
-
543
- const blobFields = block.getCheckpointBlobFields();
624
+ // const ignoreSignatures = attestationsAndSigners.attestations.length === 0;
625
+ // if (ignoreSignatures) {
626
+ // const { committee } = await this.epochCache.getCommittee(block.header.globalVariables.slotNumber);
627
+ // if (!committee) {
628
+ // this.log.warn(`No committee found for slot ${block.header.globalVariables.slotNumber}`);
629
+ // throw new Error(`No committee found for slot ${block.header.globalVariables.slotNumber}`);
630
+ // }
631
+ // attestationsAndSigners.attestations = committee.map(committeeMember =>
632
+ // CommitteeAttestation.fromAddress(committeeMember),
633
+ // );
634
+ // }
635
+
636
+ const blobFields = checkpoint.toBlobFields();
544
637
  const blobs = getBlobsPerL1Block(blobFields);
545
638
  const blobInput = getPrefixedEthBlobCommitments(blobs);
546
639
 
547
640
  const args = [
548
641
  {
549
- header: block.getCheckpointHeader().toViem(),
550
- archive: toHex(block.archive.root.toBuffer()),
642
+ header: checkpoint.header.toViem(),
643
+ archive: toHex(checkpoint.archive.root.toBuffer()),
551
644
  oracleInput: {
552
645
  feeAssetPriceModifier: 0n,
553
646
  },
@@ -585,10 +678,19 @@ export class SequencerPublisher {
585
678
  const round = await base.computeRound(slotNumber);
586
679
  const roundInfo = await base.getRoundInfo(this.rollupContract.address, round);
587
680
 
681
+ if (roundInfo.quorumReached) {
682
+ return false;
683
+ }
684
+
588
685
  if (roundInfo.lastSignalSlot >= slotNumber) {
589
686
  return false;
590
687
  }
591
688
 
689
+ if (await this.isPayloadEmpty(payload)) {
690
+ this.log.warn(`Skipping vote cast for payload with empty code`);
691
+ return false;
692
+ }
693
+
592
694
  const cachedLastVote = this.lastActions[signalType];
593
695
  this.lastActions[signalType] = slotNumber;
594
696
  const action = signalType;
@@ -608,7 +710,7 @@ export class SequencerPublisher {
608
710
  });
609
711
 
610
712
  try {
611
- await this.l1TxUtils.simulate(request, { time: timestamp }, [], ErrorsAbi);
713
+ await this.l1TxUtils.simulate(request, { time: timestamp }, [], mergeAbis([request.abi ?? [], ErrorsAbi]));
612
714
  this.log.debug(`Simulation for ${action} at slot ${slotNumber} succeeded`, { request });
613
715
  } catch (err) {
614
716
  this.log.error(`Failed simulation for ${action} at slot ${slotNumber} (enqueuing the action anyway)`, err);
@@ -631,14 +733,14 @@ export class SequencerPublisher {
631
733
  const logData = { ...result, slotNumber, round, payload: payload.toString() };
632
734
  if (!success) {
633
735
  this.log.error(
634
- `Signaling in [${action}] for ${payload} at slot ${slotNumber} in round ${round} failed`,
736
+ `Signaling in ${action} for ${payload} at slot ${slotNumber} in round ${round} failed`,
635
737
  logData,
636
738
  );
637
739
  this.lastActions[signalType] = cachedLastVote;
638
740
  return false;
639
741
  } else {
640
742
  this.log.info(
641
- `Signaling in [${action}] for ${payload} at slot ${slotNumber} in round ${round} succeeded`,
743
+ `Signaling in ${action} for ${payload} at slot ${slotNumber} in round ${round} succeeded`,
642
744
  logData,
643
745
  );
644
746
  return true;
@@ -648,6 +750,17 @@ export class SequencerPublisher {
648
750
  return true;
649
751
  }
650
752
 
753
+ private async isPayloadEmpty(payload: EthAddress): Promise<boolean> {
754
+ const key = payload.toString();
755
+ const cached = this.isPayloadEmptyCache.get(key);
756
+ if (cached) {
757
+ return cached;
758
+ }
759
+ const isEmpty = !(await this.l1TxUtils.getCode(payload));
760
+ this.isPayloadEmptyCache.set(key, isEmpty);
761
+ return isEmpty;
762
+ }
763
+
651
764
  /**
652
765
  * Enqueues a governance castSignal transaction to cast a signal for a given slot number.
653
766
  * @param slotNumber - The slot number to cast a signal for.
@@ -795,27 +908,21 @@ export class SequencerPublisher {
795
908
  return true;
796
909
  }
797
910
 
798
- /**
799
- * Proposes a L2 block on L1.
800
- *
801
- * @param block - L2 block to propose.
802
- * @returns True if the tx has been enqueued, throws otherwise. See #9315
803
- */
804
- public async enqueueProposeL2Block(
805
- block: L2Block,
911
+ /** Simulates and enqueues a proposal for a checkpoint on L1 */
912
+ public async enqueueProposeCheckpoint(
913
+ checkpoint: Checkpoint,
806
914
  attestationsAndSigners: CommitteeAttestationsAndSigners,
807
915
  attestationsAndSignersSignature: Signature,
808
- opts: { txTimeoutAt?: Date; forcePendingBlockNumber?: BlockNumber } = {},
809
- ): Promise<boolean> {
810
- const checkpointHeader = block.getCheckpointHeader();
916
+ opts: { txTimeoutAt?: Date; forcePendingCheckpointNumber?: CheckpointNumber } = {},
917
+ ): Promise<void> {
918
+ const checkpointHeader = checkpoint.header;
811
919
 
812
- const blobFields = block.getCheckpointBlobFields();
920
+ const blobFields = checkpoint.toBlobFields();
813
921
  const blobs = getBlobsPerL1Block(blobFields);
814
922
 
815
923
  const proposeTxArgs = {
816
924
  header: checkpointHeader,
817
- archive: block.archive.root.toBuffer(),
818
- body: block.body.toBuffer(),
925
+ archive: checkpoint.archive.root.toBuffer(),
819
926
  blobs,
820
927
  attestationsAndSigners,
821
928
  attestationsAndSignersSignature,
@@ -829,22 +936,29 @@ export class SequencerPublisher {
829
936
  // By simulation issue, I mean the fact that the block.timestamp is equal to the last block, not the next, which
830
937
  // make time consistency checks break.
831
938
  // TODO(palla): Check whether we're validating twice, once here and once within addProposeTx, since we call simulateProposeTx in both places.
832
- ts = await this.validateBlockForSubmission(block, attestationsAndSigners, attestationsAndSignersSignature, opts);
939
+ ts = await this.validateCheckpointForSubmission(
940
+ checkpoint,
941
+ attestationsAndSigners,
942
+ attestationsAndSignersSignature,
943
+ opts,
944
+ );
833
945
  } catch (err: any) {
834
- this.log.error(`Block validation failed. ${err instanceof Error ? err.message : 'No error message'}`, err, {
835
- ...block.getStats(),
836
- slotNumber: block.header.globalVariables.slotNumber,
837
- forcePendingBlockNumber: opts.forcePendingBlockNumber,
946
+ this.log.error(`Checkpoint validation failed. ${err instanceof Error ? err.message : 'No error message'}`, err, {
947
+ ...checkpoint.getStats(),
948
+ slotNumber: checkpoint.header.slotNumber,
949
+ forcePendingCheckpointNumber: opts.forcePendingCheckpointNumber,
838
950
  });
839
951
  throw err;
840
952
  }
841
953
 
842
- this.log.verbose(`Enqueuing block propose transaction`, { ...block.toBlockInfo(), ...opts });
843
- await this.addProposeTx(block, proposeTxArgs, opts, ts);
844
- return true;
954
+ this.log.verbose(`Enqueuing checkpoint propose transaction`, { ...checkpoint.toCheckpointInfo(), ...opts });
955
+ await this.addProposeTx(checkpoint, proposeTxArgs, opts, ts);
845
956
  }
846
957
 
847
- public enqueueInvalidateBlock(request: InvalidateBlockRequest | undefined, opts: { txTimeoutAt?: Date } = {}) {
958
+ public enqueueInvalidateCheckpoint(
959
+ request: InvalidateCheckpointRequest | undefined,
960
+ opts: { txTimeoutAt?: Date } = {},
961
+ ) {
848
962
  if (!request) {
849
963
  return;
850
964
  }
@@ -852,9 +966,9 @@ export class SequencerPublisher {
852
966
  // We issued the simulation against the rollup contract, so we need to account for the overhead of the multicall3
853
967
  const gasLimit = this.l1TxUtils.bumpGasLimit(BigInt(Math.ceil((Number(request.gasUsed) * 64) / 63)));
854
968
 
855
- const { gasUsed, blockNumber } = request;
856
- const logData = { gasUsed, blockNumber, gasLimit, opts };
857
- this.log.verbose(`Enqueuing invalidate block request`, logData);
969
+ const { gasUsed, checkpointNumber } = request;
970
+ const logData = { gasUsed, checkpointNumber, gasLimit, opts };
971
+ this.log.verbose(`Enqueuing invalidate checkpoint request`, logData);
858
972
  this.addRequest({
859
973
  action: `invalidate-by-${request.reason}`,
860
974
  request: request.request,
@@ -867,9 +981,9 @@ export class SequencerPublisher {
867
981
  result.receipt.status === 'success' &&
868
982
  tryExtractEvent(result.receipt.logs, this.rollupContract.address, RollupAbi, 'CheckpointInvalidated');
869
983
  if (!success) {
870
- this.log.warn(`Invalidate block ${request.blockNumber} failed`, { ...result, ...logData });
984
+ this.log.warn(`Invalidate checkpoint ${request.checkpointNumber} failed`, { ...result, ...logData });
871
985
  } else {
872
- this.log.info(`Invalidate block ${request.blockNumber} succeeded`, { ...result, ...logData });
986
+ this.log.info(`Invalidate checkpoint ${request.checkpointNumber} succeeded`, { ...result, ...logData });
873
987
  }
874
988
  return !!success;
875
989
  },
@@ -895,12 +1009,14 @@ export class SequencerPublisher {
895
1009
  this.log.debug(`Simulating ${action} for slot ${slotNumber}`, logData);
896
1010
 
897
1011
  let gasUsed: bigint;
1012
+ const simulateAbi = mergeAbis([request.abi ?? [], ErrorsAbi]);
898
1013
  try {
899
- ({ gasUsed } = await this.l1TxUtils.simulate(request, { time: timestamp }, [], ErrorsAbi)); // TODO(palla/slash): Check the timestamp logic
1014
+ ({ gasUsed } = await this.l1TxUtils.simulate(request, { time: timestamp }, [], simulateAbi)); // TODO(palla/slash): Check the timestamp logic
900
1015
  this.log.verbose(`Simulation for ${action} succeeded`, { ...logData, request, gasUsed });
901
1016
  } catch (err) {
902
- const viemError = formatViemError(err);
1017
+ const viemError = formatViemError(err, simulateAbi);
903
1018
  this.log.error(`Simulation for ${action} at ${slotNumber} failed`, viemError, logData);
1019
+
904
1020
  return false;
905
1021
  }
906
1022
 
@@ -908,10 +1024,14 @@ export class SequencerPublisher {
908
1024
  const gasLimit = this.l1TxUtils.bumpGasLimit(BigInt(Math.ceil((Number(gasUsed) * 64) / 63)));
909
1025
  logData.gasLimit = gasLimit;
910
1026
 
1027
+ // Store the ABI used for simulation on the request so Multicall3.forward can decode errors
1028
+ // when the tx is sent and a revert is diagnosed via simulation.
1029
+ const requestWithAbi = { ...request, abi: simulateAbi };
1030
+
911
1031
  this.log.debug(`Enqueuing ${action}`, logData);
912
1032
  this.addRequest({
913
1033
  action,
914
- request,
1034
+ request: requestWithAbi,
915
1035
  gasConfig: { gasLimit },
916
1036
  lastValidL2Slot: slotNumber,
917
1037
  checkSuccess: (_req, result) => {
@@ -948,7 +1068,7 @@ export class SequencerPublisher {
948
1068
  private async prepareProposeTx(
949
1069
  encodedData: L1ProcessArgs,
950
1070
  timestamp: bigint,
951
- options: { forcePendingBlockNumber?: BlockNumber },
1071
+ options: { forcePendingCheckpointNumber?: CheckpointNumber },
952
1072
  ) {
953
1073
  const kzg = Blob.getViemKzgInstance();
954
1074
  const blobInput = getPrefixedEthBlobCommitments(encodedData.blobs);
@@ -1029,7 +1149,7 @@ export class SequencerPublisher {
1029
1149
  `0x${string}`,
1030
1150
  ],
1031
1151
  timestamp: bigint,
1032
- options: { forcePendingBlockNumber?: BlockNumber },
1152
+ options: { forcePendingCheckpointNumber?: CheckpointNumber },
1033
1153
  ) {
1034
1154
  const rollupData = encodeFunctionData({
1035
1155
  abi: RollupAbi,
@@ -1038,13 +1158,9 @@ export class SequencerPublisher {
1038
1158
  });
1039
1159
 
1040
1160
  // override the pending checkpoint number if requested
1041
- const optsForcePendingCheckpointNumber =
1042
- options.forcePendingBlockNumber !== undefined
1043
- ? CheckpointNumber.fromBlockNumber(options.forcePendingBlockNumber)
1044
- : undefined;
1045
1161
  const forcePendingCheckpointNumberStateDiff = (
1046
- optsForcePendingCheckpointNumber !== undefined
1047
- ? await this.rollupContract.makePendingCheckpointNumberOverride(optsForcePendingCheckpointNumber)
1162
+ options.forcePendingCheckpointNumber !== undefined
1163
+ ? await this.rollupContract.makePendingCheckpointNumberOverride(options.forcePendingCheckpointNumber)
1048
1164
  : []
1049
1165
  ).flatMap(override => override.stateDiff ?? []);
1050
1166
 
@@ -1071,20 +1187,20 @@ export class SequencerPublisher {
1071
1187
  {
1072
1188
  to: this.rollupContract.address,
1073
1189
  data: rollupData,
1074
- gas: SequencerPublisher.PROPOSE_GAS_GUESS,
1190
+ gas: MAX_L1_TX_LIMIT,
1075
1191
  ...(this.proposerAddressForSimulation && { from: this.proposerAddressForSimulation.toString() }),
1076
1192
  },
1077
1193
  {
1078
1194
  // @note we add 1n to the timestamp because geth implementation doesn't like simulation timestamp to be equal to the current block timestamp
1079
1195
  time: timestamp + 1n,
1080
1196
  // @note reth should have a 30m gas limit per block but throws errors that this tx is beyond limit so we increase here
1081
- gasLimit: SequencerPublisher.PROPOSE_GAS_GUESS * 2n,
1197
+ gasLimit: MAX_L1_TX_LIMIT * 2n,
1082
1198
  },
1083
1199
  stateOverrides,
1084
1200
  RollupAbi,
1085
1201
  {
1086
1202
  // @note fallback gas estimate to use if the node doesn't support simulation API
1087
- fallbackGasEstimate: SequencerPublisher.PROPOSE_GAS_GUESS,
1203
+ fallbackGasEstimate: MAX_L1_TX_LIMIT,
1088
1204
  },
1089
1205
  )
1090
1206
  .catch(err => {
@@ -1094,7 +1210,7 @@ export class SequencerPublisher {
1094
1210
  this.log.debug(`Ignoring expected ValidatorSelection__MissingProposerSignature error in fisherman mode`);
1095
1211
  // Return a minimal simulation result with the fallback gas estimate
1096
1212
  return {
1097
- gasUsed: SequencerPublisher.PROPOSE_GAS_GUESS,
1213
+ gasUsed: MAX_L1_TX_LIMIT,
1098
1214
  logs: [],
1099
1215
  };
1100
1216
  }
@@ -1106,11 +1222,12 @@ export class SequencerPublisher {
1106
1222
  }
1107
1223
 
1108
1224
  private async addProposeTx(
1109
- block: L2Block,
1225
+ checkpoint: Checkpoint,
1110
1226
  encodedData: L1ProcessArgs,
1111
- opts: { txTimeoutAt?: Date; forcePendingBlockNumber?: BlockNumber } = {},
1227
+ opts: { txTimeoutAt?: Date; forcePendingCheckpointNumber?: CheckpointNumber } = {},
1112
1228
  timestamp: bigint,
1113
1229
  ): Promise<void> {
1230
+ const slot = checkpoint.header.slotNumber;
1114
1231
  const timer = new Timer();
1115
1232
  const kzg = Blob.getViemKzgInstance();
1116
1233
  const { rollupData, simulationResult, blobEvaluationGas } = await this.prepareProposeTx(
@@ -1125,11 +1242,13 @@ export class SequencerPublisher {
1125
1242
  SequencerPublisher.MULTICALL_OVERHEAD_GAS_GUESS, // We issue the simulation against the rollup contract, so we need to account for the overhead of the multicall3
1126
1243
  );
1127
1244
 
1128
- // Send the blobs to the blob sink preemptively. This helps in tests where the sequencer mistakingly thinks that the propose
1129
- // tx fails but it does get mined. We make sure that the blobs are sent to the blob sink regardless of the tx outcome.
1130
- void this.blobSinkClient.sendBlobsToBlobSink(encodedData.blobs).catch(_err => {
1131
- this.log.error('Failed to send blobs to blob sink');
1132
- });
1245
+ // Send the blobs to the blob client preemptively. This helps in tests where the sequencer mistakingly thinks that the propose
1246
+ // tx fails but it does get mined. We make sure that the blobs are sent to the blob client regardless of the tx outcome.
1247
+ void Promise.resolve().then(() =>
1248
+ this.blobClient.sendBlobsToFilestore(encodedData.blobs).catch(_err => {
1249
+ this.log.error('Failed to send blobs to blob client');
1250
+ }),
1251
+ );
1133
1252
 
1134
1253
  return this.addRequest({
1135
1254
  action: 'propose',
@@ -1137,7 +1256,7 @@ export class SequencerPublisher {
1137
1256
  to: this.rollupContract.address,
1138
1257
  data: rollupData,
1139
1258
  },
1140
- lastValidL2Slot: block.header.globalVariables.slotNumber,
1259
+ lastValidL2Slot: checkpoint.header.slotNumber,
1141
1260
  gasConfig: { ...opts, gasLimit },
1142
1261
  blobConfig: {
1143
1262
  blobs: encodedData.blobs.map(b => b.data),
@@ -1152,11 +1271,12 @@ export class SequencerPublisher {
1152
1271
  receipt &&
1153
1272
  receipt.status === 'success' &&
1154
1273
  tryExtractEvent(receipt.logs, this.rollupContract.address, RollupAbi, 'CheckpointProposed');
1274
+
1155
1275
  if (success) {
1156
1276
  const endBlock = receipt.blockNumber;
1157
1277
  const inclusionBlocks = Number(endBlock - startBlock);
1158
1278
  const { calldataGas, calldataSize, sender } = stats!;
1159
- const publishStats: L1PublishBlockStats = {
1279
+ const publishStats: L1PublishCheckpointStats = {
1160
1280
  gasPrice: receipt.effectiveGasPrice,
1161
1281
  gasUsed: receipt.gasUsed,
1162
1282
  blobGasUsed: receipt.blobGasUsed ?? 0n,
@@ -1165,23 +1285,26 @@ export class SequencerPublisher {
1165
1285
  calldataGas,
1166
1286
  calldataSize,
1167
1287
  sender,
1168
- ...block.getStats(),
1288
+ ...checkpoint.getStats(),
1169
1289
  eventName: 'rollup-published-to-l1',
1170
1290
  blobCount: encodedData.blobs.length,
1171
1291
  inclusionBlocks,
1172
1292
  };
1173
- this.log.info(`Published L2 block to L1 rollup contract`, { ...stats, ...block.getStats(), ...receipt });
1293
+ this.log.info(`Published checkpoint ${checkpoint.number} at slot ${slot} to rollup contract`, {
1294
+ ...stats,
1295
+ ...checkpoint.getStats(),
1296
+ ...pick(receipt, 'transactionHash', 'blockHash'),
1297
+ });
1174
1298
  this.metrics.recordProcessBlockTx(timer.ms(), publishStats);
1175
1299
 
1176
1300
  return true;
1177
1301
  } else {
1178
1302
  this.metrics.recordFailedTx('process');
1179
- this.log.error(`Rollup process tx failed: ${errorMsg ?? 'no error message'}`, undefined, {
1180
- ...block.getStats(),
1181
- receipt,
1182
- txHash: receipt.transactionHash,
1183
- slotNumber: block.header.globalVariables.slotNumber,
1184
- });
1303
+ this.log.error(
1304
+ `Publishing checkpoint at slot ${slot} failed with ${errorMsg ?? 'no error message'}`,
1305
+ undefined,
1306
+ { ...checkpoint.getStats(), ...receipt },
1307
+ );
1185
1308
  return false;
1186
1309
  }
1187
1310
  },