@aztec/prover-node 0.0.1-commit.d431d1c → 0.0.1-commit.d58ff9d0
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.
- package/README.md +518 -0
- package/dest/actions/download-epoch-proving-job.js +1 -1
- package/dest/actions/rerun-epoch-proving-job.d.ts +6 -4
- package/dest/actions/rerun-epoch-proving-job.d.ts.map +1 -1
- package/dest/actions/rerun-epoch-proving-job.js +191 -28
- package/dest/actions/upload-epoch-proof-failure.d.ts +2 -2
- package/dest/actions/upload-epoch-proof-failure.d.ts.map +1 -1
- package/dest/bin/run-failed-epoch.js +6 -5
- package/dest/checkpoint-store.d.ts +87 -0
- package/dest/checkpoint-store.d.ts.map +1 -0
- package/dest/checkpoint-store.js +186 -0
- package/dest/config.d.ts +7 -8
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +24 -20
- package/dest/factory.d.ts +22 -13
- package/dest/factory.d.ts.map +1 -1
- package/dest/factory.js +41 -69
- package/dest/index.d.ts +2 -1
- package/dest/index.d.ts.map +1 -1
- package/dest/index.js +1 -0
- package/dest/job/checkpoint-prover.d.ts +134 -0
- package/dest/job/checkpoint-prover.d.ts.map +1 -0
- package/dest/job/checkpoint-prover.js +353 -0
- package/dest/job/epoch-session.d.ts +146 -0
- package/dest/job/epoch-session.d.ts.map +1 -0
- package/dest/job/{epoch-proving-job.js → epoch-session.js} +277 -295
- package/dest/job/top-tree-job.d.ts +82 -0
- package/dest/job/top-tree-job.d.ts.map +1 -0
- package/dest/job/top-tree-job.js +152 -0
- package/dest/metrics.d.ts +40 -3
- package/dest/metrics.d.ts.map +1 -1
- package/dest/metrics.js +112 -7
- package/dest/monitors/epoch-monitor.d.ts +1 -1
- package/dest/monitors/epoch-monitor.d.ts.map +1 -1
- package/dest/monitors/epoch-monitor.js +11 -9
- package/dest/proof-publishing-service.d.ts +161 -0
- package/dest/proof-publishing-service.d.ts.map +1 -0
- package/dest/proof-publishing-service.js +335 -0
- package/dest/prover-node-publisher.d.ts +28 -17
- package/dest/prover-node-publisher.d.ts.map +1 -1
- package/dest/prover-node-publisher.js +205 -65
- package/dest/prover-node.d.ts +120 -70
- package/dest/prover-node.d.ts.map +1 -1
- package/dest/prover-node.js +491 -226
- package/dest/prover-publisher-factory.d.ts +9 -5
- package/dest/prover-publisher-factory.d.ts.map +1 -1
- package/dest/prover-publisher-factory.js +8 -5
- package/dest/session-manager.d.ts +158 -0
- package/dest/session-manager.d.ts.map +1 -0
- package/dest/session-manager.js +452 -0
- package/dest/test/index.d.ts +7 -6
- package/dest/test/index.d.ts.map +1 -1
- package/package.json +24 -22
- package/src/actions/download-epoch-proving-job.ts +1 -1
- package/src/actions/rerun-epoch-proving-job.ts +120 -30
- package/src/actions/upload-epoch-proof-failure.ts +1 -1
- package/src/bin/run-failed-epoch.ts +5 -3
- package/src/checkpoint-store.ts +218 -0
- package/src/config.ts +35 -32
- package/src/factory.ts +73 -112
- package/src/index.ts +1 -0
- package/src/job/checkpoint-prover.ts +468 -0
- package/src/job/epoch-session.ts +436 -0
- package/src/job/top-tree-job.ts +227 -0
- package/src/metrics.ts +129 -12
- package/src/monitors/epoch-monitor.ts +5 -6
- package/src/proof-publishing-service.ts +427 -0
- package/src/prover-node-publisher.ts +241 -81
- package/src/prover-node.ts +566 -255
- package/src/prover-publisher-factory.ts +19 -10
- package/src/session-manager.ts +552 -0
- package/src/test/index.ts +6 -6
- package/dest/job/epoch-proving-job.d.ts +0 -62
- package/dest/job/epoch-proving-job.d.ts.map +0 -1
- package/src/job/epoch-proving-job.ts +0 -427
|
@@ -1,27 +1,25 @@
|
|
|
1
1
|
import { BatchedBlob, getEthBlobEvaluationInputs } from '@aztec/blob-lib';
|
|
2
|
-
import {
|
|
2
|
+
import { MAX_CHECKPOINTS_PER_EPOCH } from '@aztec/constants';
|
|
3
3
|
import type { RollupContract, ViemCommitteeAttestation } from '@aztec/ethereum/contracts';
|
|
4
4
|
import type { L1TxUtils } from '@aztec/ethereum/l1-tx-utils';
|
|
5
|
-
import { makeTuple } from '@aztec/foundation/array';
|
|
6
5
|
import { CheckpointNumber, EpochNumber } from '@aztec/foundation/branded-types';
|
|
7
6
|
import { areArraysEqual } from '@aztec/foundation/collection';
|
|
8
7
|
import { Fr } from '@aztec/foundation/curves/bn254';
|
|
9
8
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
10
|
-
import { createLogger } from '@aztec/foundation/log';
|
|
11
|
-
import type { Tuple } from '@aztec/foundation/serialize';
|
|
9
|
+
import { type Logger, type LoggerBindings, createLogger } from '@aztec/foundation/log';
|
|
12
10
|
import { Timer } from '@aztec/foundation/timer';
|
|
13
11
|
import { RollupAbi } from '@aztec/l1-artifacts';
|
|
14
12
|
import type { PublisherConfig, TxSenderConfig } from '@aztec/sequencer-client';
|
|
15
13
|
import { CommitteeAttestation, CommitteeAttestationsAndSigners } from '@aztec/stdlib/block';
|
|
16
14
|
import type { Proof } from '@aztec/stdlib/proofs';
|
|
17
|
-
import type {
|
|
15
|
+
import type { CheckpointHeader, RootRollupPublicInputs } from '@aztec/stdlib/rollup';
|
|
18
16
|
import type { L1PublishProofStats } from '@aztec/stdlib/stats';
|
|
19
17
|
import { type TelemetryClient, getTelemetryClient } from '@aztec/telemetry-client';
|
|
20
18
|
|
|
21
19
|
import { inspect } from 'util';
|
|
22
|
-
import { type Hex, type TransactionReceipt, encodeFunctionData } from 'viem';
|
|
20
|
+
import { type Hex, type TransactionReceipt, encodeFunctionData, formatEther, formatGwei } from 'viem';
|
|
23
21
|
|
|
24
|
-
import { ProverNodePublisherMetrics } from './metrics.js';
|
|
22
|
+
import { type EstimatedSubmitProofStats, ProverNodePublisherMetrics } from './metrics.js';
|
|
25
23
|
|
|
26
24
|
/** Arguments to the submitEpochProof method of the rollup contract */
|
|
27
25
|
export type L1SubmitEpochProofArgs = {
|
|
@@ -31,18 +29,19 @@ export type L1SubmitEpochProofArgs = {
|
|
|
31
29
|
endTimestamp: Fr;
|
|
32
30
|
outHash: Fr;
|
|
33
31
|
proverId: Fr;
|
|
34
|
-
|
|
32
|
+
headers: CheckpointHeader[];
|
|
35
33
|
proof: Proof;
|
|
36
34
|
};
|
|
37
35
|
|
|
38
36
|
export class ProverNodePublisher {
|
|
39
|
-
private interrupted = false;
|
|
40
37
|
private metrics: ProverNodePublisherMetrics;
|
|
41
38
|
|
|
42
|
-
protected log
|
|
39
|
+
protected log: Logger;
|
|
43
40
|
|
|
44
41
|
protected rollupContract: RollupContract;
|
|
45
42
|
|
|
43
|
+
protected proofSubmissionTarget: Hex;
|
|
44
|
+
|
|
46
45
|
public readonly l1TxUtils: L1TxUtils;
|
|
47
46
|
|
|
48
47
|
constructor(
|
|
@@ -50,14 +49,18 @@ export class ProverNodePublisher {
|
|
|
50
49
|
deps: {
|
|
51
50
|
rollupContract: RollupContract;
|
|
52
51
|
l1TxUtils: L1TxUtils;
|
|
52
|
+
proofSubmissionTarget?: EthAddress;
|
|
53
53
|
telemetry?: TelemetryClient;
|
|
54
54
|
},
|
|
55
|
+
bindings?: LoggerBindings,
|
|
55
56
|
) {
|
|
56
57
|
const telemetry = deps.telemetry ?? getTelemetryClient();
|
|
57
58
|
|
|
58
59
|
this.metrics = new ProverNodePublisherMetrics(telemetry, 'ProverNode');
|
|
60
|
+
this.log = createLogger('prover-node:l1-tx-publisher', bindings);
|
|
59
61
|
|
|
60
62
|
this.rollupContract = deps.rollupContract;
|
|
63
|
+
this.proofSubmissionTarget = deps.proofSubmissionTarget?.toString() ?? deps.rollupContract.address;
|
|
61
64
|
this.l1TxUtils = deps.l1TxUtils;
|
|
62
65
|
}
|
|
63
66
|
|
|
@@ -65,23 +68,6 @@ export class ProverNodePublisher {
|
|
|
65
68
|
return this.rollupContract;
|
|
66
69
|
}
|
|
67
70
|
|
|
68
|
-
/**
|
|
69
|
-
* Calling `interrupt` will cause any in progress call to `publishRollup` to return `false` asap.
|
|
70
|
-
* Be warned, the call may return false even if the tx subsequently gets successfully mined.
|
|
71
|
-
* In practice this shouldn't matter, as we'll only ever be calling `interrupt` when we know it's going to fail.
|
|
72
|
-
* A call to `restart` is required before you can continue publishing.
|
|
73
|
-
*/
|
|
74
|
-
public interrupt() {
|
|
75
|
-
this.interrupted = true;
|
|
76
|
-
this.l1TxUtils.interrupt();
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/** Restarts the publisher after calling `interrupt`. */
|
|
80
|
-
public restart() {
|
|
81
|
-
this.interrupted = false;
|
|
82
|
-
this.l1TxUtils.restart();
|
|
83
|
-
}
|
|
84
|
-
|
|
85
71
|
public getSenderAddress() {
|
|
86
72
|
return this.l1TxUtils.getSenderAddress();
|
|
87
73
|
}
|
|
@@ -94,54 +80,53 @@ export class ProverNodePublisher {
|
|
|
94
80
|
proof: Proof;
|
|
95
81
|
batchedBlobInputs: BatchedBlob;
|
|
96
82
|
attestations: ViemCommitteeAttestation[];
|
|
83
|
+
headers: CheckpointHeader[];
|
|
84
|
+
/** Wall-clock deadline (proof-submission window end) past which the L1 tx should stop retrying. */
|
|
85
|
+
deadline?: Date;
|
|
97
86
|
}): Promise<boolean> {
|
|
98
87
|
const { epochNumber, fromCheckpoint, toCheckpoint } = args;
|
|
99
88
|
const ctx = { epochNumber, fromCheckpoint, toCheckpoint };
|
|
100
89
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
await this.validateEpochProofSubmission(args);
|
|
90
|
+
const timer = new Timer();
|
|
91
|
+
// Validate epoch proof range and hashes are correct before submitting
|
|
92
|
+
await this.validateEpochProofSubmission(args);
|
|
105
93
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
try {
|
|
113
|
-
this.metrics.recordSenderBalance(
|
|
114
|
-
await this.l1TxUtils.getSenderBalance(),
|
|
115
|
-
this.l1TxUtils.getSenderAddress().toString(),
|
|
116
|
-
);
|
|
117
|
-
} catch (err) {
|
|
118
|
-
this.log.warn(`Failed to record the ETH balance of the prover node: ${err}`);
|
|
119
|
-
}
|
|
94
|
+
const txReceipt = await this.sendSubmitEpochProofTx(args);
|
|
95
|
+
if (!txReceipt) {
|
|
96
|
+
this.log.error(`Failed to mine submitEpochProof tx`, undefined, ctx);
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
120
99
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
calldataSize: tx!.calldataSize,
|
|
130
|
-
sender: tx!.sender,
|
|
131
|
-
blobDataGas: 0n,
|
|
132
|
-
blobGasUsed: 0n,
|
|
133
|
-
eventName: 'proof-published-to-l1',
|
|
134
|
-
};
|
|
135
|
-
this.log.info(`Published epoch proof to L1 rollup contract`, { ...stats, ...ctx });
|
|
136
|
-
this.metrics.recordSubmitProof(timer.ms(), stats);
|
|
137
|
-
return true;
|
|
138
|
-
}
|
|
100
|
+
try {
|
|
101
|
+
this.metrics.recordSenderBalance(
|
|
102
|
+
await this.l1TxUtils.getSenderBalance(),
|
|
103
|
+
this.l1TxUtils.getSenderAddress().toString(),
|
|
104
|
+
);
|
|
105
|
+
} catch (err) {
|
|
106
|
+
this.log.warn(`Failed to record the ETH balance of the prover node: ${err}`);
|
|
107
|
+
}
|
|
139
108
|
|
|
140
|
-
|
|
141
|
-
|
|
109
|
+
// Tx was mined successfully
|
|
110
|
+
if (txReceipt.status === 'success') {
|
|
111
|
+
const tx = await this.l1TxUtils.getTransactionStats(txReceipt.transactionHash);
|
|
112
|
+
const stats: L1PublishProofStats = {
|
|
113
|
+
gasPrice: txReceipt.effectiveGasPrice,
|
|
114
|
+
gasUsed: txReceipt.gasUsed,
|
|
115
|
+
transactionHash: txReceipt.transactionHash,
|
|
116
|
+
calldataGas: tx!.calldataGas,
|
|
117
|
+
calldataSize: tx!.calldataSize,
|
|
118
|
+
sender: tx!.sender,
|
|
119
|
+
blobDataGas: 0n,
|
|
120
|
+
blobGasUsed: 0n,
|
|
121
|
+
eventName: 'proof-published-to-l1',
|
|
122
|
+
};
|
|
123
|
+
this.log.info(`Published epoch proof to L1 rollup contract`, { ...stats, ...ctx });
|
|
124
|
+
this.metrics.recordSubmitProof(timer.ms(), stats);
|
|
125
|
+
return true;
|
|
142
126
|
}
|
|
143
127
|
|
|
144
|
-
this.
|
|
128
|
+
this.metrics.recordFailedTx();
|
|
129
|
+
this.log.error(`Rollup submitEpochProof tx reverted ${txReceipt.transactionHash}`, undefined, ctx);
|
|
145
130
|
return false;
|
|
146
131
|
}
|
|
147
132
|
|
|
@@ -152,6 +137,7 @@ export class ProverNodePublisher {
|
|
|
152
137
|
proof: Proof;
|
|
153
138
|
batchedBlobInputs: BatchedBlob;
|
|
154
139
|
attestations: ViemCommitteeAttestation[];
|
|
140
|
+
headers: CheckpointHeader[];
|
|
155
141
|
}) {
|
|
156
142
|
const { fromCheckpoint, toCheckpoint, publicInputs, batchedBlobInputs } = args;
|
|
157
143
|
|
|
@@ -166,7 +152,7 @@ export class ProverNodePublisher {
|
|
|
166
152
|
// toCheckpoint can't be greater than pending
|
|
167
153
|
if (toCheckpoint > pending) {
|
|
168
154
|
throw new Error(
|
|
169
|
-
`Cannot submit epoch proof for ${fromCheckpoint}-${toCheckpoint} as
|
|
155
|
+
`Cannot submit epoch proof for ${fromCheckpoint}-${toCheckpoint} as proposed checkpoint is ${pending}`,
|
|
170
156
|
);
|
|
171
157
|
}
|
|
172
158
|
|
|
@@ -201,20 +187,96 @@ export class ProverNodePublisher {
|
|
|
201
187
|
const argsPublicInputs = [...publicInputs.toFields()];
|
|
202
188
|
|
|
203
189
|
if (!areArraysEqual(rollupPublicInputs, argsPublicInputs, (a, b) => a.equals(b))) {
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
190
|
+
throw await reportPublicInputsMismatch({
|
|
191
|
+
rollupPublicInputs,
|
|
192
|
+
argsPublicInputs,
|
|
193
|
+
fromCheckpoint,
|
|
194
|
+
toCheckpoint,
|
|
195
|
+
rollupContract: this.rollupContract,
|
|
196
|
+
log: this.log,
|
|
197
|
+
});
|
|
208
198
|
}
|
|
209
199
|
}
|
|
210
200
|
|
|
201
|
+
/**
|
|
202
|
+
* Estimates what submitting the epoch proof would have cost on L1 without actually sending it.
|
|
203
|
+
* Runs the same validation as `submitEpochProof`, encodes the calldata, estimates gas, and records metrics.
|
|
204
|
+
* Used when proof publishing is disabled (e.g. PROVER_NODE_DISABLE_PROOF_PUBLISH=true on mainnet).
|
|
205
|
+
*/
|
|
206
|
+
public async analyzeEpochProofSubmission(args: {
|
|
207
|
+
epochNumber: EpochNumber;
|
|
208
|
+
fromCheckpoint: CheckpointNumber;
|
|
209
|
+
toCheckpoint: CheckpointNumber;
|
|
210
|
+
publicInputs: RootRollupPublicInputs;
|
|
211
|
+
proof: Proof;
|
|
212
|
+
batchedBlobInputs: BatchedBlob;
|
|
213
|
+
attestations: ViemCommitteeAttestation[];
|
|
214
|
+
headers: CheckpointHeader[];
|
|
215
|
+
}): Promise<void> {
|
|
216
|
+
const { epochNumber, fromCheckpoint, toCheckpoint } = args;
|
|
217
|
+
|
|
218
|
+
await this.validateEpochProofSubmission(args);
|
|
219
|
+
|
|
220
|
+
const data = this.encodeSubmitEpochProofCalldata(args);
|
|
221
|
+
const senderAddress = this.l1TxUtils.getSenderAddress();
|
|
222
|
+
|
|
223
|
+
const [gasLimit, gasPrice, latestBlock] = await Promise.all([
|
|
224
|
+
this.l1TxUtils.estimateGas(senderAddress.toString() as `0x${string}`, { to: this.proofSubmissionTarget, data }),
|
|
225
|
+
this.l1TxUtils.getGasPrice(),
|
|
226
|
+
this.l1TxUtils.client.getBlock({ blockTag: 'latest' }),
|
|
227
|
+
]);
|
|
228
|
+
|
|
229
|
+
const baseFeePerGas = latestBlock.baseFeePerGas ?? 0n;
|
|
230
|
+
const { maxPriorityFeePerGas } = gasPrice;
|
|
231
|
+
|
|
232
|
+
const effectiveFeePerGas = baseFeePerGas + maxPriorityFeePerGas;
|
|
233
|
+
const estimatedTotalFee = gasLimit * effectiveFeePerGas;
|
|
234
|
+
|
|
235
|
+
const stats: EstimatedSubmitProofStats = {
|
|
236
|
+
gasLimit,
|
|
237
|
+
baseFeePerGas,
|
|
238
|
+
maxPriorityFeePerGas,
|
|
239
|
+
estimatedTotalFee,
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
this.log.info(`Estimated epoch proof submission cost (not submitted)`, {
|
|
243
|
+
epochNumber,
|
|
244
|
+
fromCheckpoint,
|
|
245
|
+
toCheckpoint,
|
|
246
|
+
gasLimit: gasLimit.toString(),
|
|
247
|
+
baseFeePerGas: formatGwei(baseFeePerGas),
|
|
248
|
+
maxPriorityFeePerGas: formatGwei(maxPriorityFeePerGas),
|
|
249
|
+
estimatedTotalFeeEth: formatEther(estimatedTotalFee),
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
this.metrics.recordEstimatedSubmitProof(stats);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
private encodeSubmitEpochProofCalldata(args: {
|
|
256
|
+
fromCheckpoint: CheckpointNumber;
|
|
257
|
+
toCheckpoint: CheckpointNumber;
|
|
258
|
+
publicInputs: RootRollupPublicInputs;
|
|
259
|
+
proof: Proof;
|
|
260
|
+
batchedBlobInputs: BatchedBlob;
|
|
261
|
+
attestations: ViemCommitteeAttestation[];
|
|
262
|
+
headers: CheckpointHeader[];
|
|
263
|
+
}): Hex {
|
|
264
|
+
return encodeFunctionData({
|
|
265
|
+
abi: RollupAbi,
|
|
266
|
+
functionName: 'submitEpochRootProof',
|
|
267
|
+
args: [this.getSubmitEpochProofArgs(args)],
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
|
|
211
271
|
private async sendSubmitEpochProofTx(args: {
|
|
212
272
|
fromCheckpoint: CheckpointNumber;
|
|
213
273
|
toCheckpoint: CheckpointNumber;
|
|
274
|
+
deadline?: Date;
|
|
214
275
|
publicInputs: RootRollupPublicInputs;
|
|
215
276
|
proof: Proof;
|
|
216
277
|
batchedBlobInputs: BatchedBlob;
|
|
217
278
|
attestations: ViemCommitteeAttestation[];
|
|
279
|
+
headers: CheckpointHeader[];
|
|
218
280
|
}): Promise<TransactionReceipt | undefined> {
|
|
219
281
|
const txArgs = [this.getSubmitEpochProofArgs(args)] as const;
|
|
220
282
|
|
|
@@ -229,7 +291,10 @@ export class ProverNodePublisher {
|
|
|
229
291
|
args: txArgs,
|
|
230
292
|
});
|
|
231
293
|
try {
|
|
232
|
-
const { receipt } = await this.l1TxUtils.sendAndMonitorTransaction(
|
|
294
|
+
const { receipt } = await this.l1TxUtils.sendAndMonitorTransaction(
|
|
295
|
+
{ to: this.proofSubmissionTarget, data },
|
|
296
|
+
{ txTimeoutAt: args.deadline },
|
|
297
|
+
);
|
|
233
298
|
if (receipt.status !== 'success') {
|
|
234
299
|
const errorMsg = await this.l1TxUtils.tryGetErrorFromRevertedTx(
|
|
235
300
|
data,
|
|
@@ -237,7 +302,7 @@ export class ProverNodePublisher {
|
|
|
237
302
|
args: [...txArgs],
|
|
238
303
|
functionName: 'submitEpochRootProof',
|
|
239
304
|
abi: RollupAbi,
|
|
240
|
-
address: this.
|
|
305
|
+
address: this.proofSubmissionTarget,
|
|
241
306
|
},
|
|
242
307
|
/*blobInputs*/ undefined,
|
|
243
308
|
/*stateOverride*/ [],
|
|
@@ -258,6 +323,7 @@ export class ProverNodePublisher {
|
|
|
258
323
|
publicInputs: RootRollupPublicInputs;
|
|
259
324
|
batchedBlobInputs: BatchedBlob;
|
|
260
325
|
attestations: ViemCommitteeAttestation[];
|
|
326
|
+
headers: CheckpointHeader[];
|
|
261
327
|
}) {
|
|
262
328
|
// Returns arguments for EpochProofLib.sol -> getEpochProofPublicInputs()
|
|
263
329
|
return [
|
|
@@ -269,11 +335,7 @@ export class ProverNodePublisher {
|
|
|
269
335
|
outHash: args.publicInputs.outHash.toString(),
|
|
270
336
|
proverId: EthAddress.fromField(args.publicInputs.constants.proverId).toString(),
|
|
271
337
|
} /*_args*/,
|
|
272
|
-
|
|
273
|
-
i % 2 === 0
|
|
274
|
-
? args.publicInputs.fees[i / 2].recipient.toField().toString()
|
|
275
|
-
: args.publicInputs.fees[(i - 1) / 2].value.toString(),
|
|
276
|
-
) /*_fees*/,
|
|
338
|
+
args.headers.map(header => header.toViem()) /*_headers*/,
|
|
277
339
|
getEthBlobEvaluationInputs(args.batchedBlobInputs) /*_blobPublicInputs*/,
|
|
278
340
|
] as const;
|
|
279
341
|
}
|
|
@@ -285,6 +347,7 @@ export class ProverNodePublisher {
|
|
|
285
347
|
proof: Proof;
|
|
286
348
|
batchedBlobInputs: BatchedBlob;
|
|
287
349
|
attestations: ViemCommitteeAttestation[];
|
|
350
|
+
headers: CheckpointHeader[];
|
|
288
351
|
}) {
|
|
289
352
|
// Returns arguments for EpochProofLib.sol -> submitEpochRootProof()
|
|
290
353
|
const proofHex: Hex = `0x${args.proof.withoutPublicInputs().toString('hex')}`;
|
|
@@ -293,12 +356,109 @@ export class ProverNodePublisher {
|
|
|
293
356
|
start: argsArray[0],
|
|
294
357
|
end: argsArray[1],
|
|
295
358
|
args: argsArray[2],
|
|
296
|
-
|
|
297
|
-
attestations:
|
|
359
|
+
headers: argsArray[3],
|
|
360
|
+
attestations: CommitteeAttestationsAndSigners.packAttestations(
|
|
298
361
|
args.attestations.map(a => CommitteeAttestation.fromViem(a)),
|
|
299
|
-
)
|
|
362
|
+
),
|
|
300
363
|
blobInputs: argsArray[4],
|
|
301
364
|
proof: proofHex,
|
|
302
365
|
};
|
|
303
366
|
}
|
|
304
367
|
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Decodes a `Root rollup public inputs mismatch`, fetches the on-chain CheckpointLog for any
|
|
371
|
+
* mismatching `checkpointHeaderHashes[i]`, emits a structured error log, and returns a thrown-ready
|
|
372
|
+
* Error with a human-readable summary.
|
|
373
|
+
*
|
|
374
|
+
* Layout of `RootRollupPublicInputs.toFields()`:
|
|
375
|
+
* [0] previousArchiveRoot
|
|
376
|
+
* [1] endArchiveRoot
|
|
377
|
+
* [2] outHash
|
|
378
|
+
* [3 .. 3+N-1] checkpointHeaderHashes[i] for i in 0..N-1 (N = MAX_CHECKPOINTS_PER_EPOCH)
|
|
379
|
+
* [3+N .. 3+3N-1] fees[i] = (recipient, value) for i in 0..N-1
|
|
380
|
+
* [3+3N .. 3+3N+4] EpochConstantData (chainId, version, vkTreeRoot, protocolContractsHash, proverId)
|
|
381
|
+
* [3+3N+5 ..] blobPublicInputs (FinalBlobAccumulator)
|
|
382
|
+
*/
|
|
383
|
+
async function reportPublicInputsMismatch(input: {
|
|
384
|
+
rollupPublicInputs: readonly Fr[];
|
|
385
|
+
argsPublicInputs: readonly Fr[];
|
|
386
|
+
fromCheckpoint: CheckpointNumber;
|
|
387
|
+
toCheckpoint: CheckpointNumber;
|
|
388
|
+
rollupContract: RollupContract;
|
|
389
|
+
log: Logger;
|
|
390
|
+
}): Promise<Error> {
|
|
391
|
+
const { rollupPublicInputs, argsPublicInputs, fromCheckpoint, toCheckpoint, rollupContract, log } = input;
|
|
392
|
+
const N = MAX_CHECKPOINTS_PER_EPOCH;
|
|
393
|
+
const constantsStart = 3 + 3 * N;
|
|
394
|
+
const blobStart = constantsStart + 5;
|
|
395
|
+
const constantLabels = ['chainId', 'version', 'vkTreeRoot', 'protocolContractsHash', 'proverId'];
|
|
396
|
+
|
|
397
|
+
const diffs: { index: number; label: string; rollup: Fr; computed: Fr; checkpointIndex?: number }[] = [];
|
|
398
|
+
const len = Math.max(rollupPublicInputs.length, argsPublicInputs.length);
|
|
399
|
+
for (let i = 0; i < len; i++) {
|
|
400
|
+
const a = rollupPublicInputs[i] ?? Fr.ZERO;
|
|
401
|
+
const b = argsPublicInputs[i] ?? Fr.ZERO;
|
|
402
|
+
if (a.equals(b)) {
|
|
403
|
+
continue;
|
|
404
|
+
}
|
|
405
|
+
let label: string;
|
|
406
|
+
let checkpointIndex: number | undefined;
|
|
407
|
+
if (i === 0) {
|
|
408
|
+
label = 'previousArchiveRoot';
|
|
409
|
+
} else if (i === 1) {
|
|
410
|
+
label = 'endArchiveRoot';
|
|
411
|
+
} else if (i === 2) {
|
|
412
|
+
label = 'outHash';
|
|
413
|
+
} else if (i < 3 + N) {
|
|
414
|
+
checkpointIndex = i - 3;
|
|
415
|
+
label = `checkpointHeaderHashes[${checkpointIndex}]`;
|
|
416
|
+
} else if (i < 3 + 3 * N) {
|
|
417
|
+
const feePairIndex = i - (3 + N);
|
|
418
|
+
const feeIndex = Math.floor(feePairIndex / 2);
|
|
419
|
+
const sub = feePairIndex % 2 === 0 ? 'recipient' : 'value';
|
|
420
|
+
label = `fees[${feeIndex}].${sub}`;
|
|
421
|
+
} else if (i < blobStart) {
|
|
422
|
+
label = `constants.${constantLabels[i - constantsStart]}`;
|
|
423
|
+
} else {
|
|
424
|
+
label = `blobPublicInputs[${i - blobStart}]`;
|
|
425
|
+
}
|
|
426
|
+
diffs.push({ index: i, label, rollup: a, computed: b, checkpointIndex });
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// For each mismatching checkpointHeaderHash, fetch the L1 CheckpointLog so the operator can
|
|
430
|
+
// see what was published on-chain alongside the prover's recomputed hash.
|
|
431
|
+
const onChainCheckpoints = await Promise.all(
|
|
432
|
+
diffs
|
|
433
|
+
.filter(d => d.checkpointIndex !== undefined)
|
|
434
|
+
.map(async d => {
|
|
435
|
+
const checkpointNumber = CheckpointNumber(fromCheckpoint + d.checkpointIndex!);
|
|
436
|
+
try {
|
|
437
|
+
const cp = await rollupContract.getCheckpoint(checkpointNumber);
|
|
438
|
+
return { checkpointIndex: d.checkpointIndex!, checkpointNumber, headerHash: cp.headerHash.toString() };
|
|
439
|
+
} catch (err) {
|
|
440
|
+
return { checkpointIndex: d.checkpointIndex!, checkpointNumber, error: (err as Error).message };
|
|
441
|
+
}
|
|
442
|
+
}),
|
|
443
|
+
);
|
|
444
|
+
|
|
445
|
+
log.error(`Root rollup public inputs mismatch`, undefined, {
|
|
446
|
+
fromCheckpoint,
|
|
447
|
+
toCheckpoint,
|
|
448
|
+
numDiffs: diffs.length,
|
|
449
|
+
diffs: diffs.map(d => ({
|
|
450
|
+
index: d.index,
|
|
451
|
+
label: d.label,
|
|
452
|
+
rollup: d.rollup.toString(),
|
|
453
|
+
computed: d.computed.toString(),
|
|
454
|
+
})),
|
|
455
|
+
onChainCheckpoints,
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
const fmt = (inputs: readonly Fr[]) => inputs.map(x => x.toString()).join(', ');
|
|
459
|
+
const summary = diffs.map(d => `[${d.index} ${d.label}] L1=${d.rollup} prover=${d.computed}`).join('\n');
|
|
460
|
+
return new Error(
|
|
461
|
+
`Root rollup public inputs mismatch (${diffs.length} fields differ):\n${summary}\n` +
|
|
462
|
+
`Rollup: ${fmt(rollupPublicInputs)}\nComputed:${fmt(argsPublicInputs)}`,
|
|
463
|
+
);
|
|
464
|
+
}
|