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