@aztec/prover-node 0.0.0-test.1 → 0.0.1-commit.017a351

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/actions/download-epoch-proving-job.d.ts +18 -0
  2. package/dest/actions/download-epoch-proving-job.d.ts.map +1 -0
  3. package/dest/actions/download-epoch-proving-job.js +37 -0
  4. package/dest/actions/index.d.ts +3 -0
  5. package/dest/actions/index.d.ts.map +1 -0
  6. package/dest/actions/index.js +2 -0
  7. package/dest/actions/rerun-epoch-proving-job.d.ts +13 -0
  8. package/dest/actions/rerun-epoch-proving-job.d.ts.map +1 -0
  9. package/dest/actions/rerun-epoch-proving-job.js +120 -0
  10. package/dest/actions/upload-epoch-proof-failure.d.ts +15 -0
  11. package/dest/actions/upload-epoch-proof-failure.d.ts.map +1 -0
  12. package/dest/actions/upload-epoch-proof-failure.js +78 -0
  13. package/dest/bin/run-failed-epoch.d.ts +2 -0
  14. package/dest/bin/run-failed-epoch.d.ts.map +1 -0
  15. package/dest/bin/run-failed-epoch.js +68 -0
  16. package/dest/config.d.ts +17 -16
  17. package/dest/config.d.ts.map +1 -1
  18. package/dest/config.js +93 -28
  19. package/dest/factory.d.ts +26 -18
  20. package/dest/factory.d.ts.map +1 -1
  21. package/dest/factory.js +92 -43
  22. package/dest/index.d.ts +3 -2
  23. package/dest/index.d.ts.map +1 -1
  24. package/dest/index.js +2 -1
  25. package/dest/job/epoch-proving-job-data.d.ts +18 -0
  26. package/dest/job/epoch-proving-job-data.d.ts.map +1 -0
  27. package/dest/job/epoch-proving-job-data.js +59 -0
  28. package/dest/job/epoch-proving-job.d.ts +31 -18
  29. package/dest/job/epoch-proving-job.d.ts.map +1 -1
  30. package/dest/job/epoch-proving-job.js +756 -99
  31. package/dest/metrics.d.ts +50 -5
  32. package/dest/metrics.d.ts.map +1 -1
  33. package/dest/metrics.js +157 -72
  34. package/dest/monitors/epoch-monitor.d.ts +5 -2
  35. package/dest/monitors/epoch-monitor.d.ts.map +1 -1
  36. package/dest/monitors/epoch-monitor.js +24 -17
  37. package/dest/monitors/index.d.ts +1 -1
  38. package/dest/prover-node-publisher.d.ts +38 -19
  39. package/dest/prover-node-publisher.d.ts.map +1 -1
  40. package/dest/prover-node-publisher.js +288 -87
  41. package/dest/prover-node.d.ts +64 -49
  42. package/dest/prover-node.d.ts.map +1 -1
  43. package/dest/prover-node.js +650 -148
  44. package/dest/prover-publisher-factory.d.ts +25 -0
  45. package/dest/prover-publisher-factory.d.ts.map +1 -0
  46. package/dest/prover-publisher-factory.js +28 -0
  47. package/dest/test/index.d.ts +5 -3
  48. package/dest/test/index.d.ts.map +1 -1
  49. package/dest/test/index.js +1 -3
  50. package/package.json +40 -33
  51. package/src/actions/download-epoch-proving-job.ts +44 -0
  52. package/src/actions/index.ts +2 -0
  53. package/src/actions/rerun-epoch-proving-job.ts +74 -0
  54. package/src/actions/upload-epoch-proof-failure.ts +88 -0
  55. package/src/bin/run-failed-epoch.ts +79 -0
  56. package/src/config.ts +123 -45
  57. package/src/factory.ts +163 -67
  58. package/src/index.ts +2 -1
  59. package/src/job/epoch-proving-job-data.ts +82 -0
  60. package/src/job/epoch-proving-job.ts +364 -88
  61. package/src/metrics.ts +188 -70
  62. package/src/monitors/epoch-monitor.ts +23 -19
  63. package/src/prover-node-publisher.ts +368 -125
  64. package/src/prover-node.ts +292 -166
  65. package/src/prover-publisher-factory.ts +45 -0
  66. package/src/test/index.ts +7 -4
  67. package/dest/http.d.ts +0 -8
  68. package/dest/http.d.ts.map +0 -1
  69. package/dest/http.js +0 -9
  70. package/dest/prover-coordination/config.d.ts +0 -7
  71. package/dest/prover-coordination/config.d.ts.map +0 -1
  72. package/dest/prover-coordination/config.js +0 -11
  73. package/dest/prover-coordination/factory.d.ts +0 -22
  74. package/dest/prover-coordination/factory.d.ts.map +0 -1
  75. package/dest/prover-coordination/factory.js +0 -42
  76. package/dest/prover-coordination/index.d.ts +0 -3
  77. package/dest/prover-coordination/index.d.ts.map +0 -1
  78. package/dest/prover-coordination/index.js +0 -2
  79. package/src/http.ts +0 -13
  80. package/src/prover-coordination/config.ts +0 -17
  81. package/src/prover-coordination/factory.ts +0 -72
  82. package/src/prover-coordination/index.ts +0 -2
@@ -1,48 +1,47 @@
1
- import { AGGREGATION_OBJECT_LENGTH, AZTEC_MAX_EPOCH_DURATION } from '@aztec/constants';
2
- import type { L1TxUtils, RollupContract } from '@aztec/ethereum';
1
+ import { BatchedBlob, getEthBlobEvaluationInputs } from '@aztec/blob-lib';
2
+ import { MAX_CHECKPOINTS_PER_EPOCH } from '@aztec/constants';
3
+ import type { RollupContract, ViemCommitteeAttestation } from '@aztec/ethereum/contracts';
4
+ import type { L1TxUtils } from '@aztec/ethereum/l1-tx-utils';
3
5
  import { makeTuple } from '@aztec/foundation/array';
4
- import { areArraysEqual, times } from '@aztec/foundation/collection';
6
+ import { CheckpointNumber, EpochNumber } from '@aztec/foundation/branded-types';
7
+ import { areArraysEqual } from '@aztec/foundation/collection';
8
+ import { Fr } from '@aztec/foundation/curves/bn254';
5
9
  import { EthAddress } from '@aztec/foundation/eth-address';
6
- import { Fr } from '@aztec/foundation/fields';
7
- import { createLogger } from '@aztec/foundation/log';
8
- import { type Tuple, serializeToBuffer } from '@aztec/foundation/serialize';
9
- import { InterruptibleSleep } from '@aztec/foundation/sleep';
10
+ import { type Logger, type LoggerBindings, createLogger } from '@aztec/foundation/log';
11
+ import { retryUntil } from '@aztec/foundation/retry';
12
+ import type { Tuple } from '@aztec/foundation/serialize';
10
13
  import { Timer } from '@aztec/foundation/timer';
11
14
  import { RollupAbi } from '@aztec/l1-artifacts';
12
15
  import type { PublisherConfig, TxSenderConfig } from '@aztec/sequencer-client';
16
+ import { CommitteeAttestation, CommitteeAttestationsAndSigners } from '@aztec/stdlib/block';
17
+ import { getProofSubmissionDeadlineTimestamp } from '@aztec/stdlib/epoch-helpers';
13
18
  import type { Proof } from '@aztec/stdlib/proofs';
14
19
  import type { FeeRecipient, RootRollupPublicInputs } from '@aztec/stdlib/rollup';
15
20
  import type { L1PublishProofStats } from '@aztec/stdlib/stats';
16
21
  import { type TelemetryClient, getTelemetryClient } from '@aztec/telemetry-client';
17
22
 
18
- import { type Hex, type TransactionReceipt, encodeFunctionData } from 'viem';
23
+ import { inspect } from 'util';
24
+ import { type Hex, type TransactionReceipt, encodeFunctionData, formatEther, formatGwei } from 'viem';
19
25
 
20
- import { ProverNodeMetrics } from './metrics.js';
26
+ import { type EstimatedSubmitProofStats, ProverNodePublisherMetrics } from './metrics.js';
21
27
 
22
- /**
23
- * Stats for a sent transaction.
24
- */
25
28
  /** Arguments to the submitEpochProof method of the rollup contract */
26
29
  export type L1SubmitEpochProofArgs = {
27
30
  epochSize: number;
28
31
  previousArchive: Fr;
29
32
  endArchive: Fr;
30
- previousBlockHash: Fr;
31
- endBlockHash: Fr;
32
33
  endTimestamp: Fr;
33
34
  outHash: Fr;
34
35
  proverId: Fr;
35
- fees: Tuple<FeeRecipient, typeof AZTEC_MAX_EPOCH_DURATION>;
36
+ fees: Tuple<FeeRecipient, typeof MAX_CHECKPOINTS_PER_EPOCH>;
36
37
  proof: Proof;
37
38
  };
38
39
 
39
40
  export class ProverNodePublisher {
40
- private interruptibleSleep = new InterruptibleSleep();
41
- private sleepTimeMs: number;
42
41
  private interrupted = false;
43
- private metrics: ProverNodeMetrics;
42
+ private metrics: ProverNodePublisherMetrics;
44
43
 
45
- protected log = createLogger('prover-node:l1-tx-publisher');
44
+ protected log: Logger;
46
45
 
47
46
  protected rollupContract: RollupContract;
48
47
 
@@ -55,17 +54,21 @@ export class ProverNodePublisher {
55
54
  l1TxUtils: L1TxUtils;
56
55
  telemetry?: TelemetryClient;
57
56
  },
57
+ bindings?: LoggerBindings,
58
58
  ) {
59
- this.sleepTimeMs = config?.l1PublishRetryIntervalMS ?? 60_000;
60
-
61
59
  const telemetry = deps.telemetry ?? getTelemetryClient();
62
60
 
63
- this.metrics = new ProverNodeMetrics(telemetry, 'ProverNode');
61
+ this.metrics = new ProverNodePublisherMetrics(telemetry, 'ProverNode');
62
+ this.log = createLogger('prover-node:l1-tx-publisher', bindings);
64
63
 
65
64
  this.rollupContract = deps.rollupContract;
66
65
  this.l1TxUtils = deps.l1TxUtils;
67
66
  }
68
67
 
68
+ public getRollupContract() {
69
+ return this.rollupContract;
70
+ }
71
+
69
72
  /**
70
73
  * Calling `interrupt` will cause any in progress call to `publishRollup` to return `false` asap.
71
74
  * Be warned, the call may return false even if the tx subsequently gets successfully mined.
@@ -74,46 +77,58 @@ export class ProverNodePublisher {
74
77
  */
75
78
  public interrupt() {
76
79
  this.interrupted = true;
77
- this.interruptibleSleep.interrupt();
80
+ this.l1TxUtils.interrupt();
78
81
  }
79
82
 
80
83
  /** Restarts the publisher after calling `interrupt`. */
81
84
  public restart() {
82
85
  this.interrupted = false;
86
+ this.l1TxUtils.restart();
83
87
  }
84
88
 
85
89
  public getSenderAddress() {
86
- return EthAddress.fromString(this.l1TxUtils.getSenderAddress());
90
+ return this.l1TxUtils.getSenderAddress();
87
91
  }
88
92
 
89
93
  public async submitEpochProof(args: {
90
- epochNumber: number;
91
- fromBlock: number;
92
- toBlock: number;
94
+ epochNumber: EpochNumber;
95
+ fromCheckpoint: CheckpointNumber;
96
+ toCheckpoint: CheckpointNumber;
93
97
  publicInputs: RootRollupPublicInputs;
94
98
  proof: Proof;
99
+ batchedBlobInputs: BatchedBlob;
100
+ attestations: ViemCommitteeAttestation[];
95
101
  }): Promise<boolean> {
96
- const { epochNumber, fromBlock, toBlock } = args;
97
- const ctx = { epochNumber, fromBlock, toBlock };
102
+ const { epochNumber, fromCheckpoint, toCheckpoint } = args;
103
+ const ctx = { epochNumber, fromCheckpoint, toCheckpoint };
104
+
98
105
  if (!this.interrupted) {
99
- const timer = new Timer();
106
+ if (!(await this.waitUntilStartBuildsOnProven(args))) {
107
+ this.log.verbose('Checkpoint data syncing interrupted', ctx);
108
+ return false;
109
+ }
100
110
 
111
+ const timer = new Timer();
101
112
  // Validate epoch proof range and hashes are correct before submitting
102
113
  await this.validateEpochProofSubmission(args);
103
114
 
104
115
  const txReceipt = await this.sendSubmitEpochProofTx(args);
105
116
  if (!txReceipt) {
117
+ this.log.error(`Failed to mine submitEpochProof tx`, undefined, ctx);
106
118
  return false;
107
119
  }
108
120
 
109
121
  try {
110
- this.metrics.recordSenderBalance(await this.l1TxUtils.getSenderBalance(), this.l1TxUtils.getSenderAddress());
122
+ this.metrics.recordSenderBalance(
123
+ await this.l1TxUtils.getSenderBalance(),
124
+ this.l1TxUtils.getSenderAddress().toString(),
125
+ );
111
126
  } catch (err) {
112
127
  this.log.warn(`Failed to record the ETH balance of the prover node: ${err}`);
113
128
  }
114
129
 
115
130
  // Tx was mined successfully
116
- if (txReceipt.status) {
131
+ if (txReceipt.status === 'success') {
117
132
  const tx = await this.l1TxUtils.getTransactionStats(txReceipt.transactionHash);
118
133
  const stats: L1PublishProofStats = {
119
134
  gasPrice: txReceipt.effectiveGasPrice,
@@ -132,155 +147,383 @@ export class ProverNodePublisher {
132
147
  }
133
148
 
134
149
  this.metrics.recordFailedTx();
135
- this.log.error(`Rollup.submitEpochProof tx status failed: ${txReceipt.transactionHash}`, ctx);
136
- await this.sleepOrInterrupted();
150
+ this.log.error(`Rollup submitEpochProof tx reverted ${txReceipt.transactionHash}`, undefined, ctx);
137
151
  }
138
152
 
139
- this.log.verbose('L2 block data syncing interrupted while processing blocks.', ctx);
153
+ this.log.verbose('Checkpoint data syncing interrupted', ctx);
140
154
  return false;
141
155
  }
142
156
 
157
+ private async waitUntilStartBuildsOnProven(args: { epochNumber: EpochNumber; fromCheckpoint: CheckpointNumber }) {
158
+ const { epochNumber, fromCheckpoint } = args;
159
+ const provenCheckpoint = await this.getProvenCheckpoint();
160
+ if (this.isStartBuildingOnProven(fromCheckpoint, provenCheckpoint)) {
161
+ return true;
162
+ }
163
+
164
+ const timeout = await this.getSecondsUntilProofSubmissionWindowEnd(epochNumber);
165
+ this.log.info(`Waiting for proven checkpoint to reach proof start`, {
166
+ epochNumber,
167
+ fromCheckpoint,
168
+ provenCheckpoint,
169
+ timeout,
170
+ });
171
+
172
+ await retryUntil(
173
+ async () => {
174
+ if (this.interrupted) {
175
+ return true;
176
+ }
177
+
178
+ const proven = await this.getProvenCheckpoint();
179
+ this.log.verbose(`Proven checkpoint is at ${proven} (waiting for ${fromCheckpoint - 1})`, { epochNumber });
180
+ return this.isStartBuildingOnProven(fromCheckpoint, proven) ? true : undefined;
181
+ },
182
+ `proven checkpoint to reach ${fromCheckpoint - 1}`,
183
+ timeout,
184
+ 4,
185
+ );
186
+
187
+ return !this.interrupted;
188
+ }
189
+
190
+ private async getProvenCheckpoint() {
191
+ return (await this.rollupContract.getTips()).proven;
192
+ }
193
+
194
+ private isStartBuildingOnProven(fromCheckpoint: CheckpointNumber, provenCheckpoint: CheckpointNumber) {
195
+ return fromCheckpoint - 1 <= provenCheckpoint;
196
+ }
197
+
198
+ private async getSecondsUntilProofSubmissionWindowEnd(epochNumber: EpochNumber) {
199
+ const deadline = getProofSubmissionDeadlineTimestamp(epochNumber, await this.rollupContract.getRollupConstants());
200
+ const now = BigInt(Math.floor(Date.now() / 1000));
201
+ return Math.max(Number(deadline - now), 0.001);
202
+ }
203
+
143
204
  private async validateEpochProofSubmission(args: {
144
- fromBlock: number;
145
- toBlock: number;
205
+ fromCheckpoint: CheckpointNumber;
206
+ toCheckpoint: CheckpointNumber;
146
207
  publicInputs: RootRollupPublicInputs;
147
208
  proof: Proof;
209
+ batchedBlobInputs: BatchedBlob;
210
+ attestations: ViemCommitteeAttestation[];
148
211
  }) {
149
- const { fromBlock, toBlock, publicInputs, proof } = args;
212
+ const { fromCheckpoint, toCheckpoint, publicInputs, batchedBlobInputs } = args;
150
213
 
151
- // Check that the block numbers match the expected epoch to be proven
152
- const { pendingBlockNumber: pending, provenBlockNumber: proven } = await this.rollupContract.getTips();
153
- if (proven !== BigInt(fromBlock) - 1n) {
154
- throw new Error(`Cannot submit epoch proof for ${fromBlock}-${toBlock} as proven block is ${proven}`);
214
+ // Check that the checkpoint numbers match the expected epoch to be proven
215
+ const { pending, proven } = await this.rollupContract.getTips();
216
+ // Don't publish if proven is beyond our toCheckpoint, pointless to do so
217
+ if (proven > toCheckpoint) {
218
+ throw new Error(
219
+ `Cannot submit epoch proof for ${fromCheckpoint}-${toCheckpoint} as proven checkpoint is ${proven}`,
220
+ );
155
221
  }
156
- if (toBlock > pending) {
157
- throw new Error(`Cannot submit epoch proof for ${fromBlock}-${toBlock} as pending block is ${pending}`);
222
+ // toCheckpoint can't be greater than pending
223
+ if (toCheckpoint > pending) {
224
+ throw new Error(
225
+ `Cannot submit epoch proof for ${fromCheckpoint}-${toCheckpoint} as proposed checkpoint is ${pending}`,
226
+ );
158
227
  }
159
228
 
160
- // Check the block hash and archive for the immediate block before the epoch
161
- const blockLog = await this.rollupContract.getBlock(proven);
162
- if (publicInputs.previousArchive.root.toString() !== blockLog.archive) {
229
+ // Check the archive for the immediate checkpoint before the epoch
230
+ const checkpointLog = await this.rollupContract.getCheckpoint(CheckpointNumber(fromCheckpoint - 1));
231
+ if (!publicInputs.previousArchiveRoot.equals(checkpointLog.archive)) {
163
232
  throw new Error(
164
- `Previous archive root mismatch: ${publicInputs.previousArchive.root.toString()} !== ${blockLog.archive}`,
233
+ `Previous archive root mismatch: ${publicInputs.previousArchiveRoot.toString()} !== ${checkpointLog.archive.toString()}`,
165
234
  );
166
235
  }
167
- // TODO: Remove zero check once we inject the proper zero blockhash
168
- if (blockLog.blockHash !== Fr.ZERO.toString() && publicInputs.previousBlockHash.toString() !== blockLog.blockHash) {
236
+
237
+ // Check the archive for the last checkpoint in the epoch
238
+ const endCheckpointLog = await this.rollupContract.getCheckpoint(toCheckpoint);
239
+ if (!publicInputs.endArchiveRoot.equals(endCheckpointLog.archive)) {
169
240
  throw new Error(
170
- `Previous block hash mismatch: ${publicInputs.previousBlockHash.toString()} !== ${blockLog.blockHash}`,
241
+ `End archive root mismatch: ${publicInputs.endArchiveRoot.toString()} !== ${endCheckpointLog.archive.toString()}`,
171
242
  );
172
243
  }
173
244
 
174
- // Check the block hash and archive for the last block in the epoch
175
- const endBlockLog = await this.rollupContract.getBlock(BigInt(toBlock));
176
- if (publicInputs.endArchive.root.toString() !== endBlockLog.archive) {
245
+ // Check the batched blob inputs from the root rollup against the batched blob computed in ts
246
+ const finalBlobAccumulator = batchedBlobInputs.toFinalBlobAccumulator();
247
+ if (!publicInputs.blobPublicInputs.equals(finalBlobAccumulator)) {
177
248
  throw new Error(
178
- `End archive root mismatch: ${publicInputs.endArchive.root.toString()} !== ${endBlockLog.archive}`,
249
+ `Batched blob mismatch: ${inspect(publicInputs.blobPublicInputs)} !== ${inspect(finalBlobAccumulator)}`,
179
250
  );
180
251
  }
181
- if (publicInputs.endBlockHash.toString() !== endBlockLog.blockHash) {
182
- throw new Error(`End block hash mismatch: ${publicInputs.endBlockHash.toString()} !== ${endBlockLog.blockHash}`);
183
- }
184
252
 
185
253
  // Compare the public inputs computed by the contract with the ones injected
186
- const rollupPublicInputs = await this.rollupContract.getEpochProofPublicInputs(this.getSubmitEpochProofArgs(args));
187
- const aggregationObject = proof.isEmpty()
188
- ? times(AGGREGATION_OBJECT_LENGTH, Fr.zero)
189
- : proof.extractAggregationObject();
190
- const argsPublicInputs = [...publicInputs.toFields(), ...aggregationObject];
191
-
192
- if (!areArraysEqual(rollupPublicInputs.map(Fr.fromHexString), argsPublicInputs, (a, b) => a.equals(b))) {
193
- const fmt = (inputs: Fr[] | readonly string[]) => inputs.map(x => x.toString()).join(', ');
194
- throw new Error(
195
- `Root rollup public inputs mismatch:\nRollup: ${fmt(rollupPublicInputs)}\nComputed:${fmt(argsPublicInputs)}`,
196
- );
254
+ const rollupPublicInputs = await this.rollupContract.getEpochProofPublicInputs(
255
+ this.getEpochProofPublicInputsArgs(args),
256
+ );
257
+ const argsPublicInputs = [...publicInputs.toFields()];
258
+
259
+ if (!areArraysEqual(rollupPublicInputs, argsPublicInputs, (a, b) => a.equals(b))) {
260
+ throw await reportPublicInputsMismatch({
261
+ rollupPublicInputs,
262
+ argsPublicInputs,
263
+ fromCheckpoint,
264
+ toCheckpoint,
265
+ rollupContract: this.rollupContract,
266
+ log: this.log,
267
+ });
197
268
  }
198
269
  }
199
270
 
271
+ /**
272
+ * Estimates what submitting the epoch proof would have cost on L1 without actually sending it.
273
+ * Runs the same validation as `submitEpochProof`, encodes the calldata, estimates gas, and records metrics.
274
+ * Used when proof publishing is disabled (e.g. PROVER_NODE_DISABLE_PROOF_PUBLISH=true on mainnet).
275
+ */
276
+ public async analyzeEpochProofSubmission(args: {
277
+ epochNumber: EpochNumber;
278
+ fromCheckpoint: CheckpointNumber;
279
+ toCheckpoint: CheckpointNumber;
280
+ publicInputs: RootRollupPublicInputs;
281
+ proof: Proof;
282
+ batchedBlobInputs: BatchedBlob;
283
+ attestations: ViemCommitteeAttestation[];
284
+ }): Promise<void> {
285
+ const { epochNumber, fromCheckpoint, toCheckpoint } = args;
286
+
287
+ await this.validateEpochProofSubmission(args);
288
+
289
+ const data = this.encodeSubmitEpochProofCalldata(args);
290
+ const senderAddress = this.l1TxUtils.getSenderAddress();
291
+
292
+ const [gasLimit, gasPrice, latestBlock] = await Promise.all([
293
+ this.l1TxUtils.estimateGas(senderAddress.toString() as `0x${string}`, { to: this.rollupContract.address, data }),
294
+ this.l1TxUtils.getGasPrice(),
295
+ this.l1TxUtils.client.getBlock({ blockTag: 'latest' }),
296
+ ]);
297
+
298
+ const baseFeePerGas = latestBlock.baseFeePerGas ?? 0n;
299
+ const { maxPriorityFeePerGas } = gasPrice;
300
+
301
+ const effectiveFeePerGas = baseFeePerGas + maxPriorityFeePerGas;
302
+ const estimatedTotalFee = gasLimit * effectiveFeePerGas;
303
+
304
+ const stats: EstimatedSubmitProofStats = {
305
+ gasLimit,
306
+ baseFeePerGas,
307
+ maxPriorityFeePerGas,
308
+ estimatedTotalFee,
309
+ };
310
+
311
+ this.log.info(`Estimated epoch proof submission cost (not submitted)`, {
312
+ epochNumber,
313
+ fromCheckpoint,
314
+ toCheckpoint,
315
+ gasLimit: gasLimit.toString(),
316
+ baseFeePerGas: formatGwei(baseFeePerGas),
317
+ maxPriorityFeePerGas: formatGwei(maxPriorityFeePerGas),
318
+ estimatedTotalFeeEth: formatEther(estimatedTotalFee),
319
+ });
320
+
321
+ this.metrics.recordEstimatedSubmitProof(stats);
322
+ }
323
+
324
+ private encodeSubmitEpochProofCalldata(args: {
325
+ fromCheckpoint: CheckpointNumber;
326
+ toCheckpoint: CheckpointNumber;
327
+ publicInputs: RootRollupPublicInputs;
328
+ proof: Proof;
329
+ batchedBlobInputs: BatchedBlob;
330
+ attestations: ViemCommitteeAttestation[];
331
+ }): Hex {
332
+ return encodeFunctionData({
333
+ abi: RollupAbi,
334
+ functionName: 'submitEpochRootProof',
335
+ args: [this.getSubmitEpochProofArgs(args)],
336
+ });
337
+ }
338
+
200
339
  private async sendSubmitEpochProofTx(args: {
201
- fromBlock: number;
202
- toBlock: number;
340
+ fromCheckpoint: CheckpointNumber;
341
+ toCheckpoint: CheckpointNumber;
203
342
  publicInputs: RootRollupPublicInputs;
204
343
  proof: Proof;
344
+ batchedBlobInputs: BatchedBlob;
345
+ attestations: ViemCommitteeAttestation[];
205
346
  }): Promise<TransactionReceipt | undefined> {
206
- const proofHex: Hex = `0x${args.proof.withoutPublicInputs().toString('hex')}`;
207
- const argsArray = this.getSubmitEpochProofArgs(args);
208
-
209
- const txArgs = [
210
- {
211
- start: argsArray[0],
212
- end: argsArray[1],
213
- args: argsArray[2],
214
- fees: argsArray[3],
215
- blobPublicInputs: argsArray[4],
216
- aggregationObject: argsArray[5],
217
- proof: proofHex,
218
- },
219
- ] as const;
347
+ const txArgs = [this.getSubmitEpochProofArgs(args)] as const;
220
348
 
221
- this.log.info(`SubmitEpochProof proofSize=${args.proof.withoutPublicInputs().length} bytes`);
349
+ this.log.info(`Submitting epoch proof to L1 rollup contract`, {
350
+ proofSize: args.proof.withoutPublicInputs().length,
351
+ fromCheckpoint: args.fromCheckpoint,
352
+ toCheckpoint: args.toCheckpoint,
353
+ });
222
354
  const data = encodeFunctionData({
223
355
  abi: RollupAbi,
224
356
  functionName: 'submitEpochRootProof',
225
357
  args: txArgs,
226
358
  });
227
359
  try {
228
- const { receipt } = await this.l1TxUtils.sendAndMonitorTransaction({
229
- to: this.rollupContract.address,
230
- data,
231
- });
232
-
360
+ const { receipt } = await this.l1TxUtils.sendAndMonitorTransaction({ to: this.rollupContract.address, data });
361
+ if (receipt.status !== 'success') {
362
+ const errorMsg = await this.l1TxUtils.tryGetErrorFromRevertedTx(
363
+ data,
364
+ {
365
+ args: [...txArgs],
366
+ functionName: 'submitEpochRootProof',
367
+ abi: RollupAbi,
368
+ address: this.rollupContract.address,
369
+ },
370
+ /*blobInputs*/ undefined,
371
+ /*stateOverride*/ [],
372
+ );
373
+ this.log.error(`Rollup submit epoch proof tx reverted with ${errorMsg ?? 'unknown error'}`);
374
+ return undefined;
375
+ }
233
376
  return receipt;
234
377
  } catch (err) {
235
378
  this.log.error(`Rollup submit epoch proof failed`, err);
236
- const errorMsg = await this.l1TxUtils.tryGetErrorFromRevertedTx(
237
- data,
238
- {
239
- args: [...txArgs],
240
- functionName: 'submitEpochRootProof',
241
- abi: RollupAbi,
242
- address: this.rollupContract.address,
243
- },
244
- /*blobInputs*/ undefined,
245
- /*stateOverride*/ [],
246
- );
247
- this.log.error(`Rollup submit epoch proof tx reverted. ${errorMsg}`);
248
379
  return undefined;
249
380
  }
250
381
  }
251
382
 
252
- private getSubmitEpochProofArgs(args: {
253
- fromBlock: number;
254
- toBlock: number;
383
+ private getEpochProofPublicInputsArgs(args: {
384
+ fromCheckpoint: CheckpointNumber;
385
+ toCheckpoint: CheckpointNumber;
255
386
  publicInputs: RootRollupPublicInputs;
256
- proof: Proof;
387
+ batchedBlobInputs: BatchedBlob;
388
+ attestations: ViemCommitteeAttestation[];
257
389
  }) {
390
+ // Returns arguments for EpochProofLib.sol -> getEpochProofPublicInputs()
258
391
  return [
259
- BigInt(args.fromBlock),
260
- BigInt(args.toBlock),
392
+ BigInt(args.fromCheckpoint) /*_start*/,
393
+ BigInt(args.toCheckpoint) /*_end*/,
261
394
  {
262
- previousArchive: args.publicInputs.previousArchive.root.toString(),
263
- endArchive: args.publicInputs.endArchive.root.toString(),
264
- previousBlockHash: args.publicInputs.previousBlockHash.toString(),
265
- endBlockHash: args.publicInputs.endBlockHash.toString(),
266
- endTimestamp: args.publicInputs.endTimestamp.toBigInt(),
395
+ previousArchive: args.publicInputs.previousArchiveRoot.toString(),
396
+ endArchive: args.publicInputs.endArchiveRoot.toString(),
267
397
  outHash: args.publicInputs.outHash.toString(),
268
- proverId: EthAddress.fromField(args.publicInputs.proverId).toString(),
269
- },
270
- makeTuple(AZTEC_MAX_EPOCH_DURATION * 2, i =>
398
+ proverId: EthAddress.fromField(args.publicInputs.constants.proverId).toString(),
399
+ } /*_args*/,
400
+ makeTuple(MAX_CHECKPOINTS_PER_EPOCH * 2, i =>
271
401
  i % 2 === 0
272
402
  ? args.publicInputs.fees[i / 2].recipient.toField().toString()
273
403
  : args.publicInputs.fees[(i - 1) / 2].value.toString(),
274
- ),
275
- `0x${args.publicInputs.blobPublicInputs
276
- .filter((_, i) => i < args.toBlock - args.fromBlock + 1)
277
- .map(b => b.toString())
278
- .join(``)}`,
279
- `0x${serializeToBuffer(args.proof.extractAggregationObject()).toString('hex')}`,
404
+ ) /*_fees*/,
405
+ getEthBlobEvaluationInputs(args.batchedBlobInputs) /*_blobPublicInputs*/,
280
406
  ] as const;
281
407
  }
282
408
 
283
- protected async sleepOrInterrupted() {
284
- await this.interruptibleSleep.sleep(this.sleepTimeMs);
409
+ private getSubmitEpochProofArgs(args: {
410
+ fromCheckpoint: CheckpointNumber;
411
+ toCheckpoint: CheckpointNumber;
412
+ publicInputs: RootRollupPublicInputs;
413
+ proof: Proof;
414
+ batchedBlobInputs: BatchedBlob;
415
+ attestations: ViemCommitteeAttestation[];
416
+ }) {
417
+ // Returns arguments for EpochProofLib.sol -> submitEpochRootProof()
418
+ const proofHex: Hex = `0x${args.proof.withoutPublicInputs().toString('hex')}`;
419
+ const argsArray = this.getEpochProofPublicInputsArgs(args);
420
+ return {
421
+ start: argsArray[0],
422
+ end: argsArray[1],
423
+ args: argsArray[2],
424
+ fees: argsArray[3],
425
+ attestations: CommitteeAttestationsAndSigners.packAttestations(
426
+ args.attestations.map(a => CommitteeAttestation.fromViem(a)),
427
+ ),
428
+ blobInputs: argsArray[4],
429
+ proof: proofHex,
430
+ };
285
431
  }
286
432
  }
433
+
434
+ /**
435
+ * Decodes a `Root rollup public inputs mismatch`, fetches the on-chain CheckpointLog for any
436
+ * mismatching `checkpointHeaderHashes[i]`, emits a structured error log, and returns a thrown-ready
437
+ * Error with a human-readable summary.
438
+ *
439
+ * Layout of `RootRollupPublicInputs.toFields()`:
440
+ * [0] previousArchiveRoot
441
+ * [1] endArchiveRoot
442
+ * [2] outHash
443
+ * [3 .. 3+N-1] checkpointHeaderHashes[i] for i in 0..N-1 (N = MAX_CHECKPOINTS_PER_EPOCH)
444
+ * [3+N .. 3+3N-1] fees[i] = (recipient, value) for i in 0..N-1
445
+ * [3+3N .. 3+3N+4] EpochConstantData (chainId, version, vkTreeRoot, protocolContractsHash, proverId)
446
+ * [3+3N+5 ..] blobPublicInputs (FinalBlobAccumulator)
447
+ */
448
+ async function reportPublicInputsMismatch(input: {
449
+ rollupPublicInputs: readonly Fr[];
450
+ argsPublicInputs: readonly Fr[];
451
+ fromCheckpoint: CheckpointNumber;
452
+ toCheckpoint: CheckpointNumber;
453
+ rollupContract: RollupContract;
454
+ log: Logger;
455
+ }): Promise<Error> {
456
+ const { rollupPublicInputs, argsPublicInputs, fromCheckpoint, toCheckpoint, rollupContract, log } = input;
457
+ const N = MAX_CHECKPOINTS_PER_EPOCH;
458
+ const constantsStart = 3 + 3 * N;
459
+ const blobStart = constantsStart + 5;
460
+ const constantLabels = ['chainId', 'version', 'vkTreeRoot', 'protocolContractsHash', 'proverId'];
461
+
462
+ const diffs: { index: number; label: string; rollup: Fr; computed: Fr; checkpointIndex?: number }[] = [];
463
+ const len = Math.max(rollupPublicInputs.length, argsPublicInputs.length);
464
+ for (let i = 0; i < len; i++) {
465
+ const a = rollupPublicInputs[i] ?? Fr.ZERO;
466
+ const b = argsPublicInputs[i] ?? Fr.ZERO;
467
+ if (a.equals(b)) {
468
+ continue;
469
+ }
470
+ let label: string;
471
+ let checkpointIndex: number | undefined;
472
+ if (i === 0) {
473
+ label = 'previousArchiveRoot';
474
+ } else if (i === 1) {
475
+ label = 'endArchiveRoot';
476
+ } else if (i === 2) {
477
+ label = 'outHash';
478
+ } else if (i < 3 + N) {
479
+ checkpointIndex = i - 3;
480
+ label = `checkpointHeaderHashes[${checkpointIndex}]`;
481
+ } else if (i < 3 + 3 * N) {
482
+ const feePairIndex = i - (3 + N);
483
+ const feeIndex = Math.floor(feePairIndex / 2);
484
+ const sub = feePairIndex % 2 === 0 ? 'recipient' : 'value';
485
+ label = `fees[${feeIndex}].${sub}`;
486
+ } else if (i < blobStart) {
487
+ label = `constants.${constantLabels[i - constantsStart]}`;
488
+ } else {
489
+ label = `blobPublicInputs[${i - blobStart}]`;
490
+ }
491
+ diffs.push({ index: i, label, rollup: a, computed: b, checkpointIndex });
492
+ }
493
+
494
+ // For each mismatching checkpointHeaderHash, fetch the L1 CheckpointLog so the operator can
495
+ // see what was published on-chain alongside the prover's recomputed hash.
496
+ const onChainCheckpoints = await Promise.all(
497
+ diffs
498
+ .filter(d => d.checkpointIndex !== undefined)
499
+ .map(async d => {
500
+ const checkpointNumber = CheckpointNumber(fromCheckpoint + d.checkpointIndex!);
501
+ try {
502
+ const cp = await rollupContract.getCheckpoint(checkpointNumber);
503
+ return { checkpointIndex: d.checkpointIndex!, checkpointNumber, headerHash: cp.headerHash.toString() };
504
+ } catch (err) {
505
+ return { checkpointIndex: d.checkpointIndex!, checkpointNumber, error: (err as Error).message };
506
+ }
507
+ }),
508
+ );
509
+
510
+ log.error(`Root rollup public inputs mismatch`, undefined, {
511
+ fromCheckpoint,
512
+ toCheckpoint,
513
+ numDiffs: diffs.length,
514
+ diffs: diffs.map(d => ({
515
+ index: d.index,
516
+ label: d.label,
517
+ rollup: d.rollup.toString(),
518
+ computed: d.computed.toString(),
519
+ })),
520
+ onChainCheckpoints,
521
+ });
522
+
523
+ const fmt = (inputs: readonly Fr[]) => inputs.map(x => x.toString()).join(', ');
524
+ const summary = diffs.map(d => `[${d.index} ${d.label}] L1=${d.rollup} prover=${d.computed}`).join('\n');
525
+ return new Error(
526
+ `Root rollup public inputs mismatch (${diffs.length} fields differ):\n${summary}\n` +
527
+ `Rollup: ${fmt(rollupPublicInputs)}\nComputed:${fmt(argsPublicInputs)}`,
528
+ );
529
+ }