@aztec/prover-node 0.74.0 → 0.75.0-commit.c03ba01a2a4122e43e90d5133ba017e54b90e9d2

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 (80) hide show
  1. package/dest/bond/bond-manager.js +10 -12
  2. package/dest/bond/config.js +3 -4
  3. package/dest/bond/escrow-contract.js +23 -15
  4. package/dest/bond/factory.js +4 -2
  5. package/dest/bond/index.js +0 -1
  6. package/dest/bond/token-contract.js +52 -29
  7. package/dest/config.js +20 -21
  8. package/dest/factory.js +26 -14
  9. package/dest/http.js +1 -3
  10. package/dest/index.js +0 -1
  11. package/dest/job/epoch-proving-job.js +230 -190
  12. package/dest/metrics.js +33 -23
  13. package/dest/monitors/claims-monitor.js +52 -51
  14. package/dest/monitors/epoch-monitor.js +48 -47
  15. package/dest/monitors/index.js +0 -1
  16. package/dest/prover-coordination/config.js +2 -3
  17. package/dest/prover-coordination/factory.js +8 -7
  18. package/dest/prover-coordination/index.js +0 -1
  19. package/dest/prover-node-publisher.js +46 -43
  20. package/dest/prover-node.js +303 -308
  21. package/dest/quote-provider/http.js +11 -5
  22. package/dest/quote-provider/index.js +1 -2
  23. package/dest/quote-provider/simple.js +7 -3
  24. package/dest/quote-provider/utils.js +2 -3
  25. package/dest/quote-signer.js +6 -3
  26. package/dest/test/index.js +2 -1
  27. package/package.json +19 -19
  28. package/src/prover-node.ts +34 -62
  29. package/dest/bond/bond-manager.d.ts +0 -22
  30. package/dest/bond/bond-manager.d.ts.map +0 -1
  31. package/dest/bond/config.d.ts +0 -8
  32. package/dest/bond/config.d.ts.map +0 -1
  33. package/dest/bond/escrow-contract.d.ts +0 -19
  34. package/dest/bond/escrow-contract.d.ts.map +0 -1
  35. package/dest/bond/factory.d.ts +0 -6
  36. package/dest/bond/factory.d.ts.map +0 -1
  37. package/dest/bond/index.d.ts +0 -3
  38. package/dest/bond/index.d.ts.map +0 -1
  39. package/dest/bond/token-contract.d.ts +0 -26
  40. package/dest/bond/token-contract.d.ts.map +0 -1
  41. package/dest/config.d.ts +0 -31
  42. package/dest/config.d.ts.map +0 -1
  43. package/dest/factory.d.ts +0 -22
  44. package/dest/factory.d.ts.map +0 -1
  45. package/dest/http.d.ts +0 -8
  46. package/dest/http.d.ts.map +0 -1
  47. package/dest/index.d.ts +0 -6
  48. package/dest/index.d.ts.map +0 -1
  49. package/dest/job/epoch-proving-job.d.ts +0 -50
  50. package/dest/job/epoch-proving-job.d.ts.map +0 -1
  51. package/dest/metrics.d.ts +0 -23
  52. package/dest/metrics.d.ts.map +0 -1
  53. package/dest/monitors/claims-monitor.d.ts +0 -24
  54. package/dest/monitors/claims-monitor.d.ts.map +0 -1
  55. package/dest/monitors/epoch-monitor.d.ts +0 -22
  56. package/dest/monitors/epoch-monitor.d.ts.map +0 -1
  57. package/dest/monitors/index.d.ts +0 -3
  58. package/dest/monitors/index.d.ts.map +0 -1
  59. package/dest/prover-coordination/config.d.ts +0 -7
  60. package/dest/prover-coordination/config.d.ts.map +0 -1
  61. package/dest/prover-coordination/factory.d.ts +0 -22
  62. package/dest/prover-coordination/factory.d.ts.map +0 -1
  63. package/dest/prover-coordination/index.d.ts +0 -3
  64. package/dest/prover-coordination/index.d.ts.map +0 -1
  65. package/dest/prover-node-publisher.d.ts +0 -67
  66. package/dest/prover-node-publisher.d.ts.map +0 -1
  67. package/dest/prover-node.d.ts +0 -107
  68. package/dest/prover-node.d.ts.map +0 -1
  69. package/dest/quote-provider/http.d.ts +0 -15
  70. package/dest/quote-provider/http.d.ts.map +0 -1
  71. package/dest/quote-provider/index.d.ts +0 -6
  72. package/dest/quote-provider/index.d.ts.map +0 -1
  73. package/dest/quote-provider/simple.d.ts +0 -9
  74. package/dest/quote-provider/simple.d.ts.map +0 -1
  75. package/dest/quote-provider/utils.d.ts +0 -4
  76. package/dest/quote-provider/utils.d.ts.map +0 -1
  77. package/dest/quote-signer.d.ts +0 -13
  78. package/dest/quote-signer.d.ts.map +0 -1
  79. package/dest/test/index.d.ts +0 -10
  80. package/dest/test/index.d.ts.map +0 -1
@@ -1,14 +1,17 @@
1
- import { __esDecorate, __runInitializers } from "tslib";
2
- import { EpochProofQuotePayload, getTimestampRangeForEpoch, tryStop, } from '@aztec/circuit-types';
3
- import { asyncPool } from '@aztec/foundation/async-pool';
1
+ function _ts_decorate(decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ }
7
+ import { EpochProofQuotePayload, getTimestampRangeForEpoch, tryStop } from '@aztec/circuit-types';
4
8
  import { compact } from '@aztec/foundation/collection';
5
9
  import { memoize } from '@aztec/foundation/decorators';
6
- import { TimeoutError } from '@aztec/foundation/error';
7
10
  import { createLogger } from '@aztec/foundation/log';
8
- import { retryUntil } from '@aztec/foundation/retry';
11
+ import { RunningPromise } from '@aztec/foundation/running-promise';
9
12
  import { DateProvider } from '@aztec/foundation/timer';
10
13
  import { PublicProcessorFactory } from '@aztec/simulator/server';
11
- import { Attributes, getTelemetryClient, trackSpan, } from '@aztec/telemetry-client';
14
+ import { Attributes, getTelemetryClient, trackSpan } from '@aztec/telemetry-client';
12
15
  import { EpochProvingJob } from './job/epoch-proving-job.js';
13
16
  import { ProverNodeMetrics } from './metrics.js';
14
17
  /**
@@ -16,314 +19,306 @@ import { ProverNodeMetrics } from './metrics.js';
16
19
  * submits bids for proving them, and monitors if they are accepted. If so, the prover node fetches the txs
17
20
  * from a tx source in the p2p network or an external node, re-executes their public functions, creates a rollup
18
21
  * proof for the epoch, and submits it to L1.
19
- */
20
- let ProverNode = (() => {
21
- var _a;
22
- let _instanceExtraInitializers = [];
23
- let _createProvingJob_decorators;
24
- let _getL1Constants_decorators;
25
- let _gatherEpochData_decorators;
26
- return _a = class ProverNode {
27
- constructor(prover, publisher, l2BlockSource, l1ToL2MessageSource, contractDataSource, worldState, coordination, quoteProvider, quoteSigner, claimsMonitor, epochsMonitor, bondManager, options = {}, telemetryClient = getTelemetryClient()) {
28
- this.prover = (__runInitializers(this, _instanceExtraInitializers), prover);
29
- this.publisher = publisher;
30
- this.l2BlockSource = l2BlockSource;
31
- this.l1ToL2MessageSource = l1ToL2MessageSource;
32
- this.contractDataSource = contractDataSource;
33
- this.worldState = worldState;
34
- this.coordination = coordination;
35
- this.quoteProvider = quoteProvider;
36
- this.quoteSigner = quoteSigner;
37
- this.claimsMonitor = claimsMonitor;
38
- this.epochsMonitor = epochsMonitor;
39
- this.bondManager = bondManager;
40
- this.telemetryClient = telemetryClient;
41
- this.log = createLogger('prover-node');
42
- this.dateProvider = new DateProvider();
43
- this.jobs = new Map();
44
- this.cachedEpochData = undefined;
45
- this.options = {
46
- pollingIntervalMs: 1000,
47
- maxPendingJobs: 100,
48
- maxParallelBlocksPerEpoch: 32,
49
- txGatheringTimeoutMs: 60000,
50
- txGatheringIntervalMs: 1000,
51
- txGatheringMaxParallelRequests: 100,
52
- ...compact(options),
53
- };
54
- this.metrics = new ProverNodeMetrics(telemetryClient, 'ProverNode');
55
- this.tracer = telemetryClient.getTracer('ProverNode');
56
- }
57
- getP2P() {
58
- const asP2PClient = this.coordination;
59
- if (typeof asP2PClient.isP2PClient === 'function' && asP2PClient.isP2PClient()) {
60
- return asP2PClient;
61
- }
62
- return undefined;
63
- }
64
- async handleClaim(proofClaim) {
65
- if (proofClaim.epochToProve === this.latestEpochWeAreProving) {
66
- this.log.verbose(`Already proving claim for epoch ${proofClaim.epochToProve}`);
67
- return;
68
- }
69
- const provenEpoch = await this.l2BlockSource.getProvenL2EpochNumber();
70
- if (provenEpoch !== undefined && proofClaim.epochToProve <= provenEpoch) {
71
- this.log.verbose(`Claim for epoch ${proofClaim.epochToProve} is already proven`);
72
- return;
73
- }
74
- try {
75
- await this.startProof(proofClaim.epochToProve);
76
- this.latestEpochWeAreProving = proofClaim.epochToProve;
77
- }
78
- catch (err) {
79
- this.log.error(`Error handling claim for epoch ${proofClaim.epochToProve}`, err);
80
- }
81
- try {
82
- // Staked amounts are lowered after a claim, so this is a good time for doing a top-up if needed
83
- await this.bondManager.ensureBond();
84
- }
85
- catch (err) {
86
- this.log.error(`Error ensuring prover bond after handling claim for epoch ${proofClaim.epochToProve}`, err);
87
- }
88
- }
89
- /**
90
- * Handles the epoch number to prove when the prover node starts by checking if there
91
- * is an existing claim for it. If not, it creates and sends a quote for it.
92
- * @param epochNumber - The epoch immediately before the current one when the prover node starts.
93
- */
94
- async handleInitialEpochSync(epochNumber) {
95
- try {
96
- const claim = await this.publisher.getProofClaim();
97
- if (!claim || claim.epochToProve < epochNumber) {
98
- this.log.verbose(`Handling epoch ${epochNumber} completed as initial sync`);
99
- await this.handleEpochCompleted(epochNumber);
100
- }
101
- }
102
- catch (err) {
103
- this.log.error(`Error handling initial epoch sync`, err);
104
- }
105
- }
106
- /**
107
- * Handles an epoch being completed by sending a quote for proving it.
108
- * @param epochNumber - The epoch number that was just completed.
109
- */
110
- async handleEpochCompleted(epochNumber) {
111
- try {
112
- // Gather data for the epoch
113
- const epochData = await this.gatherEpochData(epochNumber);
114
- const { blocks } = epochData;
115
- this.cachedEpochData = { epochNumber, ...epochData };
116
- // Construct a quote for the epoch
117
- const partialQuote = await this.quoteProvider.getQuote(Number(epochNumber), blocks);
118
- if (!partialQuote) {
119
- this.log.info(`No quote produced for epoch ${epochNumber}`);
120
- return;
121
- }
122
- // Ensure we have deposited enough funds for sending this quote
123
- await this.bondManager.ensureBond(partialQuote.bondAmount);
124
- // Assemble and sign full quote
125
- const quote = EpochProofQuotePayload.from({
126
- ...partialQuote,
127
- epochToProve: BigInt(epochNumber),
128
- prover: this.publisher.getSenderAddress(),
129
- validUntilSlot: partialQuote.validUntilSlot ?? BigInt(Number.MAX_SAFE_INTEGER), // Should we constrain this?
130
- });
131
- const signed = await this.quoteSigner.sign(quote);
132
- // Send it to the coordinator
133
- this.log.info(`Sending quote for epoch ${epochNumber} with blocks ${blocks[0].number} to ${blocks.at(-1).number}`, quote.toViemArgs());
134
- await this.doSendEpochProofQuote(signed);
135
- }
136
- catch (err) {
137
- if (err instanceof EmptyEpochError) {
138
- this.log.info(`Not producing quote for ${epochNumber} since no blocks were found`);
139
- }
140
- else {
141
- this.log.error(`Error handling epoch completed`, err);
142
- }
143
- }
144
- }
145
- /**
146
- * Starts the prover node so it periodically checks for unproven epochs in the unfinalised chain from L1 and sends
147
- * quotes for them, as well as monitors the claims for the epochs it has sent quotes for and starts proving jobs.
148
- * This method returns once the prover node has deposited an initial bond into the escrow contract.
149
- */
150
- async start() {
151
- await this.bondManager.ensureBond();
152
- this.epochsMonitor.start(this);
153
- this.claimsMonitor.start(this);
154
- this.log.info('Started ProverNode', this.options);
155
- }
156
- /**
157
- * Stops the prover node and all its dependencies.
158
- */
159
- async stop() {
160
- this.log.info('Stopping ProverNode');
161
- await this.epochsMonitor.stop();
162
- await this.claimsMonitor.stop();
163
- await this.prover.stop();
164
- await tryStop(this.l2BlockSource);
165
- this.publisher.interrupt();
166
- await Promise.all(Array.from(this.jobs.values()).map(job => job.stop()));
167
- await this.worldState.stop();
168
- await tryStop(this.coordination);
169
- await this.telemetryClient.stop();
170
- this.log.info('Stopped ProverNode');
171
- }
172
- /** Sends an epoch proof quote to the coordinator. */
173
- sendEpochProofQuote(quote) {
174
- this.log.info(`Sending quote for epoch`, quote.toViemArgs().quote);
175
- return this.doSendEpochProofQuote(quote);
176
- }
177
- doSendEpochProofQuote(quote) {
178
- return this.coordination.addEpochProofQuote(quote);
179
- }
180
- /**
181
- * Creates a proof for a block range. Returns once the proof has been submitted to L1.
182
- */
183
- async prove(epochNumber) {
184
- const job = await this.createProvingJob(BigInt(epochNumber));
185
- return job.run();
186
- }
187
- /**
188
- * Starts a proving process and returns immediately.
189
- */
190
- async startProof(epochNumber) {
191
- const job = await this.createProvingJob(BigInt(epochNumber));
192
- void job.run().catch(err => this.log.error(`Error proving epoch ${epochNumber}`, err));
193
- }
194
- /**
195
- * Returns the prover instance.
196
- */
197
- getProver() {
198
- return this.prover;
199
- }
200
- /**
201
- * Returns an array of jobs being processed.
202
- */
203
- getJobs() {
204
- return Promise.resolve(Array.from(this.jobs.entries()).map(([uuid, job]) => ({ uuid, status: job.getState() })));
205
- }
206
- checkMaximumPendingJobs() {
207
- const { maxPendingJobs } = this.options;
208
- return maxPendingJobs === 0 || this.jobs.size < maxPendingJobs;
209
- }
210
- async createProvingJob(epochNumber) {
211
- if (!this.checkMaximumPendingJobs()) {
212
- throw new Error(`Maximum pending proving jobs ${this.options.maxPendingJobs} reached. Cannot create new job.`);
213
- }
214
- // Gather blocks for this epoch
215
- const cachedEpochData = this.cachedEpochData?.epochNumber === epochNumber ? this.cachedEpochData : undefined;
216
- const { blocks, txs } = cachedEpochData ?? (await this.gatherEpochData(epochNumber));
217
- const fromBlock = blocks[0].number;
218
- const toBlock = blocks.at(-1).number;
219
- // Fast forward world state to right before the target block and get a fork
220
- this.log.verbose(`Creating proving job for epoch ${epochNumber} for block range ${fromBlock} to ${toBlock}`);
221
- await this.worldState.syncImmediate(fromBlock - 1);
222
- // Create a processor using the forked world state
223
- const publicProcessorFactory = new PublicProcessorFactory(this.contractDataSource, this.dateProvider, this.telemetryClient);
224
- const cleanUp = () => {
225
- this.jobs.delete(job.getId());
226
- return Promise.resolve();
227
- };
228
- const [_, endTimestamp] = getTimestampRangeForEpoch(epochNumber + 1n, await this.getL1Constants());
229
- const deadline = new Date(Number(endTimestamp) * 1000);
230
- const job = this.doCreateEpochProvingJob(epochNumber, deadline, blocks, txs, publicProcessorFactory, cleanUp);
231
- this.jobs.set(job.getId(), job);
232
- return job;
233
- }
234
- getL1Constants() {
235
- return this.l2BlockSource.getL1Constants();
236
- }
237
- async gatherEpochData(epochNumber) {
238
- // Gather blocks for this epoch and their txs
239
- const blocks = await this.gatherBlocks(epochNumber);
240
- const txs = await this.gatherTxs(epochNumber, blocks);
241
- return { blocks, txs };
242
- }
243
- async gatherBlocks(epochNumber) {
244
- const blocks = await this.l2BlockSource.getBlocksForEpoch(epochNumber);
245
- if (blocks.length === 0) {
246
- throw new EmptyEpochError(epochNumber);
247
- }
248
- return blocks;
22
+ */ export class ProverNode {
23
+ prover;
24
+ publisher;
25
+ l2BlockSource;
26
+ l1ToL2MessageSource;
27
+ contractDataSource;
28
+ worldState;
29
+ coordination;
30
+ quoteProvider;
31
+ quoteSigner;
32
+ claimsMonitor;
33
+ epochsMonitor;
34
+ bondManager;
35
+ telemetryClient;
36
+ log;
37
+ dateProvider;
38
+ latestEpochWeAreProving;
39
+ jobs;
40
+ cachedEpochData;
41
+ options;
42
+ metrics;
43
+ txFetcher;
44
+ lastBlockNumber;
45
+ tracer;
46
+ constructor(prover, publisher, l2BlockSource, l1ToL2MessageSource, contractDataSource, worldState, coordination, quoteProvider, quoteSigner, claimsMonitor, epochsMonitor, bondManager, options = {}, telemetryClient = getTelemetryClient()){
47
+ this.prover = prover;
48
+ this.publisher = publisher;
49
+ this.l2BlockSource = l2BlockSource;
50
+ this.l1ToL2MessageSource = l1ToL2MessageSource;
51
+ this.contractDataSource = contractDataSource;
52
+ this.worldState = worldState;
53
+ this.coordination = coordination;
54
+ this.quoteProvider = quoteProvider;
55
+ this.quoteSigner = quoteSigner;
56
+ this.claimsMonitor = claimsMonitor;
57
+ this.epochsMonitor = epochsMonitor;
58
+ this.bondManager = bondManager;
59
+ this.telemetryClient = telemetryClient;
60
+ this.log = createLogger('prover-node');
61
+ this.dateProvider = new DateProvider();
62
+ this.jobs = new Map();
63
+ this.cachedEpochData = undefined;
64
+ this.options = {
65
+ pollingIntervalMs: 1_000,
66
+ maxPendingJobs: 100,
67
+ maxParallelBlocksPerEpoch: 32,
68
+ txGatheringTimeoutMs: 60_000,
69
+ txGatheringIntervalMs: 1_000,
70
+ txGatheringMaxParallelRequests: 100,
71
+ ...compact(options)
72
+ };
73
+ this.metrics = new ProverNodeMetrics(telemetryClient, 'ProverNode');
74
+ this.tracer = telemetryClient.getTracer('ProverNode');
75
+ this.txFetcher = new RunningPromise(()=>this.checkForTxs(), this.log, this.options.txGatheringIntervalMs);
76
+ }
77
+ getP2P() {
78
+ const asP2PClient = this.coordination;
79
+ if (typeof asP2PClient.isP2PClient === 'function' && asP2PClient.isP2PClient()) {
80
+ return asP2PClient;
81
+ }
82
+ return undefined;
83
+ }
84
+ async handleClaim(proofClaim) {
85
+ if (proofClaim.epochToProve === this.latestEpochWeAreProving) {
86
+ this.log.verbose(`Already proving claim for epoch ${proofClaim.epochToProve}`);
87
+ return;
88
+ }
89
+ const provenEpoch = await this.l2BlockSource.getProvenL2EpochNumber();
90
+ if (provenEpoch !== undefined && proofClaim.epochToProve <= provenEpoch) {
91
+ this.log.verbose(`Claim for epoch ${proofClaim.epochToProve} is already proven`);
92
+ return;
93
+ }
94
+ try {
95
+ await this.startProof(proofClaim.epochToProve);
96
+ this.latestEpochWeAreProving = proofClaim.epochToProve;
97
+ } catch (err) {
98
+ this.log.error(`Error handling claim for epoch ${proofClaim.epochToProve}`, err);
99
+ }
100
+ try {
101
+ // Staked amounts are lowered after a claim, so this is a good time for doing a top-up if needed
102
+ await this.bondManager.ensureBond();
103
+ } catch (err) {
104
+ this.log.error(`Error ensuring prover bond after handling claim for epoch ${proofClaim.epochToProve}`, err);
105
+ }
106
+ }
107
+ /**
108
+ * Handles the epoch number to prove when the prover node starts by checking if there
109
+ * is an existing claim for it. If not, it creates and sends a quote for it.
110
+ * @param epochNumber - The epoch immediately before the current one when the prover node starts.
111
+ */ async handleInitialEpochSync(epochNumber) {
112
+ try {
113
+ const claim = await this.publisher.getProofClaim();
114
+ if (!claim || claim.epochToProve < epochNumber) {
115
+ this.log.verbose(`Handling epoch ${epochNumber} completed as initial sync`);
116
+ await this.handleEpochCompleted(epochNumber);
249
117
  }
250
- async gatherTxs(epochNumber, blocks) {
251
- let txsToFind = [];
252
- const txHashToBlock = new Map();
253
- const results = new Map();
254
- for (const block of blocks) {
255
- for (const tx of block.body.txEffects) {
256
- txsToFind.push(tx.txHash);
257
- txHashToBlock.set(tx.txHash.toString(), block.number);
258
- }
259
- }
260
- const totalTxsRequired = txsToFind.length;
261
- this.log.info(`Gathering a total of ${totalTxsRequired} txs for epoch=${epochNumber} made up of ${blocks.length} blocks`, { epochNumber });
262
- let iteration = 0;
263
- try {
264
- await retryUntil(async () => {
265
- const batch = [...txsToFind];
266
- txsToFind = [];
267
- const batchResults = await asyncPool(this.options.txGatheringMaxParallelRequests, batch, async (txHash) => {
268
- const tx = await this.coordination.getTxByHash(txHash);
269
- return [txHash, tx];
270
- });
271
- let found = 0;
272
- for (const [txHash, maybeTx] of batchResults) {
273
- if (maybeTx) {
274
- found++;
275
- results.set(txHash.toString(), maybeTx);
276
- }
277
- else {
278
- txsToFind.push(txHash);
279
- }
280
- }
281
- this.log.verbose(`Gathered ${found}/${batch.length} txs in iteration ${iteration} for epoch ${epochNumber}. In total ${results.size}/${totalTxsRequired} have been retrieved.`, { epochNumber });
282
- iteration++;
283
- // stop when we found all transactions
284
- return txsToFind.length === 0;
285
- }, 'Gather txs', this.options.txGatheringTimeoutMs / 1000, this.options.txGatheringIntervalMs / 1000);
286
- }
287
- catch (err) {
288
- if (err && err instanceof TimeoutError) {
289
- const notFoundList = txsToFind
290
- .map(txHash => `${txHash.toString()} (block ${txHashToBlock.get(txHash.toString())})`)
291
- .join(', ');
292
- throw new Error(`Txs not found for epoch ${epochNumber}: ${notFoundList}`);
293
- }
294
- else {
295
- throw err;
296
- }
297
- }
298
- return Array.from(results.values());
118
+ } catch (err) {
119
+ this.log.error(`Error handling initial epoch sync`, err);
120
+ }
121
+ }
122
+ /**
123
+ * Handles an epoch being completed by sending a quote for proving it.
124
+ * @param epochNumber - The epoch number that was just completed.
125
+ */ async handleEpochCompleted(epochNumber) {
126
+ try {
127
+ // Gather data for the epoch
128
+ const epochData = await this.gatherEpochData(epochNumber);
129
+ const { blocks } = epochData;
130
+ this.cachedEpochData = {
131
+ epochNumber,
132
+ ...epochData
133
+ };
134
+ // Construct a quote for the epoch
135
+ const partialQuote = await this.quoteProvider.getQuote(Number(epochNumber), blocks);
136
+ if (!partialQuote) {
137
+ this.log.info(`No quote produced for epoch ${epochNumber}`);
138
+ return;
299
139
  }
300
- /** Extracted for testing purposes. */
301
- doCreateEpochProvingJob(epochNumber, deadline, blocks, txs, publicProcessorFactory, cleanUp) {
302
- return new EpochProvingJob(this.worldState, epochNumber, blocks, txs, this.prover.createEpochProver(), publicProcessorFactory, this.publisher, this.l2BlockSource, this.l1ToL2MessageSource, this.metrics, deadline, { parallelBlockLimit: this.options.maxParallelBlocksPerEpoch }, cleanUp);
140
+ // Ensure we have deposited enough funds for sending this quote
141
+ await this.bondManager.ensureBond(partialQuote.bondAmount);
142
+ // Assemble and sign full quote
143
+ const quote = EpochProofQuotePayload.from({
144
+ ...partialQuote,
145
+ epochToProve: BigInt(epochNumber),
146
+ prover: this.publisher.getSenderAddress(),
147
+ validUntilSlot: partialQuote.validUntilSlot ?? BigInt(Number.MAX_SAFE_INTEGER)
148
+ });
149
+ const signed = await this.quoteSigner.sign(quote);
150
+ // Send it to the coordinator
151
+ this.log.info(`Sending quote for epoch ${epochNumber} with blocks ${blocks[0].number} to ${blocks.at(-1).number}`, quote.toViemArgs());
152
+ await this.doSendEpochProofQuote(signed);
153
+ } catch (err) {
154
+ if (err instanceof EmptyEpochError) {
155
+ this.log.info(`Not producing quote for ${epochNumber} since no blocks were found`);
156
+ } else {
157
+ this.log.error(`Error handling epoch completed`, err);
303
158
  }
304
- /** Extracted for testing purposes. */
305
- async triggerMonitors() {
306
- await this.epochsMonitor.work();
307
- await this.claimsMonitor.work();
159
+ }
160
+ }
161
+ /**
162
+ * Starts the prover node so it periodically checks for unproven epochs in the unfinalised chain from L1 and sends
163
+ * quotes for them, as well as monitors the claims for the epochs it has sent quotes for and starts proving jobs.
164
+ * This method returns once the prover node has deposited an initial bond into the escrow contract.
165
+ */ async start() {
166
+ this.txFetcher.start();
167
+ await this.bondManager.ensureBond();
168
+ this.epochsMonitor.start(this);
169
+ this.claimsMonitor.start(this);
170
+ this.log.info('Started ProverNode', this.options);
171
+ }
172
+ /**
173
+ * Stops the prover node and all its dependencies.
174
+ */ async stop() {
175
+ this.log.info('Stopping ProverNode');
176
+ await this.txFetcher.stop();
177
+ await this.epochsMonitor.stop();
178
+ await this.claimsMonitor.stop();
179
+ await this.prover.stop();
180
+ await tryStop(this.l2BlockSource);
181
+ this.publisher.interrupt();
182
+ await Promise.all(Array.from(this.jobs.values()).map((job)=>job.stop()));
183
+ await this.worldState.stop();
184
+ await tryStop(this.coordination);
185
+ await this.telemetryClient.stop();
186
+ this.log.info('Stopped ProverNode');
187
+ }
188
+ /** Sends an epoch proof quote to the coordinator. */ sendEpochProofQuote(quote) {
189
+ this.log.info(`Sending quote for epoch`, quote.toViemArgs().quote);
190
+ return this.doSendEpochProofQuote(quote);
191
+ }
192
+ doSendEpochProofQuote(quote) {
193
+ return this.coordination.addEpochProofQuote(quote);
194
+ }
195
+ /**
196
+ * Creates a proof for a block range. Returns once the proof has been submitted to L1.
197
+ */ async prove(epochNumber) {
198
+ const job = await this.createProvingJob(BigInt(epochNumber));
199
+ return job.run();
200
+ }
201
+ /**
202
+ * Starts a proving process and returns immediately.
203
+ */ async startProof(epochNumber) {
204
+ const job = await this.createProvingJob(BigInt(epochNumber));
205
+ void job.run().catch((err)=>this.log.error(`Error proving epoch ${epochNumber}`, err));
206
+ }
207
+ /**
208
+ * Returns the prover instance.
209
+ */ getProver() {
210
+ return this.prover;
211
+ }
212
+ /**
213
+ * Returns an array of jobs being processed.
214
+ */ getJobs() {
215
+ return Promise.resolve(Array.from(this.jobs.entries()).map(([uuid, job])=>({
216
+ uuid,
217
+ status: job.getState()
218
+ })));
219
+ }
220
+ checkMaximumPendingJobs() {
221
+ const { maxPendingJobs } = this.options;
222
+ return maxPendingJobs === 0 || this.jobs.size < maxPendingJobs;
223
+ }
224
+ async createProvingJob(epochNumber) {
225
+ if (!this.checkMaximumPendingJobs()) {
226
+ throw new Error(`Maximum pending proving jobs ${this.options.maxPendingJobs} reached. Cannot create new job.`);
227
+ }
228
+ // Gather blocks for this epoch
229
+ const cachedEpochData = this.cachedEpochData?.epochNumber === epochNumber ? this.cachedEpochData : undefined;
230
+ const { blocks, txs } = cachedEpochData ?? await this.gatherEpochData(epochNumber);
231
+ const fromBlock = blocks[0].number;
232
+ const toBlock = blocks.at(-1).number;
233
+ // Fast forward world state to right before the target block and get a fork
234
+ this.log.verbose(`Creating proving job for epoch ${epochNumber} for block range ${fromBlock} to ${toBlock}`);
235
+ await this.worldState.syncImmediate(fromBlock - 1);
236
+ // Create a processor using the forked world state
237
+ const publicProcessorFactory = new PublicProcessorFactory(this.contractDataSource, this.dateProvider, this.telemetryClient);
238
+ const cleanUp = ()=>{
239
+ this.jobs.delete(job.getId());
240
+ return Promise.resolve();
241
+ };
242
+ const [_, endTimestamp] = getTimestampRangeForEpoch(epochNumber + 1n, await this.getL1Constants());
243
+ const deadline = new Date(Number(endTimestamp) * 1000);
244
+ const job = this.doCreateEpochProvingJob(epochNumber, deadline, blocks, txs, publicProcessorFactory, cleanUp);
245
+ this.jobs.set(job.getId(), job);
246
+ return job;
247
+ }
248
+ getL1Constants() {
249
+ return this.l2BlockSource.getL1Constants();
250
+ }
251
+ /** Monitors for new blocks and requests their txs from the p2p layer to ensure they are available for proving. */ async checkForTxs() {
252
+ const blockNumber = await this.l2BlockSource.getBlockNumber();
253
+ if (this.lastBlockNumber === undefined || blockNumber > this.lastBlockNumber) {
254
+ const block = await this.l2BlockSource.getBlock(blockNumber);
255
+ if (!block) {
256
+ return;
308
257
  }
309
- },
310
- (() => {
311
- const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
312
- _createProvingJob_decorators = [trackSpan('ProverNode.createProvingJob', epochNumber => ({ [Attributes.EPOCH_NUMBER]: Number(epochNumber) }))];
313
- _getL1Constants_decorators = [memoize];
314
- _gatherEpochData_decorators = [trackSpan('ProverNode.gatherEpochData', epochNumber => ({ [Attributes.EPOCH_NUMBER]: Number(epochNumber) }))];
315
- __esDecorate(_a, null, _createProvingJob_decorators, { kind: "method", name: "createProvingJob", static: false, private: false, access: { has: obj => "createProvingJob" in obj, get: obj => obj.createProvingJob }, metadata: _metadata }, null, _instanceExtraInitializers);
316
- __esDecorate(_a, null, _getL1Constants_decorators, { kind: "method", name: "getL1Constants", static: false, private: false, access: { has: obj => "getL1Constants" in obj, get: obj => obj.getL1Constants }, metadata: _metadata }, null, _instanceExtraInitializers);
317
- __esDecorate(_a, null, _gatherEpochData_decorators, { kind: "method", name: "gatherEpochData", static: false, private: false, access: { has: obj => "gatherEpochData" in obj, get: obj => obj.gatherEpochData }, metadata: _metadata }, null, _instanceExtraInitializers);
318
- if (_metadata) Object.defineProperty(_a, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
319
- })(),
320
- _a;
321
- })();
322
- export { ProverNode };
258
+ const txHashes = block.body.txEffects.map((tx)=>tx.txHash);
259
+ this.log.verbose(`Fetching ${txHashes.length} for block number ${blockNumber} from coordination`);
260
+ await this.coordination.getTxsByHash(txHashes); // This stores the txs in the tx pool, no need to persist them here
261
+ this.lastBlockNumber = blockNumber;
262
+ }
263
+ }
264
+ async gatherEpochData(epochNumber) {
265
+ // Gather blocks for this epoch and their txs
266
+ const blocks = await this.gatherBlocks(epochNumber);
267
+ const txs = await this.gatherTxs(epochNumber, blocks);
268
+ return {
269
+ blocks,
270
+ txs
271
+ };
272
+ }
273
+ async gatherBlocks(epochNumber) {
274
+ const blocks = await this.l2BlockSource.getBlocksForEpoch(epochNumber);
275
+ if (blocks.length === 0) {
276
+ throw new EmptyEpochError(epochNumber);
277
+ }
278
+ return blocks;
279
+ }
280
+ async gatherTxs(epochNumber, blocks) {
281
+ const txsToFind = blocks.flatMap((block)=>block.body.txEffects.map((tx)=>tx.txHash));
282
+ const txs = await this.coordination.getTxsByHash(txsToFind);
283
+ if (txs.length === txsToFind.length) {
284
+ this.log.verbose(`Gathered all ${txs.length} txs for epoch ${epochNumber}`, {
285
+ epochNumber
286
+ });
287
+ return txs;
288
+ }
289
+ const txHashesFound = await Promise.all(txs.map((tx)=>tx.getTxHash()));
290
+ const missingTxHashes = txsToFind.filter((txHashToFind)=>!txHashesFound.some((txHashFound)=>txHashToFind.equals(txHashFound))).join(', ');
291
+ throw new Error(`Txs not found for epoch ${epochNumber}: ${missingTxHashes}`);
292
+ }
293
+ /** Extracted for testing purposes. */ doCreateEpochProvingJob(epochNumber, deadline, blocks, txs, publicProcessorFactory, cleanUp) {
294
+ return new EpochProvingJob(this.worldState, epochNumber, blocks, txs, this.prover.createEpochProver(), publicProcessorFactory, this.publisher, this.l2BlockSource, this.l1ToL2MessageSource, this.metrics, deadline, {
295
+ parallelBlockLimit: this.options.maxParallelBlocksPerEpoch
296
+ }, cleanUp);
297
+ }
298
+ /** Extracted for testing purposes. */ async triggerMonitors() {
299
+ await this.epochsMonitor.work();
300
+ await this.claimsMonitor.work();
301
+ }
302
+ }
303
+ _ts_decorate([
304
+ trackSpan('ProverNode.createProvingJob', (epochNumber)=>({
305
+ [Attributes.EPOCH_NUMBER]: Number(epochNumber)
306
+ }))
307
+ ], ProverNode.prototype, "createProvingJob", null);
308
+ _ts_decorate([
309
+ memoize
310
+ ], ProverNode.prototype, "getL1Constants", null);
311
+ _ts_decorate([
312
+ trackSpan('ProverNode.checkForTxs')
313
+ ], ProverNode.prototype, "checkForTxs", null);
314
+ _ts_decorate([
315
+ trackSpan('ProverNode.gatherEpochData', (epochNumber)=>({
316
+ [Attributes.EPOCH_NUMBER]: Number(epochNumber)
317
+ }))
318
+ ], ProverNode.prototype, "gatherEpochData", null);
323
319
  class EmptyEpochError extends Error {
324
- constructor(epochNumber) {
320
+ constructor(epochNumber){
325
321
  super(`No blocks found for epoch ${epochNumber}`);
326
322
  this.name = 'EmptyEpochError';
327
323
  }
328
324
  }
329
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicHJvdmVyLW5vZGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvcHJvdmVyLW5vZGUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBLE9BQU8sRUFHTCxzQkFBc0IsRUFZdEIseUJBQXlCLEVBQ3pCLE9BQU8sR0FDUixNQUFNLHNCQUFzQixDQUFDO0FBRTlCLE9BQU8sRUFBRSxTQUFTLEVBQUUsTUFBTSw4QkFBOEIsQ0FBQztBQUN6RCxPQUFPLEVBQUUsT0FBTyxFQUFFLE1BQU0sOEJBQThCLENBQUM7QUFDdkQsT0FBTyxFQUFFLE9BQU8sRUFBRSxNQUFNLDhCQUE4QixDQUFDO0FBQ3ZELE9BQU8sRUFBRSxZQUFZLEVBQUUsTUFBTSx5QkFBeUIsQ0FBQztBQUN2RCxPQUFPLEVBQUUsWUFBWSxFQUFFLE1BQU0sdUJBQXVCLENBQUM7QUFDckQsT0FBTyxFQUFFLFVBQVUsRUFBRSxNQUFNLHlCQUF5QixDQUFDO0FBQ3JELE9BQU8sRUFBRSxZQUFZLEVBQUUsTUFBTSx5QkFBeUIsQ0FBQztBQUd2RCxPQUFPLEVBQUUsc0JBQXNCLEVBQUUsTUFBTSx5QkFBeUIsQ0FBQztBQUNqRSxPQUFPLEVBQ0wsVUFBVSxFQUlWLGtCQUFrQixFQUNsQixTQUFTLEdBQ1YsTUFBTSx5QkFBeUIsQ0FBQztBQUdqQyxPQUFPLEVBQUUsZUFBZSxFQUE2QixNQUFNLDRCQUE0QixDQUFDO0FBQ3hGLE9BQU8sRUFBRSxpQkFBaUIsRUFBRSxNQUFNLGNBQWMsQ0FBQztBQWdCakQ7Ozs7O0dBS0c7SUFDVSxVQUFVOzs7Ozs7c0JBQVYsVUFBVTtZQVlyQixZQUNxQixNQUEwQixFQUMxQixTQUE4QixFQUM5QixhQUE2QyxFQUM3QyxtQkFBd0MsRUFDeEMsa0JBQXNDLEVBQ3RDLFVBQWtDLEVBQ2xDLFlBQWlELEVBQ2pELGFBQTRCLEVBQzVCLFdBQXdCLEVBQ3hCLGFBQTRCLEVBQzVCLGFBQTJCLEVBQzNCLFdBQXdCLEVBQzNDLFVBQXNDLEVBQUUsRUFDckIsa0JB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