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

Commit

Permalink
feat(acvm)!: replace PartialWitnessGeneratorStatus with ACVMStatus (
Browse files Browse the repository at this point in the history
  • Loading branch information
TomAFrench authored Jul 5, 2023
1 parent f1c7940 commit fc3240d
Show file tree
Hide file tree
Showing 2 changed files with 71 additions and 35 deletions.
46 changes: 35 additions & 11 deletions acvm/src/pwg/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,18 @@ mod block;

pub use brillig::ForeignCallWaitInfo;

#[derive(Debug, PartialEq)]
pub enum PartialWitnessGeneratorStatus {
#[derive(Debug, Clone, PartialEq)]
pub enum ACVMStatus {
/// All opcodes have been solved.
Solved,

/// The ACVM is in the process of executing the circuit.
InProgress,

/// The ACVM has encountered an irrecoverable error while executing the circuit and can not progress.
/// Most commonly this will be due to an unsatisfied constraint due to invalid inputs to the circuit.
Failure(OpcodeResolutionError),

/// 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`].
Expand Down Expand Up @@ -62,15 +69,15 @@ pub enum OpcodeResolution {
// TODO: ExpressionHasTooManyUnknowns is specific for arithmetic expressions
// TODO: we could have a error enum for arithmetic failure cases in that module
// TODO that can be converted into an OpcodeNotSolvable or OpcodeResolutionError enum
#[derive(PartialEq, Eq, Debug, Error)]
#[derive(Clone, PartialEq, Eq, Debug, Error)]
pub enum OpcodeNotSolvable {
#[error("missing assignment for witness index {0}")]
MissingAssignment(u32),
#[error("expression has too many unknowns {0}")]
ExpressionHasTooManyUnknowns(Expression),
}

#[derive(PartialEq, Eq, Debug, Error)]
#[derive(Clone, PartialEq, Eq, Debug, Error)]
pub enum OpcodeResolutionError {
#[error("cannot solve opcode: {0}")]
OpcodeNotSolvable(#[from] OpcodeNotSolvable),
Expand All @@ -85,6 +92,8 @@ pub enum OpcodeResolutionError {
}

pub struct ACVM<B: BlackBoxFunctionSolver> {
status: ACVMStatus,

backend: B,
/// Stores the solver for each [block][`Opcode::Block`] opcode. This persists their internal state to prevent recomputation.
block_solvers: HashMap<BlockId, BlockSolver>,
Expand All @@ -102,6 +111,7 @@ pub struct ACVM<B: BlackBoxFunctionSolver> {
impl<B: BlackBoxFunctionSolver> ACVM<B> {
pub fn new(backend: B, opcodes: Vec<Opcode>, initial_witness: WitnessMap) -> Self {
ACVM {
status: ACVMStatus::InProgress,
backend,
block_solvers: HashMap::default(),
opcodes,
Expand All @@ -124,9 +134,22 @@ impl<B: BlackBoxFunctionSolver> ACVM<B> {
&self.opcodes
}

/// Updates the current status of the VM.
/// Returns the given status.
fn status(&mut self, status: ACVMStatus) -> ACVMStatus {
self.status = status.clone();
status
}

/// Sets the VM status to [ACVMStatus::Failure] using the provided `error`.
/// Returns the new status.
fn fail(&mut self, error: OpcodeResolutionError) -> ACVMStatus {
self.status(ACVMStatus::Failure(error))
}

/// 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() {
if self.status != ACVMStatus::Solved {
panic!("ACVM is not ready to be finalized");
}
self.witness_map
Expand All @@ -153,7 +176,7 @@ impl<B: BlackBoxFunctionSolver> ACVM<B> {
/// 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<PartialWitnessGeneratorStatus, OpcodeResolutionError> {
pub fn solve(&mut self) -> ACVMStatus {
// TODO: Prevent execution with outstanding foreign calls?
let mut unresolved_opcodes: Vec<Opcode> = Vec::new();
while !self.opcodes.is_empty() {
Expand Down Expand Up @@ -212,7 +235,7 @@ impl<B: BlackBoxFunctionSolver> ACVM<B> {
Err(OpcodeResolutionError::OpcodeNotSolvable(_)) => {
unreachable!("ICE - Result should have been converted to GateResolution")
}
Err(err) => return Err(err),
Err(error) => return self.fail(error),
}
}

Expand All @@ -221,18 +244,19 @@ impl<B: BlackBoxFunctionSolver> ACVM<B> {

// We have oracles that must be externally resolved
if self.get_pending_foreign_call().is_some() {
return Ok(PartialWitnessGeneratorStatus::RequiresForeignCall);
return self.status(ACVMStatus::RequiresForeignCall);
}

// We are stalled because of an opcode being bad
if stalled && !self.opcodes.is_empty() {
return Err(OpcodeResolutionError::OpcodeNotSolvable(
let error = OpcodeResolutionError::OpcodeNotSolvable(
opcode_not_solvable
.expect("infallible: cannot be stalled and None at the same time"),
));
);
return self.fail(error);
}
}
Ok(PartialWitnessGeneratorStatus::Solved)
self.status(ACVMStatus::Solved)
}
}

Expand Down
60 changes: 36 additions & 24 deletions acvm/tests/solver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use acir::{
};

use acvm::{
pwg::{ForeignCallWaitInfo, OpcodeResolutionError, PartialWitnessGeneratorStatus, ACVM},
pwg::{ACVMStatus, ForeignCallWaitInfo, OpcodeResolutionError, ACVM},
BlackBoxFunctionSolver,
};

Expand Down Expand Up @@ -127,12 +127,12 @@ fn inversion_brillig_oracle_equivalence() {

let mut acvm = ACVM::new(StubbedBackend, 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 solver_status = acvm.solve();

assert_eq!(
solver_status,
PartialWitnessGeneratorStatus::RequiresForeignCall,
"Should require oracle data"
ACVMStatus::RequiresForeignCall,
"should require foreign call response"
);
assert!(acvm.unresolved_opcodes().is_empty(), "brillig should have been removed");

Expand All @@ -146,8 +146,11 @@ fn inversion_brillig_oracle_equivalence() {
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");
assert_eq!(solver_status, PartialWitnessGeneratorStatus::Solved, "should be fully solved");
let solver_status = acvm.solve();
assert_eq!(solver_status, ACVMStatus::Solved, "should be fully solved");

// ACVM should be able to be finalized in `Solved` state.
acvm.finalize();
}

#[test]
Expand Down Expand Up @@ -255,11 +258,11 @@ fn double_inversion_brillig_oracle() {
let mut acvm = ACVM::new(StubbedBackend, opcodes, witness_assignments);

// use the partial witness generation solver with our acir program
let solver_status = acvm.solve().expect("should stall on oracle");
let solver_status = acvm.solve();
assert_eq!(
solver_status,
PartialWitnessGeneratorStatus::RequiresForeignCall,
"Should require oracle data"
ACVMStatus::RequiresForeignCall,
"should require foreign call response"
);
assert!(acvm.unresolved_opcodes().is_empty(), "brillig should have been removed");

Expand All @@ -273,11 +276,11 @@ fn double_inversion_brillig_oracle() {
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 solver_status = acvm.solve();
assert_eq!(
solver_status,
PartialWitnessGeneratorStatus::RequiresForeignCall,
"Should require oracle data"
ACVMStatus::RequiresForeignCall,
"should require foreign call response"
);
assert!(acvm.unresolved_opcodes().is_empty(), "should be fully solved");

Expand All @@ -292,8 +295,11 @@ fn double_inversion_brillig_oracle() {
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");
assert_eq!(solver_status, PartialWitnessGeneratorStatus::Solved, "should be fully solved");
let solver_status = acvm.solve();
assert_eq!(solver_status, ACVMStatus::Solved, "should be fully solved");

// ACVM should be able to be finalized in `Solved` state.
acvm.finalize();
}

#[test]
Expand Down Expand Up @@ -375,11 +381,11 @@ fn oracle_dependent_execution() {
let mut acvm = ACVM::new(StubbedBackend, opcodes, witness_assignments);

// use the partial witness generation solver with our acir program
let solver_status = acvm.solve().expect("should stall on oracle");
let solver_status = acvm.solve();
assert_eq!(
solver_status,
PartialWitnessGeneratorStatus::RequiresForeignCall,
"Should require oracle data"
ACVMStatus::RequiresForeignCall,
"should require foreign call response"
);
assert_eq!(acvm.unresolved_opcodes().len(), 1, "brillig should have been removed");
assert_eq!(
Expand All @@ -397,11 +403,11 @@ fn oracle_dependent_execution() {
acvm.resolve_pending_foreign_call(x_inverse.into());

// After filling data request, continue solving
let solver_status = acvm.solve().expect("should stall on oracle");
let solver_status = acvm.solve();
assert_eq!(
solver_status,
PartialWitnessGeneratorStatus::RequiresForeignCall,
"Should require oracle data"
ACVMStatus::RequiresForeignCall,
"should require foreign call response"
);
assert_eq!(acvm.unresolved_opcodes().len(), 1, "brillig should have been removed");
assert_eq!(
Expand All @@ -421,8 +427,11 @@ fn oracle_dependent_execution() {
// 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 = acvm.solve().expect("should not stall on brillig call");
assert_eq!(solver_status, PartialWitnessGeneratorStatus::Solved, "should be fully solved");
let solver_status = acvm.solve();
assert_eq!(solver_status, ACVMStatus::Solved, "should be fully solved");

// ACVM should be able to be finalized in `Solved` state.
acvm.finalize();
}

#[test]
Expand Down Expand Up @@ -504,6 +513,9 @@ fn brillig_oracle_predicate() {
.into();

let mut acvm = ACVM::new(StubbedBackend, 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");
let solver_status = acvm.solve();
assert_eq!(solver_status, ACVMStatus::Solved, "should be fully solved");

// ACVM should be able to be finalized in `Solved` state.
acvm.finalize();
}

0 comments on commit fc3240d

Please sign in to comment.