@aztec/prover-node 0.0.1-commit.b6e433891 → 0.0.1-commit.b8a057fa
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 +4 -3
- package/dest/actions/rerun-epoch-proving-job.d.ts.map +1 -1
- package/dest/actions/rerun-epoch-proving-job.js +191 -28
- package/dest/bin/run-failed-epoch.js +1 -3
- 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 +3 -1
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +8 -1
- package/dest/factory.d.ts +4 -1
- package/dest/factory.d.ts.map +1 -1
- package/dest/factory.js +23 -8
- 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-session.js +720 -0
- 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 +35 -8
- package/dest/metrics.d.ts.map +1 -1
- package/dest/metrics.js +86 -14
- package/dest/monitors/epoch-monitor.js +6 -2
- 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 +25 -15
- package/dest/prover-node-publisher.d.ts.map +1 -1
- package/dest/prover-node-publisher.js +201 -62
- package/dest/prover-node.d.ts +107 -67
- package/dest/prover-node.d.ts.map +1 -1
- package/dest/prover-node.js +484 -224
- package/dest/prover-publisher-factory.d.ts +4 -2
- package/dest/prover-publisher-factory.d.ts.map +1 -1
- package/dest/prover-publisher-factory.js +4 -3
- 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 -23
- package/src/actions/download-epoch-proving-job.ts +1 -1
- package/src/actions/rerun-epoch-proving-job.ts +117 -29
- package/src/bin/run-failed-epoch.ts +1 -2
- package/src/checkpoint-store.ts +218 -0
- package/src/config.ts +12 -1
- package/src/factory.ts +23 -10
- 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 +102 -23
- package/src/monitors/epoch-monitor.ts +2 -2
- package/src/proof-publishing-service.ts +427 -0
- package/src/prover-node-publisher.ts +236 -78
- package/src/prover-node.ts +555 -252
- package/src/prover-publisher-factory.ts +6 -3
- package/src/session-manager.ts +552 -0
- package/src/test/index.ts +6 -6
- package/dest/job/epoch-proving-job.d.ts +0 -63
- package/dest/job/epoch-proving-job.d.ts.map +0 -1
- package/dest/job/epoch-proving-job.js +0 -762
- package/src/job/epoch-proving-job.ts +0 -465
|
@@ -2,26 +2,24 @@ import { BatchedBlob, getEthBlobEvaluationInputs } from '@aztec/blob-lib';
|
|
|
2
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
9
|
import { type Logger, type LoggerBindings, createLogger } from '@aztec/foundation/log';
|
|
11
|
-
import type { Tuple } from '@aztec/foundation/serialize';
|
|
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
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,6 +49,7 @@ export class ProverNodePublisher {
|
|
|
50
49
|
deps: {
|
|
51
50
|
rollupContract: RollupContract;
|
|
52
51
|
l1TxUtils: L1TxUtils;
|
|
52
|
+
proofSubmissionTarget?: EthAddress;
|
|
53
53
|
telemetry?: TelemetryClient;
|
|
54
54
|
},
|
|
55
55
|
bindings?: LoggerBindings,
|
|
@@ -60,6 +60,7 @@ export class ProverNodePublisher {
|
|
|
60
60
|
this.log = createLogger('prover-node:l1-tx-publisher', bindings);
|
|
61
61
|
|
|
62
62
|
this.rollupContract = deps.rollupContract;
|
|
63
|
+
this.proofSubmissionTarget = deps.proofSubmissionTarget?.toString() ?? deps.rollupContract.address;
|
|
63
64
|
this.l1TxUtils = deps.l1TxUtils;
|
|
64
65
|
}
|
|
65
66
|
|
|
@@ -67,23 +68,6 @@ export class ProverNodePublisher {
|
|
|
67
68
|
return this.rollupContract;
|
|
68
69
|
}
|
|
69
70
|
|
|
70
|
-
/**
|
|
71
|
-
* Calling `interrupt` will cause any in progress call to `publishRollup` to return `false` asap.
|
|
72
|
-
* Be warned, the call may return false even if the tx subsequently gets successfully mined.
|
|
73
|
-
* In practice this shouldn't matter, as we'll only ever be calling `interrupt` when we know it's going to fail.
|
|
74
|
-
* A call to `restart` is required before you can continue publishing.
|
|
75
|
-
*/
|
|
76
|
-
public interrupt() {
|
|
77
|
-
this.interrupted = true;
|
|
78
|
-
this.l1TxUtils.interrupt();
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/** Restarts the publisher after calling `interrupt`. */
|
|
82
|
-
public restart() {
|
|
83
|
-
this.interrupted = false;
|
|
84
|
-
this.l1TxUtils.restart();
|
|
85
|
-
}
|
|
86
|
-
|
|
87
71
|
public getSenderAddress() {
|
|
88
72
|
return this.l1TxUtils.getSenderAddress();
|
|
89
73
|
}
|
|
@@ -96,54 +80,53 @@ export class ProverNodePublisher {
|
|
|
96
80
|
proof: Proof;
|
|
97
81
|
batchedBlobInputs: BatchedBlob;
|
|
98
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;
|
|
99
86
|
}): Promise<boolean> {
|
|
100
87
|
const { epochNumber, fromCheckpoint, toCheckpoint } = args;
|
|
101
88
|
const ctx = { epochNumber, fromCheckpoint, toCheckpoint };
|
|
102
89
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
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);
|
|
107
93
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
try {
|
|
115
|
-
this.metrics.recordSenderBalance(
|
|
116
|
-
await this.l1TxUtils.getSenderBalance(),
|
|
117
|
-
this.l1TxUtils.getSenderAddress().toString(),
|
|
118
|
-
);
|
|
119
|
-
} catch (err) {
|
|
120
|
-
this.log.warn(`Failed to record the ETH balance of the prover node: ${err}`);
|
|
121
|
-
}
|
|
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
|
+
}
|
|
122
99
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
calldataSize: tx!.calldataSize,
|
|
132
|
-
sender: tx!.sender,
|
|
133
|
-
blobDataGas: 0n,
|
|
134
|
-
blobGasUsed: 0n,
|
|
135
|
-
eventName: 'proof-published-to-l1',
|
|
136
|
-
};
|
|
137
|
-
this.log.info(`Published epoch proof to L1 rollup contract`, { ...stats, ...ctx });
|
|
138
|
-
this.metrics.recordSubmitProof(timer.ms(), stats);
|
|
139
|
-
return true;
|
|
140
|
-
}
|
|
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
|
+
}
|
|
141
108
|
|
|
142
|
-
|
|
143
|
-
|
|
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;
|
|
144
126
|
}
|
|
145
127
|
|
|
146
|
-
this.
|
|
128
|
+
this.metrics.recordFailedTx();
|
|
129
|
+
this.log.error(`Rollup submitEpochProof tx reverted ${txReceipt.transactionHash}`, undefined, ctx);
|
|
147
130
|
return false;
|
|
148
131
|
}
|
|
149
132
|
|
|
@@ -154,6 +137,7 @@ export class ProverNodePublisher {
|
|
|
154
137
|
proof: Proof;
|
|
155
138
|
batchedBlobInputs: BatchedBlob;
|
|
156
139
|
attestations: ViemCommitteeAttestation[];
|
|
140
|
+
headers: CheckpointHeader[];
|
|
157
141
|
}) {
|
|
158
142
|
const { fromCheckpoint, toCheckpoint, publicInputs, batchedBlobInputs } = args;
|
|
159
143
|
|
|
@@ -168,7 +152,7 @@ export class ProverNodePublisher {
|
|
|
168
152
|
// toCheckpoint can't be greater than pending
|
|
169
153
|
if (toCheckpoint > pending) {
|
|
170
154
|
throw new Error(
|
|
171
|
-
`Cannot submit epoch proof for ${fromCheckpoint}-${toCheckpoint} as
|
|
155
|
+
`Cannot submit epoch proof for ${fromCheckpoint}-${toCheckpoint} as proposed checkpoint is ${pending}`,
|
|
172
156
|
);
|
|
173
157
|
}
|
|
174
158
|
|
|
@@ -203,20 +187,96 @@ export class ProverNodePublisher {
|
|
|
203
187
|
const argsPublicInputs = [...publicInputs.toFields()];
|
|
204
188
|
|
|
205
189
|
if (!areArraysEqual(rollupPublicInputs, argsPublicInputs, (a, b) => a.equals(b))) {
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
190
|
+
throw await reportPublicInputsMismatch({
|
|
191
|
+
rollupPublicInputs,
|
|
192
|
+
argsPublicInputs,
|
|
193
|
+
fromCheckpoint,
|
|
194
|
+
toCheckpoint,
|
|
195
|
+
rollupContract: this.rollupContract,
|
|
196
|
+
log: this.log,
|
|
197
|
+
});
|
|
210
198
|
}
|
|
211
199
|
}
|
|
212
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
|
+
|
|
213
271
|
private async sendSubmitEpochProofTx(args: {
|
|
214
272
|
fromCheckpoint: CheckpointNumber;
|
|
215
273
|
toCheckpoint: CheckpointNumber;
|
|
274
|
+
deadline?: Date;
|
|
216
275
|
publicInputs: RootRollupPublicInputs;
|
|
217
276
|
proof: Proof;
|
|
218
277
|
batchedBlobInputs: BatchedBlob;
|
|
219
278
|
attestations: ViemCommitteeAttestation[];
|
|
279
|
+
headers: CheckpointHeader[];
|
|
220
280
|
}): Promise<TransactionReceipt | undefined> {
|
|
221
281
|
const txArgs = [this.getSubmitEpochProofArgs(args)] as const;
|
|
222
282
|
|
|
@@ -231,7 +291,10 @@ export class ProverNodePublisher {
|
|
|
231
291
|
args: txArgs,
|
|
232
292
|
});
|
|
233
293
|
try {
|
|
234
|
-
const { receipt } = await this.l1TxUtils.sendAndMonitorTransaction(
|
|
294
|
+
const { receipt } = await this.l1TxUtils.sendAndMonitorTransaction(
|
|
295
|
+
{ to: this.proofSubmissionTarget, data },
|
|
296
|
+
{ txTimeoutAt: args.deadline },
|
|
297
|
+
);
|
|
235
298
|
if (receipt.status !== 'success') {
|
|
236
299
|
const errorMsg = await this.l1TxUtils.tryGetErrorFromRevertedTx(
|
|
237
300
|
data,
|
|
@@ -239,7 +302,7 @@ export class ProverNodePublisher {
|
|
|
239
302
|
args: [...txArgs],
|
|
240
303
|
functionName: 'submitEpochRootProof',
|
|
241
304
|
abi: RollupAbi,
|
|
242
|
-
address: this.
|
|
305
|
+
address: this.proofSubmissionTarget,
|
|
243
306
|
},
|
|
244
307
|
/*blobInputs*/ undefined,
|
|
245
308
|
/*stateOverride*/ [],
|
|
@@ -260,6 +323,7 @@ export class ProverNodePublisher {
|
|
|
260
323
|
publicInputs: RootRollupPublicInputs;
|
|
261
324
|
batchedBlobInputs: BatchedBlob;
|
|
262
325
|
attestations: ViemCommitteeAttestation[];
|
|
326
|
+
headers: CheckpointHeader[];
|
|
263
327
|
}) {
|
|
264
328
|
// Returns arguments for EpochProofLib.sol -> getEpochProofPublicInputs()
|
|
265
329
|
return [
|
|
@@ -271,11 +335,7 @@ export class ProverNodePublisher {
|
|
|
271
335
|
outHash: args.publicInputs.outHash.toString(),
|
|
272
336
|
proverId: EthAddress.fromField(args.publicInputs.constants.proverId).toString(),
|
|
273
337
|
} /*_args*/,
|
|
274
|
-
|
|
275
|
-
i % 2 === 0
|
|
276
|
-
? args.publicInputs.fees[i / 2].recipient.toField().toString()
|
|
277
|
-
: args.publicInputs.fees[(i - 1) / 2].value.toString(),
|
|
278
|
-
) /*_fees*/,
|
|
338
|
+
args.headers.map(header => header.toViem()) /*_headers*/,
|
|
279
339
|
getEthBlobEvaluationInputs(args.batchedBlobInputs) /*_blobPublicInputs*/,
|
|
280
340
|
] as const;
|
|
281
341
|
}
|
|
@@ -287,6 +347,7 @@ export class ProverNodePublisher {
|
|
|
287
347
|
proof: Proof;
|
|
288
348
|
batchedBlobInputs: BatchedBlob;
|
|
289
349
|
attestations: ViemCommitteeAttestation[];
|
|
350
|
+
headers: CheckpointHeader[];
|
|
290
351
|
}) {
|
|
291
352
|
// Returns arguments for EpochProofLib.sol -> submitEpochRootProof()
|
|
292
353
|
const proofHex: Hex = `0x${args.proof.withoutPublicInputs().toString('hex')}`;
|
|
@@ -295,12 +356,109 @@ export class ProverNodePublisher {
|
|
|
295
356
|
start: argsArray[0],
|
|
296
357
|
end: argsArray[1],
|
|
297
358
|
args: argsArray[2],
|
|
298
|
-
|
|
299
|
-
attestations:
|
|
359
|
+
headers: argsArray[3],
|
|
360
|
+
attestations: CommitteeAttestationsAndSigners.packAttestations(
|
|
300
361
|
args.attestations.map(a => CommitteeAttestation.fromViem(a)),
|
|
301
|
-
)
|
|
362
|
+
),
|
|
302
363
|
blobInputs: argsArray[4],
|
|
303
364
|
proof: proofHex,
|
|
304
365
|
};
|
|
305
366
|
}
|
|
306
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
|
+
}
|