@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.
@@ -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
- p2pClient;
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, p2pClient, epochsMonitor, config = {}, telemetryClient = getTelemetryClient()){
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.p2pClient = p2pClient;
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.p2pClient;
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 deadline = new Date(this.dateProvider.now() + this.config.txGatheringTimeoutMs);
246
- const txProvider = this.p2pClient.getTxProvider();
247
- const txsByBlock = await Promise.all(blocks.map((block)=>txProvider.getTxsForBlock(block, {
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
- throw new Error(`Txs not found for epoch ${epochNumber}: ${missingTxs.map((hash)=>hash.toString()).join(', ')}`);
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-nightly.20250708",
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-nightly.20250708",
60
- "@aztec/bb-prover": "1.0.0-nightly.20250708",
61
- "@aztec/blob-lib": "1.0.0-nightly.20250708",
62
- "@aztec/blob-sink": "1.0.0-nightly.20250708",
63
- "@aztec/constants": "1.0.0-nightly.20250708",
64
- "@aztec/epoch-cache": "1.0.0-nightly.20250708",
65
- "@aztec/ethereum": "1.0.0-nightly.20250708",
66
- "@aztec/foundation": "1.0.0-nightly.20250708",
67
- "@aztec/kv-store": "1.0.0-nightly.20250708",
68
- "@aztec/l1-artifacts": "1.0.0-nightly.20250708",
69
- "@aztec/node-lib": "1.0.0-nightly.20250708",
70
- "@aztec/noir-protocol-circuits-types": "1.0.0-nightly.20250708",
71
- "@aztec/p2p": "1.0.0-nightly.20250708",
72
- "@aztec/protocol-contracts": "1.0.0-nightly.20250708",
73
- "@aztec/prover-client": "1.0.0-nightly.20250708",
74
- "@aztec/sequencer-client": "1.0.0-nightly.20250708",
75
- "@aztec/simulator": "1.0.0-nightly.20250708",
76
- "@aztec/stdlib": "1.0.0-nightly.20250708",
77
- "@aztec/telemetry-client": "1.0.0-nightly.20250708",
78
- "@aztec/world-state": "1.0.0-nightly.20250708",
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 { AztecNode, ProvingJobBroker } from '@aztec/stdlib/interfaces/server';
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: ProverNodeDeps = {},
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
- const proofVerifier = new QueuedIVCVerifier(
82
- config,
83
- config.realProofs ? await BBCircuitVerifier.new(config) : new TestCircuitVerifier(),
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
- p2pClient,
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
+ }
@@ -0,0 +1,2 @@
1
+ export * from './config.js';
2
+ export * from './factory.js';