@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.
- package/dest/bb/bb_js_backend.d.ts +29 -19
- package/dest/bb/bb_js_backend.d.ts.map +1 -1
- package/dest/bb/bb_js_backend.js +54 -72
- package/dest/prover/server/bb_prover.d.ts +1 -1
- package/dest/prover/server/bb_prover.d.ts.map +1 -1
- package/dest/prover/server/bb_prover.js +3 -2
- package/dest/test/fake_bb_js.d.ts +66 -0
- package/dest/test/fake_bb_js.d.ts.map +1 -0
- package/dest/test/fake_bb_js.js +111 -0
- package/dest/verifier/bb_verifier.d.ts +14 -3
- package/dest/verifier/bb_verifier.d.ts.map +1 -1
- package/dest/verifier/bb_verifier.js +101 -32
- package/dest/verifier/queued_chonk_verifier.d.ts +1 -1
- package/dest/verifier/queued_chonk_verifier.d.ts.map +1 -1
- package/dest/verifier/queued_chonk_verifier.js +7 -2
- package/package.json +14 -14
- package/src/bb/bb_js_backend.ts +66 -75
- package/src/prover/server/bb_prover.ts +3 -2
- package/src/test/fake_bb_js.ts +125 -0
- package/src/verifier/bb_verifier.ts +87 -11
- package/src/verifier/queued_chonk_verifier.ts +7 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bb_verifier.d.ts","sourceRoot":"","sources":["../../src/verifier/bb_verifier.ts"],"names":[],"mappings":"
|
|
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
|
-
|
|
83
|
-
|
|
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
|
-
/**
|
|
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
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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,
|
|
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,
|
|
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
|
-
|
|
100
|
-
|
|
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.
|
|
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.
|
|
74
|
-
"@aztec-foundation/noir-noirc_abi": "6.0.0-rc.
|
|
75
|
-
"@aztec-foundation/noir-types": "6.0.0-rc.
|
|
76
|
-
"@aztec-labs/constants": "6.0.0-rc.
|
|
77
|
-
"@aztec-labs/foundation": "6.0.0-rc.
|
|
78
|
-
"@aztec-labs/noir-protocol-circuits-types": "6.0.0-rc.
|
|
79
|
-
"@aztec-labs/simulator": "6.0.0-rc.
|
|
80
|
-
"@aztec-labs/stdlib": "6.0.0-rc.
|
|
81
|
-
"@aztec-labs/telemetry-client": "6.0.0-rc.
|
|
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.
|
|
90
|
-
"@aztec-labs/noir-test-contracts.js": "6.0.0-rc.
|
|
91
|
-
"@aztec-labs/protocol-contracts": "6.0.0-rc.
|
|
92
|
-
"@aztec-labs/world-state": "6.0.0-rc.
|
|
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",
|
package/src/bb/bb_js_backend.ts
CHANGED
|
@@ -76,12 +76,19 @@ export interface BBJsApi {
|
|
|
76
76
|
export class BBJsInstance implements BBJsApi {
|
|
77
77
|
private constructor(private api: Barretenberg) {}
|
|
78
78
|
|
|
79
|
-
/**
|
|
80
|
-
|
|
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
|
-
/**
|
|
279
|
-
private
|
|
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.
|
|
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 (
|
|
293
|
-
|
|
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
|
|
300
|
-
*
|
|
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.
|
|
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
|
-
|
|
312
|
-
|
|
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
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
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
|
-
|
|
320
|
-
|
|
321
|
-
throw new Error('BBJsFactory
|
|
347
|
+
if (this.destroyed) {
|
|
348
|
+
await this.release(slot);
|
|
349
|
+
throw new Error('BBJsFactory has been destroyed');
|
|
322
350
|
}
|
|
323
|
-
return this.
|
|
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
|
|
329
|
-
*
|
|
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
|
-
|
|
337
|
-
this.pool = undefined;
|
|
338
|
-
if (!pool) {
|
|
364
|
+
if (!this.slots) {
|
|
339
365
|
return;
|
|
340
366
|
}
|
|
341
367
|
const idle: BBJsApi[] = [];
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
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
|
-
|
|
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
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
+
}
|