@aztec/sequencer-client 0.56.0 → 0.57.0

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 (38) hide show
  1. package/dest/block_builder/index.d.ts +2 -2
  2. package/dest/block_builder/index.d.ts.map +1 -1
  3. package/dest/block_builder/light.d.ts +7 -11
  4. package/dest/block_builder/light.d.ts.map +1 -1
  5. package/dest/block_builder/light.js +6 -11
  6. package/dest/block_builder/orchestrator.d.ts +7 -10
  7. package/dest/block_builder/orchestrator.d.ts.map +1 -1
  8. package/dest/block_builder/orchestrator.js +3 -10
  9. package/dest/config.js +8 -5
  10. package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
  11. package/dest/global_variable_builder/global_builder.js +2 -1
  12. package/dest/publisher/config.d.ts.map +1 -1
  13. package/dest/publisher/config.js +7 -2
  14. package/dest/publisher/index.d.ts +1 -1
  15. package/dest/publisher/index.d.ts.map +1 -1
  16. package/dest/publisher/index.js +1 -1
  17. package/dest/publisher/l1-publisher.d.ts +45 -34
  18. package/dest/publisher/l1-publisher.d.ts.map +1 -1
  19. package/dest/publisher/l1-publisher.js +257 -115
  20. package/dest/publisher/utils.d.ts +1 -1
  21. package/dest/publisher/utils.js +1 -1
  22. package/dest/sequencer/sequencer.d.ts +3 -2
  23. package/dest/sequencer/sequencer.d.ts.map +1 -1
  24. package/dest/sequencer/sequencer.js +54 -18
  25. package/dest/tx_validator/gas_validator.d.ts.map +1 -1
  26. package/dest/tx_validator/gas_validator.js +7 -5
  27. package/package.json +22 -19
  28. package/src/block_builder/index.ts +2 -2
  29. package/src/block_builder/light.ts +10 -19
  30. package/src/block_builder/orchestrator.ts +7 -20
  31. package/src/config.ts +7 -4
  32. package/src/global_variable_builder/global_builder.ts +1 -0
  33. package/src/publisher/config.ts +6 -1
  34. package/src/publisher/index.ts +1 -1
  35. package/src/publisher/l1-publisher.ts +369 -142
  36. package/src/publisher/utils.ts +1 -1
  37. package/src/sequencer/sequencer.ts +68 -20
  38. package/src/tx_validator/gas_validator.ts +6 -7
@@ -1,39 +1,66 @@
1
- import { ConsensusPayload, type L2Block, type TxHash, getHashedSignaturePayload } from '@aztec/circuit-types';
1
+ import {
2
+ ConsensusPayload,
3
+ type EpochProofClaim,
4
+ type EpochProofQuote,
5
+ type L2Block,
6
+ type TxHash,
7
+ getHashedSignaturePayload,
8
+ } from '@aztec/circuit-types';
2
9
  import { type L1PublishBlockStats, type L1PublishProofStats } from '@aztec/circuit-types/stats';
3
- import { ETHEREUM_SLOT_DURATION, EthAddress, type Header, type Proof } from '@aztec/circuits.js';
10
+ import {
11
+ AGGREGATION_OBJECT_LENGTH,
12
+ ETHEREUM_SLOT_DURATION,
13
+ EthAddress,
14
+ type FeeRecipient,
15
+ type Header,
16
+ type Proof,
17
+ type RootRollupPublicInputs,
18
+ } from '@aztec/circuits.js';
4
19
  import { createEthereumChain } from '@aztec/ethereum';
20
+ import { makeTuple } from '@aztec/foundation/array';
21
+ import { areArraysEqual, times } from '@aztec/foundation/collection';
5
22
  import { type Signature } from '@aztec/foundation/eth-signature';
6
- import { type Fr } from '@aztec/foundation/fields';
23
+ import { Fr } from '@aztec/foundation/fields';
7
24
  import { createDebugLogger } from '@aztec/foundation/log';
8
- import { serializeToBuffer } from '@aztec/foundation/serialize';
25
+ import { type Tuple, serializeToBuffer } from '@aztec/foundation/serialize';
9
26
  import { InterruptibleSleep } from '@aztec/foundation/sleep';
10
27
  import { Timer } from '@aztec/foundation/timer';
11
28
  import { RollupAbi } from '@aztec/l1-artifacts';
12
29
  import { type TelemetryClient } from '@aztec/telemetry-client';
13
30
 
14
31
  import pick from 'lodash.pick';
32
+ import { inspect } from 'util';
15
33
  import {
34
+ type BaseError,
35
+ type Chain,
36
+ type Client,
16
37
  ContractFunctionRevertedError,
17
38
  type GetContractReturnType,
18
39
  type Hex,
19
40
  type HttpTransport,
20
41
  type PrivateKeyAccount,
42
+ type PublicActions,
21
43
  type PublicClient,
44
+ type PublicRpcSchema,
45
+ type WalletActions,
22
46
  type WalletClient,
47
+ type WalletRpcSchema,
23
48
  createPublicClient,
24
49
  createWalletClient,
25
50
  encodeFunctionData,
51
+ getAbiItem,
26
52
  getAddress,
27
53
  getContract,
28
54
  hexToBytes,
29
55
  http,
56
+ publicActions,
30
57
  } from 'viem';
31
58
  import { privateKeyToAccount } from 'viem/accounts';
32
59
  import type * as chains from 'viem/chains';
33
60
 
34
61
  import { type PublisherConfig, type TxSenderConfig } from './config.js';
35
62
  import { L1PublisherMetrics } from './l1-publisher-metrics.js';
36
- import { prettyLogVeimError } from './utils.js';
63
+ import { prettyLogViemError } from './utils.js';
37
64
 
38
65
  /**
39
66
  * Stats for a sent transaction.
@@ -64,7 +91,7 @@ export type MinimalTransactionReceipt = {
64
91
  };
65
92
 
66
93
  /** Arguments to the process method of the rollup contract */
67
- export type L1ProcessArgs = {
94
+ type L1ProcessArgs = {
68
95
  /** The L2 block header. */
69
96
  header: Buffer;
70
97
  /** A root of the archive tree after the L2 block is applied. */
@@ -79,18 +106,18 @@ export type L1ProcessArgs = {
79
106
  attestations?: Signature[];
80
107
  };
81
108
 
82
- /** Arguments to the submitProof method of the rollup contract */
83
- export type L1SubmitProofArgs = {
84
- /** The L2 block header. */
85
- header: Buffer;
86
- /** A root of the archive tree after the L2 block is applied. */
87
- archive: Buffer;
88
- /** Identifier of the prover. */
89
- proverId: Buffer;
90
- /** The proof for the block. */
91
- proof: Buffer;
92
- /** The aggregation object for the block's proof. */
93
- aggregationObject: Buffer;
109
+ /** Arguments to the submitEpochProof method of the rollup contract */
110
+ export type L1SubmitEpochProofArgs = {
111
+ epochSize: number;
112
+ previousArchive: Fr;
113
+ endArchive: Fr;
114
+ previousBlockHash: Fr;
115
+ endBlockHash: Fr;
116
+ endTimestamp: Fr;
117
+ outHash: Fr;
118
+ proverId: Fr;
119
+ fees: Tuple<FeeRecipient, 32>;
120
+ proof: Proof;
94
121
  };
95
122
 
96
123
  /**
@@ -112,10 +139,13 @@ export class L1Publisher {
112
139
  typeof RollupAbi,
113
140
  WalletClient<HttpTransport, chains.Chain, PrivateKeyAccount>
114
141
  >;
142
+
115
143
  private publicClient: PublicClient<HttpTransport, chains.Chain>;
144
+ private walletClient: WalletClient<HttpTransport, chains.Chain, PrivateKeyAccount>;
116
145
  private account: PrivateKeyAccount;
117
146
 
118
147
  public static PROPOSE_GAS_GUESS: bigint = 500_000n;
148
+ public static PROPOSE_AND_CLAIM_GAS_GUESS: bigint = 600_000n;
119
149
 
120
150
  constructor(config: TxSenderConfig & PublisherConfig, client: TelemetryClient) {
121
151
  this.sleepTimeMs = config?.l1PublishRetryIntervalMS ?? 60_000;
@@ -125,7 +155,8 @@ export class L1Publisher {
125
155
  const chain = createEthereumChain(rpcUrl, chainId);
126
156
  this.account = privateKeyToAccount(publisherPrivateKey);
127
157
  this.log.debug(`Publishing from address ${this.account.address}`);
128
- const walletClient = createWalletClient({
158
+
159
+ this.walletClient = createWalletClient({
129
160
  account: this.account,
130
161
  chain: chain.chainInfo,
131
162
  transport: http(chain.rpcUrl),
@@ -134,17 +165,35 @@ export class L1Publisher {
134
165
  this.publicClient = createPublicClient({
135
166
  chain: chain.chainInfo,
136
167
  transport: http(chain.rpcUrl),
168
+ pollingInterval: config.viemPollingIntervalMS,
137
169
  });
138
170
 
139
171
  this.rollupContract = getContract({
140
172
  address: getAddress(l1Contracts.rollupAddress.toString()),
141
173
  abi: RollupAbi,
142
- client: walletClient,
174
+ client: this.walletClient,
143
175
  });
144
176
  }
145
177
 
146
- public getSenderAddress(): Promise<EthAddress> {
147
- return Promise.resolve(EthAddress.fromString(this.account.address));
178
+ public getSenderAddress(): EthAddress {
179
+ return EthAddress.fromString(this.account.address);
180
+ }
181
+
182
+ public getClient(): Client<
183
+ HttpTransport,
184
+ Chain,
185
+ PrivateKeyAccount,
186
+ [...WalletRpcSchema, ...PublicRpcSchema],
187
+ PublicActions<HttpTransport, Chain> & WalletActions<Chain, PrivateKeyAccount>
188
+ > {
189
+ return this.walletClient.extend(publicActions);
190
+ }
191
+
192
+ public getRollupContract(): GetContractReturnType<
193
+ typeof RollupAbi,
194
+ WalletClient<HttpTransport, chains.Chain, PrivateKeyAccount>
195
+ > {
196
+ return this.rollupContract;
148
197
  }
149
198
 
150
199
  /**
@@ -162,6 +211,60 @@ export class L1Publisher {
162
211
  return [slot, blockNumber];
163
212
  }
164
213
 
214
+ public async nextEpochToClaim(): Promise<bigint> {
215
+ return await this.rollupContract.read.nextEpochToClaim();
216
+ }
217
+
218
+ public async getEpochForSlotNumber(slotNumber: bigint): Promise<bigint> {
219
+ return await this.rollupContract.read.getEpochAtSlot([slotNumber]);
220
+ }
221
+
222
+ public async getEpochToProve(): Promise<bigint | undefined> {
223
+ try {
224
+ return await this.rollupContract.read.getEpochToProve();
225
+ } catch (err: any) {
226
+ // If this is a revert with Rollup__NoEpochToProve, it means there is no epoch to prove, so we return undefined
227
+ // See https://viem.sh/docs/contract/simulateContract#handling-custom-errors
228
+ const errorName = tryGetCustomErrorName(err);
229
+ if (errorName === getAbiItem({ abi: RollupAbi, name: 'Rollup__NoEpochToProve' }).name) {
230
+ return undefined;
231
+ }
232
+ throw err;
233
+ }
234
+ }
235
+
236
+ public async getProofClaim(): Promise<EpochProofClaim | undefined> {
237
+ const [epochToProve, basisPointFee, bondAmount, bondProviderHex, proposerClaimantHex] =
238
+ await this.rollupContract.read.proofClaim();
239
+
240
+ const bondProvider = EthAddress.fromString(bondProviderHex);
241
+ const proposerClaimant = EthAddress.fromString(proposerClaimantHex);
242
+
243
+ if (bondProvider.isZero() && proposerClaimant.isZero() && epochToProve === 0n) {
244
+ return undefined;
245
+ }
246
+
247
+ return {
248
+ epochToProve,
249
+ basisPointFee,
250
+ bondAmount,
251
+ bondProvider,
252
+ proposerClaimant,
253
+ };
254
+ }
255
+
256
+ public async validateProofQuote(quote: EpochProofQuote): Promise<EpochProofQuote | undefined> {
257
+ const args = [quote.toViemArgs()] as const;
258
+ try {
259
+ await this.rollupContract.read.validateEpochProofRightClaim(args, { account: this.account });
260
+ } catch (err) {
261
+ const errorName = tryGetCustomErrorName(err);
262
+ this.log.verbose(`Proof quote validation failed: ${errorName}`);
263
+ return undefined;
264
+ }
265
+ return quote;
266
+ }
267
+
165
268
  /**
166
269
  * @notice Will call `validateHeader` to make sure that it is possible to propose
167
270
  *
@@ -229,7 +332,12 @@ export class L1Publisher {
229
332
  * @param block - L2 block to propose.
230
333
  * @returns True once the tx has been confirmed and is successful, false on revert or interrupt, blocks otherwise.
231
334
  */
232
- public async proposeL2Block(block: L2Block, attestations?: Signature[], txHashes?: TxHash[]): Promise<boolean> {
335
+ public async proposeL2Block(
336
+ block: L2Block,
337
+ attestations?: Signature[],
338
+ txHashes?: TxHash[],
339
+ proofQuote?: EpochProofQuote,
340
+ ): Promise<boolean> {
233
341
  const ctx = {
234
342
  blockNumber: block.number,
235
343
  slotNumber: block.header.globalVariables.slotNumber.toBigInt(),
@@ -249,77 +357,77 @@ export class L1Publisher {
249
357
  };
250
358
 
251
359
  // Publish body and propose block (if not already published)
252
- if (!this.interrupted) {
253
- const timer = new Timer();
254
-
255
- // @note This will make sure that we are passing the checks for our header ASSUMING that the data is also made available
256
- // This means that we can avoid the simulation issues in later checks.
257
- // By simulation issue, I mean the fact that the block.timestamp is equal to the last block, not the next, which
258
- // make time consistency checks break.
259
- await this.validateBlockForSubmission(block.header, {
260
- digest: digest.toBuffer(),
261
- signatures: attestations ?? [],
262
- });
360
+ if (this.interrupted) {
361
+ this.log.verbose('L2 block data syncing interrupted while processing blocks.', ctx);
362
+ return false;
363
+ }
263
364
 
264
- const txHash = await this.sendProposeTx(proposeTxArgs);
365
+ const timer = new Timer();
265
366
 
266
- if (!txHash) {
267
- this.log.info(`Failed to publish block ${block.number} to L1`, ctx);
268
- return false;
269
- }
367
+ // @note This will make sure that we are passing the checks for our header ASSUMING that the data is also made available
368
+ // This means that we can avoid the simulation issues in later checks.
369
+ // By simulation issue, I mean the fact that the block.timestamp is equal to the last block, not the next, which
370
+ // make time consistency checks break.
371
+ await this.validateBlockForSubmission(block.header, {
372
+ digest: digest.toBuffer(),
373
+ signatures: attestations ?? [],
374
+ });
270
375
 
271
- const receipt = await this.getTransactionReceipt(txHash);
272
- if (!receipt) {
273
- this.log.info(`Failed to get receipt for tx ${txHash}`, ctx);
274
- return false;
275
- }
376
+ this.log.verbose(`Submitting propose transaction`);
276
377
 
277
- // Tx was mined successfully
278
- if (receipt.status) {
279
- const tx = await this.getTransactionStats(txHash);
280
- const stats: L1PublishBlockStats = {
281
- ...pick(receipt, 'gasPrice', 'gasUsed', 'transactionHash'),
282
- ...pick(tx!, 'calldataGas', 'calldataSize'),
283
- ...block.getStats(),
284
- eventName: 'rollup-published-to-l1',
285
- };
286
- this.log.info(`Published L2 block to L1 rollup contract`, { ...stats, ...ctx });
287
- this.metrics.recordProcessBlockTx(timer.ms(), stats);
378
+ const txHash = proofQuote
379
+ ? await this.sendProposeAndClaimTx(proposeTxArgs, proofQuote)
380
+ : await this.sendProposeTx(proposeTxArgs);
288
381
 
289
- return true;
290
- }
382
+ if (!txHash) {
383
+ this.log.info(`Failed to publish block ${block.number} to L1`, ctx);
384
+ return false;
385
+ }
291
386
 
292
- this.metrics.recordFailedTx('process');
387
+ const receipt = await this.getTransactionReceipt(txHash);
388
+ if (!receipt) {
389
+ this.log.info(`Failed to get receipt for tx ${txHash}`, ctx);
390
+ return false;
391
+ }
293
392
 
294
- this.log.error(`Rollup.process tx status failed: ${receipt.transactionHash}`, ctx);
295
- await this.sleepOrInterrupted();
393
+ // Tx was mined successfully
394
+ if (receipt.status) {
395
+ const tx = await this.getTransactionStats(txHash);
396
+ const stats: L1PublishBlockStats = {
397
+ ...pick(receipt, 'gasPrice', 'gasUsed', 'transactionHash'),
398
+ ...pick(tx!, 'calldataGas', 'calldataSize'),
399
+ ...block.getStats(),
400
+ eventName: 'rollup-published-to-l1',
401
+ };
402
+ this.log.info(`Published L2 block to L1 rollup contract`, { ...stats, ...ctx });
403
+ this.metrics.recordProcessBlockTx(timer.ms(), stats);
404
+
405
+ return true;
296
406
  }
297
407
 
298
- this.log.verbose('L2 block data syncing interrupted while processing blocks.', ctx);
408
+ this.metrics.recordFailedTx('process');
409
+
410
+ this.log.error(`Rollup.process tx status failed: ${receipt.transactionHash}`, ctx);
411
+ await this.sleepOrInterrupted();
299
412
  return false;
300
413
  }
301
414
 
302
- public async submitProof(
303
- header: Header,
304
- archiveRoot: Fr,
305
- proverId: Fr,
306
- aggregationObject: Fr[],
307
- proof: Proof,
308
- ): Promise<boolean> {
309
- const ctx = { blockNumber: header.globalVariables.blockNumber, slotNumber: header.globalVariables.slotNumber };
310
-
311
- const txArgs: L1SubmitProofArgs = {
312
- header: header.toBuffer(),
313
- archive: archiveRoot.toBuffer(),
314
- proverId: proverId.toBuffer(),
315
- aggregationObject: serializeToBuffer(aggregationObject),
316
- proof: proof.withoutPublicInputs(),
317
- };
318
-
319
- // Process block
415
+ public async submitEpochProof(args: {
416
+ epochNumber: number;
417
+ fromBlock: number;
418
+ toBlock: number;
419
+ publicInputs: RootRollupPublicInputs;
420
+ proof: Proof;
421
+ }): Promise<boolean> {
422
+ const { epochNumber, fromBlock, toBlock } = args;
423
+ const ctx = { epochNumber, fromBlock, toBlock };
320
424
  if (!this.interrupted) {
321
425
  const timer = new Timer();
322
- const txHash = await this.sendSubmitProofTx(txArgs);
426
+
427
+ // Validate epoch proof range and hashes are correct before submitting
428
+ await this.validateEpochProofSubmission(args);
429
+
430
+ const txHash = await this.sendSubmitEpochProofTx(args);
323
431
  if (!txHash) {
324
432
  return false;
325
433
  }
@@ -337,13 +445,13 @@ export class L1Publisher {
337
445
  ...pick(tx!, 'calldataGas', 'calldataSize'),
338
446
  eventName: 'proof-published-to-l1',
339
447
  };
340
- this.log.info(`Published proof to L1 rollup contract`, { ...stats, ...ctx });
448
+ this.log.info(`Published epoch proof to L1 rollup contract`, { ...stats, ...ctx });
341
449
  this.metrics.recordSubmitProof(timer.ms(), stats);
342
450
  return true;
343
451
  }
344
452
 
345
453
  this.metrics.recordFailedTx('submitProof');
346
- this.log.error(`Rollup.submitProof tx status failed: ${receipt.transactionHash}`, ctx);
454
+ this.log.error(`Rollup.submitEpochProof tx status failed: ${receipt.transactionHash}`, ctx);
347
455
  await this.sleepOrInterrupted();
348
456
  }
349
457
 
@@ -351,6 +459,63 @@ export class L1Publisher {
351
459
  return false;
352
460
  }
353
461
 
462
+ private async validateEpochProofSubmission(args: {
463
+ fromBlock: number;
464
+ toBlock: number;
465
+ publicInputs: RootRollupPublicInputs;
466
+ proof: Proof;
467
+ }) {
468
+ const { fromBlock, toBlock, publicInputs, proof } = args;
469
+
470
+ // Check that the block numbers match the expected epoch to be proven
471
+ const [pending, proven] = await this.rollupContract.read.tips();
472
+ if (proven !== BigInt(fromBlock) - 1n) {
473
+ throw new Error(`Cannot submit epoch proof for ${fromBlock}-${toBlock} as proven block is ${proven}`);
474
+ }
475
+ if (toBlock > pending) {
476
+ throw new Error(`Cannot submit epoch proof for ${fromBlock}-${toBlock} as pending block is ${pending}`);
477
+ }
478
+
479
+ // Check the block hash and archive for the immediate block before the epoch
480
+ const [previousArchive, previousBlockHash] = await this.rollupContract.read.blocks([proven]);
481
+ if (publicInputs.previousArchive.root.toString() !== previousArchive) {
482
+ throw new Error(
483
+ `Previous archive root mismatch: ${publicInputs.previousArchive.root.toString()} !== ${previousArchive}`,
484
+ );
485
+ }
486
+ // TODO: Remove zero check once we inject the proper zero blockhash
487
+ if (previousBlockHash !== Fr.ZERO.toString() && publicInputs.previousBlockHash.toString() !== previousBlockHash) {
488
+ throw new Error(
489
+ `Previous block hash mismatch: ${publicInputs.previousBlockHash.toString()} !== ${previousBlockHash}`,
490
+ );
491
+ }
492
+
493
+ // Check the block hash and archive for the last block in the epoch
494
+ const [endArchive, endBlockHash] = await this.rollupContract.read.blocks([BigInt(toBlock)]);
495
+ if (publicInputs.endArchive.root.toString() !== endArchive) {
496
+ throw new Error(`End archive root mismatch: ${publicInputs.endArchive.root.toString()} !== ${endArchive}`);
497
+ }
498
+ if (publicInputs.endBlockHash.toString() !== endBlockHash) {
499
+ throw new Error(`End block hash mismatch: ${publicInputs.endBlockHash.toString()} !== ${endBlockHash}`);
500
+ }
501
+
502
+ // Compare the public inputs computed by the contract with the ones injected
503
+ const rollupPublicInputs = await this.rollupContract.read.getEpochProofPublicInputs(
504
+ this.getSubmitEpochProofArgs(args),
505
+ );
506
+ const aggregationObject = proof.isEmpty()
507
+ ? times(AGGREGATION_OBJECT_LENGTH, Fr.zero)
508
+ : proof.extractAggregationObject();
509
+ const argsPublicInputs = [...publicInputs.toFields(), ...aggregationObject];
510
+
511
+ if (!areArraysEqual(rollupPublicInputs.map(Fr.fromString), argsPublicInputs, (a, b) => a.equals(b))) {
512
+ const fmt = (inputs: Fr[] | readonly string[]) => inputs.map(x => x.toString()).join(', ');
513
+ throw new Error(
514
+ `Root rollup public inputs mismatch:\nRollup: ${fmt(rollupPublicInputs)}\nComputed:${fmt(argsPublicInputs)}`,
515
+ );
516
+ }
517
+ }
518
+
354
519
  /**
355
520
  * Calling `interrupt` will cause any in progress call to `publishRollup` to return `false` asap.
356
521
  * Be warned, the call may return false even if the tx subsequently gets successfully mined.
@@ -367,75 +532,122 @@ export class L1Publisher {
367
532
  this.interrupted = false;
368
533
  }
369
534
 
370
- private async sendSubmitProofTx(submitProofArgs: L1SubmitProofArgs): Promise<string | undefined> {
535
+ private async sendSubmitEpochProofTx(args: {
536
+ fromBlock: number;
537
+ toBlock: number;
538
+ publicInputs: RootRollupPublicInputs;
539
+ proof: Proof;
540
+ }): Promise<string | undefined> {
371
541
  try {
372
- const size = Object.values(submitProofArgs).reduce((acc, arg) => acc + arg.length, 0);
373
- this.log.info(`SubmitProof size=${size} bytes`);
374
-
375
- const { header, archive, proverId, aggregationObject, proof } = submitProofArgs;
376
- const args = [
377
- `0x${header.toString('hex')}`,
378
- `0x${archive.toString('hex')}`,
379
- `0x${proverId.toString('hex')}`,
380
- `0x${aggregationObject.toString('hex')}`,
381
- `0x${proof.toString('hex')}`,
382
- ] as const;
383
-
384
- await this.rollupContract.simulate.submitBlockRootProof(args, {
385
- account: this.account,
386
- });
542
+ const proofHex: Hex = `0x${args.proof.withoutPublicInputs().toString('hex')}`;
543
+ const txArgs = [...this.getSubmitEpochProofArgs(args), proofHex] as const;
544
+ this.log.info(`SubmitEpochProof proofSize=${args.proof.withoutPublicInputs().length} bytes`);
545
+ await this.rollupContract.simulate.submitEpochRootProof(txArgs, { account: this.account });
546
+ return await this.rollupContract.write.submitEpochRootProof(txArgs, { account: this.account });
547
+ } catch (err) {
548
+ this.log.error(`Rollup submit epoch proof failed`, err);
549
+ return undefined;
550
+ }
551
+ }
552
+
553
+ private async prepareProposeTx(encodedData: L1ProcessArgs, gasGuess: bigint) {
554
+ // We have to jump a few hoops because viem is not happy around estimating gas for view functions
555
+ const computeTxsEffectsHashGas = await this.publicClient.estimateGas({
556
+ to: this.rollupContract.address,
557
+ data: encodeFunctionData({
558
+ abi: this.rollupContract.abi,
559
+ functionName: 'computeTxsEffectsHash',
560
+ args: [`0x${encodedData.body.toString('hex')}`],
561
+ }),
562
+ });
563
+
564
+ // @note We perform this guesstimate instead of the usual `gasEstimate` since
565
+ // viem will use the current state to simulate against, which means that
566
+ // we will fail estimation in the case where we are simulating for the
567
+ // first ethereum block within our slot (as current time is not in the
568
+ // slot yet).
569
+ const gasGuesstimate = computeTxsEffectsHashGas + gasGuess;
570
+
571
+ const attestations = encodedData.attestations
572
+ ? encodedData.attestations.map(attest => attest.toViemSignature())
573
+ : [];
574
+ const txHashes = encodedData.txHashes ? encodedData.txHashes.map(txHash => txHash.to0xString()) : [];
575
+ const args = [
576
+ `0x${encodedData.header.toString('hex')}`,
577
+ `0x${encodedData.archive.toString('hex')}`,
578
+ `0x${encodedData.blockHash.toString('hex')}`,
579
+ txHashes,
580
+ attestations,
581
+ `0x${encodedData.body.toString('hex')}`,
582
+ ] as const;
583
+
584
+ return { args, gasGuesstimate };
585
+ }
586
+
587
+ private getSubmitEpochProofArgs(args: {
588
+ fromBlock: number;
589
+ toBlock: number;
590
+ publicInputs: RootRollupPublicInputs;
591
+ proof: Proof;
592
+ }) {
593
+ return [
594
+ BigInt(args.toBlock - args.fromBlock + 1),
595
+ [
596
+ args.publicInputs.previousArchive.root.toString(),
597
+ args.publicInputs.endArchive.root.toString(),
598
+ args.publicInputs.previousBlockHash.toString(),
599
+ args.publicInputs.endBlockHash.toString(),
600
+ args.publicInputs.endTimestamp.toString(),
601
+ args.publicInputs.outHash.toString(),
602
+ args.publicInputs.proverId.toString(),
603
+ ],
604
+ makeTuple(64, i =>
605
+ i % 2 === 0
606
+ ? args.publicInputs.fees[i / 2].recipient.toField().toString()
607
+ : args.publicInputs.fees[(i - 1) / 2].value.toString(),
608
+ ),
609
+ `0x${serializeToBuffer(args.proof.extractAggregationObject()).toString('hex')}`,
610
+ ] as const;
611
+ }
612
+
613
+ private async sendProposeTx(encodedData: L1ProcessArgs): Promise<string | undefined> {
614
+ if (this.interrupted) {
615
+ return;
616
+ }
617
+ try {
618
+ const { args, gasGuesstimate } = await this.prepareProposeTx(encodedData, L1Publisher.PROPOSE_GAS_GUESS);
387
619
 
388
- return await this.rollupContract.write.submitBlockRootProof(args, {
620
+ return await this.rollupContract.write.propose(args, {
389
621
  account: this.account,
622
+ gas: gasGuesstimate,
390
623
  });
391
624
  } catch (err) {
392
- this.log.error(`Rollup submit proof failed`, err);
625
+ prettyLogViemError(err, this.log);
626
+ this.log.error(`Rollup publish failed`, err);
393
627
  return undefined;
394
628
  }
395
629
  }
396
630
 
397
- private async sendProposeTx(encodedData: L1ProcessArgs): Promise<string | undefined> {
398
- if (!this.interrupted) {
399
- try {
400
- // We have to jump a few hoops because viem is not happy around estimating gas for view functions
401
- const computeTxsEffectsHashGas = await this.publicClient.estimateGas({
402
- to: this.rollupContract.address,
403
- data: encodeFunctionData({
404
- abi: this.rollupContract.abi,
405
- functionName: 'computeTxsEffectsHash',
406
- args: [`0x${encodedData.body.toString('hex')}`],
407
- }),
408
- });
409
-
410
- // @note We perform this guesstimate instead of the usual `gasEstimate` since
411
- // viem will use the current state to simulate against, which means that
412
- // we will fail estimation in the case where we are simulating for the
413
- // first ethereum block within our slot (as current time is not in the
414
- // slot yet).
415
- const gasGuesstimate = computeTxsEffectsHashGas + L1Publisher.PROPOSE_GAS_GUESS;
416
-
417
- const attestations = encodedData.attestations
418
- ? encodedData.attestations.map(attest => attest.toViemSignature())
419
- : [];
420
- const txHashes = encodedData.txHashes ? encodedData.txHashes.map(txHash => txHash.to0xString()) : [];
421
- const args = [
422
- `0x${encodedData.header.toString('hex')}`,
423
- `0x${encodedData.archive.toString('hex')}`,
424
- `0x${encodedData.blockHash.toString('hex')}`,
425
- txHashes,
426
- attestations,
427
- `0x${encodedData.body.toString('hex')}`,
428
- ] as const;
429
-
430
- return await this.rollupContract.write.propose(args, {
431
- account: this.account,
432
- gas: gasGuesstimate,
433
- });
434
- } catch (err) {
435
- prettyLogVeimError(err, this.log);
436
- this.log.error(`Rollup publish failed`, err);
437
- return undefined;
438
- }
631
+ private async sendProposeAndClaimTx(encodedData: L1ProcessArgs, quote: EpochProofQuote): Promise<string | undefined> {
632
+ if (this.interrupted) {
633
+ return;
634
+ }
635
+ try {
636
+ const { args, gasGuesstimate } = await this.prepareProposeTx(
637
+ encodedData,
638
+ L1Publisher.PROPOSE_AND_CLAIM_GAS_GUESS,
639
+ );
640
+ this.log.info(`ProposeAndClaim`);
641
+ this.log.info(inspect(quote.payload));
642
+
643
+ return await this.rollupContract.write.proposeAndClaim([...args, quote.toViemArgs()], {
644
+ account: this.account,
645
+ gas: gasGuesstimate,
646
+ });
647
+ } catch (err) {
648
+ prettyLogViemError(err, this.log);
649
+ this.log.error(`Rollup publish failed`, err);
650
+ return undefined;
439
651
  }
440
652
  }
441
653
 
@@ -487,3 +699,18 @@ export class L1Publisher {
487
699
  function getCalldataGasUsage(data: Uint8Array) {
488
700
  return data.filter(byte => byte === 0).length * 4 + data.filter(byte => byte !== 0).length * 16;
489
701
  }
702
+
703
+ function tryGetCustomErrorName(err: any) {
704
+ try {
705
+ // See https://viem.sh/docs/contract/simulateContract#handling-custom-errors
706
+ if (err.name === 'ViemError') {
707
+ const baseError = err as BaseError;
708
+ const revertError = baseError.walk(err => (err as Error).name === 'ContractFunctionRevertedError');
709
+ if (revertError) {
710
+ return (revertError as ContractFunctionRevertedError).data?.errorName;
711
+ }
712
+ }
713
+ } catch (_e) {
714
+ return undefined;
715
+ }
716
+ }
@@ -2,7 +2,7 @@ import { type Logger } from '@aztec/foundation/log';
2
2
 
3
3
  import { BaseError, ContractFunctionRevertedError } from 'viem';
4
4
 
5
- export function prettyLogVeimError(err: any, logger: Logger) {
5
+ export function prettyLogViemError(err: any, logger: Logger) {
6
6
  if (err instanceof BaseError) {
7
7
  const revertError = err.walk(err => err instanceof ContractFunctionRevertedError);
8
8
  if (revertError instanceof ContractFunctionRevertedError) {