@aztec-labs/prover-node 6.0.0-nightly.20260829
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 +573 -0
- package/dest/actions/download-epoch-proving-job.d.ts +18 -0
- package/dest/actions/download-epoch-proving-job.d.ts.map +1 -0
- package/dest/actions/download-epoch-proving-job.js +37 -0
- package/dest/actions/index.d.ts +3 -0
- package/dest/actions/index.d.ts.map +1 -0
- package/dest/actions/index.js +2 -0
- package/dest/actions/rerun-epoch-proving-job.d.ts +23 -0
- package/dest/actions/rerun-epoch-proving-job.d.ts.map +1 -0
- package/dest/actions/rerun-epoch-proving-job.js +264 -0
- package/dest/actions/upload-epoch-proof-failure.d.ts +15 -0
- package/dest/actions/upload-epoch-proof-failure.d.ts.map +1 -0
- package/dest/actions/upload-epoch-proof-failure.js +78 -0
- package/dest/bin/run-failed-epoch.d.ts +2 -0
- package/dest/bin/run-failed-epoch.d.ts.map +1 -0
- package/dest/bin/run-failed-epoch.js +68 -0
- package/dest/checkpoint-store.d.ts +95 -0
- package/dest/checkpoint-store.d.ts.map +1 -0
- package/dest/checkpoint-store.js +178 -0
- package/dest/config.d.ts +31 -0
- package/dest/config.d.ts.map +1 -0
- package/dest/config.js +143 -0
- package/dest/factory.d.ts +36 -0
- package/dest/factory.d.ts.map +1 -0
- package/dest/factory.js +105 -0
- package/dest/index.d.ts +8 -0
- package/dest/index.d.ts.map +1 -0
- package/dest/index.js +7 -0
- package/dest/job/checkpoint-prover.d.ts +169 -0
- package/dest/job/checkpoint-prover.d.ts.map +1 -0
- package/dest/job/checkpoint-prover.js +413 -0
- package/dest/job/epoch-proving-job-data.d.ts +20 -0
- package/dest/job/epoch-proving-job-data.d.ts.map +1 -0
- package/dest/job/epoch-proving-job-data.js +61 -0
- package/dest/job/epoch-session.d.ts +160 -0
- package/dest/job/epoch-session.d.ts.map +1 -0
- package/dest/job/epoch-session.js +744 -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 +88 -0
- package/dest/metrics.d.ts.map +1 -0
- package/dest/metrics.js +264 -0
- package/dest/monitors/epoch-monitor.d.ts +43 -0
- package/dest/monitors/epoch-monitor.d.ts.map +1 -0
- package/dest/monitors/epoch-monitor.js +111 -0
- package/dest/monitors/index.d.ts +2 -0
- package/dest/monitors/index.d.ts.map +1 -0
- package/dest/monitors/index.js +1 -0
- 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 +71 -0
- package/dest/prover-node-publisher.d.ts.map +1 -0
- package/dest/prover-node-publisher.js +346 -0
- package/dest/prover-node.d.ts +185 -0
- package/dest/prover-node.d.ts.map +1 -0
- package/dest/prover-node.js +1056 -0
- package/dest/prover-publisher-factory.d.ts +27 -0
- package/dest/prover-publisher-factory.d.ts.map +1 -0
- package/dest/prover-publisher-factory.js +29 -0
- package/dest/session-manager.d.ts +158 -0
- package/dest/session-manager.d.ts.map +1 -0
- package/dest/session-manager.js +493 -0
- package/dest/test/index.d.ts +13 -0
- package/dest/test/index.d.ts.map +1 -0
- package/dest/test/index.js +3 -0
- package/package.json +102 -0
- package/src/actions/download-epoch-proving-job.ts +43 -0
- package/src/actions/index.ts +2 -0
- package/src/actions/rerun-epoch-proving-job.ts +224 -0
- package/src/actions/upload-epoch-proof-failure.ts +87 -0
- package/src/bin/run-failed-epoch.ts +78 -0
- package/src/checkpoint-store.ts +212 -0
- package/src/config.ts +208 -0
- package/src/factory.ts +191 -0
- package/src/index.ts +7 -0
- package/src/job/checkpoint-prover.ts +551 -0
- package/src/job/epoch-proving-job-data.ts +94 -0
- package/src/job/epoch-session.ts +466 -0
- package/src/job/top-tree-job.ts +227 -0
- package/src/metrics.ts +323 -0
- package/src/monitors/epoch-monitor.ts +110 -0
- package/src/monitors/index.ts +1 -0
- package/src/proof-publishing-service.ts +427 -0
- package/src/prover-node-publisher.ts +473 -0
- package/src/prover-node.ts +830 -0
- package/src/prover-publisher-factory.ts +48 -0
- package/src/session-manager.ts +593 -0
- package/src/test/index.ts +14 -0
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
import { RollupAbi } from '@aztec/l1-artifacts';
|
|
2
|
+
|
|
3
|
+
import { BatchedBlob, getEthBlobEvaluationInputs } from '@aztec-labs/blob-lib';
|
|
4
|
+
import { MAX_CHECKPOINTS_PER_EPOCH } from '@aztec-labs/constants';
|
|
5
|
+
import type { RollupContract, ViemCommitteeAttestation } from '@aztec-labs/ethereum/contracts';
|
|
6
|
+
import type { L1TxUtils } from '@aztec-labs/ethereum/l1-tx-utils';
|
|
7
|
+
import { CheckpointNumber, EpochNumber } from '@aztec-labs/foundation/branded-types';
|
|
8
|
+
import { areArraysEqual } from '@aztec-labs/foundation/collection';
|
|
9
|
+
import { Fr } from '@aztec-labs/foundation/curves/bn254';
|
|
10
|
+
import { EthAddress } from '@aztec-labs/foundation/eth-address';
|
|
11
|
+
import { type Logger, type LoggerBindings, createLogger } from '@aztec-labs/foundation/log';
|
|
12
|
+
import { Timer } from '@aztec-labs/foundation/timer';
|
|
13
|
+
import type { PublisherConfig, TxSenderConfig } from '@aztec-labs/sequencer-client';
|
|
14
|
+
import { CommitteeAttestation, CommitteeAttestationsAndSigners } from '@aztec-labs/stdlib/block';
|
|
15
|
+
import type { Proof } from '@aztec-labs/stdlib/proofs';
|
|
16
|
+
import type { CheckpointHeader, RootRollupPublicInputs } from '@aztec-labs/stdlib/rollup';
|
|
17
|
+
import type { L1PublishProofStats } from '@aztec-labs/stdlib/stats';
|
|
18
|
+
import { type TelemetryClient, getTelemetryClient } from '@aztec-labs/telemetry-client';
|
|
19
|
+
import { inspect } from 'util';
|
|
20
|
+
import { type Hex, type TransactionReceipt, encodeFunctionData, formatEther, formatGwei } from 'viem';
|
|
21
|
+
|
|
22
|
+
import { type EstimatedSubmitProofStats, ProverNodePublisherMetrics } from './metrics.js';
|
|
23
|
+
|
|
24
|
+
/** Arguments to the submitEpochProof method of the rollup contract */
|
|
25
|
+
export type L1SubmitEpochProofArgs = {
|
|
26
|
+
epochSize: number;
|
|
27
|
+
previousArchive: Fr;
|
|
28
|
+
endArchive: Fr;
|
|
29
|
+
endTimestamp: Fr;
|
|
30
|
+
outHash: Fr;
|
|
31
|
+
proverId: Fr;
|
|
32
|
+
headers: CheckpointHeader[];
|
|
33
|
+
proof: Proof;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export class ProverNodePublisher {
|
|
37
|
+
private metrics: ProverNodePublisherMetrics;
|
|
38
|
+
|
|
39
|
+
protected log: Logger;
|
|
40
|
+
|
|
41
|
+
protected rollupContract: RollupContract;
|
|
42
|
+
|
|
43
|
+
protected proofSubmissionTarget: Hex;
|
|
44
|
+
|
|
45
|
+
public readonly l1TxUtils: L1TxUtils;
|
|
46
|
+
|
|
47
|
+
constructor(
|
|
48
|
+
config: TxSenderConfig & PublisherConfig,
|
|
49
|
+
deps: {
|
|
50
|
+
rollupContract: RollupContract;
|
|
51
|
+
l1TxUtils: L1TxUtils;
|
|
52
|
+
proofSubmissionTarget?: EthAddress;
|
|
53
|
+
telemetry?: TelemetryClient;
|
|
54
|
+
},
|
|
55
|
+
bindings?: LoggerBindings,
|
|
56
|
+
) {
|
|
57
|
+
const telemetry = deps.telemetry ?? getTelemetryClient();
|
|
58
|
+
|
|
59
|
+
this.metrics = new ProverNodePublisherMetrics(telemetry, 'ProverNode');
|
|
60
|
+
this.log = createLogger('prover-node:l1-tx-publisher', bindings);
|
|
61
|
+
|
|
62
|
+
this.rollupContract = deps.rollupContract;
|
|
63
|
+
this.proofSubmissionTarget = deps.proofSubmissionTarget?.toString() ?? deps.rollupContract.address;
|
|
64
|
+
this.l1TxUtils = deps.l1TxUtils;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
public getRollupContract() {
|
|
68
|
+
return this.rollupContract;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
public getSenderAddress() {
|
|
72
|
+
return this.l1TxUtils.getSenderAddress();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
public async submitEpochProof(args: {
|
|
76
|
+
epochNumber: EpochNumber;
|
|
77
|
+
fromCheckpoint: CheckpointNumber;
|
|
78
|
+
toCheckpoint: CheckpointNumber;
|
|
79
|
+
publicInputs: RootRollupPublicInputs;
|
|
80
|
+
proof: Proof;
|
|
81
|
+
batchedBlobInputs: BatchedBlob;
|
|
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;
|
|
86
|
+
}): Promise<boolean> {
|
|
87
|
+
const { epochNumber, fromCheckpoint, toCheckpoint } = args;
|
|
88
|
+
const ctx = { epochNumber, fromCheckpoint, toCheckpoint };
|
|
89
|
+
|
|
90
|
+
const timer = new Timer();
|
|
91
|
+
// Validate epoch proof range and hashes are correct before submitting
|
|
92
|
+
await this.validateEpochProofSubmission(args);
|
|
93
|
+
|
|
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
|
+
}
|
|
99
|
+
|
|
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
|
+
}
|
|
108
|
+
|
|
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;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
this.metrics.recordFailedTx();
|
|
129
|
+
this.log.error(`Rollup submitEpochProof tx reverted ${txReceipt.transactionHash}`, undefined, ctx);
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
private async validateEpochProofSubmission(args: {
|
|
134
|
+
fromCheckpoint: CheckpointNumber;
|
|
135
|
+
toCheckpoint: CheckpointNumber;
|
|
136
|
+
publicInputs: RootRollupPublicInputs;
|
|
137
|
+
proof: Proof;
|
|
138
|
+
batchedBlobInputs: BatchedBlob;
|
|
139
|
+
attestations: ViemCommitteeAttestation[];
|
|
140
|
+
headers: CheckpointHeader[];
|
|
141
|
+
}) {
|
|
142
|
+
const { fromCheckpoint, toCheckpoint, publicInputs, batchedBlobInputs } = args;
|
|
143
|
+
|
|
144
|
+
// Check that the checkpoint numbers match the expected epoch to be proven
|
|
145
|
+
const { pending, proven } = await this.rollupContract.getTips();
|
|
146
|
+
// Don't publish if proven is beyond our toCheckpoint, pointless to do so
|
|
147
|
+
if (proven > toCheckpoint) {
|
|
148
|
+
throw new Error(
|
|
149
|
+
`Cannot submit epoch proof for ${fromCheckpoint}-${toCheckpoint} as proven checkpoint is ${proven}`,
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
// toCheckpoint can't be greater than pending
|
|
153
|
+
if (toCheckpoint > pending) {
|
|
154
|
+
throw new Error(
|
|
155
|
+
`Cannot submit epoch proof for ${fromCheckpoint}-${toCheckpoint} as proposed checkpoint is ${pending}`,
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Check the archive for the immediate checkpoint before the epoch
|
|
160
|
+
const checkpointLog = await this.rollupContract.getCheckpoint(CheckpointNumber(fromCheckpoint - 1));
|
|
161
|
+
if (!publicInputs.previousArchiveRoot.equals(checkpointLog.archive)) {
|
|
162
|
+
throw new Error(
|
|
163
|
+
`Previous archive root mismatch: ${publicInputs.previousArchiveRoot.toString()} !== ${checkpointLog.archive.toString()}`,
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Check the archive for the last checkpoint in the epoch
|
|
168
|
+
const endCheckpointLog = await this.rollupContract.getCheckpoint(toCheckpoint);
|
|
169
|
+
if (!publicInputs.endArchiveRoot.equals(endCheckpointLog.archive)) {
|
|
170
|
+
throw new Error(
|
|
171
|
+
`End archive root mismatch: ${publicInputs.endArchiveRoot.toString()} !== ${endCheckpointLog.archive.toString()}`,
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Check the batched blob inputs from the root rollup against the batched blob computed in ts
|
|
176
|
+
const finalBlobAccumulator = batchedBlobInputs.toFinalBlobAccumulator();
|
|
177
|
+
if (!publicInputs.blobPublicInputs.equals(finalBlobAccumulator)) {
|
|
178
|
+
throw new Error(
|
|
179
|
+
`Batched blob mismatch: ${inspect(publicInputs.blobPublicInputs)} !== ${inspect(finalBlobAccumulator)}`,
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Compare the public inputs computed by the contract with the ones injected
|
|
184
|
+
const rollupPublicInputs = await this.rollupContract.getEpochProofPublicInputs(
|
|
185
|
+
this.getEpochProofPublicInputsArgs(args),
|
|
186
|
+
);
|
|
187
|
+
const argsPublicInputs = [...publicInputs.toFields()];
|
|
188
|
+
|
|
189
|
+
if (!areArraysEqual(rollupPublicInputs, argsPublicInputs, (a, b) => a.equals(b))) {
|
|
190
|
+
throw await reportPublicInputsMismatch({
|
|
191
|
+
rollupPublicInputs,
|
|
192
|
+
argsPublicInputs,
|
|
193
|
+
fromCheckpoint,
|
|
194
|
+
toCheckpoint,
|
|
195
|
+
rollupContract: this.rollupContract,
|
|
196
|
+
log: this.log,
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
}
|
|
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, feesPerGas, latestBlock] = await Promise.all([
|
|
224
|
+
this.l1TxUtils.estimateGas(senderAddress.toString() as `0x${string}`, { to: this.proofSubmissionTarget, data }),
|
|
225
|
+
this.l1TxUtils.getFeesPerGas(),
|
|
226
|
+
this.l1TxUtils.client.getBlock({ blockTag: 'latest' }),
|
|
227
|
+
]);
|
|
228
|
+
|
|
229
|
+
const baseFeePerGas = latestBlock.baseFeePerGas ?? 0n;
|
|
230
|
+
const { maxPriorityFeePerGas } = feesPerGas;
|
|
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
|
+
|
|
271
|
+
private async sendSubmitEpochProofTx(args: {
|
|
272
|
+
fromCheckpoint: CheckpointNumber;
|
|
273
|
+
toCheckpoint: CheckpointNumber;
|
|
274
|
+
deadline?: Date;
|
|
275
|
+
publicInputs: RootRollupPublicInputs;
|
|
276
|
+
proof: Proof;
|
|
277
|
+
batchedBlobInputs: BatchedBlob;
|
|
278
|
+
attestations: ViemCommitteeAttestation[];
|
|
279
|
+
headers: CheckpointHeader[];
|
|
280
|
+
}): Promise<TransactionReceipt | undefined> {
|
|
281
|
+
const txArgs = [this.getSubmitEpochProofArgs(args)] as const;
|
|
282
|
+
|
|
283
|
+
this.log.info(`Submitting epoch proof to L1 rollup contract`, {
|
|
284
|
+
proofSize: args.proof.withoutPublicInputs().length,
|
|
285
|
+
fromCheckpoint: args.fromCheckpoint,
|
|
286
|
+
toCheckpoint: args.toCheckpoint,
|
|
287
|
+
});
|
|
288
|
+
const data = encodeFunctionData({
|
|
289
|
+
abi: RollupAbi,
|
|
290
|
+
functionName: 'submitEpochRootProof',
|
|
291
|
+
args: txArgs,
|
|
292
|
+
});
|
|
293
|
+
try {
|
|
294
|
+
const { receipt } = await this.l1TxUtils.sendAndMonitorTransaction(
|
|
295
|
+
{ to: this.proofSubmissionTarget, data },
|
|
296
|
+
{ txTimeoutAt: args.deadline },
|
|
297
|
+
);
|
|
298
|
+
if (receipt.status !== 'success') {
|
|
299
|
+
const errorMsg = await this.l1TxUtils.tryGetErrorFromRevertedTx(
|
|
300
|
+
data,
|
|
301
|
+
{
|
|
302
|
+
args: [...txArgs],
|
|
303
|
+
functionName: 'submitEpochRootProof',
|
|
304
|
+
abi: RollupAbi,
|
|
305
|
+
address: this.proofSubmissionTarget,
|
|
306
|
+
},
|
|
307
|
+
/*blobInputs*/ undefined,
|
|
308
|
+
/*stateOverride*/ [],
|
|
309
|
+
);
|
|
310
|
+
this.log.error(`Rollup submit epoch proof tx reverted with ${errorMsg ?? 'unknown error'}`);
|
|
311
|
+
return undefined;
|
|
312
|
+
}
|
|
313
|
+
return receipt;
|
|
314
|
+
} catch (err) {
|
|
315
|
+
this.log.error(`Rollup submit epoch proof failed`, err);
|
|
316
|
+
return undefined;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
private getEpochProofPublicInputsArgs(args: {
|
|
321
|
+
fromCheckpoint: CheckpointNumber;
|
|
322
|
+
toCheckpoint: CheckpointNumber;
|
|
323
|
+
publicInputs: RootRollupPublicInputs;
|
|
324
|
+
batchedBlobInputs: BatchedBlob;
|
|
325
|
+
attestations: ViemCommitteeAttestation[];
|
|
326
|
+
headers: CheckpointHeader[];
|
|
327
|
+
}) {
|
|
328
|
+
// Returns arguments for EpochProofLib.sol -> getEpochProofPublicInputs()
|
|
329
|
+
return [
|
|
330
|
+
BigInt(args.fromCheckpoint) /*_start*/,
|
|
331
|
+
BigInt(args.toCheckpoint) /*_end*/,
|
|
332
|
+
{
|
|
333
|
+
previousArchive: args.publicInputs.previousArchiveRoot.toString(),
|
|
334
|
+
endArchive: args.publicInputs.endArchiveRoot.toString(),
|
|
335
|
+
outHash: args.publicInputs.outHash.toString(),
|
|
336
|
+
previousInboxRollingHash: args.publicInputs.previousInboxRollingHash.toString(),
|
|
337
|
+
endInboxRollingHash: args.publicInputs.endInboxRollingHash.toString(),
|
|
338
|
+
proverId: EthAddress.fromField(args.publicInputs.constants.proverId).toString(),
|
|
339
|
+
} /*_args*/,
|
|
340
|
+
args.headers.map(header => header.toViem()) /*_headers*/,
|
|
341
|
+
getEthBlobEvaluationInputs(args.batchedBlobInputs) /*_blobPublicInputs*/,
|
|
342
|
+
] as const;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
private getSubmitEpochProofArgs(args: {
|
|
346
|
+
fromCheckpoint: CheckpointNumber;
|
|
347
|
+
toCheckpoint: CheckpointNumber;
|
|
348
|
+
publicInputs: RootRollupPublicInputs;
|
|
349
|
+
proof: Proof;
|
|
350
|
+
batchedBlobInputs: BatchedBlob;
|
|
351
|
+
attestations: ViemCommitteeAttestation[];
|
|
352
|
+
headers: CheckpointHeader[];
|
|
353
|
+
}) {
|
|
354
|
+
// Returns arguments for EpochProofLib.sol -> submitEpochRootProof()
|
|
355
|
+
const proofHex: Hex = `0x${args.proof.withoutPublicInputs().toString('hex')}`;
|
|
356
|
+
const argsArray = this.getEpochProofPublicInputsArgs(args);
|
|
357
|
+
return {
|
|
358
|
+
start: argsArray[0],
|
|
359
|
+
end: argsArray[1],
|
|
360
|
+
args: argsArray[2],
|
|
361
|
+
headers: argsArray[3],
|
|
362
|
+
attestations: CommitteeAttestationsAndSigners.packAttestations(
|
|
363
|
+
args.attestations.map(a => CommitteeAttestation.fromViem(a)),
|
|
364
|
+
),
|
|
365
|
+
blobInputs: argsArray[4],
|
|
366
|
+
proof: proofHex,
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Decodes a `Root rollup public inputs mismatch`, fetches the on-chain CheckpointLog for any
|
|
373
|
+
* mismatching `checkpointHeaderHashes[i]`, emits a structured error log, and returns a thrown-ready
|
|
374
|
+
* Error with a human-readable summary.
|
|
375
|
+
*
|
|
376
|
+
* Layout of `RootRollupPublicInputs.toFields()`:
|
|
377
|
+
* [0] previousArchiveRoot
|
|
378
|
+
* [1] endArchiveRoot
|
|
379
|
+
* [2] outHash
|
|
380
|
+
* [3] previousInboxRollingHash
|
|
381
|
+
* [4] endInboxRollingHash
|
|
382
|
+
* [5 .. 5+N-1] checkpointHeaderHashes[i] for i in 0..N-1 (N = MAX_CHECKPOINTS_PER_EPOCH)
|
|
383
|
+
* [5+N .. 5+3N-1] fees[i] = (recipient, value) for i in 0..N-1
|
|
384
|
+
* [5+3N .. 5+3N+4] EpochConstantData (chainId, version, vkTreeRoot, protocolContractsHash, proverId)
|
|
385
|
+
* [5+3N+5 ..] blobPublicInputs (FinalBlobAccumulator)
|
|
386
|
+
*/
|
|
387
|
+
async function reportPublicInputsMismatch(input: {
|
|
388
|
+
rollupPublicInputs: readonly Fr[];
|
|
389
|
+
argsPublicInputs: readonly Fr[];
|
|
390
|
+
fromCheckpoint: CheckpointNumber;
|
|
391
|
+
toCheckpoint: CheckpointNumber;
|
|
392
|
+
rollupContract: RollupContract;
|
|
393
|
+
log: Logger;
|
|
394
|
+
}): Promise<Error> {
|
|
395
|
+
const { rollupPublicInputs, argsPublicInputs, fromCheckpoint, toCheckpoint, rollupContract, log } = input;
|
|
396
|
+
const N = MAX_CHECKPOINTS_PER_EPOCH;
|
|
397
|
+
const headerHashesStart = 5;
|
|
398
|
+
const constantsStart = headerHashesStart + 3 * N;
|
|
399
|
+
const blobStart = constantsStart + 5;
|
|
400
|
+
const constantLabels = ['chainId', 'version', 'vkTreeRoot', 'protocolContractsHash', 'proverId'];
|
|
401
|
+
|
|
402
|
+
const diffs: { index: number; label: string; rollup: Fr; computed: Fr; checkpointIndex?: number }[] = [];
|
|
403
|
+
const len = Math.max(rollupPublicInputs.length, argsPublicInputs.length);
|
|
404
|
+
for (let i = 0; i < len; i++) {
|
|
405
|
+
const a = rollupPublicInputs[i] ?? Fr.ZERO;
|
|
406
|
+
const b = argsPublicInputs[i] ?? Fr.ZERO;
|
|
407
|
+
if (a.equals(b)) {
|
|
408
|
+
continue;
|
|
409
|
+
}
|
|
410
|
+
let label: string;
|
|
411
|
+
let checkpointIndex: number | undefined;
|
|
412
|
+
if (i === 0) {
|
|
413
|
+
label = 'previousArchiveRoot';
|
|
414
|
+
} else if (i === 1) {
|
|
415
|
+
label = 'endArchiveRoot';
|
|
416
|
+
} else if (i === 2) {
|
|
417
|
+
label = 'outHash';
|
|
418
|
+
} else if (i === 3) {
|
|
419
|
+
label = 'previousInboxRollingHash';
|
|
420
|
+
} else if (i === 4) {
|
|
421
|
+
label = 'endInboxRollingHash';
|
|
422
|
+
} else if (i < headerHashesStart + N) {
|
|
423
|
+
checkpointIndex = i - headerHashesStart;
|
|
424
|
+
label = `checkpointHeaderHashes[${checkpointIndex}]`;
|
|
425
|
+
} else if (i < headerHashesStart + 3 * N) {
|
|
426
|
+
const feePairIndex = i - (headerHashesStart + N);
|
|
427
|
+
const feeIndex = Math.floor(feePairIndex / 2);
|
|
428
|
+
const sub = feePairIndex % 2 === 0 ? 'recipient' : 'value';
|
|
429
|
+
label = `fees[${feeIndex}].${sub}`;
|
|
430
|
+
} else if (i < blobStart) {
|
|
431
|
+
label = `constants.${constantLabels[i - constantsStart]}`;
|
|
432
|
+
} else {
|
|
433
|
+
label = `blobPublicInputs[${i - blobStart}]`;
|
|
434
|
+
}
|
|
435
|
+
diffs.push({ index: i, label, rollup: a, computed: b, checkpointIndex });
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// For each mismatching checkpointHeaderHash, fetch the L1 CheckpointLog so the operator can
|
|
439
|
+
// see what was published on-chain alongside the prover's recomputed hash.
|
|
440
|
+
const onChainCheckpoints = await Promise.all(
|
|
441
|
+
diffs
|
|
442
|
+
.filter(d => d.checkpointIndex !== undefined)
|
|
443
|
+
.map(async d => {
|
|
444
|
+
const checkpointNumber = CheckpointNumber(fromCheckpoint + d.checkpointIndex!);
|
|
445
|
+
try {
|
|
446
|
+
const cp = await rollupContract.getCheckpoint(checkpointNumber);
|
|
447
|
+
return { checkpointIndex: d.checkpointIndex!, checkpointNumber, headerHash: cp.headerHash.toString() };
|
|
448
|
+
} catch (err) {
|
|
449
|
+
return { checkpointIndex: d.checkpointIndex!, checkpointNumber, error: (err as Error).message };
|
|
450
|
+
}
|
|
451
|
+
}),
|
|
452
|
+
);
|
|
453
|
+
|
|
454
|
+
log.error(`Root rollup public inputs mismatch`, undefined, {
|
|
455
|
+
fromCheckpoint,
|
|
456
|
+
toCheckpoint,
|
|
457
|
+
numDiffs: diffs.length,
|
|
458
|
+
diffs: diffs.map(d => ({
|
|
459
|
+
index: d.index,
|
|
460
|
+
label: d.label,
|
|
461
|
+
rollup: d.rollup.toString(),
|
|
462
|
+
computed: d.computed.toString(),
|
|
463
|
+
})),
|
|
464
|
+
onChainCheckpoints,
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
const fmt = (inputs: readonly Fr[]) => inputs.map(x => x.toString()).join(', ');
|
|
468
|
+
const summary = diffs.map(d => `[${d.index} ${d.label}] L1=${d.rollup} prover=${d.computed}`).join('\n');
|
|
469
|
+
return new Error(
|
|
470
|
+
`Root rollup public inputs mismatch (${diffs.length} fields differ):\n${summary}\n` +
|
|
471
|
+
`Rollup: ${fmt(rollupPublicInputs)}\nComputed:${fmt(argsPublicInputs)}`,
|
|
472
|
+
);
|
|
473
|
+
}
|