@aztec/prover-node 0.69.0 → 0.69.1-devnet

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/src/metrics.ts CHANGED
@@ -1,13 +1,18 @@
1
- import { type Timer } from '@aztec/foundation/timer';
2
1
  import { type Histogram, Metrics, type TelemetryClient, ValueType } from '@aztec/telemetry-client';
3
2
 
4
3
  export class ProverNodeMetrics {
4
+ proverEpochExecutionDuration: Histogram;
5
5
  provingJobDuration: Histogram;
6
6
  provingJobBlocks: Histogram;
7
7
  provingJobTransactions: Histogram;
8
8
 
9
9
  constructor(public readonly client: TelemetryClient, name = 'ProverNode') {
10
10
  const meter = client.getMeter(name);
11
+ this.proverEpochExecutionDuration = meter.createHistogram(Metrics.PROVER_NODE_EXECUTION_DURATION, {
12
+ description: 'Duration of execution of an epoch by the prover',
13
+ unit: 'ms',
14
+ valueType: ValueType.INT,
15
+ });
11
16
  this.provingJobDuration = meter.createHistogram(Metrics.PROVER_NODE_JOB_DURATION, {
12
17
  description: 'Duration of proving job',
13
18
  unit: 'ms',
@@ -23,9 +28,9 @@ export class ProverNodeMetrics {
23
28
  });
24
29
  }
25
30
 
26
- public recordProvingJob(timerOrMs: Timer | number, numBlocks: number, numTxs: number) {
27
- const ms = Math.ceil(typeof timerOrMs === 'number' ? timerOrMs : timerOrMs.ms());
28
- this.provingJobDuration.record(ms);
31
+ public recordProvingJob(executionTimeMs: number, totalTimeMs: number, numBlocks: number, numTxs: number) {
32
+ this.proverEpochExecutionDuration.record(Math.ceil(executionTimeMs));
33
+ this.provingJobDuration.record(Math.ceil(totalTimeMs));
29
34
  this.provingJobBlocks.record(Math.floor(numBlocks));
30
35
  this.provingJobTransactions.record(Math.floor(numTxs));
31
36
  }
@@ -10,12 +10,19 @@ import {
10
10
  type ProverCoordination,
11
11
  type ProverNodeApi,
12
12
  type Service,
13
+ type Tx,
14
+ type TxHash,
13
15
  type WorldStateSynchronizer,
16
+ getTimestampRangeForEpoch,
14
17
  tryStop,
15
18
  } from '@aztec/circuit-types';
16
19
  import { type ContractDataSource } from '@aztec/circuits.js';
20
+ import { asyncPool } from '@aztec/foundation/async-pool';
17
21
  import { compact } from '@aztec/foundation/collection';
22
+ import { memoize } from '@aztec/foundation/decorators';
23
+ import { TimeoutError } from '@aztec/foundation/error';
18
24
  import { createLogger } from '@aztec/foundation/log';
25
+ import { retryUntil } from '@aztec/foundation/retry';
19
26
  import { DateProvider } from '@aztec/foundation/timer';
20
27
  import { type Maybe } from '@aztec/foundation/types';
21
28
  import { type P2P } from '@aztec/p2p';
@@ -35,6 +42,9 @@ export type ProverNodeOptions = {
35
42
  pollingIntervalMs: number;
36
43
  maxPendingJobs: number;
37
44
  maxParallelBlocksPerEpoch: number;
45
+ txGatheringTimeoutMs: number;
46
+ txGatheringIntervalMs: number;
47
+ txGatheringMaxParallelRequests: number;
38
48
  };
39
49
 
40
50
  /**
@@ -49,31 +59,35 @@ export class ProverNode implements ClaimsMonitorHandler, EpochMonitorHandler, Pr
49
59
 
50
60
  private latestEpochWeAreProving: bigint | undefined;
51
61
  private jobs: Map<string, EpochProvingJob> = new Map();
62
+ private cachedEpochData: { epochNumber: bigint; blocks: L2Block[]; txs: Tx[] } | undefined = undefined;
52
63
  private options: ProverNodeOptions;
53
64
  private metrics: ProverNodeMetrics;
54
65
 
55
66
  public readonly tracer: Tracer;
56
67
 
57
68
  constructor(
58
- private readonly prover: EpochProverManager,
59
- private readonly publisher: L1Publisher,
60
- private readonly l2BlockSource: L2BlockSource & Maybe<Service>,
61
- private readonly l1ToL2MessageSource: L1ToL2MessageSource,
62
- private readonly contractDataSource: ContractDataSource,
63
- private readonly worldState: WorldStateSynchronizer,
64
- private readonly coordination: ProverCoordination & Maybe<Service>,
65
- private readonly quoteProvider: QuoteProvider,
66
- private readonly quoteSigner: QuoteSigner,
67
- private readonly claimsMonitor: ClaimsMonitor,
68
- private readonly epochsMonitor: EpochMonitor,
69
- private readonly bondManager: BondManager,
70
- private readonly telemetryClient: TelemetryClient,
69
+ protected readonly prover: EpochProverManager,
70
+ protected readonly publisher: L1Publisher,
71
+ protected readonly l2BlockSource: L2BlockSource & Maybe<Service>,
72
+ protected readonly l1ToL2MessageSource: L1ToL2MessageSource,
73
+ protected readonly contractDataSource: ContractDataSource,
74
+ protected readonly worldState: WorldStateSynchronizer,
75
+ protected readonly coordination: ProverCoordination & Maybe<Service>,
76
+ protected readonly quoteProvider: QuoteProvider,
77
+ protected readonly quoteSigner: QuoteSigner,
78
+ protected readonly claimsMonitor: ClaimsMonitor,
79
+ protected readonly epochsMonitor: EpochMonitor,
80
+ protected readonly bondManager: BondManager,
81
+ protected readonly telemetryClient: TelemetryClient,
71
82
  options: Partial<ProverNodeOptions> = {},
72
83
  ) {
73
84
  this.options = {
74
85
  pollingIntervalMs: 1_000,
75
86
  maxPendingJobs: 100,
76
87
  maxParallelBlocksPerEpoch: 32,
88
+ txGatheringTimeoutMs: 60_000,
89
+ txGatheringIntervalMs: 1_000,
90
+ txGatheringMaxParallelRequests: 100,
77
91
  ...compact(options),
78
92
  };
79
93
 
@@ -139,13 +153,12 @@ export class ProverNode implements ClaimsMonitorHandler, EpochMonitorHandler, Pr
139
153
  */
140
154
  async handleEpochCompleted(epochNumber: bigint): Promise<void> {
141
155
  try {
142
- // Construct a quote for the epoch
143
- const blocks = await this.l2BlockSource.getBlocksForEpoch(epochNumber);
144
- if (blocks.length === 0) {
145
- this.log.info(`No blocks found for epoch ${epochNumber}`);
146
- return;
147
- }
156
+ // Gather data for the epoch
157
+ const epochData = await this.gatherEpochData(epochNumber);
158
+ const { blocks } = epochData;
159
+ this.cachedEpochData = { epochNumber, ...epochData };
148
160
 
161
+ // Construct a quote for the epoch
149
162
  const partialQuote = await this.quoteProvider.getQuote(Number(epochNumber), blocks);
150
163
  if (!partialQuote) {
151
164
  this.log.info(`No quote produced for epoch ${epochNumber}`);
@@ -256,10 +269,9 @@ export class ProverNode implements ClaimsMonitorHandler, EpochMonitorHandler, Pr
256
269
  }
257
270
 
258
271
  // Gather blocks for this epoch
259
- const blocks = await this.l2BlockSource.getBlocksForEpoch(epochNumber);
260
- if (blocks.length === 0) {
261
- throw new Error(`No blocks found for epoch ${epochNumber}`);
262
- }
272
+ const cachedEpochData = this.cachedEpochData?.epochNumber === epochNumber ? this.cachedEpochData : undefined;
273
+ const { blocks, txs } = cachedEpochData ?? (await this.gatherEpochData(epochNumber));
274
+
263
275
  const fromBlock = blocks[0].number;
264
276
  const toBlock = blocks.at(-1)!.number;
265
277
 
@@ -279,15 +291,107 @@ export class ProverNode implements ClaimsMonitorHandler, EpochMonitorHandler, Pr
279
291
  return Promise.resolve();
280
292
  };
281
293
 
282
- const job = this.doCreateEpochProvingJob(epochNumber, blocks, publicProcessorFactory, cleanUp);
294
+ const [_, endTimestamp] = getTimestampRangeForEpoch(epochNumber + 1n, await this.getL1Constants());
295
+ const deadline = new Date(Number(endTimestamp) * 1000);
296
+
297
+ const job = this.doCreateEpochProvingJob(epochNumber, deadline, blocks, txs, publicProcessorFactory, cleanUp);
283
298
  this.jobs.set(job.getId(), job);
284
299
  return job;
285
300
  }
286
301
 
302
+ @memoize
303
+ private getL1Constants() {
304
+ return this.l2BlockSource.getL1Constants();
305
+ }
306
+
307
+ @trackSpan('ProverNode.gatherEpochData', epochNumber => ({ [Attributes.EPOCH_NUMBER]: Number(epochNumber) }))
308
+ private async gatherEpochData(epochNumber: bigint) {
309
+ // Gather blocks for this epoch and their txs
310
+ const blocks = await this.gatherBlocks(epochNumber);
311
+ const txs = await this.gatherTxs(epochNumber, blocks);
312
+
313
+ return { blocks, txs };
314
+ }
315
+
316
+ private async gatherBlocks(epochNumber: bigint) {
317
+ const blocks = await this.l2BlockSource.getBlocksForEpoch(epochNumber);
318
+ if (blocks.length === 0) {
319
+ throw new Error(`No blocks found for epoch ${epochNumber}`);
320
+ }
321
+ return blocks;
322
+ }
323
+
324
+ private async gatherTxs(epochNumber: bigint, blocks: L2Block[]) {
325
+ let txsToFind: TxHash[] = [];
326
+ const txHashToBlock = new Map<string, number>();
327
+ const results = new Map<string, Tx>();
328
+
329
+ for (const block of blocks) {
330
+ for (const tx of block.body.txEffects) {
331
+ txsToFind.push(tx.txHash);
332
+ txHashToBlock.set(tx.txHash.toString(), block.number);
333
+ }
334
+ }
335
+
336
+ const totalTxsRequired = txsToFind.length;
337
+ this.log.info(
338
+ `Gathering a total of ${totalTxsRequired} txs for epoch=${epochNumber} made up of ${blocks.length} blocks`,
339
+ { epochNumber },
340
+ );
341
+
342
+ let iteration = 0;
343
+ try {
344
+ await retryUntil(
345
+ async () => {
346
+ const batch = [...txsToFind];
347
+ txsToFind = [];
348
+ const batchResults = await asyncPool(this.options.txGatheringMaxParallelRequests, batch, async txHash => {
349
+ const tx = await this.coordination.getTxByHash(txHash);
350
+ return [txHash, tx] as const;
351
+ });
352
+ let found = 0;
353
+ for (const [txHash, maybeTx] of batchResults) {
354
+ if (maybeTx) {
355
+ found++;
356
+ results.set(txHash.toString(), maybeTx);
357
+ } else {
358
+ txsToFind.push(txHash);
359
+ }
360
+ }
361
+
362
+ this.log.verbose(
363
+ `Gathered ${found}/${batch.length} txs in iteration ${iteration} for epoch ${epochNumber}. In total ${results.size}/${totalTxsRequired} have been retrieved.`,
364
+ { epochNumber },
365
+ );
366
+ iteration++;
367
+
368
+ // stop when we found all transactions
369
+ return txsToFind.length === 0;
370
+ },
371
+ 'Gather txs',
372
+ this.options.txGatheringTimeoutMs / 1_000,
373
+ this.options.txGatheringIntervalMs / 1_000,
374
+ );
375
+ } catch (err) {
376
+ if (err && err instanceof TimeoutError) {
377
+ const notFoundList = txsToFind
378
+ .map(txHash => `${txHash.toString()} (block ${txHashToBlock.get(txHash.toString())})`)
379
+ .join(', ');
380
+ throw new Error(`Txs not found for epoch ${epochNumber}: ${notFoundList}`);
381
+ } else {
382
+ throw err;
383
+ }
384
+ }
385
+
386
+ return Array.from(results.values());
387
+ }
388
+
287
389
  /** Extracted for testing purposes. */
288
390
  protected doCreateEpochProvingJob(
289
391
  epochNumber: bigint,
392
+ deadline: Date | undefined,
290
393
  blocks: L2Block[],
394
+ txs: Tx[],
291
395
  publicProcessorFactory: PublicProcessorFactory,
292
396
  cleanUp: () => Promise<void>,
293
397
  ) {
@@ -295,13 +399,14 @@ export class ProverNode implements ClaimsMonitorHandler, EpochMonitorHandler, Pr
295
399
  this.worldState,
296
400
  epochNumber,
297
401
  blocks,
402
+ txs,
298
403
  this.prover.createEpochProver(),
299
404
  publicProcessorFactory,
300
405
  this.publisher,
301
406
  this.l2BlockSource,
302
407
  this.l1ToL2MessageSource,
303
- this.coordination,
304
408
  this.metrics,
409
+ deadline,
305
410
  { parallelBlockLimit: this.options.maxParallelBlocksPerEpoch },
306
411
  cleanUp,
307
412
  );
@@ -0,0 +1,11 @@
1
+ import { type EpochProverManager } from '@aztec/circuit-types';
2
+ import { type L1Publisher } from '@aztec/sequencer-client';
3
+
4
+ import { ProverNode } from '../prover-node.js';
5
+
6
+ class TestProverNode_ extends ProverNode {
7
+ public override prover!: EpochProverManager;
8
+ public override publisher!: L1Publisher;
9
+ }
10
+
11
+ export type TestProverNode = TestProverNode_;