@aztec/prover-node 0.0.0-test.1 → 0.0.1-commit.017a351
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/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 +13 -0
- package/dest/actions/rerun-epoch-proving-job.d.ts.map +1 -0
- package/dest/actions/rerun-epoch-proving-job.js +120 -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/config.d.ts +17 -16
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +93 -28
- package/dest/factory.d.ts +26 -18
- package/dest/factory.d.ts.map +1 -1
- package/dest/factory.js +92 -43
- package/dest/index.d.ts +3 -2
- package/dest/index.d.ts.map +1 -1
- package/dest/index.js +2 -1
- package/dest/job/epoch-proving-job-data.d.ts +18 -0
- package/dest/job/epoch-proving-job-data.d.ts.map +1 -0
- package/dest/job/epoch-proving-job-data.js +59 -0
- package/dest/job/epoch-proving-job.d.ts +31 -18
- package/dest/job/epoch-proving-job.d.ts.map +1 -1
- package/dest/job/epoch-proving-job.js +756 -99
- package/dest/metrics.d.ts +50 -5
- package/dest/metrics.d.ts.map +1 -1
- package/dest/metrics.js +157 -72
- package/dest/monitors/epoch-monitor.d.ts +5 -2
- package/dest/monitors/epoch-monitor.d.ts.map +1 -1
- package/dest/monitors/epoch-monitor.js +24 -17
- package/dest/monitors/index.d.ts +1 -1
- package/dest/prover-node-publisher.d.ts +38 -19
- package/dest/prover-node-publisher.d.ts.map +1 -1
- package/dest/prover-node-publisher.js +288 -87
- package/dest/prover-node.d.ts +64 -49
- package/dest/prover-node.d.ts.map +1 -1
- package/dest/prover-node.js +650 -148
- package/dest/prover-publisher-factory.d.ts +25 -0
- package/dest/prover-publisher-factory.d.ts.map +1 -0
- package/dest/prover-publisher-factory.js +28 -0
- package/dest/test/index.d.ts +5 -3
- package/dest/test/index.d.ts.map +1 -1
- package/dest/test/index.js +1 -3
- package/package.json +40 -33
- package/src/actions/download-epoch-proving-job.ts +44 -0
- package/src/actions/index.ts +2 -0
- package/src/actions/rerun-epoch-proving-job.ts +74 -0
- package/src/actions/upload-epoch-proof-failure.ts +88 -0
- package/src/bin/run-failed-epoch.ts +79 -0
- package/src/config.ts +123 -45
- package/src/factory.ts +163 -67
- package/src/index.ts +2 -1
- package/src/job/epoch-proving-job-data.ts +82 -0
- package/src/job/epoch-proving-job.ts +364 -88
- package/src/metrics.ts +188 -70
- package/src/monitors/epoch-monitor.ts +23 -19
- package/src/prover-node-publisher.ts +368 -125
- package/src/prover-node.ts +292 -166
- package/src/prover-publisher-factory.ts +45 -0
- package/src/test/index.ts +7 -4
- package/dest/http.d.ts +0 -8
- package/dest/http.d.ts.map +0 -1
- package/dest/http.js +0 -9
- package/dest/prover-coordination/config.d.ts +0 -7
- package/dest/prover-coordination/config.d.ts.map +0 -1
- package/dest/prover-coordination/config.js +0 -11
- package/dest/prover-coordination/factory.d.ts +0 -22
- package/dest/prover-coordination/factory.d.ts.map +0 -1
- package/dest/prover-coordination/factory.js +0 -42
- package/dest/prover-coordination/index.d.ts +0 -3
- package/dest/prover-coordination/index.d.ts.map +0 -1
- package/dest/prover-coordination/index.js +0 -2
- package/src/http.ts +0 -13
- package/src/prover-coordination/config.ts +0 -17
- package/src/prover-coordination/factory.ts +0 -72
- package/src/prover-coordination/index.ts +0 -2
|
@@ -1,48 +1,47 @@
|
|
|
1
|
-
import {
|
|
2
|
-
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';
|
|
3
5
|
import { makeTuple } from '@aztec/foundation/array';
|
|
4
|
-
import {
|
|
6
|
+
import { CheckpointNumber, EpochNumber } from '@aztec/foundation/branded-types';
|
|
7
|
+
import { areArraysEqual } from '@aztec/foundation/collection';
|
|
8
|
+
import { Fr } from '@aztec/foundation/curves/bn254';
|
|
5
9
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import { InterruptibleSleep } from '@aztec/foundation/sleep';
|
|
10
|
+
import { type Logger, type LoggerBindings, createLogger } from '@aztec/foundation/log';
|
|
11
|
+
import { retryUntil } from '@aztec/foundation/retry';
|
|
12
|
+
import type { Tuple } from '@aztec/foundation/serialize';
|
|
10
13
|
import { Timer } from '@aztec/foundation/timer';
|
|
11
14
|
import { RollupAbi } from '@aztec/l1-artifacts';
|
|
12
15
|
import type { PublisherConfig, TxSenderConfig } from '@aztec/sequencer-client';
|
|
16
|
+
import { CommitteeAttestation, CommitteeAttestationsAndSigners } from '@aztec/stdlib/block';
|
|
17
|
+
import { getProofSubmissionDeadlineTimestamp } from '@aztec/stdlib/epoch-helpers';
|
|
13
18
|
import type { Proof } from '@aztec/stdlib/proofs';
|
|
14
19
|
import type { FeeRecipient, RootRollupPublicInputs } from '@aztec/stdlib/rollup';
|
|
15
20
|
import type { L1PublishProofStats } from '@aztec/stdlib/stats';
|
|
16
21
|
import { type TelemetryClient, getTelemetryClient } from '@aztec/telemetry-client';
|
|
17
22
|
|
|
18
|
-
import {
|
|
23
|
+
import { inspect } from 'util';
|
|
24
|
+
import { type Hex, type TransactionReceipt, encodeFunctionData, formatEther, formatGwei } from 'viem';
|
|
19
25
|
|
|
20
|
-
import {
|
|
26
|
+
import { type EstimatedSubmitProofStats, ProverNodePublisherMetrics } from './metrics.js';
|
|
21
27
|
|
|
22
|
-
/**
|
|
23
|
-
* Stats for a sent transaction.
|
|
24
|
-
*/
|
|
25
28
|
/** Arguments to the submitEpochProof method of the rollup contract */
|
|
26
29
|
export type L1SubmitEpochProofArgs = {
|
|
27
30
|
epochSize: number;
|
|
28
31
|
previousArchive: Fr;
|
|
29
32
|
endArchive: Fr;
|
|
30
|
-
previousBlockHash: Fr;
|
|
31
|
-
endBlockHash: Fr;
|
|
32
33
|
endTimestamp: Fr;
|
|
33
34
|
outHash: Fr;
|
|
34
35
|
proverId: Fr;
|
|
35
|
-
fees: Tuple<FeeRecipient, typeof
|
|
36
|
+
fees: Tuple<FeeRecipient, typeof MAX_CHECKPOINTS_PER_EPOCH>;
|
|
36
37
|
proof: Proof;
|
|
37
38
|
};
|
|
38
39
|
|
|
39
40
|
export class ProverNodePublisher {
|
|
40
|
-
private interruptibleSleep = new InterruptibleSleep();
|
|
41
|
-
private sleepTimeMs: number;
|
|
42
41
|
private interrupted = false;
|
|
43
|
-
private metrics:
|
|
42
|
+
private metrics: ProverNodePublisherMetrics;
|
|
44
43
|
|
|
45
|
-
protected log
|
|
44
|
+
protected log: Logger;
|
|
46
45
|
|
|
47
46
|
protected rollupContract: RollupContract;
|
|
48
47
|
|
|
@@ -55,17 +54,21 @@ export class ProverNodePublisher {
|
|
|
55
54
|
l1TxUtils: L1TxUtils;
|
|
56
55
|
telemetry?: TelemetryClient;
|
|
57
56
|
},
|
|
57
|
+
bindings?: LoggerBindings,
|
|
58
58
|
) {
|
|
59
|
-
this.sleepTimeMs = config?.l1PublishRetryIntervalMS ?? 60_000;
|
|
60
|
-
|
|
61
59
|
const telemetry = deps.telemetry ?? getTelemetryClient();
|
|
62
60
|
|
|
63
|
-
this.metrics = new
|
|
61
|
+
this.metrics = new ProverNodePublisherMetrics(telemetry, 'ProverNode');
|
|
62
|
+
this.log = createLogger('prover-node:l1-tx-publisher', bindings);
|
|
64
63
|
|
|
65
64
|
this.rollupContract = deps.rollupContract;
|
|
66
65
|
this.l1TxUtils = deps.l1TxUtils;
|
|
67
66
|
}
|
|
68
67
|
|
|
68
|
+
public getRollupContract() {
|
|
69
|
+
return this.rollupContract;
|
|
70
|
+
}
|
|
71
|
+
|
|
69
72
|
/**
|
|
70
73
|
* Calling `interrupt` will cause any in progress call to `publishRollup` to return `false` asap.
|
|
71
74
|
* Be warned, the call may return false even if the tx subsequently gets successfully mined.
|
|
@@ -74,46 +77,58 @@ export class ProverNodePublisher {
|
|
|
74
77
|
*/
|
|
75
78
|
public interrupt() {
|
|
76
79
|
this.interrupted = true;
|
|
77
|
-
this.
|
|
80
|
+
this.l1TxUtils.interrupt();
|
|
78
81
|
}
|
|
79
82
|
|
|
80
83
|
/** Restarts the publisher after calling `interrupt`. */
|
|
81
84
|
public restart() {
|
|
82
85
|
this.interrupted = false;
|
|
86
|
+
this.l1TxUtils.restart();
|
|
83
87
|
}
|
|
84
88
|
|
|
85
89
|
public getSenderAddress() {
|
|
86
|
-
return
|
|
90
|
+
return this.l1TxUtils.getSenderAddress();
|
|
87
91
|
}
|
|
88
92
|
|
|
89
93
|
public async submitEpochProof(args: {
|
|
90
|
-
epochNumber:
|
|
91
|
-
|
|
92
|
-
|
|
94
|
+
epochNumber: EpochNumber;
|
|
95
|
+
fromCheckpoint: CheckpointNumber;
|
|
96
|
+
toCheckpoint: CheckpointNumber;
|
|
93
97
|
publicInputs: RootRollupPublicInputs;
|
|
94
98
|
proof: Proof;
|
|
99
|
+
batchedBlobInputs: BatchedBlob;
|
|
100
|
+
attestations: ViemCommitteeAttestation[];
|
|
95
101
|
}): Promise<boolean> {
|
|
96
|
-
const { epochNumber,
|
|
97
|
-
const ctx = { epochNumber,
|
|
102
|
+
const { epochNumber, fromCheckpoint, toCheckpoint } = args;
|
|
103
|
+
const ctx = { epochNumber, fromCheckpoint, toCheckpoint };
|
|
104
|
+
|
|
98
105
|
if (!this.interrupted) {
|
|
99
|
-
|
|
106
|
+
if (!(await this.waitUntilStartBuildsOnProven(args))) {
|
|
107
|
+
this.log.verbose('Checkpoint data syncing interrupted', ctx);
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
100
110
|
|
|
111
|
+
const timer = new Timer();
|
|
101
112
|
// Validate epoch proof range and hashes are correct before submitting
|
|
102
113
|
await this.validateEpochProofSubmission(args);
|
|
103
114
|
|
|
104
115
|
const txReceipt = await this.sendSubmitEpochProofTx(args);
|
|
105
116
|
if (!txReceipt) {
|
|
117
|
+
this.log.error(`Failed to mine submitEpochProof tx`, undefined, ctx);
|
|
106
118
|
return false;
|
|
107
119
|
}
|
|
108
120
|
|
|
109
121
|
try {
|
|
110
|
-
this.metrics.recordSenderBalance(
|
|
122
|
+
this.metrics.recordSenderBalance(
|
|
123
|
+
await this.l1TxUtils.getSenderBalance(),
|
|
124
|
+
this.l1TxUtils.getSenderAddress().toString(),
|
|
125
|
+
);
|
|
111
126
|
} catch (err) {
|
|
112
127
|
this.log.warn(`Failed to record the ETH balance of the prover node: ${err}`);
|
|
113
128
|
}
|
|
114
129
|
|
|
115
130
|
// Tx was mined successfully
|
|
116
|
-
if (txReceipt.status) {
|
|
131
|
+
if (txReceipt.status === 'success') {
|
|
117
132
|
const tx = await this.l1TxUtils.getTransactionStats(txReceipt.transactionHash);
|
|
118
133
|
const stats: L1PublishProofStats = {
|
|
119
134
|
gasPrice: txReceipt.effectiveGasPrice,
|
|
@@ -132,155 +147,383 @@ export class ProverNodePublisher {
|
|
|
132
147
|
}
|
|
133
148
|
|
|
134
149
|
this.metrics.recordFailedTx();
|
|
135
|
-
this.log.error(`Rollup
|
|
136
|
-
await this.sleepOrInterrupted();
|
|
150
|
+
this.log.error(`Rollup submitEpochProof tx reverted ${txReceipt.transactionHash}`, undefined, ctx);
|
|
137
151
|
}
|
|
138
152
|
|
|
139
|
-
this.log.verbose('
|
|
153
|
+
this.log.verbose('Checkpoint data syncing interrupted', ctx);
|
|
140
154
|
return false;
|
|
141
155
|
}
|
|
142
156
|
|
|
157
|
+
private async waitUntilStartBuildsOnProven(args: { epochNumber: EpochNumber; fromCheckpoint: CheckpointNumber }) {
|
|
158
|
+
const { epochNumber, fromCheckpoint } = args;
|
|
159
|
+
const provenCheckpoint = await this.getProvenCheckpoint();
|
|
160
|
+
if (this.isStartBuildingOnProven(fromCheckpoint, provenCheckpoint)) {
|
|
161
|
+
return true;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const timeout = await this.getSecondsUntilProofSubmissionWindowEnd(epochNumber);
|
|
165
|
+
this.log.info(`Waiting for proven checkpoint to reach proof start`, {
|
|
166
|
+
epochNumber,
|
|
167
|
+
fromCheckpoint,
|
|
168
|
+
provenCheckpoint,
|
|
169
|
+
timeout,
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
await retryUntil(
|
|
173
|
+
async () => {
|
|
174
|
+
if (this.interrupted) {
|
|
175
|
+
return true;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const proven = await this.getProvenCheckpoint();
|
|
179
|
+
this.log.verbose(`Proven checkpoint is at ${proven} (waiting for ${fromCheckpoint - 1})`, { epochNumber });
|
|
180
|
+
return this.isStartBuildingOnProven(fromCheckpoint, proven) ? true : undefined;
|
|
181
|
+
},
|
|
182
|
+
`proven checkpoint to reach ${fromCheckpoint - 1}`,
|
|
183
|
+
timeout,
|
|
184
|
+
4,
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
return !this.interrupted;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
private async getProvenCheckpoint() {
|
|
191
|
+
return (await this.rollupContract.getTips()).proven;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
private isStartBuildingOnProven(fromCheckpoint: CheckpointNumber, provenCheckpoint: CheckpointNumber) {
|
|
195
|
+
return fromCheckpoint - 1 <= provenCheckpoint;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
private async getSecondsUntilProofSubmissionWindowEnd(epochNumber: EpochNumber) {
|
|
199
|
+
const deadline = getProofSubmissionDeadlineTimestamp(epochNumber, await this.rollupContract.getRollupConstants());
|
|
200
|
+
const now = BigInt(Math.floor(Date.now() / 1000));
|
|
201
|
+
return Math.max(Number(deadline - now), 0.001);
|
|
202
|
+
}
|
|
203
|
+
|
|
143
204
|
private async validateEpochProofSubmission(args: {
|
|
144
|
-
|
|
145
|
-
|
|
205
|
+
fromCheckpoint: CheckpointNumber;
|
|
206
|
+
toCheckpoint: CheckpointNumber;
|
|
146
207
|
publicInputs: RootRollupPublicInputs;
|
|
147
208
|
proof: Proof;
|
|
209
|
+
batchedBlobInputs: BatchedBlob;
|
|
210
|
+
attestations: ViemCommitteeAttestation[];
|
|
148
211
|
}) {
|
|
149
|
-
const {
|
|
212
|
+
const { fromCheckpoint, toCheckpoint, publicInputs, batchedBlobInputs } = args;
|
|
150
213
|
|
|
151
|
-
// Check that the
|
|
152
|
-
const {
|
|
153
|
-
if
|
|
154
|
-
|
|
214
|
+
// Check that the checkpoint numbers match the expected epoch to be proven
|
|
215
|
+
const { pending, proven } = await this.rollupContract.getTips();
|
|
216
|
+
// Don't publish if proven is beyond our toCheckpoint, pointless to do so
|
|
217
|
+
if (proven > toCheckpoint) {
|
|
218
|
+
throw new Error(
|
|
219
|
+
`Cannot submit epoch proof for ${fromCheckpoint}-${toCheckpoint} as proven checkpoint is ${proven}`,
|
|
220
|
+
);
|
|
155
221
|
}
|
|
156
|
-
|
|
157
|
-
|
|
222
|
+
// toCheckpoint can't be greater than pending
|
|
223
|
+
if (toCheckpoint > pending) {
|
|
224
|
+
throw new Error(
|
|
225
|
+
`Cannot submit epoch proof for ${fromCheckpoint}-${toCheckpoint} as proposed checkpoint is ${pending}`,
|
|
226
|
+
);
|
|
158
227
|
}
|
|
159
228
|
|
|
160
|
-
// Check the
|
|
161
|
-
const
|
|
162
|
-
if (publicInputs.
|
|
229
|
+
// Check the archive for the immediate checkpoint before the epoch
|
|
230
|
+
const checkpointLog = await this.rollupContract.getCheckpoint(CheckpointNumber(fromCheckpoint - 1));
|
|
231
|
+
if (!publicInputs.previousArchiveRoot.equals(checkpointLog.archive)) {
|
|
163
232
|
throw new Error(
|
|
164
|
-
`Previous archive root mismatch: ${publicInputs.
|
|
233
|
+
`Previous archive root mismatch: ${publicInputs.previousArchiveRoot.toString()} !== ${checkpointLog.archive.toString()}`,
|
|
165
234
|
);
|
|
166
235
|
}
|
|
167
|
-
|
|
168
|
-
|
|
236
|
+
|
|
237
|
+
// Check the archive for the last checkpoint in the epoch
|
|
238
|
+
const endCheckpointLog = await this.rollupContract.getCheckpoint(toCheckpoint);
|
|
239
|
+
if (!publicInputs.endArchiveRoot.equals(endCheckpointLog.archive)) {
|
|
169
240
|
throw new Error(
|
|
170
|
-
`
|
|
241
|
+
`End archive root mismatch: ${publicInputs.endArchiveRoot.toString()} !== ${endCheckpointLog.archive.toString()}`,
|
|
171
242
|
);
|
|
172
243
|
}
|
|
173
244
|
|
|
174
|
-
// Check the
|
|
175
|
-
const
|
|
176
|
-
if (publicInputs.
|
|
245
|
+
// Check the batched blob inputs from the root rollup against the batched blob computed in ts
|
|
246
|
+
const finalBlobAccumulator = batchedBlobInputs.toFinalBlobAccumulator();
|
|
247
|
+
if (!publicInputs.blobPublicInputs.equals(finalBlobAccumulator)) {
|
|
177
248
|
throw new Error(
|
|
178
|
-
`
|
|
249
|
+
`Batched blob mismatch: ${inspect(publicInputs.blobPublicInputs)} !== ${inspect(finalBlobAccumulator)}`,
|
|
179
250
|
);
|
|
180
251
|
}
|
|
181
|
-
if (publicInputs.endBlockHash.toString() !== endBlockLog.blockHash) {
|
|
182
|
-
throw new Error(`End block hash mismatch: ${publicInputs.endBlockHash.toString()} !== ${endBlockLog.blockHash}`);
|
|
183
|
-
}
|
|
184
252
|
|
|
185
253
|
// Compare the public inputs computed by the contract with the ones injected
|
|
186
|
-
const rollupPublicInputs = await this.rollupContract.getEpochProofPublicInputs(
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
254
|
+
const rollupPublicInputs = await this.rollupContract.getEpochProofPublicInputs(
|
|
255
|
+
this.getEpochProofPublicInputsArgs(args),
|
|
256
|
+
);
|
|
257
|
+
const argsPublicInputs = [...publicInputs.toFields()];
|
|
258
|
+
|
|
259
|
+
if (!areArraysEqual(rollupPublicInputs, argsPublicInputs, (a, b) => a.equals(b))) {
|
|
260
|
+
throw await reportPublicInputsMismatch({
|
|
261
|
+
rollupPublicInputs,
|
|
262
|
+
argsPublicInputs,
|
|
263
|
+
fromCheckpoint,
|
|
264
|
+
toCheckpoint,
|
|
265
|
+
rollupContract: this.rollupContract,
|
|
266
|
+
log: this.log,
|
|
267
|
+
});
|
|
197
268
|
}
|
|
198
269
|
}
|
|
199
270
|
|
|
271
|
+
/**
|
|
272
|
+
* Estimates what submitting the epoch proof would have cost on L1 without actually sending it.
|
|
273
|
+
* Runs the same validation as `submitEpochProof`, encodes the calldata, estimates gas, and records metrics.
|
|
274
|
+
* Used when proof publishing is disabled (e.g. PROVER_NODE_DISABLE_PROOF_PUBLISH=true on mainnet).
|
|
275
|
+
*/
|
|
276
|
+
public async analyzeEpochProofSubmission(args: {
|
|
277
|
+
epochNumber: EpochNumber;
|
|
278
|
+
fromCheckpoint: CheckpointNumber;
|
|
279
|
+
toCheckpoint: CheckpointNumber;
|
|
280
|
+
publicInputs: RootRollupPublicInputs;
|
|
281
|
+
proof: Proof;
|
|
282
|
+
batchedBlobInputs: BatchedBlob;
|
|
283
|
+
attestations: ViemCommitteeAttestation[];
|
|
284
|
+
}): Promise<void> {
|
|
285
|
+
const { epochNumber, fromCheckpoint, toCheckpoint } = args;
|
|
286
|
+
|
|
287
|
+
await this.validateEpochProofSubmission(args);
|
|
288
|
+
|
|
289
|
+
const data = this.encodeSubmitEpochProofCalldata(args);
|
|
290
|
+
const senderAddress = this.l1TxUtils.getSenderAddress();
|
|
291
|
+
|
|
292
|
+
const [gasLimit, gasPrice, latestBlock] = await Promise.all([
|
|
293
|
+
this.l1TxUtils.estimateGas(senderAddress.toString() as `0x${string}`, { to: this.rollupContract.address, data }),
|
|
294
|
+
this.l1TxUtils.getGasPrice(),
|
|
295
|
+
this.l1TxUtils.client.getBlock({ blockTag: 'latest' }),
|
|
296
|
+
]);
|
|
297
|
+
|
|
298
|
+
const baseFeePerGas = latestBlock.baseFeePerGas ?? 0n;
|
|
299
|
+
const { maxPriorityFeePerGas } = gasPrice;
|
|
300
|
+
|
|
301
|
+
const effectiveFeePerGas = baseFeePerGas + maxPriorityFeePerGas;
|
|
302
|
+
const estimatedTotalFee = gasLimit * effectiveFeePerGas;
|
|
303
|
+
|
|
304
|
+
const stats: EstimatedSubmitProofStats = {
|
|
305
|
+
gasLimit,
|
|
306
|
+
baseFeePerGas,
|
|
307
|
+
maxPriorityFeePerGas,
|
|
308
|
+
estimatedTotalFee,
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
this.log.info(`Estimated epoch proof submission cost (not submitted)`, {
|
|
312
|
+
epochNumber,
|
|
313
|
+
fromCheckpoint,
|
|
314
|
+
toCheckpoint,
|
|
315
|
+
gasLimit: gasLimit.toString(),
|
|
316
|
+
baseFeePerGas: formatGwei(baseFeePerGas),
|
|
317
|
+
maxPriorityFeePerGas: formatGwei(maxPriorityFeePerGas),
|
|
318
|
+
estimatedTotalFeeEth: formatEther(estimatedTotalFee),
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
this.metrics.recordEstimatedSubmitProof(stats);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
private encodeSubmitEpochProofCalldata(args: {
|
|
325
|
+
fromCheckpoint: CheckpointNumber;
|
|
326
|
+
toCheckpoint: CheckpointNumber;
|
|
327
|
+
publicInputs: RootRollupPublicInputs;
|
|
328
|
+
proof: Proof;
|
|
329
|
+
batchedBlobInputs: BatchedBlob;
|
|
330
|
+
attestations: ViemCommitteeAttestation[];
|
|
331
|
+
}): Hex {
|
|
332
|
+
return encodeFunctionData({
|
|
333
|
+
abi: RollupAbi,
|
|
334
|
+
functionName: 'submitEpochRootProof',
|
|
335
|
+
args: [this.getSubmitEpochProofArgs(args)],
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
|
|
200
339
|
private async sendSubmitEpochProofTx(args: {
|
|
201
|
-
|
|
202
|
-
|
|
340
|
+
fromCheckpoint: CheckpointNumber;
|
|
341
|
+
toCheckpoint: CheckpointNumber;
|
|
203
342
|
publicInputs: RootRollupPublicInputs;
|
|
204
343
|
proof: Proof;
|
|
344
|
+
batchedBlobInputs: BatchedBlob;
|
|
345
|
+
attestations: ViemCommitteeAttestation[];
|
|
205
346
|
}): Promise<TransactionReceipt | undefined> {
|
|
206
|
-
const
|
|
207
|
-
const argsArray = this.getSubmitEpochProofArgs(args);
|
|
208
|
-
|
|
209
|
-
const txArgs = [
|
|
210
|
-
{
|
|
211
|
-
start: argsArray[0],
|
|
212
|
-
end: argsArray[1],
|
|
213
|
-
args: argsArray[2],
|
|
214
|
-
fees: argsArray[3],
|
|
215
|
-
blobPublicInputs: argsArray[4],
|
|
216
|
-
aggregationObject: argsArray[5],
|
|
217
|
-
proof: proofHex,
|
|
218
|
-
},
|
|
219
|
-
] as const;
|
|
347
|
+
const txArgs = [this.getSubmitEpochProofArgs(args)] as const;
|
|
220
348
|
|
|
221
|
-
this.log.info(`
|
|
349
|
+
this.log.info(`Submitting epoch proof to L1 rollup contract`, {
|
|
350
|
+
proofSize: args.proof.withoutPublicInputs().length,
|
|
351
|
+
fromCheckpoint: args.fromCheckpoint,
|
|
352
|
+
toCheckpoint: args.toCheckpoint,
|
|
353
|
+
});
|
|
222
354
|
const data = encodeFunctionData({
|
|
223
355
|
abi: RollupAbi,
|
|
224
356
|
functionName: 'submitEpochRootProof',
|
|
225
357
|
args: txArgs,
|
|
226
358
|
});
|
|
227
359
|
try {
|
|
228
|
-
const { receipt } = await this.l1TxUtils.sendAndMonitorTransaction({
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
360
|
+
const { receipt } = await this.l1TxUtils.sendAndMonitorTransaction({ to: this.rollupContract.address, data });
|
|
361
|
+
if (receipt.status !== 'success') {
|
|
362
|
+
const errorMsg = await this.l1TxUtils.tryGetErrorFromRevertedTx(
|
|
363
|
+
data,
|
|
364
|
+
{
|
|
365
|
+
args: [...txArgs],
|
|
366
|
+
functionName: 'submitEpochRootProof',
|
|
367
|
+
abi: RollupAbi,
|
|
368
|
+
address: this.rollupContract.address,
|
|
369
|
+
},
|
|
370
|
+
/*blobInputs*/ undefined,
|
|
371
|
+
/*stateOverride*/ [],
|
|
372
|
+
);
|
|
373
|
+
this.log.error(`Rollup submit epoch proof tx reverted with ${errorMsg ?? 'unknown error'}`);
|
|
374
|
+
return undefined;
|
|
375
|
+
}
|
|
233
376
|
return receipt;
|
|
234
377
|
} catch (err) {
|
|
235
378
|
this.log.error(`Rollup submit epoch proof failed`, err);
|
|
236
|
-
const errorMsg = await this.l1TxUtils.tryGetErrorFromRevertedTx(
|
|
237
|
-
data,
|
|
238
|
-
{
|
|
239
|
-
args: [...txArgs],
|
|
240
|
-
functionName: 'submitEpochRootProof',
|
|
241
|
-
abi: RollupAbi,
|
|
242
|
-
address: this.rollupContract.address,
|
|
243
|
-
},
|
|
244
|
-
/*blobInputs*/ undefined,
|
|
245
|
-
/*stateOverride*/ [],
|
|
246
|
-
);
|
|
247
|
-
this.log.error(`Rollup submit epoch proof tx reverted. ${errorMsg}`);
|
|
248
379
|
return undefined;
|
|
249
380
|
}
|
|
250
381
|
}
|
|
251
382
|
|
|
252
|
-
private
|
|
253
|
-
|
|
254
|
-
|
|
383
|
+
private getEpochProofPublicInputsArgs(args: {
|
|
384
|
+
fromCheckpoint: CheckpointNumber;
|
|
385
|
+
toCheckpoint: CheckpointNumber;
|
|
255
386
|
publicInputs: RootRollupPublicInputs;
|
|
256
|
-
|
|
387
|
+
batchedBlobInputs: BatchedBlob;
|
|
388
|
+
attestations: ViemCommitteeAttestation[];
|
|
257
389
|
}) {
|
|
390
|
+
// Returns arguments for EpochProofLib.sol -> getEpochProofPublicInputs()
|
|
258
391
|
return [
|
|
259
|
-
BigInt(args.
|
|
260
|
-
BigInt(args.
|
|
392
|
+
BigInt(args.fromCheckpoint) /*_start*/,
|
|
393
|
+
BigInt(args.toCheckpoint) /*_end*/,
|
|
261
394
|
{
|
|
262
|
-
previousArchive: args.publicInputs.
|
|
263
|
-
endArchive: args.publicInputs.
|
|
264
|
-
previousBlockHash: args.publicInputs.previousBlockHash.toString(),
|
|
265
|
-
endBlockHash: args.publicInputs.endBlockHash.toString(),
|
|
266
|
-
endTimestamp: args.publicInputs.endTimestamp.toBigInt(),
|
|
395
|
+
previousArchive: args.publicInputs.previousArchiveRoot.toString(),
|
|
396
|
+
endArchive: args.publicInputs.endArchiveRoot.toString(),
|
|
267
397
|
outHash: args.publicInputs.outHash.toString(),
|
|
268
|
-
proverId: EthAddress.fromField(args.publicInputs.proverId).toString(),
|
|
269
|
-
}
|
|
270
|
-
makeTuple(
|
|
398
|
+
proverId: EthAddress.fromField(args.publicInputs.constants.proverId).toString(),
|
|
399
|
+
} /*_args*/,
|
|
400
|
+
makeTuple(MAX_CHECKPOINTS_PER_EPOCH * 2, i =>
|
|
271
401
|
i % 2 === 0
|
|
272
402
|
? args.publicInputs.fees[i / 2].recipient.toField().toString()
|
|
273
403
|
: args.publicInputs.fees[(i - 1) / 2].value.toString(),
|
|
274
|
-
)
|
|
275
|
-
|
|
276
|
-
.filter((_, i) => i < args.toBlock - args.fromBlock + 1)
|
|
277
|
-
.map(b => b.toString())
|
|
278
|
-
.join(``)}`,
|
|
279
|
-
`0x${serializeToBuffer(args.proof.extractAggregationObject()).toString('hex')}`,
|
|
404
|
+
) /*_fees*/,
|
|
405
|
+
getEthBlobEvaluationInputs(args.batchedBlobInputs) /*_blobPublicInputs*/,
|
|
280
406
|
] as const;
|
|
281
407
|
}
|
|
282
408
|
|
|
283
|
-
|
|
284
|
-
|
|
409
|
+
private getSubmitEpochProofArgs(args: {
|
|
410
|
+
fromCheckpoint: CheckpointNumber;
|
|
411
|
+
toCheckpoint: CheckpointNumber;
|
|
412
|
+
publicInputs: RootRollupPublicInputs;
|
|
413
|
+
proof: Proof;
|
|
414
|
+
batchedBlobInputs: BatchedBlob;
|
|
415
|
+
attestations: ViemCommitteeAttestation[];
|
|
416
|
+
}) {
|
|
417
|
+
// Returns arguments for EpochProofLib.sol -> submitEpochRootProof()
|
|
418
|
+
const proofHex: Hex = `0x${args.proof.withoutPublicInputs().toString('hex')}`;
|
|
419
|
+
const argsArray = this.getEpochProofPublicInputsArgs(args);
|
|
420
|
+
return {
|
|
421
|
+
start: argsArray[0],
|
|
422
|
+
end: argsArray[1],
|
|
423
|
+
args: argsArray[2],
|
|
424
|
+
fees: argsArray[3],
|
|
425
|
+
attestations: CommitteeAttestationsAndSigners.packAttestations(
|
|
426
|
+
args.attestations.map(a => CommitteeAttestation.fromViem(a)),
|
|
427
|
+
),
|
|
428
|
+
blobInputs: argsArray[4],
|
|
429
|
+
proof: proofHex,
|
|
430
|
+
};
|
|
285
431
|
}
|
|
286
432
|
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Decodes a `Root rollup public inputs mismatch`, fetches the on-chain CheckpointLog for any
|
|
436
|
+
* mismatching `checkpointHeaderHashes[i]`, emits a structured error log, and returns a thrown-ready
|
|
437
|
+
* Error with a human-readable summary.
|
|
438
|
+
*
|
|
439
|
+
* Layout of `RootRollupPublicInputs.toFields()`:
|
|
440
|
+
* [0] previousArchiveRoot
|
|
441
|
+
* [1] endArchiveRoot
|
|
442
|
+
* [2] outHash
|
|
443
|
+
* [3 .. 3+N-1] checkpointHeaderHashes[i] for i in 0..N-1 (N = MAX_CHECKPOINTS_PER_EPOCH)
|
|
444
|
+
* [3+N .. 3+3N-1] fees[i] = (recipient, value) for i in 0..N-1
|
|
445
|
+
* [3+3N .. 3+3N+4] EpochConstantData (chainId, version, vkTreeRoot, protocolContractsHash, proverId)
|
|
446
|
+
* [3+3N+5 ..] blobPublicInputs (FinalBlobAccumulator)
|
|
447
|
+
*/
|
|
448
|
+
async function reportPublicInputsMismatch(input: {
|
|
449
|
+
rollupPublicInputs: readonly Fr[];
|
|
450
|
+
argsPublicInputs: readonly Fr[];
|
|
451
|
+
fromCheckpoint: CheckpointNumber;
|
|
452
|
+
toCheckpoint: CheckpointNumber;
|
|
453
|
+
rollupContract: RollupContract;
|
|
454
|
+
log: Logger;
|
|
455
|
+
}): Promise<Error> {
|
|
456
|
+
const { rollupPublicInputs, argsPublicInputs, fromCheckpoint, toCheckpoint, rollupContract, log } = input;
|
|
457
|
+
const N = MAX_CHECKPOINTS_PER_EPOCH;
|
|
458
|
+
const constantsStart = 3 + 3 * N;
|
|
459
|
+
const blobStart = constantsStart + 5;
|
|
460
|
+
const constantLabels = ['chainId', 'version', 'vkTreeRoot', 'protocolContractsHash', 'proverId'];
|
|
461
|
+
|
|
462
|
+
const diffs: { index: number; label: string; rollup: Fr; computed: Fr; checkpointIndex?: number }[] = [];
|
|
463
|
+
const len = Math.max(rollupPublicInputs.length, argsPublicInputs.length);
|
|
464
|
+
for (let i = 0; i < len; i++) {
|
|
465
|
+
const a = rollupPublicInputs[i] ?? Fr.ZERO;
|
|
466
|
+
const b = argsPublicInputs[i] ?? Fr.ZERO;
|
|
467
|
+
if (a.equals(b)) {
|
|
468
|
+
continue;
|
|
469
|
+
}
|
|
470
|
+
let label: string;
|
|
471
|
+
let checkpointIndex: number | undefined;
|
|
472
|
+
if (i === 0) {
|
|
473
|
+
label = 'previousArchiveRoot';
|
|
474
|
+
} else if (i === 1) {
|
|
475
|
+
label = 'endArchiveRoot';
|
|
476
|
+
} else if (i === 2) {
|
|
477
|
+
label = 'outHash';
|
|
478
|
+
} else if (i < 3 + N) {
|
|
479
|
+
checkpointIndex = i - 3;
|
|
480
|
+
label = `checkpointHeaderHashes[${checkpointIndex}]`;
|
|
481
|
+
} else if (i < 3 + 3 * N) {
|
|
482
|
+
const feePairIndex = i - (3 + N);
|
|
483
|
+
const feeIndex = Math.floor(feePairIndex / 2);
|
|
484
|
+
const sub = feePairIndex % 2 === 0 ? 'recipient' : 'value';
|
|
485
|
+
label = `fees[${feeIndex}].${sub}`;
|
|
486
|
+
} else if (i < blobStart) {
|
|
487
|
+
label = `constants.${constantLabels[i - constantsStart]}`;
|
|
488
|
+
} else {
|
|
489
|
+
label = `blobPublicInputs[${i - blobStart}]`;
|
|
490
|
+
}
|
|
491
|
+
diffs.push({ index: i, label, rollup: a, computed: b, checkpointIndex });
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// For each mismatching checkpointHeaderHash, fetch the L1 CheckpointLog so the operator can
|
|
495
|
+
// see what was published on-chain alongside the prover's recomputed hash.
|
|
496
|
+
const onChainCheckpoints = await Promise.all(
|
|
497
|
+
diffs
|
|
498
|
+
.filter(d => d.checkpointIndex !== undefined)
|
|
499
|
+
.map(async d => {
|
|
500
|
+
const checkpointNumber = CheckpointNumber(fromCheckpoint + d.checkpointIndex!);
|
|
501
|
+
try {
|
|
502
|
+
const cp = await rollupContract.getCheckpoint(checkpointNumber);
|
|
503
|
+
return { checkpointIndex: d.checkpointIndex!, checkpointNumber, headerHash: cp.headerHash.toString() };
|
|
504
|
+
} catch (err) {
|
|
505
|
+
return { checkpointIndex: d.checkpointIndex!, checkpointNumber, error: (err as Error).message };
|
|
506
|
+
}
|
|
507
|
+
}),
|
|
508
|
+
);
|
|
509
|
+
|
|
510
|
+
log.error(`Root rollup public inputs mismatch`, undefined, {
|
|
511
|
+
fromCheckpoint,
|
|
512
|
+
toCheckpoint,
|
|
513
|
+
numDiffs: diffs.length,
|
|
514
|
+
diffs: diffs.map(d => ({
|
|
515
|
+
index: d.index,
|
|
516
|
+
label: d.label,
|
|
517
|
+
rollup: d.rollup.toString(),
|
|
518
|
+
computed: d.computed.toString(),
|
|
519
|
+
})),
|
|
520
|
+
onChainCheckpoints,
|
|
521
|
+
});
|
|
522
|
+
|
|
523
|
+
const fmt = (inputs: readonly Fr[]) => inputs.map(x => x.toString()).join(', ');
|
|
524
|
+
const summary = diffs.map(d => `[${d.index} ${d.label}] L1=${d.rollup} prover=${d.computed}`).join('\n');
|
|
525
|
+
return new Error(
|
|
526
|
+
`Root rollup public inputs mismatch (${diffs.length} fields differ):\n${summary}\n` +
|
|
527
|
+
`Rollup: ${fmt(rollupPublicInputs)}\nComputed:${fmt(argsPublicInputs)}`,
|
|
528
|
+
);
|
|
529
|
+
}
|