@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,56 +1,76 @@
|
|
|
1
1
|
import { asyncPool } from '@aztec/foundation/async-pool';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { BlockNumber, EpochNumber } from '@aztec/foundation/branded-types';
|
|
3
|
+
import { Fr } from '@aztec/foundation/curves/bn254';
|
|
4
|
+
import { type Logger, type LoggerBindings, createLogger } from '@aztec/foundation/log';
|
|
5
|
+
import { RunningPromise, promiseWithResolvers } from '@aztec/foundation/promise';
|
|
4
6
|
import { Timer } from '@aztec/foundation/timer';
|
|
7
|
+
import { AVM_MAX_CONCURRENT_SIMULATIONS } from '@aztec/native';
|
|
8
|
+
import { getVKTreeRoot } from '@aztec/noir-protocol-circuits-types/vk-tree';
|
|
9
|
+
import { protocolContractsHash } from '@aztec/protocol-contracts';
|
|
10
|
+
import { buildFinalBlobChallenges } from '@aztec/prover-client/helpers';
|
|
5
11
|
import type { PublicProcessor, PublicProcessorFactory } from '@aztec/simulator/server';
|
|
12
|
+
import { PublicSimulatorConfig } from '@aztec/stdlib/avm';
|
|
6
13
|
import type { L2Block, L2BlockSource } from '@aztec/stdlib/block';
|
|
14
|
+
import type { Checkpoint } from '@aztec/stdlib/checkpoint';
|
|
7
15
|
import {
|
|
8
16
|
type EpochProver,
|
|
9
17
|
type EpochProvingJobState,
|
|
10
18
|
EpochProvingJobTerminalState,
|
|
11
19
|
type ForkMerkleTreeOperations,
|
|
12
20
|
} from '@aztec/stdlib/interfaces/server';
|
|
13
|
-
import
|
|
21
|
+
import { appendL1ToL2MessagesToTree } from '@aztec/stdlib/messaging';
|
|
22
|
+
import { CheckpointConstantData } from '@aztec/stdlib/rollup';
|
|
14
23
|
import type { ProcessedTx, Tx } from '@aztec/stdlib/tx';
|
|
15
24
|
import { Attributes, type Traceable, type Tracer, trackSpan } from '@aztec/telemetry-client';
|
|
16
25
|
|
|
17
26
|
import * as crypto from 'node:crypto';
|
|
18
27
|
|
|
19
|
-
import type {
|
|
28
|
+
import type { ProverNodeJobMetrics } from '../metrics.js';
|
|
20
29
|
import type { ProverNodePublisher } from '../prover-node-publisher.js';
|
|
30
|
+
import { type EpochProvingJobData, validateEpochProvingJobData } from './epoch-proving-job-data.js';
|
|
31
|
+
|
|
32
|
+
export type EpochProvingJobOptions = {
|
|
33
|
+
parallelBlockLimit?: number;
|
|
34
|
+
skipEpochCheck?: boolean;
|
|
35
|
+
skipSubmitProof?: boolean;
|
|
36
|
+
};
|
|
21
37
|
|
|
22
38
|
/**
|
|
23
|
-
* Job that grabs a range of blocks from the
|
|
39
|
+
* Job that grabs a range of blocks from the unfinalized chain from L1, gets their txs given their hashes,
|
|
24
40
|
* re-executes their public calls, generates a rollup proof, and submits it to L1. This job will update the
|
|
25
41
|
* world state as part of public call execution via the public processor.
|
|
26
42
|
*/
|
|
27
43
|
export class EpochProvingJob implements Traceable {
|
|
28
44
|
private state: EpochProvingJobState = 'initialized';
|
|
29
|
-
private log
|
|
45
|
+
private log: Logger;
|
|
30
46
|
private uuid: string;
|
|
31
47
|
|
|
32
48
|
private runPromise: Promise<void> | undefined;
|
|
49
|
+
private abortController = new AbortController();
|
|
50
|
+
private epochCheckPromise: RunningPromise | undefined;
|
|
33
51
|
private deadlineTimeoutHandler: NodeJS.Timeout | undefined;
|
|
34
52
|
|
|
35
53
|
public readonly tracer: Tracer;
|
|
36
54
|
|
|
37
55
|
constructor(
|
|
38
|
-
private
|
|
39
|
-
private
|
|
40
|
-
private blocks: L2Block[],
|
|
41
|
-
private txs: Tx[],
|
|
56
|
+
private data: EpochProvingJobData,
|
|
57
|
+
private dbProvider: Pick<ForkMerkleTreeOperations, 'fork'>,
|
|
42
58
|
private prover: EpochProver,
|
|
43
59
|
private publicProcessorFactory: PublicProcessorFactory,
|
|
44
|
-
private publisher: ProverNodePublisher,
|
|
45
|
-
private l2BlockSource: L2BlockSource,
|
|
46
|
-
private
|
|
47
|
-
private metrics: ProverNodeMetrics,
|
|
60
|
+
private publisher: Pick<ProverNodePublisher, 'submitEpochProof' | 'analyzeEpochProofSubmission'>,
|
|
61
|
+
private l2BlockSource: L2BlockSource | undefined,
|
|
62
|
+
private metrics: ProverNodeJobMetrics,
|
|
48
63
|
private deadline: Date | undefined,
|
|
49
|
-
private config:
|
|
50
|
-
|
|
64
|
+
private config: EpochProvingJobOptions,
|
|
65
|
+
bindings?: LoggerBindings,
|
|
51
66
|
) {
|
|
67
|
+
validateEpochProvingJobData(data);
|
|
52
68
|
this.uuid = crypto.randomUUID();
|
|
53
|
-
this.
|
|
69
|
+
this.log = createLogger('prover-node:epoch-proving-job', {
|
|
70
|
+
...bindings,
|
|
71
|
+
instanceId: `epoch-${data.epochNumber}`,
|
|
72
|
+
});
|
|
73
|
+
this.tracer = metrics.tracer;
|
|
54
74
|
}
|
|
55
75
|
|
|
56
76
|
public getId(): string {
|
|
@@ -61,27 +81,63 @@ export class EpochProvingJob implements Traceable {
|
|
|
61
81
|
return this.state;
|
|
62
82
|
}
|
|
63
83
|
|
|
64
|
-
public getEpochNumber():
|
|
65
|
-
return this.epochNumber;
|
|
84
|
+
public getEpochNumber(): EpochNumber {
|
|
85
|
+
return this.data.epochNumber;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
public getDeadline(): Date | undefined {
|
|
89
|
+
return this.deadline;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
public getProvingData(): EpochProvingJobData {
|
|
93
|
+
return this.data;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
private get epochNumber() {
|
|
97
|
+
return this.data.epochNumber;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
private get checkpoints() {
|
|
101
|
+
return this.data.checkpoints;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
private get txs() {
|
|
105
|
+
return this.data.txs;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
private get attestations() {
|
|
109
|
+
return this.data.attestations;
|
|
66
110
|
}
|
|
67
111
|
|
|
68
112
|
/**
|
|
69
113
|
* Proves the given epoch and submits the proof to L1.
|
|
70
114
|
*/
|
|
71
115
|
@trackSpan('EpochProvingJob.run', function () {
|
|
72
|
-
return { [Attributes.EPOCH_NUMBER]:
|
|
116
|
+
return { [Attributes.EPOCH_NUMBER]: this.data.epochNumber };
|
|
73
117
|
})
|
|
74
118
|
public async run() {
|
|
75
119
|
this.scheduleDeadlineStop();
|
|
120
|
+
if (!this.config.skipEpochCheck) {
|
|
121
|
+
await this.scheduleEpochCheck();
|
|
122
|
+
}
|
|
76
123
|
|
|
77
|
-
const
|
|
78
|
-
const
|
|
79
|
-
const
|
|
80
|
-
const
|
|
81
|
-
this.
|
|
124
|
+
const attestations = this.attestations.map(attestation => attestation.toViem());
|
|
125
|
+
const epochNumber = this.epochNumber;
|
|
126
|
+
const epochSizeCheckpoints = this.checkpoints.length;
|
|
127
|
+
const epochSizeBlocks = this.checkpoints.reduce((accum, checkpoint) => accum + checkpoint.blocks.length, 0);
|
|
128
|
+
const epochSizeTxs = this.checkpoints.reduce(
|
|
129
|
+
(accum, checkpoint) =>
|
|
130
|
+
accum + checkpoint.blocks.reduce((accumC, block) => accumC + block.body.txEffects.length, 0),
|
|
131
|
+
0,
|
|
132
|
+
);
|
|
133
|
+
const fromCheckpoint = this.checkpoints[0].number;
|
|
134
|
+
const toCheckpoint = this.checkpoints.at(-1)!.number;
|
|
135
|
+
const fromBlock = this.checkpoints[0].blocks[0].number;
|
|
136
|
+
const toBlock = this.checkpoints.at(-1)!.blocks.at(-1)!.number;
|
|
137
|
+
this.log.info(`Starting epoch ${epochNumber} proving job with checkpoints ${fromCheckpoint} to ${toCheckpoint}`, {
|
|
82
138
|
fromBlock,
|
|
83
139
|
toBlock,
|
|
84
|
-
|
|
140
|
+
epochSizeTxs,
|
|
85
141
|
epochNumber,
|
|
86
142
|
uuid: this.uuid,
|
|
87
143
|
});
|
|
@@ -92,101 +148,281 @@ export class EpochProvingJob implements Traceable {
|
|
|
92
148
|
this.runPromise = promise;
|
|
93
149
|
|
|
94
150
|
try {
|
|
95
|
-
|
|
96
|
-
|
|
151
|
+
const blobTimer = new Timer();
|
|
152
|
+
const blobFieldsPerCheckpoint = this.checkpoints.map(checkpoint => checkpoint.toBlobFields());
|
|
153
|
+
const finalBlobBatchingChallenges = await buildFinalBlobChallenges(blobFieldsPerCheckpoint);
|
|
154
|
+
this.metrics.recordBlobProcessing(blobTimer.ms());
|
|
97
155
|
|
|
98
|
-
|
|
99
|
-
|
|
156
|
+
this.prover.startNewEpoch(epochNumber, epochSizeCheckpoints, finalBlobBatchingChallenges);
|
|
157
|
+
const chonkTimer = new Timer();
|
|
158
|
+
await this.prover.startChonkVerifierCircuits(Array.from(this.txs.values()));
|
|
159
|
+
this.metrics.recordChonkVerifier(chonkTimer.ms());
|
|
100
160
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
const l1ToL2Messages = await this.getL1ToL2Messages(block);
|
|
104
|
-
const previousHeader = (await this.getBlockHeader(block.number - 1))!;
|
|
105
|
-
|
|
106
|
-
this.log.verbose(`Starting processing block ${block.number}`, {
|
|
107
|
-
number: block.number,
|
|
108
|
-
blockHash: (await block.hash()).toString(),
|
|
109
|
-
lastArchive: block.header.lastArchive.root,
|
|
110
|
-
noteHashTreeRoot: block.header.state.partial.noteHashTree.root,
|
|
111
|
-
nullifierTreeRoot: block.header.state.partial.nullifierTree.root,
|
|
112
|
-
publicDataTreeRoot: block.header.state.partial.publicDataTree.root,
|
|
113
|
-
previousHeader: previousHeader.hash(),
|
|
114
|
-
uuid: this.uuid,
|
|
115
|
-
...globalVariables,
|
|
116
|
-
});
|
|
161
|
+
// Everything in the epoch should have the same chainId and version.
|
|
162
|
+
const { chainId, version } = this.checkpoints[0].blocks[0].header.globalVariables;
|
|
117
163
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
164
|
+
const previousBlockHeaders = this.gatherPreviousBlockHeaders();
|
|
165
|
+
|
|
166
|
+
const allCheckpointsTimer = new Timer();
|
|
167
|
+
|
|
168
|
+
const parallelism = this.config.parallelBlockLimit
|
|
169
|
+
? this.config.parallelBlockLimit
|
|
170
|
+
: AVM_MAX_CONCURRENT_SIMULATIONS > 0
|
|
171
|
+
? AVM_MAX_CONCURRENT_SIMULATIONS
|
|
172
|
+
: this.checkpoints.length;
|
|
173
|
+
|
|
174
|
+
await this.processCheckpoints(parallelism, async checkpoint => {
|
|
175
|
+
this.checkState();
|
|
176
|
+
const checkpointTimer = new Timer();
|
|
177
|
+
|
|
178
|
+
const checkpointIndex = checkpoint.number - fromCheckpoint;
|
|
179
|
+
const checkpointConstants = CheckpointConstantData.from({
|
|
180
|
+
chainId,
|
|
181
|
+
version,
|
|
182
|
+
vkTreeRoot: getVKTreeRoot(),
|
|
183
|
+
protocolContractsHash: protocolContractsHash,
|
|
184
|
+
proverId: this.prover.getProverId().toField(),
|
|
185
|
+
slotNumber: checkpoint.header.slotNumber,
|
|
186
|
+
coinbase: checkpoint.header.coinbase,
|
|
187
|
+
feeRecipient: checkpoint.header.feeRecipient,
|
|
188
|
+
gasFees: checkpoint.header.gasFees,
|
|
189
|
+
});
|
|
190
|
+
const previousHeader = previousBlockHeaders[checkpointIndex];
|
|
191
|
+
const l1ToL2Messages = this.getL1ToL2Messages(checkpoint);
|
|
192
|
+
|
|
193
|
+
this.log.debug(`Starting processing checkpoint ${checkpoint.number}`, {
|
|
194
|
+
number: checkpoint.number,
|
|
195
|
+
checkpointHash: checkpoint.hash().toString(),
|
|
196
|
+
headerHash: checkpoint.header.hash().toString(),
|
|
197
|
+
numL1ToL2Messages: l1ToL2Messages.length,
|
|
198
|
+
previousBlockNumber: previousHeader.globalVariables.blockNumber,
|
|
130
199
|
uuid: this.uuid,
|
|
131
200
|
});
|
|
132
201
|
|
|
133
|
-
|
|
134
|
-
|
|
202
|
+
await this.prover.startNewCheckpoint(
|
|
203
|
+
checkpointIndex,
|
|
204
|
+
checkpointConstants,
|
|
205
|
+
l1ToL2Messages,
|
|
206
|
+
checkpoint.blocks.length,
|
|
207
|
+
previousHeader,
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
for (let blockIndex = 0; blockIndex < checkpoint.blocks.length; blockIndex++) {
|
|
211
|
+
const blockTimer = new Timer();
|
|
212
|
+
const block = checkpoint.blocks[blockIndex];
|
|
213
|
+
const globalVariables = block.header.globalVariables;
|
|
214
|
+
const txs = this.getTxs(block);
|
|
215
|
+
|
|
216
|
+
this.log.verbose(`Starting processing block ${block.number}`, {
|
|
217
|
+
number: block.number,
|
|
218
|
+
blockHash: (await block.hash()).toString(),
|
|
219
|
+
lastArchive: block.header.lastArchive.root,
|
|
220
|
+
noteHashTreeRoot: block.header.state.partial.noteHashTree.root,
|
|
221
|
+
nullifierTreeRoot: block.header.state.partial.nullifierTree.root,
|
|
222
|
+
publicDataTreeRoot: block.header.state.partial.publicDataTree.root,
|
|
223
|
+
...globalVariables,
|
|
224
|
+
numTxs: txs.length,
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
// Start block proving
|
|
228
|
+
await this.prover.startNewBlock(block.number, globalVariables.timestamp, txs.length);
|
|
229
|
+
|
|
230
|
+
// Process public fns. L1 to L2 messages are only inserted for the first block of a checkpoint,
|
|
231
|
+
// as the fork for subsequent blocks already includes them from the previous block's synced state.
|
|
232
|
+
{
|
|
233
|
+
await using db = await this.createFork(
|
|
234
|
+
BlockNumber(block.number - 1),
|
|
235
|
+
blockIndex === 0 ? l1ToL2Messages : undefined,
|
|
236
|
+
);
|
|
237
|
+
this.checkState();
|
|
238
|
+
const config = PublicSimulatorConfig.from({
|
|
239
|
+
proverId: this.prover.getProverId().toField(),
|
|
240
|
+
skipFeeEnforcement: false,
|
|
241
|
+
collectDebugLogs: false,
|
|
242
|
+
collectHints: true,
|
|
243
|
+
collectPublicInputs: true,
|
|
244
|
+
collectStatistics: false,
|
|
245
|
+
});
|
|
246
|
+
const publicProcessor = this.publicProcessorFactory.create(db, globalVariables, config);
|
|
247
|
+
const processed = await this.processTxs(publicProcessor, txs);
|
|
248
|
+
this.checkState();
|
|
249
|
+
await this.prover.addTxs(processed);
|
|
250
|
+
}
|
|
251
|
+
this.checkState();
|
|
252
|
+
this.log.verbose(`Processed all ${txs.length} txs for block ${block.number}`, {
|
|
253
|
+
blockNumber: block.number,
|
|
254
|
+
blockHash: (await block.hash()).toString(),
|
|
255
|
+
uuid: this.uuid,
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
// Mark block as completed to pad it
|
|
259
|
+
const expectedBlockHeader = block.header;
|
|
260
|
+
await this.prover.setBlockCompleted(block.number, expectedBlockHeader);
|
|
261
|
+
this.metrics.recordBlockProcessing(blockTimer.ms());
|
|
262
|
+
}
|
|
263
|
+
this.metrics.recordCheckpointProcessing(checkpointTimer.ms());
|
|
135
264
|
});
|
|
265
|
+
this.metrics.recordAllCheckpointsProcessing(allCheckpointsTimer.ms());
|
|
136
266
|
|
|
137
267
|
const executionTime = timer.ms();
|
|
138
268
|
|
|
139
269
|
this.progressState('awaiting-prover');
|
|
140
|
-
const { publicInputs, proof } = await this.prover.
|
|
141
|
-
this.log.info(`
|
|
270
|
+
const { publicInputs, proof, batchedBlobInputs } = await this.prover.finalizeEpoch();
|
|
271
|
+
this.log.info(`Finalized proof for epoch ${epochNumber}`, { epochNumber, uuid: this.uuid, duration: timer.ms() });
|
|
142
272
|
|
|
143
273
|
this.progressState('publishing-proof');
|
|
144
|
-
|
|
274
|
+
|
|
275
|
+
if (this.config.skipSubmitProof) {
|
|
276
|
+
this.log.info(
|
|
277
|
+
`Proof publishing is disabled. Analyzing estimated L1 fees for epoch ${epochNumber} (checkpoints ${fromCheckpoint} to ${toCheckpoint})`,
|
|
278
|
+
);
|
|
279
|
+
try {
|
|
280
|
+
await this.publisher.analyzeEpochProofSubmission({
|
|
281
|
+
fromCheckpoint,
|
|
282
|
+
toCheckpoint,
|
|
283
|
+
epochNumber,
|
|
284
|
+
publicInputs,
|
|
285
|
+
proof,
|
|
286
|
+
batchedBlobInputs,
|
|
287
|
+
attestations,
|
|
288
|
+
});
|
|
289
|
+
} catch (err) {
|
|
290
|
+
this.log.warn(`Failed to analyze estimated L1 fees for epoch ${epochNumber}`, err);
|
|
291
|
+
}
|
|
292
|
+
this.state = 'completed';
|
|
293
|
+
this.metrics.recordProvingJob(executionTime, timer.ms(), epochSizeCheckpoints, epochSizeBlocks, epochSizeTxs);
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const success = await this.publisher.submitEpochProof({
|
|
298
|
+
fromCheckpoint,
|
|
299
|
+
toCheckpoint,
|
|
300
|
+
epochNumber,
|
|
301
|
+
publicInputs,
|
|
302
|
+
proof,
|
|
303
|
+
batchedBlobInputs,
|
|
304
|
+
attestations,
|
|
305
|
+
});
|
|
145
306
|
if (!success) {
|
|
146
307
|
throw new Error('Failed to submit epoch proof to L1');
|
|
147
308
|
}
|
|
148
309
|
|
|
149
|
-
this.log.info(`Submitted proof for epoch ${epochNumber} (
|
|
310
|
+
this.log.info(`Submitted proof for epoch ${epochNumber} (checkpoints ${fromCheckpoint} to ${toCheckpoint})`, {
|
|
150
311
|
epochNumber,
|
|
151
312
|
uuid: this.uuid,
|
|
152
313
|
});
|
|
153
314
|
this.state = 'completed';
|
|
154
|
-
this.metrics.recordProvingJob(executionTime, timer.ms(), epochSizeBlocks, epochSizeTxs);
|
|
315
|
+
this.metrics.recordProvingJob(executionTime, timer.ms(), epochSizeCheckpoints, epochSizeBlocks, epochSizeTxs);
|
|
155
316
|
} catch (err: any) {
|
|
156
317
|
if (err && err.name === 'HaltExecutionError') {
|
|
157
|
-
this.log.warn(`Halted execution of epoch ${epochNumber} prover job`, {
|
|
318
|
+
this.log.warn(`Halted execution of epoch ${epochNumber} prover job`, {
|
|
319
|
+
uuid: this.uuid,
|
|
320
|
+
epochNumber,
|
|
321
|
+
details: err.message,
|
|
322
|
+
});
|
|
158
323
|
return;
|
|
159
324
|
}
|
|
160
325
|
this.log.error(`Error running epoch ${epochNumber} prover job`, err, { uuid: this.uuid, epochNumber });
|
|
161
|
-
this.state
|
|
326
|
+
if (this.state === 'processing' || this.state === 'awaiting-prover' || this.state === 'publishing-proof') {
|
|
327
|
+
this.state = 'failed';
|
|
328
|
+
}
|
|
162
329
|
} finally {
|
|
163
330
|
clearTimeout(this.deadlineTimeoutHandler);
|
|
164
|
-
await this.
|
|
331
|
+
await this.epochCheckPromise?.stop();
|
|
165
332
|
await this.prover.stop();
|
|
166
333
|
resolve();
|
|
167
334
|
}
|
|
168
335
|
}
|
|
169
336
|
|
|
337
|
+
/**
|
|
338
|
+
* Create a new db fork for tx processing, optionally inserting L1 to L2 messages.
|
|
339
|
+
* L1 to L2 messages should only be inserted for the first block in a checkpoint,
|
|
340
|
+
* as subsequent blocks' synced state already includes them.
|
|
341
|
+
* REFACTOR: The prover already spawns a db fork of its own for each block, so we may be able to do away with just one fork.
|
|
342
|
+
*/
|
|
343
|
+
private async createFork(blockNumber: BlockNumber, l1ToL2Messages: Fr[] | undefined) {
|
|
344
|
+
this.log.verbose(`Creating fork at ${blockNumber}`, { blockNumber });
|
|
345
|
+
// temporary stack to control fork lifetime
|
|
346
|
+
await using cleanup = new AsyncDisposableStack();
|
|
347
|
+
const db = cleanup.use(await this.dbProvider.fork(blockNumber));
|
|
348
|
+
|
|
349
|
+
if (l1ToL2Messages !== undefined) {
|
|
350
|
+
this.log.verbose(`Inserting ${l1ToL2Messages.length} L1 to L2 messages in fork`, {
|
|
351
|
+
blockNumber,
|
|
352
|
+
l1ToL2Messages: l1ToL2Messages.map(m => m.toString()),
|
|
353
|
+
});
|
|
354
|
+
await appendL1ToL2MessagesToTree(db, l1ToL2Messages);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// everything run succesfully so we can release this stack and give control of the fork's lifetime to the caller
|
|
358
|
+
cleanup.move();
|
|
359
|
+
return db;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
private async processCheckpoints(
|
|
363
|
+
parallelism: number,
|
|
364
|
+
processCheckpoint: (checkpoint: Checkpoint) => Promise<void>,
|
|
365
|
+
): Promise<void> {
|
|
366
|
+
let hasError = false;
|
|
367
|
+
let firstError: unknown;
|
|
368
|
+
|
|
369
|
+
await asyncPool(Math.max(parallelism, 1), this.checkpoints, async checkpoint => {
|
|
370
|
+
if (hasError || this.abortController.signal.aborted) {
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
try {
|
|
375
|
+
this.checkState();
|
|
376
|
+
await processCheckpoint(checkpoint);
|
|
377
|
+
} catch (err) {
|
|
378
|
+
if (!hasError) {
|
|
379
|
+
hasError = true;
|
|
380
|
+
firstError = err;
|
|
381
|
+
this.failProcessing();
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
if (hasError) {
|
|
387
|
+
throw firstError;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
if (this.abortController.signal.aborted) {
|
|
391
|
+
this.checkState();
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
170
395
|
private progressState(state: EpochProvingJobState) {
|
|
171
396
|
this.checkState();
|
|
172
397
|
this.state = state;
|
|
173
398
|
}
|
|
174
399
|
|
|
175
400
|
private checkState() {
|
|
176
|
-
if (this.state === 'timed-out' || this.state === 'stopped' || this.state === 'failed') {
|
|
401
|
+
if (this.state === 'timed-out' || this.state === 'stopped' || this.state === 'failed' || this.state === 'reorg') {
|
|
177
402
|
throw new HaltExecutionError(this.state);
|
|
178
403
|
}
|
|
179
404
|
}
|
|
180
405
|
|
|
181
|
-
public async stop(state:
|
|
406
|
+
public async stop(state: EpochProvingJobTerminalState = 'stopped') {
|
|
182
407
|
this.state = state;
|
|
183
|
-
this.
|
|
184
|
-
// TODO(palla/prover): Stop the publisher as well
|
|
408
|
+
this.interruptProcessing();
|
|
185
409
|
if (this.runPromise) {
|
|
186
410
|
await this.runPromise;
|
|
187
411
|
}
|
|
188
412
|
}
|
|
189
413
|
|
|
414
|
+
private failProcessing() {
|
|
415
|
+
if (!EpochProvingJobTerminalState.includes(this.state)) {
|
|
416
|
+
this.state = 'failed';
|
|
417
|
+
}
|
|
418
|
+
this.interruptProcessing();
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
private interruptProcessing() {
|
|
422
|
+
this.abortController.abort();
|
|
423
|
+
this.prover.cancel();
|
|
424
|
+
}
|
|
425
|
+
|
|
190
426
|
private scheduleDeadlineStop() {
|
|
191
427
|
const deadline = this.deadline;
|
|
192
428
|
if (deadline) {
|
|
@@ -207,33 +443,73 @@ export class EpochProvingJob implements Traceable {
|
|
|
207
443
|
}
|
|
208
444
|
}
|
|
209
445
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
446
|
+
/**
|
|
447
|
+
* Kicks off a running promise that queries the archiver for the set of L2 blocks of the current epoch.
|
|
448
|
+
* If those change, stops the proving job with a `rerun` state, so the node re-enqueues it.
|
|
449
|
+
*/
|
|
450
|
+
private async scheduleEpochCheck() {
|
|
451
|
+
const l2BlockSource = this.l2BlockSource;
|
|
452
|
+
if (!l2BlockSource) {
|
|
453
|
+
this.log.warn(`No L2 block source available, skipping epoch check`);
|
|
454
|
+
return;
|
|
214
455
|
}
|
|
215
|
-
|
|
456
|
+
|
|
457
|
+
const intervalMs = Math.ceil((await l2BlockSource.getL1Constants()).ethereumSlotDuration / 2) * 1000;
|
|
458
|
+
this.epochCheckPromise = new RunningPromise(
|
|
459
|
+
async () => {
|
|
460
|
+
const blockHeaders = (
|
|
461
|
+
await l2BlockSource.getBlocksData({ epoch: this.epochNumber, onlyCheckpointed: true })
|
|
462
|
+
).map(d => d.header);
|
|
463
|
+
const blockHashes = await Promise.all(blockHeaders.map(header => header.hash()));
|
|
464
|
+
const thisBlocks = this.checkpoints.flatMap(checkpoint => checkpoint.blocks);
|
|
465
|
+
const thisBlockHashes = await Promise.all(thisBlocks.map(block => block.hash()));
|
|
466
|
+
if (
|
|
467
|
+
blockHeaders.length !== thisBlocks.length ||
|
|
468
|
+
!blockHashes.every((block, i) => block.equals(thisBlockHashes[i]))
|
|
469
|
+
) {
|
|
470
|
+
this.log.warn('Epoch blocks changed underfoot', {
|
|
471
|
+
uuid: this.uuid,
|
|
472
|
+
epochNumber: this.epochNumber,
|
|
473
|
+
oldBlockHashes: thisBlockHashes,
|
|
474
|
+
newBlockHashes: blockHashes,
|
|
475
|
+
});
|
|
476
|
+
void this.stop('reorg');
|
|
477
|
+
}
|
|
478
|
+
},
|
|
479
|
+
this.log,
|
|
480
|
+
intervalMs,
|
|
481
|
+
).start();
|
|
482
|
+
this.log.verbose(`Scheduled epoch check for epoch ${this.epochNumber} every ${intervalMs}ms`);
|
|
216
483
|
}
|
|
217
484
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
const
|
|
221
|
-
return
|
|
222
|
-
.filter(txAndHash => txHashes.includes(txAndHash.hash.toBigInt()))
|
|
223
|
-
.map(txAndHash => txAndHash.tx);
|
|
485
|
+
/* Returns the last block header in the previous checkpoint for all checkpoints in the epoch */
|
|
486
|
+
private gatherPreviousBlockHeaders() {
|
|
487
|
+
const lastBlocks = this.checkpoints.map(checkpoint => checkpoint.blocks.at(-1)!);
|
|
488
|
+
return [this.data.previousBlockHeader, ...lastBlocks.map(block => block.header).slice(0, -1)];
|
|
224
489
|
}
|
|
225
490
|
|
|
226
|
-
private
|
|
227
|
-
return this.
|
|
491
|
+
private getTxs(block: L2Block): Tx[] {
|
|
492
|
+
return block.body.txEffects.map(txEffect => this.txs.get(txEffect.txHash.toString())!);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
private getL1ToL2Messages(checkpoint: Checkpoint) {
|
|
496
|
+
return this.data.l1ToL2Messages[checkpoint.number];
|
|
228
497
|
}
|
|
229
498
|
|
|
230
499
|
private async processTxs(publicProcessor: PublicProcessor, txs: Tx[]): Promise<ProcessedTx[]> {
|
|
231
500
|
const { deadline } = this;
|
|
232
|
-
const [processedTxs, failedTxs] = await publicProcessor.process(txs, {
|
|
501
|
+
const [processedTxs, failedTxs] = await publicProcessor.process(txs, {
|
|
502
|
+
deadline,
|
|
503
|
+
signal: this.abortController.signal,
|
|
504
|
+
});
|
|
505
|
+
this.checkState();
|
|
233
506
|
|
|
234
507
|
if (failedTxs.length) {
|
|
508
|
+
const failedTxHashes = await Promise.all(failedTxs.map(({ tx }) => tx.getTxHash()));
|
|
235
509
|
throw new Error(
|
|
236
|
-
`Txs failed processing: ${failedTxs
|
|
510
|
+
`Txs failed processing: ${failedTxs
|
|
511
|
+
.map(({ error }, index) => `${failedTxHashes[index]} (${error})`)
|
|
512
|
+
.join(', ')}`,
|
|
237
513
|
);
|
|
238
514
|
}
|
|
239
515
|
|
|
@@ -246,7 +522,7 @@ export class EpochProvingJob implements Traceable {
|
|
|
246
522
|
}
|
|
247
523
|
|
|
248
524
|
class HaltExecutionError extends Error {
|
|
249
|
-
constructor(state: EpochProvingJobState) {
|
|
525
|
+
constructor(public readonly state: EpochProvingJobState) {
|
|
250
526
|
super(`Halted execution due to state ${state}`);
|
|
251
527
|
this.name = 'HaltExecutionError';
|
|
252
528
|
}
|