Skip to content

Commit

Permalink
fix: add new BrilligFunctionUnsatisfiedConstrain resolution error v…
Browse files Browse the repository at this point in the history
…ariant (#6)

* Convert BrilligSolver Trap failure into `BrilligFunctionUnsatisfiedConstrain` error

* extract extract_failure_payload function to improve readability

* Changed the variant checking on ACVM.map_brillig_error from OpcodeResolutionError::BrilligFunctionFailed to OpcodeResolutionError::BrilligFunctionUnsatisfiedConstrain

* return ownership of the solver to brillig context when the debugger fails due to `BrilligFunctionUnsatisfiedConstrain`
  • Loading branch information
anaPerezGhiglia authored Jul 3, 2024
1 parent a8928dd commit 3936c62
Show file tree
Hide file tree
Showing 7 changed files with 110 additions and 64 deletions.
121 changes: 68 additions & 53 deletions acvm-repo/acvm/src/pwg/brillig.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,70 +162,85 @@ impl<'b, B: BlackBoxFunctionSolver<F>, F: AcirField> BrilligSolver<'b, F, B> {
VMStatus::Finished { .. } => Ok(BrilligSolverStatus::Finished),
VMStatus::InProgress => Ok(BrilligSolverStatus::InProgress),
VMStatus::Failure { reason, call_stack } => {
let payload = match reason {
FailureReason::RuntimeError { message } => {
Some(ResolvedAssertionPayload::String(message))
}
FailureReason::Trap { revert_data_offset, revert_data_size } => {
// Since noir can only revert with strings currently, we can parse return data as a string
if revert_data_size == 0 {
None
} else {
let memory = self.vm.get_memory();
let mut revert_values_iter = memory
[revert_data_offset..(revert_data_offset + revert_data_size)]
.iter();
let error_selector = ErrorSelector::new(
revert_values_iter
.next()
.expect("Incorrect revert data size")
.try_into()
.expect("Error selector is not u64"),
);
let call_stack = call_stack
.iter()
.map(|brillig_index| OpcodeLocation::Brillig {
acir_index: self.acir_index,
brillig_index: *brillig_index,
})
.collect();
let payload = self.extract_failure_payload(&reason);

match error_selector {
STRING_ERROR_SELECTOR => {
// If the error selector is 0, it means the error is a string
let string = revert_values_iter
.map(|memory_value| {
let as_u8: u8 = memory_value
.try_into()
.expect("String item is not u8");
as_u8 as char
})
.collect();
Some(ResolvedAssertionPayload::String(string))
}
_ => {
// If the error selector is not 0, it means the error is a custom error
Some(ResolvedAssertionPayload::Raw(RawAssertionPayload {
selector: error_selector,
data: revert_values_iter
.map(|value| value.to_field())
.collect(),
}))
}
}
let resolution_error = |payload, call_stack| match &reason {
FailureReason::RuntimeError { .. } => {
OpcodeResolutionError::BrilligFunctionFailed { payload, call_stack }
}
FailureReason::Trap { .. } => {
OpcodeResolutionError::BrilligFunctionUnsatisfiedConstrain {
payload,
call_stack,
}
}
};
Err(OpcodeResolutionError::BrilligFunctionFailed {
payload,
call_stack: call_stack
.iter()
.map(|brillig_index| OpcodeLocation::Brillig {
acir_index: self.acir_index,
brillig_index: *brillig_index,
})
.collect(),
})

Err(resolution_error(payload, call_stack))
}
VMStatus::ForeignCallWait { function, inputs } => {
Ok(BrilligSolverStatus::ForeignCallWait(ForeignCallWaitInfo { function, inputs }))
}
}
}

fn extract_failure_payload(
&self,
reason: &FailureReason,
) -> Option<ResolvedAssertionPayload<F>> {
match reason {
FailureReason::RuntimeError { message } => {
Some(ResolvedAssertionPayload::String(message.clone()))
}
FailureReason::Trap { revert_data_offset, revert_data_size } => {
// Since noir can only revert with strings currently, we can parse return data as a string
if *revert_data_size == 0 {
None
} else {
let memory = self.vm.get_memory();
let mut revert_values_iter = memory
[*revert_data_offset..(*revert_data_offset + *revert_data_size)]
.iter();
let error_selector = ErrorSelector::new(
revert_values_iter
.next()
.expect("Incorrect revert data size")
.try_into()
.expect("Error selector is not u64"),
);

match error_selector {
STRING_ERROR_SELECTOR => {
// If the error selector is 0, it means the error is a string
let string = revert_values_iter
.map(|memory_value| {
let as_u8: u8 =
memory_value.try_into().expect("String item is not u8");
as_u8 as char
})
.collect();
Some(ResolvedAssertionPayload::String(string))
}
_ => {
// If the error selector is not 0, it means the error is a custom error
Some(ResolvedAssertionPayload::Raw(RawAssertionPayload {
selector: error_selector,
data: revert_values_iter.map(|value| value.to_field()).collect(),
}))
}
}
}
}
}
}

pub(crate) fn finalize(
self,
witness: &mut WitnessMap<F>,
Expand Down
7 changes: 6 additions & 1 deletion acvm-repo/acvm/src/pwg/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,11 @@ pub enum OpcodeResolutionError<F> {
call_stack: Vec<OpcodeLocation>,
payload: Option<ResolvedAssertionPayload<F>>,
},
#[error("Cannot satisfy constraint")]
BrilligFunctionUnsatisfiedConstrain {
call_stack: Vec<OpcodeLocation>,
payload: Option<ResolvedAssertionPayload<F>>,
},
#[error("Attempted to call `main` with a `Call` opcode")]
AcirMainCallAttempted { opcode_location: ErrorLocation },
#[error("{results_size:?} result values were provided for {outputs_size:?} call output witnesses, most likely due to bad ACIR codegen")]
Expand Down Expand Up @@ -501,7 +506,7 @@ impl<'a, F: AcirField, B: BlackBoxFunctionSolver<F>> ACVM<'a, F, B> {

fn map_brillig_error(&self, mut err: OpcodeResolutionError<F>) -> OpcodeResolutionError<F> {
match &mut err {
OpcodeResolutionError::BrilligFunctionFailed { call_stack, payload } => {
OpcodeResolutionError::BrilligFunctionUnsatisfiedConstrain { call_stack, payload } => {
// Some brillig errors have static strings as payloads, we can resolve them here
let last_location =
call_stack.last().expect("Call stacks should have at least one item");
Expand Down
2 changes: 1 addition & 1 deletion acvm-repo/acvm/tests/solver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -670,7 +670,7 @@ fn unsatisfied_opcode_resolved_brillig() {
let solver_status = acvm.solve();
assert_eq!(
solver_status,
ACVMStatus::Failure(OpcodeResolutionError::BrilligFunctionFailed {
ACVMStatus::Failure(OpcodeResolutionError::BrilligFunctionUnsatisfiedConstrain {
payload: None,
call_stack: vec![OpcodeLocation::Brillig { acir_index: 0, brillig_index: 3 }]
}),
Expand Down
12 changes: 9 additions & 3 deletions acvm-repo/acvm_js/src/execute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -200,9 +200,11 @@ impl<'a, B: BlackBoxFunctionSolver<FieldElement>> ProgramExecutor<'a, B> {
opcode_location: ErrorLocation::Resolved(opcode_location),
..
} => Some(vec![*opcode_location]),
OpcodeResolutionError::BrilligFunctionFailed { call_stack, .. } => {
Some(call_stack.clone())
}
OpcodeResolutionError::BrilligFunctionFailed { call_stack, .. }
| OpcodeResolutionError::BrilligFunctionUnsatisfiedConstrain {
call_stack,
..
} => Some(call_stack.clone()),
_ => None,
};
// If the failed opcode has an assertion message, integrate it into the error message for backwards compatibility.
Expand All @@ -215,6 +217,10 @@ impl<'a, B: BlackBoxFunctionSolver<FieldElement>> ProgramExecutor<'a, B> {
| OpcodeResolutionError::BrilligFunctionFailed {
payload: Some(payload),
..
}
| OpcodeResolutionError::BrilligFunctionUnsatisfiedConstrain {
payload: Some(payload),
..
} => match payload {
ResolvedAssertionPayload::Raw(raw_payload) => {
("Assertion failed".to_string(), Some(raw_payload))
Expand Down
17 changes: 12 additions & 5 deletions tooling/debugger/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ use acvm::acir::circuit::{Circuit, Opcode, OpcodeLocation};
use acvm::acir::native_types::{Witness, WitnessMap};
use acvm::brillig_vm::MemoryValue;
use acvm::pwg::{
ACVMStatus, BrilligSolver, BrilligSolverStatus, ForeignCallWaitInfo, StepResult, ACVM,
ACVMStatus, BrilligSolver, BrilligSolverStatus, ForeignCallWaitInfo, OpcodeResolutionError,
StepResult, ACVM,
};
use acvm::{BlackBoxFunctionSolver, FieldElement};

Expand Down Expand Up @@ -295,10 +296,16 @@ impl<'a, B: BlackBoxFunctionSolver<FieldElement>> DebugContext<'a, B> {
self.brillig_solver = Some(solver);
self.handle_foreign_call(foreign_call)
}
Err(err) => DebugCommandResult::Error(NargoError::ExecutionError(
// TODO: debugger does not handle multiple acir calls
ExecutionError::SolvingError(err, None),
)),
Err(err) => {
if let OpcodeResolutionError::BrilligFunctionUnsatisfiedConstrain { .. } = err {
// return solver ownership so brillig_solver it has the right opcode location
self.brillig_solver = Some(solver);
}
// TODO: should we return solver ownership in all Err scenarios>?
DebugCommandResult::Error(NargoError::ExecutionError(ExecutionError::SolvingError(
err, None,
)))
}
}
}

Expand Down
5 changes: 5 additions & 0 deletions tooling/nargo/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ impl<F: AcirField> NargoError<F> {
| OpcodeResolutionError::UnsatisfiedConstrain { .. }
| OpcodeResolutionError::AcirMainCallAttempted { .. }
| OpcodeResolutionError::BrilligFunctionFailed { .. }
| OpcodeResolutionError::BrilligFunctionUnsatisfiedConstrain { .. }
| OpcodeResolutionError::AcirCallOutputsMismatch { .. } => None,
OpcodeResolutionError::BlackBoxFunctionFailed(_, reason) => {
Some(reason.to_string())
Expand Down Expand Up @@ -111,6 +112,10 @@ fn extract_locations_from_error<F: AcirField>(
ExecutionError::SolvingError(
OpcodeResolutionError::BrilligFunctionFailed { .. },
acir_call_stack,
)
| ExecutionError::SolvingError(
OpcodeResolutionError::BrilligFunctionUnsatisfiedConstrain { .. },
acir_call_stack,
) => acir_call_stack.clone(),
ExecutionError::AssertionFailed(_, call_stack) => Some(call_stack.clone()),
ExecutionError::SolvingError(
Expand Down
10 changes: 9 additions & 1 deletion tooling/nargo/src/ops/execute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,11 @@ impl<'a, F: AcirField, B: BlackBoxFunctionSolver<F>, E: ForeignCallExecutor<F>>
self.call_stack.push(resolved_location);
Some(self.call_stack.clone())
}
OpcodeResolutionError::BrilligFunctionFailed { call_stack, .. } => {
OpcodeResolutionError::BrilligFunctionFailed { call_stack, .. }
| OpcodeResolutionError::BrilligFunctionUnsatisfiedConstrain {
call_stack,
..
} => {
let brillig_call_stack =
call_stack.iter().map(|location| ResolvedOpcodeLocation {
acir_function_index: self.current_function_index,
Expand All @@ -111,6 +115,10 @@ impl<'a, F: AcirField, B: BlackBoxFunctionSolver<F>, E: ForeignCallExecutor<F>>

let assertion_payload: Option<ResolvedAssertionPayload<F>> = match &error {
OpcodeResolutionError::BrilligFunctionFailed { payload, .. }
| OpcodeResolutionError::BrilligFunctionUnsatisfiedConstrain {
payload,
..
}
| OpcodeResolutionError::UnsatisfiedConstrain { payload, .. } => {
payload.clone()
}
Expand Down

0 comments on commit 3936c62

Please sign in to comment.