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

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,3 +1,4 @@
1
+ import { isRetryableError } from '@aztec-labs/foundation/error';
1
2
  import { type Logger, createLogger } from '@aztec-labs/foundation/log';
2
3
  import { Timer } from '@aztec-labs/foundation/timer';
3
4
  import { ProtocolCircuitVks } from '@aztec-labs/noir-protocol-circuits-types/server/vks';
@@ -14,24 +15,43 @@ import { Tx } from '@aztec-labs/stdlib/tx';
14
15
  import type { VerificationKeyData } from '@aztec-labs/stdlib/vks';
15
16
  import { promises as fs } from 'fs';
16
17
 
17
- import { BBJsFactory } from '../bb/bb_js_backend.js';
18
+ import { type BBJsApi, BBJsFactory } from '../bb/bb_js_backend.js';
18
19
  import type { BBConfig } from '../config.js';
19
20
  import { getUltraHonkFlavorForCircuit } from '../honk.js';
20
21
 
22
+ /** Thrown when no live bb process could check a proof, so the proof was neither accepted nor rejected. */
23
+ export class ProofVerifierUnavailableError extends Error {
24
+ constructor(message: string, options?: ErrorOptions) {
25
+ super(message, options);
26
+ this.name = 'ProofVerifierUnavailableError';
27
+ }
28
+ }
29
+
21
30
  export class BBCircuitVerifier implements ClientProtocolCircuitVerifier {
31
+ /** bb instances a Chonk verification tries, while each one's bb dies under it, before the verifier is unavailable. */
32
+ private static readonly MAX_CHONK_VERIFY_ATTEMPTS = 2;
33
+
22
34
  private bbJsFactory: BBJsFactory;
23
35
 
24
- private constructor(
36
+ protected constructor(
25
37
  private config: BBConfig,
26
38
  private logger: Logger,
39
+ bbJsFactory?: BBJsFactory,
27
40
  ) {
28
41
  // BB_NUM_IVC_VERIFIERS bounds the number of long-lived bb processes the pool keeps alive.
29
42
  // If 0, fall back to spawning a fresh bb per verification.
30
- this.bbJsFactory = new BBJsFactory(config.bbBinaryPath, {
31
- poolSize: config.numConcurrentIVCVerifiers > 0 ? config.numConcurrentIVCVerifiers : undefined,
32
- logger,
33
- debugDir: config.bbDebugOutputDir,
34
- });
43
+ const poolSize = config.numConcurrentIVCVerifiers > 0 ? config.numConcurrentIVCVerifiers : undefined;
44
+ this.bbJsFactory =
45
+ bbJsFactory ??
46
+ new BBJsFactory(config.bbBinaryPath, {
47
+ poolSize,
48
+ // A pooled instance outlives the call that borrowed it, so it replaces a bb process that dies
49
+ // under it and the next borrower gets a working one. Each verification stands alone, so a
50
+ // replacement has nothing to carry over. A fresh-per-call instance has nothing to heal.
51
+ respawn: poolSize !== undefined,
52
+ logger,
53
+ debugDir: config.bbDebugOutputDir,
54
+ });
35
55
  }
36
56
 
37
57
  public stop(): Promise<void> {
@@ -85,9 +105,13 @@ export class BBCircuitVerifier implements ClientProtocolCircuitVerifier {
85
105
  } satisfies CircuitVerificationStats);
86
106
  }
87
107
 
88
- /** Verify a Chonk (IVC) proof from a transaction via bb.js API. */
108
+ /**
109
+ * Verify a Chonk (IVC) proof from a transaction via bb.js API. Throws {@link ProofVerifierUnavailableError} when no
110
+ * live bb process could check the proof; any other failure returns `valid: false`.
111
+ */
89
112
  public async verifyProof(tx: Tx): Promise<IVCProofVerificationResult> {
90
113
  const proofType = 'Chonk';
114
+ const txHash = tx.getTxHash().toString();
91
115
  try {
92
116
  const totalTimer = new Timer();
93
117
 
@@ -98,8 +122,11 @@ export class BBCircuitVerifier implements ClientProtocolCircuitVerifier {
98
122
  const proofWithPubInputs = tx.chonkProof.attachPublicInputs(tx.data.publicInputs().toFields());
99
123
  const fieldsAsBuffers = proofWithPubInputs.fieldsWithPublicInputs.map(f => new Uint8Array(f.toBuffer()));
100
124
 
101
- await using instance = await this.bbJsFactory.getInstance();
102
- const { verified, durationMs } = await instance.verifyChonkProof(fieldsAsBuffers, verificationKey.keyAsBytes);
125
+ const { verified, durationMs } = await this.verifyChonkProofOnLiveInstance(
126
+ fieldsAsBuffers,
127
+ verificationKey.keyAsBytes,
128
+ txHash,
129
+ );
103
130
 
104
131
  if (!verified) {
105
132
  throw new Error(`Failed to verify ${proofType} proof for ${circuit}!`);
@@ -114,10 +141,59 @@ export class BBCircuitVerifier implements ClientProtocolCircuitVerifier {
114
141
 
115
142
  return { valid: true, durationMs, totalDurationMs: totalTimer.ms() };
116
143
  } catch (err) {
117
- this.logger.warn(`Failed to verify ${proofType} proof for tx ${tx.getTxHash().toString()}: ${String(err)}`);
144
+ if (err instanceof ProofVerifierUnavailableError) {
145
+ throw err;
146
+ }
147
+ this.logger.warn(`Failed to verify ${proofType} proof`, { txHash, err });
118
148
  return { valid: false, durationMs: 0, totalDurationMs: 0 };
119
149
  }
120
150
  }
151
+
152
+ /**
153
+ * Runs a Chonk verification on a pooled bb instance. A call that failed for environmental reasons — its bb process
154
+ * died, or could not be started — is retried; any other failure is the verification's own verdict and is rethrown.
155
+ *
156
+ * The error says so itself, through the `retry` property bb.js sets. Asking the instance whether it is still alive
157
+ * would be a guess: the process can die between the answer and the next call.
158
+ */
159
+ private async verifyChonkProofOnLiveInstance(
160
+ fieldsWithPublicInputs: Uint8Array[],
161
+ verificationKey: Uint8Array,
162
+ txHash: string,
163
+ ): Promise<{ verified: boolean; durationMs: number }> {
164
+ for (let attempt = 1; ; attempt++) {
165
+ let borrowed: BBJsApi & AsyncDisposable;
166
+ try {
167
+ borrowed = await this.bbJsFactory.getInstance();
168
+ } catch (err) {
169
+ // A bb that could not be started is worth another go, within the same budget a death gets.
170
+ // Anything else — the factory destroyed under us — will not improve by asking again. Either
171
+ // way no proof was checked, so this is never the proof's fault.
172
+ if (isRetryableError(err) && attempt < BBCircuitVerifier.MAX_CHONK_VERIFY_ATTEMPTS) {
173
+ this.logger.warn('no bb instance available to verify a proof; retrying', { txHash, attempt });
174
+ continue;
175
+ }
176
+ throw new ProofVerifierUnavailableError('No bb instance available to verify the proof', { cause: err });
177
+ }
178
+
179
+ await using instance = borrowed;
180
+ try {
181
+ return await instance.verifyChonkProof(fieldsWithPublicInputs, verificationKey);
182
+ } catch (err) {
183
+ // Only an environmental failure is worth retrying; anything else is the verification's own
184
+ // verdict and belongs to the caller.
185
+ if (!isRetryableError(err)) {
186
+ throw err;
187
+ }
188
+ if (attempt >= BBCircuitVerifier.MAX_CHONK_VERIFY_ATTEMPTS) {
189
+ throw new ProofVerifierUnavailableError(`bb died while verifying the proof, on ${attempt} attempts`, {
190
+ cause: err,
191
+ });
192
+ }
193
+ this.logger.warn('bb died while verifying a proof; retrying', { txHash, attempt });
194
+ }
195
+ }
196
+ }
121
197
  }
122
198
 
123
199
  /** Split a buffer into 32-byte Uint8Array field elements. */
@@ -102,7 +102,12 @@ export class QueuedIVCVerifier implements ClientProtocolCircuitVerifier {
102
102
  }
103
103
 
104
104
  async stop(): Promise<void> {
105
- await this.queue.end();
106
- await this.verifier.stop();
105
+ // Stopped first so that verifications waiting for a bb instance fail rather than keep the queue from draining. Queued
106
+ // verifications that have not started yet fail too.
107
+ try {
108
+ await this.verifier.stop();
109
+ } finally {
110
+ await this.queue.end();
111
+ }
107
112
  }
108
113
  }