halo2_axiom/dev/
failure.rs

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/// The location within the circuit at which a particular [`VerifyFailure`] occurred.
22#[derive(Debug, PartialEq, Eq, Clone)]
23pub enum FailureLocation {
24    /// A location inside a region.
25    InRegion {
26        /// The region in which the failure occurred.
27        region: metadata::Region,
28        /// The offset (relative to the start of the region) at which the failure
29        /// occurred.
30        offset: usize,
31    },
32    /// A location outside of a region.
33    OutsideRegion {
34        /// The circuit row on which the failure occurred.
35        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    /// Returns a `DebugColumn` from Column metadata and `&self`.
52    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    /// Figures out whether the given row and columns overlap an assigned region.
94    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                    // We match the region if any input columns overlap, rather than all of
105                    // them, because matching complex selector columns is hard. As long as
106                    // regions are rectangles, and failures occur due to assignments entirely
107                    // within single regions, "any" will be equivalent to "all". If these
108                    // assumptions change, we'll start getting bug reports from users :)
109                    (start..=end).contains(&failure_row) && !failure_columns.is_disjoint(&r.columns)
110                } else {
111                    // Zero-area region
112                    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/// The reasons why a particular circuit is not satisfied.
124#[derive(PartialEq, Eq)]
125pub enum VerifyFailure {
126    /// A cell used in an active gate was not assigned to.
127    CellNotAssigned {
128        /// The index of the active gate.
129        gate: metadata::Gate,
130        /// The region in which this cell should be assigned.
131        region: metadata::Region,
132        /// The offset (relative to the start of the region) at which the active gate
133        /// queries this cell.
134        gate_offset: usize,
135        /// The column in which this cell should be assigned.
136        column: Column<Any>,
137        /// The offset (relative to the start of the region) at which this cell should be
138        /// assigned. This may be negative (for example, if a selector enables a gate at
139        /// offset 0, but the gate uses `Rotation::prev()`).
140        offset: isize,
141    },
142    /// An instance cell used in an active gate was not assigned to.
143    InstanceCellNotAssigned {
144        /// The index of the active gate.
145        gate: metadata::Gate,
146        /// The region in which this gate was activated.
147        region: metadata::Region,
148        /// The offset (relative to the start of the region) at which the active gate
149        /// queries this cell.
150        gate_offset: usize,
151        /// The column in which this cell should be assigned.
152        column: Column<Instance>,
153        /// The absolute row at which this cell should be assigned.
154        row: usize,
155    },
156    /// A constraint was not satisfied for a particular row.
157    ConstraintNotSatisfied {
158        /// The polynomial constraint that is not satisfied.
159        constraint: metadata::Constraint,
160        /// The location at which this constraint is not satisfied.
161        ///
162        /// `FailureLocation::OutsideRegion` is usually caused by a constraint that does
163        /// not contain a selector, and as a result is active on every row.
164        location: FailureLocation,
165        /// The values of the virtual cells used by this constraint.
166        cell_values: Vec<(metadata::VirtualCell, String)>,
167    },
168    /// A constraint was active on an unusable row, and is likely missing a selector.
169    ConstraintPoisoned {
170        /// The polynomial constraint that is not satisfied.
171        constraint: metadata::Constraint,
172    },
173    /// A lookup input did not exist in its corresponding table.
174    Lookup {
175        /// The name of the lookup that is not satisfied.
176        name: String,
177        /// The index of the lookup that is not satisfied. These indices are assigned in
178        /// the order in which `ConstraintSystem::lookup` is called during
179        /// `Circuit::configure`.
180        lookup_index: usize,
181        /// The location at which the lookup is not satisfied.
182        ///
183        /// `FailureLocation::InRegion` is most common, and may be due to the intentional
184        /// use of a lookup (if its inputs are conditional on a complex selector), or an
185        /// unintentional lookup constraint that overlaps the region (indicating that the
186        /// lookup's inputs should be made conditional).
187        ///
188        /// `FailureLocation::OutsideRegion` is uncommon, and could mean that:
189        /// - The input expressions do not correctly constrain a default value that exists
190        ///   in the table when the lookup is not being used.
191        /// - The input expressions use a column queried at a non-zero `Rotation`, and the
192        ///   lookup is active on a row adjacent to an unrelated region.
193        location: FailureLocation,
194    },
195    /// A permutation did not preserve the original value of a cell.
196    Permutation {
197        /// The column in which this permutation is not satisfied.
198        column: metadata::Column,
199        /// The location at which the permutation is not satisfied.
200        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                            &region.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
327/// Renders `VerifyFailure::CellNotAssigned`.
328///
329/// ```text
330/// error: cell not assigned
331///   Cell layout in region 'Faulty synthesis':
332///     | Offset | A0 | A1 |
333///     +--------+----+----+
334///     |    0   | x0 |    |
335///     |    1   |    |  X | <--{ X marks the spot! 🦜
336///
337///   Gate 'Equality check' (applied at offset 1) queries these cells.
338/// ```
339fn 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    // Collect the necessary rendering information:
348    // - The columns involved in this gate.
349    // - How many cells are in each column.
350    // - The grid of cell values, indexed by rotation.
351    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
391/// Renders `VerifyFailure::ConstraintNotSatisfied`.
392///
393/// ```text
394/// error: constraint not satisfied
395///   Cell layout in region 'somewhere':
396///     | Offset | A0 |
397///     +--------+----+
398///     |    0   | x0 | <--{ Gate 'foo' applied here
399///     |    1   | x1 |
400///
401///   Constraint 'bar':
402///     x1 + x1 * 0x100 + x1 * 0x10000 + x1 * 0x100_0000 - x0 = 0
403///
404///   Assigned cell values:
405///     x0 = 0x5
406///     x1 = 0x5
407/// ```
408fn 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    // Collect the necessary rendering information:
415    // - The columns involved in this constraint.
416    // - How many cells are in each column.
417    // - The grid of cell values, indexed by rotation.
418    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    // Print the unsatisfied constraint, in terms of the local variables.
437    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    // Print the map from local variables to assigned values.
448    eprintln!();
449    eprintln!("  Assigned cell values:");
450    for (i, (_, value)) in cell_values.iter().enumerate() {
451        eprintln!("    x{} = {}", i, value);
452    }
453}
454
455/// Renders `VerifyFailure::Lookup`.
456///
457/// ```text
458/// error: lookup input does not exist in table
459///   (L0) ∉ (F0)
460///
461///   Lookup inputs:
462///     L0 = x1 * x0 + (1 - x1) * 0x2
463///     ^
464///     | Cell layout in region 'Faulty synthesis':
465///     |   | Offset | A0 | F1 |
466///     |   +--------+----+----+
467///     |   |    1   | x0 | x1 | <--{ Lookup inputs queried here
468///     |
469///     | Assigned cell values:
470///     |   x0 = 0x5
471///     |   x1 = 1
472/// ```
473fn 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    // Get the absolute row on which the lookup's inputs are being queried, so we can
484    // fetch the input values.
485    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    // Recover the fixed columns from the table expressions. We don't allow composite
493    // expressions for the table side of lookups.
494    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        // Fetch the cell values (since we don't store them in VerifyFailure::Lookup).
590        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        // Collect the necessary rendering information:
615        // - The columns involved in this constraint.
616        // - How many cells are in each column.
617        // - The grid of cell values, indexed by rotation.
618        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        // Print the map from local variables to assigned values.
646        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    /// Emits this failure in pretty-printed format to stderr.
656    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}