@aztec/bb-prover 0.0.1-commit.2f68f620 → 0.0.1-commit.3100065

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.
@@ -9,8 +9,8 @@ import type { Tx } from '@aztec/stdlib/tx';
9
9
 
10
10
  import { Unpackr } from 'msgpackr';
11
11
  import { execFile } from 'node:child_process';
12
- import { rmSync } from 'node:fs';
13
- import { mkdtemp, rm } from 'node:fs/promises';
12
+ import { unlinkSync } from 'node:fs';
13
+ import { unlink } from 'node:fs/promises';
14
14
  import * as os from 'node:os';
15
15
  import * as path from 'node:path';
16
16
  import { promisify } from 'node:util';
@@ -18,8 +18,6 @@ import { promisify } from 'node:util';
18
18
  import type { BBConfig } from '../config.js';
19
19
 
20
20
  const execFileAsync = promisify(execFile);
21
- const RESULT_TIMEOUT_MS = 5 * 60 * 1000;
22
- const STOP_DRAIN_TIMEOUT_MS = 5_000;
23
21
 
24
22
  /** Result from the FIFO, matching the C++ VerifyResult struct. */
25
23
  interface FifoVerifyResult {
@@ -36,12 +34,11 @@ interface PendingRequest {
36
34
  resolve: (result: IVCProofVerificationResult) => void;
37
35
  reject: (error: Error) => void;
38
36
  totalTimer: Timer;
39
- timeout: ReturnType<typeof setTimeout>;
40
37
  }
41
38
 
42
39
  /**
43
40
  * Batch verifier for Chonk IVC proofs. Uses the bb batch verifier service
44
- * which batches IPA verification into a constant number of SRS MSMs for better throughput.
41
+ * which batches IPA verification into a single SRS MSM for better throughput.
45
42
  *
46
43
  * Architecture:
47
44
  * - Spawns a persistent `bb msgpack run` process via Barretenberg (native backend)
@@ -51,8 +48,7 @@ interface PendingRequest {
51
48
  */
52
49
  export class BatchChonkVerifier implements ClientProtocolCircuitVerifier {
53
50
  private bb!: Barretenberg;
54
- private fifoDir: string | undefined;
55
- private fifoPath = '';
51
+ private fifoPath: string;
56
52
  private nextRequestId = 0;
57
53
  private pendingRequests = new Map<number, PendingRequest>();
58
54
  private sendQueue: SerialQueue;
@@ -62,9 +58,6 @@ export class BatchChonkVerifier implements ClientProtocolCircuitVerifier {
62
58
  private vkIndexMap = new Map<string, number>();
63
59
  /** Bound cleanup handler for process exit signals. */
64
60
  private exitCleanup: (() => void) | null = null;
65
- private stopped = false;
66
- private fatalError: Error | undefined;
67
- private pendingDrainedResolvers = new Set<() => void>();
68
61
 
69
62
  private constructor(
70
63
  private config: Pick<BBConfig, 'bbChonkVerifyConcurrency'> & Partial<Pick<BBConfig, 'bbBinaryPath'>>,
@@ -72,6 +65,7 @@ export class BatchChonkVerifier implements ClientProtocolCircuitVerifier {
72
65
  private batchSize: number,
73
66
  private label: string,
74
67
  ) {
68
+ this.fifoPath = path.join(os.tmpdir(), `bb-batch-${label}-${process.pid}-${Date.now()}.fifo`);
75
69
  this.fifoReader = new FifoFrameReader();
76
70
  this.sendQueue = new SerialQueue();
77
71
  this.sendQueue.start(1);
@@ -112,87 +106,48 @@ export class BatchChonkVerifier implements ClientProtocolCircuitVerifier {
112
106
  private async start(): Promise<void> {
113
107
  this.logger.info('Starting BatchChonkVerifier');
114
108
 
115
- try {
116
- this.bb = await Barretenberg.new({
117
- bbPath: this.config.bbBinaryPath,
118
- backend: BackendType.NativeUnixSocket,
119
- });
120
- await this.bb.initSRSChonk();
121
-
122
- // Keep the FIFO in a private directory so cleanup has a single owner.
123
- this.fifoDir = await mkdtemp(path.join(os.tmpdir(), `bb-batch-${this.label}-${process.pid}-`));
124
- this.fifoPath = path.join(this.fifoDir, 'results.fifo');
125
- await execFileAsync('mkfifo', [this.fifoPath]);
126
- this.registerExitCleanup();
127
- this.startFifoReader();
128
-
129
- await this.bb.chonkBatchVerifierStart({
130
- vks: this.vkBuffers,
131
- numCores: this.config.bbChonkVerifyConcurrency || 0,
132
- batchSize: this.batchSize,
133
- fifoPath: this.fifoPath,
134
- });
135
- } catch (err) {
136
- this.fifoReader.stop();
137
- this.deregisterExitCleanup();
138
- await this.cleanupFifo();
139
- await this.bb?.destroy().catch(() => {});
140
- throw err;
141
- }
109
+ this.bb = await Barretenberg.new({
110
+ bbPath: this.config.bbBinaryPath,
111
+ backend: BackendType.NativeUnixSocket,
112
+ });
113
+ await this.bb.initSRSChonk();
114
+
115
+ await execFileAsync('mkfifo', [this.fifoPath]);
116
+ this.registerExitCleanup();
117
+
118
+ await this.bb.chonkBatchVerifierStart({
119
+ vks: this.vkBuffers,
120
+ numCores: this.config.bbChonkVerifyConcurrency || 0,
121
+ batchSize: this.batchSize,
122
+ fifoPath: this.fifoPath,
123
+ });
124
+
125
+ this.startFifoReader();
142
126
  this.logger.info('BatchChonkVerifier started', { fifoPath: this.fifoPath });
143
127
  }
144
128
 
145
129
  public verifyProof(tx: Tx): Promise<IVCProofVerificationResult> {
146
- const totalTimer = new Timer();
147
- return (async () => {
148
- const circuit = tx.data.forPublic ? 'HidingKernelToPublic' : 'HidingKernelToRollup';
149
- const vkIndex = this.vkIndexMap.get(circuit);
150
- if (vkIndex === undefined) {
151
- throw new Error(`No VK index for circuit ${circuit}`);
152
- }
153
- const proofWithPubInputs = tx.chonkProof.attachPublicInputs(tx.data.publicInputs().toFields());
154
- const proofFields = proofWithPubInputs.fieldsWithPublicInputs.map(f => f.toBuffer());
155
- return await this.enqueueProof(vkIndex, proofFields);
156
- })().catch(err => {
157
- this.logger.warn(`Failed to verify Chonk proof for tx ${tx.getTxHash().toString()}: ${String(err)}`);
158
- return { valid: false, durationMs: 0, totalDurationMs: totalTimer.ms() };
159
- });
130
+ const circuit = tx.data.forPublic ? 'HidingKernelToPublic' : 'HidingKernelToRollup';
131
+ const vkIndex = this.vkIndexMap.get(circuit);
132
+ if (vkIndex === undefined) {
133
+ throw new Error(`No VK index for circuit ${circuit}`);
134
+ }
135
+ const proofWithPubInputs = tx.chonkProof.attachPublicInputs(tx.data.publicInputs().toFields());
136
+ const proofFields = proofWithPubInputs.fieldsWithPublicInputs.map(f => f.toBuffer());
137
+ return this.enqueueProof(vkIndex, proofFields);
160
138
  }
161
139
 
162
140
  /** Enqueue raw proof fields for verification. Used directly by tests with custom VKs. */
163
141
  public enqueueProof(vkIndex: number, proofFields: Uint8Array[]): Promise<IVCProofVerificationResult> {
164
- if (this.stopped) {
165
- return Promise.reject(new Error('BatchChonkVerifier stopped'));
166
- }
167
- if (this.fatalError) {
168
- return Promise.reject(this.fatalError);
169
- }
170
-
171
142
  const totalTimer = new Timer();
172
143
  const requestId = this.nextRequestId++;
173
144
 
174
145
  const resultPromise = new Promise<IVCProofVerificationResult>((resolve, reject) => {
175
- const timeout = setTimeout(() => {
176
- const pending = this.pendingRequests.get(requestId);
177
- if (!pending) {
178
- return;
179
- }
180
- this.pendingRequests.delete(requestId);
181
- pending.reject(new Error(`BatchChonkVerifier result timed out for request_id=${requestId}`));
182
- this.notifyPendingDrained();
183
- }, RESULT_TIMEOUT_MS);
184
- // A pending result timer must never keep the host process alive on its own (e.g. an
185
- // orphaned request at process exit); the FIFO reader keeps the loop alive while results
186
- // are genuinely awaited.
187
- timeout.unref();
188
- this.pendingRequests.set(requestId, { resolve, reject, totalTimer, timeout });
146
+ this.pendingRequests.set(requestId, { resolve, reject, totalTimer });
189
147
  });
190
148
 
191
149
  void this.sendQueue
192
150
  .put(async () => {
193
- if (this.fatalError) {
194
- throw this.fatalError;
195
- }
196
151
  await this.bb.chonkBatchVerifierQueue({
197
152
  requestId,
198
153
  vkIndex,
@@ -203,9 +158,7 @@ export class BatchChonkVerifier implements ClientProtocolCircuitVerifier {
203
158
  const pending = this.pendingRequests.get(requestId);
204
159
  if (pending) {
205
160
  this.pendingRequests.delete(requestId);
206
- clearTimeout(pending.timeout);
207
161
  pending.reject(err instanceof Error ? err : new Error(String(err)));
208
- this.notifyPendingDrained();
209
162
  }
210
163
  });
211
164
 
@@ -214,58 +167,34 @@ export class BatchChonkVerifier implements ClientProtocolCircuitVerifier {
214
167
 
215
168
  public async stop(): Promise<void> {
216
169
  this.logger.info('Stopping BatchChonkVerifier');
217
- this.stopped = true;
218
-
219
- try {
220
- // Stop accepting new proofs and flush the send queue. Bound it so an unresponsive
221
- // native process can't block teardown of our own event-loop handles indefinitely.
222
- await this.withTimeout(this.sendQueue.end(), STOP_DRAIN_TIMEOUT_MS, 'send queue flush');
223
170
 
224
- // Stop the bb service (flushes remaining proofs).
225
- await this.withTimeout(this.bb.chonkBatchVerifierStop({}), STOP_DRAIN_TIMEOUT_MS, 'chonkBatchVerifierStop');
171
+ // Stop accepting new proofs
172
+ await this.sendQueue.end();
226
173
 
227
- // Native stop flushes callbacks; keep the FIFO open until those frames are observed.
228
- const drained = await this.waitForPendingRequestsToDrain(STOP_DRAIN_TIMEOUT_MS);
229
- if (!drained) {
230
- this.rejectPendingRequests(new Error('Timed out waiting for BatchChonkVerifier results during stop'));
231
- }
174
+ // Stop the bb service (flushes remaining proofs)
175
+ try {
176
+ await this.bb.chonkBatchVerifierStop({});
232
177
  } catch (err) {
233
- this.logger.warn(`Error during BatchChonkVerifier graceful stop: ${err}`);
234
- this.rejectPendingRequests(err instanceof Error ? err : new Error(String(err)));
235
- } finally {
236
- // Always release our own event-loop handles — the FIFO read stream (a blocking
237
- // threadpool read that can't be unref'd), the unix socket, the native process, and the
238
- // exit handler — so the host process exits cleanly even if the native backend is wedged.
239
- this.fifoReader.stop();
240
- this.deregisterExitCleanup();
241
- await this.cleanupFifo();
242
- await this.bb.destroy().catch(err => this.logger.warn(`Error destroying bb backend during stop: ${err}`));
178
+ this.logger.warn(`Error stopping batch verifier service: ${err}`);
243
179
  }
244
180
 
245
- this.logger.info('BatchChonkVerifier stopped');
246
- }
181
+ // Stop FIFO reader
182
+ this.fifoReader.stop();
247
183
 
248
- /**
249
- * Races a promise against a timeout so a wedged native backend can't block teardown. The
250
- * timer is unref'd so it never keeps the process alive; the underlying promise stays handled
251
- * by the race even if the timeout wins, so it cannot surface as an unhandled rejection.
252
- */
253
- private async withTimeout<T>(promise: Promise<T>, timeoutMs: number, label: string): Promise<T | void> {
254
- let timer: ReturnType<typeof setTimeout> | undefined;
255
- const timeout = new Promise<void>((_, reject) => {
256
- timer = setTimeout(
257
- () => reject(new Error(`BatchChonkVerifier ${label} timed out after ${timeoutMs}ms`)),
258
- timeoutMs,
259
- );
260
- timer.unref();
261
- });
262
- try {
263
- return await Promise.race([promise, timeout]);
264
- } finally {
265
- if (timer) {
266
- clearTimeout(timer);
267
- }
184
+ // Clean up FIFO file and deregister exit handler
185
+ await unlink(this.fifoPath).catch(() => {});
186
+ this.deregisterExitCleanup();
187
+
188
+ // Reject any remaining pending requests
189
+ for (const [id, pending] of this.pendingRequests) {
190
+ pending.reject(new Error('BatchChonkVerifier stopped'));
191
+ this.pendingRequests.delete(id);
268
192
  }
193
+
194
+ // Destroy bb process
195
+ await this.bb.destroy();
196
+
197
+ this.logger.info('BatchChonkVerifier stopped');
269
198
  }
270
199
 
271
200
  private startFifoReader(): void {
@@ -277,20 +206,18 @@ export class BatchChonkVerifier implements ClientProtocolCircuitVerifier {
277
206
  this.handleResult(result);
278
207
  } catch (err) {
279
208
  this.logger.error(`FIFO: failed to decode msgpack result: ${err}`);
280
- // A corrupt result stream cannot safely be matched to outstanding requests.
281
- this.failVerifier(err instanceof Error ? err : new Error(String(err)));
282
209
  }
283
210
  });
284
211
 
285
212
  this.fifoReader.on('error', (err: Error) => {
286
213
  this.logger.error(`FIFO reader error: ${err}`);
287
- this.failVerifier(err);
288
214
  });
289
215
 
290
216
  this.fifoReader.on('end', () => {
291
217
  this.logger.debug('FIFO reader: stream ended');
292
- if (!this.stopped) {
293
- this.failVerifier(new Error('FIFO stream ended unexpectedly'));
218
+ for (const [id, pending] of this.pendingRequests) {
219
+ pending.reject(new Error('FIFO stream ended unexpectedly'));
220
+ this.pendingRequests.delete(id);
294
221
  }
295
222
  });
296
223
 
@@ -304,7 +231,6 @@ export class BatchChonkVerifier implements ClientProtocolCircuitVerifier {
304
231
  return;
305
232
  }
306
233
  this.pendingRequests.delete(result.request_id);
307
- clearTimeout(pending.timeout);
308
234
 
309
235
  const valid = result.status === 0; // VerifyStatus::OK
310
236
  const durationMs = result.time_in_verify_ms;
@@ -323,93 +249,28 @@ export class BatchChonkVerifier implements ClientProtocolCircuitVerifier {
323
249
  }
324
250
 
325
251
  pending.resolve(ivcResult);
326
- this.notifyPendingDrained();
327
252
  }
328
253
 
329
254
  private registerExitCleanup(): void {
255
+ // Signal handlers must be synchronous — unlinkSync is intentional here
330
256
  this.exitCleanup = () => {
331
- this.cleanupFifoSync();
257
+ try {
258
+ unlinkSync(this.fifoPath);
259
+ } catch {
260
+ /* ignore */
261
+ }
332
262
  };
333
263
  process.on('exit', this.exitCleanup);
264
+ process.on('SIGINT', this.exitCleanup);
265
+ process.on('SIGTERM', this.exitCleanup);
334
266
  }
335
267
 
336
268
  private deregisterExitCleanup(): void {
337
269
  if (this.exitCleanup) {
338
270
  process.removeListener('exit', this.exitCleanup);
271
+ process.removeListener('SIGINT', this.exitCleanup);
272
+ process.removeListener('SIGTERM', this.exitCleanup);
339
273
  this.exitCleanup = null;
340
274
  }
341
275
  }
342
-
343
- private rejectPendingRequests(error: Error): void {
344
- for (const [id, pending] of Array.from(this.pendingRequests)) {
345
- pending.reject(error);
346
- clearTimeout(pending.timeout);
347
- this.pendingRequests.delete(id);
348
- }
349
- this.notifyPendingDrained();
350
- }
351
-
352
- private failVerifier(error: Error): void {
353
- if (!this.fatalError) {
354
- this.fatalError = error;
355
- }
356
- this.rejectPendingRequests(error);
357
- }
358
-
359
- private waitForPendingRequestsToDrain(timeoutMs: number): Promise<boolean> {
360
- if (this.pendingRequests.size === 0) {
361
- return Promise.resolve(true);
362
- }
363
-
364
- let timeout: ReturnType<typeof setTimeout> | undefined;
365
- let onDrain: (() => void) | undefined;
366
- const drained = new Promise<boolean>(resolve => {
367
- onDrain = () => resolve(true);
368
- this.pendingDrainedResolvers.add(onDrain);
369
- });
370
- const timedOut = new Promise<boolean>(resolve => {
371
- timeout = setTimeout(() => resolve(false), timeoutMs);
372
- timeout.unref();
373
- });
374
-
375
- return Promise.race([drained, timedOut]).finally(() => {
376
- if (timeout) {
377
- clearTimeout(timeout);
378
- }
379
- if (onDrain) {
380
- this.pendingDrainedResolvers.delete(onDrain);
381
- }
382
- });
383
- }
384
-
385
- private notifyPendingDrained(): void {
386
- if (this.pendingRequests.size > 0) {
387
- return;
388
- }
389
- for (const resolve of Array.from(this.pendingDrainedResolvers)) {
390
- resolve();
391
- }
392
- }
393
-
394
- private async cleanupFifo(): Promise<void> {
395
- if (this.fifoDir) {
396
- await rm(this.fifoDir, { recursive: true, force: true }).catch(() => {});
397
- } else if (this.fifoPath) {
398
- await rm(this.fifoPath, { force: true }).catch(() => {});
399
- }
400
- this.fifoDir = undefined;
401
- this.fifoPath = '';
402
- }
403
-
404
- private cleanupFifoSync(): void {
405
- try {
406
- if (this.fifoDir) {
407
- rmSync(this.fifoDir, { recursive: true, force: true });
408
- } else if (this.fifoPath) {
409
- rmSync(this.fifoPath, { force: true });
410
- }
411
- } catch {
412
- /* ignore */
413
- }
414
- }
415
276
  }