1use std::collections::{BTreeMap, BTreeSet, HashSet};
2use std::fmt;
3use std::iter;
4
5use group::ff::Field;
6use pasta_curves::arithmetic::FieldExt;
7
8use super::{metadata, util, MockProver, Region};
9use crate::{
10 dev::Value,
11 plonk::{Any, Column, ConstraintSystem, Expression, Gate},
12 poly::Rotation,
13};
14
15mod emitter;
16
17#[derive(Debug, PartialEq)]
19pub enum FailureLocation {
20 InRegion {
22 region: metadata::Region,
24 offset: usize,
27 },
28 OutsideRegion {
30 row: usize,
32 },
33}
34
35impl fmt::Display for FailureLocation {
36 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
37 match self {
38 Self::InRegion { region, offset } => write!(f, "in {} at offset {}", region, offset),
39 Self::OutsideRegion { row } => {
40 write!(f, "outside any region, on row {}", row)
41 }
42 }
43 }
44}
45
46impl FailureLocation {
47 pub(super) fn find_expressions<'a, F: Field>(
48 cs: &ConstraintSystem<F>,
49 regions: &[Region],
50 failure_row: usize,
51 failure_expressions: impl Iterator<Item = &'a Expression<F>>,
52 ) -> Self {
53 let failure_columns: HashSet<Column<Any>> = failure_expressions
54 .flat_map(|expression| {
55 expression.evaluate(
56 &|_| vec![],
57 &|_| panic!("virtual selectors are removed during optimization"),
58 &|index, _, _| vec![cs.fixed_queries[index].0.into()],
59 &|index, _, _| vec![cs.advice_queries[index].0.into()],
60 &|index, _, _| vec![cs.instance_queries[index].0.into()],
61 &|a| a,
62 &|mut a, mut b| {
63 a.append(&mut b);
64 a
65 },
66 &|mut a, mut b| {
67 a.append(&mut b);
68 a
69 },
70 &|a, _| a,
71 )
72 })
73 .collect();
74
75 Self::find(regions, failure_row, failure_columns)
76 }
77
78 pub(super) fn find(
80 regions: &[Region],
81 failure_row: usize,
82 failure_columns: HashSet<Column<Any>>,
83 ) -> Self {
84 regions
85 .iter()
86 .enumerate()
87 .find(|(_, r)| {
88 let (start, end) = r.rows.unwrap();
89 (start..=end).contains(&failure_row) && !failure_columns.is_disjoint(&r.columns)
95 })
96 .map(|(r_i, r)| FailureLocation::InRegion {
97 region: (r_i, r.name.clone()).into(),
98 offset: failure_row as usize - r.rows.unwrap().0 as usize,
99 })
100 .unwrap_or_else(|| FailureLocation::OutsideRegion {
101 row: failure_row as usize,
102 })
103 }
104}
105
106#[derive(Debug, PartialEq)]
108pub enum VerifyFailure {
109 CellNotAssigned {
111 gate: metadata::Gate,
113 region: metadata::Region,
115 gate_offset: usize,
118 column: Column<Any>,
120 offset: isize,
124 },
125 ConstraintNotSatisfied {
127 constraint: metadata::Constraint,
129 location: FailureLocation,
134 cell_values: Vec<(metadata::VirtualCell, String)>,
136 },
137 ConstraintPoisoned {
139 constraint: metadata::Constraint,
141 },
142 Lookup {
144 lookup_index: usize,
148 location: FailureLocation,
161 },
162 Permutation {
164 column: metadata::Column,
166 location: FailureLocation,
168 },
169}
170
171impl fmt::Display for VerifyFailure {
172 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
173 match self {
174 Self::CellNotAssigned {
175 gate,
176 region,
177 gate_offset,
178 column,
179 offset,
180 } => {
181 write!(
182 f,
183 "{} uses {} at offset {}, which requires cell in column {:?} at offset {} to be assigned.",
184 region, gate, gate_offset, column, offset
185 )
186 }
187 Self::ConstraintNotSatisfied {
188 constraint,
189 location,
190 cell_values,
191 } => {
192 writeln!(f, "{} is not satisfied {}", constraint, location)?;
193 for (name, value) in cell_values {
194 writeln!(f, "- {} = {}", name, value)?;
195 }
196 Ok(())
197 }
198 Self::ConstraintPoisoned { constraint } => {
199 write!(
200 f,
201 "{} is active on an unusable row - missing selector?",
202 constraint
203 )
204 }
205 Self::Lookup {
206 lookup_index,
207 location,
208 } => write!(f, "Lookup {} is not satisfied {}", lookup_index, location),
209 Self::Permutation { column, location } => {
210 write!(
211 f,
212 "Equality constraint not satisfied by cell ({:?}, {})",
213 column, location
214 )
215 }
216 }
217 }
218}
219
220fn render_cell_not_assigned<F: Field>(
233 gates: &[Gate<F>],
234 gate: &metadata::Gate,
235 region: &metadata::Region,
236 gate_offset: usize,
237 column: Column<Any>,
238 offset: isize,
239) {
240 let mut columns = BTreeMap::<metadata::Column, usize>::default();
245 let mut layout = BTreeMap::<i32, BTreeMap<metadata::Column, _>>::default();
246 for (i, cell) in gates[gate.index].queried_cells().iter().enumerate() {
247 let cell_column = cell.column.into();
248 *columns.entry(cell_column).or_default() += 1;
249 layout
250 .entry(cell.rotation.0)
251 .or_default()
252 .entry(cell_column)
253 .or_insert_with(|| {
254 if cell.column == column && gate_offset as i32 + cell.rotation.0 == offset as i32 {
255 "X".to_string()
256 } else {
257 format!("x{}", i)
258 }
259 });
260 }
261
262 eprintln!("error: cell not assigned");
263 emitter::render_cell_layout(
264 " ",
265 &FailureLocation::InRegion {
266 region: region.clone(),
267 offset: gate_offset,
268 },
269 &columns,
270 &layout,
271 |row_offset, rotation| {
272 if (row_offset.unwrap() + rotation) as isize == offset {
273 eprint!(" <--{{ X marks the spot! 🦜");
274 }
275 },
276 );
277 eprintln!();
278 eprintln!(
279 " Gate '{}' (applied at offset {}) queries these cells.",
280 gate.name, gate_offset
281 );
282}
283
284fn render_constraint_not_satisfied<F: Field>(
302 gates: &[Gate<F>],
303 constraint: &metadata::Constraint,
304 location: &FailureLocation,
305 cell_values: &[(metadata::VirtualCell, String)],
306) {
307 let mut columns = BTreeMap::<metadata::Column, usize>::default();
312 let mut layout = BTreeMap::<i32, BTreeMap<metadata::Column, _>>::default();
313 for (i, (cell, _)) in cell_values.iter().enumerate() {
314 *columns.entry(cell.column).or_default() += 1;
315 layout
316 .entry(cell.rotation)
317 .or_default()
318 .entry(cell.column)
319 .or_insert(format!("x{}", i));
320 }
321
322 eprintln!("error: constraint not satisfied");
323 emitter::render_cell_layout(" ", location, &columns, &layout, |_, rotation| {
324 if rotation == 0 {
325 eprint!(" <--{{ Gate '{}' applied here", constraint.gate.name);
326 }
327 });
328
329 eprintln!();
331 eprintln!(" Constraint '{}':", constraint.name);
332 eprintln!(
333 " {} = 0",
334 emitter::expression_to_string(
335 &gates[constraint.gate.index].polynomials()[constraint.index],
336 &layout
337 )
338 );
339
340 eprintln!();
342 eprintln!(" Assigned cell values:");
343 for (i, (_, value)) in cell_values.iter().enumerate() {
344 eprintln!(" x{} = {}", i, value);
345 }
346}
347
348fn render_lookup<F: FieldExt>(
367 prover: &MockProver<F>,
368 lookup_index: usize,
369 location: &FailureLocation,
370) {
371 let n = prover.n as i32;
372 let cs = &prover.cs;
373 let lookup = &cs.lookups[lookup_index];
374
375 let row = match location {
378 FailureLocation::InRegion { region, offset } => {
379 prover.regions[region.index].rows.unwrap().0 + offset
380 }
381 FailureLocation::OutsideRegion { row } => *row,
382 } as i32;
383
384 let table_columns = lookup.table_expressions.iter().map(|expr| {
387 expr.evaluate(
388 &|_| panic!("no constants in table expressions"),
389 &|_| panic!("no selectors in table expressions"),
390 &|_, column, _| format!("F{}", column),
391 &|_, _, _| panic!("no advice columns in table expressions"),
392 &|_, _, _| panic!("no instance columns in table expressions"),
393 &|_| panic!("no negations in table expressions"),
394 &|_, _| panic!("no sums in table expressions"),
395 &|_, _| panic!("no products in table expressions"),
396 &|_, _| panic!("no scaling in table expressions"),
397 )
398 });
399
400 fn cell_value<'a, F: FieldExt>(
401 column_type: Any,
402 load: impl Fn(usize, usize, Rotation) -> Value<F> + 'a,
403 ) -> impl Fn(usize, usize, Rotation) -> BTreeMap<metadata::VirtualCell, String> + 'a {
404 move |query_index, column_index, rotation| {
405 Some((
406 ((column_type, column_index).into(), rotation.0).into(),
407 match load(query_index, column_index, rotation) {
408 Value::Real(v) => util::format_value(v),
409 Value::Poison => unreachable!(),
410 },
411 ))
412 .into_iter()
413 .collect()
414 }
415 }
416
417 eprintln!("error: lookup input does not exist in table");
418 eprint!(" (");
419 for i in 0..lookup.input_expressions.len() {
420 eprint!("{}L{}", if i == 0 { "" } else { ", " }, i);
421 }
422 eprint!(") ∉ (");
423 for (i, column) in table_columns.enumerate() {
424 eprint!("{}{}", if i == 0 { "" } else { ", " }, column);
425 }
426 eprintln!(")");
427
428 eprintln!();
429 eprintln!(" Lookup inputs:");
430 for (i, input) in lookup.input_expressions.iter().enumerate() {
431 let cell_values = input.evaluate(
433 &|_| BTreeMap::default(),
434 &|_| panic!("virtual selectors are removed during optimization"),
435 &cell_value(
436 Any::Fixed,
437 &util::load(n, row, &cs.fixed_queries, &prover.fixed),
438 ),
439 &cell_value(
440 Any::Advice,
441 &util::load(n, row, &cs.advice_queries, &prover.advice),
442 ),
443 &cell_value(
444 Any::Instance,
445 &util::load_instance(n, row, &cs.instance_queries, &prover.instance),
446 ),
447 &|a| a,
448 &|mut a, mut b| {
449 a.append(&mut b);
450 a
451 },
452 &|mut a, mut b| {
453 a.append(&mut b);
454 a
455 },
456 &|a, _| a,
457 );
458
459 let mut columns = BTreeMap::<metadata::Column, usize>::default();
464 let mut layout = BTreeMap::<i32, BTreeMap<metadata::Column, _>>::default();
465 for (i, (cell, _)) in cell_values.iter().enumerate() {
466 *columns.entry(cell.column).or_default() += 1;
467 layout
468 .entry(cell.rotation)
469 .or_default()
470 .entry(cell.column)
471 .or_insert(format!("x{}", i));
472 }
473
474 if i != 0 {
475 eprintln!();
476 }
477 eprintln!(
478 " L{} = {}",
479 i,
480 emitter::expression_to_string(input, &layout)
481 );
482 eprintln!(" ^");
483 emitter::render_cell_layout(" | ", location, &columns, &layout, |_, rotation| {
484 if rotation == 0 {
485 eprint!(" <--{{ Lookup inputs queried here");
486 }
487 });
488
489 eprintln!(" |");
491 eprintln!(" | Assigned cell values:");
492 for (i, (_, value)) in cell_values.iter().enumerate() {
493 eprintln!(" | x{} = {}", i, value);
494 }
495 }
496}
497
498impl VerifyFailure {
499 pub(super) fn emit<F: FieldExt>(&self, prover: &MockProver<F>) {
501 match self {
502 Self::CellNotAssigned {
503 gate,
504 region,
505 gate_offset,
506 column,
507 offset,
508 } => render_cell_not_assigned(
509 &prover.cs.gates,
510 gate,
511 region,
512 *gate_offset,
513 *column,
514 *offset,
515 ),
516 Self::ConstraintNotSatisfied {
517 constraint,
518 location,
519 cell_values,
520 } => {
521 render_constraint_not_satisfied(&prover.cs.gates, constraint, location, cell_values)
522 }
523 Self::Lookup {
524 lookup_index,
525 location,
526 } => render_lookup(prover, *lookup_index, location),
527 _ => eprintln!("{}", self),
528 }
529 }
530}