Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
chonk_batch_verifier.cpp
Go to the documentation of this file.
1#ifndef __wasm__
9
10namespace bb {
11
13 uint32_t num_cores,
14 uint32_t batch_size,
15 ResultCallback on_result)
16{
17 {
18 std::lock_guard lock(mutex_);
19 if (running_ || stopping_) {
20 throw_or_abort("ChonkBatchVerifier: already started");
21 }
22 vks_ = std::move(vks);
23 num_cores_ = std::max(1u, num_cores);
24 batch_size_ = std::max(1u, batch_size);
25 on_result_ = std::move(on_result);
26 queue_.clear();
27 in_flight_ids_.clear();
28 shutdown_ = false;
29 running_ = true;
30 }
31
32 coordinator_thread_ = std::thread([this]() { coordinator_loop(); });
33 info("ChonkBatchVerifier started with ", num_cores_, " cores, batch_size=", batch_size_);
34}
35
37{
38 VerifyResult failure;
39 bool has_failure = false;
40 {
41 std::lock_guard lock(mutex_);
42 if (!running_ || shutdown_) {
43 throw_or_abort("ChonkBatchVerifier: enqueue called while verifier is not running");
44 }
45 if (in_flight_ids_.contains(request.request_id)) {
46 throw_or_abort("ChonkBatchVerifier: duplicate request_id: " + std::to_string(request.request_id));
47 }
48 if (queue_.size() >= MAX_QUEUE_SIZE) {
49 throw_or_abort("ChonkBatchVerifier: queue is full");
50 }
51
52 request.enqueue_time = std::chrono::steady_clock::now();
53 in_flight_ids_.insert(request.request_id);
54
55 if (request.vk_index >= vks_.size()) {
56 failure = VerifyResult::failed(request.request_id, "invalid vk_index: " + std::to_string(request.vk_index));
57 has_failure = true;
58 } else if (vks_[request.vk_index] == nullptr || vks_[request.vk_index]->vk == nullptr) {
59 failure = VerifyResult::failed(request.request_id, "missing verification key");
60 has_failure = true;
61 } else {
62 const size_t expected_proof_size = static_cast<size_t>(vks_[request.vk_index]->vk->num_public_inputs) +
64 if (request.proof.size() != expected_proof_size) {
65 failure = VerifyResult::failed(request.request_id,
66 "proof has wrong size: expected " + std::to_string(expected_proof_size) +
67 ", got " + std::to_string(request.proof.size()));
68 has_failure = true;
69 } else {
70 queue_.push_back(std::move(request));
71 }
72 }
73 }
74 if (has_failure) {
75 dispatch(std::move(failure));
76 } else {
77 cv_.notify_one();
78 }
79}
80
82{
83 std::thread coordinator_thread;
84 {
85 std::unique_lock lock(mutex_);
86 if (stopping_) {
87 stopped_cv_.wait(lock, [this] { return !stopping_; });
88 return;
89 }
90 if (!running_ && !coordinator_thread_.joinable()) {
91 return;
92 }
93 stopping_ = true;
94 shutdown_ = true;
95 if (coordinator_thread_.joinable()) {
96 coordinator_thread = std::move(coordinator_thread_);
97 }
98 }
99 cv_.notify_one();
100 if (coordinator_thread.joinable()) {
101 coordinator_thread.join();
102 }
103 {
104 std::lock_guard lock(mutex_);
105 running_ = false;
106 queue_.clear();
107 in_flight_ids_.clear();
108 stopping_ = false;
109 }
110 stopped_cv_.notify_all();
111 info("ChonkBatchVerifier stopped");
112}
113
115{
116 bool should_stop = false;
117 {
118 std::lock_guard lock(mutex_);
119 should_stop = running_ || coordinator_thread_.joinable() || stopping_;
120 }
121 if (should_stop) {
122 stop();
123 }
124}
125
127{
128 const uint64_t request_id = result.request_id;
129 try {
130 // Result delivery owns the request id until the callback completes.
131 if (on_result_) {
133 }
134 } catch (const std::exception& e) {
135 info("ChonkBatchVerifier: result callback threw: ", e.what());
136 } catch (...) {
137 info("ChonkBatchVerifier: result callback threw unknown exception");
138 }
139 std::lock_guard lock(mutex_);
140 in_flight_ids_.erase(request_id);
141}
142
144{
145 while (true) {
146 // ── Collect a batch ──────────────────────────────────────────────
148 {
149 std::unique_lock lock(mutex_);
150
151 // Wait until we have work or are told to shut down.
152 // No timeout needed: while we're processing a batch, new proofs
153 // accumulate in the queue. When idle, process whatever arrives immediately.
154 cv_.wait(lock, [this] { return shutdown_ || !queue_.empty(); });
155
156 // Take up to batch_size_ items (may be a partial batch)
157 size_t take = std::min(queue_.size(), static_cast<size_t>(batch_size_));
158 if (take > 0) {
159 auto end = queue_.begin() + static_cast<ptrdiff_t>(take);
160 batch.assign(std::make_move_iterator(queue_.begin()), std::make_move_iterator(end));
161 queue_.erase(queue_.begin(), end);
162 }
163
164 if (batch.empty()) {
165 if (shutdown_) {
166 break;
167 }
168 continue;
169 }
170
171 // Invalid VK indices and malformed proof sizes are rejected at enqueue time.
172 }
173
174 if (batch.empty()) {
175 continue;
176 }
177
178 // ── Phase 1: parallel reduce (all cores, work-stealing) ──────────
179 auto reduce_start = std::chrono::steady_clock::now();
180 auto reduce_results = parallel_reduce(batch);
181
182 // Separate passed from failed (emit failures immediately)
183 std::vector<size_t> passed_indices;
184 passed_indices.reserve(reduce_results.size());
185 for (size_t i = 0; i < reduce_results.size(); ++i) {
186 auto& rr = reduce_results[i];
187 if (!rr.all_checks_passed) {
188 auto result = VerifyResult::failed(rr.request_id, rr.error_message);
189 result.time_in_queue_ms = ms_between(rr.enqueue_time, reduce_start);
190 result.time_in_verify_ms = rr.reduce_ms;
192 } else {
193 passed_indices.push_back(i);
194 }
195 }
196
197 if (passed_indices.empty()) {
198 continue;
199 }
200
201 // ── Phase 2: batch TripleIPA verification ────────────────────────────
202 auto ipa_start = std::chrono::steady_clock::now();
203 bool ok = batch_check(reduce_results, passed_indices);
204 const double ipa_ms = ms_since(ipa_start);
205 const double reduce_ms = ms_between(reduce_start, ipa_start);
206
207 info("ChonkBatchVerifier: batch of ",
208 passed_indices.size(),
209 ": reduce=",
210 reduce_ms,
211 "ms, batch_check=",
212 ipa_ms,
213 "ms, result=",
214 ok ? "OK" : "BISECTING");
215
216 if (ok) {
217 emit_ok(reduce_results, passed_indices, reduce_start, ipa_ms, 0);
218 } else {
219 bisect(reduce_results, passed_indices, 0, reduce_start);
220 }
221 }
222}
223
225 const std::vector<VerifyRequest>& batch)
226{
227 const size_t num_proofs = batch.size();
228 std::vector<ReduceResult> results(num_proofs);
229 std::atomic<size_t> work_index{ 0 };
230
231 uint32_t num_workers = std::min(num_cores_, static_cast<uint32_t>(num_proofs));
232 std::vector<std::thread> workers;
233 workers.reserve(num_workers);
234
235 for (uint32_t w = 0; w < num_workers; ++w) {
236 workers.emplace_back([&]() {
237 // Each worker thread is single-threaded for reduce_to_triple_ipa_opening
239 while (true) {
240 size_t idx = work_index.fetch_add(1, std::memory_order_relaxed);
241 if (idx >= num_proofs) {
242 break;
243 }
244 auto& req = batch[idx];
245 auto t0 = std::chrono::steady_clock::now();
246
247 try {
248 ChonkNativeVerifier verifier(vks_[req.vk_index]);
249 auto reduced = verifier.reduce_to_triple_ipa_opening(req.proof);
250
251 results[idx] = ReduceResult{
252 .request_id = req.request_id,
253 .triple_ipa_opening = std::move(reduced.triple_ipa_opening),
254 .all_checks_passed = reduced.all_checks_passed,
255 .error_message = reduced.all_checks_passed ? "" : "reduction failed",
256 .enqueue_time = req.enqueue_time,
257 .reduce_ms = ms_since(t0),
258 };
259 } catch (const std::exception& e) {
260 results[idx] = ReduceResult{
261 .request_id = req.request_id,
262 .triple_ipa_opening = std::nullopt,
263 .all_checks_passed = false,
264 .error_message = std::string("reduce_to_triple_ipa_opening threw: ") + e.what(),
265 .enqueue_time = req.enqueue_time,
266 .reduce_ms = ms_since(t0),
267 };
268 } catch (...) {
269 results[idx] = ReduceResult{
270 .request_id = req.request_id,
271 .triple_ipa_opening = std::nullopt,
272 .all_checks_passed = false,
273 .error_message = "reduce_to_triple_ipa_opening threw unknown exception",
274 .enqueue_time = req.enqueue_time,
275 .reduce_ms = ms_since(t0),
276 };
277 }
278 }
279 });
280 }
281 for (auto& t : workers) {
282 t.join();
283 }
284
285 return results;
286}
287
288bool ChonkBatchVerifier::batch_check(const std::vector<ReduceResult>& results, const std::vector<size_t>& indices)
289{
290 if (indices.empty()) {
291 return true;
292 }
293
295
296 try {
298 accumulators.reserve(indices.size());
299 for (size_t idx : indices) {
300 accumulators.push_back(results[idx].triple_ipa_opening.value().reduce_to_accumulator());
301 }
302
303 return ECCVMVerifier::batch_verify_accumulators(accumulators);
304 } catch (const std::exception& e) {
305 info("ChonkBatchVerifier: batch_check exception: ", e.what());
306 return false;
307 }
308}
309
311 std::vector<size_t> indices,
312 uint32_t depth,
313 std::chrono::steady_clock::time_point reduce_start)
314{
315 // Base case: single proof identified as the failure
316 if (indices.size() == 1) {
317 auto& rr = results[indices[0]];
318 auto result = VerifyResult::failed(rr.request_id, "batch check failed (bisected to individual)");
319 result.time_in_queue_ms = ms_between(rr.enqueue_time, std::chrono::steady_clock::now());
320 result.time_in_verify_ms = rr.reduce_ms;
321 result.batch_failure_count = depth + 1;
323 return;
324 }
325
326 info("ChonkBatchVerifier: bisecting ", indices.size(), " proofs at depth ", depth);
327
328 size_t mid = indices.size() / 2;
329 std::vector<size_t> left(indices.begin(), indices.begin() + static_cast<ptrdiff_t>(mid));
330 std::vector<size_t> right(indices.begin() + static_cast<ptrdiff_t>(mid), indices.end());
331
332 // Check left half; if it passes, all failures must be in the right half (skip redundant check)
333 auto t0 = std::chrono::steady_clock::now();
334 bool left_ok = batch_check(results, left);
335 double left_ms = ms_since(t0);
336
337 if (left_ok) {
338 emit_ok(results, left, reduce_start, left_ms, depth + 1);
339 // All failures are in the right half — recurse directly without re-checking
340 bisect(results, std::move(right), depth + 1, reduce_start);
341 } else {
342 // Left failed — need to check right independently
343 bisect(results, std::move(left), depth + 1, reduce_start);
344
345 auto t1 = std::chrono::steady_clock::now();
346 bool right_ok = batch_check(results, right);
347 double right_ms = ms_since(t1);
348
349 if (right_ok) {
350 emit_ok(results, right, reduce_start, right_ms, depth + 1);
351 } else {
352 bisect(results, std::move(right), depth + 1, reduce_start);
353 }
354 }
355}
356
358 const std::vector<size_t>& indices,
359 std::chrono::steady_clock::time_point reduce_start,
360 double pcs_ms,
361 uint32_t depth)
362{
363 for (size_t idx : indices) {
364 auto& rr = results[idx];
366 .request_id = rr.request_id,
367 .status = static_cast<uint8_t>(VerifyStatus::OK),
368 .error_message = "",
369 .time_in_queue_ms = ms_between(rr.enqueue_time, reduce_start),
370 .time_in_verify_ms = rr.reduce_ms + pcs_ms,
371 .batch_failure_count = depth,
372 });
373 }
374}
375
376} // namespace bb
377#endif
std::vector< ReduceResult > parallel_reduce(const std::vector< VerifyRequest > &batch)
void bisect(std::vector< ReduceResult > &results, std::vector< size_t > indices, uint32_t depth, std::chrono::steady_clock::time_point reduce_start)
static double ms_between(std::chrono::steady_clock::time_point from, std::chrono::steady_clock::time_point to)
std::function< void(VerifyResult)> ResultCallback
void dispatch(VerifyResult result)
bool batch_check(const std::vector< ReduceResult > &results, const std::vector< size_t > &indices)
std::condition_variable stopped_cv_
static double ms_since(std::chrono::steady_clock::time_point t)
static constexpr size_t MAX_QUEUE_SIZE
std::vector< std::shared_ptr< MegaZKFlavor::VKAndHash > > vks_
std::deque< VerifyRequest > queue_
std::unordered_set< uint64_t > in_flight_ids_
void enqueue(VerifyRequest request)
Enqueue a proof for verification.
void emit_ok(const std::vector< ReduceResult > &results, const std::vector< size_t > &indices, std::chrono::steady_clock::time_point reduce_start, double pcs_ms, uint32_t depth)
std::condition_variable cv_
void stop()
Stop the processor, flushing remaining proofs.
void start(std::vector< std::shared_ptr< MegaZKFlavor::VKAndHash > > vks, uint32_t num_cores, uint32_t batch_size, ResultCallback on_result)
Start the coordinator thread.
Verifier for Chonk IVC proofs (both native and recursive).
TripleIpaReductionResult reduce_to_triple_ipa_opening(const Proof &proof)
Run Chonk verification up to but not including TripleIPA verification.
static bool batch_verify_accumulators(std::span< const TripleIpaAccumulator > accumulators)
#define info(...)
Definition log.hpp:93
Entry point for Barretenberg command-line interface.
Definition api.hpp:5
void set_parallel_for_concurrency(size_t num_cores)
Definition thread.cpp:24
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
std::string to_string(bb::avm2::ValueTag tag)
Per-proof result from the reduce phase.
static constexpr size_t PROOF_LENGTH_WITHOUT_PUB_INPUTS
size_t size() const
A request to verify a single Chonk proof.
std::chrono::steady_clock::time_point enqueue_time
Result of verifying a single proof within a batch.
static VerifyResult failed(uint64_t id, std::string msg)
void throw_or_abort(std::string const &err)
VectorField result