@aztec/sequencer-client 4.0.0-nightly.20250907 → 4.0.0-nightly.20260107

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 (108) hide show
  1. package/dest/client/index.d.ts +1 -1
  2. package/dest/client/sequencer-client.d.ts +10 -8
  3. package/dest/client/sequencer-client.d.ts.map +1 -1
  4. package/dest/client/sequencer-client.js +40 -28
  5. package/dest/config.d.ts +13 -5
  6. package/dest/config.d.ts.map +1 -1
  7. package/dest/config.js +82 -25
  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 +11 -8
  16. package/dest/publisher/config.d.ts.map +1 -1
  17. package/dest/publisher/config.js +21 -13
  18. package/dest/publisher/index.d.ts +2 -2
  19. package/dest/publisher/index.d.ts.map +1 -1
  20. package/dest/publisher/index.js +1 -1
  21. package/dest/publisher/sequencer-publisher-factory.d.ts +11 -5
  22. package/dest/publisher/sequencer-publisher-factory.d.ts.map +1 -1
  23. package/dest/publisher/sequencer-publisher-factory.js +9 -2
  24. package/dest/publisher/sequencer-publisher-metrics.d.ts +4 -4
  25. package/dest/publisher/sequencer-publisher-metrics.d.ts.map +1 -1
  26. package/dest/publisher/sequencer-publisher-metrics.js +1 -1
  27. package/dest/publisher/sequencer-publisher.d.ts +76 -69
  28. package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
  29. package/dest/publisher/sequencer-publisher.js +290 -180
  30. package/dest/sequencer/block_builder.d.ts +6 -10
  31. package/dest/sequencer/block_builder.d.ts.map +1 -1
  32. package/dest/sequencer/block_builder.js +21 -10
  33. package/dest/sequencer/checkpoint_builder.d.ts +63 -0
  34. package/dest/sequencer/checkpoint_builder.d.ts.map +1 -0
  35. package/dest/sequencer/checkpoint_builder.js +131 -0
  36. package/dest/sequencer/checkpoint_proposal_job.d.ts +74 -0
  37. package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -0
  38. package/dest/sequencer/checkpoint_proposal_job.js +642 -0
  39. package/dest/sequencer/checkpoint_voter.d.ts +34 -0
  40. package/dest/sequencer/checkpoint_voter.d.ts.map +1 -0
  41. package/dest/sequencer/checkpoint_voter.js +85 -0
  42. package/dest/sequencer/config.d.ts +3 -2
  43. package/dest/sequencer/config.d.ts.map +1 -1
  44. package/dest/sequencer/errors.d.ts +11 -0
  45. package/dest/sequencer/errors.d.ts.map +1 -0
  46. package/dest/sequencer/errors.js +15 -0
  47. package/dest/sequencer/events.d.ts +46 -0
  48. package/dest/sequencer/events.d.ts.map +1 -0
  49. package/dest/sequencer/events.js +1 -0
  50. package/dest/sequencer/index.d.ts +5 -1
  51. package/dest/sequencer/index.d.ts.map +1 -1
  52. package/dest/sequencer/index.js +4 -0
  53. package/dest/sequencer/metrics.d.ts +37 -20
  54. package/dest/sequencer/metrics.d.ts.map +1 -1
  55. package/dest/sequencer/metrics.js +211 -85
  56. package/dest/sequencer/sequencer.d.ts +109 -121
  57. package/dest/sequencer/sequencer.d.ts.map +1 -1
  58. package/dest/sequencer/sequencer.js +798 -525
  59. package/dest/sequencer/timetable.d.ts +57 -21
  60. package/dest/sequencer/timetable.d.ts.map +1 -1
  61. package/dest/sequencer/timetable.js +150 -68
  62. package/dest/sequencer/types.d.ts +3 -0
  63. package/dest/sequencer/types.d.ts.map +1 -0
  64. package/dest/sequencer/types.js +1 -0
  65. package/dest/sequencer/utils.d.ts +20 -28
  66. package/dest/sequencer/utils.d.ts.map +1 -1
  67. package/dest/sequencer/utils.js +12 -24
  68. package/dest/test/index.d.ts +4 -2
  69. package/dest/test/index.d.ts.map +1 -1
  70. package/dest/test/mock_checkpoint_builder.d.ts +83 -0
  71. package/dest/test/mock_checkpoint_builder.d.ts.map +1 -0
  72. package/dest/test/mock_checkpoint_builder.js +179 -0
  73. package/dest/test/utils.d.ts +49 -0
  74. package/dest/test/utils.d.ts.map +1 -0
  75. package/dest/test/utils.js +94 -0
  76. package/dest/tx_validator/nullifier_cache.d.ts +1 -1
  77. package/dest/tx_validator/nullifier_cache.d.ts.map +1 -1
  78. package/dest/tx_validator/tx_validator_factory.d.ts +4 -3
  79. package/dest/tx_validator/tx_validator_factory.d.ts.map +1 -1
  80. package/dest/tx_validator/tx_validator_factory.js +12 -9
  81. package/package.json +32 -31
  82. package/src/client/sequencer-client.ts +34 -40
  83. package/src/config.ts +89 -29
  84. package/src/global_variable_builder/global_builder.ts +56 -48
  85. package/src/index.ts +2 -0
  86. package/src/publisher/config.ts +32 -19
  87. package/src/publisher/index.ts +1 -1
  88. package/src/publisher/sequencer-publisher-factory.ts +19 -6
  89. package/src/publisher/sequencer-publisher-metrics.ts +3 -3
  90. package/src/publisher/sequencer-publisher.ts +410 -240
  91. package/src/sequencer/README.md +531 -0
  92. package/src/sequencer/block_builder.ts +28 -30
  93. package/src/sequencer/checkpoint_builder.ts +217 -0
  94. package/src/sequencer/checkpoint_proposal_job.ts +706 -0
  95. package/src/sequencer/checkpoint_voter.ts +105 -0
  96. package/src/sequencer/config.ts +2 -1
  97. package/src/sequencer/errors.ts +21 -0
  98. package/src/sequencer/events.ts +27 -0
  99. package/src/sequencer/index.ts +4 -0
  100. package/src/sequencer/metrics.ts +269 -94
  101. package/src/sequencer/sequencer.ts +506 -676
  102. package/src/sequencer/timetable.ts +181 -91
  103. package/src/sequencer/types.ts +6 -0
  104. package/src/sequencer/utils.ts +24 -29
  105. package/src/test/index.ts +3 -1
  106. package/src/test/mock_checkpoint_builder.ts +247 -0
  107. package/src/test/utils.ts +137 -0
  108. package/src/tx_validator/tx_validator_factory.ts +13 -7
@@ -1,25 +1,23 @@
1
- import { Blob } from '@aztec/blob-lib';
2
- import { createBlobSinkClient } from '@aztec/blob-sink/client';
3
- import { FormattedViemError, MULTI_CALL_3_ADDRESS, Multicall3, RollupContract, formatViemError, tryExtractEvent } from '@aztec/ethereum';
1
+ import { Blob, getBlobsPerL1Block, getPrefixedEthBlobCommitments } from '@aztec/blob-lib';
2
+ import { MULTI_CALL_3_ADDRESS, Multicall3, RollupContract } from '@aztec/ethereum/contracts';
3
+ import { L1FeeAnalyzer } from '@aztec/ethereum/l1-fee-analysis';
4
+ import { WEI_CONST } from '@aztec/ethereum/l1-tx-utils';
5
+ import { FormattedViemError, formatViemError, tryExtractEvent } from '@aztec/ethereum/utils';
4
6
  import { sumBigint } from '@aztec/foundation/bigint';
5
7
  import { toHex as toPaddedHex } from '@aztec/foundation/bigint-buffer';
8
+ import { BlockNumber, CheckpointNumber, SlotNumber } from '@aztec/foundation/branded-types';
9
+ import { pick } from '@aztec/foundation/collection';
6
10
  import { EthAddress } from '@aztec/foundation/eth-address';
11
+ import { Signature } from '@aztec/foundation/eth-signature';
7
12
  import { createLogger } from '@aztec/foundation/log';
8
13
  import { bufferToHex } from '@aztec/foundation/string';
9
14
  import { Timer } from '@aztec/foundation/timer';
10
15
  import { EmpireBaseAbi, ErrorsAbi, RollupAbi } from '@aztec/l1-artifacts';
11
16
  import { encodeSlashConsensusVotes } from '@aztec/slasher';
12
- import { CommitteeAttestation } from '@aztec/stdlib/block';
13
- import { ConsensusPayload, SignatureDomainSeparator, getHashedSignaturePayload } from '@aztec/stdlib/p2p';
17
+ import { CommitteeAttestationsAndSigners } from '@aztec/stdlib/block';
14
18
  import { getTelemetryClient } from '@aztec/telemetry-client';
15
- import pick from 'lodash.pick';
16
19
  import { encodeFunctionData, toHex } from 'viem';
17
20
  import { SequencerPublisherMetrics } from './sequencer-publisher-metrics.js';
18
- export var SignalType = /*#__PURE__*/ function(SignalType) {
19
- SignalType[SignalType["GOVERNANCE"] = 0] = "GOVERNANCE";
20
- SignalType[SignalType["SLASHING"] = 1] = "SLASHING";
21
- return SignalType;
22
- }({});
23
21
  export const Actions = [
24
22
  'invalidate-by-invalid-attestation',
25
23
  'invalidate-by-insufficient-attestations',
@@ -40,10 +38,13 @@ export class SequencerPublisher {
40
38
  epochCache;
41
39
  governanceLog;
42
40
  slashingLog;
43
- myLastSignals;
41
+ lastActions;
42
+ isPayloadEmptyCache;
44
43
  log;
45
44
  ethereumSlotDuration;
46
- blobSinkClient;
45
+ blobClient;
46
+ /** Address to use for simulations in fisherman mode (actual proposer's address) */ proposerAddressForSimulation;
47
+ /** L1 fee analyzer for fisherman mode */ l1FeeAnalyzer;
47
48
  // @note - with blobs, the below estimate seems too large.
48
49
  // Total used for full block from int_l1_pub e2e test: 1m (of which 86k is 1x blob)
49
50
  // Total used for emptier block from above test: 429k (of which 84k is 1x blob)
@@ -63,17 +64,14 @@ export class SequencerPublisher {
63
64
  this.interrupted = false;
64
65
  this.governanceLog = createLogger('sequencer:publisher:governance');
65
66
  this.slashingLog = createLogger('sequencer:publisher:slashing');
66
- this.myLastSignals = {
67
- [0]: 0n,
68
- [1]: 0n
69
- };
70
- this.log = createLogger('sequencer:publisher');
67
+ this.lastActions = {};
68
+ this.isPayloadEmptyCache = new Map();
71
69
  this.requests = [];
70
+ this.log = deps.log ?? createLogger('sequencer:publisher');
72
71
  this.ethereumSlotDuration = BigInt(config.ethereumSlotDuration);
73
72
  this.epochCache = deps.epochCache;
74
- this.blobSinkClient = deps.blobSinkClient ?? createBlobSinkClient(config, {
75
- logger: createLogger('sequencer:blob-sink:client')
76
- });
73
+ this.lastActions = deps.lastActions;
74
+ this.blobClient = deps.blobClient;
77
75
  const telemetry = deps.telemetry ?? getTelemetryClient();
78
76
  this.metrics = deps.metrics ?? new SequencerPublisherMetrics(telemetry, 'SequencerPublisher');
79
77
  this.l1TxUtils = deps.l1TxUtils;
@@ -86,6 +84,10 @@ export class SequencerPublisher {
86
84
  this.slashingProposerContract = newSlashingProposer;
87
85
  });
88
86
  this.slashFactoryContract = deps.slashFactoryContract;
87
+ // Initialize L1 fee analyzer for fisherman mode
88
+ if (config.fishermanMode) {
89
+ this.l1FeeAnalyzer = new L1FeeAnalyzer(this.l1TxUtils.client, deps.dateProvider, createLogger('sequencer:publisher:fee-analyzer'));
90
+ }
89
91
  }
90
92
  getRollupContract() {
91
93
  return this.rollupContract;
@@ -93,6 +95,17 @@ export class SequencerPublisher {
93
95
  getSenderAddress() {
94
96
  return this.l1TxUtils.getSenderAddress();
95
97
  }
98
+ /**
99
+ * Gets the L1 fee analyzer instance (only available in fisherman mode)
100
+ */ getL1FeeAnalyzer() {
101
+ return this.l1FeeAnalyzer;
102
+ }
103
+ /**
104
+ * Sets the proposer address to use for simulations in fisherman mode.
105
+ * @param proposerAddress - The actual proposer's address to use for balance lookups in simulations
106
+ */ setProposerAddressForSimulation(proposerAddress) {
107
+ this.proposerAddressForSimulation = proposerAddress;
108
+ }
96
109
  addRequest(request) {
97
110
  this.requests.push(request);
98
111
  }
@@ -100,6 +113,55 @@ export class SequencerPublisher {
100
113
  return this.epochCache.getEpochAndSlotNow().slot;
101
114
  }
102
115
  /**
116
+ * Clears all pending requests without sending them.
117
+ */ clearPendingRequests() {
118
+ const count = this.requests.length;
119
+ this.requests = [];
120
+ if (count > 0) {
121
+ this.log.debug(`Cleared ${count} pending request(s)`);
122
+ }
123
+ }
124
+ /**
125
+ * Analyzes L1 fees for the pending requests without sending them.
126
+ * This is used in fisherman mode to validate fee calculations.
127
+ * @param l2SlotNumber - The L2 slot number for this analysis
128
+ * @param onComplete - Optional callback to invoke when analysis completes (after block is mined)
129
+ * @returns The analysis result (incomplete until block mines), or undefined if no requests
130
+ */ async analyzeL1Fees(l2SlotNumber, onComplete) {
131
+ if (!this.l1FeeAnalyzer) {
132
+ this.log.warn('L1 fee analyzer not available (not in fisherman mode)');
133
+ return undefined;
134
+ }
135
+ const requestsToAnalyze = [
136
+ ...this.requests
137
+ ];
138
+ if (requestsToAnalyze.length === 0) {
139
+ this.log.debug('No requests to analyze for L1 fees');
140
+ return undefined;
141
+ }
142
+ // Extract blob config from requests (if any)
143
+ const blobConfigs = requestsToAnalyze.filter((request)=>request.blobConfig).map((request)=>request.blobConfig);
144
+ const blobConfig = blobConfigs[0];
145
+ // Get gas configs
146
+ const gasConfigs = requestsToAnalyze.filter((request)=>request.gasConfig).map((request)=>request.gasConfig);
147
+ const gasLimits = gasConfigs.map((g)=>g?.gasLimit).filter((g)=>g !== undefined);
148
+ const gasLimit = gasLimits.length > 0 ? gasLimits.reduce((sum, g)=>sum + g, 0n) : 0n;
149
+ // Get the transaction requests
150
+ const l1Requests = requestsToAnalyze.map((r)=>r.request);
151
+ // Start the analysis
152
+ const analysisId = await this.l1FeeAnalyzer.startAnalysis(l2SlotNumber, gasLimit > 0n ? gasLimit : SequencerPublisher.PROPOSE_GAS_GUESS, l1Requests, blobConfig, onComplete);
153
+ this.log.info('Started L1 fee analysis', {
154
+ analysisId,
155
+ l2SlotNumber: l2SlotNumber.toString(),
156
+ requestCount: requestsToAnalyze.length,
157
+ hasBlobConfig: !!blobConfig,
158
+ gasLimit: gasLimit.toString(),
159
+ actions: requestsToAnalyze.map((r)=>r.action)
160
+ });
161
+ // Return the analysis result (will be incomplete until block mines)
162
+ return this.l1FeeAnalyzer.getAnalysis(analysisId);
163
+ }
164
+ /**
103
165
  * Sends all requests that are still valid.
104
166
  * @returns one of:
105
167
  * - A receipt and stats if the tx succeeded
@@ -110,7 +172,7 @@ export class SequencerPublisher {
110
172
  ...this.requests
111
173
  ];
112
174
  this.requests = [];
113
- if (this.interrupted) {
175
+ if (this.interrupted || requestsToProcess.length === 0) {
114
176
  return undefined;
115
177
  }
116
178
  const currentL2Slot = this.getCurrentL2Slot();
@@ -148,7 +210,7 @@ export class SequencerPublisher {
148
210
  const gasLimit = gasLimits.length > 0 ? sumBigint(gasLimits) : undefined; // sum
149
211
  const txTimeoutAts = gasConfigs.map((g)=>g?.txTimeoutAt).filter((g)=>g !== undefined);
150
212
  const txTimeoutAt = txTimeoutAts.length > 0 ? new Date(Math.min(...txTimeoutAts.map((g)=>g.getTime()))) : undefined; // earliest
151
- const gasConfig = {
213
+ const txConfig = {
152
214
  gasLimit,
153
215
  txTimeoutAt
154
216
  };
@@ -157,9 +219,10 @@ export class SequencerPublisher {
157
219
  validRequests.sort((a, b)=>compareActions(a.action, b.action));
158
220
  try {
159
221
  this.log.debug('Forwarding transactions', {
160
- validRequests: validRequests.map((request)=>request.action)
222
+ validRequests: validRequests.map((request)=>request.action),
223
+ txConfig
161
224
  });
162
- const result = await Multicall3.forward(validRequests.map((request)=>request.request), this.l1TxUtils, gasConfig, blobConfig, this.rollupContract.address, this.log);
225
+ const result = await Multicall3.forward(validRequests.map((request)=>request.request), this.l1TxUtils, txConfig, blobConfig, this.rollupContract.address, this.log);
163
226
  const { successfulActions = [], failedActions = [] } = this.callbackBundledTransactions(validRequests, result);
164
227
  return {
165
228
  result,
@@ -218,7 +281,9 @@ export class SequencerPublisher {
218
281
  'InvalidProposer',
219
282
  'InvalidArchive'
220
283
  ];
221
- return this.rollupContract.canProposeAtNextEthBlock(tipArchive.toBuffer(), msgSender.toString(), this.ethereumSlotDuration, opts).catch((err)=>{
284
+ return this.rollupContract.canProposeAtNextEthBlock(tipArchive.toBuffer(), msgSender.toString(), Number(this.ethereumSlotDuration), {
285
+ forcePendingCheckpointNumber: opts.forcePendingBlockNumber !== undefined ? CheckpointNumber.fromBlockNumber(opts.forcePendingBlockNumber) : undefined
286
+ }).catch((err)=>{
222
287
  if (err instanceof FormattedViemError && ignoredErrors.find((e)=>err.message.includes(e))) {
223
288
  this.log.warn(`Failed canProposeAtTime check with ${ignoredErrors.find((e)=>err.message.includes(e))}`, {
224
289
  error: err.message
@@ -241,16 +306,28 @@ export class SequencerPublisher {
241
306
  };
242
307
  const args = [
243
308
  header.toViem(),
244
- RollupContract.packAttestations([]),
309
+ CommitteeAttestationsAndSigners.empty().getPackedAttestations(),
245
310
  [],
311
+ Signature.empty().toViemSignature(),
246
312
  `0x${'0'.repeat(64)}`,
247
313
  header.contentCommitment.blobsHash.toString(),
248
314
  flags
249
315
  ];
250
316
  const ts = BigInt((await this.l1TxUtils.getBlock()).timestamp + this.ethereumSlotDuration);
251
- // use sender balance to simulate
252
- const balance = await this.l1TxUtils.getSenderBalance();
253
- this.log.debug(`Simulating validateHeader with balance: ${balance}`);
317
+ const optsForcePendingCheckpointNumber = opts?.forcePendingBlockNumber !== undefined ? CheckpointNumber.fromBlockNumber(opts.forcePendingBlockNumber) : undefined;
318
+ const stateOverrides = await this.rollupContract.makePendingCheckpointNumberOverride(optsForcePendingCheckpointNumber);
319
+ let balance = 0n;
320
+ if (this.config.fishermanMode) {
321
+ // In fisherman mode, we can't know where the proposer is publishing from
322
+ // so we just add sufficient balance to the multicall3 address
323
+ balance = 10n * WEI_CONST * WEI_CONST; // 10 ETH
324
+ } else {
325
+ balance = await this.l1TxUtils.getSenderBalance();
326
+ }
327
+ stateOverrides.push({
328
+ address: MULTI_CALL_3_ADDRESS,
329
+ balance
330
+ });
254
331
  await this.l1TxUtils.simulate({
255
332
  to: this.rollupContract.address,
256
333
  data: encodeFunctionData({
@@ -261,13 +338,7 @@ export class SequencerPublisher {
261
338
  from: MULTI_CALL_3_ADDRESS
262
339
  }, {
263
340
  time: ts + 1n
264
- }, [
265
- {
266
- address: MULTI_CALL_3_ADDRESS,
267
- balance
268
- },
269
- ...await this.rollupContract.makePendingBlockNumberOverride(opts?.forcePendingBlockNumber)
270
- ]);
341
+ }, stateOverrides);
271
342
  this.log.debug(`Simulated validateHeader`);
272
343
  }
273
344
  /**
@@ -278,13 +349,13 @@ export class SequencerPublisher {
278
349
  return undefined;
279
350
  }
280
351
  const { reason, block } = validationResult;
281
- const blockNumber = block.block.number;
352
+ const blockNumber = block.blockNumber;
282
353
  const logData = {
283
- ...block.block.toBlockInfo(),
354
+ ...block,
284
355
  reason
285
356
  };
286
- const currentBlockNumber = await this.rollupContract.getBlockNumber();
287
- if (currentBlockNumber < validationResult.block.block.number) {
357
+ const currentBlockNumber = await this.rollupContract.getCheckpointNumber();
358
+ if (currentBlockNumber < validationResult.block.blockNumber) {
288
359
  this.log.verbose(`Skipping block ${blockNumber} invalidation since it has already been removed from the pending chain`, {
289
360
  currentBlockNumber,
290
361
  ...logData
@@ -292,7 +363,10 @@ export class SequencerPublisher {
292
363
  return undefined;
293
364
  }
294
365
  const request = this.buildInvalidateBlockRequest(validationResult);
295
- this.log.debug(`Simulating invalidate block ${blockNumber}`, logData);
366
+ this.log.debug(`Simulating invalidate block ${blockNumber}`, {
367
+ ...logData,
368
+ request
369
+ });
296
370
  try {
297
371
  const { gasUsed } = await this.l1TxUtils.simulate(request, undefined, undefined, ErrorsAbi);
298
372
  this.log.verbose(`Simulation for invalidate block ${blockNumber} succeeded`, {
@@ -304,7 +378,7 @@ export class SequencerPublisher {
304
378
  request,
305
379
  gasUsed,
306
380
  blockNumber,
307
- forcePendingBlockNumber: blockNumber - 1,
381
+ forcePendingBlockNumber: BlockNumber(blockNumber - 1),
308
382
  reason
309
383
  };
310
384
  } catch (err) {
@@ -317,7 +391,7 @@ export class SequencerPublisher {
317
391
  request,
318
392
  error: viemError.message
319
393
  });
320
- const latestPendingBlockNumber = await this.rollupContract.getBlockNumber();
394
+ const latestPendingBlockNumber = await this.rollupContract.getCheckpointNumber();
321
395
  if (latestPendingBlockNumber < blockNumber) {
322
396
  this.log.verbose(`Block number ${blockNumber} has already been invalidated`, {
323
397
  ...logData
@@ -343,66 +417,58 @@ export class SequencerPublisher {
343
417
  }
344
418
  const { block, committee, reason } = validationResult;
345
419
  const logData = {
346
- ...block.block.toBlockInfo(),
420
+ ...block,
347
421
  reason
348
422
  };
349
- this.log.debug(`Simulating invalidate block ${block.block.number}`, logData);
423
+ this.log.debug(`Simulating invalidate block ${block.blockNumber}`, logData);
424
+ const attestationsAndSigners = new CommitteeAttestationsAndSigners(validationResult.attestations).getPackedAttestations();
350
425
  if (reason === 'invalid-attestation') {
351
- return this.rollupContract.buildInvalidateBadAttestationRequest(block.block.number, block.attestations.map((a)=>a.toViem()), committee, validationResult.invalidIndex);
426
+ return this.rollupContract.buildInvalidateBadAttestationRequest(CheckpointNumber.fromBlockNumber(block.blockNumber), attestationsAndSigners, committee, validationResult.invalidIndex);
352
427
  } else if (reason === 'insufficient-attestations') {
353
- return this.rollupContract.buildInvalidateInsufficientAttestationsRequest(block.block.number, block.attestations.map((a)=>a.toViem()), committee);
428
+ return this.rollupContract.buildInvalidateInsufficientAttestationsRequest(CheckpointNumber.fromBlockNumber(block.blockNumber), attestationsAndSigners, committee);
354
429
  } else {
355
430
  const _ = reason;
356
431
  throw new Error(`Unknown reason for invalidation`);
357
432
  }
358
433
  }
359
- /**
360
- * @notice Will simulate `propose` to make sure that the block is valid for submission
361
- *
362
- * @dev Throws if unable to propose
363
- *
364
- * @param block - The block to propose
365
- * @param attestationData - The block's attestation data
366
- *
367
- */ async validateBlockForSubmission(block, attestationData = {
368
- digest: Buffer.alloc(32),
369
- attestations: []
370
- }, options) {
434
+ /** Simulates `propose` to make sure that the checkpoint is valid for submission */ async validateCheckpointForSubmission(checkpoint, attestationsAndSigners, attestationsAndSignersSignature, options) {
371
435
  const ts = BigInt((await this.l1TxUtils.getBlock()).timestamp + this.ethereumSlotDuration);
436
+ // TODO(palla/mbps): This should not be needed, there's no flow where we propose with zero attestations. Or is there?
372
437
  // If we have no attestations, we still need to provide the empty attestations
373
438
  // so that the committee is recalculated correctly
374
- const ignoreSignatures = attestationData.attestations.length === 0;
375
- if (ignoreSignatures) {
376
- const { committee } = await this.epochCache.getCommittee(block.header.globalVariables.slotNumber.toBigInt());
377
- if (!committee) {
378
- this.log.warn(`No committee found for slot ${block.header.globalVariables.slotNumber.toBigInt()}`);
379
- throw new Error(`No committee found for slot ${block.header.globalVariables.slotNumber.toBigInt()}`);
380
- }
381
- attestationData.attestations = committee.map((committeeMember)=>CommitteeAttestation.fromAddress(committeeMember));
382
- }
383
- const blobs = await Blob.getBlobsPerBlock(block.body.toBlobFields());
384
- const blobInput = Blob.getPrefixedEthBlobCommitments(blobs);
385
- const formattedAttestations = attestationData.attestations.map((attest)=>attest.toViem());
386
- const signers = attestationData.attestations.filter((attest)=>!attest.signature.isEmpty()).map((attest)=>attest.address.toString());
439
+ // const ignoreSignatures = attestationsAndSigners.attestations.length === 0;
440
+ // if (ignoreSignatures) {
441
+ // const { committee } = await this.epochCache.getCommittee(block.header.globalVariables.slotNumber);
442
+ // if (!committee) {
443
+ // this.log.warn(`No committee found for slot ${block.header.globalVariables.slotNumber}`);
444
+ // throw new Error(`No committee found for slot ${block.header.globalVariables.slotNumber}`);
445
+ // }
446
+ // attestationsAndSigners.attestations = committee.map(committeeMember =>
447
+ // CommitteeAttestation.fromAddress(committeeMember),
448
+ // );
449
+ // }
450
+ const blobFields = checkpoint.toBlobFields();
451
+ const blobs = getBlobsPerL1Block(blobFields);
452
+ const blobInput = getPrefixedEthBlobCommitments(blobs);
387
453
  const args = [
388
454
  {
389
- header: block.header.toPropose().toViem(),
390
- archive: toHex(block.archive.root.toBuffer()),
391
- stateReference: block.header.state.toViem(),
392
- txHashes: block.body.txEffects.map((txEffect)=>txEffect.txHash.toString()),
455
+ header: checkpoint.header.toViem(),
456
+ archive: toHex(checkpoint.archive.root.toBuffer()),
393
457
  oracleInput: {
394
458
  feeAssetPriceModifier: 0n
395
459
  }
396
460
  },
397
- RollupContract.packAttestations(formattedAttestations),
398
- signers,
461
+ attestationsAndSigners.getPackedAttestations(),
462
+ attestationsAndSigners.getSigners().map((signer)=>signer.toString()),
463
+ attestationsAndSignersSignature.toViemSignature(),
399
464
  blobInput
400
465
  ];
401
466
  await this.simulateProposeTx(args, ts, options);
402
467
  return ts;
403
468
  }
404
469
  async enqueueCastSignalHelper(slotNumber, timestamp, signalType, payload, base, signerAddress, signer) {
405
- if (this.myLastSignals[signalType] >= slotNumber) {
470
+ if (this.lastActions[signalType] && this.lastActions[signalType] === slotNumber) {
471
+ this.log.debug(`Skipping duplicate vote cast signal ${signalType} for slot ${slotNumber}`);
406
472
  return false;
407
473
  }
408
474
  if (payload.equals(EthAddress.ZERO)) {
@@ -414,12 +480,19 @@ export class SequencerPublisher {
414
480
  }
415
481
  const round = await base.computeRound(slotNumber);
416
482
  const roundInfo = await base.getRoundInfo(this.rollupContract.address, round);
483
+ if (roundInfo.quorumReached) {
484
+ return false;
485
+ }
417
486
  if (roundInfo.lastSignalSlot >= slotNumber) {
418
487
  return false;
419
488
  }
420
- const cachedLastVote = this.myLastSignals[signalType];
421
- this.myLastSignals[signalType] = slotNumber;
422
- const action = signalType === 0 ? 'governance-signal' : 'empire-slashing-signal';
489
+ if (await this.isPayloadEmpty(payload)) {
490
+ this.log.warn(`Skipping vote cast for payload with empty code`);
491
+ return false;
492
+ }
493
+ const cachedLastVote = this.lastActions[signalType];
494
+ this.lastActions[signalType] = slotNumber;
495
+ const action = signalType;
423
496
  const request = await base.createSignalRequestWithSignature(payload.toString(), slotNumber, this.config.l1ChainId, signerAddress.toString(), signer);
424
497
  this.log.debug(`Created ${action} request with signature`, {
425
498
  request,
@@ -455,24 +528,34 @@ export class SequencerPublisher {
455
528
  payload: payload.toString()
456
529
  };
457
530
  if (!success) {
458
- this.log.error(`Signaling in [${action}] for ${payload} at slot ${slotNumber} in round ${round} failed`, logData);
459
- this.myLastSignals[signalType] = cachedLastVote;
531
+ this.log.error(`Signaling in ${action} for ${payload} at slot ${slotNumber} in round ${round} failed`, logData);
532
+ this.lastActions[signalType] = cachedLastVote;
460
533
  return false;
461
534
  } else {
462
- this.log.info(`Signaling in [${action}] for ${payload} at slot ${slotNumber} in round ${round} succeeded`, logData);
535
+ this.log.info(`Signaling in ${action} for ${payload} at slot ${slotNumber} in round ${round} succeeded`, logData);
463
536
  return true;
464
537
  }
465
538
  }
466
539
  });
467
540
  return true;
468
541
  }
542
+ async isPayloadEmpty(payload) {
543
+ const key = payload.toString();
544
+ const cached = this.isPayloadEmptyCache.get(key);
545
+ if (cached) {
546
+ return cached;
547
+ }
548
+ const isEmpty = !await this.l1TxUtils.getCode(payload);
549
+ this.isPayloadEmptyCache.set(key, isEmpty);
550
+ return isEmpty;
551
+ }
469
552
  /**
470
553
  * Enqueues a governance castSignal transaction to cast a signal for a given slot number.
471
554
  * @param slotNumber - The slot number to cast a signal for.
472
555
  * @param timestamp - The timestamp of the slot to cast a signal for.
473
556
  * @returns True if the signal was successfully enqueued, false otherwise.
474
557
  */ enqueueGovernanceCastSignal(governancePayload, slotNumber, timestamp, signerAddress, signer) {
475
- return this.enqueueCastSignalHelper(slotNumber, timestamp, 0, governancePayload, this.govProposerContract, signerAddress, signer);
558
+ return this.enqueueCastSignalHelper(slotNumber, timestamp, 'governance-signal', governancePayload, this.govProposerContract, signerAddress, signer);
476
559
  }
477
560
  /** Enqueues all slashing actions as returned by the slasher client. */ async enqueueSlashingActions(actions, slotNumber, timestamp, signerAddress, signer) {
478
561
  if (actions.length === 0) {
@@ -490,7 +573,7 @@ export class SequencerPublisher {
490
573
  this.log.debug(`Enqueuing slashing vote for payload ${action.payload} at slot ${slotNumber}`, {
491
574
  signerAddress
492
575
  });
493
- await this.enqueueCastSignalHelper(slotNumber, timestamp, 1, action.payload, this.slashingProposerContract, signerAddress, signer);
576
+ await this.enqueueCastSignalHelper(slotNumber, timestamp, 'empire-slashing-signal', action.payload, this.slashingProposerContract, signerAddress, signer);
494
577
  break;
495
578
  }
496
579
  case 'create-empire-payload':
@@ -561,24 +644,16 @@ export class SequencerPublisher {
561
644
  }
562
645
  return true;
563
646
  }
564
- /**
565
- * Proposes a L2 block on L1.
566
- *
567
- * @param block - L2 block to propose.
568
- * @returns True if the tx has been enqueued, throws otherwise. See #9315
569
- */ async enqueueProposeL2Block(block, attestations, txHashes, opts = {}) {
570
- const proposedBlockHeader = block.header.toPropose();
571
- const consensusPayload = ConsensusPayload.fromBlock(block);
572
- const digest = getHashedSignaturePayload(consensusPayload, SignatureDomainSeparator.blockAttestation);
573
- const blobs = await Blob.getBlobsPerBlock(block.body.toBlobFields());
647
+ /** Simulates and enqueues a proposal for a checkpoint on L1 */ async enqueueProposeCheckpoint(checkpoint, attestationsAndSigners, attestationsAndSignersSignature, opts = {}) {
648
+ const checkpointHeader = checkpoint.header;
649
+ const blobFields = checkpoint.toBlobFields();
650
+ const blobs = getBlobsPerL1Block(blobFields);
574
651
  const proposeTxArgs = {
575
- header: proposedBlockHeader,
576
- archive: block.archive.root.toBuffer(),
577
- stateReference: block.header.state,
578
- body: block.body.toBuffer(),
652
+ header: checkpointHeader,
653
+ archive: checkpoint.archive.root.toBuffer(),
579
654
  blobs,
580
- attestations,
581
- txHashes: txHashes ?? []
655
+ attestationsAndSigners,
656
+ attestationsAndSignersSignature
582
657
  };
583
658
  let ts;
584
659
  try {
@@ -586,26 +661,21 @@ export class SequencerPublisher {
586
661
  // This means that we can avoid the simulation issues in later checks.
587
662
  // By simulation issue, I mean the fact that the block.timestamp is equal to the last block, not the next, which
588
663
  // make time consistency checks break.
589
- const attestationData = {
590
- digest: digest.toBuffer(),
591
- attestations: attestations ?? []
592
- };
593
664
  // TODO(palla): Check whether we're validating twice, once here and once within addProposeTx, since we call simulateProposeTx in both places.
594
- ts = await this.validateBlockForSubmission(block, attestationData, opts);
665
+ ts = await this.validateCheckpointForSubmission(checkpoint, attestationsAndSigners, attestationsAndSignersSignature, opts);
595
666
  } catch (err) {
596
- this.log.error(`Block validation failed. ${err instanceof Error ? err.message : 'No error message'}`, err, {
597
- ...block.getStats(),
598
- slotNumber: block.header.globalVariables.slotNumber.toBigInt(),
667
+ this.log.error(`Checkpoint validation failed. ${err instanceof Error ? err.message : 'No error message'}`, err, {
668
+ ...checkpoint.getStats(),
669
+ slotNumber: checkpoint.header.slotNumber,
599
670
  forcePendingBlockNumber: opts.forcePendingBlockNumber
600
671
  });
601
672
  throw err;
602
673
  }
603
- this.log.verbose(`Enqueuing block propose transaction`, {
604
- ...block.toBlockInfo(),
674
+ this.log.verbose(`Enqueuing checkpoint propose transaction`, {
675
+ ...checkpoint.toCheckpointInfo(),
605
676
  ...opts
606
677
  });
607
- await this.addProposeTx(block, proposeTxArgs, opts, ts);
608
- return true;
678
+ await this.addProposeTx(checkpoint, proposeTxArgs, opts, ts);
609
679
  }
610
680
  enqueueInvalidateBlock(request, opts = {}) {
611
681
  if (!request) {
@@ -613,8 +683,10 @@ export class SequencerPublisher {
613
683
  }
614
684
  // We issued the simulation against the rollup contract, so we need to account for the overhead of the multicall3
615
685
  const gasLimit = this.l1TxUtils.bumpGasLimit(BigInt(Math.ceil(Number(request.gasUsed) * 64 / 63)));
686
+ const { gasUsed, blockNumber } = request;
616
687
  const logData = {
617
- ...pick(request, 'gasUsed', 'blockNumber'),
688
+ gasUsed,
689
+ blockNumber,
618
690
  gasLimit,
619
691
  opts
620
692
  };
@@ -626,9 +698,9 @@ export class SequencerPublisher {
626
698
  gasLimit,
627
699
  txTimeoutAt: opts.txTimeoutAt
628
700
  },
629
- lastValidL2Slot: this.getCurrentL2Slot() + 2n,
701
+ lastValidL2Slot: SlotNumber(this.getCurrentL2Slot() + 2),
630
702
  checkSuccess: (_req, result)=>{
631
- const success = result && result.receipt && result.receipt.status === 'success' && tryExtractEvent(result.receipt.logs, this.rollupContract.address, RollupAbi, 'BlockInvalidated');
703
+ const success = result && result.receipt && result.receipt.status === 'success' && tryExtractEvent(result.receipt.logs, this.rollupContract.address, RollupAbi, 'CheckpointInvalidated');
632
704
  if (!success) {
633
705
  this.log.warn(`Invalidate block ${request.blockNumber} failed`, {
634
706
  ...result,
@@ -650,8 +722,14 @@ export class SequencerPublisher {
650
722
  timestamp,
651
723
  gasLimit: undefined
652
724
  };
725
+ if (this.lastActions[action] && this.lastActions[action] === slotNumber) {
726
+ this.log.debug(`Skipping duplicate action ${action} for slot ${slotNumber}`);
727
+ return false;
728
+ }
729
+ const cachedLastActionSlot = this.lastActions[action];
730
+ this.lastActions[action] = slotNumber;
731
+ this.log.debug(`Simulating ${action} for slot ${slotNumber}`, logData);
653
732
  let gasUsed;
654
- this.log.debug(`Simulating ${action}`, logData);
655
733
  try {
656
734
  ({ gasUsed } = await this.l1TxUtils.simulate(request, {
657
735
  time: timestamp
@@ -684,6 +762,7 @@ export class SequencerPublisher {
684
762
  ...result,
685
763
  ...logData
686
764
  });
765
+ this.lastActions[action] = cachedLastActionSlot;
687
766
  } else {
688
767
  this.log.info(`Action ${action} at ${slotNumber} succeeded`, {
689
768
  ...result,
@@ -710,45 +789,52 @@ export class SequencerPublisher {
710
789
  }
711
790
  async prepareProposeTx(encodedData, timestamp, options) {
712
791
  const kzg = Blob.getViemKzgInstance();
713
- const blobInput = Blob.getPrefixedEthBlobCommitments(encodedData.blobs);
792
+ const blobInput = getPrefixedEthBlobCommitments(encodedData.blobs);
714
793
  this.log.debug('Validating blob input', {
715
794
  blobInput
716
795
  });
717
- const blobEvaluationGas = await this.l1TxUtils.estimateGas(this.getSenderAddress().toString(), {
718
- to: this.rollupContract.address,
719
- data: encodeFunctionData({
720
- abi: RollupAbi,
721
- functionName: 'validateBlobs',
722
- args: [
723
- blobInput
724
- ]
725
- })
726
- }, {}, {
727
- blobs: encodedData.blobs.map((b)=>b.data),
728
- kzg
729
- }).catch((err)=>{
730
- const { message, metaMessages } = formatViemError(err);
731
- this.log.error(`Failed to validate blobs`, message, {
732
- metaMessages
796
+ // Get blob evaluation gas
797
+ let blobEvaluationGas;
798
+ if (this.config.fishermanMode) {
799
+ // In fisherman mode, we can't estimate blob gas because estimateGas doesn't support state overrides
800
+ // Use a fixed estimate.
801
+ blobEvaluationGas = BigInt(encodedData.blobs.length) * 21_000n;
802
+ this.log.debug(`Using fixed blob evaluation gas estimate in fisherman mode: ${blobEvaluationGas}`);
803
+ } else {
804
+ // Normal mode - use estimateGas with blob inputs
805
+ blobEvaluationGas = await this.l1TxUtils.estimateGas(this.getSenderAddress().toString(), {
806
+ to: this.rollupContract.address,
807
+ data: encodeFunctionData({
808
+ abi: RollupAbi,
809
+ functionName: 'validateBlobs',
810
+ args: [
811
+ blobInput
812
+ ]
813
+ })
814
+ }, {}, {
815
+ blobs: encodedData.blobs.map((b)=>b.data),
816
+ kzg
817
+ }).catch((err)=>{
818
+ const { message, metaMessages } = formatViemError(err);
819
+ this.log.error(`Failed to validate blobs`, message, {
820
+ metaMessages
821
+ });
822
+ throw new Error('Failed to validate blobs');
733
823
  });
734
- throw new Error('Failed to validate blobs');
735
- });
736
- const attestations = encodedData.attestations ? encodedData.attestations.map((attest)=>attest.toViem()) : [];
737
- const txHashes = encodedData.txHashes ? encodedData.txHashes.map((txHash)=>txHash.toString()) : [];
738
- const signers = encodedData.attestations?.filter((attest)=>!attest.signature.isEmpty()).map((attest)=>attest.address.toString());
824
+ }
825
+ const signers = encodedData.attestationsAndSigners.getSigners().map((signer)=>signer.toString());
739
826
  const args = [
740
827
  {
741
828
  header: encodedData.header.toViem(),
742
829
  archive: toHex(encodedData.archive),
743
- stateReference: encodedData.stateReference.toViem(),
744
830
  oracleInput: {
745
831
  // We are currently not modifying these. See #9963
746
832
  feeAssetPriceModifier: 0n
747
- },
748
- txHashes
833
+ }
749
834
  },
750
- RollupContract.packAttestations(attestations),
751
- signers ?? [],
835
+ encodedData.attestationsAndSigners.getPackedAttestations(),
836
+ signers,
837
+ encodedData.attestationsAndSignersSignature.toViemSignature(),
752
838
  blobInput
753
839
  ];
754
840
  const { rollupData, simulationResult } = await this.simulateProposeTx(args, timestamp, options);
@@ -770,18 +856,10 @@ export class SequencerPublisher {
770
856
  functionName: 'propose',
771
857
  args
772
858
  });
773
- // override the pending block number if requested
774
- const forcePendingBlockNumberStateDiff = (options.forcePendingBlockNumber !== undefined ? await this.rollupContract.makePendingBlockNumberOverride(options.forcePendingBlockNumber) : []).flatMap((override)=>override.stateDiff ?? []);
775
- const simulationResult = await this.l1TxUtils.simulate({
776
- to: this.rollupContract.address,
777
- data: rollupData,
778
- gas: SequencerPublisher.PROPOSE_GAS_GUESS
779
- }, {
780
- // @note we add 1n to the timestamp because geth implementation doesn't like simulation timestamp to be equal to the current block timestamp
781
- time: timestamp + 1n,
782
- // @note reth should have a 30m gas limit per block but throws errors that this tx is beyond limit so we increase here
783
- gasLimit: SequencerPublisher.PROPOSE_GAS_GUESS * 2n
784
- }, [
859
+ // override the pending checkpoint number if requested
860
+ const optsForcePendingCheckpointNumber = options.forcePendingBlockNumber !== undefined ? CheckpointNumber.fromBlockNumber(options.forcePendingBlockNumber) : undefined;
861
+ const forcePendingCheckpointNumberStateDiff = (optsForcePendingCheckpointNumber !== undefined ? await this.rollupContract.makePendingCheckpointNumberOverride(optsForcePendingCheckpointNumber) : []).flatMap((override)=>override.stateDiff ?? []);
862
+ const stateOverrides = [
785
863
  {
786
864
  address: this.rollupContract.address,
787
865
  // @note we override checkBlob to false since blobs are not part simulate()
@@ -790,14 +868,44 @@ export class SequencerPublisher {
790
868
  slot: toPaddedHex(RollupContract.checkBlobStorageSlot, true),
791
869
  value: toPaddedHex(0n, true)
792
870
  },
793
- ...forcePendingBlockNumberStateDiff
871
+ ...forcePendingCheckpointNumberStateDiff
794
872
  ]
795
873
  }
796
- ], RollupAbi, {
874
+ ];
875
+ // In fisherman mode, simulate as the proposer but with sufficient balance
876
+ if (this.proposerAddressForSimulation) {
877
+ stateOverrides.push({
878
+ address: this.proposerAddressForSimulation.toString(),
879
+ balance: 10n * WEI_CONST * WEI_CONST
880
+ });
881
+ }
882
+ const simulationResult = await this.l1TxUtils.simulate({
883
+ to: this.rollupContract.address,
884
+ data: rollupData,
885
+ gas: SequencerPublisher.PROPOSE_GAS_GUESS,
886
+ ...this.proposerAddressForSimulation && {
887
+ from: this.proposerAddressForSimulation.toString()
888
+ }
889
+ }, {
890
+ // @note we add 1n to the timestamp because geth implementation doesn't like simulation timestamp to be equal to the current block timestamp
891
+ time: timestamp + 1n,
892
+ // @note reth should have a 30m gas limit per block but throws errors that this tx is beyond limit so we increase here
893
+ gasLimit: SequencerPublisher.PROPOSE_GAS_GUESS * 2n
894
+ }, stateOverrides, RollupAbi, {
797
895
  // @note fallback gas estimate to use if the node doesn't support simulation API
798
896
  fallbackGasEstimate: SequencerPublisher.PROPOSE_GAS_GUESS
799
897
  }).catch((err)=>{
800
- this.log.error(`Failed to simulate propose tx`, err);
898
+ // In fisherman mode, we expect ValidatorSelection__MissingProposerSignature since fisherman doesn't have proposer signature
899
+ const viemError = formatViemError(err);
900
+ if (this.config.fishermanMode && viemError.message?.includes('ValidatorSelection__MissingProposerSignature')) {
901
+ this.log.debug(`Ignoring expected ValidatorSelection__MissingProposerSignature error in fisherman mode`);
902
+ // Return a minimal simulation result with the fallback gas estimate
903
+ return {
904
+ gasUsed: SequencerPublisher.PROPOSE_GAS_GUESS,
905
+ logs: []
906
+ };
907
+ }
908
+ this.log.error(`Failed to simulate propose tx`, viemError);
801
909
  throw err;
802
910
  });
803
911
  return {
@@ -805,24 +913,25 @@ export class SequencerPublisher {
805
913
  simulationResult
806
914
  };
807
915
  }
808
- async addProposeTx(block, encodedData, opts = {}, timestamp) {
916
+ async addProposeTx(checkpoint, encodedData, opts = {}, timestamp) {
917
+ const slot = checkpoint.header.slotNumber;
809
918
  const timer = new Timer();
810
919
  const kzg = Blob.getViemKzgInstance();
811
920
  const { rollupData, simulationResult, blobEvaluationGas } = await this.prepareProposeTx(encodedData, timestamp, opts);
812
921
  const startBlock = await this.l1TxUtils.getBlockNumber();
813
922
  const gasLimit = this.l1TxUtils.bumpGasLimit(BigInt(Math.ceil(Number(simulationResult.gasUsed) * 64 / 63)) + blobEvaluationGas + SequencerPublisher.MULTICALL_OVERHEAD_GAS_GUESS);
814
- // Send the blobs to the blob sink preemptively. This helps in tests where the sequencer mistakingly thinks that the propose
815
- // tx fails but it does get mined. We make sure that the blobs are sent to the blob sink regardless of the tx outcome.
816
- void this.blobSinkClient.sendBlobsToBlobSink(encodedData.blobs).catch((_err)=>{
817
- this.log.error('Failed to send blobs to blob sink');
818
- });
923
+ // Send the blobs to the blob client preemptively. This helps in tests where the sequencer mistakingly thinks that the propose
924
+ // tx fails but it does get mined. We make sure that the blobs are sent to the blob client regardless of the tx outcome.
925
+ void Promise.resolve().then(()=>this.blobClient.sendBlobsToFilestore(encodedData.blobs).catch((_err)=>{
926
+ this.log.error('Failed to send blobs to blob client');
927
+ }));
819
928
  return this.addRequest({
820
929
  action: 'propose',
821
930
  request: {
822
931
  to: this.rollupContract.address,
823
932
  data: rollupData
824
933
  },
825
- lastValidL2Slot: block.header.globalVariables.slotNumber.toBigInt(),
934
+ lastValidL2Slot: checkpoint.header.slotNumber,
826
935
  gasConfig: {
827
936
  ...opts,
828
937
  gasLimit
@@ -836,36 +945,37 @@ export class SequencerPublisher {
836
945
  return false;
837
946
  }
838
947
  const { receipt, stats, errorMsg } = result;
839
- const success = receipt && receipt.status === 'success' && tryExtractEvent(receipt.logs, this.rollupContract.address, RollupAbi, 'L2BlockProposed');
948
+ const success = receipt && receipt.status === 'success' && tryExtractEvent(receipt.logs, this.rollupContract.address, RollupAbi, 'CheckpointProposed');
840
949
  if (success) {
841
950
  const endBlock = receipt.blockNumber;
842
951
  const inclusionBlocks = Number(endBlock - startBlock);
952
+ const { calldataGas, calldataSize, sender } = stats;
843
953
  const publishStats = {
844
954
  gasPrice: receipt.effectiveGasPrice,
845
955
  gasUsed: receipt.gasUsed,
846
956
  blobGasUsed: receipt.blobGasUsed ?? 0n,
847
957
  blobDataGas: receipt.blobGasPrice ?? 0n,
848
958
  transactionHash: receipt.transactionHash,
849
- ...pick(stats, 'calldataGas', 'calldataSize', 'sender'),
850
- ...block.getStats(),
959
+ calldataGas,
960
+ calldataSize,
961
+ sender,
962
+ ...checkpoint.getStats(),
851
963
  eventName: 'rollup-published-to-l1',
852
964
  blobCount: encodedData.blobs.length,
853
965
  inclusionBlocks
854
966
  };
855
- this.log.info(`Published L2 block to L1 rollup contract`, {
967
+ this.log.info(`Published checkpoint ${checkpoint.number} at slot ${slot} to rollup contract`, {
856
968
  ...stats,
857
- ...block.getStats(),
858
- ...receipt
969
+ ...checkpoint.getStats(),
970
+ ...pick(receipt, 'transactionHash', 'blockHash')
859
971
  });
860
972
  this.metrics.recordProcessBlockTx(timer.ms(), publishStats);
861
973
  return true;
862
974
  } else {
863
975
  this.metrics.recordFailedTx('process');
864
- this.log.error(`Rollup process tx failed: ${errorMsg ?? 'no error message'}`, undefined, {
865
- ...block.getStats(),
866
- receipt,
867
- txHash: receipt.transactionHash,
868
- slotNumber: block.header.globalVariables.slotNumber.toBigInt()
976
+ this.log.error(`Publishing checkpoint at slot ${slot} failed with ${errorMsg ?? 'no error message'}`, undefined, {
977
+ ...checkpoint.getStats(),
978
+ ...receipt
869
979
  });
870
980
  return false;
871
981
  }