Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Support for optional assertion messages #2491

Merged
merged 44 commits into from
Sep 4, 2023
Merged
Show file tree
Hide file tree
Changes from 42 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
387b541
feat: assertion messages for acir and brillig
sirasistant Aug 21, 2023
a0f1b8c
rewrite assert msg handling
sirasistant Aug 23, 2023
268d42c
refactor: improve error handling
sirasistant Aug 30, 2023
f4af108
style: error message
sirasistant Aug 30, 2023
622b82d
Merge branch 'master' into arv/assert_msgs
sirasistant Aug 31, 2023
1c4af96
refactor: improving parser
sirasistant Aug 31, 2023
2520f39
fix: assert_eq and comptime messages
sirasistant Aug 31, 2023
9ef7340
test: updated parser tests
sirasistant Aug 31, 2023
bc87640
acvm 0.24
kevaundray Aug 31, 2023
e4fcc39
add artifacts
kevaundray Aug 31, 2023
fb538ae
style: remove old todo
sirasistant Aug 31, 2023
0432010
test: reorder parser test for clarity
sirasistant Aug 31, 2023
8a8f347
Merge branch 'master' into arv/assert_msgs
sirasistant Aug 31, 2023
af96ce4
test: added assert msgs in e2e
sirasistant Aug 31, 2023
71b9664
Merge remote-tracking branch 'origin/master' into kw/acvm-0-24
kevaundray Aug 31, 2023
cc7a37f
missed file
kevaundray Aug 31, 2023
0b98ae3
change to branch
kevaundray Aug 31, 2023
ff135e8
update bytecode
kevaundray Aug 31, 2023
ac03515
change trait definitions
kevaundray Aug 31, 2023
b20fcac
rebuild script
kevaundray Aug 31, 2023
6b82986
fix trait call and remove dirs
kevaundray Aug 31, 2023
03dcd55
Merge remote-tracking branch 'origin/master' into kw/acvm-0-24
kevaundray Sep 1, 2023
48e9d1d
Merge remote-tracking branch 'origin/master' into kw/acvm-0-24
kevaundray Sep 1, 2023
83c487d
use nightly
kevaundray Sep 1, 2023
6a7ff1a
commit to rebuild
kevaundray Sep 1, 2023
2d39ed7
update 1_mul.bytecode
kevaundray Sep 1, 2023
145e735
Merge branch 'kw/acvm-0-24' into arv/assert_msgs
sirasistant Sep 1, 2023
7166321
Merge branch 'kw/acvm-0-24' into arv/assert_msgs
sirasistant Sep 1, 2023
b813e05
update to 2788
kevaundray Sep 1, 2023
4ecbd3c
refactor: use closure for resolving asserts
sirasistant Sep 1, 2023
2f82a75
Merge branch 'kw/acvm-0-24' into arv/assert_msgs
sirasistant Sep 1, 2023
223d09c
Update Cargo.toml
kevaundray Sep 1, 2023
d5fae75
Merge remote-tracking branch 'origin/master' into kw/acvm-0-24
kevaundray Sep 1, 2023
fc1172a
update cargo.lock
kevaundray Sep 1, 2023
d115fde
patch acvm traits which have disappeared with acvm-backend-barretenberg
kevaundray Sep 1, 2023
4a5e31b
use bb 0.5.0
kevaundray Sep 1, 2023
1bb8e50
remove backend specific methods
kevaundray Sep 1, 2023
3c44114
Merge remote-tracking branch 'origin/master' into kw/acvm-0-24
kevaundray Sep 3, 2023
179da4f
Merge remote-tracking branch 'origin/kw/acvm-0-24' into arv/assert_msgs
kevaundray Sep 3, 2023
f4b48ae
update to acvm 0.24.1
kevaundray Sep 3, 2023
b9a92a7
Merge remote-tracking branch 'origin/kw/acvm-0-24' into arv/assert_msgs
kevaundray Sep 3, 2023
6017201
Merge branch 'master' into arv/assert_msgs
sirasistant Sep 4, 2023
330fcde
Update crates/noirc_frontend/src/parser/errors.rs
sirasistant Sep 4, 2023
7540681
fix: improvements after peer review
sirasistant Sep 4, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions crates/nargo/src/errors.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use acvm::pwg::OpcodeResolutionError;
use acvm::{acir::circuit::OpcodeLocation, pwg::OpcodeResolutionError};
use noirc_printable_type::ForeignCallError;
use thiserror::Error;

Expand All @@ -8,10 +8,20 @@ pub enum NargoError {
#[error("Failed to compile circuit")]
CompilationError,

/// ACIR circuit solving error
/// ACIR circuit execution error
#[error(transparent)]
SolvingError(#[from] OpcodeResolutionError),
ExecutionError(#[from] ExecutionError),

/// Oracle handling error
#[error(transparent)]
ForeignCallError(#[from] ForeignCallError),
}

#[derive(Debug, Error)]
pub enum ExecutionError {
#[error("Failed assertion: '{}'", .0)]
AssertionFailed(String, OpcodeLocation),

#[error(transparent)]
SolvingError(#[from] OpcodeResolutionError),
}
2 changes: 1 addition & 1 deletion crates/nargo/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

pub mod artifacts;
pub mod constants;
mod errors;
pub mod errors;
pub mod ops;
pub mod package;
pub mod workspace;
Expand Down
26 changes: 24 additions & 2 deletions crates/nargo/src/ops/execute.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use acvm::pwg::{ACVMStatus, ACVM};
use acvm::pwg::{ACVMStatus, ErrorLocation, OpcodeResolutionError, ACVM};
use acvm::BlackBoxFunctionSolver;
use acvm::{acir::circuit::Circuit, acir::native_types::WitnessMap};

use crate::errors::ExecutionError;
use crate::NargoError;

use super::foreign_calls::ForeignCall;
Expand All @@ -14,6 +15,15 @@ pub fn execute_circuit<B: BlackBoxFunctionSolver>(
) -> Result<WitnessMap, NargoError> {
let mut acvm = ACVM::new(blackbox_solver, circuit.opcodes, initial_witness);

// Assert messages are not a map due to serde-reflect issues
let get_assert_message = |opcode_location| {
sirasistant marked this conversation as resolved.
Show resolved Hide resolved
circuit
.assert_messages
.iter()
.find(|(loc, _)| loc == opcode_location)
.map(|(_, message)| message.clone())
};

loop {
let solver_status = acvm.solve();

Expand All @@ -22,7 +32,19 @@ pub fn execute_circuit<B: BlackBoxFunctionSolver>(
ACVMStatus::InProgress => {
unreachable!("Execution should not stop while in `InProgress` state.")
}
ACVMStatus::Failure(error) => return Err(error.into()),
ACVMStatus::Failure(error) => {
return Err(NargoError::ExecutionError(match error {
OpcodeResolutionError::UnsatisfiedConstrain {
opcode_location: ErrorLocation::Resolved(opcode_location),
} => match get_assert_message(&opcode_location) {
Some(assert_message) => {
ExecutionError::AssertionFailed(assert_message, opcode_location)
}
None => ExecutionError::SolvingError(error),
},
_ => ExecutionError::SolvingError(error),
}))
}
ACVMStatus::RequiresForeignCall(foreign_call) => {
let foreign_call_result = ForeignCall::execute(&foreign_call, show_output)?;
acvm.resolve_pending_foreign_call(foreign_call_result);
Expand Down
39 changes: 22 additions & 17 deletions crates/nargo_cli/src/cli/execute_cmd.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
use acvm::acir::circuit::OpcodeLocation;
use acvm::acir::{circuit::Circuit, native_types::WitnessMap};
use acvm::pwg::ErrorLocation;
use acvm::pwg::{ErrorLocation, OpcodeResolutionError};
use clap::Args;
use nargo::constants::PROVER_INPUT_FILE;
use nargo::errors::{ExecutionError, NargoError};
use nargo::package::Package;
use nargo::NargoError;
use nargo_toml::{get_package_manifest, resolve_workspace_from_toml, PackageSelection};
use noirc_abi::input_parser::{Format, InputValue};
use noirc_abi::{Abi, InputMap};
Expand Down Expand Up @@ -95,24 +95,26 @@ fn execute_package(
/// If the location has been resolved we return the contained [OpcodeLocation].
fn extract_opcode_error_from_nargo_error(
nargo_err: &NargoError,
) -> Option<(OpcodeLocation, &acvm::pwg::OpcodeResolutionError)> {
let solving_err = match nargo_err {
nargo::NargoError::SolvingError(err) => err,
) -> Option<(OpcodeLocation, &ExecutionError)> {
let execution_error = match nargo_err {
NargoError::ExecutionError(err) => err,
_ => return None,
};
//send back here a execution error instead
sirasistant marked this conversation as resolved.
Show resolved Hide resolved

match solving_err {
acvm::pwg::OpcodeResolutionError::IndexOutOfBounds {
match execution_error {
ExecutionError::AssertionFailed(_, location) => Some((*location, execution_error)),
ExecutionError::SolvingError(OpcodeResolutionError::IndexOutOfBounds {
opcode_location: error_location,
..
}
| acvm::pwg::OpcodeResolutionError::UnsatisfiedConstrain {
})
| ExecutionError::SolvingError(OpcodeResolutionError::UnsatisfiedConstrain {
opcode_location: error_location,
} => match error_location {
}) => match error_location {
ErrorLocation::Unresolved => {
unreachable!("Cannot resolve index for unsatisfied constraint")
}
ErrorLocation::Resolved(opcode_location) => Some((*opcode_location, solving_err)),
ErrorLocation::Resolved(opcode_location) => Some((*opcode_location, execution_error)),
},
_ => None,
}
Expand All @@ -122,7 +124,7 @@ fn extract_opcode_error_from_nargo_error(
/// to determine an opcode's call stack. Then report the error using the resolved
/// call stack and any other relevant error information returned from the ACVM.
fn report_error_with_opcode_location(
opcode_err_info: Option<(OpcodeLocation, &acvm::pwg::OpcodeResolutionError)>,
opcode_err_info: Option<(OpcodeLocation, &ExecutionError)>,
debug: &DebugInfo,
context: &Context,
) {
Expand All @@ -132,18 +134,21 @@ fn report_error_with_opcode_location(
// of the call stack (the last item in the Vec).
if let Some(location) = locations.last() {
let message = match opcode_err {
acvm::pwg::OpcodeResolutionError::IndexOutOfBounds {
ExecutionError::AssertionFailed(message, _) => {
format!("Assertion failed: '{message}'")
}
ExecutionError::SolvingError(OpcodeResolutionError::IndexOutOfBounds {
index,
array_size,
..
} => {
}) => {
format!(
"Index out of bounds, array has size {array_size:?}, but index was {index:?}"
)
}
acvm::pwg::OpcodeResolutionError::UnsatisfiedConstrain { .. } => {
"Failed constraint".into()
}
ExecutionError::SolvingError(OpcodeResolutionError::UnsatisfiedConstrain {
..
}) => "Failed constraint".into(),
_ => {
// All other errors that do not have corresponding opcode locations
// should not be reported in this method.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
//
// The features being tested is assertion
fn main(x : Field, y : Field) {
assert(x == y);
assert(x == y, "x and y are not equal");
assert_eq(x, y, "x and y are not equal");
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ fn main(x: Field) {
}

unconstrained fn conditional(x : bool) -> Field {
assert(x);
assert(x, "x is false");
assert_eq(x, true, "x is false");
1
}
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ impl<'block> BrilligBlock<'block> {
);
self.convert_ssa_binary(binary, dfg, result_register);
}
Instruction::Constrain(lhs, rhs) => {
Instruction::Constrain(lhs, rhs, assert_message) => {
let condition = self.brillig_context.allocate_register();

self.convert_ssa_binary(
Expand All @@ -223,7 +223,7 @@ impl<'block> BrilligBlock<'block> {
condition,
);

self.brillig_context.constrain_instruction(condition);
self.brillig_context.constrain_instruction(condition, assert_message.clone());
self.brillig_context.deallocate_register(condition);
}
Instruction::Allocate => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ pub(crate) fn directive_invert() -> GeneratedBrillig {
},
BrilligOpcode::Stop,
],
assert_messages: Default::default(),
locations: Default::default(),
}
}
Expand Down Expand Up @@ -101,6 +102,7 @@ pub(crate) fn directive_quotient(bit_size: u32) -> GeneratedBrillig {
},
BrilligOpcode::Stop,
],
assert_messages: Default::default(),
locations: Default::default(),
}
}
9 changes: 8 additions & 1 deletion crates/noirc_evaluator/src/brillig/brillig_ir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -361,13 +361,20 @@ impl BrilligContext {
impl BrilligContext {
/// Emits brillig bytecode to jump to a trap condition if `condition`
/// is false.
pub(crate) fn constrain_instruction(&mut self, condition: RegisterIndex) {
pub(crate) fn constrain_instruction(
&mut self,
condition: RegisterIndex,
assert_message: Option<String>,
) {
self.debug_show.constrain_instruction(condition);
self.add_unresolved_jump(
BrilligOpcode::JumpIf { condition, location: 0 },
self.next_section_label(),
);
self.push_opcode(BrilligOpcode::Trap);
if let Some(assert_message) = assert_message {
self.obj.add_assert_message_to_last_opcode(assert_message);
}
self.enter_next_section();
}

Expand Down
22 changes: 19 additions & 3 deletions crates/noirc_evaluator/src/brillig/brillig_ir/artifact.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,16 @@ pub(crate) enum BrilligParameter {
pub(crate) struct GeneratedBrillig {
pub(crate) byte_code: Vec<BrilligOpcode>,
pub(crate) locations: BTreeMap<OpcodeLocation, CallStack>,
pub(crate) assert_messages: BTreeMap<OpcodeLocation, String>,
}

#[derive(Default, Debug, Clone)]
/// Artifacts resulting from the compilation of a function into brillig byte code.
/// Currently it is just the brillig bytecode of the function.
/// It includes the bytecode of the function and all the metadata that allows linking with other functions.
pub(crate) struct BrilligArtifact {
byte_code: Vec<BrilligOpcode>,
pub(crate) byte_code: Vec<BrilligOpcode>,
/// A map of bytecode positions to assertion messages
pub(crate) assert_messages: BTreeMap<OpcodeLocation, String>,
/// The set of jumps that need to have their locations
/// resolved.
unresolved_jumps: Vec<(JumpInstructionPosition, UnresolvedJumpLocation)>,
Expand Down Expand Up @@ -68,7 +71,11 @@ impl BrilligArtifact {
/// Resolves all jumps and generates the final bytecode
pub(crate) fn finish(mut self) -> GeneratedBrillig {
self.resolve_jumps();
GeneratedBrillig { byte_code: self.byte_code, locations: self.locations }
GeneratedBrillig {
byte_code: self.byte_code,
locations: self.locations,
assert_messages: self.assert_messages,
}
}

/// Gets the first unresolved function call of this artifact.
Expand Down Expand Up @@ -131,6 +138,10 @@ impl BrilligArtifact {
.push((position_in_bytecode + offset, label_id.clone()));
}

for (position_in_bytecode, message) in &obj.assert_messages {
self.assert_messages.insert(position_in_bytecode + offset, message.clone());
}

for (position_in_bytecode, call_stack) in obj.locations.iter() {
self.locations.insert(position_in_bytecode + offset, call_stack.clone());
}
Expand Down Expand Up @@ -242,4 +253,9 @@ impl BrilligArtifact {
pub(crate) fn set_call_stack(&mut self, call_stack: CallStack) {
self.call_stack = call_stack;
}

pub(crate) fn add_assert_message_to_last_opcode(&mut self, message: String) {
let position = self.index_of_next_opcode() - 1;
self.assert_messages.insert(position, message);
}
}
22 changes: 17 additions & 5 deletions crates/noirc_evaluator/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@ use crate::ssa::ir::dfg::CallStack;

#[derive(Debug, PartialEq, Eq, Clone, Error)]
pub enum RuntimeError {
// We avoid showing the actual lhs and rhs since most of the time they are just 0
// and 1 respectively. This would confuse users if a constraint such as
// assert(foo < bar) fails with "failed constraint: 0 = 1."
#[error("Failed constraint")]
FailedConstraint { lhs: Box<Expression>, rhs: Box<Expression>, call_stack: CallStack },
#[error("{}", format_failed_constraint(.assert_message))]
FailedConstraint {
lhs: Box<Expression>,
rhs: Box<Expression>,
call_stack: CallStack,
assert_message: Option<String>,
},
#[error(transparent)]
InternalError(#[from] InternalError),
#[error("Index out of bounds, array has size {index:?}, but index was {array_size:?}")]
Expand All @@ -39,6 +41,16 @@ pub enum RuntimeError {
AssertConstantFailed { call_stack: CallStack },
}

// We avoid showing the actual lhs and rhs since most of the time they are just 0
// and 1 respectively. This would confuse users if a constraint such as
// assert(foo < bar) fails with "failed constraint: 0 = 1."
fn format_failed_constraint(message: &Option<String>) -> String {
match message {
Some(message) => format!("Failed constraint: '{}'", message),
None => "Failed constraint".to_owned(),
}
}

#[derive(Debug, PartialEq, Eq, Clone, Error)]
pub enum InternalError {
#[error("ICE: Both expressions should have degree<=1")]
Expand Down
9 changes: 7 additions & 2 deletions crates/noirc_evaluator/src/ssa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,12 @@ pub fn create_circuit(
optimize_into_acir(program, enable_ssa_logging, enable_brillig_logging)?;
let opcodes = generated_acir.take_opcodes();
let GeneratedAcir {
current_witness_index, return_witnesses, locations, input_witnesses, ..
current_witness_index,
return_witnesses,
locations,
input_witnesses,
assert_messages,
..
} = generated_acir;

let abi = gen_abi(context, func_sig, &input_witnesses, return_witnesses.clone());
Expand All @@ -99,7 +104,7 @@ pub fn create_circuit(
private_parameters,
public_parameters,
return_values,
assert_messages: Default::default(),
assert_messages: assert_messages.into_iter().collect(),
};

// This converts each im::Vector in the BTreeMap to a Vec
Expand Down
Loading
Loading