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 vks_ = std::move(vks);
18 num_cores_ = std::max(1u, num_cores);
19 batch_size_ = std::max(1u, batch_size);
20 on_result_ = std::move(on_result);
21 shutdown_ = false;
22
23 coordinator_thread_ = std::thread([this]() { coordinator_loop(); });
24 info("ChonkBatchVerifier started with ", num_cores_, " cores, batch_size=", batch_size_);
25}
26
28{
29 {
30 std::lock_guard lock(mutex_);
31 request.enqueue_time = std::chrono::steady_clock::now();
32 queue_.push_back(std::move(request));
33 }
34 cv_.notify_one();
35}
36
38{
39 {
40 std::lock_guard lock(mutex_);
41 shutdown_ = true;
42 }
43 cv_.notify_one();
44 if (coordinator_thread_.joinable()) {
46 }
47 info("ChonkBatchVerifier stopped");
48}
49
56
58{
59 while (true) {
60 // ── Collect a batch ──────────────────────────────────────────────
62 {
63 std::unique_lock lock(mutex_);
64
65 // Wait until we have work or are told to shut down.
66 // No timeout needed: while we're processing a batch, new proofs
67 // accumulate in the queue. When idle, process whatever arrives immediately.
68 cv_.wait(lock, [this] { return shutdown_ || !queue_.empty(); });
69
70 // Take up to batch_size_ items (may be a partial batch)
71 size_t take = std::min(queue_.size(), static_cast<size_t>(batch_size_));
72 if (take > 0) {
73 auto end = queue_.begin() + static_cast<ptrdiff_t>(take);
74 batch.assign(std::make_move_iterator(queue_.begin()), std::make_move_iterator(end));
75 queue_.erase(queue_.begin(), end);
76 }
77
78 if (batch.empty()) {
79 if (shutdown_) {
80 break;
81 }
82 continue;
83 }
84
85 // Filter invalid-VK requests before releasing the lock
86 auto it = batch.begin();
87 while (it != batch.end()) {
88 if (it->vk_index >= vks_.size()) {
90 VerifyResult::failed(it->request_id, "invalid vk_index: " + std::to_string(it->vk_index)));
91 it = batch.erase(it);
92 } else {
93 ++it;
94 }
95 }
96 }
97
98 if (batch.empty()) {
99 continue;
100 }
101
102 // ── Phase 1: parallel reduce (all cores, work-stealing) ──────────
103 auto reduce_start = std::chrono::steady_clock::now();
104 auto reduce_results = parallel_reduce(batch);
105
106 // Separate passed from failed (emit failures immediately)
107 std::vector<size_t> passed_indices;
108 passed_indices.reserve(reduce_results.size());
109 for (size_t i = 0; i < reduce_results.size(); ++i) {
110 auto& rr = reduce_results[i];
111 if (!rr.all_checks_passed) {
112 auto result = VerifyResult::failed(rr.request_id, rr.error_message);
113 result.time_in_queue_ms = ms_between(rr.enqueue_time, reduce_start);
114 result.time_in_verify_ms = rr.reduce_ms;
115 on_result_(std::move(result));
116 } else {
117 passed_indices.push_back(i);
118 }
119 }
120
121 if (passed_indices.empty()) {
122 continue;
123 }
124
125 // ── Phase 2: batch IPA verification ────────────────────────────
126 auto ipa_start = std::chrono::steady_clock::now();
127 bool ok = batch_check(reduce_results, passed_indices);
128 double ipa_ms = ms_since(ipa_start);
129 double reduce_ms = ms_between(reduce_start, ipa_start);
130
131 info("ChonkBatchVerifier: batch of ",
132 passed_indices.size(),
133 ": reduce=",
134 reduce_ms,
135 "ms, batch_check=",
136 ipa_ms,
137 "ms, result=",
138 ok ? "OK" : "BISECTING");
139
140 if (ok) {
141 emit_ok(reduce_results, passed_indices, reduce_start, ipa_ms, 0);
142 } else {
143 bisect(reduce_results, passed_indices, 0, reduce_start);
144 }
145 }
146}
147
149 const std::vector<VerifyRequest>& batch)
150{
151 const size_t num_proofs = batch.size();
152 std::vector<ReduceResult> results(num_proofs);
153 std::atomic<size_t> work_index{ 0 };
154
155 uint32_t num_workers = std::min(num_cores_, static_cast<uint32_t>(num_proofs));
156 std::vector<std::thread> workers;
157 workers.reserve(num_workers);
158
159 for (uint32_t w = 0; w < num_workers; ++w) {
160 workers.emplace_back([&]() {
161 // Each worker thread is single-threaded for reduce_to_ipa_claim
163 while (true) {
164 size_t idx = work_index.fetch_add(1, std::memory_order_relaxed);
165 if (idx >= num_proofs) {
166 break;
167 }
168 auto& req = batch[idx];
169 auto t0 = std::chrono::steady_clock::now();
170
171 try {
172 ChonkNativeVerifier verifier(vks_[req.vk_index]);
173 auto reduced = verifier.reduce_to_ipa_claim(req.proof);
174
175 results[idx] = ReduceResult{
176 .request_id = req.request_id,
177 .ipa_claim = std::move(reduced.ipa_claim),
178 .ipa_proof = std::move(reduced.ipa_proof),
179 .all_checks_passed = reduced.all_checks_passed,
180 .error_message = reduced.all_checks_passed ? "" : "reduction failed",
181 .enqueue_time = req.enqueue_time,
182 .reduce_ms = ms_since(t0),
183 };
184 } catch (const std::exception& e) {
185 results[idx] = ReduceResult{
186 .request_id = req.request_id,
187 .all_checks_passed = false,
188 .error_message = std::string("reduce_to_ipa_claim threw: ") + e.what(),
189 .enqueue_time = req.enqueue_time,
190 .reduce_ms = ms_since(t0),
191 };
192 } catch (...) {
193 results[idx] = ReduceResult{
194 .request_id = req.request_id,
195 .all_checks_passed = false,
196 .error_message = "reduce_to_ipa_claim threw unknown exception",
197 .enqueue_time = req.enqueue_time,
198 .reduce_ms = ms_since(t0),
199 };
200 }
201 }
202 });
203 }
204 for (auto& t : workers) {
205 t.join();
206 }
207
208 return results;
209}
210
211bool ChonkBatchVerifier::batch_check(const std::vector<ReduceResult>& results, const std::vector<size_t>& indices)
212{
213 if (indices.empty()) {
214 return true;
215 }
216
218
219 try {
220 // Collect IPA claims and transcripts for batch verification
223 claims.reserve(indices.size());
224 transcripts.reserve(indices.size());
225 for (size_t idx : indices) {
226 claims.push_back(results[idx].ipa_claim);
227 transcripts.push_back(std::make_shared<NativeTranscript>(results[idx].ipa_proof));
228 }
229
231 return IPA<curve::Grumpkin>::batch_reduce_verify(ipa_vk, claims, transcripts);
232 } catch (const std::exception& e) {
233 info("ChonkBatchVerifier: batch_check exception: ", e.what());
234 return false;
235 }
236}
237
239 std::vector<size_t> indices,
240 uint32_t depth,
241 std::chrono::steady_clock::time_point reduce_start)
242{
243 // Base case: single proof identified as the failure
244 if (indices.size() == 1) {
245 auto& rr = results[indices[0]];
246 auto result = VerifyResult::failed(rr.request_id, "batch check failed (bisected to individual)");
247 result.time_in_queue_ms = ms_between(rr.enqueue_time, std::chrono::steady_clock::now());
248 result.time_in_verify_ms = rr.reduce_ms;
249 result.batch_failure_count = depth + 1;
250 on_result_(std::move(result));
251 return;
252 }
253
254 info("ChonkBatchVerifier: bisecting ", indices.size(), " proofs at depth ", depth);
255
256 size_t mid = indices.size() / 2;
257 std::vector<size_t> left(indices.begin(), indices.begin() + static_cast<ptrdiff_t>(mid));
258 std::vector<size_t> right(indices.begin() + static_cast<ptrdiff_t>(mid), indices.end());
259
260 // Check left half; if it passes, all failures must be in the right half (skip redundant check)
261 auto t0 = std::chrono::steady_clock::now();
262 bool left_ok = batch_check(results, left);
263 double left_ms = ms_since(t0);
264
265 if (left_ok) {
266 emit_ok(results, left, reduce_start, left_ms, depth + 1);
267 // All failures are in the right half — recurse directly without re-checking
268 bisect(results, std::move(right), depth + 1, reduce_start);
269 } else {
270 // Left failed — need to check right independently
271 bisect(results, std::move(left), depth + 1, reduce_start);
272
273 auto t1 = std::chrono::steady_clock::now();
274 bool right_ok = batch_check(results, right);
275 double right_ms = ms_since(t1);
276
277 if (right_ok) {
278 emit_ok(results, right, reduce_start, right_ms, depth + 1);
279 } else {
280 bisect(results, std::move(right), depth + 1, reduce_start);
281 }
282 }
283}
284
286 const std::vector<size_t>& indices,
287 std::chrono::steady_clock::time_point reduce_start,
288 double ipa_ms,
289 uint32_t depth)
290{
291 for (size_t idx : indices) {
292 auto& rr = results[idx];
294 .request_id = rr.request_id,
295 .status = static_cast<uint8_t>(VerifyStatus::OK),
296 .time_in_queue_ms = ms_between(rr.enqueue_time, reduce_start),
297 .time_in_verify_ms = rr.reduce_ms + ipa_ms,
298 .batch_failure_count = depth,
299 });
300 }
301}
302
303} // namespace bb
304#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 emit_ok(const std::vector< ReduceResult > &results, const std::vector< size_t > &indices, std::chrono::steady_clock::time_point reduce_start, double ipa_ms, uint32_t depth)
bool batch_check(const std::vector< ReduceResult > &results, const std::vector< size_t > &indices)
static double ms_since(std::chrono::steady_clock::time_point t)
std::vector< std::shared_ptr< MegaZKFlavor::VKAndHash > > vks_
std::deque< VerifyRequest > queue_
void enqueue(VerifyRequest request)
Enqueue a proof for verification.
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).
IPAReductionResult reduce_to_ipa_claim(const Proof &proof)
Run Chonk verification up to but not including IPA, returning the IPA claim for deferred verification...
static constexpr size_t ECCVM_FIXED_SIZE
IPA (inner product argument) commitment scheme class.
Definition ipa.hpp:86
Representation of the Grumpkin Verifier Commitment Key inside a bn254 circuit.
#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:23
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.
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)