@aztec/prover-node 0.86.0 → 0.87.0
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 +11 -0
- package/dest/actions/rerun-epoch-proving-job.d.ts.map +1 -0
- package/dest/actions/rerun-epoch-proving-job.js +40 -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 +67 -0
- package/dest/config.d.ts +2 -2
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +5 -0
- package/dest/factory.d.ts.map +1 -1
- package/dest/factory.js +5 -5
- package/dest/index.d.ts +1 -0
- package/dest/index.d.ts.map +1 -1
- package/dest/index.js +1 -0
- package/dest/job/epoch-proving-job-data.d.ts +15 -0
- package/dest/job/epoch-proving-job-data.d.ts.map +1 -0
- package/dest/job/epoch-proving-job-data.js +45 -0
- package/dest/job/epoch-proving-job.d.ts +10 -9
- package/dest/job/epoch-proving-job.d.ts.map +1 -1
- package/dest/job/epoch-proving-job.js +41 -24
- package/dest/metrics.js +2 -2
- package/dest/prover-coordination/combined-prover-coordination.d.ts.map +1 -1
- package/dest/prover-coordination/combined-prover-coordination.js +7 -4
- package/dest/prover-coordination/config.d.ts.map +1 -1
- package/dest/prover-coordination/config.js +2 -1
- package/dest/prover-coordination/factory.d.ts.map +1 -1
- package/dest/prover-coordination/factory.js +8 -4
- package/dest/prover-node.d.ts +23 -18
- package/dest/prover-node.d.ts.map +1 -1
- package/dest/prover-node.js +88 -37
- package/dest/test/index.d.ts +4 -2
- package/dest/test/index.d.ts.map +1 -1
- package/dest/test/index.js +1 -1
- package/package.json +25 -24
- 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 +61 -0
- package/src/actions/upload-epoch-proof-failure.ts +88 -0
- package/src/bin/run-failed-epoch.ts +77 -0
- package/src/config.ts +7 -1
- package/src/factory.ts +21 -7
- package/src/index.ts +1 -0
- package/src/job/epoch-proving-job-data.ts +68 -0
- package/src/job/epoch-proving-job.ts +55 -23
- package/src/metrics.ts +2 -2
- package/src/prover-coordination/combined-prover-coordination.ts +9 -6
- package/src/prover-coordination/config.ts +1 -0
- package/src/prover-coordination/factory.ts +7 -4
- package/src/prover-node.ts +120 -53
- package/src/test/index.ts +7 -4
|
@@ -10,7 +10,6 @@ import {
|
|
|
10
10
|
EpochProvingJobTerminalState,
|
|
11
11
|
type ForkMerkleTreeOperations,
|
|
12
12
|
} from '@aztec/stdlib/interfaces/server';
|
|
13
|
-
import type { L1ToL2MessageSource } from '@aztec/stdlib/messaging';
|
|
14
13
|
import type { ProcessedTx, Tx } from '@aztec/stdlib/tx';
|
|
15
14
|
import { Attributes, type Traceable, type Tracer, trackSpan } from '@aztec/telemetry-client';
|
|
16
15
|
|
|
@@ -18,6 +17,7 @@ import * as crypto from 'node:crypto';
|
|
|
18
17
|
|
|
19
18
|
import type { ProverNodeJobMetrics } from '../metrics.js';
|
|
20
19
|
import type { ProverNodePublisher } from '../prover-node-publisher.js';
|
|
20
|
+
import { type EpochProvingJobData, validateEpochProvingJobData } from './epoch-proving-job-data.js';
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* Job that grabs a range of blocks from the unfinalised chain from L1, gets their txs given their hashes,
|
|
@@ -36,19 +36,17 @@ export class EpochProvingJob implements Traceable {
|
|
|
36
36
|
public readonly tracer: Tracer;
|
|
37
37
|
|
|
38
38
|
constructor(
|
|
39
|
-
private
|
|
40
|
-
private
|
|
41
|
-
private blocks: L2Block[],
|
|
42
|
-
private txs: Tx[],
|
|
39
|
+
private data: EpochProvingJobData,
|
|
40
|
+
private dbProvider: Pick<ForkMerkleTreeOperations, 'fork'>,
|
|
43
41
|
private prover: EpochProver,
|
|
44
42
|
private publicProcessorFactory: PublicProcessorFactory,
|
|
45
|
-
private publisher: ProverNodePublisher,
|
|
46
|
-
private l2BlockSource: L2BlockSource,
|
|
47
|
-
private l1ToL2MessageSource: L1ToL2MessageSource,
|
|
43
|
+
private publisher: Pick<ProverNodePublisher, 'submitEpochProof'>,
|
|
44
|
+
private l2BlockSource: L2BlockSource | undefined,
|
|
48
45
|
private metrics: ProverNodeJobMetrics,
|
|
49
46
|
private deadline: Date | undefined,
|
|
50
|
-
private config: { parallelBlockLimit
|
|
47
|
+
private config: { parallelBlockLimit?: number; skipEpochCheck?: boolean },
|
|
51
48
|
) {
|
|
49
|
+
validateEpochProvingJobData(data);
|
|
52
50
|
this.uuid = crypto.randomUUID();
|
|
53
51
|
this.tracer = metrics.tracer;
|
|
54
52
|
}
|
|
@@ -62,22 +60,40 @@ export class EpochProvingJob implements Traceable {
|
|
|
62
60
|
}
|
|
63
61
|
|
|
64
62
|
public getEpochNumber(): bigint {
|
|
65
|
-
return this.epochNumber;
|
|
63
|
+
return this.data.epochNumber;
|
|
66
64
|
}
|
|
67
65
|
|
|
68
66
|
public getDeadline(): Date | undefined {
|
|
69
67
|
return this.deadline;
|
|
70
68
|
}
|
|
71
69
|
|
|
70
|
+
public getProvingData(): EpochProvingJobData {
|
|
71
|
+
return this.data;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
private get epochNumber() {
|
|
75
|
+
return this.data.epochNumber;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
private get blocks() {
|
|
79
|
+
return this.data.blocks;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
private get txs() {
|
|
83
|
+
return this.data.txs;
|
|
84
|
+
}
|
|
85
|
+
|
|
72
86
|
/**
|
|
73
87
|
* Proves the given epoch and submits the proof to L1.
|
|
74
88
|
*/
|
|
75
89
|
@trackSpan('EpochProvingJob.run', function () {
|
|
76
|
-
return { [Attributes.EPOCH_NUMBER]: Number(this.epochNumber) };
|
|
90
|
+
return { [Attributes.EPOCH_NUMBER]: Number(this.data.epochNumber) };
|
|
77
91
|
})
|
|
78
92
|
public async run() {
|
|
79
93
|
this.scheduleDeadlineStop();
|
|
80
|
-
|
|
94
|
+
if (!this.config.skipEpochCheck) {
|
|
95
|
+
await this.scheduleEpochCheck();
|
|
96
|
+
}
|
|
81
97
|
|
|
82
98
|
const epochNumber = Number(this.epochNumber);
|
|
83
99
|
const epochSizeBlocks = this.blocks.length;
|
|
@@ -100,13 +116,13 @@ export class EpochProvingJob implements Traceable {
|
|
|
100
116
|
this.prover.startNewEpoch(epochNumber, fromBlock, epochSizeBlocks);
|
|
101
117
|
await this.prover.startTubeCircuits(this.txs);
|
|
102
118
|
|
|
103
|
-
await asyncPool(this.config.parallelBlockLimit, this.blocks, async block => {
|
|
119
|
+
await asyncPool(this.config.parallelBlockLimit ?? 32, this.blocks, async block => {
|
|
104
120
|
this.checkState();
|
|
105
121
|
|
|
106
122
|
const globalVariables = block.header.globalVariables;
|
|
107
123
|
const txs = await this.getTxs(block);
|
|
108
|
-
const l1ToL2Messages =
|
|
109
|
-
const previousHeader =
|
|
124
|
+
const l1ToL2Messages = this.getL1ToL2Messages(block);
|
|
125
|
+
const previousHeader = this.getBlockHeader(block.number - 1)!;
|
|
110
126
|
|
|
111
127
|
this.log.verbose(`Starting processing block ${block.number}`, {
|
|
112
128
|
number: block.number,
|
|
@@ -221,10 +237,16 @@ export class EpochProvingJob implements Traceable {
|
|
|
221
237
|
* If those change, stops the proving job with a `rerun` state, so the node re-enqueues it.
|
|
222
238
|
*/
|
|
223
239
|
private async scheduleEpochCheck() {
|
|
224
|
-
const
|
|
240
|
+
const l2BlockSource = this.l2BlockSource;
|
|
241
|
+
if (!l2BlockSource) {
|
|
242
|
+
this.log.warn(`No L2 block source available, skipping epoch check`);
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const intervalMs = Math.ceil((await l2BlockSource.getL1Constants()).ethereumSlotDuration / 2) * 1000;
|
|
225
247
|
this.epochCheckPromise = new RunningPromise(
|
|
226
248
|
async () => {
|
|
227
|
-
const blocks = await
|
|
249
|
+
const blocks = await l2BlockSource.getBlockHeadersForEpoch(this.epochNumber);
|
|
228
250
|
const blockHashes = await Promise.all(blocks.map(block => block.hash()));
|
|
229
251
|
const thisBlockHashes = await Promise.all(this.blocks.map(block => block.hash()));
|
|
230
252
|
if (
|
|
@@ -246,12 +268,22 @@ export class EpochProvingJob implements Traceable {
|
|
|
246
268
|
this.log.verbose(`Scheduled epoch check for epoch ${this.epochNumber} every ${intervalMs}ms`);
|
|
247
269
|
}
|
|
248
270
|
|
|
249
|
-
/* Returns the header for the given block number
|
|
250
|
-
private
|
|
251
|
-
|
|
252
|
-
|
|
271
|
+
/* Returns the header for the given block number based on the epoch proving job data. */
|
|
272
|
+
private getBlockHeader(blockNumber: number) {
|
|
273
|
+
const block = this.blocks.find(b => b.number === blockNumber);
|
|
274
|
+
if (block) {
|
|
275
|
+
return block.header;
|
|
253
276
|
}
|
|
254
|
-
|
|
277
|
+
|
|
278
|
+
if (blockNumber === Number(this.data.previousBlockHeader.getBlockNumber())) {
|
|
279
|
+
return this.data.previousBlockHeader;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
throw new Error(
|
|
283
|
+
`Block header not found for block number ${blockNumber} (got ${this.blocks
|
|
284
|
+
.map(b => b.number)
|
|
285
|
+
.join(', ')} and previous header ${this.data.previousBlockHeader.getBlockNumber()})`,
|
|
286
|
+
);
|
|
255
287
|
}
|
|
256
288
|
|
|
257
289
|
private async getTxs(block: L2Block): Promise<Tx[]> {
|
|
@@ -263,7 +295,7 @@ export class EpochProvingJob implements Traceable {
|
|
|
263
295
|
}
|
|
264
296
|
|
|
265
297
|
private getL1ToL2Messages(block: L2Block) {
|
|
266
|
-
return this.
|
|
298
|
+
return this.data.l1ToL2Messages[block.number];
|
|
267
299
|
}
|
|
268
300
|
|
|
269
301
|
private async processTxs(publicProcessor: PublicProcessor, txs: Tx[]): Promise<ProcessedTx[]> {
|
package/src/metrics.ts
CHANGED
|
@@ -242,7 +242,7 @@ export class ProverNodePublisherMetrics {
|
|
|
242
242
|
|
|
243
243
|
try {
|
|
244
244
|
this.gasPrice.record(parseInt(formatEther(stats.gasPrice, 'gwei'), 10));
|
|
245
|
-
} catch
|
|
245
|
+
} catch {
|
|
246
246
|
// ignore
|
|
247
247
|
}
|
|
248
248
|
|
|
@@ -252,7 +252,7 @@ export class ProverNodePublisherMetrics {
|
|
|
252
252
|
|
|
253
253
|
try {
|
|
254
254
|
this.txTotalFee.record(parseFloat(formatEther(totalFee)));
|
|
255
|
-
} catch
|
|
255
|
+
} catch {
|
|
256
256
|
// ignore
|
|
257
257
|
}
|
|
258
258
|
}
|
|
@@ -94,7 +94,7 @@ export class CombinedProverCoordination implements ProverCoordination {
|
|
|
94
94
|
const notFound = txHashes.filter((_, index) => !availability[index]);
|
|
95
95
|
const txsToFind = new Set(notFound.map(tx => tx.toString()));
|
|
96
96
|
if (txsToFind.size === 0) {
|
|
97
|
-
this.log.info(`Check for ${txHashes.length} txs found all in the pool
|
|
97
|
+
this.log.info(`Check for ${txHashes.length} txs found all in the pool`);
|
|
98
98
|
return;
|
|
99
99
|
}
|
|
100
100
|
this.log.info(`Check for ${txHashes.length} txs found ${txsToFind.size} missing. Will gather from nodes and p2p`);
|
|
@@ -107,16 +107,19 @@ export class CombinedProverCoordination implements ProverCoordination {
|
|
|
107
107
|
const toFindFromP2P = txsToFind.size;
|
|
108
108
|
this.log.verbose(`Gathering ${toFindFromP2P} txs from p2p network`);
|
|
109
109
|
const foundFromP2P = await pool.getTxsByHash([...txsToFind].map(tx => TxHash.fromString(tx)));
|
|
110
|
+
|
|
111
|
+
// TODO(!!): test for this
|
|
112
|
+
// getTxsByHash returns undefined for transactions that are not found, so it must be filtered to find the true length
|
|
113
|
+
const foundFromP2PLength = foundFromP2P.filter(tx => !!tx).length;
|
|
114
|
+
|
|
110
115
|
const numFoundFromNodes = originalToFind - toFindFromP2P;
|
|
111
|
-
const numNotFound = toFindFromP2P -
|
|
116
|
+
const numNotFound = toFindFromP2P - foundFromP2PLength;
|
|
112
117
|
if (numNotFound === 0) {
|
|
113
|
-
this.log.info(
|
|
114
|
-
`Found all ${originalToFind} txs. ${numFoundFromNodes} from nodes, ${foundFromP2P.length} from p2p`,
|
|
115
|
-
);
|
|
118
|
+
this.log.info(`Found all ${originalToFind} txs. ${numFoundFromNodes} from nodes, ${foundFromP2PLength} from p2p`);
|
|
116
119
|
return;
|
|
117
120
|
}
|
|
118
121
|
this.log.warn(
|
|
119
|
-
`Failed to find ${numNotFound} txs from any source. Found ${
|
|
122
|
+
`Failed to find ${numNotFound} txs from any source. Found ${foundFromP2PLength} from p2p and ${numFoundFromNodes} from nodes`,
|
|
120
123
|
);
|
|
121
124
|
}
|
|
122
125
|
|
|
@@ -9,6 +9,7 @@ export const proverCoordinationConfigMappings: ConfigMappingsType<ProverCoordina
|
|
|
9
9
|
env: 'PROVER_COORDINATION_NODE_URLS',
|
|
10
10
|
description: 'The URLs of the tx provider nodes',
|
|
11
11
|
parseEnv: (val: string) => val.split(',').map(url => url.trim().replace(/\/$/, '')),
|
|
12
|
+
defaultValue: [],
|
|
12
13
|
},
|
|
13
14
|
};
|
|
14
15
|
|
|
@@ -9,6 +9,7 @@ import { protocolContractTreeRoot } from '@aztec/protocol-contracts';
|
|
|
9
9
|
import { type AztecNode, createAztecNodeClient } from '@aztec/stdlib/interfaces/client';
|
|
10
10
|
import type { ProverCoordination, WorldStateSynchronizer } from '@aztec/stdlib/interfaces/server';
|
|
11
11
|
import { P2PClientType } from '@aztec/stdlib/p2p';
|
|
12
|
+
import { getPackageVersion } from '@aztec/stdlib/update-checker';
|
|
12
13
|
import { getComponentsVersionsFromConfig } from '@aztec/stdlib/versioning';
|
|
13
14
|
import { type TelemetryClient, makeTracedFetch } from '@aztec/telemetry-client';
|
|
14
15
|
|
|
@@ -59,12 +60,13 @@ export async function createProverCoordination(
|
|
|
59
60
|
}
|
|
60
61
|
|
|
61
62
|
let nodes: AztecNode[] = [];
|
|
62
|
-
if (config.proverCoordinationNodeUrls.length > 0) {
|
|
63
|
+
if (config.proverCoordinationNodeUrls && config.proverCoordinationNodeUrls.length > 0) {
|
|
63
64
|
log.info('Using prover coordination via node urls');
|
|
64
65
|
const versions = getComponentsVersionsFromConfig(config, protocolContractTreeRoot, getVKTreeRoot());
|
|
65
|
-
nodes = config.proverCoordinationNodeUrls.map(url =>
|
|
66
|
-
|
|
67
|
-
|
|
66
|
+
nodes = config.proverCoordinationNodeUrls.map(url => {
|
|
67
|
+
log.info(`Creating aztec node client for prover coordination with url: ${url}`);
|
|
68
|
+
return createAztecNodeClient(url, versions, makeTracedFetch([1, 2, 3], false));
|
|
69
|
+
});
|
|
68
70
|
}
|
|
69
71
|
|
|
70
72
|
const proofVerifier = config.realProofs ? await BBCircuitVerifier.new(config) : new TestCircuitVerifier();
|
|
@@ -75,6 +77,7 @@ export async function createProverCoordination(
|
|
|
75
77
|
proofVerifier,
|
|
76
78
|
deps.worldStateSynchronizer,
|
|
77
79
|
deps.epochCache,
|
|
80
|
+
getPackageVersion() ?? '',
|
|
78
81
|
deps.telemetry,
|
|
79
82
|
);
|
|
80
83
|
await p2pClient.start();
|
package/src/prover-node.ts
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
|
+
import type { Archiver } from '@aztec/archiver';
|
|
1
2
|
import type { ViemPublicClient } from '@aztec/ethereum';
|
|
2
|
-
import { compact } from '@aztec/foundation/collection';
|
|
3
|
+
import { assertRequired, compact, pick, sum } from '@aztec/foundation/collection';
|
|
3
4
|
import { memoize } from '@aztec/foundation/decorators';
|
|
4
5
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
6
|
+
import type { Fr } from '@aztec/foundation/fields';
|
|
5
7
|
import { createLogger } from '@aztec/foundation/log';
|
|
6
8
|
import { RunningPromise } from '@aztec/foundation/running-promise';
|
|
7
9
|
import { DateProvider } from '@aztec/foundation/timer';
|
|
8
|
-
import type {
|
|
10
|
+
import type { DataStoreConfig } from '@aztec/kv-store/config';
|
|
9
11
|
import { PublicProcessorFactory } from '@aztec/simulator/server';
|
|
10
12
|
import type { L2Block, L2BlockSource } from '@aztec/stdlib/block';
|
|
13
|
+
import type { ChainConfig } from '@aztec/stdlib/config';
|
|
11
14
|
import type { ContractDataSource } from '@aztec/stdlib/contract';
|
|
12
15
|
import { getProofSubmissionDeadlineTimestamp } from '@aztec/stdlib/epoch-helpers';
|
|
13
16
|
import {
|
|
@@ -21,7 +24,7 @@ import {
|
|
|
21
24
|
tryStop,
|
|
22
25
|
} from '@aztec/stdlib/interfaces/server';
|
|
23
26
|
import type { L1ToL2MessageSource } from '@aztec/stdlib/messaging';
|
|
24
|
-
import type {
|
|
27
|
+
import type { TxHash } from '@aztec/stdlib/tx';
|
|
25
28
|
import {
|
|
26
29
|
Attributes,
|
|
27
30
|
L1Metrics,
|
|
@@ -32,22 +35,20 @@ import {
|
|
|
32
35
|
trackSpan,
|
|
33
36
|
} from '@aztec/telemetry-client';
|
|
34
37
|
|
|
38
|
+
import { uploadEpochProofFailure } from './actions/upload-epoch-proof-failure.js';
|
|
39
|
+
import type { SpecificProverNodeConfig } from './config.js';
|
|
40
|
+
import type { EpochProvingJobData } from './job/epoch-proving-job-data.js';
|
|
35
41
|
import { EpochProvingJob, type EpochProvingJobState } from './job/epoch-proving-job.js';
|
|
36
42
|
import { ProverNodeJobMetrics, ProverNodeRewardsMetrics } from './metrics.js';
|
|
37
43
|
import type { EpochMonitor, EpochMonitorHandler } from './monitors/epoch-monitor.js';
|
|
38
44
|
import type { ProverNodePublisher } from './prover-node-publisher.js';
|
|
39
45
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
maxPendingJobs: number;
|
|
43
|
-
maxParallelBlocksPerEpoch: number;
|
|
44
|
-
txGatheringIntervalMs: number;
|
|
45
|
-
};
|
|
46
|
+
type ProverNodeOptions = SpecificProverNodeConfig & Partial<DataStoreOptions>;
|
|
47
|
+
type DataStoreOptions = Pick<DataStoreConfig, 'dataDirectory'> & Pick<ChainConfig, 'l1ChainId' | 'rollupVersion'>;
|
|
46
48
|
|
|
47
49
|
/**
|
|
48
|
-
* An Aztec Prover Node is a standalone process that monitors the unfinalised chain on L1 for unproven
|
|
49
|
-
*
|
|
50
|
-
* from a tx source in the p2p network or an external node, re-executes their public functions, creates a rollup
|
|
50
|
+
* An Aztec Prover Node is a standalone process that monitors the unfinalised chain on L1 for unproven epochs,
|
|
51
|
+
* fetches their txs from the p2p network or external nodes, re-executes their public functions, creates a rollup
|
|
51
52
|
* proof for the epoch, and submits it to L1.
|
|
52
53
|
*/
|
|
53
54
|
export class ProverNode implements EpochMonitorHandler, ProverNodeApi, Traceable {
|
|
@@ -55,7 +56,7 @@ export class ProverNode implements EpochMonitorHandler, ProverNodeApi, Traceable
|
|
|
55
56
|
private dateProvider = new DateProvider();
|
|
56
57
|
|
|
57
58
|
private jobs: Map<string, EpochProvingJob> = new Map();
|
|
58
|
-
private
|
|
59
|
+
private config: ProverNodeOptions;
|
|
59
60
|
private jobMetrics: ProverNodeJobMetrics;
|
|
60
61
|
private rewardsMetrics: ProverNodeRewardsMetrics;
|
|
61
62
|
private l1Metrics: L1Metrics;
|
|
@@ -68,13 +69,13 @@ export class ProverNode implements EpochMonitorHandler, ProverNodeApi, Traceable
|
|
|
68
69
|
constructor(
|
|
69
70
|
protected readonly prover: EpochProverManager,
|
|
70
71
|
protected readonly publisher: ProverNodePublisher,
|
|
71
|
-
protected readonly l2BlockSource: L2BlockSource &
|
|
72
|
+
protected readonly l2BlockSource: L2BlockSource & Partial<Service>,
|
|
72
73
|
protected readonly l1ToL2MessageSource: L1ToL2MessageSource,
|
|
73
74
|
protected readonly contractDataSource: ContractDataSource,
|
|
74
75
|
protected readonly worldState: WorldStateSynchronizer,
|
|
75
|
-
protected readonly coordination: ProverCoordination
|
|
76
|
+
protected readonly coordination: ProverCoordination & Partial<Service>,
|
|
76
77
|
protected readonly epochsMonitor: EpochMonitor,
|
|
77
|
-
|
|
78
|
+
config: Partial<ProverNodeOptions> = {},
|
|
78
79
|
protected readonly telemetryClient: TelemetryClient = getTelemetryClient(),
|
|
79
80
|
) {
|
|
80
81
|
this.l1Metrics = new L1Metrics(
|
|
@@ -83,14 +84,19 @@ export class ProverNode implements EpochMonitorHandler, ProverNodeApi, Traceable
|
|
|
83
84
|
[publisher.getSenderAddress()],
|
|
84
85
|
);
|
|
85
86
|
|
|
86
|
-
this.
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
87
|
+
this.config = {
|
|
88
|
+
proverNodePollingIntervalMs: 1_000,
|
|
89
|
+
proverNodeMaxPendingJobs: 100,
|
|
90
|
+
proverNodeMaxParallelBlocksPerEpoch: 32,
|
|
90
91
|
txGatheringIntervalMs: 1_000,
|
|
91
|
-
|
|
92
|
+
txGatheringBatchSize: 10,
|
|
93
|
+
txGatheringMaxParallelRequestsPerNode: 100,
|
|
94
|
+
proverNodeFailedEpochStore: undefined,
|
|
95
|
+
...compact(config),
|
|
92
96
|
};
|
|
93
97
|
|
|
98
|
+
this.validateConfig();
|
|
99
|
+
|
|
94
100
|
const meter = telemetryClient.getMeter('ProverNode');
|
|
95
101
|
this.tracer = telemetryClient.getTracer('ProverNode');
|
|
96
102
|
|
|
@@ -102,7 +108,7 @@ export class ProverNode implements EpochMonitorHandler, ProverNodeApi, Traceable
|
|
|
102
108
|
this.publisher.getRollupContract(),
|
|
103
109
|
);
|
|
104
110
|
|
|
105
|
-
this.txFetcher = new RunningPromise(() => this.checkForTxs(), this.log, this.
|
|
111
|
+
this.txFetcher = new RunningPromise(() => this.checkForTxs(), this.log, this.config.txGatheringIntervalMs);
|
|
106
112
|
}
|
|
107
113
|
|
|
108
114
|
public getProverId() {
|
|
@@ -151,7 +157,7 @@ export class ProverNode implements EpochMonitorHandler, ProverNodeApi, Traceable
|
|
|
151
157
|
this.epochsMonitor.start(this);
|
|
152
158
|
this.l1Metrics.start();
|
|
153
159
|
await this.rewardsMetrics.start();
|
|
154
|
-
this.log.info(`Started Prover Node with prover id ${this.prover.getProverId().toString()}`, this.
|
|
160
|
+
this.log.info(`Started Prover Node with prover id ${this.prover.getProverId().toString()}`, this.config);
|
|
155
161
|
}
|
|
156
162
|
|
|
157
163
|
/**
|
|
@@ -188,28 +194,49 @@ export class ProverNode implements EpochMonitorHandler, ProverNodeApi, Traceable
|
|
|
188
194
|
* Starts a proving process and returns immediately.
|
|
189
195
|
*/
|
|
190
196
|
public async startProof(epochNumber: number | bigint) {
|
|
191
|
-
const job = await this.createProvingJob(BigInt(epochNumber));
|
|
197
|
+
const job = await this.createProvingJob(BigInt(epochNumber), { skipEpochCheck: true });
|
|
192
198
|
void this.runJob(job);
|
|
193
199
|
}
|
|
194
200
|
|
|
195
201
|
private async runJob(job: EpochProvingJob) {
|
|
196
|
-
const
|
|
202
|
+
const epochNumber = job.getEpochNumber();
|
|
203
|
+
const ctx = { id: job.getId(), epochNumber, state: undefined as EpochProvingJobState | undefined };
|
|
204
|
+
|
|
197
205
|
try {
|
|
198
206
|
await job.run();
|
|
199
207
|
const state = job.getState();
|
|
208
|
+
ctx.state = state;
|
|
209
|
+
|
|
200
210
|
if (state === 'reorg') {
|
|
201
|
-
this.log.warn(`Running new job for epoch ${
|
|
202
|
-
await this.createProvingJob(
|
|
211
|
+
this.log.warn(`Running new job for epoch ${epochNumber} due to reorg`, ctx);
|
|
212
|
+
await this.createProvingJob(epochNumber);
|
|
213
|
+
} else if (state === 'failed') {
|
|
214
|
+
this.log.error(`Job for ${epochNumber} exited with state ${state}`, ctx);
|
|
215
|
+
await this.tryUploadEpochFailure(job);
|
|
203
216
|
} else {
|
|
204
|
-
this.log.verbose(`Job for ${
|
|
217
|
+
this.log.verbose(`Job for ${epochNumber} exited with state ${state}`, ctx);
|
|
205
218
|
}
|
|
206
219
|
} catch (err) {
|
|
207
|
-
this.log.error(`Error proving epoch ${
|
|
220
|
+
this.log.error(`Error proving epoch ${epochNumber}`, err, ctx);
|
|
208
221
|
} finally {
|
|
209
222
|
this.jobs.delete(job.getId());
|
|
210
223
|
}
|
|
211
224
|
}
|
|
212
225
|
|
|
226
|
+
protected async tryUploadEpochFailure(job: EpochProvingJob) {
|
|
227
|
+
if (this.config.proverNodeFailedEpochStore) {
|
|
228
|
+
return await uploadEpochProofFailure(
|
|
229
|
+
this.config.proverNodeFailedEpochStore,
|
|
230
|
+
job.getId(),
|
|
231
|
+
job.getProvingData(),
|
|
232
|
+
this.l2BlockSource as Archiver,
|
|
233
|
+
this.worldState,
|
|
234
|
+
assertRequired(pick(this.config, 'l1ChainId', 'rollupVersion', 'dataDirectory')),
|
|
235
|
+
this.log,
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
213
240
|
/**
|
|
214
241
|
* Returns the prover instance.
|
|
215
242
|
*/
|
|
@@ -239,36 +266,38 @@ export class ProverNode implements EpochMonitorHandler, ProverNodeApi, Traceable
|
|
|
239
266
|
}
|
|
240
267
|
|
|
241
268
|
private checkMaximumPendingJobs() {
|
|
242
|
-
const { maxPendingJobs } = this.
|
|
243
|
-
|
|
269
|
+
const { proverNodeMaxPendingJobs: maxPendingJobs } = this.config;
|
|
270
|
+
if (maxPendingJobs > 0 && this.jobs.size >= maxPendingJobs) {
|
|
271
|
+
throw new Error(`Maximum pending proving jobs ${maxPendingJobs} reached. Cannot create new job.`);
|
|
272
|
+
}
|
|
244
273
|
}
|
|
245
274
|
|
|
246
275
|
@trackSpan('ProverNode.createProvingJob', epochNumber => ({ [Attributes.EPOCH_NUMBER]: Number(epochNumber) }))
|
|
247
|
-
private async createProvingJob(epochNumber: bigint) {
|
|
248
|
-
|
|
249
|
-
throw new Error(`Maximum pending proving jobs ${this.options.maxPendingJobs} reached. Cannot create new job.`);
|
|
250
|
-
}
|
|
276
|
+
private async createProvingJob(epochNumber: bigint, opts: { skipEpochCheck?: boolean } = {}) {
|
|
277
|
+
this.checkMaximumPendingJobs();
|
|
251
278
|
|
|
252
|
-
// Gather
|
|
253
|
-
const
|
|
279
|
+
// Gather all data for this epoch
|
|
280
|
+
const epochData = await this.gatherEpochData(epochNumber);
|
|
254
281
|
|
|
255
|
-
const fromBlock = blocks[0].number;
|
|
256
|
-
const toBlock = blocks.at(-1)!.number;
|
|
282
|
+
const fromBlock = epochData.blocks[0].number;
|
|
283
|
+
const toBlock = epochData.blocks.at(-1)!.number;
|
|
284
|
+
this.log.verbose(`Creating proving job for epoch ${epochNumber} for block range ${fromBlock} to ${toBlock}`);
|
|
257
285
|
|
|
258
286
|
// Fast forward world state to right before the target block and get a fork
|
|
259
|
-
this.log.verbose(`Creating proving job for epoch ${epochNumber} for block range ${fromBlock} to ${toBlock}`);
|
|
260
287
|
await this.worldState.syncImmediate(toBlock);
|
|
261
288
|
|
|
262
|
-
// Create a processor
|
|
289
|
+
// Create a processor factory
|
|
263
290
|
const publicProcessorFactory = new PublicProcessorFactory(
|
|
264
291
|
this.contractDataSource,
|
|
265
292
|
this.dateProvider,
|
|
266
293
|
this.telemetryClient,
|
|
267
294
|
);
|
|
268
295
|
|
|
296
|
+
// Set deadline for this job to run. It will abort if it takes too long.
|
|
269
297
|
const deadlineTs = getProofSubmissionDeadlineTimestamp(epochNumber, await this.getL1Constants());
|
|
270
298
|
const deadline = new Date(Number(deadlineTs) * 1000);
|
|
271
|
-
|
|
299
|
+
|
|
300
|
+
const job = this.doCreateEpochProvingJob(epochData, deadline, publicProcessorFactory, opts);
|
|
272
301
|
this.jobs.set(job.getId(), job);
|
|
273
302
|
return job;
|
|
274
303
|
}
|
|
@@ -295,12 +324,13 @@ export class ProverNode implements EpochMonitorHandler, ProverNodeApi, Traceable
|
|
|
295
324
|
}
|
|
296
325
|
|
|
297
326
|
@trackSpan('ProverNode.gatherEpochData', epochNumber => ({ [Attributes.EPOCH_NUMBER]: Number(epochNumber) }))
|
|
298
|
-
private async gatherEpochData(epochNumber: bigint) {
|
|
299
|
-
// Gather blocks for this epoch and their txs
|
|
327
|
+
private async gatherEpochData(epochNumber: bigint): Promise<EpochProvingJobData> {
|
|
300
328
|
const blocks = await this.gatherBlocks(epochNumber);
|
|
301
329
|
const txs = await this.gatherTxs(epochNumber, blocks);
|
|
330
|
+
const l1ToL2Messages = await this.gatherMessages(epochNumber, blocks);
|
|
331
|
+
const previousBlockHeader = await this.gatherPreviousBlockHeader(epochNumber, blocks[0]);
|
|
302
332
|
|
|
303
|
-
return { blocks, txs };
|
|
333
|
+
return { blocks, txs, l1ToL2Messages, epochNumber, previousBlockHeader };
|
|
304
334
|
}
|
|
305
335
|
|
|
306
336
|
private async gatherBlocks(epochNumber: bigint) {
|
|
@@ -328,27 +358,49 @@ export class ProverNode implements EpochMonitorHandler, ProverNodeApi, Traceable
|
|
|
328
358
|
throw new Error(`Txs not found for epoch ${epochNumber}: ${missingTxHashes}`);
|
|
329
359
|
}
|
|
330
360
|
|
|
361
|
+
private async gatherMessages(epochNumber: bigint, blocks: L2Block[]) {
|
|
362
|
+
const messages = await Promise.all(blocks.map(b => this.l1ToL2MessageSource.getL1ToL2Messages(BigInt(b.number))));
|
|
363
|
+
const messageCount = sum(messages.map(m => m.length));
|
|
364
|
+
this.log.verbose(`Gathered all ${messageCount} messages for epoch ${epochNumber}`, { epochNumber });
|
|
365
|
+
const messagesByBlock: Record<number, Fr[]> = {};
|
|
366
|
+
for (let i = 0; i < blocks.length; i++) {
|
|
367
|
+
messagesByBlock[blocks[i].number] = messages[i];
|
|
368
|
+
}
|
|
369
|
+
return messagesByBlock;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
private async gatherPreviousBlockHeader(epochNumber: bigint, initialBlock: L2Block) {
|
|
373
|
+
const previousBlockNumber = initialBlock.number - 1;
|
|
374
|
+
const header = await (previousBlockNumber === 0
|
|
375
|
+
? this.worldState.getCommitted().getInitialHeader()
|
|
376
|
+
: this.l2BlockSource.getBlockHeader(previousBlockNumber));
|
|
377
|
+
|
|
378
|
+
if (!header) {
|
|
379
|
+
throw new Error(`Previous block header ${initialBlock.number} not found for proving epoch ${epochNumber}`);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
this.log.verbose(`Gathered previous block header ${header.getBlockNumber()} for epoch ${epochNumber}`);
|
|
383
|
+
return header;
|
|
384
|
+
}
|
|
385
|
+
|
|
331
386
|
/** Extracted for testing purposes. */
|
|
332
387
|
protected doCreateEpochProvingJob(
|
|
333
|
-
|
|
388
|
+
data: EpochProvingJobData,
|
|
334
389
|
deadline: Date | undefined,
|
|
335
|
-
blocks: L2Block[],
|
|
336
|
-
txs: Tx[],
|
|
337
390
|
publicProcessorFactory: PublicProcessorFactory,
|
|
391
|
+
opts: { skipEpochCheck?: boolean } = {},
|
|
338
392
|
) {
|
|
393
|
+
const { proverNodeMaxParallelBlocksPerEpoch: parallelBlockLimit } = this.config;
|
|
339
394
|
return new EpochProvingJob(
|
|
395
|
+
data,
|
|
340
396
|
this.worldState,
|
|
341
|
-
epochNumber,
|
|
342
|
-
blocks,
|
|
343
|
-
txs,
|
|
344
397
|
this.prover.createEpochProver(),
|
|
345
398
|
publicProcessorFactory,
|
|
346
399
|
this.publisher,
|
|
347
400
|
this.l2BlockSource,
|
|
348
|
-
this.l1ToL2MessageSource,
|
|
349
401
|
this.jobMetrics,
|
|
350
402
|
deadline,
|
|
351
|
-
{ parallelBlockLimit
|
|
403
|
+
{ parallelBlockLimit, ...opts },
|
|
352
404
|
);
|
|
353
405
|
}
|
|
354
406
|
|
|
@@ -356,6 +408,21 @@ export class ProverNode implements EpochMonitorHandler, ProverNodeApi, Traceable
|
|
|
356
408
|
protected async triggerMonitors() {
|
|
357
409
|
await this.epochsMonitor.work();
|
|
358
410
|
}
|
|
411
|
+
|
|
412
|
+
private validateConfig() {
|
|
413
|
+
if (
|
|
414
|
+
this.config.proverNodeFailedEpochStore &&
|
|
415
|
+
(!this.config.dataDirectory || !this.config.l1ChainId || !this.config.rollupVersion)
|
|
416
|
+
) {
|
|
417
|
+
this.log.warn(
|
|
418
|
+
`Invalid prover-node config (missing dataDirectory, l1ChainId, or rollupVersion)`,
|
|
419
|
+
pick(this.config, 'proverNodeFailedEpochStore', 'dataDirectory', 'l1ChainId', 'rollupVersion'),
|
|
420
|
+
);
|
|
421
|
+
throw new Error(
|
|
422
|
+
'All of dataDirectory, l1ChainId, and rollupVersion are required if proverNodeFailedEpochStore is set.',
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
359
426
|
}
|
|
360
427
|
|
|
361
428
|
class EmptyEpochError extends Error {
|
package/src/test/index.ts
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import type { EpochProverManager } from '@aztec/stdlib/interfaces/server';
|
|
2
2
|
|
|
3
|
+
import type { EpochProvingJob } from '../job/epoch-proving-job.js';
|
|
3
4
|
import type { ProverNodePublisher } from '../prover-node-publisher.js';
|
|
4
5
|
import { ProverNode } from '../prover-node.js';
|
|
5
6
|
|
|
6
|
-
class
|
|
7
|
-
public
|
|
8
|
-
public
|
|
7
|
+
abstract class TestProverNodeClass extends ProverNode {
|
|
8
|
+
declare public prover: EpochProverManager;
|
|
9
|
+
declare public publisher: ProverNodePublisher;
|
|
10
|
+
|
|
11
|
+
public abstract override tryUploadEpochFailure(job: EpochProvingJob): Promise<string | undefined>;
|
|
9
12
|
}
|
|
10
13
|
|
|
11
|
-
export type TestProverNode =
|
|
14
|
+
export type TestProverNode = TestProverNodeClass;
|