@aztec/sequencer-client 0.0.1-commit.b655e406 → 0.0.1-commit.c7c42ec

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 (102) hide show
  1. package/dest/client/index.d.ts +1 -1
  2. package/dest/client/sequencer-client.d.ts +10 -9
  3. package/dest/client/sequencer-client.d.ts.map +1 -1
  4. package/dest/client/sequencer-client.js +32 -24
  5. package/dest/config.d.ts +12 -5
  6. package/dest/config.d.ts.map +1 -1
  7. package/dest/config.js +75 -28
  8. package/dest/global_variable_builder/global_builder.d.ts +19 -13
  9. package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
  10. package/dest/global_variable_builder/global_builder.js +41 -28
  11. package/dest/global_variable_builder/index.d.ts +1 -1
  12. package/dest/index.d.ts +2 -2
  13. package/dest/index.d.ts.map +1 -1
  14. package/dest/index.js +1 -1
  15. package/dest/publisher/config.d.ts +9 -4
  16. package/dest/publisher/config.d.ts.map +1 -1
  17. package/dest/publisher/config.js +14 -3
  18. package/dest/publisher/index.d.ts +1 -1
  19. package/dest/publisher/sequencer-publisher-factory.d.ts +5 -4
  20. package/dest/publisher/sequencer-publisher-factory.d.ts.map +1 -1
  21. package/dest/publisher/sequencer-publisher-factory.js +1 -1
  22. package/dest/publisher/sequencer-publisher-metrics.d.ts +3 -3
  23. package/dest/publisher/sequencer-publisher-metrics.d.ts.map +1 -1
  24. package/dest/publisher/sequencer-publisher.d.ts +66 -53
  25. package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
  26. package/dest/publisher/sequencer-publisher.js +230 -120
  27. package/dest/sequencer/block_builder.d.ts +4 -5
  28. package/dest/sequencer/block_builder.d.ts.map +1 -1
  29. package/dest/sequencer/block_builder.js +9 -10
  30. package/dest/sequencer/checkpoint_builder.d.ts +63 -0
  31. package/dest/sequencer/checkpoint_builder.d.ts.map +1 -0
  32. package/dest/sequencer/checkpoint_builder.js +131 -0
  33. package/dest/sequencer/checkpoint_proposal_job.d.ts +74 -0
  34. package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -0
  35. package/dest/sequencer/checkpoint_proposal_job.js +642 -0
  36. package/dest/sequencer/checkpoint_voter.d.ts +34 -0
  37. package/dest/sequencer/checkpoint_voter.d.ts.map +1 -0
  38. package/dest/sequencer/checkpoint_voter.js +85 -0
  39. package/dest/sequencer/config.d.ts +3 -2
  40. package/dest/sequencer/config.d.ts.map +1 -1
  41. package/dest/sequencer/errors.d.ts +1 -1
  42. package/dest/sequencer/errors.d.ts.map +1 -1
  43. package/dest/sequencer/events.d.ts +46 -0
  44. package/dest/sequencer/events.d.ts.map +1 -0
  45. package/dest/sequencer/events.js +1 -0
  46. package/dest/sequencer/index.d.ts +5 -1
  47. package/dest/sequencer/index.d.ts.map +1 -1
  48. package/dest/sequencer/index.js +4 -0
  49. package/dest/sequencer/metrics.d.ts +32 -3
  50. package/dest/sequencer/metrics.d.ts.map +1 -1
  51. package/dest/sequencer/metrics.js +192 -0
  52. package/dest/sequencer/sequencer.d.ts +96 -138
  53. package/dest/sequencer/sequencer.d.ts.map +1 -1
  54. package/dest/sequencer/sequencer.js +247 -479
  55. package/dest/sequencer/timetable.d.ts +54 -14
  56. package/dest/sequencer/timetable.d.ts.map +1 -1
  57. package/dest/sequencer/timetable.js +148 -59
  58. package/dest/sequencer/types.d.ts +3 -0
  59. package/dest/sequencer/types.d.ts.map +1 -0
  60. package/dest/sequencer/types.js +1 -0
  61. package/dest/sequencer/utils.d.ts +14 -8
  62. package/dest/sequencer/utils.d.ts.map +1 -1
  63. package/dest/sequencer/utils.js +7 -4
  64. package/dest/test/index.d.ts +4 -2
  65. package/dest/test/index.d.ts.map +1 -1
  66. package/dest/test/mock_checkpoint_builder.d.ts +83 -0
  67. package/dest/test/mock_checkpoint_builder.d.ts.map +1 -0
  68. package/dest/test/mock_checkpoint_builder.js +179 -0
  69. package/dest/test/utils.d.ts +49 -0
  70. package/dest/test/utils.d.ts.map +1 -0
  71. package/dest/test/utils.js +94 -0
  72. package/dest/tx_validator/nullifier_cache.d.ts +1 -1
  73. package/dest/tx_validator/nullifier_cache.d.ts.map +1 -1
  74. package/dest/tx_validator/tx_validator_factory.d.ts +4 -3
  75. package/dest/tx_validator/tx_validator_factory.d.ts.map +1 -1
  76. package/dest/tx_validator/tx_validator_factory.js +1 -1
  77. package/package.json +31 -30
  78. package/src/client/sequencer-client.ts +28 -38
  79. package/src/config.ts +81 -32
  80. package/src/global_variable_builder/global_builder.ts +56 -48
  81. package/src/index.ts +2 -0
  82. package/src/publisher/config.ts +20 -9
  83. package/src/publisher/sequencer-publisher-factory.ts +7 -5
  84. package/src/publisher/sequencer-publisher-metrics.ts +2 -2
  85. package/src/publisher/sequencer-publisher.ts +328 -161
  86. package/src/sequencer/README.md +531 -0
  87. package/src/sequencer/block_builder.ts +12 -13
  88. package/src/sequencer/checkpoint_builder.ts +217 -0
  89. package/src/sequencer/checkpoint_proposal_job.ts +706 -0
  90. package/src/sequencer/checkpoint_voter.ts +105 -0
  91. package/src/sequencer/config.ts +2 -1
  92. package/src/sequencer/events.ts +27 -0
  93. package/src/sequencer/index.ts +4 -0
  94. package/src/sequencer/metrics.ts +254 -3
  95. package/src/sequencer/sequencer.ts +360 -674
  96. package/src/sequencer/timetable.ts +173 -79
  97. package/src/sequencer/types.ts +6 -0
  98. package/src/sequencer/utils.ts +18 -9
  99. package/src/test/index.ts +3 -1
  100. package/src/test/mock_checkpoint_builder.ts +247 -0
  101. package/src/test/utils.ts +137 -0
  102. package/src/tx_validator/tx_validator_factory.ts +3 -2
@@ -1,46 +1,48 @@
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,
21
- type ViemStateReference,
22
- formatViemError,
23
- tryExtractEvent,
24
- } from '@aztec/ethereum';
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
+ type TransactionStats,
22
+ WEI_CONST,
23
+ } from '@aztec/ethereum/l1-tx-utils';
25
24
  import type { L1TxUtilsWithBlobs } from '@aztec/ethereum/l1-tx-utils-with-blobs';
25
+ import { FormattedViemError, formatViemError, tryExtractEvent } from '@aztec/ethereum/utils';
26
26
  import { sumBigint } from '@aztec/foundation/bigint';
27
27
  import { toHex as toPaddedHex } from '@aztec/foundation/bigint-buffer';
28
+ import { BlockNumber, CheckpointNumber, SlotNumber } from '@aztec/foundation/branded-types';
29
+ import { pick } from '@aztec/foundation/collection';
30
+ import type { Fr } from '@aztec/foundation/curves/bn254';
28
31
  import { EthAddress } from '@aztec/foundation/eth-address';
29
32
  import { Signature, type ViemSignature } from '@aztec/foundation/eth-signature';
30
- import type { Fr } from '@aztec/foundation/fields';
31
33
  import { type Logger, createLogger } from '@aztec/foundation/log';
32
34
  import { bufferToHex } from '@aztec/foundation/string';
33
35
  import { DateProvider, Timer } from '@aztec/foundation/timer';
34
36
  import { EmpireBaseAbi, ErrorsAbi, RollupAbi } from '@aztec/l1-artifacts';
35
37
  import { type ProposerSlashAction, encodeSlashConsensusVotes } from '@aztec/slasher';
36
- import { CommitteeAttestation, CommitteeAttestationsAndSigners, type ValidateBlockResult } from '@aztec/stdlib/block';
38
+ import { CommitteeAttestationsAndSigners, type ValidateBlockResult } from '@aztec/stdlib/block';
39
+ import type { Checkpoint } from '@aztec/stdlib/checkpoint';
37
40
  import { SlashFactoryContract } from '@aztec/stdlib/l1-contracts';
38
41
  import type { CheckpointHeader } from '@aztec/stdlib/rollup';
39
- import type { L1PublishBlockStats } from '@aztec/stdlib/stats';
40
- import { StateReference } from '@aztec/stdlib/tx';
42
+ import type { L1PublishCheckpointStats } from '@aztec/stdlib/stats';
41
43
  import { type TelemetryClient, getTelemetryClient } from '@aztec/telemetry-client';
42
44
 
43
- import { type TransactionReceipt, type TypedDataDefinition, encodeFunctionData, toHex } from 'viem';
45
+ import { type StateOverride, type TransactionReceipt, type TypedDataDefinition, encodeFunctionData, toHex } from 'viem';
44
46
 
45
47
  import type { PublisherConfig, TxSenderConfig } from './config.js';
46
48
  import { SequencerPublisherMetrics } from './sequencer-publisher-metrics.js';
@@ -51,8 +53,6 @@ type L1ProcessArgs = {
51
53
  header: CheckpointHeader;
52
54
  /** A root of the archive tree after the L2 block is applied. */
53
55
  archive: Buffer;
54
- /** State reference after the L2 block is applied. */
55
- stateReference: StateReference;
56
56
  /** L2 block blobs containing all tx effects. */
57
57
  blobs: Blob[];
58
58
  /** Attestations */
@@ -84,14 +84,14 @@ export type InvalidateBlockRequest = {
84
84
  request: L1TxRequest;
85
85
  reason: 'invalid-attestation' | 'insufficient-attestations';
86
86
  gasUsed: bigint;
87
- blockNumber: number;
88
- forcePendingBlockNumber: number;
87
+ blockNumber: BlockNumber;
88
+ forcePendingBlockNumber: BlockNumber;
89
89
  };
90
90
 
91
91
  interface RequestWithExpiry {
92
92
  action: Action;
93
93
  request: L1TxRequest;
94
- lastValidL2Slot: bigint;
94
+ lastValidL2Slot: SlotNumber;
95
95
  gasConfig?: Pick<L1TxConfig, 'txTimeoutAt' | 'gasLimit'>;
96
96
  blobConfig?: L1BlobInputs;
97
97
  checkSuccess: (
@@ -108,12 +108,20 @@ export class SequencerPublisher {
108
108
  protected governanceLog = createLogger('sequencer:publisher:governance');
109
109
  protected slashingLog = createLogger('sequencer:publisher:slashing');
110
110
 
111
- protected lastActions: Partial<Record<Action, bigint>> = {};
111
+ protected lastActions: Partial<Record<Action, SlotNumber>> = {};
112
+
113
+ private isPayloadEmptyCache: Map<string, boolean> = new Map<string, boolean>();
112
114
 
113
115
  protected log: Logger;
114
116
  protected ethereumSlotDuration: bigint;
115
117
 
116
- private blobSinkClient: BlobSinkClientInterface;
118
+ private blobClient: BlobClientInterface;
119
+
120
+ /** Address to use for simulations in fisherman mode (actual proposer's address) */
121
+ private proposerAddressForSimulation?: EthAddress;
122
+
123
+ /** L1 fee analyzer for fisherman mode */
124
+ private l1FeeAnalyzer?: L1FeeAnalyzer;
117
125
  // @note - with blobs, the below estimate seems too large.
118
126
  // Total used for full block from int_l1_pub e2e test: 1m (of which 86k is 1x blob)
119
127
  // Total used for emptier block from above test: 429k (of which 84k is 1x blob)
@@ -137,7 +145,7 @@ export class SequencerPublisher {
137
145
  private config: TxSenderConfig & PublisherConfig & Pick<L1ContractsConfig, 'ethereumSlotDuration'>,
138
146
  deps: {
139
147
  telemetry?: TelemetryClient;
140
- blobSinkClient?: BlobSinkClientInterface;
148
+ blobClient: BlobClientInterface;
141
149
  l1TxUtils: L1TxUtilsWithBlobs;
142
150
  rollupContract: RollupContract;
143
151
  slashingProposerContract: EmpireSlashingProposerContract | TallySlashingProposerContract | undefined;
@@ -146,7 +154,7 @@ export class SequencerPublisher {
146
154
  epochCache: EpochCache;
147
155
  dateProvider: DateProvider;
148
156
  metrics: SequencerPublisherMetrics;
149
- lastActions: Partial<Record<Action, bigint>>;
157
+ lastActions: Partial<Record<Action, SlotNumber>>;
150
158
  log?: Logger;
151
159
  },
152
160
  ) {
@@ -155,8 +163,7 @@ export class SequencerPublisher {
155
163
  this.epochCache = deps.epochCache;
156
164
  this.lastActions = deps.lastActions;
157
165
 
158
- this.blobSinkClient =
159
- deps.blobSinkClient ?? createBlobSinkClient(config, { logger: createLogger('sequencer:blob-sink:client') });
166
+ this.blobClient = deps.blobClient;
160
167
 
161
168
  const telemetry = deps.telemetry ?? getTelemetryClient();
162
169
  this.metrics = deps.metrics ?? new SequencerPublisherMetrics(telemetry, 'SequencerPublisher');
@@ -173,6 +180,15 @@ export class SequencerPublisher {
173
180
  this.slashingProposerContract = newSlashingProposer;
174
181
  });
175
182
  this.slashFactoryContract = deps.slashFactoryContract;
183
+
184
+ // Initialize L1 fee analyzer for fisherman mode
185
+ if (config.fishermanMode) {
186
+ this.l1FeeAnalyzer = new L1FeeAnalyzer(
187
+ this.l1TxUtils.client,
188
+ deps.dateProvider,
189
+ createLogger('sequencer:publisher:fee-analyzer'),
190
+ );
191
+ }
176
192
  }
177
193
 
178
194
  public getRollupContract(): RollupContract {
@@ -183,14 +199,96 @@ export class SequencerPublisher {
183
199
  return this.l1TxUtils.getSenderAddress();
184
200
  }
185
201
 
202
+ /**
203
+ * Gets the L1 fee analyzer instance (only available in fisherman mode)
204
+ */
205
+ public getL1FeeAnalyzer(): L1FeeAnalyzer | undefined {
206
+ return this.l1FeeAnalyzer;
207
+ }
208
+
209
+ /**
210
+ * Sets the proposer address to use for simulations in fisherman mode.
211
+ * @param proposerAddress - The actual proposer's address to use for balance lookups in simulations
212
+ */
213
+ public setProposerAddressForSimulation(proposerAddress: EthAddress | undefined) {
214
+ this.proposerAddressForSimulation = proposerAddress;
215
+ }
216
+
186
217
  public addRequest(request: RequestWithExpiry) {
187
218
  this.requests.push(request);
188
219
  }
189
220
 
190
- public getCurrentL2Slot(): bigint {
221
+ public getCurrentL2Slot(): SlotNumber {
191
222
  return this.epochCache.getEpochAndSlotNow().slot;
192
223
  }
193
224
 
225
+ /**
226
+ * Clears all pending requests without sending them.
227
+ */
228
+ public clearPendingRequests(): void {
229
+ const count = this.requests.length;
230
+ this.requests = [];
231
+ if (count > 0) {
232
+ this.log.debug(`Cleared ${count} pending request(s)`);
233
+ }
234
+ }
235
+
236
+ /**
237
+ * Analyzes L1 fees for the pending requests without sending them.
238
+ * This is used in fisherman mode to validate fee calculations.
239
+ * @param l2SlotNumber - The L2 slot number for this analysis
240
+ * @param onComplete - Optional callback to invoke when analysis completes (after block is mined)
241
+ * @returns The analysis result (incomplete until block mines), or undefined if no requests
242
+ */
243
+ public async analyzeL1Fees(
244
+ l2SlotNumber: SlotNumber,
245
+ onComplete?: (analysis: L1FeeAnalysisResult) => void,
246
+ ): Promise<L1FeeAnalysisResult | undefined> {
247
+ if (!this.l1FeeAnalyzer) {
248
+ this.log.warn('L1 fee analyzer not available (not in fisherman mode)');
249
+ return undefined;
250
+ }
251
+
252
+ const requestsToAnalyze = [...this.requests];
253
+ if (requestsToAnalyze.length === 0) {
254
+ this.log.debug('No requests to analyze for L1 fees');
255
+ return undefined;
256
+ }
257
+
258
+ // Extract blob config from requests (if any)
259
+ const blobConfigs = requestsToAnalyze.filter(request => request.blobConfig).map(request => request.blobConfig);
260
+ const blobConfig = blobConfigs[0];
261
+
262
+ // Get gas configs
263
+ const gasConfigs = requestsToAnalyze.filter(request => request.gasConfig).map(request => request.gasConfig);
264
+ const gasLimits = gasConfigs.map(g => g?.gasLimit).filter((g): g is bigint => g !== undefined);
265
+ const gasLimit = gasLimits.length > 0 ? gasLimits.reduce((sum, g) => sum + g, 0n) : 0n;
266
+
267
+ // Get the transaction requests
268
+ const l1Requests = requestsToAnalyze.map(r => r.request);
269
+
270
+ // Start the analysis
271
+ const analysisId = await this.l1FeeAnalyzer.startAnalysis(
272
+ l2SlotNumber,
273
+ gasLimit > 0n ? gasLimit : SequencerPublisher.PROPOSE_GAS_GUESS,
274
+ l1Requests,
275
+ blobConfig,
276
+ onComplete,
277
+ );
278
+
279
+ this.log.info('Started L1 fee analysis', {
280
+ analysisId,
281
+ l2SlotNumber: l2SlotNumber.toString(),
282
+ requestCount: requestsToAnalyze.length,
283
+ hasBlobConfig: !!blobConfig,
284
+ gasLimit: gasLimit.toString(),
285
+ actions: requestsToAnalyze.map(r => r.action),
286
+ });
287
+
288
+ // Return the analysis result (will be incomplete until block mines)
289
+ return this.l1FeeAnalyzer.getAnalysis(analysisId);
290
+ }
291
+
194
292
  /**
195
293
  * Sends all requests that are still valid.
196
294
  * @returns one of:
@@ -201,7 +299,7 @@ export class SequencerPublisher {
201
299
  public async sendRequests() {
202
300
  const requestsToProcess = [...this.requests];
203
301
  this.requests = [];
204
- if (this.interrupted) {
302
+ if (this.interrupted || requestsToProcess.length === 0) {
205
303
  return undefined;
206
304
  }
207
305
  const currentL2Slot = this.getCurrentL2Slot();
@@ -315,13 +413,18 @@ export class SequencerPublisher {
315
413
  public canProposeAtNextEthBlock(
316
414
  tipArchive: Fr,
317
415
  msgSender: EthAddress,
318
- opts: { forcePendingBlockNumber?: number } = {},
416
+ opts: { forcePendingBlockNumber?: BlockNumber } = {},
319
417
  ) {
320
418
  // TODO: #14291 - should loop through multiple keys to check if any of them can propose
321
419
  const ignoredErrors = ['SlotAlreadyInChain', 'InvalidProposer', 'InvalidArchive'];
322
420
 
323
421
  return this.rollupContract
324
- .canProposeAtNextEthBlock(tipArchive.toBuffer(), msgSender.toString(), this.ethereumSlotDuration, opts)
422
+ .canProposeAtNextEthBlock(tipArchive.toBuffer(), msgSender.toString(), Number(this.ethereumSlotDuration), {
423
+ forcePendingCheckpointNumber:
424
+ opts.forcePendingBlockNumber !== undefined
425
+ ? CheckpointNumber.fromBlockNumber(opts.forcePendingBlockNumber)
426
+ : undefined,
427
+ })
325
428
  .catch(err => {
326
429
  if (err instanceof FormattedViemError && ignoredErrors.find(e => err.message.includes(e))) {
327
430
  this.log.warn(`Failed canProposeAtTime check with ${ignoredErrors.find(e => err.message.includes(e))}`, {
@@ -339,7 +442,10 @@ export class SequencerPublisher {
339
442
  * It will throw if the block header is invalid.
340
443
  * @param header - The block header to validate
341
444
  */
342
- public async validateBlockHeader(header: CheckpointHeader, opts?: { forcePendingBlockNumber: number | undefined }) {
445
+ public async validateBlockHeader(
446
+ header: CheckpointHeader,
447
+ opts?: { forcePendingBlockNumber: BlockNumber | undefined },
448
+ ) {
343
449
  const flags = { ignoreDA: true, ignoreSignatures: true };
344
450
 
345
451
  const args = [
@@ -353,10 +459,26 @@ export class SequencerPublisher {
353
459
  ] as const;
354
460
 
355
461
  const ts = BigInt((await this.l1TxUtils.getBlock()).timestamp + this.ethereumSlotDuration);
462
+ const optsForcePendingCheckpointNumber =
463
+ opts?.forcePendingBlockNumber !== undefined
464
+ ? CheckpointNumber.fromBlockNumber(opts.forcePendingBlockNumber)
465
+ : undefined;
466
+ const stateOverrides = await this.rollupContract.makePendingCheckpointNumberOverride(
467
+ optsForcePendingCheckpointNumber,
468
+ );
469
+ let balance = 0n;
470
+ if (this.config.fishermanMode) {
471
+ // In fisherman mode, we can't know where the proposer is publishing from
472
+ // so we just add sufficient balance to the multicall3 address
473
+ balance = 10n * WEI_CONST * WEI_CONST; // 10 ETH
474
+ } else {
475
+ balance = await this.l1TxUtils.getSenderBalance();
476
+ }
477
+ stateOverrides.push({
478
+ address: MULTI_CALL_3_ADDRESS,
479
+ balance,
480
+ });
356
481
 
357
- // use sender balance to simulate
358
- const balance = await this.l1TxUtils.getSenderBalance();
359
- this.log.debug(`Simulating validateHeader with balance: ${balance}`);
360
482
  await this.l1TxUtils.simulate(
361
483
  {
362
484
  to: this.rollupContract.address,
@@ -364,10 +486,7 @@ export class SequencerPublisher {
364
486
  from: MULTI_CALL_3_ADDRESS,
365
487
  },
366
488
  { time: ts + 1n },
367
- [
368
- { address: MULTI_CALL_3_ADDRESS, balance },
369
- ...(await this.rollupContract.makePendingBlockNumberOverride(opts?.forcePendingBlockNumber)),
370
- ],
489
+ stateOverrides,
371
490
  );
372
491
  this.log.debug(`Simulated validateHeader`);
373
492
  }
@@ -387,7 +506,7 @@ export class SequencerPublisher {
387
506
  const blockNumber = block.blockNumber;
388
507
  const logData = { ...block, reason };
389
508
 
390
- const currentBlockNumber = await this.rollupContract.getBlockNumber();
509
+ const currentBlockNumber = await this.rollupContract.getCheckpointNumber();
391
510
  if (currentBlockNumber < validationResult.block.blockNumber) {
392
511
  this.log.verbose(
393
512
  `Skipping block ${blockNumber} invalidation since it has already been removed from the pending chain`,
@@ -403,7 +522,7 @@ export class SequencerPublisher {
403
522
  const { gasUsed } = await this.l1TxUtils.simulate(request, undefined, undefined, ErrorsAbi);
404
523
  this.log.verbose(`Simulation for invalidate block ${blockNumber} succeeded`, { ...logData, request, gasUsed });
405
524
 
406
- return { request, gasUsed, blockNumber, forcePendingBlockNumber: blockNumber - 1, reason };
525
+ return { request, gasUsed, blockNumber, forcePendingBlockNumber: BlockNumber(blockNumber - 1), reason };
407
526
  } catch (err) {
408
527
  const viemError = formatViemError(err);
409
528
 
@@ -414,7 +533,7 @@ export class SequencerPublisher {
414
533
  `Simulation for invalidate block ${blockNumber} failed due to block not being in pending chain`,
415
534
  { ...logData, request, error: viemError.message },
416
535
  );
417
- const latestPendingBlockNumber = await this.rollupContract.getBlockNumber();
536
+ const latestPendingBlockNumber = await this.rollupContract.getCheckpointNumber();
418
537
  if (latestPendingBlockNumber < blockNumber) {
419
538
  this.log.verbose(`Block number ${blockNumber} has already been invalidated`, { ...logData });
420
539
  return undefined;
@@ -451,14 +570,14 @@ export class SequencerPublisher {
451
570
 
452
571
  if (reason === 'invalid-attestation') {
453
572
  return this.rollupContract.buildInvalidateBadAttestationRequest(
454
- block.blockNumber,
573
+ CheckpointNumber.fromBlockNumber(block.blockNumber),
455
574
  attestationsAndSigners,
456
575
  committee,
457
576
  validationResult.invalidIndex,
458
577
  );
459
578
  } else if (reason === 'insufficient-attestations') {
460
579
  return this.rollupContract.buildInvalidateInsufficientAttestationsRequest(
461
- block.blockNumber,
580
+ CheckpointNumber.fromBlockNumber(block.blockNumber),
462
581
  attestationsAndSigners,
463
582
  committee,
464
583
  );
@@ -468,46 +587,38 @@ export class SequencerPublisher {
468
587
  }
469
588
  }
470
589
 
471
- /**
472
- * @notice Will simulate `propose` to make sure that the block is valid for submission
473
- *
474
- * @dev Throws if unable to propose
475
- *
476
- * @param block - The block to propose
477
- * @param attestationData - The block's attestation data
478
- *
479
- */
480
- public async validateBlockForSubmission(
481
- block: L2Block,
590
+ /** Simulates `propose` to make sure that the checkpoint is valid for submission */
591
+ public async validateCheckpointForSubmission(
592
+ checkpoint: Checkpoint,
482
593
  attestationsAndSigners: CommitteeAttestationsAndSigners,
483
594
  attestationsAndSignersSignature: Signature,
484
- options: { forcePendingBlockNumber?: number },
595
+ options: { forcePendingBlockNumber?: BlockNumber }, // TODO(palla/mbps): Should this be forcePendingCheckpointNumber?
485
596
  ): Promise<bigint> {
486
597
  const ts = BigInt((await this.l1TxUtils.getBlock()).timestamp + this.ethereumSlotDuration);
487
598
 
599
+ // TODO(palla/mbps): This should not be needed, there's no flow where we propose with zero attestations. Or is there?
488
600
  // If we have no attestations, we still need to provide the empty attestations
489
601
  // so that the committee is recalculated correctly
490
- const ignoreSignatures = attestationsAndSigners.attestations.length === 0;
491
- if (ignoreSignatures) {
492
- const { committee } = await this.epochCache.getCommittee(block.header.globalVariables.slotNumber.toBigInt());
493
- if (!committee) {
494
- this.log.warn(`No committee found for slot ${block.header.globalVariables.slotNumber.toBigInt()}`);
495
- throw new Error(`No committee found for slot ${block.header.globalVariables.slotNumber.toBigInt()}`);
496
- }
497
- attestationsAndSigners.attestations = committee.map(committeeMember =>
498
- CommitteeAttestation.fromAddress(committeeMember),
499
- );
500
- }
501
-
502
- const blobFields = block.getCheckpointBlobFields();
602
+ // const ignoreSignatures = attestationsAndSigners.attestations.length === 0;
603
+ // if (ignoreSignatures) {
604
+ // const { committee } = await this.epochCache.getCommittee(block.header.globalVariables.slotNumber);
605
+ // if (!committee) {
606
+ // this.log.warn(`No committee found for slot ${block.header.globalVariables.slotNumber}`);
607
+ // throw new Error(`No committee found for slot ${block.header.globalVariables.slotNumber}`);
608
+ // }
609
+ // attestationsAndSigners.attestations = committee.map(committeeMember =>
610
+ // CommitteeAttestation.fromAddress(committeeMember),
611
+ // );
612
+ // }
613
+
614
+ const blobFields = checkpoint.toBlobFields();
503
615
  const blobs = getBlobsPerL1Block(blobFields);
504
616
  const blobInput = getPrefixedEthBlobCommitments(blobs);
505
617
 
506
618
  const args = [
507
619
  {
508
- header: block.getCheckpointHeader().toViem(),
509
- archive: toHex(block.archive.root.toBuffer()),
510
- stateReference: block.header.state.toViem(),
620
+ header: checkpoint.header.toViem(),
621
+ archive: toHex(checkpoint.archive.root.toBuffer()),
511
622
  oracleInput: {
512
623
  feeAssetPriceModifier: 0n,
513
624
  },
@@ -523,7 +634,7 @@ export class SequencerPublisher {
523
634
  }
524
635
 
525
636
  private async enqueueCastSignalHelper(
526
- slotNumber: bigint,
637
+ slotNumber: SlotNumber,
527
638
  timestamp: bigint,
528
639
  signalType: GovernanceSignalAction,
529
640
  payload: EthAddress,
@@ -545,10 +656,19 @@ export class SequencerPublisher {
545
656
  const round = await base.computeRound(slotNumber);
546
657
  const roundInfo = await base.getRoundInfo(this.rollupContract.address, round);
547
658
 
659
+ if (roundInfo.quorumReached) {
660
+ return false;
661
+ }
662
+
548
663
  if (roundInfo.lastSignalSlot >= slotNumber) {
549
664
  return false;
550
665
  }
551
666
 
667
+ if (await this.isPayloadEmpty(payload)) {
668
+ this.log.warn(`Skipping vote cast for payload with empty code`);
669
+ return false;
670
+ }
671
+
552
672
  const cachedLastVote = this.lastActions[signalType];
553
673
  this.lastActions[signalType] = slotNumber;
554
674
  const action = signalType;
@@ -591,14 +711,14 @@ export class SequencerPublisher {
591
711
  const logData = { ...result, slotNumber, round, payload: payload.toString() };
592
712
  if (!success) {
593
713
  this.log.error(
594
- `Signaling in [${action}] for ${payload} at slot ${slotNumber} in round ${round} failed`,
714
+ `Signaling in ${action} for ${payload} at slot ${slotNumber} in round ${round} failed`,
595
715
  logData,
596
716
  );
597
717
  this.lastActions[signalType] = cachedLastVote;
598
718
  return false;
599
719
  } else {
600
720
  this.log.info(
601
- `Signaling in [${action}] for ${payload} at slot ${slotNumber} in round ${round} succeeded`,
721
+ `Signaling in ${action} for ${payload} at slot ${slotNumber} in round ${round} succeeded`,
602
722
  logData,
603
723
  );
604
724
  return true;
@@ -608,6 +728,17 @@ export class SequencerPublisher {
608
728
  return true;
609
729
  }
610
730
 
731
+ private async isPayloadEmpty(payload: EthAddress): Promise<boolean> {
732
+ const key = payload.toString();
733
+ const cached = this.isPayloadEmptyCache.get(key);
734
+ if (cached) {
735
+ return cached;
736
+ }
737
+ const isEmpty = !(await this.l1TxUtils.getCode(payload));
738
+ this.isPayloadEmptyCache.set(key, isEmpty);
739
+ return isEmpty;
740
+ }
741
+
611
742
  /**
612
743
  * Enqueues a governance castSignal transaction to cast a signal for a given slot number.
613
744
  * @param slotNumber - The slot number to cast a signal for.
@@ -616,7 +747,7 @@ export class SequencerPublisher {
616
747
  */
617
748
  public enqueueGovernanceCastSignal(
618
749
  governancePayload: EthAddress,
619
- slotNumber: bigint,
750
+ slotNumber: SlotNumber,
620
751
  timestamp: bigint,
621
752
  signerAddress: EthAddress,
622
753
  signer: (msg: TypedDataDefinition) => Promise<`0x${string}`>,
@@ -635,7 +766,7 @@ export class SequencerPublisher {
635
766
  /** Enqueues all slashing actions as returned by the slasher client. */
636
767
  public async enqueueSlashingActions(
637
768
  actions: ProposerSlashAction[],
638
- slotNumber: bigint,
769
+ slotNumber: SlotNumber,
639
770
  timestamp: bigint,
640
771
  signerAddress: EthAddress,
641
772
  signer: (msg: TypedDataDefinition) => Promise<`0x${string}`>,
@@ -755,28 +886,21 @@ export class SequencerPublisher {
755
886
  return true;
756
887
  }
757
888
 
758
- /**
759
- * Proposes a L2 block on L1.
760
- *
761
- * @param block - L2 block to propose.
762
- * @returns True if the tx has been enqueued, throws otherwise. See #9315
763
- */
764
- public async enqueueProposeL2Block(
765
- block: L2Block,
889
+ /** Simulates and enqueues a proposal for a checkpoint on L1 */
890
+ public async enqueueProposeCheckpoint(
891
+ checkpoint: Checkpoint,
766
892
  attestationsAndSigners: CommitteeAttestationsAndSigners,
767
893
  attestationsAndSignersSignature: Signature,
768
- opts: { txTimeoutAt?: Date; forcePendingBlockNumber?: number } = {},
769
- ): Promise<boolean> {
770
- const checkpointHeader = block.getCheckpointHeader();
894
+ opts: { txTimeoutAt?: Date; forcePendingBlockNumber?: BlockNumber } = {},
895
+ ): Promise<void> {
896
+ const checkpointHeader = checkpoint.header;
771
897
 
772
- const blobFields = block.getCheckpointBlobFields();
898
+ const blobFields = checkpoint.toBlobFields();
773
899
  const blobs = getBlobsPerL1Block(blobFields);
774
900
 
775
901
  const proposeTxArgs = {
776
902
  header: checkpointHeader,
777
- archive: block.archive.root.toBuffer(),
778
- stateReference: block.header.state,
779
- body: block.body.toBuffer(),
903
+ archive: checkpoint.archive.root.toBuffer(),
780
904
  blobs,
781
905
  attestationsAndSigners,
782
906
  attestationsAndSignersSignature,
@@ -790,19 +914,23 @@ export class SequencerPublisher {
790
914
  // By simulation issue, I mean the fact that the block.timestamp is equal to the last block, not the next, which
791
915
  // make time consistency checks break.
792
916
  // TODO(palla): Check whether we're validating twice, once here and once within addProposeTx, since we call simulateProposeTx in both places.
793
- ts = await this.validateBlockForSubmission(block, attestationsAndSigners, attestationsAndSignersSignature, opts);
917
+ ts = await this.validateCheckpointForSubmission(
918
+ checkpoint,
919
+ attestationsAndSigners,
920
+ attestationsAndSignersSignature,
921
+ opts,
922
+ );
794
923
  } catch (err: any) {
795
- this.log.error(`Block validation failed. ${err instanceof Error ? err.message : 'No error message'}`, err, {
796
- ...block.getStats(),
797
- slotNumber: block.header.globalVariables.slotNumber.toBigInt(),
924
+ this.log.error(`Checkpoint validation failed. ${err instanceof Error ? err.message : 'No error message'}`, err, {
925
+ ...checkpoint.getStats(),
926
+ slotNumber: checkpoint.header.slotNumber,
798
927
  forcePendingBlockNumber: opts.forcePendingBlockNumber,
799
928
  });
800
929
  throw err;
801
930
  }
802
931
 
803
- this.log.verbose(`Enqueuing block propose transaction`, { ...block.toBlockInfo(), ...opts });
804
- await this.addProposeTx(block, proposeTxArgs, opts, ts);
805
- return true;
932
+ this.log.verbose(`Enqueuing checkpoint propose transaction`, { ...checkpoint.toCheckpointInfo(), ...opts });
933
+ await this.addProposeTx(checkpoint, proposeTxArgs, opts, ts);
806
934
  }
807
935
 
808
936
  public enqueueInvalidateBlock(request: InvalidateBlockRequest | undefined, opts: { txTimeoutAt?: Date } = {}) {
@@ -820,13 +948,13 @@ export class SequencerPublisher {
820
948
  action: `invalidate-by-${request.reason}`,
821
949
  request: request.request,
822
950
  gasConfig: { gasLimit, txTimeoutAt: opts.txTimeoutAt },
823
- lastValidL2Slot: this.getCurrentL2Slot() + 2n,
951
+ lastValidL2Slot: SlotNumber(this.getCurrentL2Slot() + 2),
824
952
  checkSuccess: (_req, result) => {
825
953
  const success =
826
954
  result &&
827
955
  result.receipt &&
828
956
  result.receipt.status === 'success' &&
829
- tryExtractEvent(result.receipt.logs, this.rollupContract.address, RollupAbi, 'BlockInvalidated');
957
+ tryExtractEvent(result.receipt.logs, this.rollupContract.address, RollupAbi, 'CheckpointInvalidated');
830
958
  if (!success) {
831
959
  this.log.warn(`Invalidate block ${request.blockNumber} failed`, { ...result, ...logData });
832
960
  } else {
@@ -841,7 +969,7 @@ export class SequencerPublisher {
841
969
  action: Action,
842
970
  request: L1TxRequest,
843
971
  checkSuccess: (receipt: TransactionReceipt) => boolean | undefined,
844
- slotNumber: bigint,
972
+ slotNumber: SlotNumber,
845
973
  timestamp: bigint,
846
974
  ) {
847
975
  const logData = { slotNumber, timestamp, gasLimit: undefined as bigint | undefined };
@@ -909,41 +1037,50 @@ export class SequencerPublisher {
909
1037
  private async prepareProposeTx(
910
1038
  encodedData: L1ProcessArgs,
911
1039
  timestamp: bigint,
912
- options: { forcePendingBlockNumber?: number },
1040
+ options: { forcePendingBlockNumber?: BlockNumber },
913
1041
  ) {
914
1042
  const kzg = Blob.getViemKzgInstance();
915
1043
  const blobInput = getPrefixedEthBlobCommitments(encodedData.blobs);
916
1044
  this.log.debug('Validating blob input', { blobInput });
917
- const blobEvaluationGas = await this.l1TxUtils
918
- .estimateGas(
919
- this.getSenderAddress().toString(),
920
- {
921
- to: this.rollupContract.address,
922
- data: encodeFunctionData({
923
- abi: RollupAbi,
924
- functionName: 'validateBlobs',
925
- args: [blobInput],
926
- }),
927
- },
928
- {},
929
- {
930
- blobs: encodedData.blobs.map(b => b.data),
931
- kzg,
932
- },
933
- )
934
- .catch(err => {
935
- const { message, metaMessages } = formatViemError(err);
936
- this.log.error(`Failed to validate blobs`, message, { metaMessages });
937
- throw new Error('Failed to validate blobs');
938
- });
939
1045
 
1046
+ // Get blob evaluation gas
1047
+ let blobEvaluationGas: bigint;
1048
+ if (this.config.fishermanMode) {
1049
+ // In fisherman mode, we can't estimate blob gas because estimateGas doesn't support state overrides
1050
+ // Use a fixed estimate.
1051
+ blobEvaluationGas = BigInt(encodedData.blobs.length) * 21_000n;
1052
+ this.log.debug(`Using fixed blob evaluation gas estimate in fisherman mode: ${blobEvaluationGas}`);
1053
+ } else {
1054
+ // Normal mode - use estimateGas with blob inputs
1055
+ blobEvaluationGas = await this.l1TxUtils
1056
+ .estimateGas(
1057
+ this.getSenderAddress().toString(),
1058
+ {
1059
+ to: this.rollupContract.address,
1060
+ data: encodeFunctionData({
1061
+ abi: RollupAbi,
1062
+ functionName: 'validateBlobs',
1063
+ args: [blobInput],
1064
+ }),
1065
+ },
1066
+ {},
1067
+ {
1068
+ blobs: encodedData.blobs.map(b => b.data),
1069
+ kzg,
1070
+ },
1071
+ )
1072
+ .catch(err => {
1073
+ const { message, metaMessages } = formatViemError(err);
1074
+ this.log.error(`Failed to validate blobs`, message, { metaMessages });
1075
+ throw new Error('Failed to validate blobs');
1076
+ });
1077
+ }
940
1078
  const signers = encodedData.attestationsAndSigners.getSigners().map(signer => signer.toString());
941
1079
 
942
1080
  const args = [
943
1081
  {
944
1082
  header: encodedData.header.toViem(),
945
1083
  archive: toHex(encodedData.archive),
946
- stateReference: encodedData.stateReference.toViem(),
947
1084
  oracleInput: {
948
1085
  // We are currently not modifying these. See #9963
949
1086
  feeAssetPriceModifier: 0n,
@@ -971,7 +1108,6 @@ export class SequencerPublisher {
971
1108
  {
972
1109
  readonly header: ViemHeader;
973
1110
  readonly archive: `0x${string}`;
974
- readonly stateReference: ViemStateReference;
975
1111
  readonly oracleInput: {
976
1112
  readonly feeAssetPriceModifier: 0n;
977
1113
  };
@@ -982,7 +1118,7 @@ export class SequencerPublisher {
982
1118
  `0x${string}`,
983
1119
  ],
984
1120
  timestamp: bigint,
985
- options: { forcePendingBlockNumber?: number },
1121
+ options: { forcePendingBlockNumber?: BlockNumber },
986
1122
  ) {
987
1123
  const rollupData = encodeFunctionData({
988
1124
  abi: RollupAbi,
@@ -990,19 +1126,42 @@ export class SequencerPublisher {
990
1126
  args,
991
1127
  });
992
1128
 
993
- // override the pending block number if requested
994
- const forcePendingBlockNumberStateDiff = (
1129
+ // override the pending checkpoint number if requested
1130
+ const optsForcePendingCheckpointNumber =
995
1131
  options.forcePendingBlockNumber !== undefined
996
- ? await this.rollupContract.makePendingBlockNumberOverride(options.forcePendingBlockNumber)
1132
+ ? CheckpointNumber.fromBlockNumber(options.forcePendingBlockNumber)
1133
+ : undefined;
1134
+ const forcePendingCheckpointNumberStateDiff = (
1135
+ optsForcePendingCheckpointNumber !== undefined
1136
+ ? await this.rollupContract.makePendingCheckpointNumberOverride(optsForcePendingCheckpointNumber)
997
1137
  : []
998
1138
  ).flatMap(override => override.stateDiff ?? []);
999
1139
 
1140
+ const stateOverrides: StateOverride = [
1141
+ {
1142
+ address: this.rollupContract.address,
1143
+ // @note we override checkBlob to false since blobs are not part simulate()
1144
+ stateDiff: [
1145
+ { slot: toPaddedHex(RollupContract.checkBlobStorageSlot, true), value: toPaddedHex(0n, true) },
1146
+ ...forcePendingCheckpointNumberStateDiff,
1147
+ ],
1148
+ },
1149
+ ];
1150
+ // In fisherman mode, simulate as the proposer but with sufficient balance
1151
+ if (this.proposerAddressForSimulation) {
1152
+ stateOverrides.push({
1153
+ address: this.proposerAddressForSimulation.toString(),
1154
+ balance: 10n * WEI_CONST * WEI_CONST, // 10 ETH
1155
+ });
1156
+ }
1157
+
1000
1158
  const simulationResult = await this.l1TxUtils
1001
1159
  .simulate(
1002
1160
  {
1003
1161
  to: this.rollupContract.address,
1004
1162
  data: rollupData,
1005
1163
  gas: SequencerPublisher.PROPOSE_GAS_GUESS,
1164
+ ...(this.proposerAddressForSimulation && { from: this.proposerAddressForSimulation.toString() }),
1006
1165
  },
1007
1166
  {
1008
1167
  // @note we add 1n to the timestamp because geth implementation doesn't like simulation timestamp to be equal to the current block timestamp
@@ -1010,16 +1169,7 @@ export class SequencerPublisher {
1010
1169
  // @note reth should have a 30m gas limit per block but throws errors that this tx is beyond limit so we increase here
1011
1170
  gasLimit: SequencerPublisher.PROPOSE_GAS_GUESS * 2n,
1012
1171
  },
1013
- [
1014
- {
1015
- address: this.rollupContract.address,
1016
- // @note we override checkBlob to false since blobs are not part simulate()
1017
- stateDiff: [
1018
- { slot: toPaddedHex(RollupContract.checkBlobStorageSlot, true), value: toPaddedHex(0n, true) },
1019
- ...forcePendingBlockNumberStateDiff,
1020
- ],
1021
- },
1022
- ],
1172
+ stateOverrides,
1023
1173
  RollupAbi,
1024
1174
  {
1025
1175
  // @note fallback gas estimate to use if the node doesn't support simulation API
@@ -1027,7 +1177,17 @@ export class SequencerPublisher {
1027
1177
  },
1028
1178
  )
1029
1179
  .catch(err => {
1030
- this.log.error(`Failed to simulate propose tx`, err);
1180
+ // In fisherman mode, we expect ValidatorSelection__MissingProposerSignature since fisherman doesn't have proposer signature
1181
+ const viemError = formatViemError(err);
1182
+ if (this.config.fishermanMode && viemError.message?.includes('ValidatorSelection__MissingProposerSignature')) {
1183
+ this.log.debug(`Ignoring expected ValidatorSelection__MissingProposerSignature error in fisherman mode`);
1184
+ // Return a minimal simulation result with the fallback gas estimate
1185
+ return {
1186
+ gasUsed: SequencerPublisher.PROPOSE_GAS_GUESS,
1187
+ logs: [],
1188
+ };
1189
+ }
1190
+ this.log.error(`Failed to simulate propose tx`, viemError);
1031
1191
  throw err;
1032
1192
  });
1033
1193
 
@@ -1035,11 +1195,12 @@ export class SequencerPublisher {
1035
1195
  }
1036
1196
 
1037
1197
  private async addProposeTx(
1038
- block: L2Block,
1198
+ checkpoint: Checkpoint,
1039
1199
  encodedData: L1ProcessArgs,
1040
- opts: { txTimeoutAt?: Date; forcePendingBlockNumber?: number } = {},
1200
+ opts: { txTimeoutAt?: Date; forcePendingBlockNumber?: BlockNumber } = {},
1041
1201
  timestamp: bigint,
1042
1202
  ): Promise<void> {
1203
+ const slot = checkpoint.header.slotNumber;
1043
1204
  const timer = new Timer();
1044
1205
  const kzg = Blob.getViemKzgInstance();
1045
1206
  const { rollupData, simulationResult, blobEvaluationGas } = await this.prepareProposeTx(
@@ -1054,11 +1215,13 @@ export class SequencerPublisher {
1054
1215
  SequencerPublisher.MULTICALL_OVERHEAD_GAS_GUESS, // We issue the simulation against the rollup contract, so we need to account for the overhead of the multicall3
1055
1216
  );
1056
1217
 
1057
- // Send the blobs to the blob sink preemptively. This helps in tests where the sequencer mistakingly thinks that the propose
1058
- // tx fails but it does get mined. We make sure that the blobs are sent to the blob sink regardless of the tx outcome.
1059
- void this.blobSinkClient.sendBlobsToBlobSink(encodedData.blobs).catch(_err => {
1060
- this.log.error('Failed to send blobs to blob sink');
1061
- });
1218
+ // Send the blobs to the blob client preemptively. This helps in tests where the sequencer mistakingly thinks that the propose
1219
+ // tx fails but it does get mined. We make sure that the blobs are sent to the blob client regardless of the tx outcome.
1220
+ void Promise.resolve().then(() =>
1221
+ this.blobClient.sendBlobsToFilestore(encodedData.blobs).catch(_err => {
1222
+ this.log.error('Failed to send blobs to blob client');
1223
+ }),
1224
+ );
1062
1225
 
1063
1226
  return this.addRequest({
1064
1227
  action: 'propose',
@@ -1066,7 +1229,7 @@ export class SequencerPublisher {
1066
1229
  to: this.rollupContract.address,
1067
1230
  data: rollupData,
1068
1231
  },
1069
- lastValidL2Slot: block.header.globalVariables.slotNumber.toBigInt(),
1232
+ lastValidL2Slot: checkpoint.header.slotNumber,
1070
1233
  gasConfig: { ...opts, gasLimit },
1071
1234
  blobConfig: {
1072
1235
  blobs: encodedData.blobs.map(b => b.data),
@@ -1080,12 +1243,13 @@ export class SequencerPublisher {
1080
1243
  const success =
1081
1244
  receipt &&
1082
1245
  receipt.status === 'success' &&
1083
- tryExtractEvent(receipt.logs, this.rollupContract.address, RollupAbi, 'L2BlockProposed');
1246
+ tryExtractEvent(receipt.logs, this.rollupContract.address, RollupAbi, 'CheckpointProposed');
1247
+
1084
1248
  if (success) {
1085
1249
  const endBlock = receipt.blockNumber;
1086
1250
  const inclusionBlocks = Number(endBlock - startBlock);
1087
1251
  const { calldataGas, calldataSize, sender } = stats!;
1088
- const publishStats: L1PublishBlockStats = {
1252
+ const publishStats: L1PublishCheckpointStats = {
1089
1253
  gasPrice: receipt.effectiveGasPrice,
1090
1254
  gasUsed: receipt.gasUsed,
1091
1255
  blobGasUsed: receipt.blobGasUsed ?? 0n,
@@ -1094,23 +1258,26 @@ export class SequencerPublisher {
1094
1258
  calldataGas,
1095
1259
  calldataSize,
1096
1260
  sender,
1097
- ...block.getStats(),
1261
+ ...checkpoint.getStats(),
1098
1262
  eventName: 'rollup-published-to-l1',
1099
1263
  blobCount: encodedData.blobs.length,
1100
1264
  inclusionBlocks,
1101
1265
  };
1102
- this.log.info(`Published L2 block to L1 rollup contract`, { ...stats, ...block.getStats(), ...receipt });
1266
+ this.log.info(`Published checkpoint ${checkpoint.number} at slot ${slot} to rollup contract`, {
1267
+ ...stats,
1268
+ ...checkpoint.getStats(),
1269
+ ...pick(receipt, 'transactionHash', 'blockHash'),
1270
+ });
1103
1271
  this.metrics.recordProcessBlockTx(timer.ms(), publishStats);
1104
1272
 
1105
1273
  return true;
1106
1274
  } else {
1107
1275
  this.metrics.recordFailedTx('process');
1108
- this.log.error(`Rollup process tx failed: ${errorMsg ?? 'no error message'}`, undefined, {
1109
- ...block.getStats(),
1110
- receipt,
1111
- txHash: receipt.transactionHash,
1112
- slotNumber: block.header.globalVariables.slotNumber.toBigInt(),
1113
- });
1276
+ this.log.error(
1277
+ `Publishing checkpoint at slot ${slot} failed with ${errorMsg ?? 'no error message'}`,
1278
+ undefined,
1279
+ { ...checkpoint.getStats(), ...receipt },
1280
+ );
1114
1281
  return false;
1115
1282
  }
1116
1283
  },