1use std::collections::{BTreeMap, HashSet};
2use std::fmt::{self, Debug};
3
4use group::ff::Field;
5
6use super::metadata::{DebugColumn, DebugVirtualCell};
7use super::MockProver;
8use super::{
9 metadata,
10 util::{self, AnyQuery},
11 Region,
12};
13use crate::dev::metadata::Constraint;
14use crate::{
15 dev::{AdviceCellValue, CellValue, Instance, Value},
16 plonk::{Any, Column, ConstraintSystem, Expression, Gate},
17};
18
19mod emitter;
20
21#[derive(Debug, PartialEq, Eq, Clone)]
23pub enum FailureLocation {
24 InRegion {
26 region: metadata::Region,
28 offset: usize,
31 },
32 OutsideRegion {
34 row: usize,
36 },
37}
38
39impl fmt::Display for FailureLocation {
40 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41 match self {
42 Self::InRegion { region, offset } => write!(f, "in {} at offset {}", region, offset),
43 Self::OutsideRegion { row } => {
44 write!(f, "outside any region, on row {}", row)
45 }
46 }
47 }
48}
49
50impl FailureLocation {
51 pub(super) fn get_debug_column(&self, metadata: metadata::Column) -> DebugColumn {
53 match self {
54 Self::InRegion { region, .. } => {
55 DebugColumn::from((metadata, region.column_annotations.as_ref()))
56 }
57 _ => DebugColumn::from((metadata, None)),
58 }
59 }
60
61 pub(super) fn find_expressions<'a, F: Field>(
62 cs: &ConstraintSystem<F>,
63 regions: &[Region],
64 failure_row: usize,
65 failure_expressions: impl Iterator<Item = &'a Expression<F>>,
66 ) -> Self {
67 let failure_columns: HashSet<Column<Any>> = failure_expressions
68 .flat_map(|expression| {
69 expression.evaluate(
70 &|_| vec![],
71 &|_| panic!("virtual selectors are removed during optimization"),
72 &|query| vec![cs.fixed_queries[query.index.unwrap()].0.into()],
73 &|query| vec![cs.advice_queries[query.index.unwrap()].0.into()],
74 &|query| vec![cs.instance_queries[query.index.unwrap()].0.into()],
75 &|_| vec![],
76 &|a| a,
77 &|mut a, mut b| {
78 a.append(&mut b);
79 a
80 },
81 &|mut a, mut b| {
82 a.append(&mut b);
83 a
84 },
85 &|a, _| a,
86 )
87 })
88 .collect();
89
90 Self::find(regions, failure_row, failure_columns)
91 }
92
93 pub(super) fn find(
95 regions: &[Region],
96 failure_row: usize,
97 failure_columns: HashSet<Column<Any>>,
98 ) -> Self {
99 regions
100 .iter()
101 .enumerate()
102 .find(|(_, r)| {
103 if let Some((start, end)) = r.rows {
104 (start..=end).contains(&failure_row) && !failure_columns.is_disjoint(&r.columns)
110 } else {
111 false
113 }
114 })
115 .map(|(r_i, r)| FailureLocation::InRegion {
116 region: (r_i, r.name.clone(), r.annotations.clone()).into(),
117 offset: failure_row - r.rows.unwrap().0,
118 })
119 .unwrap_or_else(|| FailureLocation::OutsideRegion { row: failure_row })
120 }
121}
122
123#[derive(PartialEq, Eq)]
125pub enum VerifyFailure {
126 CellNotAssigned {
128 gate: metadata::Gate,
130 region: metadata::Region,
132 gate_offset: usize,
135 column: Column<Any>,
137 offset: isize,
141 },
142 InstanceCellNotAssigned {
144 gate: metadata::Gate,
146 region: metadata::Region,
148 gate_offset: usize,
151 column: Column<Instance>,
153 row: usize,
155 },
156 ConstraintNotSatisfied {
158 constraint: metadata::Constraint,
160 location: FailureLocation,
165 cell_values: Vec<(metadata::VirtualCell, String)>,
167 },
168 ConstraintPoisoned {
170 constraint: metadata::Constraint,
172 },
173 Lookup {
175 name: String,
177 lookup_index: usize,
181 location: FailureLocation,
194 },
195 Permutation {
197 column: metadata::Column,
199 location: FailureLocation,
201 },
202}
203
204impl fmt::Display for VerifyFailure {
205 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
206 match self {
207 Self::CellNotAssigned {
208 gate,
209 region,
210 gate_offset,
211 column,
212 offset,
213 } => {
214 write!(
215 f,
216 "{} uses {} at offset {}, which requires cell in column {:?} at offset {} with annotation {:?} to be assigned.",
217 region, gate, gate_offset, column, offset, region.get_column_annotation((*column).into())
218 )
219 }
220 Self::InstanceCellNotAssigned {
221 gate,
222 region,
223 gate_offset,
224 column,
225 row,
226 } => {
227 write!(
228 f,
229 "{} uses {} at offset {}, which requires cell in instance column {:?} at row {} to be assigned.",
230 region, gate, gate_offset, column, row
231 )
232 }
233 Self::ConstraintNotSatisfied {
234 constraint,
235 location,
236 cell_values,
237 } => {
238 writeln!(f, "{} is not satisfied {}", constraint, location)?;
239 for (dvc, value) in cell_values.iter().map(|(vc, string)| {
240 let ann_map = match location {
241 FailureLocation::InRegion { region, offset: _ } => {
242 ®ion.column_annotations
243 }
244 _ => &None,
245 };
246
247 (DebugVirtualCell::from((vc, ann_map.as_ref())), string)
248 }) {
249 writeln!(f, "- {} = {}", dvc, value)?;
250 }
251 Ok(())
252 }
253 Self::ConstraintPoisoned { constraint } => {
254 write!(
255 f,
256 "{} is active on an unusable row - missing selector?",
257 constraint
258 )
259 }
260 Self::Lookup {
261 name,
262 lookup_index,
263 location,
264 } => {
265 write!(
266 f,
267 "Lookup {}(index: {}) is not satisfied {}",
268 name, lookup_index, location
269 )
270 }
271 Self::Permutation { column, location } => {
272 write!(
273 f,
274 "Equality constraint not satisfied by cell ({}, {})",
275 location.get_debug_column(*column),
276 location
277 )
278 }
279 }
280 }
281}
282
283impl Debug for VerifyFailure {
284 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
285 match self {
286 VerifyFailure::ConstraintNotSatisfied {
287 constraint,
288 location,
289 cell_values,
290 } => {
291 #[allow(dead_code)]
292 #[derive(Debug)]
293 struct ConstraintCaseDebug {
294 constraint: Constraint,
295 location: FailureLocation,
296 cell_values: Vec<(DebugVirtualCell, String)>,
297 }
298
299 let ann_map = match location {
300 FailureLocation::InRegion { region, offset: _ } => {
301 region.column_annotations.clone()
302 }
303 _ => None,
304 };
305
306 let debug = ConstraintCaseDebug {
307 constraint: constraint.clone(),
308 location: location.clone(),
309 cell_values: cell_values
310 .iter()
311 .map(|(vc, value)| {
312 (
313 DebugVirtualCell::from((vc, ann_map.as_ref())),
314 value.clone(),
315 )
316 })
317 .collect(),
318 };
319
320 write!(f, "{:#?}", debug)
321 }
322 _ => write!(f, "{:#}", self),
323 }
324 }
325}
326
327fn render_cell_not_assigned<F: Field>(
340 gates: &[Gate<F>],
341 gate: &metadata::Gate,
342 region: &metadata::Region,
343 gate_offset: usize,
344 column: Column<Any>,
345 offset: isize,
346) {
347 let mut columns = BTreeMap::<metadata::Column, usize>::default();
352 let mut layout = BTreeMap::<i32, BTreeMap<metadata::Column, _>>::default();
353 for (i, cell) in gates[gate.index].queried_cells().iter().enumerate() {
354 let cell_column = cell.column.into();
355 *columns.entry(cell_column).or_default() += 1;
356 layout
357 .entry(cell.rotation.0)
358 .or_default()
359 .entry(cell_column)
360 .or_insert_with(|| {
361 if cell.column == column && gate_offset as i32 + cell.rotation.0 == offset as i32 {
362 "X".to_string()
363 } else {
364 format!("x{}", i)
365 }
366 });
367 }
368
369 eprintln!("error: cell not assigned");
370 emitter::render_cell_layout(
371 " ",
372 &FailureLocation::InRegion {
373 region: region.clone(),
374 offset: gate_offset,
375 },
376 &columns,
377 &layout,
378 |row_offset, rotation| {
379 if (row_offset.unwrap() + rotation) as isize == offset {
380 eprint!(" <--{{ X marks the spot! 🦜");
381 }
382 },
383 );
384 eprintln!();
385 eprintln!(
386 " Gate '{}' (applied at offset {}) queries these cells.",
387 gate.name, gate_offset
388 );
389}
390
391fn render_constraint_not_satisfied<F: Field>(
409 gates: &[Gate<F>],
410 constraint: &metadata::Constraint,
411 location: &FailureLocation,
412 cell_values: &[(metadata::VirtualCell, String)],
413) {
414 let mut columns = BTreeMap::<metadata::Column, usize>::default();
419 let mut layout = BTreeMap::<i32, BTreeMap<metadata::Column, _>>::default();
420 for (i, (cell, _)) in cell_values.iter().enumerate() {
421 *columns.entry(cell.column).or_default() += 1;
422 layout
423 .entry(cell.rotation)
424 .or_default()
425 .entry(cell.column)
426 .or_insert(format!("x{}", i));
427 }
428
429 eprintln!("error: constraint not satisfied");
430 emitter::render_cell_layout(" ", location, &columns, &layout, |_, rotation| {
431 if rotation == 0 {
432 eprint!(" <--{{ Gate '{}' applied here", constraint.gate.name);
433 }
434 });
435
436 eprintln!();
438 eprintln!(" Constraint '{}':", constraint.name);
439 eprintln!(
440 " {} = 0",
441 emitter::expression_to_string(
442 &gates[constraint.gate.index].polynomials()[constraint.index],
443 &layout
444 )
445 );
446
447 eprintln!();
449 eprintln!(" Assigned cell values:");
450 for (i, (_, value)) in cell_values.iter().enumerate() {
451 eprintln!(" x{} = {}", i, value);
452 }
453}
454
455fn render_lookup<F: Field>(
474 prover: &MockProver<F>,
475 name: &str,
476 lookup_index: usize,
477 location: &FailureLocation,
478) {
479 let n = prover.n as i32;
480 let cs = &prover.cs;
481 let lookup = &cs.lookups[lookup_index];
482
483 let row = match location {
486 FailureLocation::InRegion { region, offset } => {
487 prover.regions[region.index].rows.unwrap().0 + offset
488 }
489 FailureLocation::OutsideRegion { row } => *row,
490 } as i32;
491
492 let lookup_columns = lookup.table_expressions.iter().map(|expr| {
495 expr.evaluate(
496 &|f| format! {"Const: {:#?}", f},
497 &|s| format! {"S{}", s.0},
498 &|query| {
499 format!(
500 "{:?}",
501 prover
502 .cs
503 .general_column_annotations
504 .get(&metadata::Column::from((Any::Fixed, query.column_index)))
505 .cloned()
506 .unwrap_or_else(|| format!("F{}", query.column_index()))
507 )
508 },
509 &|query| {
510 format!(
511 "{:?}",
512 prover
513 .cs
514 .general_column_annotations
515 .get(&metadata::Column::from((Any::advice(), query.column_index)))
516 .cloned()
517 .unwrap_or_else(|| format!("A{}", query.column_index()))
518 )
519 },
520 &|query| {
521 format!(
522 "{:?}",
523 prover
524 .cs
525 .general_column_annotations
526 .get(&metadata::Column::from((Any::Instance, query.column_index)))
527 .cloned()
528 .unwrap_or_else(|| format!("I{}", query.column_index()))
529 )
530 },
531 &|challenge| format! {"C{}", challenge.index()},
532 &|query| format! {"-{}", query},
533 &|a, b| format! {"{} + {}", a,b},
534 &|a, b| format! {"{} * {}", a,b},
535 &|a, b| format! {"{} * {:?}", a, b},
536 )
537 });
538
539 fn cell_value<'a, F: Field, Q: Into<AnyQuery> + Copy>(
540 load: impl Fn(Q) -> Value<F> + 'a,
541 ) -> impl Fn(Q) -> BTreeMap<metadata::VirtualCell, String> + 'a {
542 move |query| {
543 let AnyQuery {
544 column_type,
545 column_index,
546 rotation,
547 ..
548 } = query.into();
549 Some((
550 ((column_type, column_index).into(), rotation.0).into(),
551 match load(query) {
552 Value::Real(v) => util::format_value(v),
553 Value::Poison => unreachable!(),
554 },
555 ))
556 .into_iter()
557 .collect()
558 }
559 }
560
561 eprintln!("error: lookup input does not exist in table");
562 eprint!(" (");
563 for i in 0..lookup.input_expressions.len() {
564 eprint!("{}L{}", if i == 0 { "" } else { ", " }, i);
565 }
566
567 eprint!(") ∉ (");
568 for (i, column) in lookup_columns.enumerate() {
569 eprint!("{}{}", if i == 0 { "" } else { ", " }, column);
570 }
571 eprintln!(")");
572
573 eprintln!();
574 eprintln!(" Lookup '{}' inputs:", name);
575 let advice = prover
576 .advice
577 .iter()
578 .map(|advice| {
579 advice
580 .iter()
581 .map(|rc| match rc {
582 AdviceCellValue::Assigned(a) => CellValue::Assigned(a.evaluate()),
583 AdviceCellValue::Poison(i) => CellValue::Poison(*i),
584 })
585 .collect::<Vec<_>>()
586 })
587 .collect::<Vec<_>>();
588 for (i, input) in lookup.input_expressions.iter().enumerate() {
589 let cell_values = input.evaluate(
591 &|_| BTreeMap::default(),
592 &|_| panic!("virtual selectors are removed during optimization"),
593 &cell_value(&util::load(n, row, &cs.fixed_queries, &prover.fixed)),
594 &cell_value(&util::load(n, row, &cs.advice_queries, &advice)),
595 &cell_value(&util::load_instance(
596 n,
597 row,
598 &cs.instance_queries,
599 &prover.instance,
600 )),
601 &|_| BTreeMap::default(),
602 &|a| a,
603 &|mut a, mut b| {
604 a.append(&mut b);
605 a
606 },
607 &|mut a, mut b| {
608 a.append(&mut b);
609 a
610 },
611 &|a, _| a,
612 );
613
614 let mut columns = BTreeMap::<metadata::Column, usize>::default();
619 let mut layout = BTreeMap::<i32, BTreeMap<metadata::Column, _>>::default();
620 for (i, (cell, _)) in cell_values.iter().enumerate() {
621 *columns.entry(cell.column).or_default() += 1;
622 layout
623 .entry(cell.rotation)
624 .or_default()
625 .entry(cell.column)
626 .or_insert(format!("x{}", i));
627 }
628
629 if i != 0 {
630 eprintln!();
631 }
632 eprintln!(
633 " L{} = {}",
634 i,
635 emitter::expression_to_string(input, &layout)
636 );
637 eprintln!(" ^");
638
639 emitter::render_cell_layout(" | ", location, &columns, &layout, |_, rotation| {
640 if rotation == 0 {
641 eprint!(" <--{{ Lookup '{}' inputs queried here", name);
642 }
643 });
644
645 eprintln!(" |");
647 eprintln!(" | Assigned cell values:");
648 for (i, (_, value)) in cell_values.iter().enumerate() {
649 eprintln!(" | x{} = {}", i, value);
650 }
651 }
652}
653
654impl VerifyFailure {
655 pub(super) fn emit<F: Field>(&self, prover: &MockProver<F>) {
657 match self {
658 Self::CellNotAssigned {
659 gate,
660 region,
661 gate_offset,
662 column,
663 offset,
664 } => render_cell_not_assigned(
665 &prover.cs.gates,
666 gate,
667 region,
668 *gate_offset,
669 *column,
670 *offset,
671 ),
672 Self::ConstraintNotSatisfied {
673 constraint,
674 location,
675 cell_values,
676 } => {
677 render_constraint_not_satisfied(&prover.cs.gates, constraint, location, cell_values)
678 }
679 Self::Lookup {
680 name,
681 lookup_index,
682 location,
683 } => render_lookup(prover, name, *lookup_index, location),
684 _ => eprintln!("{}", self),
685 }
686 }
687}