use std::collections::{BTreeMap, BTreeSet, HashSet};
use std::fmt;
use std::iter;
use group::ff::Field;
use pasta_curves::arithmetic::FieldExt;
use super::{metadata, util, MockProver, Region};
use crate::{
dev::Value,
plonk::{Any, Column, ConstraintSystem, Expression, Gate},
poly::Rotation,
};
mod emitter;
#[derive(Debug, PartialEq)]
pub enum FailureLocation {
InRegion {
region: metadata::Region,
offset: usize,
},
OutsideRegion {
row: usize,
},
}
impl fmt::Display for FailureLocation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InRegion { region, offset } => write!(f, "in {} at offset {}", region, offset),
Self::OutsideRegion { row } => {
write!(f, "outside any region, on row {}", row)
}
}
}
}
impl FailureLocation {
pub(super) fn find_expressions<'a, F: Field>(
cs: &ConstraintSystem<F>,
regions: &[Region],
failure_row: usize,
failure_expressions: impl Iterator<Item = &'a Expression<F>>,
) -> Self {
let failure_columns: HashSet<Column<Any>> = failure_expressions
.flat_map(|expression| {
expression.evaluate(
&|_| vec![],
&|_| panic!("virtual selectors are removed during optimization"),
&|index, _, _| vec![cs.fixed_queries[index].0.into()],
&|index, _, _| vec![cs.advice_queries[index].0.into()],
&|index, _, _| vec![cs.instance_queries[index].0.into()],
&|a| a,
&|mut a, mut b| {
a.append(&mut b);
a
},
&|mut a, mut b| {
a.append(&mut b);
a
},
&|a, _| a,
)
})
.collect();
Self::find(regions, failure_row, failure_columns)
}
pub(super) fn find(
regions: &[Region],
failure_row: usize,
failure_columns: HashSet<Column<Any>>,
) -> Self {
regions
.iter()
.enumerate()
.find(|(_, r)| {
let (start, end) = r.rows.unwrap();
(start..=end).contains(&failure_row) && !failure_columns.is_disjoint(&r.columns)
})
.map(|(r_i, r)| FailureLocation::InRegion {
region: (r_i, r.name.clone()).into(),
offset: failure_row as usize - r.rows.unwrap().0 as usize,
})
.unwrap_or_else(|| FailureLocation::OutsideRegion {
row: failure_row as usize,
})
}
}
#[derive(Debug, PartialEq)]
pub enum VerifyFailure {
CellNotAssigned {
gate: metadata::Gate,
region: metadata::Region,
gate_offset: usize,
column: Column<Any>,
offset: isize,
},
ConstraintNotSatisfied {
constraint: metadata::Constraint,
location: FailureLocation,
cell_values: Vec<(metadata::VirtualCell, String)>,
},
ConstraintPoisoned {
constraint: metadata::Constraint,
},
Lookup {
lookup_index: usize,
location: FailureLocation,
},
Permutation {
column: metadata::Column,
location: FailureLocation,
},
}
impl fmt::Display for VerifyFailure {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::CellNotAssigned {
gate,
region,
gate_offset,
column,
offset,
} => {
write!(
f,
"{} uses {} at offset {}, which requires cell in column {:?} at offset {} to be assigned.",
region, gate, gate_offset, column, offset
)
}
Self::ConstraintNotSatisfied {
constraint,
location,
cell_values,
} => {
writeln!(f, "{} is not satisfied {}", constraint, location)?;
for (name, value) in cell_values {
writeln!(f, "- {} = {}", name, value)?;
}
Ok(())
}
Self::ConstraintPoisoned { constraint } => {
write!(
f,
"{} is active on an unusable row - missing selector?",
constraint
)
}
Self::Lookup {
lookup_index,
location,
} => write!(f, "Lookup {} is not satisfied {}", lookup_index, location),
Self::Permutation { column, location } => {
write!(
f,
"Equality constraint not satisfied by cell ({:?}, {})",
column, location
)
}
}
}
}
fn render_cell_not_assigned<F: Field>(
gates: &[Gate<F>],
gate: &metadata::Gate,
region: &metadata::Region,
gate_offset: usize,
column: Column<Any>,
offset: isize,
) {
let mut columns = BTreeMap::<metadata::Column, usize>::default();
let mut layout = BTreeMap::<i32, BTreeMap<metadata::Column, _>>::default();
for (i, cell) in gates[gate.index].queried_cells().iter().enumerate() {
let cell_column = cell.column.into();
*columns.entry(cell_column).or_default() += 1;
layout
.entry(cell.rotation.0)
.or_default()
.entry(cell_column)
.or_insert_with(|| {
if cell.column == column && gate_offset as i32 + cell.rotation.0 == offset as i32 {
"X".to_string()
} else {
format!("x{}", i)
}
});
}
eprintln!("error: cell not assigned");
emitter::render_cell_layout(
" ",
&FailureLocation::InRegion {
region: region.clone(),
offset: gate_offset,
},
&columns,
&layout,
|row_offset, rotation| {
if (row_offset.unwrap() + rotation) as isize == offset {
eprint!(" <--{{ X marks the spot! 🦜");
}
},
);
eprintln!();
eprintln!(
" Gate '{}' (applied at offset {}) queries these cells.",
gate.name, gate_offset
);
}
fn render_constraint_not_satisfied<F: Field>(
gates: &[Gate<F>],
constraint: &metadata::Constraint,
location: &FailureLocation,
cell_values: &[(metadata::VirtualCell, String)],
) {
let mut columns = BTreeMap::<metadata::Column, usize>::default();
let mut layout = BTreeMap::<i32, BTreeMap<metadata::Column, _>>::default();
for (i, (cell, _)) in cell_values.iter().enumerate() {
*columns.entry(cell.column).or_default() += 1;
layout
.entry(cell.rotation)
.or_default()
.entry(cell.column)
.or_insert(format!("x{}", i));
}
eprintln!("error: constraint not satisfied");
emitter::render_cell_layout(" ", location, &columns, &layout, |_, rotation| {
if rotation == 0 {
eprint!(" <--{{ Gate '{}' applied here", constraint.gate.name);
}
});
eprintln!();
eprintln!(" Constraint '{}':", constraint.name);
eprintln!(
" {} = 0",
emitter::expression_to_string(
&gates[constraint.gate.index].polynomials()[constraint.index],
&layout
)
);
eprintln!();
eprintln!(" Assigned cell values:");
for (i, (_, value)) in cell_values.iter().enumerate() {
eprintln!(" x{} = {}", i, value);
}
}
fn render_lookup<F: FieldExt>(
prover: &MockProver<F>,
lookup_index: usize,
location: &FailureLocation,
) {
let n = prover.n as i32;
let cs = &prover.cs;
let lookup = &cs.lookups[lookup_index];
let row = match location {
FailureLocation::InRegion { region, offset } => {
prover.regions[region.index].rows.unwrap().0 + offset
}
FailureLocation::OutsideRegion { row } => *row,
} as i32;
let table_columns = lookup.table_expressions.iter().map(|expr| {
expr.evaluate(
&|_| panic!("no constants in table expressions"),
&|_| panic!("no selectors in table expressions"),
&|_, column, _| format!("F{}", column),
&|_, _, _| panic!("no advice columns in table expressions"),
&|_, _, _| panic!("no instance columns in table expressions"),
&|_| panic!("no negations in table expressions"),
&|_, _| panic!("no sums in table expressions"),
&|_, _| panic!("no products in table expressions"),
&|_, _| panic!("no scaling in table expressions"),
)
});
fn cell_value<'a, F: FieldExt>(
column_type: Any,
load: impl Fn(usize, usize, Rotation) -> Value<F> + 'a,
) -> impl Fn(usize, usize, Rotation) -> BTreeMap<metadata::VirtualCell, String> + 'a {
move |query_index, column_index, rotation| {
Some((
((column_type, column_index).into(), rotation.0).into(),
match load(query_index, column_index, rotation) {
Value::Real(v) => util::format_value(v),
Value::Poison => unreachable!(),
},
))
.into_iter()
.collect()
}
}
eprintln!("error: lookup input does not exist in table");
eprint!(" (");
for i in 0..lookup.input_expressions.len() {
eprint!("{}L{}", if i == 0 { "" } else { ", " }, i);
}
eprint!(") ∉ (");
for (i, column) in table_columns.enumerate() {
eprint!("{}{}", if i == 0 { "" } else { ", " }, column);
}
eprintln!(")");
eprintln!();
eprintln!(" Lookup inputs:");
for (i, input) in lookup.input_expressions.iter().enumerate() {
let cell_values = input.evaluate(
&|_| BTreeMap::default(),
&|_| panic!("virtual selectors are removed during optimization"),
&cell_value(
Any::Fixed,
&util::load(n, row, &cs.fixed_queries, &prover.fixed),
),
&cell_value(
Any::Advice,
&util::load(n, row, &cs.advice_queries, &prover.advice),
),
&cell_value(
Any::Instance,
&util::load_instance(n, row, &cs.instance_queries, &prover.instance),
),
&|a| a,
&|mut a, mut b| {
a.append(&mut b);
a
},
&|mut a, mut b| {
a.append(&mut b);
a
},
&|a, _| a,
);
let mut columns = BTreeMap::<metadata::Column, usize>::default();
let mut layout = BTreeMap::<i32, BTreeMap<metadata::Column, _>>::default();
for (i, (cell, _)) in cell_values.iter().enumerate() {
*columns.entry(cell.column).or_default() += 1;
layout
.entry(cell.rotation)
.or_default()
.entry(cell.column)
.or_insert(format!("x{}", i));
}
if i != 0 {
eprintln!();
}
eprintln!(
" L{} = {}",
i,
emitter::expression_to_string(input, &layout)
);
eprintln!(" ^");
emitter::render_cell_layout(" | ", location, &columns, &layout, |_, rotation| {
if rotation == 0 {
eprint!(" <--{{ Lookup inputs queried here");
}
});
eprintln!(" |");
eprintln!(" | Assigned cell values:");
for (i, (_, value)) in cell_values.iter().enumerate() {
eprintln!(" | x{} = {}", i, value);
}
}
}
impl VerifyFailure {
pub(super) fn emit<F: FieldExt>(&self, prover: &MockProver<F>) {
match self {
Self::CellNotAssigned {
gate,
region,
gate_offset,
column,
offset,
} => render_cell_not_assigned(
&prover.cs.gates,
gate,
region,
*gate_offset,
*column,
*offset,
),
Self::ConstraintNotSatisfied {
constraint,
location,
cell_values,
} => {
render_constraint_not_satisfied(&prover.cs.gates, constraint, location, cell_values)
}
Self::Lookup {
lookup_index,
location,
} => render_lookup(prover, *lookup_index, location),
_ => eprintln!("{}", self),
}
}
}