@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.
- package/dest/avm_proving_tests/avm_proving_tester.js +1 -1
- package/dest/prover/client/bb_private_kernel_prover.d.ts +4 -4
- package/dest/prover/client/bb_private_kernel_prover.d.ts.map +1 -1
- package/dest/prover/client/bb_private_kernel_prover.js +9 -7
- package/dest/prover/server/bb_prover.d.ts +3 -3
- package/dest/prover/server/bb_prover.d.ts.map +1 -1
- package/dest/prover/server/bb_prover.js +4 -6
- package/dest/test/test_circuit_prover.d.ts +3 -3
- package/dest/test/test_circuit_prover.d.ts.map +1 -1
- package/dest/test/test_circuit_prover.js +2 -2
- package/dest/verifier/batch_chonk_verifier.d.ts +2 -13
- package/dest/verifier/batch_chonk_verifier.d.ts.map +1 -1
- package/dest/verifier/batch_chonk_verifier.js +58 -210
- package/package.json +16 -16
- package/src/avm_proving_tests/avm_proving_tester.ts +1 -1
- package/src/prover/client/bb_private_kernel_prover.ts +18 -15
- package/src/prover/server/bb_prover.ts +6 -16
- package/src/test/test_circuit_prover.ts +3 -5
- package/src/verifier/batch_chonk_verifier.ts +65 -204
|
@@ -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 {
|
|
13
|
-
import {
|
|
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
|
|
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
|
|
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
|
-
|
|
116
|
-
this.
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
this.
|
|
127
|
-
this.
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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
|
-
|
|
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
|
-
|
|
225
|
-
|
|
171
|
+
// Stop accepting new proofs
|
|
172
|
+
await this.sendQueue.end();
|
|
226
173
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
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
|
|
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
|
-
|
|
246
|
-
|
|
181
|
+
// Stop FIFO reader
|
|
182
|
+
this.fifoReader.stop();
|
|
247
183
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
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
|
-
|
|
293
|
-
|
|
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
|
-
|
|
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
|
}
|