@aztec/prover-node 0.0.1-commit.24de95ac → 0.0.1-commit.2606882

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 (58) hide show
  1. package/dest/actions/download-epoch-proving-job.d.ts +4 -4
  2. package/dest/actions/download-epoch-proving-job.js +1 -1
  3. package/dest/actions/index.d.ts +1 -1
  4. package/dest/actions/rerun-epoch-proving-job.d.ts +5 -3
  5. package/dest/actions/rerun-epoch-proving-job.d.ts.map +1 -1
  6. package/dest/actions/rerun-epoch-proving-job.js +9 -7
  7. package/dest/actions/upload-epoch-proof-failure.d.ts +2 -2
  8. package/dest/actions/upload-epoch-proof-failure.d.ts.map +1 -1
  9. package/dest/bin/run-failed-epoch.d.ts +1 -1
  10. package/dest/bin/run-failed-epoch.js +6 -5
  11. package/dest/config.d.ts +8 -10
  12. package/dest/config.d.ts.map +1 -1
  13. package/dest/config.js +19 -21
  14. package/dest/factory.d.ts +20 -16
  15. package/dest/factory.d.ts.map +1 -1
  16. package/dest/factory.js +48 -63
  17. package/dest/index.d.ts +2 -1
  18. package/dest/index.d.ts.map +1 -1
  19. package/dest/index.js +1 -0
  20. package/dest/job/epoch-proving-job-data.d.ts +8 -6
  21. package/dest/job/epoch-proving-job-data.d.ts.map +1 -1
  22. package/dest/job/epoch-proving-job-data.js +25 -18
  23. package/dest/job/epoch-proving-job.d.ts +11 -13
  24. package/dest/job/epoch-proving-job.d.ts.map +1 -1
  25. package/dest/job/epoch-proving-job.js +669 -111
  26. package/dest/metrics.d.ts +24 -3
  27. package/dest/metrics.d.ts.map +1 -1
  28. package/dest/metrics.js +77 -98
  29. package/dest/monitors/epoch-monitor.d.ts +3 -2
  30. package/dest/monitors/epoch-monitor.d.ts.map +1 -1
  31. package/dest/monitors/epoch-monitor.js +14 -20
  32. package/dest/monitors/index.d.ts +1 -1
  33. package/dest/prover-node-publisher.d.ts +33 -11
  34. package/dest/prover-node-publisher.d.ts.map +1 -1
  35. package/dest/prover-node-publisher.js +248 -46
  36. package/dest/prover-node.d.ts +30 -19
  37. package/dest/prover-node.d.ts.map +1 -1
  38. package/dest/prover-node.js +512 -81
  39. package/dest/prover-publisher-factory.d.ts +10 -6
  40. package/dest/prover-publisher-factory.d.ts.map +1 -1
  41. package/dest/prover-publisher-factory.js +7 -5
  42. package/dest/test/index.d.ts +1 -1
  43. package/dest/test/index.d.ts.map +1 -1
  44. package/package.json +27 -25
  45. package/src/actions/download-epoch-proving-job.ts +1 -1
  46. package/src/actions/rerun-epoch-proving-job.ts +20 -7
  47. package/src/actions/upload-epoch-proof-failure.ts +1 -1
  48. package/src/bin/run-failed-epoch.ts +5 -3
  49. package/src/config.ts +27 -33
  50. package/src/factory.ts +81 -103
  51. package/src/index.ts +1 -0
  52. package/src/job/epoch-proving-job-data.ts +31 -25
  53. package/src/job/epoch-proving-job.ts +228 -117
  54. package/src/metrics.ts +102 -82
  55. package/src/monitors/epoch-monitor.ts +10 -17
  56. package/src/prover-node-publisher.ts +302 -66
  57. package/src/prover-node.ts +135 -86
  58. package/src/prover-publisher-factory.ts +19 -11
@@ -1,23 +1,25 @@
1
- import { NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP } from '@aztec/constants';
2
1
  import { asyncPool } from '@aztec/foundation/async-pool';
3
- import { padArrayEnd } from '@aztec/foundation/collection';
4
- import { Fr } from '@aztec/foundation/fields';
5
- import { createLogger } from '@aztec/foundation/log';
2
+ import { BlockNumber, EpochNumber } from '@aztec/foundation/branded-types';
3
+ import { Fr } from '@aztec/foundation/curves/bn254';
4
+ import { type Logger, type LoggerBindings, createLogger } from '@aztec/foundation/log';
6
5
  import { RunningPromise, promiseWithResolvers } from '@aztec/foundation/promise';
7
6
  import { Timer } from '@aztec/foundation/timer';
7
+ import { AVM_MAX_CONCURRENT_SIMULATIONS } from '@aztec/native';
8
8
  import { getVKTreeRoot } from '@aztec/noir-protocol-circuits-types/vk-tree';
9
9
  import { protocolContractsHash } from '@aztec/protocol-contracts';
10
10
  import { buildFinalBlobChallenges } from '@aztec/prover-client/helpers';
11
11
  import type { PublicProcessor, PublicProcessorFactory } from '@aztec/simulator/server';
12
+ import { PublicSimulatorConfig } from '@aztec/stdlib/avm';
12
13
  import type { L2Block, L2BlockSource } from '@aztec/stdlib/block';
14
+ import type { Checkpoint } from '@aztec/stdlib/checkpoint';
13
15
  import {
14
16
  type EpochProver,
15
17
  type EpochProvingJobState,
16
18
  EpochProvingJobTerminalState,
17
19
  type ForkMerkleTreeOperations,
18
20
  } from '@aztec/stdlib/interfaces/server';
21
+ import { appendL1ToL2MessagesToTree } from '@aztec/stdlib/messaging';
19
22
  import { CheckpointConstantData } from '@aztec/stdlib/rollup';
20
- import { MerkleTreeId } from '@aztec/stdlib/trees';
21
23
  import type { ProcessedTx, Tx } from '@aztec/stdlib/tx';
22
24
  import { Attributes, type Traceable, type Tracer, trackSpan } from '@aztec/telemetry-client';
23
25
 
@@ -40,10 +42,11 @@ export type EpochProvingJobOptions = {
40
42
  */
41
43
  export class EpochProvingJob implements Traceable {
42
44
  private state: EpochProvingJobState = 'initialized';
43
- private log = createLogger('prover-node:epoch-proving-job');
45
+ private log: Logger;
44
46
  private uuid: string;
45
47
 
46
48
  private runPromise: Promise<void> | undefined;
49
+ private abortController = new AbortController();
47
50
  private epochCheckPromise: RunningPromise | undefined;
48
51
  private deadlineTimeoutHandler: NodeJS.Timeout | undefined;
49
52
 
@@ -54,14 +57,19 @@ export class EpochProvingJob implements Traceable {
54
57
  private dbProvider: Pick<ForkMerkleTreeOperations, 'fork'>,
55
58
  private prover: EpochProver,
56
59
  private publicProcessorFactory: PublicProcessorFactory,
57
- private publisher: Pick<ProverNodePublisher, 'submitEpochProof'>,
60
+ private publisher: Pick<ProverNodePublisher, 'submitEpochProof' | 'analyzeEpochProofSubmission'>,
58
61
  private l2BlockSource: L2BlockSource | undefined,
59
62
  private metrics: ProverNodeJobMetrics,
60
63
  private deadline: Date | undefined,
61
64
  private config: EpochProvingJobOptions,
65
+ bindings?: LoggerBindings,
62
66
  ) {
63
67
  validateEpochProvingJobData(data);
64
68
  this.uuid = crypto.randomUUID();
69
+ this.log = createLogger('prover-node:epoch-proving-job', {
70
+ ...bindings,
71
+ instanceId: `epoch-${data.epochNumber}`,
72
+ });
65
73
  this.tracer = metrics.tracer;
66
74
  }
67
75
 
@@ -73,7 +81,7 @@ export class EpochProvingJob implements Traceable {
73
81
  return this.state;
74
82
  }
75
83
 
76
- public getEpochNumber(): bigint {
84
+ public getEpochNumber(): EpochNumber {
77
85
  return this.data.epochNumber;
78
86
  }
79
87
 
@@ -89,8 +97,8 @@ export class EpochProvingJob implements Traceable {
89
97
  return this.data.epochNumber;
90
98
  }
91
99
 
92
- private get blocks() {
93
- return this.data.blocks;
100
+ private get checkpoints() {
101
+ return this.data.checkpoints;
94
102
  }
95
103
 
96
104
  private get txs() {
@@ -105,7 +113,7 @@ export class EpochProvingJob implements Traceable {
105
113
  * Proves the given epoch and submits the proof to L1.
106
114
  */
107
115
  @trackSpan('EpochProvingJob.run', function () {
108
- return { [Attributes.EPOCH_NUMBER]: Number(this.data.epochNumber) };
116
+ return { [Attributes.EPOCH_NUMBER]: this.data.epochNumber };
109
117
  })
110
118
  public async run() {
111
119
  this.scheduleDeadlineStop();
@@ -114,14 +122,22 @@ export class EpochProvingJob implements Traceable {
114
122
  }
115
123
 
116
124
  const attestations = this.attestations.map(attestation => attestation.toViem());
117
- const epochNumber = Number(this.epochNumber);
118
- const epochSizeBlocks = this.blocks.length;
119
- const epochSizeTxs = this.blocks.reduce((total, current) => total + current.body.txEffects.length, 0);
120
- const [fromBlock, toBlock] = [this.blocks[0].number, this.blocks.at(-1)!.number];
121
- this.log.info(`Starting epoch ${epochNumber} proving job with blocks ${fromBlock} to ${toBlock}`, {
125
+ const epochNumber = this.epochNumber;
126
+ const epochSizeCheckpoints = this.checkpoints.length;
127
+ const epochSizeBlocks = this.checkpoints.reduce((accum, checkpoint) => accum + checkpoint.blocks.length, 0);
128
+ const epochSizeTxs = this.checkpoints.reduce(
129
+ (accum, checkpoint) =>
130
+ accum + checkpoint.blocks.reduce((accumC, block) => accumC + block.body.txEffects.length, 0),
131
+ 0,
132
+ );
133
+ const fromCheckpoint = this.checkpoints[0].number;
134
+ const toCheckpoint = this.checkpoints.at(-1)!.number;
135
+ const fromBlock = this.checkpoints[0].blocks[0].number;
136
+ const toBlock = this.checkpoints.at(-1)!.blocks.at(-1)!.number;
137
+ this.log.info(`Starting epoch ${epochNumber} proving job with checkpoints ${fromCheckpoint} to ${toCheckpoint}`, {
122
138
  fromBlock,
123
139
  toBlock,
124
- epochSizeBlocks,
140
+ epochSizeTxs,
125
141
  epochNumber,
126
142
  uuid: this.uuid,
127
143
  });
@@ -132,84 +148,121 @@ export class EpochProvingJob implements Traceable {
132
148
  this.runPromise = promise;
133
149
 
134
150
  try {
135
- const blobFieldsPerCheckpoint = this.blocks.map(block => block.getCheckpointBlobFields());
151
+ const blobTimer = new Timer();
152
+ const blobFieldsPerCheckpoint = this.checkpoints.map(checkpoint => checkpoint.toBlobFields());
136
153
  const finalBlobBatchingChallenges = await buildFinalBlobChallenges(blobFieldsPerCheckpoint);
154
+ this.metrics.recordBlobProcessing(blobTimer.ms());
137
155
 
138
- // TODO(#17027): Enable multiple blocks per checkpoint.
139
- // Total number of checkpoints equals number of blocks because we currently build a checkpoint with only one block.
140
- const totalNumCheckpoints = epochSizeBlocks;
141
-
142
- this.prover.startNewEpoch(epochNumber, totalNumCheckpoints, finalBlobBatchingChallenges);
156
+ this.prover.startNewEpoch(epochNumber, epochSizeCheckpoints, finalBlobBatchingChallenges);
157
+ const chonkTimer = new Timer();
143
158
  await this.prover.startChonkVerifierCircuits(Array.from(this.txs.values()));
159
+ this.metrics.recordChonkVerifier(chonkTimer.ms());
144
160
 
145
- await asyncPool(this.config.parallelBlockLimit ?? 32, this.blocks, async block => {
146
- this.checkState();
161
+ // Everything in the epoch should have the same chainId and version.
162
+ const { chainId, version } = this.checkpoints[0].blocks[0].header.globalVariables;
147
163
 
148
- const globalVariables = block.header.globalVariables;
149
- const txs = this.getTxs(block);
150
- const l1ToL2Messages = this.getL1ToL2Messages(block);
151
- const previousHeader = this.getBlockHeader(block.number - 1)!;
152
-
153
- this.log.verbose(`Starting processing block ${block.number}`, {
154
- number: block.number,
155
- blockHash: (await block.hash()).toString(),
156
- lastArchive: block.header.lastArchive.root,
157
- noteHashTreeRoot: block.header.state.partial.noteHashTree.root,
158
- nullifierTreeRoot: block.header.state.partial.nullifierTree.root,
159
- publicDataTreeRoot: block.header.state.partial.publicDataTree.root,
160
- previousHeader: previousHeader.hash(),
161
- uuid: this.uuid,
162
- ...globalVariables,
163
- });
164
+ const previousBlockHeaders = this.gatherPreviousBlockHeaders();
165
+
166
+ const allCheckpointsTimer = new Timer();
164
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 => {
175
+ this.checkState();
176
+ const checkpointTimer = new Timer();
177
+
178
+ const checkpointIndex = checkpoint.number - fromCheckpoint;
165
179
  const checkpointConstants = CheckpointConstantData.from({
166
- chainId: globalVariables.chainId,
167
- version: globalVariables.version,
180
+ chainId,
181
+ version,
168
182
  vkTreeRoot: getVKTreeRoot(),
169
183
  protocolContractsHash: protocolContractsHash,
170
184
  proverId: this.prover.getProverId().toField(),
171
- slotNumber: globalVariables.slotNumber,
172
- coinbase: globalVariables.coinbase,
173
- feeRecipient: globalVariables.feeRecipient,
174
- gasFees: globalVariables.gasFees,
185
+ slotNumber: checkpoint.header.slotNumber,
186
+ coinbase: checkpoint.header.coinbase,
187
+ feeRecipient: checkpoint.header.feeRecipient,
188
+ gasFees: checkpoint.header.gasFees,
189
+ });
190
+ const previousHeader = previousBlockHeaders[checkpointIndex];
191
+ const l1ToL2Messages = this.getL1ToL2Messages(checkpoint);
192
+
193
+ this.log.debug(`Starting processing checkpoint ${checkpoint.number}`, {
194
+ number: checkpoint.number,
195
+ checkpointHash: checkpoint.hash().toString(),
196
+ headerHash: checkpoint.header.hash().toString(),
197
+ numL1ToL2Messages: l1ToL2Messages.length,
198
+ previousBlockNumber: previousHeader.globalVariables.blockNumber,
199
+ uuid: this.uuid,
175
200
  });
176
201
 
177
- // TODO(#17027): Enable multiple blocks per checkpoint.
178
- // Each checkpoint has only one block.
179
- const totalNumBlocks = 1;
180
- const checkpointIndex = block.number - fromBlock;
181
202
  await this.prover.startNewCheckpoint(
182
203
  checkpointIndex,
183
204
  checkpointConstants,
184
205
  l1ToL2Messages,
185
- totalNumBlocks,
186
- blobFieldsPerCheckpoint[checkpointIndex].length,
206
+ checkpoint.blocks.length,
187
207
  previousHeader,
188
208
  );
189
209
 
190
- // Start block proving
191
- await this.prover.startNewBlock(block.number, globalVariables.timestamp, txs.length);
210
+ for (let blockIndex = 0; blockIndex < checkpoint.blocks.length; blockIndex++) {
211
+ const blockTimer = new Timer();
212
+ const block = checkpoint.blocks[blockIndex];
213
+ const globalVariables = block.header.globalVariables;
214
+ const txs = this.getTxs(block);
215
+
216
+ this.log.verbose(`Starting processing block ${block.number}`, {
217
+ number: block.number,
218
+ blockHash: (await block.hash()).toString(),
219
+ lastArchive: block.header.lastArchive.root,
220
+ noteHashTreeRoot: block.header.state.partial.noteHashTree.root,
221
+ nullifierTreeRoot: block.header.state.partial.nullifierTree.root,
222
+ publicDataTreeRoot: block.header.state.partial.publicDataTree.root,
223
+ ...globalVariables,
224
+ numTxs: txs.length,
225
+ });
192
226
 
193
- // Process public fns
194
- const db = await this.createFork(block.number - 1, l1ToL2Messages);
195
- const publicProcessor = this.publicProcessorFactory.create(db, globalVariables, {
196
- skipFeeEnforcement: true,
197
- clientInitiatedSimulation: false,
198
- proverId: this.prover.getProverId().toField(),
199
- });
200
- const processed = await this.processTxs(publicProcessor, txs);
201
- await this.prover.addTxs(processed);
202
- await db.close();
203
- this.log.verbose(`Processed all ${txs.length} txs for block ${block.number}`, {
204
- blockNumber: block.number,
205
- blockHash: (await block.hash()).toString(),
206
- uuid: this.uuid,
207
- });
227
+ // Start block proving
228
+ await this.prover.startNewBlock(block.number, globalVariables.timestamp, txs.length);
229
+
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();
252
+ this.log.verbose(`Processed all ${txs.length} txs for block ${block.number}`, {
253
+ blockNumber: block.number,
254
+ blockHash: (await block.hash()).toString(),
255
+ uuid: this.uuid,
256
+ });
208
257
 
209
- // Mark block as completed to pad it
210
- const expectedBlockHeader = block.getBlockHeader();
211
- await this.prover.setBlockCompleted(block.number, expectedBlockHeader);
258
+ // Mark block as completed to pad it
259
+ const expectedBlockHeader = block.header;
260
+ await this.prover.setBlockCompleted(block.number, expectedBlockHeader);
261
+ this.metrics.recordBlockProcessing(blockTimer.ms());
262
+ }
263
+ this.metrics.recordCheckpointProcessing(checkpointTimer.ms());
212
264
  });
265
+ this.metrics.recordAllCheckpointsProcessing(allCheckpointsTimer.ms());
213
266
 
214
267
  const executionTime = timer.ms();
215
268
 
@@ -221,16 +274,29 @@ export class EpochProvingJob implements Traceable {
221
274
 
222
275
  if (this.config.skipSubmitProof) {
223
276
  this.log.info(
224
- `Proof publishing is disabled. Dropping valid proof for epoch ${epochNumber} (blocks ${fromBlock} to ${toBlock})`,
277
+ `Proof publishing is disabled. Analyzing estimated L1 fees for epoch ${epochNumber} (checkpoints ${fromCheckpoint} to ${toCheckpoint})`,
225
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
+ }
226
292
  this.state = 'completed';
227
- this.metrics.recordProvingJob(executionTime, timer.ms(), epochSizeBlocks, epochSizeTxs);
293
+ this.metrics.recordProvingJob(executionTime, timer.ms(), epochSizeCheckpoints, epochSizeBlocks, epochSizeTxs);
228
294
  return;
229
295
  }
230
296
 
231
297
  const success = await this.publisher.submitEpochProof({
232
- fromBlock,
233
- toBlock,
298
+ fromCheckpoint,
299
+ toCheckpoint,
234
300
  epochNumber,
235
301
  publicInputs,
236
302
  proof,
@@ -241,12 +307,12 @@ export class EpochProvingJob implements Traceable {
241
307
  throw new Error('Failed to submit epoch proof to L1');
242
308
  }
243
309
 
244
- this.log.info(`Submitted proof for epoch ${epochNumber} (blocks ${fromBlock} to ${toBlock})`, {
310
+ this.log.info(`Submitted proof for epoch ${epochNumber} (checkpoints ${fromCheckpoint} to ${toCheckpoint})`, {
245
311
  epochNumber,
246
312
  uuid: this.uuid,
247
313
  });
248
314
  this.state = 'completed';
249
- this.metrics.recordProvingJob(executionTime, timer.ms(), epochSizeBlocks, epochSizeTxs);
315
+ this.metrics.recordProvingJob(executionTime, timer.ms(), epochSizeCheckpoints, epochSizeBlocks, epochSizeTxs);
250
316
  } catch (err: any) {
251
317
  if (err && err.name === 'HaltExecutionError') {
252
318
  this.log.warn(`Halted execution of epoch ${epochNumber} prover job`, {
@@ -269,25 +335,63 @@ export class EpochProvingJob implements Traceable {
269
335
  }
270
336
 
271
337
  /**
272
- * 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.
273
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.
274
342
  */
275
- private async createFork(blockNumber: number, l1ToL2Messages: Fr[]) {
276
- const db = await this.dbProvider.fork(blockNumber);
277
- const l1ToL2MessagesPadded = padArrayEnd<Fr, number>(
278
- l1ToL2Messages,
279
- Fr.ZERO,
280
- NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP,
281
- 'Too many L1 to L2 messages',
282
- );
283
- this.log.verbose(`Creating fork at ${blockNumber} with ${l1ToL2Messages.length} L1 to L2 messages`, {
284
- blockNumber,
285
- l1ToL2Messages: l1ToL2MessagesPadded.map(m => m.toString()),
286
- });
287
- 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();
288
359
  return db;
289
360
  }
290
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
+
291
395
  private progressState(state: EpochProvingJobState) {
292
396
  this.checkState();
293
397
  this.state = state;
@@ -301,12 +405,24 @@ export class EpochProvingJob implements Traceable {
301
405
 
302
406
  public async stop(state: EpochProvingJobTerminalState = 'stopped') {
303
407
  this.state = state;
304
- this.prover.cancel();
408
+ this.interruptProcessing();
305
409
  if (this.runPromise) {
306
410
  await this.runPromise;
307
411
  }
308
412
  }
309
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
+
310
426
  private scheduleDeadlineStop() {
311
427
  const deadline = this.deadline;
312
428
  if (deadline) {
@@ -341,11 +457,14 @@ export class EpochProvingJob implements Traceable {
341
457
  const intervalMs = Math.ceil((await l2BlockSource.getL1Constants()).ethereumSlotDuration / 2) * 1000;
342
458
  this.epochCheckPromise = new RunningPromise(
343
459
  async () => {
344
- const blocks = await l2BlockSource.getBlockHeadersForEpoch(this.epochNumber);
345
- const blockHashes = await Promise.all(blocks.map(block => block.hash()));
346
- const thisBlockHashes = await Promise.all(this.blocks.map(block => block.hash()));
460
+ const blockHeaders = (
461
+ await l2BlockSource.getBlocksData({ epoch: this.epochNumber, onlyCheckpointed: true })
462
+ ).map(d => d.header);
463
+ const blockHashes = await Promise.all(blockHeaders.map(header => header.hash()));
464
+ const thisBlocks = this.checkpoints.flatMap(checkpoint => checkpoint.blocks);
465
+ const thisBlockHashes = await Promise.all(thisBlocks.map(block => block.hash()));
347
466
  if (
348
- blocks.length !== this.blocks.length ||
467
+ blockHeaders.length !== thisBlocks.length ||
349
468
  !blockHashes.every((block, i) => block.equals(thisBlockHashes[i]))
350
469
  ) {
351
470
  this.log.warn('Epoch blocks changed underfoot', {
@@ -363,35 +482,27 @@ export class EpochProvingJob implements Traceable {
363
482
  this.log.verbose(`Scheduled epoch check for epoch ${this.epochNumber} every ${intervalMs}ms`);
364
483
  }
365
484
 
366
- /* Returns the header for the given block number based on the epoch proving job data. */
367
- private getBlockHeader(blockNumber: number) {
368
- const block = this.blocks.find(b => b.number === blockNumber);
369
- if (block) {
370
- return block.getBlockHeader();
371
- }
372
-
373
- if (blockNumber === Number(this.data.previousBlockHeader.getBlockNumber())) {
374
- return this.data.previousBlockHeader;
375
- }
376
-
377
- throw new Error(
378
- `Block header not found for block number ${blockNumber} (got ${this.blocks
379
- .map(b => b.number)
380
- .join(', ')} and previous header ${this.data.previousBlockHeader.getBlockNumber()})`,
381
- );
485
+ /* Returns the last block header in the previous checkpoint for all checkpoints in the epoch */
486
+ private gatherPreviousBlockHeaders() {
487
+ const lastBlocks = this.checkpoints.map(checkpoint => checkpoint.blocks.at(-1)!);
488
+ return [this.data.previousBlockHeader, ...lastBlocks.map(block => block.header).slice(0, -1)];
382
489
  }
383
490
 
384
491
  private getTxs(block: L2Block): Tx[] {
385
492
  return block.body.txEffects.map(txEffect => this.txs.get(txEffect.txHash.toString())!);
386
493
  }
387
494
 
388
- private getL1ToL2Messages(block: L2Block) {
389
- return this.data.l1ToL2Messages[block.number];
495
+ private getL1ToL2Messages(checkpoint: Checkpoint) {
496
+ return this.data.l1ToL2Messages[checkpoint.number];
390
497
  }
391
498
 
392
499
  private async processTxs(publicProcessor: PublicProcessor, txs: Tx[]): Promise<ProcessedTx[]> {
393
500
  const { deadline } = this;
394
- 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();
395
506
 
396
507
  if (failedTxs.length) {
397
508
  const failedTxHashes = await Promise.all(failedTxs.map(({ tx }) => tx.getTxHash()));