@aztec/prover-node 1.0.0-nightly.20250708 → 1.0.0-staging.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/config.d.ts +2 -2
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +2 -5
- package/dest/factory.d.ts +6 -8
- package/dest/factory.d.ts.map +1 -1
- package/dest/factory.js +10 -13
- package/dest/prover-coordination/combined-prover-coordination.d.ts +22 -0
- package/dest/prover-coordination/combined-prover-coordination.d.ts.map +1 -0
- package/dest/prover-coordination/combined-prover-coordination.js +140 -0
- package/dest/prover-coordination/config.d.ts +7 -0
- package/dest/prover-coordination/config.d.ts.map +1 -0
- package/dest/prover-coordination/config.js +12 -0
- package/dest/prover-coordination/factory.d.ts +23 -0
- package/dest/prover-coordination/factory.d.ts.map +1 -0
- package/dest/prover-coordination/factory.js +52 -0
- package/dest/prover-coordination/index.d.ts +3 -0
- package/dest/prover-coordination/index.d.ts.map +1 -0
- package/dest/prover-coordination/index.js +2 -0
- package/dest/prover-node.d.ts +8 -6
- package/dest/prover-node.d.ts.map +1 -1
- package/dest/prover-node.js +33 -15
- package/package.json +21 -21
- package/src/config.ts +4 -6
- package/src/factory.ts +20 -41
- package/src/prover-coordination/combined-prover-coordination.ts +164 -0
- package/src/prover-coordination/config.ts +18 -0
- package/src/prover-coordination/factory.ts +86 -0
- package/src/prover-coordination/index.ts +2 -0
- package/src/prover-node.ts +38 -13
package/dest/prover-node.js
CHANGED
|
@@ -8,6 +8,7 @@ import { assertRequired, compact, pick, sum } from '@aztec/foundation/collection
|
|
|
8
8
|
import { memoize } from '@aztec/foundation/decorators';
|
|
9
9
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
10
10
|
import { createLogger } from '@aztec/foundation/log';
|
|
11
|
+
import { RunningPromise } from '@aztec/foundation/running-promise';
|
|
11
12
|
import { DateProvider } from '@aztec/foundation/timer';
|
|
12
13
|
import { PublicProcessorFactory } from '@aztec/simulator/server';
|
|
13
14
|
import { getProofSubmissionDeadlineTimestamp } from '@aztec/stdlib/epoch-helpers';
|
|
@@ -27,7 +28,7 @@ import { ProverNodeJobMetrics, ProverNodeRewardsMetrics } from './metrics.js';
|
|
|
27
28
|
l1ToL2MessageSource;
|
|
28
29
|
contractDataSource;
|
|
29
30
|
worldState;
|
|
30
|
-
|
|
31
|
+
coordination;
|
|
31
32
|
epochsMonitor;
|
|
32
33
|
telemetryClient;
|
|
33
34
|
log;
|
|
@@ -37,15 +38,17 @@ import { ProverNodeJobMetrics, ProverNodeRewardsMetrics } from './metrics.js';
|
|
|
37
38
|
jobMetrics;
|
|
38
39
|
rewardsMetrics;
|
|
39
40
|
l1Metrics;
|
|
41
|
+
txFetcher;
|
|
42
|
+
lastBlockNumber;
|
|
40
43
|
tracer;
|
|
41
|
-
constructor(prover, publisher, l2BlockSource, l1ToL2MessageSource, contractDataSource, worldState,
|
|
44
|
+
constructor(prover, publisher, l2BlockSource, l1ToL2MessageSource, contractDataSource, worldState, coordination, epochsMonitor, config = {}, telemetryClient = getTelemetryClient()){
|
|
42
45
|
this.prover = prover;
|
|
43
46
|
this.publisher = publisher;
|
|
44
47
|
this.l2BlockSource = l2BlockSource;
|
|
45
48
|
this.l1ToL2MessageSource = l1ToL2MessageSource;
|
|
46
49
|
this.contractDataSource = contractDataSource;
|
|
47
50
|
this.worldState = worldState;
|
|
48
|
-
this.
|
|
51
|
+
this.coordination = coordination;
|
|
49
52
|
this.epochsMonitor = epochsMonitor;
|
|
50
53
|
this.telemetryClient = telemetryClient;
|
|
51
54
|
this.log = createLogger('prover-node');
|
|
@@ -61,7 +64,6 @@ import { ProverNodeJobMetrics, ProverNodeRewardsMetrics } from './metrics.js';
|
|
|
61
64
|
txGatheringIntervalMs: 1_000,
|
|
62
65
|
txGatheringBatchSize: 10,
|
|
63
66
|
txGatheringMaxParallelRequestsPerNode: 100,
|
|
64
|
-
txGatheringTimeoutMs: 120_000,
|
|
65
67
|
proverNodeFailedEpochStore: undefined,
|
|
66
68
|
...compact(config)
|
|
67
69
|
};
|
|
@@ -70,12 +72,13 @@ import { ProverNodeJobMetrics, ProverNodeRewardsMetrics } from './metrics.js';
|
|
|
70
72
|
this.tracer = telemetryClient.getTracer('ProverNode');
|
|
71
73
|
this.jobMetrics = new ProverNodeJobMetrics(meter, telemetryClient.getTracer('EpochProvingJob'));
|
|
72
74
|
this.rewardsMetrics = new ProverNodeRewardsMetrics(meter, EthAddress.fromField(this.prover.getProverId()), this.publisher.getRollupContract());
|
|
75
|
+
this.txFetcher = new RunningPromise(()=>this.checkForTxs(), this.log, this.config.txGatheringIntervalMs);
|
|
73
76
|
}
|
|
74
77
|
getProverId() {
|
|
75
78
|
return this.prover.getProverId();
|
|
76
79
|
}
|
|
77
80
|
getP2P() {
|
|
78
|
-
return this.
|
|
81
|
+
return this.coordination.getP2PClient();
|
|
79
82
|
}
|
|
80
83
|
/**
|
|
81
84
|
* Handles an epoch being completed by starting a proof for it if there are no active jobs for it.
|
|
@@ -108,6 +111,7 @@ import { ProverNodeJobMetrics, ProverNodeRewardsMetrics } from './metrics.js';
|
|
|
108
111
|
* Starts the prover node so it periodically checks for unproven epochs in the unfinalised chain from L1 and
|
|
109
112
|
* starts proving jobs for them.
|
|
110
113
|
*/ async start() {
|
|
114
|
+
this.txFetcher.start();
|
|
111
115
|
this.epochsMonitor.start(this);
|
|
112
116
|
this.l1Metrics.start();
|
|
113
117
|
await this.rewardsMetrics.start();
|
|
@@ -117,13 +121,14 @@ import { ProverNodeJobMetrics, ProverNodeRewardsMetrics } from './metrics.js';
|
|
|
117
121
|
* Stops the prover node and all its dependencies.
|
|
118
122
|
*/ async stop() {
|
|
119
123
|
this.log.info('Stopping ProverNode');
|
|
124
|
+
await this.txFetcher.stop();
|
|
120
125
|
await this.epochsMonitor.stop();
|
|
121
126
|
await this.prover.stop();
|
|
122
|
-
await tryStop(this.p2pClient);
|
|
123
127
|
await tryStop(this.l2BlockSource);
|
|
124
128
|
this.publisher.interrupt();
|
|
125
129
|
await Promise.all(Array.from(this.jobs.values()).map((job)=>job.stop()));
|
|
126
130
|
await this.worldState.stop();
|
|
131
|
+
await tryStop(this.coordination);
|
|
127
132
|
this.l1Metrics.stop();
|
|
128
133
|
this.rewardsMetrics.stop();
|
|
129
134
|
await this.telemetryClient.stop();
|
|
@@ -221,6 +226,19 @@ import { ProverNodeJobMetrics, ProverNodeRewardsMetrics } from './metrics.js';
|
|
|
221
226
|
getL1Constants() {
|
|
222
227
|
return this.l2BlockSource.getL1Constants();
|
|
223
228
|
}
|
|
229
|
+
/** Monitors for new blocks and requests their txs from the p2p layer to ensure they are available for proving. */ async checkForTxs() {
|
|
230
|
+
const blockNumber = await this.l2BlockSource.getBlockNumber();
|
|
231
|
+
if (this.lastBlockNumber === undefined || blockNumber > this.lastBlockNumber) {
|
|
232
|
+
const block = await this.l2BlockSource.getBlock(blockNumber);
|
|
233
|
+
if (!block) {
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
const txHashes = block.body.txEffects.map((tx)=>tx.txHash);
|
|
237
|
+
this.log.verbose(`Fetching ${txHashes.length} tx hashes for block number ${blockNumber} from coordination`);
|
|
238
|
+
await this.coordination.gatherTxs(txHashes); // This stores the txs in the tx pool, no need to persist them here
|
|
239
|
+
this.lastBlockNumber = blockNumber;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
224
242
|
async gatherEpochData(epochNumber) {
|
|
225
243
|
const blocks = await this.gatherBlocks(epochNumber);
|
|
226
244
|
const txs = await this.gatherTxs(epochNumber, blocks);
|
|
@@ -242,20 +260,17 @@ import { ProverNodeJobMetrics, ProverNodeRewardsMetrics } from './metrics.js';
|
|
|
242
260
|
return blocks;
|
|
243
261
|
}
|
|
244
262
|
async gatherTxs(epochNumber, blocks) {
|
|
245
|
-
const
|
|
246
|
-
const
|
|
247
|
-
|
|
248
|
-
deadline
|
|
249
|
-
})));
|
|
250
|
-
const txs = txsByBlock.map(({ txs })=>txs).flat();
|
|
251
|
-
const missingTxs = txsByBlock.map(({ missingTxs })=>missingTxs).flat();
|
|
252
|
-
if (missingTxs.length === 0) {
|
|
263
|
+
const txsToFind = blocks.flatMap((block)=>block.body.txEffects.map((tx)=>tx.txHash));
|
|
264
|
+
const txs = await this.coordination.getTxsByHash(txsToFind);
|
|
265
|
+
if (txs.length === txsToFind.length) {
|
|
253
266
|
this.log.verbose(`Gathered all ${txs.length} txs for epoch ${epochNumber}`, {
|
|
254
267
|
epochNumber
|
|
255
268
|
});
|
|
256
269
|
return txs;
|
|
257
270
|
}
|
|
258
|
-
|
|
271
|
+
const txHashesFound = await Promise.all(txs.map((tx)=>tx.getTxHash()));
|
|
272
|
+
const missingTxHashes = txsToFind.filter((txHashToFind)=>!txHashesFound.some((txHashFound)=>txHashToFind.equals(txHashFound))).join(', ');
|
|
273
|
+
throw new Error(`Txs not found for epoch ${epochNumber}: ${missingTxHashes}`);
|
|
259
274
|
}
|
|
260
275
|
async gatherMessages(epochNumber, blocks) {
|
|
261
276
|
const messages = await Promise.all(blocks.map((b)=>this.l1ToL2MessageSource.getL1ToL2Messages(b.number)));
|
|
@@ -303,6 +318,9 @@ _ts_decorate([
|
|
|
303
318
|
_ts_decorate([
|
|
304
319
|
memoize
|
|
305
320
|
], ProverNode.prototype, "getL1Constants", null);
|
|
321
|
+
_ts_decorate([
|
|
322
|
+
trackSpan('ProverNode.checkForTxs')
|
|
323
|
+
], ProverNode.prototype, "checkForTxs", null);
|
|
306
324
|
_ts_decorate([
|
|
307
325
|
trackSpan('ProverNode.gatherEpochData', (epochNumber)=>({
|
|
308
326
|
[Attributes.EPOCH_NUMBER]: Number(epochNumber)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aztec/prover-node",
|
|
3
|
-
"version": "1.0.0-
|
|
3
|
+
"version": "1.0.0-staging.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": "./dest/index.js",
|
|
@@ -56,26 +56,26 @@
|
|
|
56
56
|
]
|
|
57
57
|
},
|
|
58
58
|
"dependencies": {
|
|
59
|
-
"@aztec/archiver": "1.0.0-
|
|
60
|
-
"@aztec/bb-prover": "1.0.0-
|
|
61
|
-
"@aztec/blob-lib": "1.0.0-
|
|
62
|
-
"@aztec/blob-sink": "1.0.0-
|
|
63
|
-
"@aztec/constants": "1.0.0-
|
|
64
|
-
"@aztec/epoch-cache": "1.0.0-
|
|
65
|
-
"@aztec/ethereum": "1.0.0-
|
|
66
|
-
"@aztec/foundation": "1.0.0-
|
|
67
|
-
"@aztec/kv-store": "1.0.0-
|
|
68
|
-
"@aztec/l1-artifacts": "1.0.0-
|
|
69
|
-
"@aztec/node-lib": "1.0.0-
|
|
70
|
-
"@aztec/noir-protocol-circuits-types": "1.0.0-
|
|
71
|
-
"@aztec/p2p": "1.0.0-
|
|
72
|
-
"@aztec/protocol-contracts": "1.0.0-
|
|
73
|
-
"@aztec/prover-client": "1.0.0-
|
|
74
|
-
"@aztec/sequencer-client": "1.0.0-
|
|
75
|
-
"@aztec/simulator": "1.0.0-
|
|
76
|
-
"@aztec/stdlib": "1.0.0-
|
|
77
|
-
"@aztec/telemetry-client": "1.0.0-
|
|
78
|
-
"@aztec/world-state": "1.0.0-
|
|
59
|
+
"@aztec/archiver": "1.0.0-staging.0",
|
|
60
|
+
"@aztec/bb-prover": "1.0.0-staging.0",
|
|
61
|
+
"@aztec/blob-lib": "1.0.0-staging.0",
|
|
62
|
+
"@aztec/blob-sink": "1.0.0-staging.0",
|
|
63
|
+
"@aztec/constants": "1.0.0-staging.0",
|
|
64
|
+
"@aztec/epoch-cache": "1.0.0-staging.0",
|
|
65
|
+
"@aztec/ethereum": "1.0.0-staging.0",
|
|
66
|
+
"@aztec/foundation": "1.0.0-staging.0",
|
|
67
|
+
"@aztec/kv-store": "1.0.0-staging.0",
|
|
68
|
+
"@aztec/l1-artifacts": "1.0.0-staging.0",
|
|
69
|
+
"@aztec/node-lib": "1.0.0-staging.0",
|
|
70
|
+
"@aztec/noir-protocol-circuits-types": "1.0.0-staging.0",
|
|
71
|
+
"@aztec/p2p": "1.0.0-staging.0",
|
|
72
|
+
"@aztec/protocol-contracts": "1.0.0-staging.0",
|
|
73
|
+
"@aztec/prover-client": "1.0.0-staging.0",
|
|
74
|
+
"@aztec/sequencer-client": "1.0.0-staging.0",
|
|
75
|
+
"@aztec/simulator": "1.0.0-staging.0",
|
|
76
|
+
"@aztec/stdlib": "1.0.0-staging.0",
|
|
77
|
+
"@aztec/telemetry-client": "1.0.0-staging.0",
|
|
78
|
+
"@aztec/world-state": "1.0.0-staging.0",
|
|
79
79
|
"source-map-support": "^0.5.21",
|
|
80
80
|
"tslib": "^2.4.0",
|
|
81
81
|
"viem": "2.23.7"
|
package/src/config.ts
CHANGED
|
@@ -26,6 +26,8 @@ import {
|
|
|
26
26
|
} from '@aztec/sequencer-client/config';
|
|
27
27
|
import { type WorldStateConfig, worldStateConfigMappings } from '@aztec/world-state/config';
|
|
28
28
|
|
|
29
|
+
import { type ProverCoordinationConfig, proverCoordinationConfigMappings } from './prover-coordination/config.js';
|
|
30
|
+
|
|
29
31
|
export type ProverNodeConfig = ArchiverConfig &
|
|
30
32
|
ProverClientUserConfig &
|
|
31
33
|
P2PConfig &
|
|
@@ -33,6 +35,7 @@ export type ProverNodeConfig = ArchiverConfig &
|
|
|
33
35
|
PublisherConfig &
|
|
34
36
|
TxSenderConfig &
|
|
35
37
|
DataStoreConfig &
|
|
38
|
+
ProverCoordinationConfig &
|
|
36
39
|
SharedNodeConfig &
|
|
37
40
|
SpecificProverNodeConfig &
|
|
38
41
|
GenesisStateConfig;
|
|
@@ -42,7 +45,6 @@ export type SpecificProverNodeConfig = {
|
|
|
42
45
|
proverNodePollingIntervalMs: number;
|
|
43
46
|
proverNodeMaxParallelBlocksPerEpoch: number;
|
|
44
47
|
proverNodeFailedEpochStore: string | undefined;
|
|
45
|
-
txGatheringTimeoutMs: number;
|
|
46
48
|
txGatheringIntervalMs: number;
|
|
47
49
|
txGatheringBatchSize: number;
|
|
48
50
|
txGatheringMaxParallelRequestsPerNode: number;
|
|
@@ -84,11 +86,6 @@ const specificProverNodeConfigMappings: ConfigMappingsType<SpecificProverNodeCon
|
|
|
84
86
|
description: 'How many tx requests to make in parallel to each node',
|
|
85
87
|
...numberConfigHelper(100),
|
|
86
88
|
},
|
|
87
|
-
txGatheringTimeoutMs: {
|
|
88
|
-
env: 'PROVER_NODE_TX_GATHERING_TIMEOUT_MS',
|
|
89
|
-
description: 'How long to wait for tx data to be available before giving up',
|
|
90
|
-
...numberConfigHelper(120_000),
|
|
91
|
-
},
|
|
92
89
|
};
|
|
93
90
|
|
|
94
91
|
export const proverNodeConfigMappings: ConfigMappingsType<ProverNodeConfig> = {
|
|
@@ -99,6 +96,7 @@ export const proverNodeConfigMappings: ConfigMappingsType<ProverNodeConfig> = {
|
|
|
99
96
|
...worldStateConfigMappings,
|
|
100
97
|
...getPublisherConfigMappings('PROVER'),
|
|
101
98
|
...getTxSenderConfigMappings('PROVER'),
|
|
99
|
+
...proverCoordinationConfigMappings,
|
|
102
100
|
...specificProverNodeConfigMappings,
|
|
103
101
|
...genesisStateConfigMappings,
|
|
104
102
|
...sharedNodeConfigMappings,
|
package/src/factory.ts
CHANGED
|
@@ -1,51 +1,44 @@
|
|
|
1
1
|
import { type Archiver, createArchiver } from '@aztec/archiver';
|
|
2
|
-
import { BBCircuitVerifier, QueuedIVCVerifier, TestCircuitVerifier } from '@aztec/bb-prover';
|
|
3
2
|
import { type BlobSinkClientInterface, createBlobSinkClient } from '@aztec/blob-sink/client';
|
|
4
3
|
import { EpochCache } from '@aztec/epoch-cache';
|
|
5
4
|
import { L1TxUtils, RollupContract, createEthereumChain, createExtendedL1Client } from '@aztec/ethereum';
|
|
6
5
|
import { pick } from '@aztec/foundation/collection';
|
|
7
6
|
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
8
|
-
import { DateProvider } from '@aztec/foundation/timer';
|
|
9
7
|
import type { DataStoreConfig } from '@aztec/kv-store/config';
|
|
10
8
|
import { trySnapshotSync } from '@aztec/node-lib/actions';
|
|
11
|
-
import { NodeRpcTxSource, createP2PClient } from '@aztec/p2p';
|
|
12
9
|
import { createProverClient } from '@aztec/prover-client';
|
|
13
10
|
import { createAndStartProvingBroker } from '@aztec/prover-client/broker';
|
|
14
|
-
import type {
|
|
15
|
-
import { P2PClientType } from '@aztec/stdlib/p2p';
|
|
11
|
+
import type { ProvingJobBroker } from '@aztec/stdlib/interfaces/server';
|
|
16
12
|
import type { PublicDataTreeLeaf } from '@aztec/stdlib/trees';
|
|
17
|
-
import { getPackageVersion } from '@aztec/stdlib/update-checker';
|
|
18
13
|
import { type TelemetryClient, getTelemetryClient } from '@aztec/telemetry-client';
|
|
19
14
|
import { createWorldStateSynchronizer } from '@aztec/world-state';
|
|
20
15
|
|
|
21
16
|
import { type ProverNodeConfig, resolveConfig } from './config.js';
|
|
22
17
|
import { EpochMonitor } from './monitors/epoch-monitor.js';
|
|
18
|
+
import type { TxSource } from './prover-coordination/combined-prover-coordination.js';
|
|
19
|
+
import { createProverCoordination } from './prover-coordination/factory.js';
|
|
23
20
|
import { ProverNodePublisher } from './prover-node-publisher.js';
|
|
24
21
|
import { ProverNode } from './prover-node.js';
|
|
25
22
|
|
|
26
|
-
export type ProverNodeDeps = {
|
|
27
|
-
telemetry?: TelemetryClient;
|
|
28
|
-
log?: Logger;
|
|
29
|
-
aztecNodeTxProvider?: Pick<AztecNode, 'getTxsByHash'>;
|
|
30
|
-
archiver?: Archiver;
|
|
31
|
-
publisher?: ProverNodePublisher;
|
|
32
|
-
blobSinkClient?: BlobSinkClientInterface;
|
|
33
|
-
broker?: ProvingJobBroker;
|
|
34
|
-
l1TxUtils?: L1TxUtils;
|
|
35
|
-
dateProvider?: DateProvider;
|
|
36
|
-
};
|
|
37
|
-
|
|
38
23
|
/** Creates a new prover node given a config. */
|
|
39
24
|
export async function createProverNode(
|
|
40
25
|
userConfig: ProverNodeConfig & DataStoreConfig,
|
|
41
|
-
deps:
|
|
26
|
+
deps: {
|
|
27
|
+
telemetry?: TelemetryClient;
|
|
28
|
+
log?: Logger;
|
|
29
|
+
aztecNodeTxProvider?: TxSource;
|
|
30
|
+
archiver?: Archiver;
|
|
31
|
+
publisher?: ProverNodePublisher;
|
|
32
|
+
blobSinkClient?: BlobSinkClientInterface;
|
|
33
|
+
broker?: ProvingJobBroker;
|
|
34
|
+
l1TxUtils?: L1TxUtils;
|
|
35
|
+
} = {},
|
|
42
36
|
options: {
|
|
43
37
|
prefilledPublicData?: PublicDataTreeLeaf[];
|
|
44
38
|
} = {},
|
|
45
39
|
) {
|
|
46
40
|
const config = resolveConfig(userConfig);
|
|
47
41
|
const telemetry = deps.telemetry ?? getTelemetryClient();
|
|
48
|
-
const dateProvider = deps.dateProvider ?? new DateProvider();
|
|
49
42
|
const blobSinkClient =
|
|
50
43
|
deps.blobSinkClient ?? createBlobSinkClient(config, { logger: createLogger('prover-node:blob-sink:client') });
|
|
51
44
|
const log = deps.log ?? createLogger('prover-node');
|
|
@@ -78,29 +71,15 @@ export async function createProverNode(
|
|
|
78
71
|
|
|
79
72
|
const epochCache = await EpochCache.create(config.l1Contracts.rollupAddress, config);
|
|
80
73
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
const p2pClient = await createP2PClient(
|
|
87
|
-
P2PClientType.Prover,
|
|
88
|
-
config,
|
|
89
|
-
archiver,
|
|
90
|
-
proofVerifier,
|
|
74
|
+
// If config.p2pEnabled is true, createProverCoordination will create a p2p client where txs are requested
|
|
75
|
+
// If config.proverCoordinationNodeUrls is not empty, createProverCoordination will create set of aztec node clients from which txs are requested
|
|
76
|
+
const proverCoordination = await createProverCoordination(config, {
|
|
77
|
+
aztecNodeTxProvider: deps.aztecNodeTxProvider,
|
|
91
78
|
worldStateSynchronizer,
|
|
79
|
+
archiver,
|
|
92
80
|
epochCache,
|
|
93
|
-
getPackageVersion() ?? '',
|
|
94
|
-
dateProvider,
|
|
95
81
|
telemetry,
|
|
96
|
-
|
|
97
|
-
txCollectionNodeSources: deps.aztecNodeTxProvider
|
|
98
|
-
? [new NodeRpcTxSource(deps.aztecNodeTxProvider, 'TestNode')]
|
|
99
|
-
: [],
|
|
100
|
-
},
|
|
101
|
-
);
|
|
102
|
-
|
|
103
|
-
await p2pClient.start();
|
|
82
|
+
});
|
|
104
83
|
|
|
105
84
|
const proverNodeConfig = {
|
|
106
85
|
...pick(
|
|
@@ -131,7 +110,7 @@ export async function createProverNode(
|
|
|
131
110
|
archiver,
|
|
132
111
|
archiver,
|
|
133
112
|
worldStateSynchronizer,
|
|
134
|
-
|
|
113
|
+
proverCoordination,
|
|
135
114
|
epochMonitor,
|
|
136
115
|
proverNodeConfig,
|
|
137
116
|
telemetry,
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { asyncPool } from '@aztec/foundation/async-pool';
|
|
2
|
+
import { createLogger } from '@aztec/foundation/log';
|
|
3
|
+
import type { P2P } from '@aztec/p2p';
|
|
4
|
+
import type { P2PClient, ProverCoordination } from '@aztec/stdlib/interfaces/server';
|
|
5
|
+
import { type Tx, TxHash } from '@aztec/stdlib/tx';
|
|
6
|
+
|
|
7
|
+
export type CombinedCoordinationOptions = {
|
|
8
|
+
// These options apply to http tx gathering only
|
|
9
|
+
txGatheringBatchSize: number;
|
|
10
|
+
txGatheringMaxParallelRequestsPerNode: number;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
interface CoordinationPool {
|
|
14
|
+
getTxsByHash(txHashes: TxHash[]): Promise<(Tx | undefined)[]>;
|
|
15
|
+
hasTxsInPool(txHashes: TxHash[]): Promise<boolean[]>;
|
|
16
|
+
getTxsByHashFromPool(txHashes: TxHash[]): Promise<(Tx | undefined)[]>;
|
|
17
|
+
addTxs(txs: Tx[]): Promise<number>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface TxSource {
|
|
21
|
+
getTxsByHash(txHashes: TxHash[]): Promise<(Tx | undefined)[]>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Wraps the p2p client into a coordination pool
|
|
25
|
+
class P2PCoordinationPool implements CoordinationPool {
|
|
26
|
+
constructor(private readonly p2p: P2P) {}
|
|
27
|
+
getTxsByHash(txHashes: TxHash[]): Promise<(Tx | undefined)[]> {
|
|
28
|
+
return this.p2p.getTxsByHash(txHashes, undefined);
|
|
29
|
+
}
|
|
30
|
+
hasTxsInPool(txHashes: TxHash[]): Promise<boolean[]> {
|
|
31
|
+
return this.p2p.hasTxsInPool(txHashes);
|
|
32
|
+
}
|
|
33
|
+
getTxsByHashFromPool(txHashes: TxHash[]): Promise<(Tx | undefined)[]> {
|
|
34
|
+
return this.p2p.getTxsByHashFromPool(txHashes);
|
|
35
|
+
}
|
|
36
|
+
addTxs(txs: Tx[]): Promise<number> {
|
|
37
|
+
return this.p2p.addTxsToPool(txs);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Wraps an in memory tx pool into a coordination pool. Used for testing when no p2p/tx pool is available.
|
|
42
|
+
class InMemoryCoordinationPool implements CoordinationPool {
|
|
43
|
+
private txs: Map<string, Tx> = new Map();
|
|
44
|
+
getTxsByHash(txHashes: TxHash[]): Promise<(Tx | undefined)[]> {
|
|
45
|
+
return Promise.resolve(txHashes.map(hash => this.txs.get(hash.toString())));
|
|
46
|
+
}
|
|
47
|
+
hasTxsInPool(txHashes: TxHash[]): Promise<boolean[]> {
|
|
48
|
+
return Promise.resolve(txHashes.map(hash => this.txs.has(hash.toString())));
|
|
49
|
+
}
|
|
50
|
+
getTxsByHashFromPool(txHashes: TxHash[]): Promise<(Tx | undefined)[]> {
|
|
51
|
+
return this.getTxsByHash(txHashes);
|
|
52
|
+
}
|
|
53
|
+
async addTxs(txs: Tx[]): Promise<number> {
|
|
54
|
+
const hashes = await Promise.all(txs.map(tx => tx.getTxHash()));
|
|
55
|
+
txs.forEach((tx, index) => this.txs.set(hashes[index].toString(), tx));
|
|
56
|
+
return txs.length;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Class to implement combined transaction retrieval from p2p and any available nodes
|
|
61
|
+
export class CombinedProverCoordination implements ProverCoordination {
|
|
62
|
+
constructor(
|
|
63
|
+
public readonly p2p: P2P | undefined,
|
|
64
|
+
public readonly aztecNodes: TxSource[],
|
|
65
|
+
private readonly options: CombinedCoordinationOptions = {
|
|
66
|
+
txGatheringBatchSize: 10,
|
|
67
|
+
txGatheringMaxParallelRequestsPerNode: 10,
|
|
68
|
+
},
|
|
69
|
+
private readonly log = createLogger('prover-node:combined-prover-coordination'),
|
|
70
|
+
) {}
|
|
71
|
+
|
|
72
|
+
public getP2PClient(): P2PClient | undefined {
|
|
73
|
+
return this.p2p;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
public async getTxsByHash(txHashes: TxHash[]): Promise<Tx[]> {
|
|
77
|
+
const pool = this.p2p ? new P2PCoordinationPool(this.p2p) : new InMemoryCoordinationPool();
|
|
78
|
+
await this.#gatherTxs(txHashes, pool);
|
|
79
|
+
const availability = await pool.hasTxsInPool(txHashes);
|
|
80
|
+
const notFound = txHashes.filter((_, index) => !availability[index]);
|
|
81
|
+
if (notFound.length > 0) {
|
|
82
|
+
throw new Error(`Could not find txs: ${notFound.map(tx => tx.toString())}`);
|
|
83
|
+
}
|
|
84
|
+
const txs = await pool.getTxsByHashFromPool(txHashes);
|
|
85
|
+
return txs.filter(tx => tx !== undefined) as Tx[];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
public async gatherTxs(txHashes: TxHash[]): Promise<void> {
|
|
89
|
+
const pool = this.p2p ? new P2PCoordinationPool(this.p2p) : new InMemoryCoordinationPool();
|
|
90
|
+
await this.#gatherTxs(txHashes, pool);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async #gatherTxs(txHashes: TxHash[], pool: CoordinationPool): Promise<void> {
|
|
94
|
+
const availability = await pool.hasTxsInPool(txHashes);
|
|
95
|
+
const notFound = txHashes.filter((_, index) => !availability[index]);
|
|
96
|
+
const txsToFind = new Set(notFound.map(tx => tx.toString()));
|
|
97
|
+
if (txsToFind.size === 0) {
|
|
98
|
+
this.log.info(`Check for ${txHashes.length} txs found all in the pool`);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
this.log.info(`Check for ${txHashes.length} txs found ${txsToFind.size} missing. Will gather from nodes and p2p`);
|
|
102
|
+
const originalToFind = txsToFind.size;
|
|
103
|
+
await this.#gatherTxsFromAllNodes(txsToFind, pool);
|
|
104
|
+
if (txsToFind.size === 0) {
|
|
105
|
+
this.log.info(`Found all ${originalToFind} txs directly from nodes`);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
const toFindFromP2P = txsToFind.size;
|
|
109
|
+
this.log.verbose(`Gathering ${toFindFromP2P} txs from p2p network`);
|
|
110
|
+
const foundFromP2P = await pool.getTxsByHash([...txsToFind].map(tx => TxHash.fromString(tx)));
|
|
111
|
+
|
|
112
|
+
// TODO(!!): test for this
|
|
113
|
+
// getTxsByHash returns undefined for transactions that are not found, so it must be filtered to find the true length
|
|
114
|
+
const foundFromP2PLength = foundFromP2P.filter(tx => !!tx).length;
|
|
115
|
+
|
|
116
|
+
const numFoundFromNodes = originalToFind - toFindFromP2P;
|
|
117
|
+
const numNotFound = toFindFromP2P - foundFromP2PLength;
|
|
118
|
+
if (numNotFound === 0) {
|
|
119
|
+
this.log.info(`Found all ${originalToFind} txs. ${numFoundFromNodes} from nodes, ${foundFromP2PLength} from p2p`);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
this.log.warn(
|
|
123
|
+
`Failed to find ${numNotFound} txs from any source. Found ${foundFromP2PLength} from p2p and ${numFoundFromNodes} from nodes`,
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async #gatherTxsFromAllNodes(txsToFind: Set<string>, pool: CoordinationPool) {
|
|
128
|
+
if (txsToFind.size === 0 || this.aztecNodes.length === 0) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
await Promise.all(this.aztecNodes.map(aztecNode => this.#gatherTxsFromNode(txsToFind, aztecNode, pool)));
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async #gatherTxsFromNode(txsToFind: Set<string>, aztecNode: TxSource, pool: CoordinationPool) {
|
|
135
|
+
const totalTxsRequired = txsToFind.size;
|
|
136
|
+
|
|
137
|
+
// It's possible that the set is empty as we already found the txs
|
|
138
|
+
if (totalTxsRequired === 0) {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
let totalTxsGathered = 0;
|
|
142
|
+
|
|
143
|
+
const batches: string[][] = [];
|
|
144
|
+
const allTxs: string[] = [...txsToFind];
|
|
145
|
+
while (allTxs.length) {
|
|
146
|
+
const batch = allTxs.splice(0, this.options.txGatheringBatchSize);
|
|
147
|
+
batches.push(batch);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
await asyncPool(this.options.txGatheringMaxParallelRequestsPerNode, batches, async batch => {
|
|
151
|
+
try {
|
|
152
|
+
const txs = (await aztecNode.getTxsByHash(batch.map(b => TxHash.fromString(b)))).filter((tx): tx is Tx => !!tx);
|
|
153
|
+
const hashes = await Promise.all(txs.map(tx => tx.getTxHash()));
|
|
154
|
+
await pool.addTxs(txs);
|
|
155
|
+
hashes.forEach(hash => txsToFind.delete(hash.toString()));
|
|
156
|
+
totalTxsGathered += txs.length;
|
|
157
|
+
} catch (err) {
|
|
158
|
+
this.log.error(`Error gathering txs from aztec node: ${err}`);
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
this.log.verbose(`Gathered ${totalTxsGathered} of ${totalTxsRequired} txs from a node`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type ConfigMappingsType, getConfigFromMappings } from '@aztec/foundation/config';
|
|
2
|
+
|
|
3
|
+
export type ProverCoordinationConfig = {
|
|
4
|
+
proverCoordinationNodeUrls: string[];
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export const proverCoordinationConfigMappings: ConfigMappingsType<ProverCoordinationConfig> = {
|
|
8
|
+
proverCoordinationNodeUrls: {
|
|
9
|
+
env: 'PROVER_COORDINATION_NODE_URLS',
|
|
10
|
+
description: 'The URLs of the tx provider nodes',
|
|
11
|
+
parseEnv: (val: string) => val.split(',').map(url => url.trim().replace(/\/$/, '')),
|
|
12
|
+
defaultValue: [],
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export function getTxProviderConfigFromEnv(): ProverCoordinationConfig {
|
|
17
|
+
return getConfigFromMappings<ProverCoordinationConfig>(proverCoordinationConfigMappings);
|
|
18
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { ArchiveSource, Archiver } from '@aztec/archiver';
|
|
2
|
+
import { BBCircuitVerifier, TestCircuitVerifier } from '@aztec/bb-prover';
|
|
3
|
+
import type { EpochCache } from '@aztec/epoch-cache';
|
|
4
|
+
import { createLogger } from '@aztec/foundation/log';
|
|
5
|
+
import type { DataStoreConfig } from '@aztec/kv-store/config';
|
|
6
|
+
import { getVKTreeRoot } from '@aztec/noir-protocol-circuits-types/vk-tree';
|
|
7
|
+
import { createP2PClient } from '@aztec/p2p';
|
|
8
|
+
import { protocolContractTreeRoot } from '@aztec/protocol-contracts';
|
|
9
|
+
import { type AztecNode, createAztecNodeClient } from '@aztec/stdlib/interfaces/client';
|
|
10
|
+
import type { ProverCoordination, WorldStateSynchronizer } from '@aztec/stdlib/interfaces/server';
|
|
11
|
+
import { P2PClientType } from '@aztec/stdlib/p2p';
|
|
12
|
+
import { getPackageVersion } from '@aztec/stdlib/update-checker';
|
|
13
|
+
import { getComponentsVersionsFromConfig } from '@aztec/stdlib/versioning';
|
|
14
|
+
import { type TelemetryClient, makeTracedFetch } from '@aztec/telemetry-client';
|
|
15
|
+
|
|
16
|
+
import type { ProverNodeConfig } from '../config.js';
|
|
17
|
+
import {
|
|
18
|
+
type CombinedCoordinationOptions,
|
|
19
|
+
CombinedProverCoordination,
|
|
20
|
+
type TxSource,
|
|
21
|
+
} from './combined-prover-coordination.js';
|
|
22
|
+
|
|
23
|
+
// We return a reference to the P2P client so that the prover node can stop the service when it shuts down.
|
|
24
|
+
type ProverCoordinationDeps = {
|
|
25
|
+
aztecNodeTxProvider?: TxSource;
|
|
26
|
+
worldStateSynchronizer: WorldStateSynchronizer;
|
|
27
|
+
archiver: Archiver | ArchiveSource;
|
|
28
|
+
telemetry?: TelemetryClient;
|
|
29
|
+
epochCache: EpochCache;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Creates a prover coordination service.
|
|
34
|
+
* If p2p is enabled, prover coordination is done via p2p.
|
|
35
|
+
* If an Aztec node URL is provided, prover coordination is done via the Aztec node over http.
|
|
36
|
+
* If an aztec node is provided, it is returned directly.
|
|
37
|
+
*/
|
|
38
|
+
export async function createProverCoordination(
|
|
39
|
+
config: ProverNodeConfig & DataStoreConfig,
|
|
40
|
+
deps: ProverCoordinationDeps,
|
|
41
|
+
): Promise<ProverCoordination> {
|
|
42
|
+
const log = createLogger('prover-node:prover-coordination');
|
|
43
|
+
|
|
44
|
+
const coordinationConfig: CombinedCoordinationOptions = {
|
|
45
|
+
txGatheringBatchSize: config.txGatheringBatchSize ?? 10,
|
|
46
|
+
txGatheringMaxParallelRequestsPerNode: config.txGatheringMaxParallelRequestsPerNode ?? 10,
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
if (deps.aztecNodeTxProvider) {
|
|
50
|
+
log.info('Using prover coordination via aztec node');
|
|
51
|
+
return new CombinedProverCoordination(undefined, [deps.aztecNodeTxProvider!], coordinationConfig);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (config.p2pEnabled) {
|
|
55
|
+
log.info('Using prover coordination via p2p');
|
|
56
|
+
|
|
57
|
+
if (!deps.archiver || !deps.worldStateSynchronizer || !deps.telemetry || !deps.epochCache) {
|
|
58
|
+
throw new Error('Missing dependencies for p2p prover coordination');
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
let nodes: AztecNode[] = [];
|
|
63
|
+
if (config.proverCoordinationNodeUrls && config.proverCoordinationNodeUrls.length > 0) {
|
|
64
|
+
log.info('Using prover coordination via node urls');
|
|
65
|
+
const versions = getComponentsVersionsFromConfig(config, protocolContractTreeRoot, getVKTreeRoot());
|
|
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
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const proofVerifier = config.realProofs ? await BBCircuitVerifier.new(config) : new TestCircuitVerifier();
|
|
73
|
+
const p2pClient = await createP2PClient(
|
|
74
|
+
P2PClientType.Prover,
|
|
75
|
+
config,
|
|
76
|
+
deps.archiver,
|
|
77
|
+
proofVerifier,
|
|
78
|
+
deps.worldStateSynchronizer,
|
|
79
|
+
deps.epochCache,
|
|
80
|
+
getPackageVersion() ?? '',
|
|
81
|
+
deps.telemetry,
|
|
82
|
+
);
|
|
83
|
+
await p2pClient.start();
|
|
84
|
+
|
|
85
|
+
return new CombinedProverCoordination(p2pClient, nodes, coordinationConfig);
|
|
86
|
+
}
|