@aztec/sequencer-client 0.69.0 → 0.69.1-devnet

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 (82) hide show
  1. package/dest/client/sequencer-client.d.ts +4 -0
  2. package/dest/client/sequencer-client.d.ts.map +1 -1
  3. package/dest/client/sequencer-client.js +4 -5
  4. package/dest/config.d.ts.map +1 -1
  5. package/dest/config.js +11 -1
  6. package/dest/index.d.ts +3 -1
  7. package/dest/index.d.ts.map +1 -1
  8. package/dest/index.js +4 -2
  9. package/dest/publisher/config.d.ts +4 -0
  10. package/dest/publisher/config.d.ts.map +1 -1
  11. package/dest/publisher/config.js +6 -1
  12. package/dest/publisher/index.d.ts +0 -1
  13. package/dest/publisher/index.d.ts.map +1 -1
  14. package/dest/publisher/index.js +1 -2
  15. package/dest/publisher/l1-publisher.d.ts +43 -12
  16. package/dest/publisher/l1-publisher.d.ts.map +1 -1
  17. package/dest/publisher/l1-publisher.js +184 -132
  18. package/dest/sequencer/index.d.ts +1 -0
  19. package/dest/sequencer/index.d.ts.map +1 -1
  20. package/dest/sequencer/index.js +2 -1
  21. package/dest/sequencer/sequencer.d.ts +22 -29
  22. package/dest/sequencer/sequencer.d.ts.map +1 -1
  23. package/dest/sequencer/sequencer.js +68 -132
  24. package/dest/sequencer/utils.d.ts +2 -2
  25. package/dest/sequencer/utils.d.ts.map +1 -1
  26. package/dest/sequencer/utils.js +3 -3
  27. package/dest/slasher/factory.d.ts +11 -0
  28. package/dest/slasher/factory.d.ts.map +1 -0
  29. package/dest/slasher/factory.js +10 -0
  30. package/dest/slasher/index.d.ts +3 -0
  31. package/dest/slasher/index.d.ts.map +1 -0
  32. package/dest/slasher/index.js +3 -0
  33. package/dest/slasher/slasher_client.d.ts +127 -0
  34. package/dest/slasher/slasher_client.d.ts.map +1 -0
  35. package/dest/slasher/slasher_client.js +305 -0
  36. package/dest/test/index.d.ts +18 -0
  37. package/dest/test/index.d.ts.map +1 -0
  38. package/dest/test/index.js +8 -0
  39. package/dest/{publisher → test}/test-l1-publisher.d.ts +1 -1
  40. package/dest/test/test-l1-publisher.d.ts.map +1 -0
  41. package/dest/test/test-l1-publisher.js +11 -0
  42. package/dest/tx_validator/archive_cache.d.ts +14 -0
  43. package/dest/tx_validator/archive_cache.d.ts.map +1 -0
  44. package/dest/tx_validator/archive_cache.js +22 -0
  45. package/dest/tx_validator/gas_validator.d.ts +2 -3
  46. package/dest/tx_validator/gas_validator.d.ts.map +1 -1
  47. package/dest/tx_validator/gas_validator.js +9 -22
  48. package/dest/tx_validator/nullifier_cache.d.ts +16 -0
  49. package/dest/tx_validator/nullifier_cache.d.ts.map +1 -0
  50. package/dest/tx_validator/nullifier_cache.js +24 -0
  51. package/dest/tx_validator/phases_validator.d.ts +2 -3
  52. package/dest/tx_validator/phases_validator.d.ts.map +1 -1
  53. package/dest/tx_validator/phases_validator.js +15 -24
  54. package/dest/tx_validator/tx_validator_factory.d.ts +15 -14
  55. package/dest/tx_validator/tx_validator_factory.d.ts.map +1 -1
  56. package/dest/tx_validator/tx_validator_factory.js +41 -24
  57. package/package.json +24 -20
  58. package/src/client/sequencer-client.ts +9 -3
  59. package/src/config.ts +10 -0
  60. package/src/index.ts +3 -1
  61. package/src/publisher/config.ts +10 -0
  62. package/src/publisher/index.ts +0 -1
  63. package/src/publisher/l1-publisher.ts +222 -137
  64. package/src/sequencer/index.ts +1 -0
  65. package/src/sequencer/sequencer.ts +91 -195
  66. package/src/sequencer/utils.ts +2 -2
  67. package/src/slasher/factory.ts +22 -0
  68. package/src/slasher/index.ts +2 -0
  69. package/src/slasher/slasher_client.ts +402 -0
  70. package/src/test/index.ts +23 -0
  71. package/src/{publisher → test}/test-l1-publisher.ts +1 -1
  72. package/src/tx_validator/archive_cache.ts +27 -0
  73. package/src/tx_validator/gas_validator.ts +11 -24
  74. package/src/tx_validator/nullifier_cache.ts +29 -0
  75. package/src/tx_validator/phases_validator.ts +22 -33
  76. package/src/tx_validator/tx_validator_factory.ts +89 -40
  77. package/dest/publisher/test-l1-publisher.d.ts.map +0 -1
  78. package/dest/publisher/test-l1-publisher.js +0 -11
  79. package/dest/publisher/utils.d.ts +0 -2
  80. package/dest/publisher/utils.d.ts.map +0 -1
  81. package/dest/publisher/utils.js +0 -13
  82. package/src/publisher/utils.ts +0 -14
@@ -1,9 +1,10 @@
1
+ import { type BlobSinkClientInterface, createBlobSinkClient } from '@aztec/blob-sink/client';
1
2
  import {
2
3
  ConsensusPayload,
3
4
  type EpochProofClaim,
4
5
  type EpochProofQuote,
5
6
  type L2Block,
6
- SignatureDomainSeperator,
7
+ SignatureDomainSeparator,
7
8
  type TxHash,
8
9
  getHashedSignaturePayload,
9
10
  } from '@aztec/circuit-types';
@@ -18,10 +19,11 @@ import {
18
19
  import { type FeeRecipient, type RootRollupPublicInputs } from '@aztec/circuits.js/rollup';
19
20
  import {
20
21
  type EthereumChain,
22
+ type GasPrice,
21
23
  type L1ContractsConfig,
22
24
  L1TxUtils,
23
- type L1TxUtilsConfig,
24
25
  createEthereumChain,
26
+ formatViemError,
25
27
  } from '@aztec/ethereum';
26
28
  import { makeTuple } from '@aztec/foundation/array';
27
29
  import { toHex } from '@aztec/foundation/bigint-buffer';
@@ -29,19 +31,20 @@ import { Blob } from '@aztec/foundation/blob';
29
31
  import { areArraysEqual, compactArray, times } from '@aztec/foundation/collection';
30
32
  import { type Signature } from '@aztec/foundation/eth-signature';
31
33
  import { Fr } from '@aztec/foundation/fields';
32
- import { createLogger } from '@aztec/foundation/log';
34
+ import { type Logger, createLogger } from '@aztec/foundation/log';
33
35
  import { type Tuple, serializeToBuffer } from '@aztec/foundation/serialize';
34
36
  import { InterruptibleSleep } from '@aztec/foundation/sleep';
35
37
  import { Timer } from '@aztec/foundation/timer';
36
- import { ExtRollupLibAbi, GovernanceProposerAbi, LeonidasLibAbi, RollupAbi } from '@aztec/l1-artifacts';
38
+ import { EmpireBaseAbi, RollupAbi, SlasherAbi } from '@aztec/l1-artifacts';
37
39
  import { type TelemetryClient } from '@aztec/telemetry-client';
40
+ import { NoopTelemetryClient } from '@aztec/telemetry-client/noop';
38
41
 
39
42
  import pick from 'lodash.pick';
40
43
  import {
41
44
  type BaseError,
42
45
  type Chain,
43
46
  type Client,
44
- ContractFunctionExecutionError,
47
+ type ContractFunctionExecutionError,
45
48
  ContractFunctionRevertedError,
46
49
  type GetContractReturnType,
47
50
  type Hex,
@@ -69,7 +72,6 @@ import { privateKeyToAccount } from 'viem/accounts';
69
72
 
70
73
  import { type PublisherConfig, type TxSenderConfig } from './config.js';
71
74
  import { L1PublisherMetrics } from './l1-publisher-metrics.js';
72
- import { prettyLogViemErrorMsg } from './utils.js';
73
75
 
74
76
  /**
75
77
  * Stats for a sent transaction.
@@ -101,6 +103,8 @@ export type MinimalTransactionReceipt = {
101
103
  logs: any[];
102
104
  /** Block number in which this tx was mined. */
103
105
  blockNumber: bigint;
106
+ /** The block hash in which this tx was mined */
107
+ blockHash: `0x${string}`;
104
108
  };
105
109
 
106
110
  /** Arguments to the process method of the rollup contract */
@@ -121,6 +125,14 @@ type L1ProcessArgs = {
121
125
  attestations?: Signature[];
122
126
  };
123
127
 
128
+ type L1ProcessReturnType = {
129
+ receipt: TransactionReceipt | undefined;
130
+ args: any;
131
+ functionName: string;
132
+ data: Hex;
133
+ gasPrice: GasPrice;
134
+ };
135
+
124
136
  /** Arguments to the submitEpochProof method of the rollup contract */
125
137
  export type L1SubmitEpochProofArgs = {
126
138
  epochSize: number;
@@ -135,6 +147,13 @@ export type L1SubmitEpochProofArgs = {
135
147
  proof: Proof;
136
148
  };
137
149
 
150
+ export enum VoteType {
151
+ GOVERNANCE,
152
+ SLASHING,
153
+ }
154
+
155
+ type GetSlashPayloadCallBack = (slotNumber: bigint) => Promise<EthAddress | undefined>;
156
+
138
157
  /**
139
158
  * Publishes L2 blocks to L1. This implementation does *not* retry a transaction in
140
159
  * the event of network congestion, but should work for local development.
@@ -149,26 +168,32 @@ export class L1Publisher {
149
168
  private interrupted = false;
150
169
  private metrics: L1PublisherMetrics;
151
170
 
152
- private payload: EthAddress = EthAddress.ZERO;
153
- private myLastVote: bigint = 0n;
171
+ protected governanceLog = createLogger('sequencer:publisher:governance');
172
+ protected governanceProposerAddress?: EthAddress;
173
+ private governancePayload: EthAddress = EthAddress.ZERO;
174
+
175
+ protected slashingLog = createLogger('sequencer:publisher:slashing');
176
+ protected slashingProposerAddress?: EthAddress;
177
+ private getSlashPayload?: GetSlashPayloadCallBack = undefined;
178
+
179
+ private myLastVotes: Record<VoteType, bigint> = {
180
+ [VoteType.GOVERNANCE]: 0n,
181
+ [VoteType.SLASHING]: 0n,
182
+ };
154
183
 
155
184
  protected log = createLogger('sequencer:publisher');
156
- protected governanceLog = createLogger('sequencer:publisher:governance');
157
185
 
158
186
  protected rollupContract: GetContractReturnType<
159
187
  typeof RollupAbi,
160
188
  WalletClient<HttpTransport, Chain, PrivateKeyAccount>
161
189
  >;
162
- protected governanceProposerContract?: GetContractReturnType<
163
- typeof GovernanceProposerAbi,
164
- WalletClient<HttpTransport, Chain, PrivateKeyAccount>
165
- > = undefined;
166
190
 
167
191
  protected publicClient: PublicClient<HttpTransport, Chain>;
168
192
  protected walletClient: WalletClient<HttpTransport, Chain, PrivateKeyAccount>;
169
193
  protected account: PrivateKeyAccount;
170
194
  protected ethereumSlotDuration: bigint;
171
195
 
196
+ private blobSinkClient: BlobSinkClientInterface;
172
197
  // @note - with blobs, the below estimate seems too large.
173
198
  // Total used for full block from int_l1_pub e2e test: 1m (of which 86k is 1x blob)
174
199
  // Total used for emptier block from above test: 429k (of which 84k is 1x blob)
@@ -178,12 +203,16 @@ export class L1Publisher {
178
203
  private readonly l1TxUtils: L1TxUtils;
179
204
 
180
205
  constructor(
181
- config: TxSenderConfig & PublisherConfig & Pick<L1ContractsConfig, 'ethereumSlotDuration'> & L1TxUtilsConfig,
182
- client: TelemetryClient,
206
+ config: TxSenderConfig & PublisherConfig & Pick<L1ContractsConfig, 'ethereumSlotDuration'>,
207
+ deps: { telemetry?: TelemetryClient; blobSinkClient?: BlobSinkClientInterface } = {},
183
208
  ) {
184
209
  this.sleepTimeMs = config?.l1PublishRetryIntervalMS ?? 60_000;
185
210
  this.ethereumSlotDuration = BigInt(config.ethereumSlotDuration);
186
- this.metrics = new L1PublisherMetrics(client, 'L1Publisher');
211
+
212
+ const telemetry = deps.telemetry ?? new NoopTelemetryClient();
213
+ this.blobSinkClient = deps.blobSinkClient ?? createBlobSinkClient(config.blobSinkUrl);
214
+
215
+ this.metrics = new L1PublisherMetrics(telemetry, 'L1Publisher');
187
216
 
188
217
  const { l1RpcUrl: rpcUrl, l1ChainId: chainId, publisherPrivateKey, l1Contracts } = config;
189
218
  const chain = createEthereumChain(rpcUrl, chainId);
@@ -205,16 +234,31 @@ export class L1Publisher {
205
234
  });
206
235
 
207
236
  if (l1Contracts.governanceProposerAddress) {
208
- this.governanceProposerContract = getContract({
209
- address: getAddress(l1Contracts.governanceProposerAddress.toString()),
210
- abi: GovernanceProposerAbi,
211
- client: this.walletClient,
212
- });
237
+ this.governanceProposerAddress = EthAddress.fromString(l1Contracts.governanceProposerAddress.toString());
213
238
  }
214
239
 
215
240
  this.l1TxUtils = new L1TxUtils(this.publicClient, this.walletClient, this.log, config);
216
241
  }
217
242
 
243
+ public registerSlashPayloadGetter(callback: GetSlashPayloadCallBack) {
244
+ this.getSlashPayload = callback;
245
+ }
246
+
247
+ private async getSlashingProposerAddress() {
248
+ if (this.slashingProposerAddress) {
249
+ return this.slashingProposerAddress;
250
+ }
251
+
252
+ const slasherAddress = await this.rollupContract.read.SLASHER();
253
+ const slasher = getContract({
254
+ address: getAddress(slasherAddress.toString()),
255
+ abi: SlasherAbi,
256
+ client: this.walletClient,
257
+ });
258
+ this.slashingProposerAddress = EthAddress.fromString(await slasher.read.PROPOSER());
259
+ return this.slashingProposerAddress;
260
+ }
261
+
218
262
  get publisherAddress() {
219
263
  return this.account.address;
220
264
  }
@@ -230,12 +274,12 @@ export class L1Publisher {
230
274
  });
231
275
  }
232
276
 
233
- public getPayLoad() {
234
- return this.payload;
277
+ public getGovernancePayload() {
278
+ return this.governancePayload;
235
279
  }
236
280
 
237
- public setPayload(payload: EthAddress) {
238
- this.payload = payload;
281
+ public setGovernancePayload(payload: EthAddress) {
282
+ this.governancePayload = payload;
239
283
  }
240
284
 
241
285
  public getSenderAddress(): EthAddress {
@@ -395,38 +439,6 @@ export class L1Publisher {
395
439
  if (error instanceof ContractFunctionRevertedError) {
396
440
  const err = error as ContractFunctionRevertedError;
397
441
  this.log.debug(`Validation failed: ${err.message}`, err.data);
398
- } else if (error instanceof ContractFunctionExecutionError) {
399
- let err = error as ContractFunctionRevertedError;
400
- if (!tryGetCustomErrorName(err)) {
401
- // If we get here, it's because the custom error no longer exists in Rollup.sol,
402
- // but in another lib. The below reconstructs the error message.
403
- try {
404
- await this.publicClient.estimateGas({
405
- data: encodeFunctionData({
406
- abi: this.rollupContract.abi,
407
- functionName: 'validateHeader',
408
- args,
409
- }),
410
- account: this.account,
411
- to: this.rollupContract.address,
412
- });
413
- } catch (estGasErr: unknown) {
414
- const possibleAbis = [ExtRollupLibAbi, LeonidasLibAbi];
415
- possibleAbis.forEach(abi => {
416
- const possibleErr = getContractError(estGasErr as BaseError, {
417
- args: [],
418
- abi: abi,
419
- functionName: 'validateHeader',
420
- address: this.rollupContract.address,
421
- sender: this.account.address,
422
- });
423
- err = tryGetCustomErrorName(possibleErr) ? possibleErr : err;
424
- });
425
- }
426
- throw err;
427
- }
428
- } else {
429
- this.log.debug(`Unexpected error during validation: ${error}`);
430
442
  }
431
443
  throw error;
432
444
  }
@@ -450,68 +462,106 @@ export class L1Publisher {
450
462
  calldataGas: getCalldataGasUsage(calldata),
451
463
  };
452
464
  }
453
-
454
- public async castVote(slotNumber: bigint, timestamp: bigint): Promise<boolean> {
455
- if (this.payload.equals(EthAddress.ZERO)) {
465
+ public async castVote(slotNumber: bigint, timestamp: bigint, voteType: VoteType) {
466
+ // @todo This function can be optimized by doing some of the computations locally instead of calling the L1 contracts
467
+ if (this.myLastVotes[voteType] >= slotNumber) {
456
468
  return false;
457
469
  }
458
470
 
459
- if (!this.governanceProposerContract) {
460
- return false;
461
- }
471
+ const voteConfig = async (): Promise<
472
+ { payload: EthAddress; voteContractAddress: EthAddress; logger: Logger } | undefined
473
+ > => {
474
+ if (voteType === VoteType.GOVERNANCE) {
475
+ if (this.governancePayload.equals(EthAddress.ZERO)) {
476
+ return undefined;
477
+ }
478
+ if (!this.governanceProposerAddress) {
479
+ return undefined;
480
+ }
481
+ return {
482
+ payload: this.governancePayload,
483
+ voteContractAddress: this.governanceProposerAddress,
484
+ logger: this.governanceLog,
485
+ };
486
+ } else if (voteType === VoteType.SLASHING) {
487
+ if (!this.getSlashPayload) {
488
+ return undefined;
489
+ }
490
+ const slashingProposerAddress = await this.getSlashingProposerAddress();
491
+ if (!slashingProposerAddress) {
492
+ return undefined;
493
+ }
494
+
495
+ const slashPayload = await this.getSlashPayload(slotNumber);
496
+
497
+ if (!slashPayload) {
498
+ return undefined;
499
+ }
462
500
 
463
- if (this.myLastVote >= slotNumber) {
501
+ return {
502
+ payload: slashPayload,
503
+ voteContractAddress: slashingProposerAddress,
504
+ logger: this.slashingLog,
505
+ };
506
+ } else {
507
+ throw new Error('Invalid vote type');
508
+ }
509
+ };
510
+
511
+ const vConfig = await voteConfig();
512
+
513
+ if (!vConfig) {
464
514
  return false;
465
515
  }
466
516
 
467
- // @todo This can be optimized A LOT by doing the computation instead of making calls to L1, but it is very convenient
468
- // for when we keep changing the values and don't want to have multiple versions of the same logic implemented.
517
+ const { payload, voteContractAddress, logger } = vConfig;
518
+
519
+ const voteContract = getContract({
520
+ address: getAddress(voteContractAddress.toString()),
521
+ abi: EmpireBaseAbi,
522
+ client: this.walletClient,
523
+ });
469
524
 
470
525
  const [proposer, roundNumber] = await Promise.all([
471
526
  this.rollupContract.read.getProposerAt([timestamp]),
472
- this.governanceProposerContract.read.computeRound([slotNumber]),
527
+ voteContract.read.computeRound([slotNumber]),
473
528
  ]);
474
529
 
475
530
  if (proposer.toLowerCase() !== this.account.address.toLowerCase()) {
476
531
  return false;
477
532
  }
478
533
 
479
- const [slotForLastVote] = await this.governanceProposerContract.read.rounds([
480
- this.rollupContract.address,
481
- roundNumber,
482
- ]);
534
+ const [slotForLastVote] = await voteContract.read.rounds([this.rollupContract.address, roundNumber]);
483
535
 
484
536
  if (slotForLastVote >= slotNumber) {
485
537
  return false;
486
538
  }
487
539
 
488
- // Storing these early such that a quick entry again would not send another tx,
489
- // revert the state if there is a failure.
490
- const cachedMyLastVote = this.myLastVote;
491
- this.myLastVote = slotNumber;
492
-
493
- this.governanceLog.verbose(`Casting vote for ${this.payload}`);
540
+ const cachedMyLastVote = this.myLastVotes[voteType];
541
+ this.myLastVotes[voteType] = slotNumber;
494
542
 
495
543
  let txHash;
496
544
  try {
497
- txHash = await this.governanceProposerContract.write.vote([this.payload.toString()], { account: this.account });
545
+ txHash = await voteContract.write.vote([payload.toString()], {
546
+ account: this.account,
547
+ });
498
548
  } catch (err) {
499
- const msg = prettyLogViemErrorMsg(err);
500
- this.governanceLog.error(`Failed to vote`, msg);
501
- this.myLastVote = cachedMyLastVote;
549
+ const msg = formatViemError(err);
550
+ logger.error(`Failed to vote`, msg);
551
+ this.myLastVotes[voteType] = cachedMyLastVote;
502
552
  return false;
503
553
  }
504
554
 
505
555
  if (txHash) {
506
556
  const receipt = await this.getTransactionReceipt(txHash);
507
557
  if (!receipt) {
508
- this.governanceLog.warn(`Failed to get receipt for tx ${txHash}`);
509
- this.myLastVote = cachedMyLastVote;
558
+ logger.warn(`Failed to get receipt for tx ${txHash}`);
559
+ this.myLastVotes[voteType] = cachedMyLastVote;
510
560
  return false;
511
561
  }
512
562
  }
513
563
 
514
- this.governanceLog.info(`Cast vote for ${this.payload}`);
564
+ logger.info(`Cast vote for ${payload}`);
515
565
  return true;
516
566
  }
517
567
 
@@ -525,6 +575,7 @@ export class L1Publisher {
525
575
  attestations?: Signature[],
526
576
  txHashes?: TxHash[],
527
577
  proofQuote?: EpochProofQuote,
578
+ opts: { txTimeoutAt?: Date } = {},
528
579
  ): Promise<boolean> {
529
580
  const ctx = {
530
581
  blockNumber: block.number,
@@ -534,16 +585,19 @@ export class L1Publisher {
534
585
 
535
586
  const consensusPayload = new ConsensusPayload(block.header, block.archive.root, txHashes ?? []);
536
587
 
537
- const digest = getHashedSignaturePayload(consensusPayload, SignatureDomainSeperator.blockAttestation);
588
+ const digest = getHashedSignaturePayload(consensusPayload, SignatureDomainSeparator.blockAttestation);
589
+
590
+ const blobs = Blob.getBlobs(block.body.toBlobFields());
538
591
  const proposeTxArgs = {
539
592
  header: block.header.toBuffer(),
540
593
  archive: block.archive.root.toBuffer(),
541
594
  blockHash: block.header.hash().toBuffer(),
542
595
  body: block.body.toBuffer(),
543
- blobs: Blob.getBlobs(block.body.toBlobFields()),
596
+ blobs,
544
597
  attestations,
545
598
  txHashes: txHashes ?? [],
546
599
  };
600
+
547
601
  // Publish body and propose block (if not already published)
548
602
  if (this.interrupted) {
549
603
  this.log.verbose('L2 block data syncing interrupted while processing blocks.', ctx);
@@ -563,15 +617,15 @@ export class L1Publisher {
563
617
 
564
618
  this.log.debug(`Submitting propose transaction`);
565
619
  const result = proofQuote
566
- ? await this.sendProposeAndClaimTx(proposeTxArgs, proofQuote)
567
- : await this.sendProposeTx(proposeTxArgs);
620
+ ? await this.sendProposeAndClaimTx(proposeTxArgs, proofQuote, opts)
621
+ : await this.sendProposeTx(proposeTxArgs, opts);
568
622
 
569
623
  if (!result?.receipt) {
570
624
  this.log.info(`Failed to publish block ${block.number} to L1`, ctx);
571
625
  return false;
572
626
  }
573
627
 
574
- const { receipt, args, functionName, data } = result;
628
+ const { receipt, args, functionName, data, gasPrice } = result;
575
629
 
576
630
  // Tx was mined successfully
577
631
  if (receipt.status === 'success') {
@@ -588,6 +642,12 @@ export class L1Publisher {
588
642
  };
589
643
  this.log.verbose(`Published L2 block to L1 rollup contract`, { ...stats, ...ctx });
590
644
  this.metrics.recordProcessBlockTx(timer.ms(), stats);
645
+
646
+ // Send the blobs to the blob sink
647
+ this.sendBlobsToBlobSink(receipt.blockHash, blobs).catch(_err => {
648
+ this.log.error('Failed to send blobs to blob sink');
649
+ });
650
+
591
651
  return true;
592
652
  }
593
653
 
@@ -602,9 +662,9 @@ export class L1Publisher {
602
662
  address: this.rollupContract.address,
603
663
  },
604
664
  {
605
- blobs: proposeTxArgs.blobs.map(b => b.data),
665
+ blobs: proposeTxArgs.blobs.map(b => b.dataWithZeros),
606
666
  kzg,
607
- maxFeePerBlobGas: 10000000000n,
667
+ maxFeePerBlobGas: gasPrice.maxFeePerBlobGas ?? 10000000000n,
608
668
  },
609
669
  );
610
670
  this.log.error(`Rollup process tx reverted. ${errorMsg}`, undefined, {
@@ -618,11 +678,10 @@ export class L1Publisher {
618
678
  /** Calls claimEpochProofRight in the Rollup contract to submit a chosen prover quote for the previous epoch. */
619
679
  public async claimEpochProofRight(proofQuote: EpochProofQuote) {
620
680
  const timer = new Timer();
621
-
622
- let receipt;
681
+ let result;
623
682
  try {
624
683
  this.log.debug(`Submitting claimEpochProofRight transaction`);
625
- receipt = await this.l1TxUtils.sendAndMonitorTransaction({
684
+ result = await this.l1TxUtils.sendAndMonitorTransaction({
626
685
  to: this.rollupContract.address,
627
686
  data: encodeFunctionData({
628
687
  abi: RollupAbi,
@@ -631,12 +690,14 @@ export class L1Publisher {
631
690
  }),
632
691
  });
633
692
  } catch (err) {
634
- this.log.error(`Failed to claim epoch proof right: ${prettyLogViemErrorMsg(err)}`, err, {
693
+ this.log.error(`Failed to claim epoch proof right`, err, {
635
694
  proofQuote: proofQuote.toInspect(),
636
695
  });
637
696
  return false;
638
697
  }
639
698
 
699
+ const { receipt } = result;
700
+
640
701
  if (receipt.status === 'success') {
641
702
  const tx = await this.getTransactionStats(receipt.transactionHash);
642
703
  const stats: L1PublishStats = {
@@ -698,8 +759,7 @@ export class L1Publisher {
698
759
  },
699
760
  ],
700
761
  });
701
- // If the above passes, we have a blob error. We cannot simulate blob txs, and failed txs no longer throw errors,
702
- // and viem provides no way to get the revert reason from a given tx.
762
+ // If the above passes, we have a blob error. We cannot simulate blob txs, and failed txs no longer throw errors.
703
763
  // Strangely, the only way to throw the revert reason as an error and provide blobs is prepareTransactionRequest.
704
764
  // See: https://github.com/wevm/viem/issues/2075
705
765
  // This throws a EstimateGasExecutionError with the custom error information:
@@ -711,13 +771,13 @@ export class L1Publisher {
711
771
  });
712
772
  return undefined;
713
773
  } catch (simulationErr: any) {
714
- // If we don't have a ContractFunctionExecutionError, we have a blob related error => use ExtRollupLibAbi to get the error msg.
774
+ // If we don't have a ContractFunctionExecutionError, we have a blob related error => use getContractError to get the error msg.
715
775
  const contractErr =
716
776
  simulationErr.name === 'ContractFunctionExecutionError'
717
777
  ? simulationErr
718
778
  : getContractError(simulationErr as BaseError, {
719
779
  args: [],
720
- abi: ExtRollupLibAbi,
780
+ abi: RollupAbi,
721
781
  functionName: args.functionName,
722
782
  address: args.address,
723
783
  sender: this.account.address,
@@ -860,35 +920,42 @@ export class L1Publisher {
860
920
  publicInputs: RootRollupPublicInputs;
861
921
  proof: Proof;
862
922
  }): Promise<string | undefined> {
863
- try {
864
- const proofHex: Hex = `0x${args.proof.withoutPublicInputs().toString('hex')}`;
865
- const argsArray = this.getSubmitEpochProofArgs(args);
923
+ const proofHex: Hex = `0x${args.proof.withoutPublicInputs().toString('hex')}`;
924
+ const argsArray = this.getSubmitEpochProofArgs(args);
866
925
 
867
- const txArgs = [
868
- {
869
- epochSize: argsArray[0],
870
- args: argsArray[1],
871
- fees: argsArray[2],
872
- blobPublicInputs: argsArray[3],
873
- aggregationObject: argsArray[4],
874
- proof: proofHex,
875
- },
876
- ] as const;
877
-
878
- this.log.info(`SubmitEpochProof proofSize=${args.proof.withoutPublicInputs().length} bytes`);
926
+ const txArgs = [
927
+ {
928
+ epochSize: argsArray[0],
929
+ args: argsArray[1],
930
+ fees: argsArray[2],
931
+ blobPublicInputs: argsArray[3],
932
+ aggregationObject: argsArray[4],
933
+ proof: proofHex,
934
+ },
935
+ ] as const;
879
936
 
880
- const txReceipt = await this.l1TxUtils.sendAndMonitorTransaction({
937
+ this.log.info(`SubmitEpochProof proofSize=${args.proof.withoutPublicInputs().length} bytes`);
938
+ const data = encodeFunctionData({
939
+ abi: this.rollupContract.abi,
940
+ functionName: 'submitEpochRootProof',
941
+ args: txArgs,
942
+ });
943
+ try {
944
+ const { receipt } = await this.l1TxUtils.sendAndMonitorTransaction({
881
945
  to: this.rollupContract.address,
882
- data: encodeFunctionData({
883
- abi: this.rollupContract.abi,
884
- functionName: 'submitEpochRootProof',
885
- args: txArgs,
886
- }),
946
+ data,
887
947
  });
888
948
 
889
- return txReceipt.transactionHash;
949
+ return receipt.transactionHash;
890
950
  } catch (err) {
891
951
  this.log.error(`Rollup submit epoch proof failed`, err);
952
+ const errorMsg = await this.tryGetErrorFromRevertedTx(data, {
953
+ args: [...txArgs],
954
+ functionName: 'submitEpochRootProof',
955
+ abi: this.rollupContract.abi,
956
+ address: this.rollupContract.address,
957
+ });
958
+ this.log.error(`Rollup submit epoch proof tx reverted. ${errorMsg}`);
892
959
  return undefined;
893
960
  }
894
961
  }
@@ -907,9 +974,8 @@ export class L1Publisher {
907
974
  },
908
975
  {},
909
976
  {
910
- blobs: encodedData.blobs.map(b => b.data),
977
+ blobs: encodedData.blobs.map(b => b.dataWithZeros),
911
978
  kzg,
912
- maxFeePerBlobGas: 10000000000n, //This is 10 gwei, taken from DEFAULT_MAX_FEE_PER_GAS
913
979
  },
914
980
  );
915
981
 
@@ -976,7 +1042,8 @@ export class L1Publisher {
976
1042
 
977
1043
  private async sendProposeTx(
978
1044
  encodedData: L1ProcessArgs,
979
- ): Promise<{ receipt: TransactionReceipt | undefined; args: any; functionName: string; data: Hex } | undefined> {
1045
+ opts: { txTimeoutAt?: Date } = {},
1046
+ ): Promise<L1ProcessReturnType | undefined> {
980
1047
  if (this.interrupted) {
981
1048
  return undefined;
982
1049
  }
@@ -988,28 +1055,29 @@ export class L1Publisher {
988
1055
  functionName: 'propose',
989
1056
  args,
990
1057
  });
991
- const receipt = await this.l1TxUtils.sendAndMonitorTransaction(
1058
+ const result = await this.l1TxUtils.sendAndMonitorTransaction(
992
1059
  {
993
1060
  to: this.rollupContract.address,
994
1061
  data,
995
1062
  },
996
1063
  {
997
1064
  fixedGas: gas,
1065
+ ...opts,
998
1066
  },
999
1067
  {
1000
- blobs: encodedData.blobs.map(b => b.data),
1068
+ blobs: encodedData.blobs.map(b => b.dataWithZeros),
1001
1069
  kzg,
1002
- maxFeePerBlobGas: 10000000000n, //This is 10 gwei, taken from DEFAULT_MAX_FEE_PER_GAS
1003
1070
  },
1004
1071
  );
1005
1072
  return {
1006
- receipt,
1073
+ receipt: result.receipt,
1074
+ gasPrice: result.gasPrice,
1007
1075
  args,
1008
1076
  functionName: 'propose',
1009
1077
  data,
1010
1078
  };
1011
1079
  } catch (err) {
1012
- this.log.error(`Rollup publish failed: ${prettyLogViemErrorMsg(err)}`, err);
1080
+ this.log.error(`Rollup publish failed.`, err);
1013
1081
  return undefined;
1014
1082
  }
1015
1083
  }
@@ -1017,7 +1085,8 @@ export class L1Publisher {
1017
1085
  private async sendProposeAndClaimTx(
1018
1086
  encodedData: L1ProcessArgs,
1019
1087
  quote: EpochProofQuote,
1020
- ): Promise<{ receipt: TransactionReceipt | undefined; args: any; functionName: string; data: Hex } | undefined> {
1088
+ opts: { txTimeoutAt?: Date } = {},
1089
+ ): Promise<L1ProcessReturnType | undefined> {
1021
1090
  if (this.interrupted) {
1022
1091
  return undefined;
1023
1092
  }
@@ -1029,27 +1098,30 @@ export class L1Publisher {
1029
1098
  functionName: 'proposeAndClaim',
1030
1099
  args: [...args, quote.toViemArgs()],
1031
1100
  });
1032
- const receipt = await this.l1TxUtils.sendAndMonitorTransaction(
1101
+ const result = await this.l1TxUtils.sendAndMonitorTransaction(
1033
1102
  {
1034
1103
  to: this.rollupContract.address,
1035
1104
  data,
1036
1105
  },
1037
- { fixedGas: gas },
1038
1106
  {
1039
- blobs: encodedData.blobs.map(b => b.data),
1107
+ fixedGas: gas,
1108
+ ...opts,
1109
+ },
1110
+ {
1111
+ blobs: encodedData.blobs.map(b => b.dataWithZeros),
1040
1112
  kzg,
1041
- maxFeePerBlobGas: 10000000000n, //This is 10 gwei, taken from DEFAULT_MAX_FEE_PER_GAS
1042
1113
  },
1043
1114
  );
1044
1115
 
1045
1116
  return {
1046
- receipt,
1117
+ receipt: result.receipt,
1118
+ gasPrice: result.gasPrice,
1047
1119
  args: [...args, quote.toViemArgs()],
1048
1120
  functionName: 'proposeAndClaim',
1049
1121
  data,
1050
1122
  };
1051
1123
  } catch (err) {
1052
- this.log.error(`Rollup publish failed: ${prettyLogViemErrorMsg(err)}`, err);
1124
+ this.log.error(`Rollup publish failed.`, err);
1053
1125
  return undefined;
1054
1126
  }
1055
1127
  }
@@ -1078,6 +1150,7 @@ export class L1Publisher {
1078
1150
  gasPrice: receipt.effectiveGasPrice,
1079
1151
  logs: receipt.logs,
1080
1152
  blockNumber: receipt.blockNumber,
1153
+ blockHash: receipt.blockHash,
1081
1154
  };
1082
1155
  }
1083
1156
 
@@ -1093,9 +1166,21 @@ export class L1Publisher {
1093
1166
  protected async sleepOrInterrupted() {
1094
1167
  await this.interruptibleSleep.sleep(this.sleepTimeMs);
1095
1168
  }
1169
+
1170
+ /**
1171
+ * Send blobs to the blob sink
1172
+ *
1173
+ * If a blob sink url is configured, then we send blobs to the blob sink
1174
+ * - for now we use the blockHash as the identifier for the blobs;
1175
+ * In the future this will move to be the beacon block id - which takes a bit more work
1176
+ * to calculate and will need to be mocked in e2e tests
1177
+ */
1178
+ protected sendBlobsToBlobSink(blockHash: string, blobs: Blob[]): Promise<boolean> {
1179
+ return this.blobSinkClient.sendBlobsToBlobSink(blockHash, blobs);
1180
+ }
1096
1181
  }
1097
1182
 
1098
- /**
1183
+ /*
1099
1184
  * Returns cost of calldata usage in Ethereum.
1100
1185
  * @param data - Calldata.
1101
1186
  * @returns 4 for each zero byte, 16 for each nonzero.