@aztec/prover-node 0.0.1-commit.d3ec352c → 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.d.ts +1 -1
- 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 +10 -10
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +27 -22
- package/dest/factory.d.ts +23 -16
- package/dest/factory.d.ts.map +1 -1
- package/dest/factory.js +49 -68
- package/dest/index.d.ts +3 -1
- package/dest/index.d.ts.map +1 -1
- package/dest/index.js +2 -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-proving-job-data.d.ts +2 -2
- package/dest/job/epoch-proving-job-data.d.ts.map +1 -1
- package/dest/job/epoch-proving-job-data.js +1 -1
- 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 +41 -4
- package/dest/metrics.d.ts.map +1 -1
- package/dest/metrics.js +125 -102
- 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 +12 -19
- 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 +31 -19
- package/dest/prover-node-publisher.d.ts.map +1 -1
- package/dest/prover-node-publisher.js +222 -78
- package/dest/prover-node.d.ts +122 -72
- package/dest/prover-node.d.ts.map +1 -1
- package/dest/prover-node.js +872 -229
- package/dest/prover-publisher-factory.d.ts +12 -6
- 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 +25 -23
- 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 +39 -34
- package/src/factory.ts +86 -113
- package/src/index.ts +2 -0
- package/src/job/checkpoint-prover.ts +468 -0
- package/src/job/epoch-proving-job-data.ts +1 -1
- package/src/job/epoch-session.ts +436 -0
- package/src/job/top-tree-job.ts +227 -0
- package/src/metrics.ts +143 -93
- package/src/monitors/epoch-monitor.ts +6 -14
- package/src/proof-publishing-service.ts +427 -0
- package/src/prover-node-publisher.ts +264 -98
- package/src/prover-node.ts +568 -259
- package/src/prover-publisher-factory.ts +22 -11
- 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/dest/job/epoch-proving-job.js +0 -363
- package/src/job/epoch-proving-job.ts +0 -426
|
@@ -1,26 +1,25 @@
|
|
|
1
1
|
import { BatchedBlob, getEthBlobEvaluationInputs } from '@aztec/blob-lib';
|
|
2
|
-
import {
|
|
3
|
-
import type {
|
|
4
|
-
import {
|
|
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';
|
|
5
5
|
import { CheckpointNumber, EpochNumber } from '@aztec/foundation/branded-types';
|
|
6
6
|
import { areArraysEqual } from '@aztec/foundation/collection';
|
|
7
|
+
import { Fr } from '@aztec/foundation/curves/bn254';
|
|
7
8
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
8
|
-
import {
|
|
9
|
-
import { createLogger } from '@aztec/foundation/log';
|
|
10
|
-
import type { Tuple } from '@aztec/foundation/serialize';
|
|
9
|
+
import { type Logger, type LoggerBindings, createLogger } from '@aztec/foundation/log';
|
|
11
10
|
import { Timer } from '@aztec/foundation/timer';
|
|
12
11
|
import { RollupAbi } from '@aztec/l1-artifacts';
|
|
13
12
|
import type { PublisherConfig, TxSenderConfig } from '@aztec/sequencer-client';
|
|
14
13
|
import { CommitteeAttestation, CommitteeAttestationsAndSigners } from '@aztec/stdlib/block';
|
|
15
14
|
import type { Proof } from '@aztec/stdlib/proofs';
|
|
16
|
-
import type {
|
|
15
|
+
import type { CheckpointHeader, RootRollupPublicInputs } from '@aztec/stdlib/rollup';
|
|
17
16
|
import type { L1PublishProofStats } from '@aztec/stdlib/stats';
|
|
18
17
|
import { type TelemetryClient, getTelemetryClient } from '@aztec/telemetry-client';
|
|
19
18
|
|
|
20
19
|
import { inspect } from 'util';
|
|
21
|
-
import { type Hex, type TransactionReceipt, encodeFunctionData } from 'viem';
|
|
20
|
+
import { type Hex, type TransactionReceipt, encodeFunctionData, formatEther, formatGwei } from 'viem';
|
|
22
21
|
|
|
23
|
-
import { ProverNodePublisherMetrics } from './metrics.js';
|
|
22
|
+
import { type EstimatedSubmitProofStats, ProverNodePublisherMetrics } from './metrics.js';
|
|
24
23
|
|
|
25
24
|
/** Arguments to the submitEpochProof method of the rollup contract */
|
|
26
25
|
export type L1SubmitEpochProofArgs = {
|
|
@@ -30,18 +29,19 @@ export type L1SubmitEpochProofArgs = {
|
|
|
30
29
|
endTimestamp: Fr;
|
|
31
30
|
outHash: Fr;
|
|
32
31
|
proverId: Fr;
|
|
33
|
-
|
|
32
|
+
headers: CheckpointHeader[];
|
|
34
33
|
proof: Proof;
|
|
35
34
|
};
|
|
36
35
|
|
|
37
36
|
export class ProverNodePublisher {
|
|
38
|
-
private interrupted = false;
|
|
39
37
|
private metrics: ProverNodePublisherMetrics;
|
|
40
38
|
|
|
41
|
-
protected log
|
|
39
|
+
protected log: Logger;
|
|
42
40
|
|
|
43
41
|
protected rollupContract: RollupContract;
|
|
44
42
|
|
|
43
|
+
protected proofSubmissionTarget: Hex;
|
|
44
|
+
|
|
45
45
|
public readonly l1TxUtils: L1TxUtils;
|
|
46
46
|
|
|
47
47
|
constructor(
|
|
@@ -49,14 +49,18 @@ export class ProverNodePublisher {
|
|
|
49
49
|
deps: {
|
|
50
50
|
rollupContract: RollupContract;
|
|
51
51
|
l1TxUtils: L1TxUtils;
|
|
52
|
+
proofSubmissionTarget?: EthAddress;
|
|
52
53
|
telemetry?: TelemetryClient;
|
|
53
54
|
},
|
|
55
|
+
bindings?: LoggerBindings,
|
|
54
56
|
) {
|
|
55
57
|
const telemetry = deps.telemetry ?? getTelemetryClient();
|
|
56
58
|
|
|
57
59
|
this.metrics = new ProverNodePublisherMetrics(telemetry, 'ProverNode');
|
|
60
|
+
this.log = createLogger('prover-node:l1-tx-publisher', bindings);
|
|
58
61
|
|
|
59
62
|
this.rollupContract = deps.rollupContract;
|
|
63
|
+
this.proofSubmissionTarget = deps.proofSubmissionTarget?.toString() ?? deps.rollupContract.address;
|
|
60
64
|
this.l1TxUtils = deps.l1TxUtils;
|
|
61
65
|
}
|
|
62
66
|
|
|
@@ -64,23 +68,6 @@ export class ProverNodePublisher {
|
|
|
64
68
|
return this.rollupContract;
|
|
65
69
|
}
|
|
66
70
|
|
|
67
|
-
/**
|
|
68
|
-
* Calling `interrupt` will cause any in progress call to `publishRollup` to return `false` asap.
|
|
69
|
-
* Be warned, the call may return false even if the tx subsequently gets successfully mined.
|
|
70
|
-
* In practice this shouldn't matter, as we'll only ever be calling `interrupt` when we know it's going to fail.
|
|
71
|
-
* A call to `restart` is required before you can continue publishing.
|
|
72
|
-
*/
|
|
73
|
-
public interrupt() {
|
|
74
|
-
this.interrupted = true;
|
|
75
|
-
this.l1TxUtils.interrupt();
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/** Restarts the publisher after calling `interrupt`. */
|
|
79
|
-
public restart() {
|
|
80
|
-
this.interrupted = false;
|
|
81
|
-
this.l1TxUtils.restart();
|
|
82
|
-
}
|
|
83
|
-
|
|
84
71
|
public getSenderAddress() {
|
|
85
72
|
return this.l1TxUtils.getSenderAddress();
|
|
86
73
|
}
|
|
@@ -93,53 +80,53 @@ export class ProverNodePublisher {
|
|
|
93
80
|
proof: Proof;
|
|
94
81
|
batchedBlobInputs: BatchedBlob;
|
|
95
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;
|
|
96
86
|
}): Promise<boolean> {
|
|
97
87
|
const { epochNumber, fromCheckpoint, toCheckpoint } = args;
|
|
98
88
|
const ctx = { epochNumber, fromCheckpoint, toCheckpoint };
|
|
99
89
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
await this.validateEpochProofSubmission(args);
|
|
104
|
-
|
|
105
|
-
const txReceipt = await this.sendSubmitEpochProofTx(args);
|
|
106
|
-
if (!txReceipt) {
|
|
107
|
-
return false;
|
|
108
|
-
}
|
|
90
|
+
const timer = new Timer();
|
|
91
|
+
// Validate epoch proof range and hashes are correct before submitting
|
|
92
|
+
await this.validateEpochProofSubmission(args);
|
|
109
93
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
} catch (err) {
|
|
116
|
-
this.log.warn(`Failed to record the ETH balance of the prover node: ${err}`);
|
|
117
|
-
}
|
|
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
|
+
}
|
|
118
99
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
calldataSize: tx!.calldataSize,
|
|
128
|
-
sender: tx!.sender,
|
|
129
|
-
blobDataGas: 0n,
|
|
130
|
-
blobGasUsed: 0n,
|
|
131
|
-
eventName: 'proof-published-to-l1',
|
|
132
|
-
};
|
|
133
|
-
this.log.info(`Published epoch proof to L1 rollup contract`, { ...stats, ...ctx });
|
|
134
|
-
this.metrics.recordSubmitProof(timer.ms(), stats);
|
|
135
|
-
return true;
|
|
136
|
-
}
|
|
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
|
+
}
|
|
137
108
|
|
|
138
|
-
|
|
139
|
-
|
|
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;
|
|
140
126
|
}
|
|
141
127
|
|
|
142
|
-
this.
|
|
128
|
+
this.metrics.recordFailedTx();
|
|
129
|
+
this.log.error(`Rollup submitEpochProof tx reverted ${txReceipt.transactionHash}`, undefined, ctx);
|
|
143
130
|
return false;
|
|
144
131
|
}
|
|
145
132
|
|
|
@@ -150,6 +137,7 @@ export class ProverNodePublisher {
|
|
|
150
137
|
proof: Proof;
|
|
151
138
|
batchedBlobInputs: BatchedBlob;
|
|
152
139
|
attestations: ViemCommitteeAttestation[];
|
|
140
|
+
headers: CheckpointHeader[];
|
|
153
141
|
}) {
|
|
154
142
|
const { fromCheckpoint, toCheckpoint, publicInputs, batchedBlobInputs } = args;
|
|
155
143
|
|
|
@@ -164,23 +152,23 @@ export class ProverNodePublisher {
|
|
|
164
152
|
// toCheckpoint can't be greater than pending
|
|
165
153
|
if (toCheckpoint > pending) {
|
|
166
154
|
throw new Error(
|
|
167
|
-
`Cannot submit epoch proof for ${fromCheckpoint}-${toCheckpoint} as
|
|
155
|
+
`Cannot submit epoch proof for ${fromCheckpoint}-${toCheckpoint} as proposed checkpoint is ${pending}`,
|
|
168
156
|
);
|
|
169
157
|
}
|
|
170
158
|
|
|
171
159
|
// Check the archive for the immediate checkpoint before the epoch
|
|
172
160
|
const checkpointLog = await this.rollupContract.getCheckpoint(CheckpointNumber(fromCheckpoint - 1));
|
|
173
|
-
if (publicInputs.previousArchiveRoot.
|
|
161
|
+
if (!publicInputs.previousArchiveRoot.equals(checkpointLog.archive)) {
|
|
174
162
|
throw new Error(
|
|
175
|
-
`Previous archive root mismatch: ${publicInputs.previousArchiveRoot.toString()} !== ${checkpointLog.archive}`,
|
|
163
|
+
`Previous archive root mismatch: ${publicInputs.previousArchiveRoot.toString()} !== ${checkpointLog.archive.toString()}`,
|
|
176
164
|
);
|
|
177
165
|
}
|
|
178
166
|
|
|
179
167
|
// Check the archive for the last checkpoint in the epoch
|
|
180
168
|
const endCheckpointLog = await this.rollupContract.getCheckpoint(toCheckpoint);
|
|
181
|
-
if (publicInputs.endArchiveRoot.
|
|
169
|
+
if (!publicInputs.endArchiveRoot.equals(endCheckpointLog.archive)) {
|
|
182
170
|
throw new Error(
|
|
183
|
-
`End archive root mismatch: ${publicInputs.endArchiveRoot.toString()} !== ${endCheckpointLog.archive}`,
|
|
171
|
+
`End archive root mismatch: ${publicInputs.endArchiveRoot.toString()} !== ${endCheckpointLog.archive.toString()}`,
|
|
184
172
|
);
|
|
185
173
|
}
|
|
186
174
|
|
|
@@ -198,21 +186,97 @@ export class ProverNodePublisher {
|
|
|
198
186
|
);
|
|
199
187
|
const argsPublicInputs = [...publicInputs.toFields()];
|
|
200
188
|
|
|
201
|
-
if (!areArraysEqual(rollupPublicInputs
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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
|
+
});
|
|
206
198
|
}
|
|
207
199
|
}
|
|
208
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
|
+
|
|
209
271
|
private async sendSubmitEpochProofTx(args: {
|
|
210
272
|
fromCheckpoint: CheckpointNumber;
|
|
211
273
|
toCheckpoint: CheckpointNumber;
|
|
274
|
+
deadline?: Date;
|
|
212
275
|
publicInputs: RootRollupPublicInputs;
|
|
213
276
|
proof: Proof;
|
|
214
277
|
batchedBlobInputs: BatchedBlob;
|
|
215
278
|
attestations: ViemCommitteeAttestation[];
|
|
279
|
+
headers: CheckpointHeader[];
|
|
216
280
|
}): Promise<TransactionReceipt | undefined> {
|
|
217
281
|
const txArgs = [this.getSubmitEpochProofArgs(args)] as const;
|
|
218
282
|
|
|
@@ -227,22 +291,28 @@ export class ProverNodePublisher {
|
|
|
227
291
|
args: txArgs,
|
|
228
292
|
});
|
|
229
293
|
try {
|
|
230
|
-
const { receipt } = await this.l1TxUtils.sendAndMonitorTransaction(
|
|
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
|
+
}
|
|
231
313
|
return receipt;
|
|
232
314
|
} catch (err) {
|
|
233
315
|
this.log.error(`Rollup submit epoch proof failed`, err);
|
|
234
|
-
const errorMsg = await this.l1TxUtils.tryGetErrorFromRevertedTx(
|
|
235
|
-
data,
|
|
236
|
-
{
|
|
237
|
-
args: [...txArgs],
|
|
238
|
-
functionName: 'submitEpochRootProof',
|
|
239
|
-
abi: RollupAbi,
|
|
240
|
-
address: this.rollupContract.address,
|
|
241
|
-
},
|
|
242
|
-
/*blobInputs*/ undefined,
|
|
243
|
-
/*stateOverride*/ [],
|
|
244
|
-
);
|
|
245
|
-
this.log.error(`Rollup submit epoch proof tx reverted. ${errorMsg}`);
|
|
246
316
|
return undefined;
|
|
247
317
|
}
|
|
248
318
|
}
|
|
@@ -253,6 +323,7 @@ export class ProverNodePublisher {
|
|
|
253
323
|
publicInputs: RootRollupPublicInputs;
|
|
254
324
|
batchedBlobInputs: BatchedBlob;
|
|
255
325
|
attestations: ViemCommitteeAttestation[];
|
|
326
|
+
headers: CheckpointHeader[];
|
|
256
327
|
}) {
|
|
257
328
|
// Returns arguments for EpochProofLib.sol -> getEpochProofPublicInputs()
|
|
258
329
|
return [
|
|
@@ -261,13 +332,10 @@ export class ProverNodePublisher {
|
|
|
261
332
|
{
|
|
262
333
|
previousArchive: args.publicInputs.previousArchiveRoot.toString(),
|
|
263
334
|
endArchive: args.publicInputs.endArchiveRoot.toString(),
|
|
335
|
+
outHash: args.publicInputs.outHash.toString(),
|
|
264
336
|
proverId: EthAddress.fromField(args.publicInputs.constants.proverId).toString(),
|
|
265
337
|
} /*_args*/,
|
|
266
|
-
|
|
267
|
-
i % 2 === 0
|
|
268
|
-
? args.publicInputs.fees[i / 2].recipient.toField().toString()
|
|
269
|
-
: args.publicInputs.fees[(i - 1) / 2].value.toString(),
|
|
270
|
-
) /*_fees*/,
|
|
338
|
+
args.headers.map(header => header.toViem()) /*_headers*/,
|
|
271
339
|
getEthBlobEvaluationInputs(args.batchedBlobInputs) /*_blobPublicInputs*/,
|
|
272
340
|
] as const;
|
|
273
341
|
}
|
|
@@ -279,6 +347,7 @@ export class ProverNodePublisher {
|
|
|
279
347
|
proof: Proof;
|
|
280
348
|
batchedBlobInputs: BatchedBlob;
|
|
281
349
|
attestations: ViemCommitteeAttestation[];
|
|
350
|
+
headers: CheckpointHeader[];
|
|
282
351
|
}) {
|
|
283
352
|
// Returns arguments for EpochProofLib.sol -> submitEpochRootProof()
|
|
284
353
|
const proofHex: Hex = `0x${args.proof.withoutPublicInputs().toString('hex')}`;
|
|
@@ -287,12 +356,109 @@ export class ProverNodePublisher {
|
|
|
287
356
|
start: argsArray[0],
|
|
288
357
|
end: argsArray[1],
|
|
289
358
|
args: argsArray[2],
|
|
290
|
-
|
|
291
|
-
attestations:
|
|
359
|
+
headers: argsArray[3],
|
|
360
|
+
attestations: CommitteeAttestationsAndSigners.packAttestations(
|
|
292
361
|
args.attestations.map(a => CommitteeAttestation.fromViem(a)),
|
|
293
|
-
)
|
|
362
|
+
),
|
|
294
363
|
blobInputs: argsArray[4],
|
|
295
364
|
proof: proofHex,
|
|
296
365
|
};
|
|
297
366
|
}
|
|
298
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
|
+
}
|