Skip to content
This repository has been archived by the owner on Apr 9, 2024. It is now read-only.

Commit

Permalink
feat: track pending foreign calls inside ACVM struct
Browse files Browse the repository at this point in the history
  • Loading branch information
TomAFrench committed Jun 20, 2023
1 parent cbc8fc0 commit 5a809ec
Show file tree
Hide file tree
Showing 2 changed files with 58 additions and 38 deletions.
36 changes: 29 additions & 7 deletions acvm/src/pwg/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ pub enum PartialWitnessGeneratorStatus {
/// The result of the foreign call is inserted into the `Brillig` opcode which made the call using [`UnresolvedBrilligCall::resolve`].
///
/// Once this is done, the `PartialWitnessGenerator` can be restarted to solve the remaining opcodes.
RequiresForeignCall { unresolved_brillig_calls: Vec<UnresolvedBrilligCall> },
RequiresForeignCall,
}

#[derive(Debug, PartialEq)]
Expand Down Expand Up @@ -86,27 +86,51 @@ pub enum OpcodeResolutionError {
pub struct ACVM<B: PartialWitnessGenerator> {
backend: B,
blocks: Blocks,
/// 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<Opcode>,

witness_map: WitnessMap,

/// A list of foreign calls which must be resolved before the ACVM can resume execution.
pending_foreign_calls: Vec<UnresolvedBrilligCall>,
}

impl<B: PartialWitnessGenerator> ACVM<B> {
pub fn new(backend: B, opcodes: Vec<Opcode>, initial_witness: WitnessMap) -> Self {
ACVM { backend, blocks: Blocks::default(), opcodes, witness_map: initial_witness }
ACVM {
backend,
blocks: Blocks::default(),
opcodes,
witness_map: initial_witness,
pending_foreign_calls: Vec::new(),
}
}

/// Finalize the ACVM execution, returning the resulting [`WitnessMap`].
pub fn finalize(self) -> WitnessMap {
self.witness_map
}

pub fn resolve_brillig_foreign_call(&mut self, foreign_call: Brillig) {
self.opcodes.insert(0, Opcode::Brillig(foreign_call));
/// 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 a [`Circuit`] against an [initial witness][`WitnessMap`] to calculate the solved partial witness.
pub fn solve(&mut self) -> Result<PartialWitnessGeneratorStatus, OpcodeResolutionError> {
// TODO: Prevent execution with outstanding foreign calls?
let mut unresolved_opcodes: Vec<Opcode> = Vec::new();
let mut unresolved_brillig_calls: Vec<UnresolvedBrilligCall> = Vec::new();
while !self.opcodes.is_empty() {
Expand Down Expand Up @@ -172,9 +196,7 @@ impl<B: PartialWitnessGenerator> ACVM<B> {

// We have oracles that must be externally resolved
if !unresolved_brillig_calls.is_empty() {
return Ok(PartialWitnessGeneratorStatus::RequiresForeignCall {
unresolved_brillig_calls,
});
return Ok(PartialWitnessGeneratorStatus::RequiresForeignCall);
}
// We are stalled because of an opcode being bad
if stalled && !self.opcodes.is_empty() {
Expand Down
60 changes: 29 additions & 31 deletions acvm/tests/solver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use acir::{
use acvm::{
pwg::{
ForeignCallWaitInfo, OpcodeResolution, OpcodeResolutionError,
PartialWitnessGeneratorStatus, UnresolvedBrilligCall, ACVM,
PartialWitnessGeneratorStatus, ACVM,
},
PartialWitnessGenerator,
};
Expand Down Expand Up @@ -142,23 +142,22 @@ fn inversion_brillig_oracle_equivalence() {
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 brillig call");
let PartialWitnessGeneratorStatus::RequiresForeignCall { mut unresolved_brillig_calls, .. } = solver_status else {
panic!("Should require oracle data")
};

assert_eq!(
solver_status,
PartialWitnessGeneratorStatus::RequiresForeignCall,
"Should require oracle data"
);
assert_eq!(acvm.opcodes.len(), 0, "brillig should have been removed");
assert_eq!(unresolved_brillig_calls.len(), 1, "should have a brillig foreign call request");

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());
acvm.resolve_brillig_foreign_call(brillig);
acvm.resolve_pending_foreign_call(foreign_call_result.into());

// After filling data request, continue solving
let solver_status = acvm.solve().expect("should not stall on brillig call");
Expand Down Expand Up @@ -273,41 +272,40 @@ fn double_inversion_brillig_oracle() {

// use the partial witness generation solver with our acir program
let solver_status = acvm.solve().expect("should stall on oracle");
let PartialWitnessGeneratorStatus::RequiresForeignCall { mut unresolved_brillig_calls, .. } = solver_status else {
panic!("Should require foreign call response")
};

assert_eq!(
solver_status,
PartialWitnessGeneratorStatus::RequiresForeignCall,
"Should require oracle data"
);
assert_eq!(acvm.opcodes.len(), 0, "brillig should have been removed");
assert_eq!(unresolved_brillig_calls.len(), 1, "should have a brillig foreign call request");

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");

let x_plus_y_inverse = foreign_call.foreign_call_wait_info.inputs[0][0].to_field().inverse();
let x_plus_y_inverse = Value::from(foreign_call_wait_info.inputs[0][0].to_field().inverse());

// Resolve Brillig foreign call
let brillig: Brillig = foreign_call.resolve(Value::from(x_plus_y_inverse).into());
acvm.resolve_brillig_foreign_call(brillig);
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");
let PartialWitnessGeneratorStatus::RequiresForeignCall { mut unresolved_brillig_calls, .. } = solver_status else {
panic!("Should require foreign call data")
};

assert_eq!(
solver_status,
PartialWitnessGeneratorStatus::RequiresForeignCall,
"Should require oracle data"
);
assert!(acvm.opcodes.is_empty(), "should be fully solved");
assert_eq!(unresolved_brillig_calls.len(), 1, "should have an unresolved foreign call");
let foreign_call: UnresolvedBrilligCall = unresolved_brillig_calls.remove(0);

let foreign_call_wait_info: &ForeignCallWaitInfo = &foreign_call.foreign_call_wait_info;
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 i_plus_j_inverse = 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 = foreign_call.resolve(Value::from(i_plus_j_inverse).into());
acvm.resolve_brillig_foreign_call(brillig);
acvm.resolve_pending_foreign_call(i_plus_j_inverse.into());

// After filling data request, continue solving
let solver_status = acvm.solve().expect("should not stall on brillig call");
Expand Down

0 comments on commit 5a809ec

Please sign in to comment.