@aztec-labs/bb-prover 6.0.0-rc.1 → 6.0.0-rc.3

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.
@@ -1 +1 @@
1
- {"version":3,"file":"bb_verifier.d.ts","sourceRoot":"","sources":["../../src/verifier/bb_verifier.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,MAAM,EAAgB,MAAM,4BAA4B,CAAC;AAGvE,OAAO,EAEL,KAAK,gBAAgB,EACrB,KAAK,sBAAsB,EAE5B,MAAM,gDAAgD,CAAC;AACxD,OAAO,KAAK,EAAE,6BAA6B,EAAE,0BAA0B,EAAE,MAAM,sCAAsC,CAAC;AACtH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,2BAA2B,CAAC;AAEvD,OAAO,EAAE,EAAE,EAAE,MAAM,uBAAuB,CAAC;AAC3C,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAIlE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAG7C,qBAAa,iBAAkB,YAAW,6BAA6B;IAInE,OAAO,CAAC,MAAM;IACd,OAAO,CAAC,MAAM;IAJhB,OAAO,CAAC,WAAW,CAAc;IAEjC,OAAO,eAWN;IAEM,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAE3B;IAED,OAAoB,GAAG,CAAC,MAAM,EAAE,QAAQ,EAAE,MAAM,SAAqC,8BAMpF;IAEM,sBAAsB,CAAC,OAAO,EAAE,gBAAgB,GAAG,mBAAmB,CAM5E;IAED,+DAA+D;IAClD,qBAAqB,CAAC,OAAO,EAAE,sBAAsB,EAAE,KAAK,EAAE,KAAK,iBA4B/E;IAED,mEAAmE;IACtD,WAAW,CAAC,EAAE,EAAE,EAAE,GAAG,OAAO,CAAC,0BAA0B,CAAC,CA+BpE;CACF"}
1
+ {"version":3,"file":"bb_verifier.d.ts","sourceRoot":"","sources":["../../src/verifier/bb_verifier.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,MAAM,EAAgB,MAAM,4BAA4B,CAAC;AAGvE,OAAO,EAEL,KAAK,gBAAgB,EACrB,KAAK,sBAAsB,EAE5B,MAAM,gDAAgD,CAAC;AACxD,OAAO,KAAK,EAAE,6BAA6B,EAAE,0BAA0B,EAAE,MAAM,sCAAsC,CAAC;AACtH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,2BAA2B,CAAC;AAEvD,OAAO,EAAE,EAAE,EAAE,MAAM,uBAAuB,CAAC;AAC3C,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAGlE,OAAO,EAAgB,WAAW,EAAE,MAAM,wBAAwB,CAAC;AACnE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAG7C,0GAA0G;AAC1G,qBAAa,6BAA8B,SAAQ,KAAK;IACtD,YAAY,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY,EAGlD;CACF;AAED,qBAAa,iBAAkB,YAAW,6BAA6B;IAOnE,OAAO,CAAC,MAAM;IACd,OAAO,CAAC,MAAM;IAPhB,sHAAsH;IACtH,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,yBAAyB,CAAK;IAEtD,OAAO,CAAC,WAAW,CAAc;IAEjC,SAAS,aACC,MAAM,EAAE,QAAQ,EAChB,MAAM,EAAE,MAAM,EACtB,WAAW,CAAC,EAAE,WAAW,EAgB1B;IAEM,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAE3B;IAED,OAAoB,GAAG,CAAC,MAAM,EAAE,QAAQ,EAAE,MAAM,SAAqC,8BAMpF;IAEM,sBAAsB,CAAC,OAAO,EAAE,gBAAgB,GAAG,mBAAmB,CAM5E;IAED,+DAA+D;IAClD,qBAAqB,CAAC,OAAO,EAAE,sBAAsB,EAAE,KAAK,EAAE,KAAK,iBA4B/E;IAED;;;OAGG;IACU,WAAW,CAAC,EAAE,EAAE,EAAE,GAAG,OAAO,CAAC,0BAA0B,CAAC,CAsCpE;YASa,8BAA8B;CAsC7C"}
@@ -63,6 +63,7 @@ function _ts_dispose_resources(env) {
63
63
  return next();
64
64
  })(env);
65
65
  }
66
+ import { isRetryableError } from '@aztec-labs/foundation/error';
66
67
  import { createLogger } from '@aztec-labs/foundation/log';
67
68
  import { Timer } from '@aztec-labs/foundation/timer';
68
69
  import { ProtocolCircuitVks } from '@aztec-labs/noir-protocol-circuits-types/server/vks';
@@ -70,17 +71,29 @@ import { mapProtocolArtifactNameToCircuitName } from '@aztec-labs/noir-protocol-
70
71
  import { promises as fs } from 'fs';
71
72
  import { BBJsFactory } from '../bb/bb_js_backend.js';
72
73
  import { getUltraHonkFlavorForCircuit } from '../honk.js';
74
+ /** Thrown when no live bb process could check a proof, so the proof was neither accepted nor rejected. */ export class ProofVerifierUnavailableError extends Error {
75
+ constructor(message, options){
76
+ super(message, options);
77
+ this.name = 'ProofVerifierUnavailableError';
78
+ }
79
+ }
73
80
  export class BBCircuitVerifier {
74
81
  config;
75
82
  logger;
83
+ /** bb instances a Chonk verification tries, while each one's bb dies under it, before the verifier is unavailable. */ static MAX_CHONK_VERIFY_ATTEMPTS = 2;
76
84
  bbJsFactory;
77
- constructor(config, logger){
85
+ constructor(config, logger, bbJsFactory){
78
86
  this.config = config;
79
87
  this.logger = logger;
80
88
  // BB_NUM_IVC_VERIFIERS bounds the number of long-lived bb processes the pool keeps alive.
81
89
  // If 0, fall back to spawning a fresh bb per verification.
82
- this.bbJsFactory = new BBJsFactory(config.bbBinaryPath, {
83
- poolSize: config.numConcurrentIVCVerifiers > 0 ? config.numConcurrentIVCVerifiers : undefined,
90
+ const poolSize = config.numConcurrentIVCVerifiers > 0 ? config.numConcurrentIVCVerifiers : undefined;
91
+ this.bbJsFactory = bbJsFactory ?? new BBJsFactory(config.bbBinaryPath, {
92
+ poolSize,
93
+ // A pooled instance outlives the call that borrowed it, so it replaces a bb process that dies
94
+ // under it and the next borrower gets a working one. Each verification stands alone, so a
95
+ // replacement has nothing to carry over. A fresh-per-call instance has nothing to heal.
96
+ respawn: poolSize !== undefined,
84
97
  logger,
85
98
  debugDir: config.bbDebugOutputDir
86
99
  });
@@ -136,37 +149,100 @@ export class BBCircuitVerifier {
136
149
  if (result) await result;
137
150
  }
138
151
  }
139
- /** Verify a Chonk (IVC) proof from a transaction via bb.js API. */ async verifyProof(tx) {
152
+ /**
153
+ * Verify a Chonk (IVC) proof from a transaction via bb.js API. Throws {@link ProofVerifierUnavailableError} when no
154
+ * live bb process could check the proof; any other failure returns `valid: false`.
155
+ */ async verifyProof(tx) {
140
156
  const proofType = 'Chonk';
157
+ const txHash = tx.getTxHash().toString();
141
158
  try {
159
+ const totalTimer = new Timer();
160
+ const circuit = tx.data.forPublic ? 'HidingKernelToPublic' : 'HidingKernelToRollup';
161
+ const verificationKey = this.getVerificationKeyData(circuit);
162
+ // Reconstruct the full proof with public inputs prepended, then convert Fr[] to Uint8Array[]
163
+ const proofWithPubInputs = tx.chonkProof.attachPublicInputs(tx.data.publicInputs().toFields());
164
+ const fieldsAsBuffers = proofWithPubInputs.fieldsWithPublicInputs.map((f)=>new Uint8Array(f.toBuffer()));
165
+ const { verified, durationMs } = await this.verifyChonkProofOnLiveInstance(fieldsAsBuffers, verificationKey.keyAsBytes, txHash);
166
+ if (!verified) {
167
+ throw new Error(`Failed to verify ${proofType} proof for ${circuit}!`);
168
+ }
169
+ this.logger.debug(`${proofType} verification successful`, {
170
+ circuitName: mapProtocolArtifactNameToCircuitName(circuit),
171
+ duration: durationMs,
172
+ eventName: 'circuit-verification',
173
+ proofType: 'chonk'
174
+ });
175
+ return {
176
+ valid: true,
177
+ durationMs,
178
+ totalDurationMs: totalTimer.ms()
179
+ };
180
+ } catch (err) {
181
+ if (err instanceof ProofVerifierUnavailableError) {
182
+ throw err;
183
+ }
184
+ this.logger.warn(`Failed to verify ${proofType} proof`, {
185
+ txHash,
186
+ err
187
+ });
188
+ return {
189
+ valid: false,
190
+ durationMs: 0,
191
+ totalDurationMs: 0
192
+ };
193
+ }
194
+ }
195
+ /**
196
+ * Runs a Chonk verification on a pooled bb instance. A call that failed for environmental reasons — its bb process
197
+ * died, or could not be started — is retried; any other failure is the verification's own verdict and is rethrown.
198
+ *
199
+ * The error says so itself, through the `retry` property bb.js sets. Asking the instance whether it is still alive
200
+ * would be a guess: the process can die between the answer and the next call.
201
+ */ async verifyChonkProofOnLiveInstance(fieldsWithPublicInputs, verificationKey, txHash) {
202
+ for(let attempt = 1;; attempt++){
142
203
  const env = {
143
204
  stack: [],
144
205
  error: void 0,
145
206
  hasError: false
146
207
  };
147
208
  try {
148
- const totalTimer = new Timer();
149
- const circuit = tx.data.forPublic ? 'HidingKernelToPublic' : 'HidingKernelToRollup';
150
- const verificationKey = this.getVerificationKeyData(circuit);
151
- // Reconstruct the full proof with public inputs prepended, then convert Fr[] to Uint8Array[]
152
- const proofWithPubInputs = tx.chonkProof.attachPublicInputs(tx.data.publicInputs().toFields());
153
- const fieldsAsBuffers = proofWithPubInputs.fieldsWithPublicInputs.map((f)=>new Uint8Array(f.toBuffer()));
154
- const instance = _ts_add_disposable_resource(env, await this.bbJsFactory.getInstance(), true);
155
- const { verified, durationMs } = await instance.verifyChonkProof(fieldsAsBuffers, verificationKey.keyAsBytes);
156
- if (!verified) {
157
- throw new Error(`Failed to verify ${proofType} proof for ${circuit}!`);
209
+ let borrowed;
210
+ try {
211
+ borrowed = await this.bbJsFactory.getInstance();
212
+ } catch (err) {
213
+ // A bb that could not be started is worth another go, within the same budget a death gets.
214
+ // Anything else — the factory destroyed under us — will not improve by asking again. Either
215
+ // way no proof was checked, so this is never the proof's fault.
216
+ if (isRetryableError(err) && attempt < BBCircuitVerifier.MAX_CHONK_VERIFY_ATTEMPTS) {
217
+ this.logger.warn('no bb instance available to verify a proof; retrying', {
218
+ txHash,
219
+ attempt
220
+ });
221
+ continue;
222
+ }
223
+ throw new ProofVerifierUnavailableError('No bb instance available to verify the proof', {
224
+ cause: err
225
+ });
226
+ }
227
+ const instance = _ts_add_disposable_resource(env, borrowed, true);
228
+ try {
229
+ return await instance.verifyChonkProof(fieldsWithPublicInputs, verificationKey);
230
+ } catch (err) {
231
+ // Only an environmental failure is worth retrying; anything else is the verification's own
232
+ // verdict and belongs to the caller.
233
+ if (!isRetryableError(err)) {
234
+ throw err;
235
+ }
236
+ if (attempt >= BBCircuitVerifier.MAX_CHONK_VERIFY_ATTEMPTS) {
237
+ throw new ProofVerifierUnavailableError(`bb died while verifying the proof, on ${attempt} attempts`, {
238
+ cause: err
239
+ });
240
+ }
241
+ this.logger.warn('bb died while verifying a proof; retrying', {
242
+ txHash,
243
+ attempt
244
+ });
158
245
  }
159
- this.logger.debug(`${proofType} verification successful`, {
160
- circuitName: mapProtocolArtifactNameToCircuitName(circuit),
161
- duration: durationMs,
162
- eventName: 'circuit-verification',
163
- proofType: 'chonk'
164
- });
165
- return {
166
- valid: true,
167
- durationMs,
168
- totalDurationMs: totalTimer.ms()
169
- };
170
246
  } catch (e) {
171
247
  env.error = e;
172
248
  env.hasError = true;
@@ -174,13 +250,6 @@ export class BBCircuitVerifier {
174
250
  const result = _ts_dispose_resources(env);
175
251
  if (result) await result;
176
252
  }
177
- } catch (err) {
178
- this.logger.warn(`Failed to verify ${proofType} proof for tx ${tx.getTxHash().toString()}: ${String(err)}`);
179
- return {
180
- valid: false,
181
- durationMs: 0,
182
- totalDurationMs: 0
183
- };
184
253
  }
185
254
  }
186
255
  }
@@ -23,4 +23,4 @@ export declare class QueuedIVCVerifier implements ClientProtocolCircuitVerifier
23
23
  verifyProof(tx: Tx): Promise<IVCProofVerificationResult>;
24
24
  stop(): Promise<void>;
25
25
  }
26
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicXVldWVkX2Nob25rX3ZlcmlmaWVyLmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvdmVyaWZpZXIvcXVldWVkX2Nob25rX3ZlcmlmaWVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUVBLE9BQU8sS0FBSyxFQUFFLDZCQUE2QixFQUFFLDBCQUEwQixFQUFFLE1BQU0sc0NBQXNDLENBQUM7QUFDdEgsT0FBTyxLQUFLLEVBQUUsRUFBRSxFQUFFLE1BQU0sdUJBQXVCLENBQUM7QUFDaEQsT0FBTyxFQU1MLEtBQUssZUFBZSxFQUlyQixNQUFNLDhCQUE4QixDQUFDO0FBR3RDLHlGQUF5RjtBQUN6RixxQkFBYSxrQkFBa0I7SUFDN0IsT0FBTyxDQUFDLHdCQUF3QixDQUFZO0lBQzVDLE9BQU8sQ0FBQyw2QkFBNkIsQ0FBWTtJQUNqRCxPQUFPLENBQUMsZUFBZSxDQUFnQjtJQUN2QyxPQUFPLENBQUMsZ0JBQWdCLENBR3JCO0lBQ0gsT0FBTyxDQUFDLG1CQUFtQixDQUd4QjtJQUVILE9BQU8sQ0FBQyxrQkFBa0IsQ0FBaUU7SUFFM0YsWUFBWSxNQUFNLEVBQUUsZUFBZSxFQUFFLElBQUksU0FBc0IsRUFrQjlEO0lBRUQscUJBQXFCLENBQUMsTUFBTSxFQUFFLDBCQUEwQixRQVN2RDtJQUVELE9BQU8sQ0FBQyxTQUFTLENBY2Y7Q0FDSDtBQUVELHFCQUFhLGlCQUFrQixZQUFXLDZCQUE2QjtJQUtuRSxPQUFPLENBQUMsUUFBUTtJQUVoQixPQUFPLENBQUMsU0FBUztJQUNqQixPQUFPLENBQUMsTUFBTTtJQVBoQixPQUFPLENBQUMsS0FBSyxDQUFjO0lBQzNCLE9BQU8sQ0FBQyxPQUFPLENBQXFCO0lBRXBDLFlBQ1UsUUFBUSxFQUFFLDZCQUE2QixFQUMvQyxXQUFXLEVBQUUsTUFBTSxFQUNYLFNBQVMsR0FBRSxlQUFzQyxFQUNqRCxNQUFNLDhDQUFrRCxFQU1qRTtJQUVZLFdBQVcsQ0FBQyxFQUFFLEVBQUUsRUFBRSxHQUFHLE9BQU8sQ0FBQywwQkFBMEIsQ0FBQyxDQUlwRTtJQUVLLElBQUksSUFBSSxPQUFPLENBQUMsSUFBSSxDQUFDLENBRzFCO0NBQ0YifQ==
26
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicXVldWVkX2Nob25rX3ZlcmlmaWVyLmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvdmVyaWZpZXIvcXVldWVkX2Nob25rX3ZlcmlmaWVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUVBLE9BQU8sS0FBSyxFQUFFLDZCQUE2QixFQUFFLDBCQUEwQixFQUFFLE1BQU0sc0NBQXNDLENBQUM7QUFDdEgsT0FBTyxLQUFLLEVBQUUsRUFBRSxFQUFFLE1BQU0sdUJBQXVCLENBQUM7QUFDaEQsT0FBTyxFQU1MLEtBQUssZUFBZSxFQUlyQixNQUFNLDhCQUE4QixDQUFDO0FBR3RDLHlGQUF5RjtBQUN6RixxQkFBYSxrQkFBa0I7SUFDN0IsT0FBTyxDQUFDLHdCQUF3QixDQUFZO0lBQzVDLE9BQU8sQ0FBQyw2QkFBNkIsQ0FBWTtJQUNqRCxPQUFPLENBQUMsZUFBZSxDQUFnQjtJQUN2QyxPQUFPLENBQUMsZ0JBQWdCLENBR3JCO0lBQ0gsT0FBTyxDQUFDLG1CQUFtQixDQUd4QjtJQUVILE9BQU8sQ0FBQyxrQkFBa0IsQ0FBaUU7SUFFM0YsWUFBWSxNQUFNLEVBQUUsZUFBZSxFQUFFLElBQUksU0FBc0IsRUFrQjlEO0lBRUQscUJBQXFCLENBQUMsTUFBTSxFQUFFLDBCQUEwQixRQVN2RDtJQUVELE9BQU8sQ0FBQyxTQUFTLENBY2Y7Q0FDSDtBQUVELHFCQUFhLGlCQUFrQixZQUFXLDZCQUE2QjtJQUtuRSxPQUFPLENBQUMsUUFBUTtJQUVoQixPQUFPLENBQUMsU0FBUztJQUNqQixPQUFPLENBQUMsTUFBTTtJQVBoQixPQUFPLENBQUMsS0FBSyxDQUFjO0lBQzNCLE9BQU8sQ0FBQyxPQUFPLENBQXFCO0lBRXBDLFlBQ1UsUUFBUSxFQUFFLDZCQUE2QixFQUMvQyxXQUFXLEVBQUUsTUFBTSxFQUNYLFNBQVMsR0FBRSxlQUFzQyxFQUNqRCxNQUFNLDhDQUFrRCxFQU1qRTtJQUVZLFdBQVcsQ0FBQyxFQUFFLEVBQUUsRUFBRSxHQUFHLE9BQU8sQ0FBQywwQkFBMEIsQ0FBQyxDQUlwRTtJQUVLLElBQUksSUFBSSxPQUFPLENBQUMsSUFBSSxDQUFDLENBUTFCO0NBQ0YifQ==
@@ -1 +1 @@
1
- {"version":3,"file":"queued_chonk_verifier.d.ts","sourceRoot":"","sources":["../../src/verifier/queued_chonk_verifier.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,6BAA6B,EAAE,0BAA0B,EAAE,MAAM,sCAAsC,CAAC;AACtH,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,uBAAuB,CAAC;AAChD,OAAO,EAML,KAAK,eAAe,EAIrB,MAAM,8BAA8B,CAAC;AAGtC,yFAAyF;AACzF,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,wBAAwB,CAAY;IAC5C,OAAO,CAAC,6BAA6B,CAAY;IACjD,OAAO,CAAC,eAAe,CAAgB;IACvC,OAAO,CAAC,gBAAgB,CAGrB;IACH,OAAO,CAAC,mBAAmB,CAGxB;IAEH,OAAO,CAAC,kBAAkB,CAAiE;IAE3F,YAAY,MAAM,EAAE,eAAe,EAAE,IAAI,SAAsB,EAkB9D;IAED,qBAAqB,CAAC,MAAM,EAAE,0BAA0B,QASvD;IAED,OAAO,CAAC,SAAS,CAcf;CACH;AAED,qBAAa,iBAAkB,YAAW,6BAA6B;IAKnE,OAAO,CAAC,QAAQ;IAEhB,OAAO,CAAC,SAAS;IACjB,OAAO,CAAC,MAAM;IAPhB,OAAO,CAAC,KAAK,CAAc;IAC3B,OAAO,CAAC,OAAO,CAAqB;IAEpC,YACU,QAAQ,EAAE,6BAA6B,EAC/C,WAAW,EAAE,MAAM,EACX,SAAS,GAAE,eAAsC,EACjD,MAAM,8CAAkD,EAMjE;IAEY,WAAW,CAAC,EAAE,EAAE,EAAE,GAAG,OAAO,CAAC,0BAA0B,CAAC,CAIpE;IAEK,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAG1B;CACF"}
1
+ {"version":3,"file":"queued_chonk_verifier.d.ts","sourceRoot":"","sources":["../../src/verifier/queued_chonk_verifier.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,6BAA6B,EAAE,0BAA0B,EAAE,MAAM,sCAAsC,CAAC;AACtH,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,uBAAuB,CAAC;AAChD,OAAO,EAML,KAAK,eAAe,EAIrB,MAAM,8BAA8B,CAAC;AAGtC,yFAAyF;AACzF,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,wBAAwB,CAAY;IAC5C,OAAO,CAAC,6BAA6B,CAAY;IACjD,OAAO,CAAC,eAAe,CAAgB;IACvC,OAAO,CAAC,gBAAgB,CAGrB;IACH,OAAO,CAAC,mBAAmB,CAGxB;IAEH,OAAO,CAAC,kBAAkB,CAAiE;IAE3F,YAAY,MAAM,EAAE,eAAe,EAAE,IAAI,SAAsB,EAkB9D;IAED,qBAAqB,CAAC,MAAM,EAAE,0BAA0B,QASvD;IAED,OAAO,CAAC,SAAS,CAcf;CACH;AAED,qBAAa,iBAAkB,YAAW,6BAA6B;IAKnE,OAAO,CAAC,QAAQ;IAEhB,OAAO,CAAC,SAAS;IACjB,OAAO,CAAC,MAAM;IAPhB,OAAO,CAAC,KAAK,CAAc;IAC3B,OAAO,CAAC,OAAO,CAAqB;IAEpC,YACU,QAAQ,EAAE,6BAA6B,EAC/C,WAAW,EAAE,MAAM,EACX,SAAS,GAAE,eAAsC,EACjD,MAAM,8CAAkD,EAMjE;IAEY,WAAW,CAAC,EAAE,EAAE,EAAE,GAAG,OAAO,CAAC,0BAA0B,CAAC,CAIpE;IAEK,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAQ1B;CACF"}
@@ -96,7 +96,12 @@ export class QueuedIVCVerifier {
96
96
  return result;
97
97
  }
98
98
  async stop() {
99
- await this.queue.end();
100
- await this.verifier.stop();
99
+ // Stopped first so that verifications waiting for a bb instance fail rather than keep the queue from draining. Queued
100
+ // verifications that have not started yet fail too.
101
+ try {
102
+ await this.verifier.stop();
103
+ } finally{
104
+ await this.queue.end();
105
+ }
101
106
  }
102
107
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aztec-labs/bb-prover",
3
- "version": "6.0.0-rc.1",
3
+ "version": "6.0.0-rc.3",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./dest/index.js",
@@ -70,15 +70,15 @@
70
70
  ]
71
71
  },
72
72
  "dependencies": {
73
- "@aztec-foundation/bb.js": "6.0.0-rc.1",
74
- "@aztec-foundation/noir-noirc_abi": "6.0.0-rc.1",
75
- "@aztec-foundation/noir-types": "6.0.0-rc.1",
76
- "@aztec-labs/constants": "6.0.0-rc.1",
77
- "@aztec-labs/foundation": "6.0.0-rc.1",
78
- "@aztec-labs/noir-protocol-circuits-types": "6.0.0-rc.1",
79
- "@aztec-labs/simulator": "6.0.0-rc.1",
80
- "@aztec-labs/stdlib": "6.0.0-rc.1",
81
- "@aztec-labs/telemetry-client": "6.0.0-rc.1",
73
+ "@aztec-foundation/bb.js": "6.0.0-rc.6",
74
+ "@aztec-foundation/noir-noirc_abi": "6.0.0-rc.6",
75
+ "@aztec-foundation/noir-types": "6.0.0-rc.6",
76
+ "@aztec-labs/constants": "6.0.0-rc.3",
77
+ "@aztec-labs/foundation": "6.0.0-rc.3",
78
+ "@aztec-labs/noir-protocol-circuits-types": "6.0.0-rc.3",
79
+ "@aztec-labs/simulator": "6.0.0-rc.3",
80
+ "@aztec-labs/stdlib": "6.0.0-rc.3",
81
+ "@aztec-labs/telemetry-client": "6.0.0-rc.3",
82
82
  "commander": "^12.1.0",
83
83
  "msgpackr": "^1.11.2",
84
84
  "pako": "^2.1.0",
@@ -86,10 +86,10 @@
86
86
  "tslib": "^2.4.0"
87
87
  },
88
88
  "devDependencies": {
89
- "@aztec-labs/noir-contracts.js": "6.0.0-rc.1",
90
- "@aztec-labs/noir-test-contracts.js": "6.0.0-rc.1",
91
- "@aztec-labs/protocol-contracts": "6.0.0-rc.1",
92
- "@aztec-labs/world-state": "6.0.0-rc.1",
89
+ "@aztec-labs/noir-contracts.js": "6.0.0-rc.3",
90
+ "@aztec-labs/noir-test-contracts.js": "6.0.0-rc.3",
91
+ "@aztec-labs/protocol-contracts": "6.0.0-rc.3",
92
+ "@aztec-labs/world-state": "6.0.0-rc.3",
93
93
  "@jest/globals": "^30.0.0",
94
94
  "@types/jest": "^30.0.0",
95
95
  "@types/node": "^22.15.17",
@@ -76,12 +76,19 @@ export interface BBJsApi {
76
76
  export class BBJsInstance implements BBJsApi {
77
77
  private constructor(private api: Barretenberg) {}
78
78
 
79
- /** Creates a new Barretenberg instance connected to a fresh bb process. */
80
- static async create(bbPath: string, logger?: LogFn, threads?: number): Promise<BBJsInstance> {
79
+ /**
80
+ * Creates a new Barretenberg instance connected to a fresh bb process.
81
+ *
82
+ * `respawn` lets the instance replace its bb process when it dies, so a long-lived instance stays
83
+ * usable instead of failing every later call. Only safe where the instance holds no state between
84
+ * calls, since a replacement has no Chonk accumulation and no batch-verifier session.
85
+ */
86
+ static async create(bbPath: string, logger?: LogFn, threads?: number, respawn?: boolean): Promise<BBJsInstance> {
81
87
  const options: BackendOptions = {
82
88
  bbPath,
83
89
  backend: BackendType.NativeUnixSocket,
84
90
  logger,
91
+ respawn,
85
92
  };
86
93
  if (threads !== undefined) {
87
94
  options.threads = threads;
@@ -251,17 +258,31 @@ export interface BBJsFactoryOptions {
251
258
  * If omitted, every `getInstance()` call spawns a fresh bb that is destroyed on dispose.
252
259
  */
253
260
  poolSize?: number;
261
+ /**
262
+ * Let each instance replace its bb process when it dies. Only for callers whose calls stand alone:
263
+ * a replacement process remembers nothing, so state held across calls (a Chonk accumulation, a
264
+ * batch-verifier session) would be silently lost.
265
+ */
266
+ respawn?: boolean;
254
267
  logger?: Logger;
255
268
  threads?: number;
256
269
  debugDir?: string;
257
270
  }
258
271
 
272
+ /** A place in a {@link BBJsFactory} pool: empty until a borrower first starts a bb in it. */
273
+ type PoolSlot = { instance?: BBJsApi };
274
+
259
275
  /**
260
276
  * Manages bb.js instance lifecycle. By default every `getInstance()` call spawns a fresh
261
277
  * bb process that is destroyed when the borrow is disposed. Pass `poolSize` to keep a fixed
262
278
  * set of long-lived bb processes that are reused across calls — useful when the per-call
263
279
  * bb startup cost dominates the workload (e.g. high-rate IVC verification).
264
280
  *
281
+ * The pool is a queue of `poolSize` slots, each starting its bb on the first borrow that needs it.
282
+ * A borrower waits in exactly two places: for a slot, which destroy() releases by cancelling the
283
+ * queue, and for its slot's bb to start, which bb.js bounds with its own startup deadline. A bb that
284
+ * fails to start costs only that slot, which goes back empty for the next borrower to try again.
285
+ *
265
286
  * Idiomatic usage:
266
287
  * ```
267
288
  * await using inst = await factory.getInstance();
@@ -270,82 +291,86 @@ export interface BBJsFactoryOptions {
270
291
  * ```
271
292
  */
272
293
  export class BBJsFactory {
273
- private readonly poolSize?: number;
274
294
  private readonly logger?: Logger;
275
295
  private readonly threads?: number;
276
296
  private readonly debugDir?: string;
297
+ private readonly respawn: boolean;
277
298
 
278
- /** Available pooled instances when poolSize is set; otherwise undefined. */
279
- private pool?: FifoMemoryQueue<BBJsApi>;
280
- /** Lazily-resolved on first `getInstance()` call to prevent racing pool initialization. */
281
- private initPromise?: Promise<void>;
299
+ /** Slots not currently borrowed, when poolSize is set; otherwise undefined. */
300
+ private readonly slots?: FifoMemoryQueue<PoolSlot>;
282
301
  private destroyed = false;
283
302
 
284
303
  constructor(
285
304
  private bbPath: string,
286
305
  options: BBJsFactoryOptions = {},
287
306
  ) {
288
- this.poolSize = options.poolSize;
307
+ this.respawn = options.respawn ?? false;
289
308
  this.logger = options.logger;
290
309
  this.threads = options.threads;
291
310
  this.debugDir = options.debugDir;
292
- if (this.poolSize !== undefined && this.poolSize < 1) {
293
- throw new Error(`BBJsFactory poolSize must be >= 1, got ${this.poolSize}`);
311
+ if (options.poolSize !== undefined) {
312
+ if (options.poolSize < 1) {
313
+ throw new Error(`BBJsFactory poolSize must be >= 1, got ${options.poolSize}`);
314
+ }
315
+ this.slots = new FifoMemoryQueue<PoolSlot>();
316
+ for (let i = 0; i < options.poolSize; i++) {
317
+ this.slots.put({});
318
+ }
294
319
  }
295
320
  }
296
321
 
297
322
  /**
298
323
  * Acquire a bb instance. The returned object implements `BBJsApi` and `AsyncDisposable`.
299
- * With no pool: spawns a fresh bb that is destroyed on dispose. With a pool: borrows from
300
- * the pool and returns to it on dispose.
324
+ * With no pool: spawns a fresh bb that is destroyed on dispose. With a pool: borrows a slot,
325
+ * starting its bb if it has none, and returns the slot to the pool on dispose.
301
326
  */
302
327
  async getInstance(): Promise<BBJsApi & AsyncDisposable> {
303
328
  if (this.destroyed) {
304
329
  throw new Error('BBJsFactory has been destroyed');
305
330
  }
306
- if (this.poolSize === undefined) {
331
+ if (!this.slots) {
307
332
  // No pool: fresh-per-call, dispose destroys.
308
333
  const instance = await this.createInstance();
309
334
  return this.makeOwned(instance);
310
335
  }
311
- if (!this.initPromise) {
312
- this.initPromise = this.initPool();
336
+ const slot = await this.slots.get();
337
+ if (!slot) {
338
+ throw new Error('BBJsFactory was destroyed while waiting for an instance');
313
339
  }
314
- await this.initPromise;
315
- const pool = this.pool;
316
- if (!pool) {
317
- throw new Error('BBJsFactory has been destroyed');
340
+ let instance: BBJsApi;
341
+ try {
342
+ instance = slot.instance ??= await this.createInstance();
343
+ } catch (err) {
344
+ await this.release(slot);
345
+ throw err;
318
346
  }
319
- const instance = await pool.get();
320
- if (!instance) {
321
- throw new Error('BBJsFactory was destroyed while waiting for an instance');
347
+ if (this.destroyed) {
348
+ await this.release(slot);
349
+ throw new Error('BBJsFactory has been destroyed');
322
350
  }
323
- return this.makeBorrowed(instance);
351
+ return this.makeDisposable(instance, () => this.release(slot));
324
352
  }
325
353
 
326
354
  /**
327
355
  * Tear down all pooled instances. Idempotent. No-op when no pool is configured (fresh-per-call
328
- * instances are destroyed by their own dispose callbacks). Instances currently held by an
329
- * in-flight pooled borrow are destroyed by their dispose callback when released.
356
+ * instances are destroyed by their own dispose callbacks). Instances in borrowed slots are
357
+ * destroyed when their borrow is released.
330
358
  */
331
359
  async destroy(): Promise<void> {
332
360
  if (this.destroyed) {
333
361
  return;
334
362
  }
335
363
  this.destroyed = true;
336
- const pool = this.pool;
337
- this.pool = undefined;
338
- if (!pool) {
364
+ if (!this.slots) {
339
365
  return;
340
366
  }
341
367
  const idle: BBJsApi[] = [];
342
- while (pool.length() > 0) {
343
- const item = pool.getImmediate();
344
- if (item) {
345
- idle.push(item);
368
+ for (let slot = this.slots.getImmediate(); slot; slot = this.slots.getImmediate()) {
369
+ if (slot.instance) {
370
+ idle.push(slot.instance);
346
371
  }
347
372
  }
348
- pool.cancel();
373
+ this.slots.cancel();
349
374
  // Aggregate teardown failures so a single bb child that fails to shut down doesn't mask others.
350
375
  const results = await Promise.allSettled(idle.map(item => item.destroy()));
351
376
  const errors = results.filter((r): r is PromiseRejectedResult => r.status === 'rejected').map(r => r.reason);
@@ -354,37 +379,18 @@ export class BBJsFactory {
354
379
  }
355
380
  }
356
381
 
357
- private async initPool(): Promise<void> {
358
- // Use allSettled so that if any createInstance() rejects we can destroy the rest instead of
359
- // leaking bb child processes whose creation succeeded.
360
- const results = await Promise.allSettled(Array.from({ length: this.poolSize! }, () => this.createInstance()));
361
- const items: BBJsApi[] = [];
362
- const errors: unknown[] = [];
363
- for (const result of results) {
364
- if (result.status === 'fulfilled') {
365
- items.push(result.value);
366
- } else {
367
- errors.push(result.reason);
368
- }
369
- }
370
- if (errors.length > 0 || this.destroyed) {
371
- // Either creation failed or destroy() raced ahead — clean up everything we successfully spawned.
372
- await Promise.all(items.map(item => item.destroy()));
373
- if (errors.length > 0) {
374
- throw errors[0];
375
- }
376
- return;
377
- }
378
- const pool = new FifoMemoryQueue<BBJsApi>();
379
- for (const item of items) {
380
- pool.put(item);
382
+ /** Return a borrowed slot to the pool, or destroy its bb if the factory was destroyed meanwhile. */
383
+ private async release(slot: PoolSlot): Promise<void> {
384
+ if (!this.destroyed) {
385
+ this.slots!.put(slot);
386
+ } else {
387
+ await slot.instance?.destroy();
381
388
  }
382
- this.pool = pool;
383
389
  }
384
390
 
385
- private async createInstance(): Promise<BBJsApi> {
391
+ protected async createInstance(): Promise<BBJsApi> {
386
392
  const logFn = this.logger ? (msg: string) => this.logger!.verbose(`bb.js - ${msg}`) : undefined;
387
- const raw = await BBJsInstance.create(this.bbPath, logFn, this.threads);
393
+ const raw = await BBJsInstance.create(this.bbPath, logFn, this.threads, this.respawn);
388
394
  return this.maybeWrapDebug(raw);
389
395
  }
390
396
 
@@ -406,21 +412,6 @@ export class BBJsFactory {
406
412
  return this.makeDisposable(instance, () => instance.destroy());
407
413
  }
408
414
 
409
- /**
410
- * Wrap a pooled instance with an `AsyncDisposable` that returns it to the pool (or destroys it
411
- * if the factory was destroyed in the meantime). Destroy errors are propagated.
412
- */
413
- private makeBorrowed(instance: BBJsApi): BBJsApi & AsyncDisposable {
414
- return this.makeDisposable(instance, async () => {
415
- const pool = this.pool;
416
- if (pool && !this.destroyed) {
417
- pool.put(instance);
418
- } else {
419
- await instance.destroy();
420
- }
421
- });
422
- }
423
-
424
415
  private makeDisposable(instance: BBJsApi, onDispose: () => void | Promise<void>): BBJsApi & AsyncDisposable {
425
416
  let disposed = false;
426
417
  const dispose = async (): Promise<void> => {
@@ -9,6 +9,7 @@ import {
9
9
  ULTRA_KECCAK_PROOF_LENGTH,
10
10
  } from '@aztec-labs/constants';
11
11
  import { Fr } from '@aztec-labs/foundation/curves/bn254';
12
+ import { isRetryableError } from '@aztec-labs/foundation/error';
12
13
  import { runInDirectory } from '@aztec-labs/foundation/fs';
13
14
  import { createLogger } from '@aztec-labs/foundation/log';
14
15
  import {
@@ -449,7 +450,7 @@ export class BBNativeRollupProver implements ServerCircuitProver {
449
450
  );
450
451
  } catch (error) {
451
452
  // Preserve retryability of the underlying failure (e.g. a transient bb startup error).
452
- const retry = error instanceof ProvingError && error.retry;
453
+ const retry = isRetryableError(error);
453
454
  throw new ProvingError(`Failed to generate proof for ${circuitType}: ${error}`, error, retry);
454
455
  }
455
456
 
@@ -590,7 +591,7 @@ export class BBNativeRollupProver implements ServerCircuitProver {
590
591
  ));
591
592
  } catch (error) {
592
593
  // Preserve retryability of the underlying failure (e.g. a transient bb startup error).
593
- const retry = error instanceof ProvingError && error.retry;
594
+ const retry = isRetryableError(error);
594
595
  throw new ProvingError(`Failed to verify proof for ${circuitType}: ${error}`, error, retry);
595
596
  }
596
597
 
@@ -0,0 +1,125 @@
1
+ import type { AvmStat } from '@aztec-foundation/bb.js';
2
+
3
+ import { type BBJsApi, BBJsFactory, type BBJsProofResult } from '../bb/bb_js_backend.js';
4
+
5
+ /** How a {@link FakeBBJsInstance} answers one `verifyChonkProof` call. */
6
+ export type FakeChonkVerifyOutcome = 'valid' | 'invalid' | 'bb-error' | 'die';
7
+
8
+ function notImplemented(): Promise<never> {
9
+ return Promise.reject(new Error('Not implemented by FakeBBJsInstance'));
10
+ }
11
+
12
+ /** An error shaped like the one bb.js raises when the bb process died: retrying may help. */
13
+ function retryable(message: string): Error {
14
+ return Object.assign(new Error(message), { retry: true });
15
+ }
16
+
17
+ /**
18
+ * A {@link BBJsApi} double whose bb process can die.
19
+ *
20
+ * A pooled instance is created with respawn, so a death fails only the call that was in flight and
21
+ * the next call is served by a replacement process. The double behaves the same way: `die` rejects
22
+ * once, retryably, and leaves the instance usable. Only `verifyChonkProof` is implemented.
23
+ */
24
+ export class FakeBBJsInstance implements BBJsApi {
25
+ public destroyCount = 0;
26
+ public chonkVerifyCalls = 0;
27
+ private destroyed = false;
28
+
29
+ /** @param outcomes - Answers to successive `verifyChonkProof` calls; `valid` once they run out. */
30
+ constructor(private readonly outcomes: FakeChonkVerifyOutcome[] = []) {}
31
+
32
+ public verifyChonkProof(): Promise<{ verified: boolean; durationMs: number }> {
33
+ this.chonkVerifyCalls++;
34
+ if (this.destroyed) {
35
+ return Promise.reject(new Error('Backend connection closed'));
36
+ }
37
+ switch (this.outcomes.shift() ?? 'valid') {
38
+ case 'valid':
39
+ return Promise.resolve({ verified: true, durationMs: 1 });
40
+ case 'invalid':
41
+ return Promise.resolve({ verified: false, durationMs: 1 });
42
+ case 'bb-error':
43
+ return Promise.reject(new Error('bb rejected the proof input'));
44
+ case 'die':
45
+ return Promise.reject(retryable('Socket connection ended unexpectedly'));
46
+ }
47
+ }
48
+
49
+ public destroy(): Promise<void> {
50
+ this.destroyed = true;
51
+ this.destroyCount++;
52
+ return Promise.resolve();
53
+ }
54
+
55
+ public generateProof(): Promise<BBJsProofResult> {
56
+ return notImplemented();
57
+ }
58
+
59
+ public verifyProof(): Promise<{ verified: boolean; durationMs: number }> {
60
+ return notImplemented();
61
+ }
62
+
63
+ public computeGateCount(): Promise<{ circuitSize: number; durationMs: number }> {
64
+ return notImplemented();
65
+ }
66
+
67
+ public generateContract(): Promise<{ solidityCode: string; durationMs: number }> {
68
+ return notImplemented();
69
+ }
70
+
71
+ public generateAvmProof(): Promise<{ proof: Uint8Array[]; stats: AvmStat[]; durationMs: number }> {
72
+ return notImplemented();
73
+ }
74
+
75
+ public verifyAvmProof(): Promise<{ verified: boolean; durationMs: number }> {
76
+ return notImplemented();
77
+ }
78
+
79
+ public checkAvmCircuit(): Promise<{ passed: boolean; stats: AvmStat[]; durationMs: number }> {
80
+ return notImplemented();
81
+ }
82
+ }
83
+
84
+ /** A scripted {@link FakeBBJsFactory} spawn. */
85
+ type PlannedSpawn = {
86
+ /** An error fails the spawn; outcomes script the created instance's verifications. */
87
+ next: FakeChonkVerifyOutcome[] | Error;
88
+ /** When set, the spawn completes only once it resolves. */
89
+ spawned?: Promise<void>;
90
+ };
91
+
92
+ /** A {@link BBJsFactory} that creates {@link FakeBBJsInstance}s instead of spawning bb. */
93
+ export class FakeBBJsFactory extends BBJsFactory {
94
+ /** Every instance created, in creation order. */
95
+ public readonly created: FakeBBJsInstance[] = [];
96
+ private readonly plan: PlannedSpawn[] = [];
97
+
98
+ /** @param poolSize - Pooled instances to keep; when omitted, every borrow creates a fresh instance. */
99
+ constructor(poolSize?: number) {
100
+ super('/unused/bb', { poolSize });
101
+ }
102
+
103
+ /**
104
+ * Scripts the next creation: an error makes that spawn fail, outcomes script the created instance's verifications.
105
+ * With `spawned`, the spawn completes only once it resolves.
106
+ */
107
+ public planNextInstance(next: FakeChonkVerifyOutcome[] | Error, spawned?: Promise<void>): void {
108
+ this.plan.push({ next, spawned });
109
+ }
110
+
111
+ protected override async createInstance(): Promise<BBJsApi> {
112
+ const { next, spawned }: PlannedSpawn = this.plan.shift() ?? { next: [] };
113
+ if (spawned) {
114
+ await spawned;
115
+ }
116
+ if (next instanceof Error) {
117
+ // BBJsInstance.create wraps a failed spawn as retryable; the double must too, or a test
118
+ // sees a permanent failure where production sees a transient one.
119
+ throw Object.assign(next, { retry: true });
120
+ }
121
+ const instance = new FakeBBJsInstance(next);
122
+ this.created.push(instance);
123
+ return instance;
124
+ }
125
+ }