1
1
import { asyncPool } from '@aztec/foundation/async-pool' ;
2
2
import { createLogger } from '@aztec/foundation/log' ;
3
- import { promiseWithResolvers } from '@aztec/foundation/promise' ;
3
+ import { RunningPromise , promiseWithResolvers } from '@aztec/foundation/promise' ;
4
4
import { Timer } from '@aztec/foundation/timer' ;
5
5
import type { PublicProcessor , PublicProcessorFactory } from '@aztec/simulator/server' ;
6
6
import type { L2Block , L2BlockSource } from '@aztec/stdlib/block' ;
@@ -30,6 +30,7 @@ export class EpochProvingJob implements Traceable {
30
30
private uuid : string ;
31
31
32
32
private runPromise : Promise < void > | undefined ;
33
+ private epochCheckPromise : RunningPromise | undefined ;
33
34
private deadlineTimeoutHandler : NodeJS . Timeout | undefined ;
34
35
35
36
public readonly tracer : Tracer ;
@@ -47,7 +48,6 @@ export class EpochProvingJob implements Traceable {
47
48
private metrics : ProverNodeMetrics ,
48
49
private deadline : Date | undefined ,
49
50
private config : { parallelBlockLimit : number } = { parallelBlockLimit : 32 } ,
50
- private cleanUp : ( job : EpochProvingJob ) => Promise < void > = ( ) => Promise . resolve ( ) ,
51
51
) {
52
52
this . uuid = crypto . randomUUID ( ) ;
53
53
this . tracer = metrics . client . getTracer ( 'EpochProvingJob' ) ;
@@ -73,6 +73,7 @@ export class EpochProvingJob implements Traceable {
73
73
} )
74
74
public async run ( ) {
75
75
this . scheduleDeadlineStop ( ) ;
76
+ await this . scheduleEpochCheck ( ) ;
76
77
77
78
const epochNumber = Number ( this . epochNumber ) ;
78
79
const epochSizeBlocks = this . blocks . length ;
@@ -154,14 +155,18 @@ export class EpochProvingJob implements Traceable {
154
155
this . metrics . recordProvingJob ( executionTime , timer . ms ( ) , epochSizeBlocks , epochSizeTxs ) ;
155
156
} catch ( err : any ) {
156
157
if ( err && err . name === 'HaltExecutionError' ) {
157
- this . log . warn ( `Halted execution of epoch ${ epochNumber } prover job` , { uuid : this . uuid , epochNumber } ) ;
158
+ this . log . warn ( `Halted execution of epoch ${ epochNumber } prover job` , {
159
+ uuid : this . uuid ,
160
+ epochNumber,
161
+ details : err . message ,
162
+ } ) ;
158
163
return ;
159
164
}
160
165
this . log . error ( `Error running epoch ${ epochNumber } prover job` , err , { uuid : this . uuid , epochNumber } ) ;
161
166
this . state = 'failed' ;
162
167
} finally {
163
168
clearTimeout ( this . deadlineTimeoutHandler ) ;
164
- await this . cleanUp ( this ) ;
169
+ await this . epochCheckPromise ?. stop ( ) ;
165
170
await this . prover . stop ( ) ;
166
171
resolve ( ) ;
167
172
}
@@ -173,12 +178,12 @@ export class EpochProvingJob implements Traceable {
173
178
}
174
179
175
180
private checkState ( ) {
176
- if ( this . state === 'timed-out' || this . state === 'stopped' || this . state === 'failed' ) {
181
+ if ( this . state === 'timed-out' || this . state === 'stopped' || this . state === 'failed' || this . state === 'reorg' ) {
177
182
throw new HaltExecutionError ( this . state ) ;
178
183
}
179
184
}
180
185
181
- public async stop ( state : EpochProvingJobState = 'stopped' ) {
186
+ public async stop ( state : EpochProvingJobTerminalState = 'stopped' ) {
182
187
this . state = state ;
183
188
this . prover . cancel ( ) ;
184
189
// TODO(palla/prover): Stop the publisher as well
@@ -207,6 +212,36 @@ export class EpochProvingJob implements Traceable {
207
212
}
208
213
}
209
214
215
+ /**
216
+ * Kicks off a running promise that queries the archiver for the set of L2 blocks of the current epoch.
217
+ * If those changed, stops the proving job with a `rerun` state, so the node re-enqueues it.
218
+ */
219
+ private async scheduleEpochCheck ( ) {
220
+ const intervalMs = Math . ceil ( ( await this . l2BlockSource . getL1Constants ( ) ) . ethereumSlotDuration / 2 ) * 1000 ;
221
+ this . epochCheckPromise = new RunningPromise (
222
+ async ( ) => {
223
+ const blocks = await this . l2BlockSource . getBlockHeadersForEpoch ( this . epochNumber ) ;
224
+ const blockHashes = await Promise . all ( blocks . map ( block => block . hash ( ) ) ) ;
225
+ const thisBlockHashes = await Promise . all ( this . blocks . map ( block => block . hash ( ) ) ) ;
226
+ if (
227
+ blocks . length !== this . blocks . length ||
228
+ ! blockHashes . every ( ( block , i ) => block . equals ( thisBlockHashes [ i ] ) )
229
+ ) {
230
+ this . log . warn ( 'Epoch blocks changed underfoot' , {
231
+ uuid : this . uuid ,
232
+ epochNumber : this . epochNumber ,
233
+ oldBlockHashes : thisBlockHashes ,
234
+ newBlockHashes : blockHashes ,
235
+ } ) ;
236
+ void this . stop ( 'reorg' ) ;
237
+ }
238
+ } ,
239
+ this . log ,
240
+ intervalMs ,
241
+ ) . start ( ) ;
242
+ this . log . verbose ( `Scheduled epoch check for epoch ${ this . epochNumber } every ${ intervalMs } ms` ) ;
243
+ }
244
+
210
245
/* Returns the header for the given block number, or the genesis block for block zero. */
211
246
private async getBlockHeader ( blockNumber : number ) {
212
247
if ( blockNumber === 0 ) {
@@ -249,7 +284,7 @@ export class EpochProvingJob implements Traceable {
249
284
}
250
285
251
286
class HaltExecutionError extends Error {
252
- constructor ( state : EpochProvingJobState ) {
287
+ constructor ( public readonly state : EpochProvingJobState ) {
253
288
super ( `Halted execution due to state ${ state } ` ) ;
254
289
this . name = 'HaltExecutionError' ;
255
290
}
0 commit comments