diff --git a/acvm/src/pwg/block.rs b/acvm/src/pwg/block.rs index 9785735ae..05ef57ef4 100644 --- a/acvm/src/pwg/block.rs +++ b/acvm/src/pwg/block.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; use acir::{ - circuit::opcodes::{BlockId, MemOp}, + circuit::opcodes::MemOp, native_types::{Witness, WitnessMap}, FieldElement, }; @@ -12,29 +12,11 @@ use super::{ }; use super::{OpcodeNotSolvable, OpcodeResolution, OpcodeResolutionError}; -/// Maps a block to its emulated state -#[derive(Default)] -pub struct Blocks { - blocks: HashMap, -} - -impl Blocks { - pub fn solve( - &mut self, - id: BlockId, - trace: &[MemOp], - solved_witness: &mut WitnessMap, - ) -> Result { - let solver = self.blocks.entry(id).or_default(); - solver.solve(solved_witness, trace) - } -} - /// Maintains the state for solving Block opcode /// block_value is the value of the Block at the solved_operations step /// solved_operations is the number of solved elements in the block #[derive(Default)] -struct BlockSolver { +pub(super) struct BlockSolver { block_value: HashMap, solved_operations: usize, } @@ -123,15 +105,14 @@ impl BlockSolver { #[cfg(test)] mod tests { use acir::{ - circuit::opcodes::{BlockId, MemOp}, + circuit::opcodes::MemOp, native_types::{Expression, Witness, WitnessMap}, FieldElement, }; + use super::BlockSolver; use crate::pwg::insert_value; - use super::Blocks; - #[test] fn test_solver() { let mut index = FieldElement::zero(); @@ -157,7 +138,6 @@ mod tests { index: Expression::one(), value: Expression::from(Witness(4)), }); - let id = BlockId::default(); let mut initial_witness = WitnessMap::new(); let mut value = FieldElement::zero(); insert_value(&Witness(1), value, &mut initial_witness).unwrap(); @@ -165,8 +145,8 @@ mod tests { insert_value(&Witness(2), value, &mut initial_witness).unwrap(); value = value + value; insert_value(&Witness(3), value, &mut initial_witness).unwrap(); - let mut blocks = Blocks::default(); - blocks.solve(id, &trace, &mut initial_witness).unwrap(); + let mut block_solver = BlockSolver::default(); + block_solver.solve(&mut initial_witness, &trace).unwrap(); assert_eq!(initial_witness[&Witness(4)], FieldElement::one()); } } diff --git a/acvm/src/pwg/mod.rs b/acvm/src/pwg/mod.rs index cf3a9f281..7fd34c590 100644 --- a/acvm/src/pwg/mod.rs +++ b/acvm/src/pwg/mod.rs @@ -1,14 +1,19 @@ // Re-usable methods that backends can use to implement their PWG +use std::collections::HashMap; + use crate::{Language, PartialWitnessGenerator}; use acir::{ brillig_vm::ForeignCallResult, - circuit::{brillig::Brillig, Opcode}, + circuit::{brillig::Brillig, opcodes::BlockId, Opcode}, native_types::{Expression, Witness, WitnessMap}, BlackBoxFunc, FieldElement, }; -use self::{arithmetic::ArithmeticSolver, brillig::BrilligSolver, directives::solve_directives}; +use self::{ + arithmetic::ArithmeticSolver, block::BlockSolver, brillig::BrilligSolver, + directives::solve_directives, +}; use thiserror::Error; @@ -22,8 +27,6 @@ mod directives; mod blackbox; mod block; -// Re-export `Blocks` so that it can be passed to `pwg::solve` -pub use block::Blocks; pub use brillig::ForeignCallWaitInfo; #[derive(Debug, PartialEq)] @@ -31,15 +34,12 @@ pub enum PartialWitnessGeneratorStatus { /// All opcodes have been solved. Solved, - /// The `PartialWitnessGenerator` has encountered a request for a Brillig [foreign call][acir::brillig_vm::Opcode::ForeignCall] - /// to retrieve information from outside of the ACVM. - /// The result of the foreign call is inserted into the `Brillig` opcode which made the call using [`UnresolvedBrilligCall::resolve`]. + /// The ACVM has encountered a request for a Brillig [foreign call][acir::brillig_vm::Opcode::ForeignCall] + /// to retrieve information from outside of the ACVM. The result of the foreign call must be passed back + /// to the ACVM using [`ACVM::resolve_pending_foreign_call`]. /// - /// Once this is done, the `PartialWitnessGenerator` can be restarted to solve the new set of opcodes. - RequiresForeignCall { - unsolved_opcodes: Vec, - unresolved_brillig_calls: Vec, - }, + /// Once this is done, the ACVM can be restarted to solve the remaining opcodes. + RequiresForeignCall, } #[derive(Debug, PartialEq)] @@ -86,84 +86,156 @@ pub enum OpcodeResolutionError { BrilligFunctionFailed(String), } -/// Executes a [`Circuit`] against an [initial witness][`WitnessMap`] to calculate the solved partial witness. -pub fn solve( - backend: &impl PartialWitnessGenerator, - initial_witness: &mut WitnessMap, - blocks: &mut Blocks, - mut opcode_to_solve: Vec, -) -> Result { - let mut unresolved_opcodes: Vec = Vec::new(); - let mut unresolved_brillig_calls: Vec = Vec::new(); - while !opcode_to_solve.is_empty() { - unresolved_opcodes.clear(); - let mut stalled = true; - let mut opcode_not_solvable = None; - for opcode in &opcode_to_solve { - let resolution = match opcode { - Opcode::Arithmetic(expr) => ArithmeticSolver::solve(initial_witness, expr), - Opcode::BlackBoxFuncCall(bb_func) => { - blackbox::solve(backend, initial_witness, bb_func) - } - Opcode::Directive(directive) => solve_directives(initial_witness, directive), - Opcode::Block(block) | Opcode::ROM(block) | Opcode::RAM(block) => { - blocks.solve(block.id, &block.trace, initial_witness) - } - Opcode::Brillig(brillig) => BrilligSolver::solve(initial_witness, brillig), - }; - match resolution { - Ok(OpcodeResolution::Solved) => { - stalled = false; - } - Ok(OpcodeResolution::InProgress) => { - stalled = false; - unresolved_opcodes.push(opcode.clone()); - } - Ok(OpcodeResolution::InProgressBrillig(oracle_wait_info)) => { - stalled = false; - // InProgressBrillig Oracles must be externally re-solved - let brillig = match opcode { - Opcode::Brillig(brillig) => brillig.clone(), - _ => unreachable!("Brillig resolution for non brillig opcode"), - }; - unresolved_brillig_calls.push(UnresolvedBrilligCall { - brillig, - foreign_call_wait_info: oracle_wait_info, - }) - } - Ok(OpcodeResolution::Stalled(not_solvable)) => { - if opcode_not_solvable.is_none() { - // we keep track of the first unsolvable opcode - opcode_not_solvable = Some(not_solvable); +pub struct ACVM { + backend: B, + /// Stores the solver for each [block][`Opcode::Block`] opcode. This persists their internal state to prevent recomputation. + block_solvers: HashMap, + /// A list of opcodes which are to be executed by the ACVM. + /// + /// Note that this doesn't include any opcodes which are waiting on a pending foreign call. + opcodes: Vec, + + witness_map: WitnessMap, + + /// A list of foreign calls which must be resolved before the ACVM can resume execution. + pending_foreign_calls: Vec, +} + +impl ACVM { + pub fn new(backend: B, opcodes: Vec, initial_witness: WitnessMap) -> Self { + ACVM { + backend, + block_solvers: HashMap::default(), + opcodes, + witness_map: initial_witness, + pending_foreign_calls: Vec::new(), + } + } + + /// Returns a reference to the current state of the ACVM's [`WitnessMap`]. + /// + /// Once execution has completed, the witness map can be extracted using [`ACVM::finalize`] + pub fn witness_map(&self) -> &WitnessMap { + &self.witness_map + } + + /// Returns a slice containing the opcodes which remain to be solved. + /// + /// Note: this doesn't include any opcodes which are waiting on a pending foreign call. + pub fn unresolved_opcodes(&self) -> &[Opcode] { + &self.opcodes + } + + /// Finalize the ACVM execution, returning the resulting [`WitnessMap`]. + pub fn finalize(self) -> WitnessMap { + if self.opcodes.is_empty() || self.get_pending_foreign_call().is_some() { + panic!("ACVM is not ready to be finalized"); + } + self.witness_map + } + + /// Return a reference to the arguments for the next pending foreign call, if one exists. + pub fn get_pending_foreign_call(&self) -> Option<&ForeignCallWaitInfo> { + self.pending_foreign_calls.first().map(|foreign_call| &foreign_call.foreign_call_wait_info) + } + + /// Resolves a pending foreign call using a result calculated outside of the ACVM. + pub fn resolve_pending_foreign_call(&mut self, foreign_call_result: ForeignCallResult) { + // Remove the first foreign call and inject the result to create a new opcode. + let foreign_call = self.pending_foreign_calls.remove(0); + let resolved_brillig = foreign_call.resolve(foreign_call_result); + + // Mark this opcode to be executed next. + self.opcodes.insert(0, Opcode::Brillig(resolved_brillig)); + } + + /// Executes the ACVM's circuit until execution halts. + /// + /// Execution can halt due to three reasons: + /// 1. All opcodes have been executed successfully. + /// 2. The circuit has been found to be unsatisfiable. + /// 2. A Brillig [foreign call][`UnresolvedBrilligCall`] has been encountered and must be resolved. + pub fn solve(&mut self) -> Result { + // TODO: Prevent execution with outstanding foreign calls? + let mut unresolved_opcodes: Vec = Vec::new(); + while !self.opcodes.is_empty() { + unresolved_opcodes.clear(); + let mut stalled = true; + let mut opcode_not_solvable = None; + for opcode in &self.opcodes { + let resolution = match opcode { + Opcode::Arithmetic(expr) => { + ArithmeticSolver::solve(&mut self.witness_map, expr) } - // We push those opcodes not solvable to the back as - // it could be because the opcodes are out of order, i.e. this assignment - // relies on a later opcodes' results - unresolved_opcodes.push(opcode.clone()); - } - Err(OpcodeResolutionError::OpcodeNotSolvable(_)) => { - unreachable!("ICE - Result should have been converted to GateResolution") + Opcode::BlackBoxFuncCall(bb_func) => { + blackbox::solve(&self.backend, &mut self.witness_map, bb_func) + } + Opcode::Directive(directive) => { + solve_directives(&mut self.witness_map, directive) + } + Opcode::Block(block) | Opcode::ROM(block) | Opcode::RAM(block) => { + let solver = self.block_solvers.entry(block.id).or_default(); + solver.solve(&mut self.witness_map, &block.trace) + } + Opcode::Brillig(brillig) => { + BrilligSolver::solve(&mut self.witness_map, brillig) + } + }; + match resolution { + Ok(OpcodeResolution::Solved) => { + stalled = false; + } + Ok(OpcodeResolution::InProgress) => { + stalled = false; + unresolved_opcodes.push(opcode.clone()); + } + Ok(OpcodeResolution::InProgressBrillig(oracle_wait_info)) => { + stalled = false; + // InProgressBrillig Oracles must be externally re-solved + let brillig = match opcode { + Opcode::Brillig(brillig) => brillig.clone(), + _ => unreachable!("Brillig resolution for non brillig opcode"), + }; + self.pending_foreign_calls.push(UnresolvedBrilligCall { + brillig, + foreign_call_wait_info: oracle_wait_info, + }) + } + Ok(OpcodeResolution::Stalled(not_solvable)) => { + if opcode_not_solvable.is_none() { + // we keep track of the first unsolvable opcode + opcode_not_solvable = Some(not_solvable); + } + // We push those opcodes not solvable to the back as + // it could be because the opcodes are out of order, i.e. this assignment + // relies on a later opcodes' results + unresolved_opcodes.push(opcode.clone()); + } + Err(OpcodeResolutionError::OpcodeNotSolvable(_)) => { + unreachable!("ICE - Result should have been converted to GateResolution") + } + Err(err) => return Err(err), } - Err(err) => return Err(err), + } + + // Before potentially ending execution, we must save the list of opcodes which remain to be solved. + std::mem::swap(&mut self.opcodes, &mut unresolved_opcodes); + + // We have oracles that must be externally resolved + if self.get_pending_foreign_call().is_some() { + return Ok(PartialWitnessGeneratorStatus::RequiresForeignCall); + } + + // We are stalled because of an opcode being bad + if stalled && !self.opcodes.is_empty() { + return Err(OpcodeResolutionError::OpcodeNotSolvable( + opcode_not_solvable + .expect("infallible: cannot be stalled and None at the same time"), + )); } } - // We have foreign calls that must be externally resolved - if !unresolved_brillig_calls.is_empty() { - return Ok(PartialWitnessGeneratorStatus::RequiresForeignCall { - unsolved_opcodes: unresolved_opcodes, - unresolved_brillig_calls, - }); - } - // We are stalled because of an opcode being bad - if stalled && !unresolved_opcodes.is_empty() { - return Err(OpcodeResolutionError::OpcodeNotSolvable( - opcode_not_solvable - .expect("infallible: cannot be stalled and None at the same time"), - )); - } - std::mem::swap(&mut opcode_to_solve, &mut unresolved_opcodes); + Ok(PartialWitnessGeneratorStatus::Solved) } - Ok(PartialWitnessGeneratorStatus::Solved) } // Returns the concrete value for a particular witness diff --git a/acvm/tests/solver.rs b/acvm/tests/solver.rs index ddb2a30d1..cf7469fb6 100644 --- a/acvm/tests/solver.rs +++ b/acvm/tests/solver.rs @@ -14,8 +14,8 @@ use acir::{ use acvm::{ pwg::{ - self, Blocks, ForeignCallWaitInfo, OpcodeResolution, OpcodeResolutionError, - PartialWitnessGeneratorStatus, UnresolvedBrilligCall, + ForeignCallWaitInfo, OpcodeResolution, OpcodeResolutionError, + PartialWitnessGeneratorStatus, ACVM, }, PartialWitnessGenerator, }; @@ -132,39 +132,34 @@ fn inversion_brillig_oracle_equivalence() { }), ]; - let backend = StubbedPwg; - - let mut witness_assignments = BTreeMap::from([ + let witness_assignments = BTreeMap::from([ (Witness(1), FieldElement::from(2u128)), (Witness(2), FieldElement::from(3u128)), ]) .into(); - let mut blocks = Blocks::default(); + + let mut acvm = ACVM::new(StubbedPwg, opcodes, witness_assignments); // use the partial witness generation solver with our acir program - let solver_status = pwg::solve(&backend, &mut witness_assignments, &mut blocks, opcodes) - .expect("should stall on oracle"); - let PartialWitnessGeneratorStatus::RequiresForeignCall { unsolved_opcodes, mut unresolved_brillig_calls, .. } = solver_status else { - panic!("Should require oracle data") - }; + let solver_status = acvm.solve().expect("should stall on brillig call"); - assert_eq!(unsolved_opcodes.len(), 0, "brillig should have been removed"); - assert_eq!(unresolved_brillig_calls.len(), 1, "should have a brillig oracle request"); + assert_eq!( + solver_status, + PartialWitnessGeneratorStatus::RequiresForeignCall, + "Should require oracle data" + ); + assert!(acvm.unresolved_opcodes().is_empty(), "brillig should have been removed"); - let foreign_call = unresolved_brillig_calls.remove(0); - let foreign_call_wait_info: &ForeignCallWaitInfo = &foreign_call.foreign_call_wait_info; + let foreign_call_wait_info: &ForeignCallWaitInfo = + acvm.get_pending_foreign_call().expect("should have a brillig foreign call request"); assert_eq!(foreign_call_wait_info.inputs.len(), 1, "Should be waiting for a single input"); // As caller of VM, need to resolve foreign calls - let foreign_call_result = - Value::from(foreign_call.foreign_call_wait_info.inputs[0][0].to_field().inverse()); + let foreign_call_result = Value::from(foreign_call_wait_info.inputs[0][0].to_field().inverse()); // Alter Brillig oracle opcode with foreign call resolution - let brillig: Brillig = foreign_call.resolve(foreign_call_result.into()); - let mut next_opcodes_for_solving = vec![Opcode::Brillig(brillig)]; - next_opcodes_for_solving.extend_from_slice(&unsolved_opcodes[..]); + acvm.resolve_pending_foreign_call(foreign_call_result.into()); + // After filling data request, continue solving - let solver_status = - pwg::solve(&backend, &mut witness_assignments, &mut blocks, next_opcodes_for_solving) - .expect("should not stall on oracle"); + let solver_status = acvm.solve().expect("should not stall on brillig call"); assert_eq!(solver_status, PartialWitnessGeneratorStatus::Solved, "should be fully solved"); } @@ -262,64 +257,184 @@ fn double_inversion_brillig_oracle() { }), ]; - let backend = StubbedPwg; - - let mut witness_assignments = BTreeMap::from([ + let witness_assignments = BTreeMap::from([ (Witness(1), FieldElement::from(2u128)), (Witness(2), FieldElement::from(3u128)), (Witness(8), FieldElement::from(5u128)), (Witness(9), FieldElement::from(10u128)), ]) .into(); - let mut blocks = Blocks::default(); + + let mut acvm = ACVM::new(StubbedPwg, opcodes, witness_assignments); + // use the partial witness generation solver with our acir program - let solver_status = pwg::solve(&backend, &mut witness_assignments, &mut blocks, opcodes) - .expect("should stall on oracle"); - let PartialWitnessGeneratorStatus::RequiresForeignCall { unsolved_opcodes, mut unresolved_brillig_calls, .. } = solver_status else { - panic!("Should require oracle data") - }; + let solver_status = acvm.solve().expect("should stall on oracle"); + assert_eq!( + solver_status, + PartialWitnessGeneratorStatus::RequiresForeignCall, + "Should require oracle data" + ); + assert!(acvm.unresolved_opcodes().is_empty(), "brillig should have been removed"); + + let foreign_call_wait_info: &ForeignCallWaitInfo = + acvm.get_pending_foreign_call().expect("should have a brillig foreign call request"); + assert_eq!(foreign_call_wait_info.inputs.len(), 1, "Should be waiting for a single input"); - assert_eq!(unsolved_opcodes.len(), 0, "brillig should have been removed"); - assert_eq!(unresolved_brillig_calls.len(), 1, "should have a brillig oracle request"); + let x_plus_y_inverse = Value::from(foreign_call_wait_info.inputs[0][0].to_field().inverse()); - let foreign_call = unresolved_brillig_calls.remove(0); - let foreign_call_wait_info: &ForeignCallWaitInfo = &foreign_call.foreign_call_wait_info; + // Resolve Brillig foreign call + acvm.resolve_pending_foreign_call(x_plus_y_inverse.into()); + + // After filling data request, continue solving + let solver_status = acvm.solve().expect("should stall on brillig call"); + assert_eq!( + solver_status, + PartialWitnessGeneratorStatus::RequiresForeignCall, + "Should require oracle data" + ); + assert!(acvm.unresolved_opcodes().is_empty(), "should be fully solved"); + + let foreign_call_wait_info = + acvm.get_pending_foreign_call().expect("should have a brillig foreign call request"); assert_eq!(foreign_call_wait_info.inputs.len(), 1, "Should be waiting for a single input"); - let x_plus_y_inverse = foreign_call.foreign_call_wait_info.inputs[0][0].to_field().inverse(); + let i_plus_j_inverse = Value::from(foreign_call_wait_info.inputs[0][0].to_field().inverse()); + assert_ne!(x_plus_y_inverse, i_plus_j_inverse); + // Alter Brillig oracle opcode - let brillig: Brillig = foreign_call.resolve(Value::from(x_plus_y_inverse).into()); + acvm.resolve_pending_foreign_call(i_plus_j_inverse.into()); - let mut next_opcodes_for_solving = vec![Opcode::Brillig(brillig)]; - next_opcodes_for_solving.extend_from_slice(&unsolved_opcodes[..]); // After filling data request, continue solving - let solver_status = - pwg::solve(&backend, &mut witness_assignments, &mut blocks, next_opcodes_for_solving) - .expect("should stall on oracle"); - let PartialWitnessGeneratorStatus::RequiresForeignCall { unsolved_opcodes, mut unresolved_brillig_calls, .. } = solver_status else { - panic!("Should require oracle data") - }; + let solver_status = acvm.solve().expect("should not stall on brillig call"); + assert_eq!(solver_status, PartialWitnessGeneratorStatus::Solved, "should be fully solved"); +} - assert!(unsolved_opcodes.is_empty(), "should be fully solved"); - assert_eq!(unresolved_brillig_calls.len(), 1, "should have no unresolved oracles"); +#[test] +fn oracle_dependent_execution() { + // This test ensures that we properly track the list of opcodes which still need to be resolved + // across any brillig foreign calls we may have to perform. + // + // Opcodes below describe the following: + // fn main(x : Field, y : pub Field) { + // assert(x == y); + // let x_inv = Oracle("inverse", x); + // let y_inv = Oracle("inverse", y); + // + // assert(x_inv == y_inv); + // } + // Also performs an unrelated equality check + // just for the sake of testing multiple brillig opcodes. + let fe_0 = FieldElement::zero(); + let fe_1 = FieldElement::one(); + let w_x = Witness(1); + let w_y = Witness(2); + let w_x_inv = Witness(3); + let w_y_inv = Witness(4); - let foreign_call: UnresolvedBrilligCall = unresolved_brillig_calls.remove(0); + let brillig_data = Brillig { + inputs: vec![ + BrilligInputs::Single(w_x.into()), // Input Register 0 + BrilligInputs::Single(Expression::default()), // Input Register 1 + BrilligInputs::Single(w_y.into()), // Input Register 2, + ], + outputs: vec![ + BrilligOutputs::Simple(w_x), // Output Register 0 - from input + BrilligOutputs::Simple(w_y_inv), // Output Register 1 + BrilligOutputs::Simple(w_y), // Output Register 2 - from input + BrilligOutputs::Simple(w_y_inv), // Output Register 3 + ], + // stack of foreign call/oracle resolutions, starts empty + foreign_call_results: vec![], + bytecode: vec![ + // Oracles are named 'foreign calls' in brillig + brillig_vm::Opcode::ForeignCall { + function: "invert".into(), + destinations: vec![RegisterOrMemory::RegisterIndex(RegisterIndex::from(1))], + inputs: vec![RegisterOrMemory::RegisterIndex(RegisterIndex::from(0))], + }, + brillig_vm::Opcode::ForeignCall { + function: "invert".into(), + destinations: vec![RegisterOrMemory::RegisterIndex(RegisterIndex::from(3))], + inputs: vec![RegisterOrMemory::RegisterIndex(RegisterIndex::from(2))], + }, + ], + predicate: None, + }; + + // This equality check can be executed immediately before resolving any foreign calls. + let equality_check = Expression { + mul_terms: vec![], + linear_combinations: vec![(-fe_1, w_x), (fe_1, w_y)], + q_c: fe_0, + }; + + // This equality check relies on the outputs of the Brillig call. + // It then cannot be solved until the foreign calls are resolved. + let inverse_equality_check = Expression { + mul_terms: vec![], + linear_combinations: vec![(-fe_1, w_x_inv), (fe_1, w_y_inv)], + q_c: fe_0, + }; + + let opcodes = vec![ + Opcode::Arithmetic(equality_check), + Opcode::Brillig(brillig_data), + Opcode::Arithmetic(inverse_equality_check.clone()), + ]; + + let witness_assignments = + BTreeMap::from([(w_x, FieldElement::from(2u128)), (w_y, FieldElement::from(2u128))]).into(); + + let mut acvm = ACVM::new(StubbedPwg, opcodes, witness_assignments); + + // use the partial witness generation solver with our acir program + let solver_status = acvm.solve().expect("should stall on oracle"); + assert_eq!( + solver_status, + PartialWitnessGeneratorStatus::RequiresForeignCall, + "Should require oracle data" + ); + assert_eq!(acvm.unresolved_opcodes().len(), 1, "brillig should have been removed"); + assert_eq!( + acvm.unresolved_opcodes()[0], + Opcode::Arithmetic(inverse_equality_check.clone()), + "Equality check of inverses should still be waiting to be resolved" + ); + + let foreign_call_wait_info: &ForeignCallWaitInfo = + acvm.get_pending_foreign_call().expect("should have a brillig foreign call request"); + assert_eq!(foreign_call_wait_info.inputs.len(), 1, "Should be waiting for a single input"); - let foreign_call_wait_info: &ForeignCallWaitInfo = &foreign_call.foreign_call_wait_info; + // Resolve Brillig foreign call + let x_inverse = Value::from(foreign_call_wait_info.inputs[0][0].to_field().inverse()); + acvm.resolve_pending_foreign_call(x_inverse.into()); + + // After filling data request, continue solving + let solver_status = acvm.solve().expect("should stall on oracle"); + assert_eq!( + solver_status, + PartialWitnessGeneratorStatus::RequiresForeignCall, + "Should require oracle data" + ); + assert_eq!(acvm.unresolved_opcodes().len(), 1, "brillig should have been removed"); + assert_eq!( + acvm.unresolved_opcodes()[0], + Opcode::Arithmetic(inverse_equality_check), + "Equality check of inverses should still be waiting to be resolved" + ); + + let foreign_call_wait_info: &ForeignCallWaitInfo = + acvm.get_pending_foreign_call().expect("should have a brillig foreign call request"); assert_eq!(foreign_call_wait_info.inputs.len(), 1, "Should be waiting for a single input"); - let i_plus_j_inverse = foreign_call_wait_info.inputs[0][0].to_field().inverse(); - assert_ne!(x_plus_y_inverse, i_plus_j_inverse); - // Alter Brillig oracle opcode - let brillig = foreign_call.resolve(Value::from(i_plus_j_inverse).into()); + // Resolve Brillig foreign call + let y_inverse = Value::from(foreign_call_wait_info.inputs[0][0].to_field().inverse()); + acvm.resolve_pending_foreign_call(y_inverse.into()); - let mut next_opcodes_for_solving = vec![Opcode::Brillig(brillig)]; - next_opcodes_for_solving.extend_from_slice(&unsolved_opcodes[..]); + // We've resolved all the brillig foreign calls so we should be able to complete execution now. // After filling data request, continue solving - let solver_status = - pwg::solve(&backend, &mut witness_assignments, &mut blocks, next_opcodes_for_solving) - .expect("should not stall on oracle"); + let solver_status = acvm.solve().expect("should not stall on brillig call"); assert_eq!(solver_status, PartialWitnessGeneratorStatus::Solved, "should be fully solved"); } @@ -395,15 +510,13 @@ fn brillig_oracle_predicate() { }), ]; - let backend = StubbedPwg; - - let mut witness_assignments = BTreeMap::from([ + let witness_assignments = BTreeMap::from([ (Witness(1), FieldElement::from(2u128)), (Witness(2), FieldElement::from(3u128)), ]) .into(); - let mut blocks = Blocks::default(); - let solver_status = pwg::solve(&backend, &mut witness_assignments, &mut blocks, opcodes) - .expect("should not stall on oracle"); + + let mut acvm = ACVM::new(StubbedPwg, opcodes, witness_assignments); + let solver_status = acvm.solve().expect("should not stall on brillig call"); assert_eq!(solver_status, PartialWitnessGeneratorStatus::Solved, "should be fully solved"); }