@aztec/prover-node 0.0.0-test.1 → 0.0.1-commit.017a351

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 (82) hide show
  1. package/dest/actions/download-epoch-proving-job.d.ts +18 -0
  2. package/dest/actions/download-epoch-proving-job.d.ts.map +1 -0
  3. package/dest/actions/download-epoch-proving-job.js +37 -0
  4. package/dest/actions/index.d.ts +3 -0
  5. package/dest/actions/index.d.ts.map +1 -0
  6. package/dest/actions/index.js +2 -0
  7. package/dest/actions/rerun-epoch-proving-job.d.ts +13 -0
  8. package/dest/actions/rerun-epoch-proving-job.d.ts.map +1 -0
  9. package/dest/actions/rerun-epoch-proving-job.js +120 -0
  10. package/dest/actions/upload-epoch-proof-failure.d.ts +15 -0
  11. package/dest/actions/upload-epoch-proof-failure.d.ts.map +1 -0
  12. package/dest/actions/upload-epoch-proof-failure.js +78 -0
  13. package/dest/bin/run-failed-epoch.d.ts +2 -0
  14. package/dest/bin/run-failed-epoch.d.ts.map +1 -0
  15. package/dest/bin/run-failed-epoch.js +68 -0
  16. package/dest/config.d.ts +17 -16
  17. package/dest/config.d.ts.map +1 -1
  18. package/dest/config.js +93 -28
  19. package/dest/factory.d.ts +26 -18
  20. package/dest/factory.d.ts.map +1 -1
  21. package/dest/factory.js +92 -43
  22. package/dest/index.d.ts +3 -2
  23. package/dest/index.d.ts.map +1 -1
  24. package/dest/index.js +2 -1
  25. package/dest/job/epoch-proving-job-data.d.ts +18 -0
  26. package/dest/job/epoch-proving-job-data.d.ts.map +1 -0
  27. package/dest/job/epoch-proving-job-data.js +59 -0
  28. package/dest/job/epoch-proving-job.d.ts +31 -18
  29. package/dest/job/epoch-proving-job.d.ts.map +1 -1
  30. package/dest/job/epoch-proving-job.js +756 -99
  31. package/dest/metrics.d.ts +50 -5
  32. package/dest/metrics.d.ts.map +1 -1
  33. package/dest/metrics.js +157 -72
  34. package/dest/monitors/epoch-monitor.d.ts +5 -2
  35. package/dest/monitors/epoch-monitor.d.ts.map +1 -1
  36. package/dest/monitors/epoch-monitor.js +24 -17
  37. package/dest/monitors/index.d.ts +1 -1
  38. package/dest/prover-node-publisher.d.ts +38 -19
  39. package/dest/prover-node-publisher.d.ts.map +1 -1
  40. package/dest/prover-node-publisher.js +288 -87
  41. package/dest/prover-node.d.ts +64 -49
  42. package/dest/prover-node.d.ts.map +1 -1
  43. package/dest/prover-node.js +650 -148
  44. package/dest/prover-publisher-factory.d.ts +25 -0
  45. package/dest/prover-publisher-factory.d.ts.map +1 -0
  46. package/dest/prover-publisher-factory.js +28 -0
  47. package/dest/test/index.d.ts +5 -3
  48. package/dest/test/index.d.ts.map +1 -1
  49. package/dest/test/index.js +1 -3
  50. package/package.json +40 -33
  51. package/src/actions/download-epoch-proving-job.ts +44 -0
  52. package/src/actions/index.ts +2 -0
  53. package/src/actions/rerun-epoch-proving-job.ts +74 -0
  54. package/src/actions/upload-epoch-proof-failure.ts +88 -0
  55. package/src/bin/run-failed-epoch.ts +79 -0
  56. package/src/config.ts +123 -45
  57. package/src/factory.ts +163 -67
  58. package/src/index.ts +2 -1
  59. package/src/job/epoch-proving-job-data.ts +82 -0
  60. package/src/job/epoch-proving-job.ts +364 -88
  61. package/src/metrics.ts +188 -70
  62. package/src/monitors/epoch-monitor.ts +23 -19
  63. package/src/prover-node-publisher.ts +368 -125
  64. package/src/prover-node.ts +292 -166
  65. package/src/prover-publisher-factory.ts +45 -0
  66. package/src/test/index.ts +7 -4
  67. package/dest/http.d.ts +0 -8
  68. package/dest/http.d.ts.map +0 -1
  69. package/dest/http.js +0 -9
  70. package/dest/prover-coordination/config.d.ts +0 -7
  71. package/dest/prover-coordination/config.d.ts.map +0 -1
  72. package/dest/prover-coordination/config.js +0 -11
  73. package/dest/prover-coordination/factory.d.ts +0 -22
  74. package/dest/prover-coordination/factory.d.ts.map +0 -1
  75. package/dest/prover-coordination/factory.js +0 -42
  76. package/dest/prover-coordination/index.d.ts +0 -3
  77. package/dest/prover-coordination/index.d.ts.map +0 -1
  78. package/dest/prover-coordination/index.js +0 -2
  79. package/src/http.ts +0 -13
  80. package/src/prover-coordination/config.ts +0 -17
  81. package/src/prover-coordination/factory.ts +0 -72
  82. package/src/prover-coordination/index.ts +0 -2
@@ -1,56 +1,76 @@
1
1
  import { asyncPool } from '@aztec/foundation/async-pool';
2
- import { createLogger } from '@aztec/foundation/log';
3
- import { promiseWithResolvers } from '@aztec/foundation/promise';
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';
5
+ import { RunningPromise, promiseWithResolvers } from '@aztec/foundation/promise';
4
6
  import { Timer } from '@aztec/foundation/timer';
7
+ import { AVM_MAX_CONCURRENT_SIMULATIONS } from '@aztec/native';
8
+ import { getVKTreeRoot } from '@aztec/noir-protocol-circuits-types/vk-tree';
9
+ import { protocolContractsHash } from '@aztec/protocol-contracts';
10
+ import { buildFinalBlobChallenges } from '@aztec/prover-client/helpers';
5
11
  import type { PublicProcessor, PublicProcessorFactory } from '@aztec/simulator/server';
12
+ import { PublicSimulatorConfig } from '@aztec/stdlib/avm';
6
13
  import type { L2Block, L2BlockSource } from '@aztec/stdlib/block';
14
+ import type { Checkpoint } from '@aztec/stdlib/checkpoint';
7
15
  import {
8
16
  type EpochProver,
9
17
  type EpochProvingJobState,
10
18
  EpochProvingJobTerminalState,
11
19
  type ForkMerkleTreeOperations,
12
20
  } from '@aztec/stdlib/interfaces/server';
13
- import type { L1ToL2MessageSource } from '@aztec/stdlib/messaging';
21
+ import { appendL1ToL2MessagesToTree } from '@aztec/stdlib/messaging';
22
+ import { CheckpointConstantData } from '@aztec/stdlib/rollup';
14
23
  import type { ProcessedTx, Tx } from '@aztec/stdlib/tx';
15
24
  import { Attributes, type Traceable, type Tracer, trackSpan } from '@aztec/telemetry-client';
16
25
 
17
26
  import * as crypto from 'node:crypto';
18
27
 
19
- import type { ProverNodeMetrics } from '../metrics.js';
28
+ import type { ProverNodeJobMetrics } from '../metrics.js';
20
29
  import type { ProverNodePublisher } from '../prover-node-publisher.js';
30
+ import { type EpochProvingJobData, validateEpochProvingJobData } from './epoch-proving-job-data.js';
31
+
32
+ export type EpochProvingJobOptions = {
33
+ parallelBlockLimit?: number;
34
+ skipEpochCheck?: boolean;
35
+ skipSubmitProof?: boolean;
36
+ };
21
37
 
22
38
  /**
23
- * Job that grabs a range of blocks from the unfinalised chain from L1, gets their txs given their hashes,
39
+ * Job that grabs a range of blocks from the unfinalized chain from L1, gets their txs given their hashes,
24
40
  * re-executes their public calls, generates a rollup proof, and submits it to L1. This job will update the
25
41
  * world state as part of public call execution via the public processor.
26
42
  */
27
43
  export class EpochProvingJob implements Traceable {
28
44
  private state: EpochProvingJobState = 'initialized';
29
- private log = createLogger('prover-node:epoch-proving-job');
45
+ private log: Logger;
30
46
  private uuid: string;
31
47
 
32
48
  private runPromise: Promise<void> | undefined;
49
+ private abortController = new AbortController();
50
+ private epochCheckPromise: RunningPromise | undefined;
33
51
  private deadlineTimeoutHandler: NodeJS.Timeout | undefined;
34
52
 
35
53
  public readonly tracer: Tracer;
36
54
 
37
55
  constructor(
38
- private dbProvider: ForkMerkleTreeOperations,
39
- private epochNumber: bigint,
40
- private blocks: L2Block[],
41
- private txs: Tx[],
56
+ private data: EpochProvingJobData,
57
+ private dbProvider: Pick<ForkMerkleTreeOperations, 'fork'>,
42
58
  private prover: EpochProver,
43
59
  private publicProcessorFactory: PublicProcessorFactory,
44
- private publisher: ProverNodePublisher,
45
- private l2BlockSource: L2BlockSource,
46
- private l1ToL2MessageSource: L1ToL2MessageSource,
47
- private metrics: ProverNodeMetrics,
60
+ private publisher: Pick<ProverNodePublisher, 'submitEpochProof' | 'analyzeEpochProofSubmission'>,
61
+ private l2BlockSource: L2BlockSource | undefined,
62
+ private metrics: ProverNodeJobMetrics,
48
63
  private deadline: Date | undefined,
49
- private config: { parallelBlockLimit: number } = { parallelBlockLimit: 32 },
50
- private cleanUp: (job: EpochProvingJob) => Promise<void> = () => Promise.resolve(),
64
+ private config: EpochProvingJobOptions,
65
+ bindings?: LoggerBindings,
51
66
  ) {
67
+ validateEpochProvingJobData(data);
52
68
  this.uuid = crypto.randomUUID();
53
- this.tracer = metrics.client.getTracer('EpochProvingJob');
69
+ this.log = createLogger('prover-node:epoch-proving-job', {
70
+ ...bindings,
71
+ instanceId: `epoch-${data.epochNumber}`,
72
+ });
73
+ this.tracer = metrics.tracer;
54
74
  }
55
75
 
56
76
  public getId(): string {
@@ -61,27 +81,63 @@ export class EpochProvingJob implements Traceable {
61
81
  return this.state;
62
82
  }
63
83
 
64
- public getEpochNumber(): bigint {
65
- return this.epochNumber;
84
+ public getEpochNumber(): EpochNumber {
85
+ return this.data.epochNumber;
86
+ }
87
+
88
+ public getDeadline(): Date | undefined {
89
+ return this.deadline;
90
+ }
91
+
92
+ public getProvingData(): EpochProvingJobData {
93
+ return this.data;
94
+ }
95
+
96
+ private get epochNumber() {
97
+ return this.data.epochNumber;
98
+ }
99
+
100
+ private get checkpoints() {
101
+ return this.data.checkpoints;
102
+ }
103
+
104
+ private get txs() {
105
+ return this.data.txs;
106
+ }
107
+
108
+ private get attestations() {
109
+ return this.data.attestations;
66
110
  }
67
111
 
68
112
  /**
69
113
  * Proves the given epoch and submits the proof to L1.
70
114
  */
71
115
  @trackSpan('EpochProvingJob.run', function () {
72
- return { [Attributes.EPOCH_NUMBER]: Number(this.epochNumber) };
116
+ return { [Attributes.EPOCH_NUMBER]: this.data.epochNumber };
73
117
  })
74
118
  public async run() {
75
119
  this.scheduleDeadlineStop();
120
+ if (!this.config.skipEpochCheck) {
121
+ await this.scheduleEpochCheck();
122
+ }
76
123
 
77
- const epochNumber = Number(this.epochNumber);
78
- const epochSizeBlocks = this.blocks.length;
79
- const epochSizeTxs = this.blocks.reduce((total, current) => total + current.body.txEffects.length, 0);
80
- const [fromBlock, toBlock] = [this.blocks[0].number, this.blocks.at(-1)!.number];
81
- this.log.info(`Starting epoch ${epochNumber} proving job with blocks ${fromBlock} to ${toBlock}`, {
124
+ const attestations = this.attestations.map(attestation => attestation.toViem());
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}`, {
82
138
  fromBlock,
83
139
  toBlock,
84
- epochSizeBlocks,
140
+ epochSizeTxs,
85
141
  epochNumber,
86
142
  uuid: this.uuid,
87
143
  });
@@ -92,101 +148,281 @@ export class EpochProvingJob implements Traceable {
92
148
  this.runPromise = promise;
93
149
 
94
150
  try {
95
- this.prover.startNewEpoch(epochNumber, fromBlock, epochSizeBlocks);
96
- await this.prover.startTubeCircuits(this.txs);
151
+ const blobTimer = new Timer();
152
+ const blobFieldsPerCheckpoint = this.checkpoints.map(checkpoint => checkpoint.toBlobFields());
153
+ const finalBlobBatchingChallenges = await buildFinalBlobChallenges(blobFieldsPerCheckpoint);
154
+ this.metrics.recordBlobProcessing(blobTimer.ms());
97
155
 
98
- await asyncPool(this.config.parallelBlockLimit, this.blocks, async block => {
99
- this.checkState();
156
+ this.prover.startNewEpoch(epochNumber, epochSizeCheckpoints, finalBlobBatchingChallenges);
157
+ const chonkTimer = new Timer();
158
+ await this.prover.startChonkVerifierCircuits(Array.from(this.txs.values()));
159
+ this.metrics.recordChonkVerifier(chonkTimer.ms());
100
160
 
101
- const globalVariables = block.header.globalVariables;
102
- const txs = await this.getTxs(block);
103
- const l1ToL2Messages = await this.getL1ToL2Messages(block);
104
- const previousHeader = (await this.getBlockHeader(block.number - 1))!;
105
-
106
- this.log.verbose(`Starting processing block ${block.number}`, {
107
- number: block.number,
108
- blockHash: (await block.hash()).toString(),
109
- lastArchive: block.header.lastArchive.root,
110
- noteHashTreeRoot: block.header.state.partial.noteHashTree.root,
111
- nullifierTreeRoot: block.header.state.partial.nullifierTree.root,
112
- publicDataTreeRoot: block.header.state.partial.publicDataTree.root,
113
- previousHeader: previousHeader.hash(),
114
- uuid: this.uuid,
115
- ...globalVariables,
116
- });
161
+ // Everything in the epoch should have the same chainId and version.
162
+ const { chainId, version } = this.checkpoints[0].blocks[0].header.globalVariables;
117
163
 
118
- // Start block proving
119
- await this.prover.startNewBlock(globalVariables, l1ToL2Messages, previousHeader);
120
-
121
- // Process public fns
122
- const db = await this.dbProvider.fork(block.number - 1);
123
- const publicProcessor = this.publicProcessorFactory.create(db, globalVariables, true);
124
- const processed = await this.processTxs(publicProcessor, txs);
125
- await this.prover.addTxs(processed);
126
- await db.close();
127
- this.log.verbose(`Processed all ${txs.length} txs for block ${block.number}`, {
128
- blockNumber: block.number,
129
- blockHash: (await block.hash()).toString(),
164
+ const previousBlockHeaders = this.gatherPreviousBlockHeaders();
165
+
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 => {
175
+ this.checkState();
176
+ const checkpointTimer = new Timer();
177
+
178
+ const checkpointIndex = checkpoint.number - fromCheckpoint;
179
+ const checkpointConstants = CheckpointConstantData.from({
180
+ chainId,
181
+ version,
182
+ vkTreeRoot: getVKTreeRoot(),
183
+ protocolContractsHash: protocolContractsHash,
184
+ proverId: this.prover.getProverId().toField(),
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,
130
199
  uuid: this.uuid,
131
200
  });
132
201
 
133
- // Mark block as completed to pad it
134
- await this.prover.setBlockCompleted(block.number, block.header);
202
+ await this.prover.startNewCheckpoint(
203
+ checkpointIndex,
204
+ checkpointConstants,
205
+ l1ToL2Messages,
206
+ checkpoint.blocks.length,
207
+ previousHeader,
208
+ );
209
+
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
+ });
226
+
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
+ });
257
+
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());
135
264
  });
265
+ this.metrics.recordAllCheckpointsProcessing(allCheckpointsTimer.ms());
136
266
 
137
267
  const executionTime = timer.ms();
138
268
 
139
269
  this.progressState('awaiting-prover');
140
- const { publicInputs, proof } = await this.prover.finaliseEpoch();
141
- this.log.info(`Finalised proof for epoch ${epochNumber}`, { epochNumber, uuid: this.uuid, duration: timer.ms() });
270
+ const { publicInputs, proof, batchedBlobInputs } = await this.prover.finalizeEpoch();
271
+ this.log.info(`Finalized proof for epoch ${epochNumber}`, { epochNumber, uuid: this.uuid, duration: timer.ms() });
142
272
 
143
273
  this.progressState('publishing-proof');
144
- const success = await this.publisher.submitEpochProof({ fromBlock, toBlock, epochNumber, publicInputs, proof });
274
+
275
+ if (this.config.skipSubmitProof) {
276
+ this.log.info(
277
+ `Proof publishing is disabled. Analyzing estimated L1 fees for epoch ${epochNumber} (checkpoints ${fromCheckpoint} to ${toCheckpoint})`,
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
+ }
292
+ this.state = 'completed';
293
+ this.metrics.recordProvingJob(executionTime, timer.ms(), epochSizeCheckpoints, epochSizeBlocks, epochSizeTxs);
294
+ return;
295
+ }
296
+
297
+ const success = await this.publisher.submitEpochProof({
298
+ fromCheckpoint,
299
+ toCheckpoint,
300
+ epochNumber,
301
+ publicInputs,
302
+ proof,
303
+ batchedBlobInputs,
304
+ attestations,
305
+ });
145
306
  if (!success) {
146
307
  throw new Error('Failed to submit epoch proof to L1');
147
308
  }
148
309
 
149
- 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})`, {
150
311
  epochNumber,
151
312
  uuid: this.uuid,
152
313
  });
153
314
  this.state = 'completed';
154
- this.metrics.recordProvingJob(executionTime, timer.ms(), epochSizeBlocks, epochSizeTxs);
315
+ this.metrics.recordProvingJob(executionTime, timer.ms(), epochSizeCheckpoints, epochSizeBlocks, epochSizeTxs);
155
316
  } catch (err: any) {
156
317
  if (err && err.name === 'HaltExecutionError') {
157
- this.log.warn(`Halted execution of epoch ${epochNumber} prover job`, { uuid: this.uuid, epochNumber });
318
+ this.log.warn(`Halted execution of epoch ${epochNumber} prover job`, {
319
+ uuid: this.uuid,
320
+ epochNumber,
321
+ details: err.message,
322
+ });
158
323
  return;
159
324
  }
160
325
  this.log.error(`Error running epoch ${epochNumber} prover job`, err, { uuid: this.uuid, epochNumber });
161
- this.state = 'failed';
326
+ if (this.state === 'processing' || this.state === 'awaiting-prover' || this.state === 'publishing-proof') {
327
+ this.state = 'failed';
328
+ }
162
329
  } finally {
163
330
  clearTimeout(this.deadlineTimeoutHandler);
164
- await this.cleanUp(this);
331
+ await this.epochCheckPromise?.stop();
165
332
  await this.prover.stop();
166
333
  resolve();
167
334
  }
168
335
  }
169
336
 
337
+ /**
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.
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.
342
+ */
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();
359
+ return db;
360
+ }
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
+
170
395
  private progressState(state: EpochProvingJobState) {
171
396
  this.checkState();
172
397
  this.state = state;
173
398
  }
174
399
 
175
400
  private checkState() {
176
- if (this.state === 'timed-out' || this.state === 'stopped' || this.state === 'failed') {
401
+ if (this.state === 'timed-out' || this.state === 'stopped' || this.state === 'failed' || this.state === 'reorg') {
177
402
  throw new HaltExecutionError(this.state);
178
403
  }
179
404
  }
180
405
 
181
- public async stop(state: EpochProvingJobState = 'stopped') {
406
+ public async stop(state: EpochProvingJobTerminalState = 'stopped') {
182
407
  this.state = state;
183
- this.prover.cancel();
184
- // TODO(palla/prover): Stop the publisher as well
408
+ this.interruptProcessing();
185
409
  if (this.runPromise) {
186
410
  await this.runPromise;
187
411
  }
188
412
  }
189
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
+
190
426
  private scheduleDeadlineStop() {
191
427
  const deadline = this.deadline;
192
428
  if (deadline) {
@@ -207,33 +443,73 @@ export class EpochProvingJob implements Traceable {
207
443
  }
208
444
  }
209
445
 
210
- /* Returns the header for the given block number, or the genesis block for block zero. */
211
- private async getBlockHeader(blockNumber: number) {
212
- if (blockNumber === 0) {
213
- return (await this.dbProvider.fork()).getInitialHeader();
446
+ /**
447
+ * Kicks off a running promise that queries the archiver for the set of L2 blocks of the current epoch.
448
+ * If those change, stops the proving job with a `rerun` state, so the node re-enqueues it.
449
+ */
450
+ private async scheduleEpochCheck() {
451
+ const l2BlockSource = this.l2BlockSource;
452
+ if (!l2BlockSource) {
453
+ this.log.warn(`No L2 block source available, skipping epoch check`);
454
+ return;
214
455
  }
215
- return this.l2BlockSource.getBlockHeader(blockNumber);
456
+
457
+ const intervalMs = Math.ceil((await l2BlockSource.getL1Constants()).ethereumSlotDuration / 2) * 1000;
458
+ this.epochCheckPromise = new RunningPromise(
459
+ async () => {
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()));
466
+ if (
467
+ blockHeaders.length !== thisBlocks.length ||
468
+ !blockHashes.every((block, i) => block.equals(thisBlockHashes[i]))
469
+ ) {
470
+ this.log.warn('Epoch blocks changed underfoot', {
471
+ uuid: this.uuid,
472
+ epochNumber: this.epochNumber,
473
+ oldBlockHashes: thisBlockHashes,
474
+ newBlockHashes: blockHashes,
475
+ });
476
+ void this.stop('reorg');
477
+ }
478
+ },
479
+ this.log,
480
+ intervalMs,
481
+ ).start();
482
+ this.log.verbose(`Scheduled epoch check for epoch ${this.epochNumber} every ${intervalMs}ms`);
216
483
  }
217
484
 
218
- private async getTxs(block: L2Block): Promise<Tx[]> {
219
- const txHashes = block.body.txEffects.map(tx => tx.txHash.toBigInt());
220
- const txsAndHashes = await Promise.all(this.txs.map(async tx => ({ tx, hash: await tx.getTxHash() })));
221
- return txsAndHashes
222
- .filter(txAndHash => txHashes.includes(txAndHash.hash.toBigInt()))
223
- .map(txAndHash => txAndHash.tx);
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)];
224
489
  }
225
490
 
226
- private getL1ToL2Messages(block: L2Block) {
227
- return this.l1ToL2MessageSource.getL1ToL2Messages(BigInt(block.number));
491
+ private getTxs(block: L2Block): Tx[] {
492
+ return block.body.txEffects.map(txEffect => this.txs.get(txEffect.txHash.toString())!);
493
+ }
494
+
495
+ private getL1ToL2Messages(checkpoint: Checkpoint) {
496
+ return this.data.l1ToL2Messages[checkpoint.number];
228
497
  }
229
498
 
230
499
  private async processTxs(publicProcessor: PublicProcessor, txs: Tx[]): Promise<ProcessedTx[]> {
231
500
  const { deadline } = this;
232
- 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();
233
506
 
234
507
  if (failedTxs.length) {
508
+ const failedTxHashes = await Promise.all(failedTxs.map(({ tx }) => tx.getTxHash()));
235
509
  throw new Error(
236
- `Txs failed processing: ${failedTxs.map(({ tx, error }) => `${tx.getTxHash()} (${error})`).join(', ')}`,
510
+ `Txs failed processing: ${failedTxs
511
+ .map(({ error }, index) => `${failedTxHashes[index]} (${error})`)
512
+ .join(', ')}`,
237
513
  );
238
514
  }
239
515
 
@@ -246,7 +522,7 @@ export class EpochProvingJob implements Traceable {
246
522
  }
247
523
 
248
524
  class HaltExecutionError extends Error {
249
- constructor(state: EpochProvingJobState) {
525
+ constructor(public readonly state: EpochProvingJobState) {
250
526
  super(`Halted execution due to state ${state}`);
251
527
  this.name = 'HaltExecutionError';
252
528
  }