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

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 (81) 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 +11 -0
  8. package/dest/actions/rerun-epoch-proving-job.d.ts.map +1 -0
  9. package/dest/actions/rerun-epoch-proving-job.js +40 -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 +67 -0
  16. package/dest/config.d.ts +12 -9
  17. package/dest/config.d.ts.map +1 -1
  18. package/dest/config.js +81 -14
  19. package/dest/factory.d.ts +12 -8
  20. package/dest/factory.d.ts.map +1 -1
  21. package/dest/factory.js +95 -31
  22. package/dest/index.d.ts +1 -1
  23. package/dest/index.d.ts.map +1 -1
  24. package/dest/index.js +1 -1
  25. package/dest/job/epoch-proving-job-data.d.ts +16 -0
  26. package/dest/job/epoch-proving-job-data.d.ts.map +1 -0
  27. package/dest/job/epoch-proving-job-data.js +52 -0
  28. package/dest/job/epoch-proving-job.d.ts +30 -15
  29. package/dest/job/epoch-proving-job.d.ts.map +1 -1
  30. package/dest/job/epoch-proving-job.js +149 -50
  31. package/dest/metrics.d.ts +28 -4
  32. package/dest/metrics.d.ts.map +1 -1
  33. package/dest/metrics.js +141 -35
  34. package/dest/monitors/epoch-monitor.d.ts +3 -1
  35. package/dest/monitors/epoch-monitor.d.ts.map +1 -1
  36. package/dest/monitors/epoch-monitor.js +15 -2
  37. package/dest/prover-node-publisher.d.ts +7 -10
  38. package/dest/prover-node-publisher.d.ts.map +1 -1
  39. package/dest/prover-node-publisher.js +59 -60
  40. package/dest/prover-node.d.ts +43 -39
  41. package/dest/prover-node.d.ts.map +1 -1
  42. package/dest/prover-node.js +171 -100
  43. package/dest/prover-publisher-factory.d.ts +21 -0
  44. package/dest/prover-publisher-factory.d.ts.map +1 -0
  45. package/dest/prover-publisher-factory.js +26 -0
  46. package/dest/test/index.d.ts +4 -2
  47. package/dest/test/index.d.ts.map +1 -1
  48. package/dest/test/index.js +1 -3
  49. package/package.json +36 -31
  50. package/src/actions/download-epoch-proving-job.ts +44 -0
  51. package/src/actions/index.ts +2 -0
  52. package/src/actions/rerun-epoch-proving-job.ts +61 -0
  53. package/src/actions/upload-epoch-proof-failure.ts +88 -0
  54. package/src/bin/run-failed-epoch.ts +77 -0
  55. package/src/config.ts +108 -24
  56. package/src/factory.ts +161 -43
  57. package/src/index.ts +1 -1
  58. package/src/job/epoch-proving-job-data.ts +76 -0
  59. package/src/job/epoch-proving-job.ts +215 -50
  60. package/src/metrics.ts +135 -37
  61. package/src/monitors/epoch-monitor.ts +16 -5
  62. package/src/prover-node-publisher.ts +93 -86
  63. package/src/prover-node.ts +203 -126
  64. package/src/prover-publisher-factory.ts +37 -0
  65. package/src/test/index.ts +7 -4
  66. package/dest/http.d.ts +0 -8
  67. package/dest/http.d.ts.map +0 -1
  68. package/dest/http.js +0 -9
  69. package/dest/prover-coordination/config.d.ts +0 -7
  70. package/dest/prover-coordination/config.d.ts.map +0 -1
  71. package/dest/prover-coordination/config.js +0 -11
  72. package/dest/prover-coordination/factory.d.ts +0 -22
  73. package/dest/prover-coordination/factory.d.ts.map +0 -1
  74. package/dest/prover-coordination/factory.js +0 -42
  75. package/dest/prover-coordination/index.d.ts +0 -3
  76. package/dest/prover-coordination/index.d.ts.map +0 -1
  77. package/dest/prover-coordination/index.js +0 -2
  78. package/src/http.ts +0 -13
  79. package/src/prover-coordination/config.ts +0 -17
  80. package/src/prover-coordination/factory.ts +0 -72
  81. package/src/prover-coordination/index.ts +0 -2
package/src/index.ts CHANGED
@@ -1,5 +1,5 @@
1
+ export * from './actions/index.js';
1
2
  export * from './config.js';
2
3
  export * from './factory.js';
3
- export * from './http.js';
4
4
  export * from './prover-node-publisher.js';
5
5
  export * from './prover-node.js';
@@ -0,0 +1,76 @@
1
+ import { Fr } from '@aztec/foundation/fields';
2
+ import { BufferReader, serializeToBuffer } from '@aztec/foundation/serialize';
3
+ import { CommitteeAttestation, L2Block } from '@aztec/stdlib/block';
4
+ import { BlockHeader, Tx } from '@aztec/stdlib/tx';
5
+
6
+ /** All data from an epoch used in proving. */
7
+ export type EpochProvingJobData = {
8
+ epochNumber: bigint;
9
+ blocks: L2Block[];
10
+ txs: Map<string, Tx>;
11
+ l1ToL2Messages: Record<number, Fr[]>;
12
+ previousBlockHeader: BlockHeader;
13
+ attestations: CommitteeAttestation[];
14
+ };
15
+
16
+ export function validateEpochProvingJobData(data: EpochProvingJobData) {
17
+ if (data.blocks.length > 0 && data.previousBlockHeader.getBlockNumber() + 1 !== data.blocks[0].number) {
18
+ throw new Error(
19
+ `Initial block number ${
20
+ data.blocks[0].number
21
+ } does not match previous block header ${data.previousBlockHeader.getBlockNumber()}`,
22
+ );
23
+ }
24
+
25
+ for (const blockNumber of data.blocks.map(block => block.number)) {
26
+ if (!(blockNumber in data.l1ToL2Messages)) {
27
+ throw new Error(`Missing L1 to L2 messages for block number ${blockNumber}`);
28
+ }
29
+ }
30
+ }
31
+
32
+ export function serializeEpochProvingJobData(data: EpochProvingJobData): Buffer {
33
+ const blocks = data.blocks.map(block => block.toBuffer());
34
+ const txs = Array.from(data.txs.values()).map(tx => tx.toBuffer());
35
+ const l1ToL2Messages = Object.entries(data.l1ToL2Messages).map(([blockNumber, messages]) => [
36
+ Number(blockNumber),
37
+ messages.length,
38
+ ...messages,
39
+ ]);
40
+ const attestations = data.attestations.map(attestation => attestation.toBuffer());
41
+
42
+ return serializeToBuffer(
43
+ Number(data.epochNumber),
44
+ data.previousBlockHeader,
45
+ blocks.length,
46
+ ...blocks,
47
+ txs.length,
48
+ ...txs,
49
+ l1ToL2Messages.length,
50
+ ...l1ToL2Messages,
51
+ attestations.length,
52
+ ...attestations,
53
+ );
54
+ }
55
+
56
+ export function deserializeEpochProvingJobData(buf: Buffer): EpochProvingJobData {
57
+ const reader = BufferReader.asReader(buf);
58
+ const epochNumber = BigInt(reader.readNumber());
59
+ const previousBlockHeader = reader.readObject(BlockHeader);
60
+ const blocks = reader.readVector(L2Block);
61
+ const txArray = reader.readVector(Tx);
62
+
63
+ const l1ToL2MessageBlockCount = reader.readNumber();
64
+ const l1ToL2Messages: Record<number, Fr[]> = {};
65
+ for (let i = 0; i < l1ToL2MessageBlockCount; i++) {
66
+ const blockNumber = reader.readNumber();
67
+ const messages = reader.readVector(Fr);
68
+ l1ToL2Messages[blockNumber] = messages;
69
+ }
70
+
71
+ const attestations = reader.readVector(CommitteeAttestation);
72
+
73
+ const txs = new Map<string, Tx>(txArray.map(tx => [tx.getTxHash().toString(), tx]));
74
+
75
+ return { epochNumber, previousBlockHeader, blocks, txs, l1ToL2Messages, attestations };
76
+ }
@@ -1,7 +1,13 @@
1
+ import { NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP } from '@aztec/constants';
1
2
  import { asyncPool } from '@aztec/foundation/async-pool';
3
+ import { padArrayEnd } from '@aztec/foundation/collection';
4
+ import { Fr } from '@aztec/foundation/fields';
2
5
  import { createLogger } from '@aztec/foundation/log';
3
- import { promiseWithResolvers } from '@aztec/foundation/promise';
6
+ import { RunningPromise, promiseWithResolvers } from '@aztec/foundation/promise';
4
7
  import { Timer } from '@aztec/foundation/timer';
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';
6
12
  import type { L2Block, L2BlockSource } from '@aztec/stdlib/block';
7
13
  import {
@@ -10,17 +16,25 @@ import {
10
16
  EpochProvingJobTerminalState,
11
17
  type ForkMerkleTreeOperations,
12
18
  } from '@aztec/stdlib/interfaces/server';
13
- import type { L1ToL2MessageSource } from '@aztec/stdlib/messaging';
19
+ import { CheckpointConstantData } from '@aztec/stdlib/rollup';
20
+ import { MerkleTreeId } from '@aztec/stdlib/trees';
14
21
  import type { ProcessedTx, Tx } from '@aztec/stdlib/tx';
15
22
  import { Attributes, type Traceable, type Tracer, trackSpan } from '@aztec/telemetry-client';
16
23
 
17
24
  import * as crypto from 'node:crypto';
18
25
 
19
- import type { ProverNodeMetrics } from '../metrics.js';
26
+ import type { ProverNodeJobMetrics } from '../metrics.js';
20
27
  import type { ProverNodePublisher } from '../prover-node-publisher.js';
28
+ import { type EpochProvingJobData, validateEpochProvingJobData } from './epoch-proving-job-data.js';
29
+
30
+ export type EpochProvingJobOptions = {
31
+ parallelBlockLimit?: number;
32
+ skipEpochCheck?: boolean;
33
+ skipSubmitProof?: boolean;
34
+ };
21
35
 
22
36
  /**
23
- * Job that grabs a range of blocks from the unfinalised chain from L1, gets their txs given their hashes,
37
+ * Job that grabs a range of blocks from the unfinalized chain from L1, gets their txs given their hashes,
24
38
  * re-executes their public calls, generates a rollup proof, and submits it to L1. This job will update the
25
39
  * world state as part of public call execution via the public processor.
26
40
  */
@@ -30,27 +44,25 @@ export class EpochProvingJob implements Traceable {
30
44
  private uuid: string;
31
45
 
32
46
  private runPromise: Promise<void> | undefined;
47
+ private epochCheckPromise: RunningPromise | undefined;
33
48
  private deadlineTimeoutHandler: NodeJS.Timeout | undefined;
34
49
 
35
50
  public readonly tracer: Tracer;
36
51
 
37
52
  constructor(
38
- private dbProvider: ForkMerkleTreeOperations,
39
- private epochNumber: bigint,
40
- private blocks: L2Block[],
41
- private txs: Tx[],
53
+ private data: EpochProvingJobData,
54
+ private dbProvider: Pick<ForkMerkleTreeOperations, 'fork'>,
42
55
  private prover: EpochProver,
43
56
  private publicProcessorFactory: PublicProcessorFactory,
44
- private publisher: ProverNodePublisher,
45
- private l2BlockSource: L2BlockSource,
46
- private l1ToL2MessageSource: L1ToL2MessageSource,
47
- private metrics: ProverNodeMetrics,
57
+ private publisher: Pick<ProverNodePublisher, 'submitEpochProof'>,
58
+ private l2BlockSource: L2BlockSource | undefined,
59
+ private metrics: ProverNodeJobMetrics,
48
60
  private deadline: Date | undefined,
49
- private config: { parallelBlockLimit: number } = { parallelBlockLimit: 32 },
50
- private cleanUp: (job: EpochProvingJob) => Promise<void> = () => Promise.resolve(),
61
+ private config: EpochProvingJobOptions,
51
62
  ) {
63
+ validateEpochProvingJobData(data);
52
64
  this.uuid = crypto.randomUUID();
53
- this.tracer = metrics.client.getTracer('EpochProvingJob');
65
+ this.tracer = metrics.tracer;
54
66
  }
55
67
 
56
68
  public getId(): string {
@@ -62,18 +74,46 @@ export class EpochProvingJob implements Traceable {
62
74
  }
63
75
 
64
76
  public getEpochNumber(): bigint {
65
- return this.epochNumber;
77
+ return this.data.epochNumber;
78
+ }
79
+
80
+ public getDeadline(): Date | undefined {
81
+ return this.deadline;
82
+ }
83
+
84
+ public getProvingData(): EpochProvingJobData {
85
+ return this.data;
86
+ }
87
+
88
+ private get epochNumber() {
89
+ return this.data.epochNumber;
90
+ }
91
+
92
+ private get blocks() {
93
+ return this.data.blocks;
94
+ }
95
+
96
+ private get txs() {
97
+ return this.data.txs;
98
+ }
99
+
100
+ private get attestations() {
101
+ return this.data.attestations;
66
102
  }
67
103
 
68
104
  /**
69
105
  * Proves the given epoch and submits the proof to L1.
70
106
  */
71
107
  @trackSpan('EpochProvingJob.run', function () {
72
- return { [Attributes.EPOCH_NUMBER]: Number(this.epochNumber) };
108
+ return { [Attributes.EPOCH_NUMBER]: Number(this.data.epochNumber) };
73
109
  })
74
110
  public async run() {
75
111
  this.scheduleDeadlineStop();
112
+ if (!this.config.skipEpochCheck) {
113
+ await this.scheduleEpochCheck();
114
+ }
76
115
 
116
+ const attestations = this.attestations.map(attestation => attestation.toViem());
77
117
  const epochNumber = Number(this.epochNumber);
78
118
  const epochSizeBlocks = this.blocks.length;
79
119
  const epochSizeTxs = this.blocks.reduce((total, current) => total + current.body.txEffects.length, 0);
@@ -92,16 +132,23 @@ export class EpochProvingJob implements Traceable {
92
132
  this.runPromise = promise;
93
133
 
94
134
  try {
95
- this.prover.startNewEpoch(epochNumber, fromBlock, epochSizeBlocks);
96
- await this.prover.startTubeCircuits(this.txs);
135
+ const blobFieldsPerCheckpoint = this.blocks.map(block => block.getCheckpointBlobFields());
136
+ const finalBlobBatchingChallenges = await buildFinalBlobChallenges(blobFieldsPerCheckpoint);
97
137
 
98
- await asyncPool(this.config.parallelBlockLimit, this.blocks, async block => {
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);
143
+ await this.prover.startChonkVerifierCircuits(Array.from(this.txs.values()));
144
+
145
+ await asyncPool(this.config.parallelBlockLimit ?? 32, this.blocks, async block => {
99
146
  this.checkState();
100
147
 
101
148
  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))!;
149
+ const txs = this.getTxs(block);
150
+ const l1ToL2Messages = this.getL1ToL2Messages(block);
151
+ const previousHeader = this.getBlockHeader(block.number - 1)!;
105
152
 
106
153
  this.log.verbose(`Starting processing block ${block.number}`, {
107
154
  number: block.number,
@@ -115,12 +162,41 @@ export class EpochProvingJob implements Traceable {
115
162
  ...globalVariables,
116
163
  });
117
164
 
165
+ const checkpointConstants = CheckpointConstantData.from({
166
+ chainId: globalVariables.chainId,
167
+ version: globalVariables.version,
168
+ vkTreeRoot: getVKTreeRoot(),
169
+ protocolContractsHash: protocolContractsHash,
170
+ proverId: this.prover.getProverId().toField(),
171
+ slotNumber: globalVariables.slotNumber,
172
+ coinbase: globalVariables.coinbase,
173
+ feeRecipient: globalVariables.feeRecipient,
174
+ gasFees: globalVariables.gasFees,
175
+ });
176
+
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
+ await this.prover.startNewCheckpoint(
182
+ checkpointIndex,
183
+ checkpointConstants,
184
+ l1ToL2Messages,
185
+ totalNumBlocks,
186
+ blobFieldsPerCheckpoint[checkpointIndex].length,
187
+ previousHeader,
188
+ );
189
+
118
190
  // Start block proving
119
- await this.prover.startNewBlock(globalVariables, l1ToL2Messages, previousHeader);
191
+ await this.prover.startNewBlock(block.number, globalVariables.timestamp, txs.length);
120
192
 
121
193
  // Process public fns
122
- const db = await this.dbProvider.fork(block.number - 1);
123
- const publicProcessor = this.publicProcessorFactory.create(db, globalVariables, true);
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
+ });
124
200
  const processed = await this.processTxs(publicProcessor, txs);
125
201
  await this.prover.addTxs(processed);
126
202
  await db.close();
@@ -131,17 +207,36 @@ export class EpochProvingJob implements Traceable {
131
207
  });
132
208
 
133
209
  // Mark block as completed to pad it
134
- await this.prover.setBlockCompleted(block.number, block.header);
210
+ const expectedBlockHeader = block.getBlockHeader();
211
+ await this.prover.setBlockCompleted(block.number, expectedBlockHeader);
135
212
  });
136
213
 
137
214
  const executionTime = timer.ms();
138
215
 
139
216
  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() });
217
+ const { publicInputs, proof, batchedBlobInputs } = await this.prover.finalizeEpoch();
218
+ this.log.info(`Finalized proof for epoch ${epochNumber}`, { epochNumber, uuid: this.uuid, duration: timer.ms() });
142
219
 
143
220
  this.progressState('publishing-proof');
144
- const success = await this.publisher.submitEpochProof({ fromBlock, toBlock, epochNumber, publicInputs, proof });
221
+
222
+ if (this.config.skipSubmitProof) {
223
+ this.log.info(
224
+ `Proof publishing is disabled. Dropping valid proof for epoch ${epochNumber} (blocks ${fromBlock} to ${toBlock})`,
225
+ );
226
+ this.state = 'completed';
227
+ this.metrics.recordProvingJob(executionTime, timer.ms(), epochSizeBlocks, epochSizeTxs);
228
+ return;
229
+ }
230
+
231
+ const success = await this.publisher.submitEpochProof({
232
+ fromBlock,
233
+ toBlock,
234
+ epochNumber,
235
+ publicInputs,
236
+ proof,
237
+ batchedBlobInputs,
238
+ attestations,
239
+ });
145
240
  if (!success) {
146
241
  throw new Error('Failed to submit epoch proof to L1');
147
242
  }
@@ -154,34 +249,59 @@ export class EpochProvingJob implements Traceable {
154
249
  this.metrics.recordProvingJob(executionTime, timer.ms(), epochSizeBlocks, epochSizeTxs);
155
250
  } catch (err: any) {
156
251
  if (err && err.name === 'HaltExecutionError') {
157
- this.log.warn(`Halted execution of epoch ${epochNumber} prover job`, { uuid: this.uuid, epochNumber });
252
+ this.log.warn(`Halted execution of epoch ${epochNumber} prover job`, {
253
+ uuid: this.uuid,
254
+ epochNumber,
255
+ details: err.message,
256
+ });
158
257
  return;
159
258
  }
160
259
  this.log.error(`Error running epoch ${epochNumber} prover job`, err, { uuid: this.uuid, epochNumber });
161
- this.state = 'failed';
260
+ if (this.state === 'processing' || this.state === 'awaiting-prover' || this.state === 'publishing-proof') {
261
+ this.state = 'failed';
262
+ }
162
263
  } finally {
163
264
  clearTimeout(this.deadlineTimeoutHandler);
164
- await this.cleanUp(this);
265
+ await this.epochCheckPromise?.stop();
165
266
  await this.prover.stop();
166
267
  resolve();
167
268
  }
168
269
  }
169
270
 
271
+ /**
272
+ * Create a new db fork for tx processing, inserting all L1 to L2.
273
+ * 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
+ */
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);
288
+ return db;
289
+ }
290
+
170
291
  private progressState(state: EpochProvingJobState) {
171
292
  this.checkState();
172
293
  this.state = state;
173
294
  }
174
295
 
175
296
  private checkState() {
176
- if (this.state === 'timed-out' || this.state === 'stopped' || this.state === 'failed') {
297
+ if (this.state === 'timed-out' || this.state === 'stopped' || this.state === 'failed' || this.state === 'reorg') {
177
298
  throw new HaltExecutionError(this.state);
178
299
  }
179
300
  }
180
301
 
181
- public async stop(state: EpochProvingJobState = 'stopped') {
302
+ public async stop(state: EpochProvingJobTerminalState = 'stopped') {
182
303
  this.state = state;
183
304
  this.prover.cancel();
184
- // TODO(palla/prover): Stop the publisher as well
185
305
  if (this.runPromise) {
186
306
  await this.runPromise;
187
307
  }
@@ -207,24 +327,66 @@ export class EpochProvingJob implements Traceable {
207
327
  }
208
328
  }
209
329
 
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();
330
+ /**
331
+ * Kicks off a running promise that queries the archiver for the set of L2 blocks of the current epoch.
332
+ * If those change, stops the proving job with a `rerun` state, so the node re-enqueues it.
333
+ */
334
+ private async scheduleEpochCheck() {
335
+ const l2BlockSource = this.l2BlockSource;
336
+ if (!l2BlockSource) {
337
+ this.log.warn(`No L2 block source available, skipping epoch check`);
338
+ return;
214
339
  }
215
- return this.l2BlockSource.getBlockHeader(blockNumber);
340
+
341
+ const intervalMs = Math.ceil((await l2BlockSource.getL1Constants()).ethereumSlotDuration / 2) * 1000;
342
+ this.epochCheckPromise = new RunningPromise(
343
+ 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()));
347
+ if (
348
+ blocks.length !== this.blocks.length ||
349
+ !blockHashes.every((block, i) => block.equals(thisBlockHashes[i]))
350
+ ) {
351
+ this.log.warn('Epoch blocks changed underfoot', {
352
+ uuid: this.uuid,
353
+ epochNumber: this.epochNumber,
354
+ oldBlockHashes: thisBlockHashes,
355
+ newBlockHashes: blockHashes,
356
+ });
357
+ void this.stop('reorg');
358
+ }
359
+ },
360
+ this.log,
361
+ intervalMs,
362
+ ).start();
363
+ this.log.verbose(`Scheduled epoch check for epoch ${this.epochNumber} every ${intervalMs}ms`);
364
+ }
365
+
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
+ );
216
382
  }
217
383
 
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);
384
+ private getTxs(block: L2Block): Tx[] {
385
+ return block.body.txEffects.map(txEffect => this.txs.get(txEffect.txHash.toString())!);
224
386
  }
225
387
 
226
388
  private getL1ToL2Messages(block: L2Block) {
227
- return this.l1ToL2MessageSource.getL1ToL2Messages(BigInt(block.number));
389
+ return this.data.l1ToL2Messages[block.number];
228
390
  }
229
391
 
230
392
  private async processTxs(publicProcessor: PublicProcessor, txs: Tx[]): Promise<ProcessedTx[]> {
@@ -232,8 +394,11 @@ export class EpochProvingJob implements Traceable {
232
394
  const [processedTxs, failedTxs] = await publicProcessor.process(txs, { deadline });
233
395
 
234
396
  if (failedTxs.length) {
397
+ const failedTxHashes = await Promise.all(failedTxs.map(({ tx }) => tx.getTxHash()));
235
398
  throw new Error(
236
- `Txs failed processing: ${failedTxs.map(({ tx, error }) => `${tx.getTxHash()} (${error})`).join(', ')}`,
399
+ `Txs failed processing: ${failedTxs
400
+ .map(({ error }, index) => `${failedTxHashes[index]} (${error})`)
401
+ .join(', ')}`,
237
402
  );
238
403
  }
239
404
 
@@ -246,7 +411,7 @@ export class EpochProvingJob implements Traceable {
246
411
  }
247
412
 
248
413
  class HaltExecutionError extends Error {
249
- constructor(state: EpochProvingJobState) {
414
+ constructor(public readonly state: EpochProvingJobState) {
250
415
  super(`Halted execution due to state ${state}`);
251
416
  this.name = 'HaltExecutionError';
252
417
  }