@aztec/prover-node 0.0.1-commit.e3c1de76 → 0.0.1-commit.e57c76e

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.
Files changed (44) hide show
  1. package/dest/actions/download-epoch-proving-job.js +1 -1
  2. package/dest/actions/rerun-epoch-proving-job.d.ts +4 -3
  3. package/dest/actions/rerun-epoch-proving-job.d.ts.map +1 -1
  4. package/dest/actions/rerun-epoch-proving-job.js +8 -8
  5. package/dest/actions/upload-epoch-proof-failure.d.ts +2 -2
  6. package/dest/actions/upload-epoch-proof-failure.d.ts.map +1 -1
  7. package/dest/bin/run-failed-epoch.js +6 -5
  8. package/dest/config.d.ts +5 -8
  9. package/dest/config.d.ts.map +1 -1
  10. package/dest/config.js +16 -19
  11. package/dest/factory.d.ts +19 -13
  12. package/dest/factory.d.ts.map +1 -1
  13. package/dest/factory.js +40 -60
  14. package/dest/job/epoch-proving-job.d.ts +6 -2
  15. package/dest/job/epoch-proving-job.d.ts.map +1 -1
  16. package/dest/job/epoch-proving-job.js +206 -35
  17. package/dest/metrics.d.ts +21 -1
  18. package/dest/metrics.d.ts.map +1 -1
  19. package/dest/metrics.js +47 -0
  20. package/dest/monitors/epoch-monitor.d.ts +1 -1
  21. package/dest/monitors/epoch-monitor.d.ts.map +1 -1
  22. package/dest/monitors/epoch-monitor.js +11 -9
  23. package/dest/prover-node-publisher.d.ts +22 -3
  24. package/dest/prover-node-publisher.d.ts.map +1 -1
  25. package/dest/prover-node-publisher.js +202 -7
  26. package/dest/prover-node.d.ts +24 -14
  27. package/dest/prover-node.d.ts.map +1 -1
  28. package/dest/prover-node.js +91 -41
  29. package/dest/prover-publisher-factory.d.ts +4 -4
  30. package/dest/prover-publisher-factory.d.ts.map +1 -1
  31. package/dest/prover-publisher-factory.js +3 -3
  32. package/package.json +23 -22
  33. package/src/actions/download-epoch-proving-job.ts +1 -1
  34. package/src/actions/rerun-epoch-proving-job.ts +17 -6
  35. package/src/actions/upload-epoch-proof-failure.ts +1 -1
  36. package/src/bin/run-failed-epoch.ts +5 -3
  37. package/src/config.ts +23 -31
  38. package/src/factory.ts +68 -103
  39. package/src/job/epoch-proving-job.ts +137 -41
  40. package/src/metrics.ts +71 -0
  41. package/src/monitors/epoch-monitor.ts +5 -6
  42. package/src/prover-node-publisher.ts +235 -12
  43. package/src/prover-node.ts +98 -55
  44. package/src/prover-publisher-factory.ts +5 -5
package/src/factory.ts CHANGED
@@ -1,34 +1,36 @@
1
- import { type Archiver, createArchiver } from '@aztec/archiver';
2
- import { BBCircuitVerifier, QueuedIVCVerifier, TestCircuitVerifier } from '@aztec/bb-prover';
3
- import { createBlobClientWithFileStores } from '@aztec/blob-client/client';
4
- import { EpochCache } from '@aztec/epoch-cache';
1
+ import type { Archiver } from '@aztec/archiver';
2
+ import type { BlobClientInterface } from '@aztec/blob-client/client';
3
+ import { Blob } from '@aztec/blob-lib';
4
+ import type { EpochCacheInterface } from '@aztec/epoch-cache';
5
5
  import { createEthereumChain } from '@aztec/ethereum/chain';
6
+ import { makeL1HttpTransport } from '@aztec/ethereum/client';
6
7
  import { RollupContract } from '@aztec/ethereum/contracts';
7
8
  import { L1TxUtils } from '@aztec/ethereum/l1-tx-utils';
8
9
  import { PublisherManager } from '@aztec/ethereum/publisher-manager';
9
10
  import { pick } from '@aztec/foundation/collection';
10
11
  import { type Logger, createLogger } from '@aztec/foundation/log';
11
12
  import { DateProvider } from '@aztec/foundation/timer';
12
- import type { DataStoreConfig } from '@aztec/kv-store/config';
13
- import { type KeyStoreConfig, KeystoreManager, loadKeystores, mergeKeystores } from '@aztec/node-keystore';
14
- import { trySnapshotSync } from '@aztec/node-lib/actions';
15
- import {
16
- createForwarderL1TxUtilsFromEthSigner,
17
- createL1TxUtilsFromEthSignerWithStore,
18
- } from '@aztec/node-lib/factories';
19
- import { NodeRpcTxSource, createP2PClient } from '@aztec/p2p';
20
- import { type ProverClientConfig, createProverClient } from '@aztec/prover-client';
13
+ import { KeystoreManager } from '@aztec/node-keystore';
14
+ import { createForwarderL1TxUtilsFromSigners, createL1TxUtilsFromSigners } from '@aztec/node-lib/factories';
15
+ import { type ProverClientConfig, type ProverClientUserConfig, createProverClient } from '@aztec/prover-client';
21
16
  import { createAndStartProvingBroker } from '@aztec/prover-client/broker';
22
- import type { AztecNode, ProvingJobBroker } from '@aztec/stdlib/interfaces/server';
23
- import { P2PClientType } from '@aztec/stdlib/p2p';
24
- import type { PublicDataTreeLeaf } from '@aztec/stdlib/trees';
25
- import { getPackageVersion } from '@aztec/stdlib/update-checker';
17
+ import {
18
+ type ProverPublisherConfig,
19
+ type ProverTxSenderConfig,
20
+ getPublisherConfigFromProverConfig,
21
+ } from '@aztec/sequencer-client';
22
+ import type {
23
+ ITxProvider,
24
+ ProverConfig,
25
+ ProvingJobBroker,
26
+ Service,
27
+ WorldStateSynchronizer,
28
+ } from '@aztec/stdlib/interfaces/server';
26
29
  import { L1Metrics, type TelemetryClient, getTelemetryClient } from '@aztec/telemetry-client';
27
- import { createWorldStateSynchronizer } from '@aztec/world-state';
28
30
 
29
- import { createPublicClient, fallback, http } from 'viem';
31
+ import { createPublicClient } from 'viem';
30
32
 
31
- import { type ProverNodeConfig, createKeyStoreForProver } from './config.js';
33
+ import type { SpecificProverNodeConfig } from './config.js';
32
34
  import { EpochMonitor } from './monitors/epoch-monitor.js';
33
35
  import { ProverNode } from './prover-node.js';
34
36
  import { ProverPublisherFactory } from './prover-publisher-factory.js';
@@ -36,54 +38,42 @@ import { ProverPublisherFactory } from './prover-publisher-factory.js';
36
38
  export type ProverNodeDeps = {
37
39
  telemetry?: TelemetryClient;
38
40
  log?: Logger;
39
- aztecNodeTxProvider?: Pick<AztecNode, 'getTxsByHash'>;
40
- archiver?: Archiver;
41
+ archiver: Archiver;
41
42
  publisherFactory?: ProverPublisherFactory;
42
43
  broker?: ProvingJobBroker;
43
44
  l1TxUtils?: L1TxUtils;
44
45
  dateProvider?: DateProvider;
46
+ worldStateSynchronizer: WorldStateSynchronizer;
47
+ p2pClient: { getTxProvider(): ITxProvider } & Partial<Service>;
48
+ epochCache: EpochCacheInterface;
49
+ blobClient: BlobClientInterface;
50
+ keyStoreManager?: KeystoreManager;
45
51
  };
46
52
 
47
- /** Creates a new prover node given a config. */
53
+ /** Creates a new prover node subsystem given a config and dependencies */
48
54
  export async function createProverNode(
49
- userConfig: ProverNodeConfig & DataStoreConfig & KeyStoreConfig,
50
- deps: ProverNodeDeps = {},
51
- options: {
52
- prefilledPublicData?: PublicDataTreeLeaf[];
53
- } = {},
55
+ userConfig: SpecificProverNodeConfig &
56
+ ProverConfig &
57
+ ProverClientUserConfig &
58
+ ProverPublisherConfig &
59
+ ProverTxSenderConfig,
60
+ deps: ProverNodeDeps,
54
61
  ) {
55
62
  const config = { ...userConfig };
56
63
  const telemetry = deps.telemetry ?? getTelemetryClient();
57
64
  const dateProvider = deps.dateProvider ?? new DateProvider();
58
- const blobClient = await createBlobClientWithFileStores(config, createLogger('prover-node:blob-client:client'));
59
- const log = deps.log ?? createLogger('prover-node');
60
-
61
- // Build a key store from file if given or from environment otherwise
62
- let keyStoreManager: KeystoreManager | undefined;
63
- const keyStoreProvided = config.keyStoreDirectory !== undefined && config.keyStoreDirectory.length > 0;
64
- if (keyStoreProvided) {
65
- const keyStores = loadKeystores(config.keyStoreDirectory!);
66
- keyStoreManager = new KeystoreManager(mergeKeystores(keyStores));
67
- } else {
68
- const keyStore = createKeyStoreForProver(config);
69
- if (keyStore) {
70
- keyStoreManager = new KeystoreManager(keyStore);
71
- }
72
- }
65
+ const log = deps.log ?? createLogger('prover');
73
66
 
74
- await keyStoreManager?.validateSigners();
67
+ const { p2pClient, archiver, keyStoreManager, worldStateSynchronizer } = deps;
75
68
 
76
69
  // Extract the prover signers from the key store and verify that we have one.
70
+ await keyStoreManager?.validateSigners();
77
71
  const proverSigners = keyStoreManager?.createProverSigners();
78
72
 
79
73
  if (proverSigners === undefined) {
80
74
  throw new Error('Failed to create prover key store configuration');
81
75
  } else if (proverSigners.signers.length === 0) {
82
76
  throw new Error('No prover signers found in the key store');
83
- } else if (!keyStoreProvided) {
84
- log.warn(
85
- 'KEY STORE CREATED FROM ENVIRONMENT, IT IS RECOMMENDED TO USE A FILE-BASED KEY STORE IN PRODUCTION ENVIRONMENTS',
86
- );
87
77
  }
88
78
 
89
79
  log.info(`Creating prover with publishers ${proverSigners.signers.map(signer => signer.address.toString()).join()}`);
@@ -95,27 +85,7 @@ export async function createProverNode(
95
85
  const proverId = proverSigners.id ?? proverIdInUserConfig ?? proverSigners.signers[0].address;
96
86
 
97
87
  // Now create the prover client configuration from this.
98
- const proverClientConfig: ProverClientConfig = {
99
- ...config,
100
- proverId,
101
- };
102
-
103
- await trySnapshotSync(config, log);
104
-
105
- const epochCache = await EpochCache.create(config.l1Contracts.rollupAddress, config);
106
-
107
- const archiver =
108
- deps.archiver ??
109
- (await createArchiver(config, { blobClient, epochCache, telemetry, dateProvider }, { blockUntilSync: true }));
110
- log.verbose(`Created archiver and synced to block ${await archiver.getBlockNumber()}`);
111
-
112
- const worldStateSynchronizer = await createWorldStateSynchronizer(
113
- config,
114
- archiver,
115
- options.prefilledPublicData,
116
- telemetry,
117
- );
118
- await worldStateSynchronizer.start();
88
+ const proverClientConfig: ProverClientConfig = { ...config, proverId };
119
89
 
120
90
  const broker = deps.broker ?? (await createAndStartProvingBroker(config, telemetry));
121
91
 
@@ -126,63 +96,53 @@ export async function createProverNode(
126
96
 
127
97
  const publicClient = createPublicClient({
128
98
  chain: chain.chainInfo,
129
- transport: fallback(config.l1RpcUrls.map((url: string) => http(url, { batch: false }))),
99
+ transport: makeL1HttpTransport(config.l1RpcUrls, { timeout: config.l1HttpTimeoutMS }),
130
100
  pollingInterval: config.viemPollingIntervalMS,
131
101
  });
132
102
 
133
- const rollupContract = new RollupContract(publicClient, config.l1Contracts.rollupAddress.toString());
103
+ const rollupContract = new RollupContract(publicClient, config.rollupAddress.toString());
134
104
 
135
105
  const l1TxUtils = deps.l1TxUtils
136
106
  ? [deps.l1TxUtils]
137
- : config.publisherForwarderAddress
138
- ? await createForwarderL1TxUtilsFromEthSigner(
107
+ : config.proverPublisherForwarderAddress
108
+ ? await createForwarderL1TxUtilsFromSigners(
139
109
  publicClient,
140
110
  proverSigners.signers,
141
- config.publisherForwarderAddress,
111
+ config.proverPublisherForwarderAddress,
142
112
  { ...config, scope: 'prover' },
143
- { telemetry, logger: log.createChild('l1-tx-utils'), dateProvider },
113
+ { telemetry, logger: log.createChild('l1-tx-utils'), dateProvider, kzg: Blob.getViemKzgInstance() },
144
114
  )
145
- : await createL1TxUtilsFromEthSignerWithStore(
115
+ : await createL1TxUtilsFromSigners(
146
116
  publicClient,
147
117
  proverSigners.signers,
148
118
  { ...config, scope: 'prover' },
149
119
  { telemetry, logger: log.createChild('l1-tx-utils'), dateProvider },
150
120
  );
151
121
 
122
+ // Create a funder L1TxUtils from the keystore funding account (if configured)
123
+ const fundingSigner = keyStoreManager?.createFundingSigner();
124
+ let funderL1TxUtils: L1TxUtils | undefined;
125
+ if (fundingSigner) {
126
+ const [funder] = await createL1TxUtilsFromSigners(
127
+ publicClient,
128
+ [fundingSigner],
129
+ { ...config, scope: 'prover' },
130
+ { telemetry, logger: log.createChild('l1-tx-utils:funder'), dateProvider },
131
+ );
132
+ funderL1TxUtils = funder;
133
+ }
134
+
152
135
  const publisherFactory =
153
136
  deps.publisherFactory ??
154
137
  new ProverPublisherFactory(config, {
155
138
  rollupContract,
156
- publisherManager: new PublisherManager(l1TxUtils, config, log.getBindings()),
139
+ publisherManager: new PublisherManager(l1TxUtils, getPublisherConfigFromProverConfig(config), {
140
+ bindings: log.getBindings(),
141
+ funder: funderL1TxUtils,
142
+ }),
157
143
  telemetry,
158
144
  });
159
145
 
160
- const proofVerifier = new QueuedIVCVerifier(
161
- config,
162
- config.realProofs || config.debugForceTxProofVerification
163
- ? await BBCircuitVerifier.new(config)
164
- : new TestCircuitVerifier(config.proverTestVerificationDelayMs),
165
- );
166
-
167
- const p2pClient = await createP2PClient(
168
- P2PClientType.Prover,
169
- config,
170
- archiver,
171
- proofVerifier,
172
- worldStateSynchronizer,
173
- epochCache,
174
- getPackageVersion() ?? '',
175
- dateProvider,
176
- telemetry,
177
- {
178
- txCollectionNodeSources: deps.aztecNodeTxProvider
179
- ? [new NodeRpcTxSource(deps.aztecNodeTxProvider, 'TestNode')]
180
- : [],
181
- },
182
- );
183
-
184
- await p2pClient.start();
185
-
186
146
  const proverNodeConfig = {
187
147
  ...pick(
188
148
  config,
@@ -213,6 +173,9 @@ export async function createProverNode(
213
173
  l1TxUtils.map(utils => utils.getSenderAddress()),
214
174
  );
215
175
 
176
+ // Extract the shared delayer from the first L1TxUtils instance (all instances share the same delayer)
177
+ const delayer = l1TxUtils[0]?.delayer;
178
+
216
179
  return new ProverNode(
217
180
  prover,
218
181
  publisherFactory,
@@ -226,5 +189,7 @@ export async function createProverNode(
226
189
  l1Metrics,
227
190
  proverNodeConfig,
228
191
  telemetry,
192
+ delayer,
193
+ dateProvider,
229
194
  );
230
195
  }
@@ -1,11 +1,10 @@
1
- import { NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP } from '@aztec/constants';
2
1
  import { asyncPool } from '@aztec/foundation/async-pool';
3
2
  import { BlockNumber, EpochNumber } from '@aztec/foundation/branded-types';
4
- import { padArrayEnd } from '@aztec/foundation/collection';
5
3
  import { Fr } from '@aztec/foundation/curves/bn254';
6
4
  import { type Logger, type LoggerBindings, createLogger } from '@aztec/foundation/log';
7
5
  import { RunningPromise, promiseWithResolvers } from '@aztec/foundation/promise';
8
6
  import { Timer } from '@aztec/foundation/timer';
7
+ import { AVM_MAX_CONCURRENT_SIMULATIONS } from '@aztec/native';
9
8
  import { getVKTreeRoot } from '@aztec/noir-protocol-circuits-types/vk-tree';
10
9
  import { protocolContractsHash } from '@aztec/protocol-contracts';
11
10
  import { buildFinalBlobChallenges } from '@aztec/prover-client/helpers';
@@ -19,8 +18,8 @@ import {
19
18
  EpochProvingJobTerminalState,
20
19
  type ForkMerkleTreeOperations,
21
20
  } from '@aztec/stdlib/interfaces/server';
21
+ import { appendL1ToL2MessagesToTree } from '@aztec/stdlib/messaging';
22
22
  import { CheckpointConstantData } from '@aztec/stdlib/rollup';
23
- import { MerkleTreeId } from '@aztec/stdlib/trees';
24
23
  import type { ProcessedTx, Tx } from '@aztec/stdlib/tx';
25
24
  import { Attributes, type Traceable, type Tracer, trackSpan } from '@aztec/telemetry-client';
26
25
 
@@ -47,6 +46,7 @@ export class EpochProvingJob implements Traceable {
47
46
  private uuid: string;
48
47
 
49
48
  private runPromise: Promise<void> | undefined;
49
+ private abortController = new AbortController();
50
50
  private epochCheckPromise: RunningPromise | undefined;
51
51
  private deadlineTimeoutHandler: NodeJS.Timeout | undefined;
52
52
 
@@ -57,7 +57,7 @@ export class EpochProvingJob implements Traceable {
57
57
  private dbProvider: Pick<ForkMerkleTreeOperations, 'fork'>,
58
58
  private prover: EpochProver,
59
59
  private publicProcessorFactory: PublicProcessorFactory,
60
- private publisher: Pick<ProverNodePublisher, 'submitEpochProof'>,
60
+ private publisher: Pick<ProverNodePublisher, 'submitEpochProof' | 'analyzeEpochProofSubmission'>,
61
61
  private l2BlockSource: L2BlockSource | undefined,
62
62
  private metrics: ProverNodeJobMetrics,
63
63
  private deadline: Date | undefined,
@@ -148,19 +148,32 @@ export class EpochProvingJob implements Traceable {
148
148
  this.runPromise = promise;
149
149
 
150
150
  try {
151
+ const blobTimer = new Timer();
151
152
  const blobFieldsPerCheckpoint = this.checkpoints.map(checkpoint => checkpoint.toBlobFields());
152
153
  const finalBlobBatchingChallenges = await buildFinalBlobChallenges(blobFieldsPerCheckpoint);
154
+ this.metrics.recordBlobProcessing(blobTimer.ms());
153
155
 
154
156
  this.prover.startNewEpoch(epochNumber, epochSizeCheckpoints, finalBlobBatchingChallenges);
157
+ const chonkTimer = new Timer();
155
158
  await this.prover.startChonkVerifierCircuits(Array.from(this.txs.values()));
159
+ this.metrics.recordChonkVerifier(chonkTimer.ms());
156
160
 
157
161
  // Everything in the epoch should have the same chainId and version.
158
162
  const { chainId, version } = this.checkpoints[0].blocks[0].header.globalVariables;
159
163
 
160
164
  const previousBlockHeaders = this.gatherPreviousBlockHeaders();
161
165
 
162
- await asyncPool(this.config.parallelBlockLimit ?? 32, this.checkpoints, async checkpoint => {
166
+ const allCheckpointsTimer = new Timer();
167
+
168
+ const parallelism = this.config.parallelBlockLimit
169
+ ? this.config.parallelBlockLimit
170
+ : AVM_MAX_CONCURRENT_SIMULATIONS > 0
171
+ ? AVM_MAX_CONCURRENT_SIMULATIONS
172
+ : this.checkpoints.length;
173
+
174
+ await this.processCheckpoints(parallelism, async checkpoint => {
163
175
  this.checkState();
176
+ const checkpointTimer = new Timer();
164
177
 
165
178
  const checkpointIndex = checkpoint.number - fromCheckpoint;
166
179
  const checkpointConstants = CheckpointConstantData.from({
@@ -177,11 +190,12 @@ export class EpochProvingJob implements Traceable {
177
190
  const previousHeader = previousBlockHeaders[checkpointIndex];
178
191
  const l1ToL2Messages = this.getL1ToL2Messages(checkpoint);
179
192
 
180
- this.log.verbose(`Starting processing checkpoint ${checkpoint.number}`, {
193
+ this.log.debug(`Starting processing checkpoint ${checkpoint.number}`, {
181
194
  number: checkpoint.number,
182
195
  checkpointHash: checkpoint.hash().toString(),
183
- lastArchive: checkpoint.header.lastArchiveRoot,
184
- previousHeader: previousHeader.hash(),
196
+ headerHash: checkpoint.header.hash().toString(),
197
+ numL1ToL2Messages: l1ToL2Messages.length,
198
+ previousBlockNumber: previousHeader.globalVariables.blockNumber,
185
199
  uuid: this.uuid,
186
200
  });
187
201
 
@@ -193,7 +207,9 @@ export class EpochProvingJob implements Traceable {
193
207
  previousHeader,
194
208
  );
195
209
 
196
- for (const block of checkpoint.blocks) {
210
+ for (let blockIndex = 0; blockIndex < checkpoint.blocks.length; blockIndex++) {
211
+ const blockTimer = new Timer();
212
+ const block = checkpoint.blocks[blockIndex];
197
213
  const globalVariables = block.header.globalVariables;
198
214
  const txs = this.getTxs(block);
199
215
 
@@ -211,20 +227,28 @@ export class EpochProvingJob implements Traceable {
211
227
  // Start block proving
212
228
  await this.prover.startNewBlock(block.number, globalVariables.timestamp, txs.length);
213
229
 
214
- // Process public fns
215
- const db = await this.createFork(BlockNumber(block.number - 1), l1ToL2Messages);
216
- const config = PublicSimulatorConfig.from({
217
- proverId: this.prover.getProverId().toField(),
218
- skipFeeEnforcement: false,
219
- collectDebugLogs: false,
220
- collectHints: true,
221
- collectPublicInputs: true,
222
- collectStatistics: false,
223
- });
224
- const publicProcessor = this.publicProcessorFactory.create(db, globalVariables, config);
225
- const processed = await this.processTxs(publicProcessor, txs);
226
- await this.prover.addTxs(processed);
227
- await db.close();
230
+ // Process public fns. L1 to L2 messages are only inserted for the first block of a checkpoint,
231
+ // as the fork for subsequent blocks already includes them from the previous block's synced state.
232
+ {
233
+ await using db = await this.createFork(
234
+ BlockNumber(block.number - 1),
235
+ blockIndex === 0 ? l1ToL2Messages : undefined,
236
+ );
237
+ this.checkState();
238
+ const config = PublicSimulatorConfig.from({
239
+ proverId: this.prover.getProverId().toField(),
240
+ skipFeeEnforcement: false,
241
+ collectDebugLogs: false,
242
+ collectHints: true,
243
+ collectPublicInputs: true,
244
+ collectStatistics: false,
245
+ });
246
+ const publicProcessor = this.publicProcessorFactory.create(db, globalVariables, config);
247
+ const processed = await this.processTxs(publicProcessor, txs);
248
+ this.checkState();
249
+ await this.prover.addTxs(processed);
250
+ }
251
+ this.checkState();
228
252
  this.log.verbose(`Processed all ${txs.length} txs for block ${block.number}`, {
229
253
  blockNumber: block.number,
230
254
  blockHash: (await block.hash()).toString(),
@@ -234,8 +258,11 @@ export class EpochProvingJob implements Traceable {
234
258
  // Mark block as completed to pad it
235
259
  const expectedBlockHeader = block.header;
236
260
  await this.prover.setBlockCompleted(block.number, expectedBlockHeader);
261
+ this.metrics.recordBlockProcessing(blockTimer.ms());
237
262
  }
263
+ this.metrics.recordCheckpointProcessing(checkpointTimer.ms());
238
264
  });
265
+ this.metrics.recordAllCheckpointsProcessing(allCheckpointsTimer.ms());
239
266
 
240
267
  const executionTime = timer.ms();
241
268
 
@@ -247,8 +274,21 @@ export class EpochProvingJob implements Traceable {
247
274
 
248
275
  if (this.config.skipSubmitProof) {
249
276
  this.log.info(
250
- `Proof publishing is disabled. Dropping valid proof for epoch ${epochNumber} (checkpoints ${fromCheckpoint} to ${toCheckpoint})`,
277
+ `Proof publishing is disabled. Analyzing estimated L1 fees for epoch ${epochNumber} (checkpoints ${fromCheckpoint} to ${toCheckpoint})`,
251
278
  );
279
+ try {
280
+ await this.publisher.analyzeEpochProofSubmission({
281
+ fromCheckpoint,
282
+ toCheckpoint,
283
+ epochNumber,
284
+ publicInputs,
285
+ proof,
286
+ batchedBlobInputs,
287
+ attestations,
288
+ });
289
+ } catch (err) {
290
+ this.log.warn(`Failed to analyze estimated L1 fees for epoch ${epochNumber}`, err);
291
+ }
252
292
  this.state = 'completed';
253
293
  this.metrics.recordProvingJob(executionTime, timer.ms(), epochSizeCheckpoints, epochSizeBlocks, epochSizeTxs);
254
294
  return;
@@ -295,25 +335,63 @@ export class EpochProvingJob implements Traceable {
295
335
  }
296
336
 
297
337
  /**
298
- * Create a new db fork for tx processing, inserting all L1 to L2.
338
+ * Create a new db fork for tx processing, optionally inserting L1 to L2 messages.
339
+ * L1 to L2 messages should only be inserted for the first block in a checkpoint,
340
+ * as subsequent blocks' synced state already includes them.
299
341
  * REFACTOR: The prover already spawns a db fork of its own for each block, so we may be able to do away with just one fork.
300
342
  */
301
- private async createFork(blockNumber: BlockNumber, l1ToL2Messages: Fr[]) {
302
- const db = await this.dbProvider.fork(blockNumber);
303
- const l1ToL2MessagesPadded = padArrayEnd<Fr, number>(
304
- l1ToL2Messages,
305
- Fr.ZERO,
306
- NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP,
307
- 'Too many L1 to L2 messages',
308
- );
309
- this.log.verbose(`Creating fork at ${blockNumber} with ${l1ToL2Messages.length} L1 to L2 messages`, {
310
- blockNumber,
311
- l1ToL2Messages: l1ToL2Messages.map(m => m.toString()),
312
- });
313
- await db.appendLeaves(MerkleTreeId.L1_TO_L2_MESSAGE_TREE, l1ToL2MessagesPadded);
343
+ private async createFork(blockNumber: BlockNumber, l1ToL2Messages: Fr[] | undefined) {
344
+ this.log.verbose(`Creating fork at ${blockNumber}`, { blockNumber });
345
+ // temporary stack to control fork lifetime
346
+ await using cleanup = new AsyncDisposableStack();
347
+ const db = cleanup.use(await this.dbProvider.fork(blockNumber));
348
+
349
+ if (l1ToL2Messages !== undefined) {
350
+ this.log.verbose(`Inserting ${l1ToL2Messages.length} L1 to L2 messages in fork`, {
351
+ blockNumber,
352
+ l1ToL2Messages: l1ToL2Messages.map(m => m.toString()),
353
+ });
354
+ await appendL1ToL2MessagesToTree(db, l1ToL2Messages);
355
+ }
356
+
357
+ // everything run succesfully so we can release this stack and give control of the fork's lifetime to the caller
358
+ cleanup.move();
314
359
  return db;
315
360
  }
316
361
 
362
+ private async processCheckpoints(
363
+ parallelism: number,
364
+ processCheckpoint: (checkpoint: Checkpoint) => Promise<void>,
365
+ ): Promise<void> {
366
+ let hasError = false;
367
+ let firstError: unknown;
368
+
369
+ await asyncPool(Math.max(parallelism, 1), this.checkpoints, async checkpoint => {
370
+ if (hasError || this.abortController.signal.aborted) {
371
+ return;
372
+ }
373
+
374
+ try {
375
+ this.checkState();
376
+ await processCheckpoint(checkpoint);
377
+ } catch (err) {
378
+ if (!hasError) {
379
+ hasError = true;
380
+ firstError = err;
381
+ this.failProcessing();
382
+ }
383
+ }
384
+ });
385
+
386
+ if (hasError) {
387
+ throw firstError;
388
+ }
389
+
390
+ if (this.abortController.signal.aborted) {
391
+ this.checkState();
392
+ }
393
+ }
394
+
317
395
  private progressState(state: EpochProvingJobState) {
318
396
  this.checkState();
319
397
  this.state = state;
@@ -327,12 +405,24 @@ export class EpochProvingJob implements Traceable {
327
405
 
328
406
  public async stop(state: EpochProvingJobTerminalState = 'stopped') {
329
407
  this.state = state;
330
- this.prover.cancel();
408
+ this.interruptProcessing();
331
409
  if (this.runPromise) {
332
410
  await this.runPromise;
333
411
  }
334
412
  }
335
413
 
414
+ private failProcessing() {
415
+ if (!EpochProvingJobTerminalState.includes(this.state)) {
416
+ this.state = 'failed';
417
+ }
418
+ this.interruptProcessing();
419
+ }
420
+
421
+ private interruptProcessing() {
422
+ this.abortController.abort();
423
+ this.prover.cancel();
424
+ }
425
+
336
426
  private scheduleDeadlineStop() {
337
427
  const deadline = this.deadline;
338
428
  if (deadline) {
@@ -367,7 +457,9 @@ export class EpochProvingJob implements Traceable {
367
457
  const intervalMs = Math.ceil((await l2BlockSource.getL1Constants()).ethereumSlotDuration / 2) * 1000;
368
458
  this.epochCheckPromise = new RunningPromise(
369
459
  async () => {
370
- const blockHeaders = await l2BlockSource.getCheckpointedBlockHeadersForEpoch(this.epochNumber);
460
+ const blockHeaders = (
461
+ await l2BlockSource.getBlocksData({ epoch: this.epochNumber, onlyCheckpointed: true })
462
+ ).map(d => d.header);
371
463
  const blockHashes = await Promise.all(blockHeaders.map(header => header.hash()));
372
464
  const thisBlocks = this.checkpoints.flatMap(checkpoint => checkpoint.blocks);
373
465
  const thisBlockHashes = await Promise.all(thisBlocks.map(block => block.hash()));
@@ -406,7 +498,11 @@ export class EpochProvingJob implements Traceable {
406
498
 
407
499
  private async processTxs(publicProcessor: PublicProcessor, txs: Tx[]): Promise<ProcessedTx[]> {
408
500
  const { deadline } = this;
409
- const [processedTxs, failedTxs] = await publicProcessor.process(txs, { deadline });
501
+ const [processedTxs, failedTxs] = await publicProcessor.process(txs, {
502
+ deadline,
503
+ signal: this.abortController.signal,
504
+ });
505
+ this.checkState();
410
506
 
411
507
  if (failedTxs.length) {
412
508
  const failedTxHashes = await Promise.all(failedTxs.map(({ tx }) => tx.getTxHash()));
package/src/metrics.ts CHANGED
@@ -25,6 +25,12 @@ export class ProverNodeJobMetrics {
25
25
  provingJobBlocks: Gauge;
26
26
  provingJobTransactions: Gauge;
27
27
 
28
+ private blobProcessingDuration: Gauge;
29
+ private chonkVerifierDuration: Gauge;
30
+ private blockProcessingDuration: Histogram;
31
+ private checkpointProcessingDuration: Histogram;
32
+ private allCheckpointsProcessingDuration: Gauge;
33
+
28
34
  constructor(
29
35
  private meter: Meter,
30
36
  public readonly tracer: Tracer,
@@ -35,6 +41,14 @@ export class ProverNodeJobMetrics {
35
41
  this.provingJobCheckpoints = this.meter.createGauge(Metrics.PROVER_NODE_JOB_CHECKPOINTS);
36
42
  this.provingJobBlocks = this.meter.createGauge(Metrics.PROVER_NODE_JOB_BLOCKS);
37
43
  this.provingJobTransactions = this.meter.createGauge(Metrics.PROVER_NODE_JOB_TRANSACTIONS);
44
+
45
+ this.blobProcessingDuration = this.meter.createGauge(Metrics.PROVER_NODE_BLOB_PROCESSING_LAST_DURATION);
46
+ this.chonkVerifierDuration = this.meter.createGauge(Metrics.PROVER_NODE_CHONK_VERIFIER_LAST_DURATION);
47
+ this.blockProcessingDuration = this.meter.createHistogram(Metrics.PROVER_NODE_BLOCK_PROCESSING_DURATION);
48
+ this.checkpointProcessingDuration = this.meter.createHistogram(Metrics.PROVER_NODE_CHECKPOINT_PROCESSING_DURATION);
49
+ this.allCheckpointsProcessingDuration = this.meter.createGauge(
50
+ Metrics.PROVER_NODE_ALL_CHECKPOINTS_PROCESSING_LAST_DURATION,
51
+ );
38
52
  }
39
53
 
40
54
  public recordProvingJob(
@@ -50,6 +64,26 @@ export class ProverNodeJobMetrics {
50
64
  this.provingJobBlocks.record(Math.floor(numBlocks));
51
65
  this.provingJobTransactions.record(Math.floor(numTxs));
52
66
  }
67
+
68
+ public recordBlobProcessing(durationMs: number) {
69
+ this.blobProcessingDuration.record(Math.ceil(durationMs));
70
+ }
71
+
72
+ public recordChonkVerifier(durationMs: number) {
73
+ this.chonkVerifierDuration.record(Math.ceil(durationMs));
74
+ }
75
+
76
+ public recordBlockProcessing(durationMs: number) {
77
+ this.blockProcessingDuration.record(Math.ceil(durationMs));
78
+ }
79
+
80
+ public recordCheckpointProcessing(durationMs: number) {
81
+ this.checkpointProcessingDuration.record(Math.ceil(durationMs));
82
+ }
83
+
84
+ public recordAllCheckpointsProcessing(durationMs: number) {
85
+ this.allCheckpointsProcessingDuration.record(Math.ceil(durationMs));
86
+ }
53
87
  }
54
88
 
55
89
  export class ProverNodeRewardsMetrics {
@@ -106,6 +140,13 @@ export class ProverNodeRewardsMetrics {
106
140
  };
107
141
  }
108
142
 
143
+ export type EstimatedSubmitProofStats = {
144
+ gasLimit: bigint;
145
+ baseFeePerGas: bigint;
146
+ maxPriorityFeePerGas: bigint;
147
+ estimatedTotalFee: bigint;
148
+ };
149
+
109
150
  export class ProverNodePublisherMetrics {
110
151
  gasPrice: Histogram;
111
152
  txCount: UpDownCounter;
@@ -117,6 +158,10 @@ export class ProverNodePublisherMetrics {
117
158
  txBlobDataGasCost: Histogram;
118
159
  txTotalFee: Histogram;
119
160
 
161
+ private txGasEstimated: Histogram;
162
+ private gasPriceEstimated: Histogram;
163
+ private txTotalFeeEstimated: Histogram;
164
+
120
165
  private senderBalance: Gauge;
121
166
  private meter: Meter;
122
167
 
@@ -148,6 +193,12 @@ export class ProverNodePublisherMetrics {
148
193
 
149
194
  this.txTotalFee = this.meter.createHistogram(Metrics.L1_PUBLISHER_TX_TOTAL_FEE);
150
195
 
196
+ this.txGasEstimated = this.meter.createHistogram(Metrics.PROVER_NODE_ESTIMATED_SUBMISSION_GAS);
197
+
198
+ this.gasPriceEstimated = this.meter.createHistogram(Metrics.PROVER_NODE_ESTIMATED_SUBMISSION_GAS_PRICE);
199
+
200
+ this.txTotalFeeEstimated = this.meter.createHistogram(Metrics.PROVER_NODE_ESTIMATED_SUBMISSION_TOTAL_FEE);
201
+
151
202
  this.senderBalance = this.meter.createGauge(Metrics.L1_PUBLISHER_BALANCE);
152
203
  }
153
204
 
@@ -162,6 +213,26 @@ export class ProverNodePublisherMetrics {
162
213
  this.recordTx(durationMs, stats);
163
214
  }
164
215
 
216
+ public recordEstimatedSubmitProof(stats: EstimatedSubmitProofStats) {
217
+ const attributes = { [Attributes.L1_TX_TYPE]: 'submitProof' } as const;
218
+
219
+ this.txGasEstimated.record(Number(stats.gasLimit), attributes);
220
+
221
+ try {
222
+ this.gasPriceEstimated.record(
223
+ parseInt(formatEther(stats.baseFeePerGas + stats.maxPriorityFeePerGas, 'gwei'), 10),
224
+ );
225
+ } catch {
226
+ // ignore
227
+ }
228
+
229
+ try {
230
+ this.txTotalFeeEstimated.record(parseFloat(formatEther(stats.estimatedTotalFee)));
231
+ } catch {
232
+ // ignore
233
+ }
234
+ }
235
+
165
236
  public recordSenderBalance(wei: bigint, senderAddress: string) {
166
237
  const eth = parseFloat(formatEther(wei, 'wei'));
167
238
  this.senderBalance.record(eth, {