@aztec/sequencer-client 3.0.0-rc.5 → 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 (88) hide show
  1. package/dest/client/sequencer-client.d.ts +9 -8
  2. package/dest/client/sequencer-client.d.ts.map +1 -1
  3. package/dest/client/sequencer-client.js +28 -24
  4. package/dest/config.d.ts +7 -1
  5. package/dest/config.d.ts.map +1 -1
  6. package/dest/config.js +63 -26
  7. package/dest/global_variable_builder/global_builder.d.ts +16 -8
  8. package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
  9. package/dest/global_variable_builder/global_builder.js +37 -28
  10. package/dest/index.d.ts +2 -2
  11. package/dest/index.d.ts.map +1 -1
  12. package/dest/index.js +1 -1
  13. package/dest/publisher/config.d.ts +3 -3
  14. package/dest/publisher/config.d.ts.map +1 -1
  15. package/dest/publisher/config.js +2 -2
  16. package/dest/publisher/sequencer-publisher-factory.d.ts +3 -3
  17. package/dest/publisher/sequencer-publisher-factory.d.ts.map +1 -1
  18. package/dest/publisher/sequencer-publisher-factory.js +1 -1
  19. package/dest/publisher/sequencer-publisher-metrics.d.ts +3 -3
  20. package/dest/publisher/sequencer-publisher-metrics.d.ts.map +1 -1
  21. package/dest/publisher/sequencer-publisher.d.ts +27 -23
  22. package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
  23. package/dest/publisher/sequencer-publisher.js +119 -64
  24. package/dest/sequencer/block_builder.d.ts +1 -3
  25. package/dest/sequencer/block_builder.d.ts.map +1 -1
  26. package/dest/sequencer/block_builder.js +4 -2
  27. package/dest/sequencer/checkpoint_builder.d.ts +63 -0
  28. package/dest/sequencer/checkpoint_builder.d.ts.map +1 -0
  29. package/dest/sequencer/checkpoint_builder.js +131 -0
  30. package/dest/sequencer/checkpoint_proposal_job.d.ts +74 -0
  31. package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -0
  32. package/dest/sequencer/checkpoint_proposal_job.js +642 -0
  33. package/dest/sequencer/checkpoint_voter.d.ts +34 -0
  34. package/dest/sequencer/checkpoint_voter.d.ts.map +1 -0
  35. package/dest/sequencer/checkpoint_voter.js +85 -0
  36. package/dest/sequencer/events.d.ts +46 -0
  37. package/dest/sequencer/events.d.ts.map +1 -0
  38. package/dest/sequencer/events.js +1 -0
  39. package/dest/sequencer/index.d.ts +5 -1
  40. package/dest/sequencer/index.d.ts.map +1 -1
  41. package/dest/sequencer/index.js +4 -0
  42. package/dest/sequencer/metrics.d.ts +21 -1
  43. package/dest/sequencer/metrics.d.ts.map +1 -1
  44. package/dest/sequencer/metrics.js +154 -0
  45. package/dest/sequencer/sequencer.d.ts +91 -125
  46. package/dest/sequencer/sequencer.d.ts.map +1 -1
  47. package/dest/sequencer/sequencer.js +581 -582
  48. package/dest/sequencer/timetable.d.ts +54 -14
  49. package/dest/sequencer/timetable.d.ts.map +1 -1
  50. package/dest/sequencer/timetable.js +148 -59
  51. package/dest/sequencer/types.d.ts +3 -0
  52. package/dest/sequencer/types.d.ts.map +1 -0
  53. package/dest/sequencer/types.js +1 -0
  54. package/dest/sequencer/utils.d.ts +14 -8
  55. package/dest/sequencer/utils.d.ts.map +1 -1
  56. package/dest/sequencer/utils.js +7 -4
  57. package/dest/test/index.d.ts +3 -1
  58. package/dest/test/index.d.ts.map +1 -1
  59. package/dest/test/mock_checkpoint_builder.d.ts +83 -0
  60. package/dest/test/mock_checkpoint_builder.d.ts.map +1 -0
  61. package/dest/test/mock_checkpoint_builder.js +179 -0
  62. package/dest/test/utils.d.ts +49 -0
  63. package/dest/test/utils.d.ts.map +1 -0
  64. package/dest/test/utils.js +94 -0
  65. package/package.json +27 -27
  66. package/src/client/sequencer-client.ts +24 -31
  67. package/src/config.ts +68 -25
  68. package/src/global_variable_builder/global_builder.ts +47 -41
  69. package/src/index.ts +2 -0
  70. package/src/publisher/config.ts +3 -3
  71. package/src/publisher/sequencer-publisher-factory.ts +3 -3
  72. package/src/publisher/sequencer-publisher-metrics.ts +2 -2
  73. package/src/publisher/sequencer-publisher.ts +170 -74
  74. package/src/sequencer/README.md +531 -0
  75. package/src/sequencer/block_builder.ts +4 -1
  76. package/src/sequencer/checkpoint_builder.ts +217 -0
  77. package/src/sequencer/checkpoint_proposal_job.ts +706 -0
  78. package/src/sequencer/checkpoint_voter.ts +105 -0
  79. package/src/sequencer/events.ts +27 -0
  80. package/src/sequencer/index.ts +4 -0
  81. package/src/sequencer/metrics.ts +202 -0
  82. package/src/sequencer/sequencer.ts +300 -779
  83. package/src/sequencer/timetable.ts +173 -79
  84. package/src/sequencer/types.ts +6 -0
  85. package/src/sequencer/utils.ts +18 -9
  86. package/src/test/index.ts +2 -0
  87. package/src/test/mock_checkpoint_builder.ts +247 -0
  88. package/src/test/utils.ts +137 -0
@@ -1,6 +1,5 @@
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';
5
4
  import type { L1ContractsConfig } from '@aztec/ethereum/config';
6
5
  import {
@@ -14,6 +13,7 @@ import {
14
13
  type ViemCommitteeAttestations,
15
14
  type ViemHeader,
16
15
  } from '@aztec/ethereum/contracts';
16
+ import { type L1FeeAnalysisResult, L1FeeAnalyzer } from '@aztec/ethereum/l1-fee-analysis';
17
17
  import {
18
18
  type L1BlobInputs,
19
19
  type L1TxConfig,
@@ -26,6 +26,7 @@ import { FormattedViemError, formatViemError, tryExtractEvent } from '@aztec/eth
26
26
  import { sumBigint } from '@aztec/foundation/bigint';
27
27
  import { toHex as toPaddedHex } from '@aztec/foundation/bigint-buffer';
28
28
  import { BlockNumber, CheckpointNumber, SlotNumber } from '@aztec/foundation/branded-types';
29
+ import { pick } from '@aztec/foundation/collection';
29
30
  import type { Fr } from '@aztec/foundation/curves/bn254';
30
31
  import { EthAddress } from '@aztec/foundation/eth-address';
31
32
  import { Signature, type ViemSignature } from '@aztec/foundation/eth-signature';
@@ -34,10 +35,11 @@ import { bufferToHex } from '@aztec/foundation/string';
34
35
  import { DateProvider, Timer } from '@aztec/foundation/timer';
35
36
  import { EmpireBaseAbi, ErrorsAbi, RollupAbi } from '@aztec/l1-artifacts';
36
37
  import { type ProposerSlashAction, encodeSlashConsensusVotes } from '@aztec/slasher';
37
- 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';
38
40
  import { SlashFactoryContract } from '@aztec/stdlib/l1-contracts';
39
41
  import type { CheckpointHeader } from '@aztec/stdlib/rollup';
40
- import type { L1PublishBlockStats } from '@aztec/stdlib/stats';
42
+ import type { L1PublishCheckpointStats } from '@aztec/stdlib/stats';
41
43
  import { type TelemetryClient, getTelemetryClient } from '@aztec/telemetry-client';
42
44
 
43
45
  import { type StateOverride, type TransactionReceipt, type TypedDataDefinition, encodeFunctionData, toHex } from 'viem';
@@ -108,13 +110,18 @@ export class SequencerPublisher {
108
110
 
109
111
  protected lastActions: Partial<Record<Action, SlotNumber>> = {};
110
112
 
113
+ private isPayloadEmptyCache: Map<string, boolean> = new Map<string, boolean>();
114
+
111
115
  protected log: Logger;
112
116
  protected ethereumSlotDuration: bigint;
113
117
 
114
- private blobSinkClient: BlobSinkClientInterface;
118
+ private blobClient: BlobClientInterface;
115
119
 
116
120
  /** Address to use for simulations in fisherman mode (actual proposer's address) */
117
121
  private proposerAddressForSimulation?: EthAddress;
122
+
123
+ /** L1 fee analyzer for fisherman mode */
124
+ private l1FeeAnalyzer?: L1FeeAnalyzer;
118
125
  // @note - with blobs, the below estimate seems too large.
119
126
  // Total used for full block from int_l1_pub e2e test: 1m (of which 86k is 1x blob)
120
127
  // Total used for emptier block from above test: 429k (of which 84k is 1x blob)
@@ -138,7 +145,7 @@ export class SequencerPublisher {
138
145
  private config: TxSenderConfig & PublisherConfig & Pick<L1ContractsConfig, 'ethereumSlotDuration'>,
139
146
  deps: {
140
147
  telemetry?: TelemetryClient;
141
- blobSinkClient?: BlobSinkClientInterface;
148
+ blobClient: BlobClientInterface;
142
149
  l1TxUtils: L1TxUtilsWithBlobs;
143
150
  rollupContract: RollupContract;
144
151
  slashingProposerContract: EmpireSlashingProposerContract | TallySlashingProposerContract | undefined;
@@ -156,8 +163,7 @@ export class SequencerPublisher {
156
163
  this.epochCache = deps.epochCache;
157
164
  this.lastActions = deps.lastActions;
158
165
 
159
- this.blobSinkClient =
160
- deps.blobSinkClient ?? createBlobSinkClient(config, { logger: createLogger('sequencer:blob-sink:client') });
166
+ this.blobClient = deps.blobClient;
161
167
 
162
168
  const telemetry = deps.telemetry ?? getTelemetryClient();
163
169
  this.metrics = deps.metrics ?? new SequencerPublisherMetrics(telemetry, 'SequencerPublisher');
@@ -174,6 +180,15 @@ export class SequencerPublisher {
174
180
  this.slashingProposerContract = newSlashingProposer;
175
181
  });
176
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
+ }
177
192
  }
178
193
 
179
194
  public getRollupContract(): RollupContract {
@@ -184,6 +199,13 @@ export class SequencerPublisher {
184
199
  return this.l1TxUtils.getSenderAddress();
185
200
  }
186
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
+
187
209
  /**
188
210
  * Sets the proposer address to use for simulations in fisherman mode.
189
211
  * @param proposerAddress - The actual proposer's address to use for balance lookups in simulations
@@ -211,6 +233,62 @@ export class SequencerPublisher {
211
233
  }
212
234
  }
213
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
+
214
292
  /**
215
293
  * Sends all requests that are still valid.
216
294
  * @returns one of:
@@ -221,7 +299,7 @@ export class SequencerPublisher {
221
299
  public async sendRequests() {
222
300
  const requestsToProcess = [...this.requests];
223
301
  this.requests = [];
224
- if (this.interrupted) {
302
+ if (this.interrupted || requestsToProcess.length === 0) {
225
303
  return undefined;
226
304
  }
227
305
  const currentL2Slot = this.getCurrentL2Slot();
@@ -509,45 +587,38 @@ export class SequencerPublisher {
509
587
  }
510
588
  }
511
589
 
512
- /**
513
- * @notice Will simulate `propose` to make sure that the block is valid for submission
514
- *
515
- * @dev Throws if unable to propose
516
- *
517
- * @param block - The block to propose
518
- * @param attestationData - The block's attestation data
519
- *
520
- */
521
- public async validateBlockForSubmission(
522
- block: L2Block,
590
+ /** Simulates `propose` to make sure that the checkpoint is valid for submission */
591
+ public async validateCheckpointForSubmission(
592
+ checkpoint: Checkpoint,
523
593
  attestationsAndSigners: CommitteeAttestationsAndSigners,
524
594
  attestationsAndSignersSignature: Signature,
525
- options: { forcePendingBlockNumber?: BlockNumber },
595
+ options: { forcePendingBlockNumber?: BlockNumber }, // TODO(palla/mbps): Should this be forcePendingCheckpointNumber?
526
596
  ): Promise<bigint> {
527
597
  const ts = BigInt((await this.l1TxUtils.getBlock()).timestamp + this.ethereumSlotDuration);
528
598
 
599
+ // TODO(palla/mbps): This should not be needed, there's no flow where we propose with zero attestations. Or is there?
529
600
  // If we have no attestations, we still need to provide the empty attestations
530
601
  // so that the committee is recalculated correctly
531
- const ignoreSignatures = attestationsAndSigners.attestations.length === 0;
532
- if (ignoreSignatures) {
533
- const { committee } = await this.epochCache.getCommittee(block.header.globalVariables.slotNumber);
534
- if (!committee) {
535
- this.log.warn(`No committee found for slot ${block.header.globalVariables.slotNumber}`);
536
- throw new Error(`No committee found for slot ${block.header.globalVariables.slotNumber}`);
537
- }
538
- attestationsAndSigners.attestations = committee.map(committeeMember =>
539
- CommitteeAttestation.fromAddress(committeeMember),
540
- );
541
- }
542
-
543
- const blobFields = block.getCheckpointBlobFields();
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();
544
615
  const blobs = getBlobsPerL1Block(blobFields);
545
616
  const blobInput = getPrefixedEthBlobCommitments(blobs);
546
617
 
547
618
  const args = [
548
619
  {
549
- header: block.getCheckpointHeader().toViem(),
550
- archive: toHex(block.archive.root.toBuffer()),
620
+ header: checkpoint.header.toViem(),
621
+ archive: toHex(checkpoint.archive.root.toBuffer()),
551
622
  oracleInput: {
552
623
  feeAssetPriceModifier: 0n,
553
624
  },
@@ -585,10 +656,19 @@ export class SequencerPublisher {
585
656
  const round = await base.computeRound(slotNumber);
586
657
  const roundInfo = await base.getRoundInfo(this.rollupContract.address, round);
587
658
 
659
+ if (roundInfo.quorumReached) {
660
+ return false;
661
+ }
662
+
588
663
  if (roundInfo.lastSignalSlot >= slotNumber) {
589
664
  return false;
590
665
  }
591
666
 
667
+ if (await this.isPayloadEmpty(payload)) {
668
+ this.log.warn(`Skipping vote cast for payload with empty code`);
669
+ return false;
670
+ }
671
+
592
672
  const cachedLastVote = this.lastActions[signalType];
593
673
  this.lastActions[signalType] = slotNumber;
594
674
  const action = signalType;
@@ -631,14 +711,14 @@ export class SequencerPublisher {
631
711
  const logData = { ...result, slotNumber, round, payload: payload.toString() };
632
712
  if (!success) {
633
713
  this.log.error(
634
- `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`,
635
715
  logData,
636
716
  );
637
717
  this.lastActions[signalType] = cachedLastVote;
638
718
  return false;
639
719
  } else {
640
720
  this.log.info(
641
- `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`,
642
722
  logData,
643
723
  );
644
724
  return true;
@@ -648,6 +728,17 @@ export class SequencerPublisher {
648
728
  return true;
649
729
  }
650
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
+
651
742
  /**
652
743
  * Enqueues a governance castSignal transaction to cast a signal for a given slot number.
653
744
  * @param slotNumber - The slot number to cast a signal for.
@@ -795,27 +886,21 @@ export class SequencerPublisher {
795
886
  return true;
796
887
  }
797
888
 
798
- /**
799
- * Proposes a L2 block on L1.
800
- *
801
- * @param block - L2 block to propose.
802
- * @returns True if the tx has been enqueued, throws otherwise. See #9315
803
- */
804
- public async enqueueProposeL2Block(
805
- block: L2Block,
889
+ /** Simulates and enqueues a proposal for a checkpoint on L1 */
890
+ public async enqueueProposeCheckpoint(
891
+ checkpoint: Checkpoint,
806
892
  attestationsAndSigners: CommitteeAttestationsAndSigners,
807
893
  attestationsAndSignersSignature: Signature,
808
894
  opts: { txTimeoutAt?: Date; forcePendingBlockNumber?: BlockNumber } = {},
809
- ): Promise<boolean> {
810
- const checkpointHeader = block.getCheckpointHeader();
895
+ ): Promise<void> {
896
+ const checkpointHeader = checkpoint.header;
811
897
 
812
- const blobFields = block.getCheckpointBlobFields();
898
+ const blobFields = checkpoint.toBlobFields();
813
899
  const blobs = getBlobsPerL1Block(blobFields);
814
900
 
815
901
  const proposeTxArgs = {
816
902
  header: checkpointHeader,
817
- archive: block.archive.root.toBuffer(),
818
- body: block.body.toBuffer(),
903
+ archive: checkpoint.archive.root.toBuffer(),
819
904
  blobs,
820
905
  attestationsAndSigners,
821
906
  attestationsAndSignersSignature,
@@ -829,19 +914,23 @@ export class SequencerPublisher {
829
914
  // By simulation issue, I mean the fact that the block.timestamp is equal to the last block, not the next, which
830
915
  // make time consistency checks break.
831
916
  // TODO(palla): Check whether we're validating twice, once here and once within addProposeTx, since we call simulateProposeTx in both places.
832
- ts = await this.validateBlockForSubmission(block, attestationsAndSigners, attestationsAndSignersSignature, opts);
917
+ ts = await this.validateCheckpointForSubmission(
918
+ checkpoint,
919
+ attestationsAndSigners,
920
+ attestationsAndSignersSignature,
921
+ opts,
922
+ );
833
923
  } catch (err: any) {
834
- this.log.error(`Block validation failed. ${err instanceof Error ? err.message : 'No error message'}`, err, {
835
- ...block.getStats(),
836
- slotNumber: block.header.globalVariables.slotNumber,
924
+ this.log.error(`Checkpoint validation failed. ${err instanceof Error ? err.message : 'No error message'}`, err, {
925
+ ...checkpoint.getStats(),
926
+ slotNumber: checkpoint.header.slotNumber,
837
927
  forcePendingBlockNumber: opts.forcePendingBlockNumber,
838
928
  });
839
929
  throw err;
840
930
  }
841
931
 
842
- this.log.verbose(`Enqueuing block propose transaction`, { ...block.toBlockInfo(), ...opts });
843
- await this.addProposeTx(block, proposeTxArgs, opts, ts);
844
- return true;
932
+ this.log.verbose(`Enqueuing checkpoint propose transaction`, { ...checkpoint.toCheckpointInfo(), ...opts });
933
+ await this.addProposeTx(checkpoint, proposeTxArgs, opts, ts);
845
934
  }
846
935
 
847
936
  public enqueueInvalidateBlock(request: InvalidateBlockRequest | undefined, opts: { txTimeoutAt?: Date } = {}) {
@@ -1106,11 +1195,12 @@ export class SequencerPublisher {
1106
1195
  }
1107
1196
 
1108
1197
  private async addProposeTx(
1109
- block: L2Block,
1198
+ checkpoint: Checkpoint,
1110
1199
  encodedData: L1ProcessArgs,
1111
1200
  opts: { txTimeoutAt?: Date; forcePendingBlockNumber?: BlockNumber } = {},
1112
1201
  timestamp: bigint,
1113
1202
  ): Promise<void> {
1203
+ const slot = checkpoint.header.slotNumber;
1114
1204
  const timer = new Timer();
1115
1205
  const kzg = Blob.getViemKzgInstance();
1116
1206
  const { rollupData, simulationResult, blobEvaluationGas } = await this.prepareProposeTx(
@@ -1125,11 +1215,13 @@ export class SequencerPublisher {
1125
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
1126
1216
  );
1127
1217
 
1128
- // Send the blobs to the blob sink preemptively. This helps in tests where the sequencer mistakingly thinks that the propose
1129
- // tx fails but it does get mined. We make sure that the blobs are sent to the blob sink regardless of the tx outcome.
1130
- void this.blobSinkClient.sendBlobsToBlobSink(encodedData.blobs).catch(_err => {
1131
- this.log.error('Failed to send blobs to blob sink');
1132
- });
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
+ );
1133
1225
 
1134
1226
  return this.addRequest({
1135
1227
  action: 'propose',
@@ -1137,7 +1229,7 @@ export class SequencerPublisher {
1137
1229
  to: this.rollupContract.address,
1138
1230
  data: rollupData,
1139
1231
  },
1140
- lastValidL2Slot: block.header.globalVariables.slotNumber,
1232
+ lastValidL2Slot: checkpoint.header.slotNumber,
1141
1233
  gasConfig: { ...opts, gasLimit },
1142
1234
  blobConfig: {
1143
1235
  blobs: encodedData.blobs.map(b => b.data),
@@ -1152,11 +1244,12 @@ export class SequencerPublisher {
1152
1244
  receipt &&
1153
1245
  receipt.status === 'success' &&
1154
1246
  tryExtractEvent(receipt.logs, this.rollupContract.address, RollupAbi, 'CheckpointProposed');
1247
+
1155
1248
  if (success) {
1156
1249
  const endBlock = receipt.blockNumber;
1157
1250
  const inclusionBlocks = Number(endBlock - startBlock);
1158
1251
  const { calldataGas, calldataSize, sender } = stats!;
1159
- const publishStats: L1PublishBlockStats = {
1252
+ const publishStats: L1PublishCheckpointStats = {
1160
1253
  gasPrice: receipt.effectiveGasPrice,
1161
1254
  gasUsed: receipt.gasUsed,
1162
1255
  blobGasUsed: receipt.blobGasUsed ?? 0n,
@@ -1165,23 +1258,26 @@ export class SequencerPublisher {
1165
1258
  calldataGas,
1166
1259
  calldataSize,
1167
1260
  sender,
1168
- ...block.getStats(),
1261
+ ...checkpoint.getStats(),
1169
1262
  eventName: 'rollup-published-to-l1',
1170
1263
  blobCount: encodedData.blobs.length,
1171
1264
  inclusionBlocks,
1172
1265
  };
1173
- 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
+ });
1174
1271
  this.metrics.recordProcessBlockTx(timer.ms(), publishStats);
1175
1272
 
1176
1273
  return true;
1177
1274
  } else {
1178
1275
  this.metrics.recordFailedTx('process');
1179
- this.log.error(`Rollup process tx failed: ${errorMsg ?? 'no error message'}`, undefined, {
1180
- ...block.getStats(),
1181
- receipt,
1182
- txHash: receipt.transactionHash,
1183
- slotNumber: block.header.globalVariables.slotNumber,
1184
- });
1276
+ this.log.error(
1277
+ `Publishing checkpoint at slot ${slot} failed with ${errorMsg ?? 'no error message'}`,
1278
+ undefined,
1279
+ { ...checkpoint.getStats(), ...receipt },
1280
+ );
1185
1281
  return false;
1186
1282
  }
1187
1283
  },