@aztec/sequencer-client 3.0.0-canary.a9708bd → 3.0.0-devnet.2-patch.1

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 (73) hide show
  1. package/dest/client/index.d.ts +1 -1
  2. package/dest/client/sequencer-client.d.ts +7 -6
  3. package/dest/client/sequencer-client.d.ts.map +1 -1
  4. package/dest/client/sequencer-client.js +23 -14
  5. package/dest/config.d.ts +5 -3
  6. package/dest/config.d.ts.map +1 -1
  7. package/dest/config.js +21 -1
  8. package/dest/global_variable_builder/global_builder.d.ts +5 -7
  9. package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
  10. package/dest/global_variable_builder/global_builder.js +12 -8
  11. package/dest/global_variable_builder/index.d.ts +1 -1
  12. package/dest/index.d.ts +1 -1
  13. package/dest/publisher/config.d.ts +9 -10
  14. package/dest/publisher/config.d.ts.map +1 -1
  15. package/dest/publisher/config.js +19 -17
  16. package/dest/publisher/index.d.ts +2 -2
  17. package/dest/publisher/index.d.ts.map +1 -1
  18. package/dest/publisher/index.js +1 -1
  19. package/dest/publisher/sequencer-publisher-factory.d.ts +9 -3
  20. package/dest/publisher/sequencer-publisher-factory.d.ts.map +1 -1
  21. package/dest/publisher/sequencer-publisher-factory.js +8 -1
  22. package/dest/publisher/sequencer-publisher-metrics.d.ts +2 -2
  23. package/dest/publisher/sequencer-publisher-metrics.d.ts.map +1 -1
  24. package/dest/publisher/sequencer-publisher-metrics.js +1 -1
  25. package/dest/publisher/sequencer-publisher.d.ts +53 -50
  26. package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
  27. package/dest/publisher/sequencer-publisher.js +187 -132
  28. package/dest/sequencer/block_builder.d.ts +6 -8
  29. package/dest/sequencer/block_builder.d.ts.map +1 -1
  30. package/dest/sequencer/block_builder.js +18 -9
  31. package/dest/sequencer/config.d.ts +2 -2
  32. package/dest/sequencer/config.d.ts.map +1 -1
  33. package/dest/sequencer/errors.d.ts +11 -0
  34. package/dest/sequencer/errors.d.ts.map +1 -0
  35. package/dest/sequencer/errors.js +15 -0
  36. package/dest/sequencer/index.d.ts +1 -1
  37. package/dest/sequencer/metrics.d.ts +17 -20
  38. package/dest/sequencer/metrics.d.ts.map +1 -1
  39. package/dest/sequencer/metrics.js +59 -87
  40. package/dest/sequencer/sequencer.d.ts +54 -32
  41. package/dest/sequencer/sequencer.d.ts.map +1 -1
  42. package/dest/sequencer/sequencer.js +458 -184
  43. package/dest/sequencer/timetable.d.ts +4 -8
  44. package/dest/sequencer/timetable.d.ts.map +1 -1
  45. package/dest/sequencer/timetable.js +3 -10
  46. package/dest/sequencer/utils.d.ts +11 -25
  47. package/dest/sequencer/utils.d.ts.map +1 -1
  48. package/dest/sequencer/utils.js +9 -24
  49. package/dest/test/index.d.ts +2 -2
  50. package/dest/test/index.d.ts.map +1 -1
  51. package/dest/tx_validator/nullifier_cache.d.ts +1 -1
  52. package/dest/tx_validator/nullifier_cache.d.ts.map +1 -1
  53. package/dest/tx_validator/tx_validator_factory.d.ts +4 -3
  54. package/dest/tx_validator/tx_validator_factory.d.ts.map +1 -1
  55. package/dest/tx_validator/tx_validator_factory.js +12 -9
  56. package/package.json +32 -31
  57. package/src/client/sequencer-client.ts +24 -18
  58. package/src/config.ts +23 -6
  59. package/src/global_variable_builder/global_builder.ts +19 -17
  60. package/src/publisher/config.ts +29 -27
  61. package/src/publisher/index.ts +1 -1
  62. package/src/publisher/sequencer-publisher-factory.ts +16 -3
  63. package/src/publisher/sequencer-publisher-metrics.ts +1 -1
  64. package/src/publisher/sequencer-publisher.ts +257 -183
  65. package/src/sequencer/block_builder.ts +23 -28
  66. package/src/sequencer/config.ts +1 -1
  67. package/src/sequencer/errors.ts +21 -0
  68. package/src/sequencer/metrics.ts +71 -98
  69. package/src/sequencer/sequencer.ts +546 -237
  70. package/src/sequencer/timetable.ts +10 -14
  71. package/src/sequencer/utils.ts +10 -24
  72. package/src/test/index.ts +1 -1
  73. package/src/tx_validator/tx_validator_factory.ts +13 -7
@@ -1,47 +1,46 @@
1
- import type { L2Block } from '@aztec/aztec.js';
2
- import { Blob } from '@aztec/blob-lib';
1
+ import { L2Block } from '@aztec/aztec.js/block';
2
+ import { Blob, getBlobsPerL1Block, getPrefixedEthBlobCommitments } from '@aztec/blob-lib';
3
3
  import { type BlobSinkClientInterface, createBlobSinkClient } from '@aztec/blob-sink/client';
4
4
  import type { EpochCache } from '@aztec/epoch-cache';
5
+ import type { L1ContractsConfig } from '@aztec/ethereum/config';
5
6
  import {
6
7
  type EmpireSlashingProposerContract,
7
- FormattedViemError,
8
- type GasPrice,
9
8
  type GovernanceProposerContract,
10
9
  type IEmpireBase,
11
- type L1BlobInputs,
12
- type L1ContractsConfig,
13
- type L1GasConfig,
14
- type L1TxRequest,
15
10
  MULTI_CALL_3_ADDRESS,
16
11
  Multicall3,
17
12
  RollupContract,
18
13
  type TallySlashingProposerContract,
19
- type TransactionStats,
20
14
  type ViemCommitteeAttestations,
21
15
  type ViemHeader,
22
- type ViemStateReference,
23
- formatViemError,
24
- tryExtractEvent,
25
- } from '@aztec/ethereum';
16
+ } from '@aztec/ethereum/contracts';
17
+ import {
18
+ type L1BlobInputs,
19
+ type L1TxConfig,
20
+ type L1TxRequest,
21
+ type TransactionStats,
22
+ WEI_CONST,
23
+ } from '@aztec/ethereum/l1-tx-utils';
26
24
  import type { L1TxUtilsWithBlobs } from '@aztec/ethereum/l1-tx-utils-with-blobs';
25
+ import { FormattedViemError, formatViemError, tryExtractEvent } from '@aztec/ethereum/utils';
27
26
  import { sumBigint } from '@aztec/foundation/bigint';
28
27
  import { toHex as toPaddedHex } from '@aztec/foundation/bigint-buffer';
28
+ import { BlockNumber, CheckpointNumber, SlotNumber } from '@aztec/foundation/branded-types';
29
+ import type { Fr } from '@aztec/foundation/curves/bn254';
29
30
  import { EthAddress } from '@aztec/foundation/eth-address';
30
- import type { Fr } from '@aztec/foundation/fields';
31
- import { createLogger } from '@aztec/foundation/log';
31
+ import { Signature, type ViemSignature } from '@aztec/foundation/eth-signature';
32
+ import { type Logger, createLogger } from '@aztec/foundation/log';
32
33
  import { bufferToHex } from '@aztec/foundation/string';
33
34
  import { DateProvider, Timer } from '@aztec/foundation/timer';
34
35
  import { EmpireBaseAbi, ErrorsAbi, RollupAbi } from '@aztec/l1-artifacts';
35
36
  import { type ProposerSlashAction, encodeSlashConsensusVotes } from '@aztec/slasher';
36
- import { CommitteeAttestation, type ValidateBlockResult } from '@aztec/stdlib/block';
37
+ import { CommitteeAttestation, CommitteeAttestationsAndSigners, type ValidateBlockResult } from '@aztec/stdlib/block';
37
38
  import { SlashFactoryContract } from '@aztec/stdlib/l1-contracts';
38
- import { ConsensusPayload, SignatureDomainSeparator, getHashedSignaturePayload } from '@aztec/stdlib/p2p';
39
+ import type { CheckpointHeader } from '@aztec/stdlib/rollup';
39
40
  import type { L1PublishBlockStats } from '@aztec/stdlib/stats';
40
- import { type ProposedBlockHeader, StateReference, TxHash } from '@aztec/stdlib/tx';
41
41
  import { type TelemetryClient, getTelemetryClient } from '@aztec/telemetry-client';
42
42
 
43
- import pick from 'lodash.pick';
44
- import { type TransactionReceipt, type TypedDataDefinition, encodeFunctionData, toHex } from 'viem';
43
+ import { type StateOverride, type TransactionReceipt, type TypedDataDefinition, encodeFunctionData, toHex } from 'viem';
45
44
 
46
45
  import type { PublisherConfig, TxSenderConfig } from './config.js';
47
46
  import { SequencerPublisherMetrics } from './sequencer-publisher-metrics.js';
@@ -49,24 +48,17 @@ import { SequencerPublisherMetrics } from './sequencer-publisher-metrics.js';
49
48
  /** Arguments to the process method of the rollup contract */
50
49
  type L1ProcessArgs = {
51
50
  /** The L2 block header. */
52
- header: ProposedBlockHeader;
51
+ header: CheckpointHeader;
53
52
  /** A root of the archive tree after the L2 block is applied. */
54
53
  archive: Buffer;
55
- /** State reference after the L2 block is applied. */
56
- stateReference: StateReference;
57
54
  /** L2 block blobs containing all tx effects. */
58
55
  blobs: Blob[];
59
- /** L2 block tx hashes */
60
- txHashes: TxHash[];
61
56
  /** Attestations */
62
- attestations?: CommitteeAttestation[];
57
+ attestationsAndSigners: CommitteeAttestationsAndSigners;
58
+ /** Attestations and signers signature */
59
+ attestationsAndSignersSignature: Signature;
63
60
  };
64
61
 
65
- export enum SignalType {
66
- GOVERNANCE,
67
- SLASHING,
68
- }
69
-
70
62
  export const Actions = [
71
63
  'invalidate-by-invalid-attestation',
72
64
  'invalidate-by-insufficient-attestations',
@@ -78,8 +70,11 @@ export const Actions = [
78
70
  'vote-offenses',
79
71
  'execute-slash',
80
72
  ] as const;
73
+
81
74
  export type Action = (typeof Actions)[number];
82
75
 
76
+ type GovernanceSignalAction = Extract<Action, 'governance-signal' | 'empire-slashing-signal'>;
77
+
83
78
  // Sorting for actions such that invalidations go before proposals, and proposals go before votes
84
79
  export const compareActions = (a: Action, b: Action) => Actions.indexOf(a) - Actions.indexOf(b);
85
80
 
@@ -87,19 +82,19 @@ export type InvalidateBlockRequest = {
87
82
  request: L1TxRequest;
88
83
  reason: 'invalid-attestation' | 'insufficient-attestations';
89
84
  gasUsed: bigint;
90
- blockNumber: number;
91
- forcePendingBlockNumber: number;
85
+ blockNumber: BlockNumber;
86
+ forcePendingBlockNumber: BlockNumber;
92
87
  };
93
88
 
94
89
  interface RequestWithExpiry {
95
90
  action: Action;
96
91
  request: L1TxRequest;
97
- lastValidL2Slot: bigint;
98
- gasConfig?: Pick<L1GasConfig, 'txTimeoutAt' | 'gasLimit'>;
92
+ lastValidL2Slot: SlotNumber;
93
+ gasConfig?: Pick<L1TxConfig, 'txTimeoutAt' | 'gasLimit'>;
99
94
  blobConfig?: L1BlobInputs;
100
95
  checkSuccess: (
101
96
  request: L1TxRequest,
102
- result?: { receipt: TransactionReceipt; gasPrice: GasPrice; stats?: TransactionStats; errorMsg?: string },
97
+ result?: { receipt: TransactionReceipt; stats?: TransactionStats; errorMsg?: string },
103
98
  ) => boolean;
104
99
  }
105
100
 
@@ -111,15 +106,15 @@ export class SequencerPublisher {
111
106
  protected governanceLog = createLogger('sequencer:publisher:governance');
112
107
  protected slashingLog = createLogger('sequencer:publisher:slashing');
113
108
 
114
- private myLastSignals: Record<SignalType, bigint> = {
115
- [SignalType.GOVERNANCE]: 0n,
116
- [SignalType.SLASHING]: 0n,
117
- };
109
+ protected lastActions: Partial<Record<Action, SlotNumber>> = {};
118
110
 
119
- protected log = createLogger('sequencer:publisher');
111
+ protected log: Logger;
120
112
  protected ethereumSlotDuration: bigint;
121
113
 
122
114
  private blobSinkClient: BlobSinkClientInterface;
115
+
116
+ /** Address to use for simulations in fisherman mode (actual proposer's address) */
117
+ private proposerAddressForSimulation?: EthAddress;
123
118
  // @note - with blobs, the below estimate seems too large.
124
119
  // Total used for full block from int_l1_pub e2e test: 1m (of which 86k is 1x blob)
125
120
  // Total used for emptier block from above test: 429k (of which 84k is 1x blob)
@@ -152,10 +147,14 @@ export class SequencerPublisher {
152
147
  epochCache: EpochCache;
153
148
  dateProvider: DateProvider;
154
149
  metrics: SequencerPublisherMetrics;
150
+ lastActions: Partial<Record<Action, SlotNumber>>;
151
+ log?: Logger;
155
152
  },
156
153
  ) {
154
+ this.log = deps.log ?? createLogger('sequencer:publisher');
157
155
  this.ethereumSlotDuration = BigInt(config.ethereumSlotDuration);
158
156
  this.epochCache = deps.epochCache;
157
+ this.lastActions = deps.lastActions;
159
158
 
160
159
  this.blobSinkClient =
161
160
  deps.blobSinkClient ?? createBlobSinkClient(config, { logger: createLogger('sequencer:blob-sink:client') });
@@ -185,14 +184,33 @@ export class SequencerPublisher {
185
184
  return this.l1TxUtils.getSenderAddress();
186
185
  }
187
186
 
187
+ /**
188
+ * Sets the proposer address to use for simulations in fisherman mode.
189
+ * @param proposerAddress - The actual proposer's address to use for balance lookups in simulations
190
+ */
191
+ public setProposerAddressForSimulation(proposerAddress: EthAddress | undefined) {
192
+ this.proposerAddressForSimulation = proposerAddress;
193
+ }
194
+
188
195
  public addRequest(request: RequestWithExpiry) {
189
196
  this.requests.push(request);
190
197
  }
191
198
 
192
- public getCurrentL2Slot(): bigint {
199
+ public getCurrentL2Slot(): SlotNumber {
193
200
  return this.epochCache.getEpochAndSlotNow().slot;
194
201
  }
195
202
 
203
+ /**
204
+ * Clears all pending requests without sending them.
205
+ */
206
+ public clearPendingRequests(): void {
207
+ const count = this.requests.length;
208
+ this.requests = [];
209
+ if (count > 0) {
210
+ this.log.debug(`Cleared ${count} pending request(s)`);
211
+ }
212
+ }
213
+
196
214
  /**
197
215
  * Sends all requests that are still valid.
198
216
  * @returns one of:
@@ -249,18 +267,21 @@ export class SequencerPublisher {
249
267
  const gasLimit = gasLimits.length > 0 ? sumBigint(gasLimits) : undefined; // sum
250
268
  const txTimeoutAts = gasConfigs.map(g => g?.txTimeoutAt).filter((g): g is Date => g !== undefined);
251
269
  const txTimeoutAt = txTimeoutAts.length > 0 ? new Date(Math.min(...txTimeoutAts.map(g => g.getTime()))) : undefined; // earliest
252
- const gasConfig: RequestWithExpiry['gasConfig'] = { gasLimit, txTimeoutAt };
270
+ const txConfig: RequestWithExpiry['gasConfig'] = { gasLimit, txTimeoutAt };
253
271
 
254
272
  // Sort the requests so that proposals always go first
255
273
  // This ensures the committee gets precomputed correctly
256
274
  validRequests.sort((a, b) => compareActions(a.action, b.action));
257
275
 
258
276
  try {
259
- this.log.debug('Forwarding transactions', { validRequests: validRequests.map(request => request.action) });
277
+ this.log.debug('Forwarding transactions', {
278
+ validRequests: validRequests.map(request => request.action),
279
+ txConfig,
280
+ });
260
281
  const result = await Multicall3.forward(
261
282
  validRequests.map(request => request.request),
262
283
  this.l1TxUtils,
263
- gasConfig,
284
+ txConfig,
264
285
  blobConfig,
265
286
  this.rollupContract.address,
266
287
  this.log,
@@ -285,7 +306,7 @@ export class SequencerPublisher {
285
306
 
286
307
  private callbackBundledTransactions(
287
308
  requests: RequestWithExpiry[],
288
- result?: { receipt: TransactionReceipt; gasPrice: GasPrice } | FormattedViemError,
309
+ result?: { receipt: TransactionReceipt } | FormattedViemError,
289
310
  ) {
290
311
  const actionsListStr = requests.map(r => r.action).join(', ');
291
312
  if (result instanceof FormattedViemError) {
@@ -314,13 +335,18 @@ export class SequencerPublisher {
314
335
  public canProposeAtNextEthBlock(
315
336
  tipArchive: Fr,
316
337
  msgSender: EthAddress,
317
- opts: { forcePendingBlockNumber?: number } = {},
338
+ opts: { forcePendingBlockNumber?: BlockNumber } = {},
318
339
  ) {
319
340
  // TODO: #14291 - should loop through multiple keys to check if any of them can propose
320
341
  const ignoredErrors = ['SlotAlreadyInChain', 'InvalidProposer', 'InvalidArchive'];
321
342
 
322
343
  return this.rollupContract
323
- .canProposeAtNextEthBlock(tipArchive.toBuffer(), msgSender.toString(), this.ethereumSlotDuration, opts)
344
+ .canProposeAtNextEthBlock(tipArchive.toBuffer(), msgSender.toString(), Number(this.ethereumSlotDuration), {
345
+ forcePendingCheckpointNumber:
346
+ opts.forcePendingBlockNumber !== undefined
347
+ ? CheckpointNumber.fromBlockNumber(opts.forcePendingBlockNumber)
348
+ : undefined,
349
+ })
324
350
  .catch(err => {
325
351
  if (err instanceof FormattedViemError && ignoredErrors.find(e => err.message.includes(e))) {
326
352
  this.log.warn(`Failed canProposeAtTime check with ${ignoredErrors.find(e => err.message.includes(e))}`, {
@@ -339,25 +365,42 @@ export class SequencerPublisher {
339
365
  * @param header - The block header to validate
340
366
  */
341
367
  public async validateBlockHeader(
342
- header: ProposedBlockHeader,
343
- opts?: { forcePendingBlockNumber: number | undefined },
368
+ header: CheckpointHeader,
369
+ opts?: { forcePendingBlockNumber: BlockNumber | undefined },
344
370
  ) {
345
371
  const flags = { ignoreDA: true, ignoreSignatures: true };
346
372
 
347
373
  const args = [
348
374
  header.toViem(),
349
- RollupContract.packAttestations([]),
375
+ CommitteeAttestationsAndSigners.empty().getPackedAttestations(),
350
376
  [], // no signers
377
+ Signature.empty().toViemSignature(),
351
378
  `0x${'0'.repeat(64)}`, // 32 empty bytes
352
379
  header.contentCommitment.blobsHash.toString(),
353
380
  flags,
354
381
  ] as const;
355
382
 
356
383
  const ts = BigInt((await this.l1TxUtils.getBlock()).timestamp + this.ethereumSlotDuration);
384
+ const optsForcePendingCheckpointNumber =
385
+ opts?.forcePendingBlockNumber !== undefined
386
+ ? CheckpointNumber.fromBlockNumber(opts.forcePendingBlockNumber)
387
+ : undefined;
388
+ const stateOverrides = await this.rollupContract.makePendingCheckpointNumberOverride(
389
+ optsForcePendingCheckpointNumber,
390
+ );
391
+ let balance = 0n;
392
+ if (this.config.fishermanMode) {
393
+ // In fisherman mode, we can't know where the proposer is publishing from
394
+ // so we just add sufficient balance to the multicall3 address
395
+ balance = 10n * WEI_CONST * WEI_CONST; // 10 ETH
396
+ } else {
397
+ balance = await this.l1TxUtils.getSenderBalance();
398
+ }
399
+ stateOverrides.push({
400
+ address: MULTI_CALL_3_ADDRESS,
401
+ balance,
402
+ });
357
403
 
358
- // use sender balance to simulate
359
- const balance = await this.l1TxUtils.getSenderBalance();
360
- this.log.debug(`Simulating validateHeader with balance: ${balance}`);
361
404
  await this.l1TxUtils.simulate(
362
405
  {
363
406
  to: this.rollupContract.address,
@@ -365,10 +408,7 @@ export class SequencerPublisher {
365
408
  from: MULTI_CALL_3_ADDRESS,
366
409
  },
367
410
  { time: ts + 1n },
368
- [
369
- { address: MULTI_CALL_3_ADDRESS, balance },
370
- ...(await this.rollupContract.makePendingBlockNumberOverride(opts?.forcePendingBlockNumber)),
371
- ],
411
+ stateOverrides,
372
412
  );
373
413
  this.log.debug(`Simulated validateHeader`);
374
414
  }
@@ -385,11 +425,11 @@ export class SequencerPublisher {
385
425
  }
386
426
 
387
427
  const { reason, block } = validationResult;
388
- const blockNumber = block.block.number;
389
- const logData = { ...block.block.toBlockInfo(), reason };
428
+ const blockNumber = block.blockNumber;
429
+ const logData = { ...block, reason };
390
430
 
391
- const currentBlockNumber = await this.rollupContract.getBlockNumber();
392
- if (currentBlockNumber < validationResult.block.block.number) {
431
+ const currentBlockNumber = await this.rollupContract.getCheckpointNumber();
432
+ if (currentBlockNumber < validationResult.block.blockNumber) {
393
433
  this.log.verbose(
394
434
  `Skipping block ${blockNumber} invalidation since it has already been removed from the pending chain`,
395
435
  { currentBlockNumber, ...logData },
@@ -398,13 +438,13 @@ export class SequencerPublisher {
398
438
  }
399
439
 
400
440
  const request = this.buildInvalidateBlockRequest(validationResult);
401
- this.log.debug(`Simulating invalidate block ${blockNumber}`, logData);
441
+ this.log.debug(`Simulating invalidate block ${blockNumber}`, { ...logData, request });
402
442
 
403
443
  try {
404
444
  const { gasUsed } = await this.l1TxUtils.simulate(request, undefined, undefined, ErrorsAbi);
405
445
  this.log.verbose(`Simulation for invalidate block ${blockNumber} succeeded`, { ...logData, request, gasUsed });
406
446
 
407
- return { request, gasUsed, blockNumber, forcePendingBlockNumber: blockNumber - 1, reason };
447
+ return { request, gasUsed, blockNumber, forcePendingBlockNumber: BlockNumber(blockNumber - 1), reason };
408
448
  } catch (err) {
409
449
  const viemError = formatViemError(err);
410
450
 
@@ -415,7 +455,7 @@ export class SequencerPublisher {
415
455
  `Simulation for invalidate block ${blockNumber} failed due to block not being in pending chain`,
416
456
  { ...logData, request, error: viemError.message },
417
457
  );
418
- const latestPendingBlockNumber = await this.rollupContract.getBlockNumber();
458
+ const latestPendingBlockNumber = await this.rollupContract.getCheckpointNumber();
419
459
  if (latestPendingBlockNumber < blockNumber) {
420
460
  this.log.verbose(`Block number ${blockNumber} has already been invalidated`, { ...logData });
421
461
  return undefined;
@@ -443,20 +483,24 @@ export class SequencerPublisher {
443
483
  }
444
484
 
445
485
  const { block, committee, reason } = validationResult;
446
- const logData = { ...block.block.toBlockInfo(), reason };
447
- this.log.debug(`Simulating invalidate block ${block.block.number}`, logData);
486
+ const logData = { ...block, reason };
487
+ this.log.debug(`Simulating invalidate block ${block.blockNumber}`, logData);
488
+
489
+ const attestationsAndSigners = new CommitteeAttestationsAndSigners(
490
+ validationResult.attestations,
491
+ ).getPackedAttestations();
448
492
 
449
493
  if (reason === 'invalid-attestation') {
450
494
  return this.rollupContract.buildInvalidateBadAttestationRequest(
451
- block.block.number,
452
- block.attestations.map(a => a.toViem()),
495
+ CheckpointNumber.fromBlockNumber(block.blockNumber),
496
+ attestationsAndSigners,
453
497
  committee,
454
498
  validationResult.invalidIndex,
455
499
  );
456
500
  } else if (reason === 'insufficient-attestations') {
457
501
  return this.rollupContract.buildInvalidateInsufficientAttestationsRequest(
458
- block.block.number,
459
- block.attestations.map(a => a.toViem()),
502
+ CheckpointNumber.fromBlockNumber(block.blockNumber),
503
+ attestationsAndSigners,
460
504
  committee,
461
505
  );
462
506
  } else {
@@ -476,48 +520,41 @@ export class SequencerPublisher {
476
520
  */
477
521
  public async validateBlockForSubmission(
478
522
  block: L2Block,
479
- attestationData: { digest: Buffer; attestations: CommitteeAttestation[] } = {
480
- digest: Buffer.alloc(32),
481
- attestations: [],
482
- },
483
- options: { forcePendingBlockNumber?: number },
523
+ attestationsAndSigners: CommitteeAttestationsAndSigners,
524
+ attestationsAndSignersSignature: Signature,
525
+ options: { forcePendingBlockNumber?: BlockNumber },
484
526
  ): Promise<bigint> {
485
527
  const ts = BigInt((await this.l1TxUtils.getBlock()).timestamp + this.ethereumSlotDuration);
486
528
 
487
529
  // If we have no attestations, we still need to provide the empty attestations
488
530
  // so that the committee is recalculated correctly
489
- const ignoreSignatures = attestationData.attestations.length === 0;
531
+ const ignoreSignatures = attestationsAndSigners.attestations.length === 0;
490
532
  if (ignoreSignatures) {
491
- const { committee } = await this.epochCache.getCommittee(block.header.globalVariables.slotNumber.toBigInt());
533
+ const { committee } = await this.epochCache.getCommittee(block.header.globalVariables.slotNumber);
492
534
  if (!committee) {
493
- this.log.warn(`No committee found for slot ${block.header.globalVariables.slotNumber.toBigInt()}`);
494
- throw new Error(`No committee found for slot ${block.header.globalVariables.slotNumber.toBigInt()}`);
535
+ this.log.warn(`No committee found for slot ${block.header.globalVariables.slotNumber}`);
536
+ throw new Error(`No committee found for slot ${block.header.globalVariables.slotNumber}`);
495
537
  }
496
- attestationData.attestations = committee.map(committeeMember =>
538
+ attestationsAndSigners.attestations = committee.map(committeeMember =>
497
539
  CommitteeAttestation.fromAddress(committeeMember),
498
540
  );
499
541
  }
500
542
 
501
- const blobs = await Blob.getBlobsPerBlock(block.body.toBlobFields());
502
- const blobInput = Blob.getPrefixedEthBlobCommitments(blobs);
503
-
504
- const formattedAttestations = attestationData.attestations.map(attest => attest.toViem());
505
- const signers = attestationData.attestations
506
- .filter(attest => !attest.signature.isEmpty())
507
- .map(attest => attest.address.toString());
543
+ const blobFields = block.getCheckpointBlobFields();
544
+ const blobs = getBlobsPerL1Block(blobFields);
545
+ const blobInput = getPrefixedEthBlobCommitments(blobs);
508
546
 
509
547
  const args = [
510
548
  {
511
- header: block.header.toPropose().toViem(),
549
+ header: block.getCheckpointHeader().toViem(),
512
550
  archive: toHex(block.archive.root.toBuffer()),
513
- stateReference: block.header.state.toViem(),
514
- txHashes: block.body.txEffects.map(txEffect => txEffect.txHash.toString()),
515
551
  oracleInput: {
516
552
  feeAssetPriceModifier: 0n,
517
553
  },
518
554
  },
519
- RollupContract.packAttestations(formattedAttestations),
520
- signers,
555
+ attestationsAndSigners.getPackedAttestations(),
556
+ attestationsAndSigners.getSigners().map(signer => signer.toString()),
557
+ attestationsAndSignersSignature.toViemSignature(),
521
558
  blobInput,
522
559
  ] as const;
523
560
 
@@ -526,15 +563,16 @@ export class SequencerPublisher {
526
563
  }
527
564
 
528
565
  private async enqueueCastSignalHelper(
529
- slotNumber: bigint,
566
+ slotNumber: SlotNumber,
530
567
  timestamp: bigint,
531
- signalType: SignalType,
568
+ signalType: GovernanceSignalAction,
532
569
  payload: EthAddress,
533
570
  base: IEmpireBase,
534
571
  signerAddress: EthAddress,
535
572
  signer: (msg: TypedDataDefinition) => Promise<`0x${string}`>,
536
573
  ): Promise<boolean> {
537
- if (this.myLastSignals[signalType] >= slotNumber) {
574
+ if (this.lastActions[signalType] && this.lastActions[signalType] === slotNumber) {
575
+ this.log.debug(`Skipping duplicate vote cast signal ${signalType} for slot ${slotNumber}`);
538
576
  return false;
539
577
  }
540
578
  if (payload.equals(EthAddress.ZERO)) {
@@ -551,10 +589,9 @@ export class SequencerPublisher {
551
589
  return false;
552
590
  }
553
591
 
554
- const cachedLastVote = this.myLastSignals[signalType];
555
- this.myLastSignals[signalType] = slotNumber;
556
-
557
- const action = signalType === SignalType.GOVERNANCE ? 'governance-signal' : 'empire-slashing-signal';
592
+ const cachedLastVote = this.lastActions[signalType];
593
+ this.lastActions[signalType] = slotNumber;
594
+ const action = signalType;
558
595
 
559
596
  const request = await base.createSignalRequestWithSignature(
560
597
  payload.toString(),
@@ -597,7 +634,7 @@ export class SequencerPublisher {
597
634
  `Signaling in [${action}] for ${payload} at slot ${slotNumber} in round ${round} failed`,
598
635
  logData,
599
636
  );
600
- this.myLastSignals[signalType] = cachedLastVote;
637
+ this.lastActions[signalType] = cachedLastVote;
601
638
  return false;
602
639
  } else {
603
640
  this.log.info(
@@ -619,7 +656,7 @@ export class SequencerPublisher {
619
656
  */
620
657
  public enqueueGovernanceCastSignal(
621
658
  governancePayload: EthAddress,
622
- slotNumber: bigint,
659
+ slotNumber: SlotNumber,
623
660
  timestamp: bigint,
624
661
  signerAddress: EthAddress,
625
662
  signer: (msg: TypedDataDefinition) => Promise<`0x${string}`>,
@@ -627,7 +664,7 @@ export class SequencerPublisher {
627
664
  return this.enqueueCastSignalHelper(
628
665
  slotNumber,
629
666
  timestamp,
630
- SignalType.GOVERNANCE,
667
+ 'governance-signal',
631
668
  governancePayload,
632
669
  this.govProposerContract,
633
670
  signerAddress,
@@ -638,7 +675,7 @@ export class SequencerPublisher {
638
675
  /** Enqueues all slashing actions as returned by the slasher client. */
639
676
  public async enqueueSlashingActions(
640
677
  actions: ProposerSlashAction[],
641
- slotNumber: bigint,
678
+ slotNumber: SlotNumber,
642
679
  timestamp: bigint,
643
680
  signerAddress: EthAddress,
644
681
  signer: (msg: TypedDataDefinition) => Promise<`0x${string}`>,
@@ -661,7 +698,7 @@ export class SequencerPublisher {
661
698
  await this.enqueueCastSignalHelper(
662
699
  slotNumber,
663
700
  timestamp,
664
- SignalType.SLASHING,
701
+ 'empire-slashing-signal',
665
702
  action.payload,
666
703
  this.slashingProposerContract,
667
704
  signerAddress,
@@ -766,24 +803,22 @@ export class SequencerPublisher {
766
803
  */
767
804
  public async enqueueProposeL2Block(
768
805
  block: L2Block,
769
- attestations?: CommitteeAttestation[],
770
- txHashes?: TxHash[],
771
- opts: { txTimeoutAt?: Date; forcePendingBlockNumber?: number } = {},
806
+ attestationsAndSigners: CommitteeAttestationsAndSigners,
807
+ attestationsAndSignersSignature: Signature,
808
+ opts: { txTimeoutAt?: Date; forcePendingBlockNumber?: BlockNumber } = {},
772
809
  ): Promise<boolean> {
773
- const proposedBlockHeader = block.header.toPropose();
810
+ const checkpointHeader = block.getCheckpointHeader();
774
811
 
775
- const consensusPayload = ConsensusPayload.fromBlock(block);
776
- const digest = getHashedSignaturePayload(consensusPayload, SignatureDomainSeparator.blockAttestation);
812
+ const blobFields = block.getCheckpointBlobFields();
813
+ const blobs = getBlobsPerL1Block(blobFields);
777
814
 
778
- const blobs = await Blob.getBlobsPerBlock(block.body.toBlobFields());
779
815
  const proposeTxArgs = {
780
- header: proposedBlockHeader,
816
+ header: checkpointHeader,
781
817
  archive: block.archive.root.toBuffer(),
782
- stateReference: block.header.state,
783
818
  body: block.body.toBuffer(),
784
819
  blobs,
785
- attestations,
786
- txHashes: txHashes ?? [],
820
+ attestationsAndSigners,
821
+ attestationsAndSignersSignature,
787
822
  };
788
823
 
789
824
  let ts: bigint;
@@ -793,13 +828,12 @@ export class SequencerPublisher {
793
828
  // This means that we can avoid the simulation issues in later checks.
794
829
  // By simulation issue, I mean the fact that the block.timestamp is equal to the last block, not the next, which
795
830
  // make time consistency checks break.
796
- const attestationData = { digest: digest.toBuffer(), attestations: attestations ?? [] };
797
831
  // TODO(palla): Check whether we're validating twice, once here and once within addProposeTx, since we call simulateProposeTx in both places.
798
- ts = await this.validateBlockForSubmission(block, attestationData, opts);
832
+ ts = await this.validateBlockForSubmission(block, attestationsAndSigners, attestationsAndSignersSignature, opts);
799
833
  } catch (err: any) {
800
834
  this.log.error(`Block validation failed. ${err instanceof Error ? err.message : 'No error message'}`, err, {
801
835
  ...block.getStats(),
802
- slotNumber: block.header.globalVariables.slotNumber.toBigInt(),
836
+ slotNumber: block.header.globalVariables.slotNumber,
803
837
  forcePendingBlockNumber: opts.forcePendingBlockNumber,
804
838
  });
805
839
  throw err;
@@ -818,19 +852,20 @@ export class SequencerPublisher {
818
852
  // We issued the simulation against the rollup contract, so we need to account for the overhead of the multicall3
819
853
  const gasLimit = this.l1TxUtils.bumpGasLimit(BigInt(Math.ceil((Number(request.gasUsed) * 64) / 63)));
820
854
 
821
- const logData = { ...pick(request, 'gasUsed', 'blockNumber'), gasLimit, opts };
855
+ const { gasUsed, blockNumber } = request;
856
+ const logData = { gasUsed, blockNumber, gasLimit, opts };
822
857
  this.log.verbose(`Enqueuing invalidate block request`, logData);
823
858
  this.addRequest({
824
859
  action: `invalidate-by-${request.reason}`,
825
860
  request: request.request,
826
861
  gasConfig: { gasLimit, txTimeoutAt: opts.txTimeoutAt },
827
- lastValidL2Slot: this.getCurrentL2Slot() + 2n,
862
+ lastValidL2Slot: SlotNumber(this.getCurrentL2Slot() + 2),
828
863
  checkSuccess: (_req, result) => {
829
864
  const success =
830
865
  result &&
831
866
  result.receipt &&
832
867
  result.receipt.status === 'success' &&
833
- tryExtractEvent(result.receipt.logs, this.rollupContract.address, RollupAbi, 'BlockInvalidated');
868
+ tryExtractEvent(result.receipt.logs, this.rollupContract.address, RollupAbi, 'CheckpointInvalidated');
834
869
  if (!success) {
835
870
  this.log.warn(`Invalidate block ${request.blockNumber} failed`, { ...result, ...logData });
836
871
  } else {
@@ -842,16 +877,24 @@ export class SequencerPublisher {
842
877
  }
843
878
 
844
879
  private async simulateAndEnqueueRequest(
845
- action: RequestWithExpiry['action'],
880
+ action: Action,
846
881
  request: L1TxRequest,
847
882
  checkSuccess: (receipt: TransactionReceipt) => boolean | undefined,
848
- slotNumber: bigint,
883
+ slotNumber: SlotNumber,
849
884
  timestamp: bigint,
850
885
  ) {
851
886
  const logData = { slotNumber, timestamp, gasLimit: undefined as bigint | undefined };
852
- let gasUsed: bigint;
887
+ if (this.lastActions[action] && this.lastActions[action] === slotNumber) {
888
+ this.log.debug(`Skipping duplicate action ${action} for slot ${slotNumber}`);
889
+ return false;
890
+ }
891
+
892
+ const cachedLastActionSlot = this.lastActions[action];
893
+ this.lastActions[action] = slotNumber;
894
+
895
+ this.log.debug(`Simulating ${action} for slot ${slotNumber}`, logData);
853
896
 
854
- this.log.debug(`Simulating ${action}`, logData);
897
+ let gasUsed: bigint;
855
898
  try {
856
899
  ({ gasUsed } = await this.l1TxUtils.simulate(request, { time: timestamp }, [], ErrorsAbi)); // TODO(palla/slash): Check the timestamp logic
857
900
  this.log.verbose(`Simulation for ${action} succeeded`, { ...logData, request, gasUsed });
@@ -875,6 +918,7 @@ export class SequencerPublisher {
875
918
  const success = result && result.receipt && result.receipt.status === 'success' && checkSuccess(result.receipt);
876
919
  if (!success) {
877
920
  this.log.warn(`Action ${action} at ${slotNumber} failed`, { ...result, ...logData });
921
+ this.lastActions[action] = cachedLastActionSlot;
878
922
  } else {
879
923
  this.log.info(`Action ${action} at ${slotNumber} succeeded`, { ...result, ...logData });
880
924
  }
@@ -904,54 +948,58 @@ export class SequencerPublisher {
904
948
  private async prepareProposeTx(
905
949
  encodedData: L1ProcessArgs,
906
950
  timestamp: bigint,
907
- options: { forcePendingBlockNumber?: number },
951
+ options: { forcePendingBlockNumber?: BlockNumber },
908
952
  ) {
909
953
  const kzg = Blob.getViemKzgInstance();
910
- const blobInput = Blob.getPrefixedEthBlobCommitments(encodedData.blobs);
954
+ const blobInput = getPrefixedEthBlobCommitments(encodedData.blobs);
911
955
  this.log.debug('Validating blob input', { blobInput });
912
- const blobEvaluationGas = await this.l1TxUtils
913
- .estimateGas(
914
- this.getSenderAddress().toString(),
915
- {
916
- to: this.rollupContract.address,
917
- data: encodeFunctionData({
918
- abi: RollupAbi,
919
- functionName: 'validateBlobs',
920
- args: [blobInput],
921
- }),
922
- },
923
- {},
924
- {
925
- blobs: encodedData.blobs.map(b => b.data),
926
- kzg,
927
- },
928
- )
929
- .catch(err => {
930
- const { message, metaMessages } = formatViemError(err);
931
- this.log.error(`Failed to validate blobs`, message, { metaMessages });
932
- throw new Error('Failed to validate blobs');
933
- });
934
-
935
- const attestations = encodedData.attestations ? encodedData.attestations.map(attest => attest.toViem()) : [];
936
- const txHashes = encodedData.txHashes ? encodedData.txHashes.map(txHash => txHash.toString()) : [];
937
956
 
938
- const signers = encodedData.attestations
939
- ?.filter(attest => !attest.signature.isEmpty())
940
- .map(attest => attest.address.toString());
957
+ // Get blob evaluation gas
958
+ let blobEvaluationGas: bigint;
959
+ if (this.config.fishermanMode) {
960
+ // In fisherman mode, we can't estimate blob gas because estimateGas doesn't support state overrides
961
+ // Use a fixed estimate.
962
+ blobEvaluationGas = BigInt(encodedData.blobs.length) * 21_000n;
963
+ this.log.debug(`Using fixed blob evaluation gas estimate in fisherman mode: ${blobEvaluationGas}`);
964
+ } else {
965
+ // Normal mode - use estimateGas with blob inputs
966
+ blobEvaluationGas = await this.l1TxUtils
967
+ .estimateGas(
968
+ this.getSenderAddress().toString(),
969
+ {
970
+ to: this.rollupContract.address,
971
+ data: encodeFunctionData({
972
+ abi: RollupAbi,
973
+ functionName: 'validateBlobs',
974
+ args: [blobInput],
975
+ }),
976
+ },
977
+ {},
978
+ {
979
+ blobs: encodedData.blobs.map(b => b.data),
980
+ kzg,
981
+ },
982
+ )
983
+ .catch(err => {
984
+ const { message, metaMessages } = formatViemError(err);
985
+ this.log.error(`Failed to validate blobs`, message, { metaMessages });
986
+ throw new Error('Failed to validate blobs');
987
+ });
988
+ }
989
+ const signers = encodedData.attestationsAndSigners.getSigners().map(signer => signer.toString());
941
990
 
942
991
  const args = [
943
992
  {
944
993
  header: encodedData.header.toViem(),
945
994
  archive: toHex(encodedData.archive),
946
- stateReference: encodedData.stateReference.toViem(),
947
995
  oracleInput: {
948
996
  // We are currently not modifying these. See #9963
949
997
  feeAssetPriceModifier: 0n,
950
998
  },
951
- txHashes,
952
999
  },
953
- RollupContract.packAttestations(attestations),
954
- signers ?? [],
1000
+ encodedData.attestationsAndSigners.getPackedAttestations(),
1001
+ signers,
1002
+ encodedData.attestationsAndSignersSignature.toViemSignature(),
955
1003
  blobInput,
956
1004
  ] as const;
957
1005
 
@@ -971,18 +1019,17 @@ export class SequencerPublisher {
971
1019
  {
972
1020
  readonly header: ViemHeader;
973
1021
  readonly archive: `0x${string}`;
974
- readonly stateReference: ViemStateReference;
975
- readonly txHashes: `0x${string}`[];
976
1022
  readonly oracleInput: {
977
1023
  readonly feeAssetPriceModifier: 0n;
978
1024
  };
979
1025
  },
980
1026
  ViemCommitteeAttestations,
981
- `0x${string}`[],
1027
+ `0x${string}`[], // Signers
1028
+ ViemSignature,
982
1029
  `0x${string}`,
983
1030
  ],
984
1031
  timestamp: bigint,
985
- options: { forcePendingBlockNumber?: number },
1032
+ options: { forcePendingBlockNumber?: BlockNumber },
986
1033
  ) {
987
1034
  const rollupData = encodeFunctionData({
988
1035
  abi: RollupAbi,
@@ -990,19 +1037,42 @@ export class SequencerPublisher {
990
1037
  args,
991
1038
  });
992
1039
 
993
- // override the pending block number if requested
994
- const forcePendingBlockNumberStateDiff = (
1040
+ // override the pending checkpoint number if requested
1041
+ const optsForcePendingCheckpointNumber =
995
1042
  options.forcePendingBlockNumber !== undefined
996
- ? await this.rollupContract.makePendingBlockNumberOverride(options.forcePendingBlockNumber)
1043
+ ? CheckpointNumber.fromBlockNumber(options.forcePendingBlockNumber)
1044
+ : undefined;
1045
+ const forcePendingCheckpointNumberStateDiff = (
1046
+ optsForcePendingCheckpointNumber !== undefined
1047
+ ? await this.rollupContract.makePendingCheckpointNumberOverride(optsForcePendingCheckpointNumber)
997
1048
  : []
998
1049
  ).flatMap(override => override.stateDiff ?? []);
999
1050
 
1051
+ const stateOverrides: StateOverride = [
1052
+ {
1053
+ address: this.rollupContract.address,
1054
+ // @note we override checkBlob to false since blobs are not part simulate()
1055
+ stateDiff: [
1056
+ { slot: toPaddedHex(RollupContract.checkBlobStorageSlot, true), value: toPaddedHex(0n, true) },
1057
+ ...forcePendingCheckpointNumberStateDiff,
1058
+ ],
1059
+ },
1060
+ ];
1061
+ // In fisherman mode, simulate as the proposer but with sufficient balance
1062
+ if (this.proposerAddressForSimulation) {
1063
+ stateOverrides.push({
1064
+ address: this.proposerAddressForSimulation.toString(),
1065
+ balance: 10n * WEI_CONST * WEI_CONST, // 10 ETH
1066
+ });
1067
+ }
1068
+
1000
1069
  const simulationResult = await this.l1TxUtils
1001
1070
  .simulate(
1002
1071
  {
1003
1072
  to: this.rollupContract.address,
1004
1073
  data: rollupData,
1005
1074
  gas: SequencerPublisher.PROPOSE_GAS_GUESS,
1075
+ ...(this.proposerAddressForSimulation && { from: this.proposerAddressForSimulation.toString() }),
1006
1076
  },
1007
1077
  {
1008
1078
  // @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 +1080,7 @@ export class SequencerPublisher {
1010
1080
  // @note reth should have a 30m gas limit per block but throws errors that this tx is beyond limit so we increase here
1011
1081
  gasLimit: SequencerPublisher.PROPOSE_GAS_GUESS * 2n,
1012
1082
  },
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
- ],
1083
+ stateOverrides,
1023
1084
  RollupAbi,
1024
1085
  {
1025
1086
  // @note fallback gas estimate to use if the node doesn't support simulation API
@@ -1027,7 +1088,17 @@ export class SequencerPublisher {
1027
1088
  },
1028
1089
  )
1029
1090
  .catch(err => {
1030
- this.log.error(`Failed to simulate propose tx`, err);
1091
+ // In fisherman mode, we expect ValidatorSelection__MissingProposerSignature since fisherman doesn't have proposer signature
1092
+ const viemError = formatViemError(err);
1093
+ if (this.config.fishermanMode && viemError.message?.includes('ValidatorSelection__MissingProposerSignature')) {
1094
+ this.log.debug(`Ignoring expected ValidatorSelection__MissingProposerSignature error in fisherman mode`);
1095
+ // Return a minimal simulation result with the fallback gas estimate
1096
+ return {
1097
+ gasUsed: SequencerPublisher.PROPOSE_GAS_GUESS,
1098
+ logs: [],
1099
+ };
1100
+ }
1101
+ this.log.error(`Failed to simulate propose tx`, viemError);
1031
1102
  throw err;
1032
1103
  });
1033
1104
 
@@ -1037,7 +1108,7 @@ export class SequencerPublisher {
1037
1108
  private async addProposeTx(
1038
1109
  block: L2Block,
1039
1110
  encodedData: L1ProcessArgs,
1040
- opts: { txTimeoutAt?: Date; forcePendingBlockNumber?: number } = {},
1111
+ opts: { txTimeoutAt?: Date; forcePendingBlockNumber?: BlockNumber } = {},
1041
1112
  timestamp: bigint,
1042
1113
  ): Promise<void> {
1043
1114
  const timer = new Timer();
@@ -1066,7 +1137,7 @@ export class SequencerPublisher {
1066
1137
  to: this.rollupContract.address,
1067
1138
  data: rollupData,
1068
1139
  },
1069
- lastValidL2Slot: block.header.globalVariables.slotNumber.toBigInt(),
1140
+ lastValidL2Slot: block.header.globalVariables.slotNumber,
1070
1141
  gasConfig: { ...opts, gasLimit },
1071
1142
  blobConfig: {
1072
1143
  blobs: encodedData.blobs.map(b => b.data),
@@ -1080,17 +1151,20 @@ export class SequencerPublisher {
1080
1151
  const success =
1081
1152
  receipt &&
1082
1153
  receipt.status === 'success' &&
1083
- tryExtractEvent(receipt.logs, this.rollupContract.address, RollupAbi, 'L2BlockProposed');
1154
+ tryExtractEvent(receipt.logs, this.rollupContract.address, RollupAbi, 'CheckpointProposed');
1084
1155
  if (success) {
1085
1156
  const endBlock = receipt.blockNumber;
1086
1157
  const inclusionBlocks = Number(endBlock - startBlock);
1158
+ const { calldataGas, calldataSize, sender } = stats!;
1087
1159
  const publishStats: L1PublishBlockStats = {
1088
1160
  gasPrice: receipt.effectiveGasPrice,
1089
1161
  gasUsed: receipt.gasUsed,
1090
1162
  blobGasUsed: receipt.blobGasUsed ?? 0n,
1091
1163
  blobDataGas: receipt.blobGasPrice ?? 0n,
1092
1164
  transactionHash: receipt.transactionHash,
1093
- ...pick(stats!, 'calldataGas', 'calldataSize', 'sender'),
1165
+ calldataGas,
1166
+ calldataSize,
1167
+ sender,
1094
1168
  ...block.getStats(),
1095
1169
  eventName: 'rollup-published-to-l1',
1096
1170
  blobCount: encodedData.blobs.length,
@@ -1106,7 +1180,7 @@ export class SequencerPublisher {
1106
1180
  ...block.getStats(),
1107
1181
  receipt,
1108
1182
  txHash: receipt.transactionHash,
1109
- slotNumber: block.header.globalVariables.slotNumber.toBigInt(),
1183
+ slotNumber: block.header.globalVariables.slotNumber,
1110
1184
  });
1111
1185
  return false;
1112
1186
  }