Skip to content

Commit ebaff44

Browse files
aakoshhTomAFrench
andauthored
chore(cli): Forward nargo execute to noir_artifact_cli (#7406)
Co-authored-by: Tom French <tom@tomfren.ch>
1 parent fdd2fe7 commit ebaff44

File tree

18 files changed

+271
-304
lines changed

18 files changed

+271
-304
lines changed

acvm-repo/acir/src/native_types/witness_stack.rs

+16-4
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ use super::WitnessMap;
1212
enum SerializationError {
1313
#[error(transparent)]
1414
Deflate(#[from] std::io::Error),
15+
16+
#[error(transparent)]
17+
BincodeError(#[from] bincode::Error),
1518
}
1619

1720
#[derive(Debug, Error)]
@@ -57,26 +60,35 @@ impl<F> From<WitnessMap<F>> for WitnessStack<F> {
5760
}
5861
}
5962

60-
impl<F: Serialize> TryFrom<WitnessStack<F>> for Vec<u8> {
63+
impl<F: Serialize> TryFrom<&WitnessStack<F>> for Vec<u8> {
6164
type Error = WitnessStackError;
6265

63-
fn try_from(val: WitnessStack<F>) -> Result<Self, Self::Error> {
64-
let buf = bincode::serialize(&val).unwrap();
66+
fn try_from(val: &WitnessStack<F>) -> Result<Self, Self::Error> {
67+
let buf = bincode::serialize(val).map_err(|e| WitnessStackError(e.into()))?;
6568
let mut deflater = GzEncoder::new(buf.as_slice(), Compression::best());
6669
let mut buf_c = Vec::new();
6770
deflater.read_to_end(&mut buf_c).map_err(|err| WitnessStackError(err.into()))?;
6871
Ok(buf_c)
6972
}
7073
}
7174

75+
impl<F: Serialize> TryFrom<WitnessStack<F>> for Vec<u8> {
76+
type Error = WitnessStackError;
77+
78+
fn try_from(val: WitnessStack<F>) -> Result<Self, Self::Error> {
79+
Self::try_from(&val)
80+
}
81+
}
82+
7283
impl<F: for<'a> Deserialize<'a>> TryFrom<&[u8]> for WitnessStack<F> {
7384
type Error = WitnessStackError;
7485

7586
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
7687
let mut deflater = GzDecoder::new(bytes);
7788
let mut buf_d = Vec::new();
7889
deflater.read_to_end(&mut buf_d).map_err(|err| WitnessStackError(err.into()))?;
79-
let witness_stack = bincode::deserialize(&buf_d).unwrap();
90+
let witness_stack =
91+
bincode::deserialize(&buf_d).map_err(|e| WitnessStackError(e.into()))?;
8092
Ok(witness_stack)
8193
}
8294
}

compiler/wasm/src/compile.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ pub(crate) struct DependencyGraph {
130130
pub(crate) root_dependencies: Vec<CrateName>,
131131
pub(crate) library_dependencies: BTreeMap<CrateName, Vec<CrateName>>,
132132
}
133-
/// This is map contains the paths of all of the files in the entry-point crate and
133+
/// This map contains the paths of all of the files in the entry-point crate and
134134
/// the transitive dependencies of the entry-point crate.
135135
///
136136
/// This is for all intents and purposes the file system that the compiler will use to resolve/compile

tooling/acvm_cli/src/cli/execute_cmd.rs

+4-3
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use bn254_blackbox_solver::Bn254BlackBoxSolver;
88
use clap::Args;
99
use nargo::PrintOutput;
1010

11-
use nargo::{foreign_calls::DefaultForeignCallBuilder, ops::execute_program};
11+
use nargo::foreign_calls::DefaultForeignCallBuilder;
1212
use noir_artifact_cli::errors::CliError;
1313
use noir_artifact_cli::fs::artifact::read_bytecode_from_file;
1414
use noir_artifact_cli::fs::witness::save_witness_to_dir;
@@ -56,7 +56,7 @@ fn run_command(args: ExecuteCommand) -> Result<String, CliError> {
5656
)?;
5757
if args.output_witness.is_some() {
5858
save_witness_to_dir(
59-
output_witness,
59+
&output_witness,
6060
&args.output_witness.unwrap(),
6161
&args.working_directory,
6262
)?;
@@ -80,7 +80,8 @@ pub(crate) fn execute_program_from_witness(
8080
) -> Result<WitnessStack<FieldElement>, CliError> {
8181
let program: Program<FieldElement> =
8282
Program::deserialize_program(bytecode).map_err(CliError::CircuitDeserializationError)?;
83-
execute_program(
83+
84+
nargo::ops::execute_program(
8485
&program,
8586
inputs_map,
8687
&Bn254BlackBoxSolver(pedantic_solving),
+39-137
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,15 @@
1-
use std::{collections::BTreeMap, path::PathBuf};
1+
use std::path::PathBuf;
22

3-
use acir::{FieldElement, circuit::Program, native_types::WitnessStack};
43
use bn254_blackbox_solver::Bn254BlackBoxSolver;
54
use clap::Args;
6-
use color_eyre::eyre::{self, bail};
75

86
use crate::{
97
Artifact,
108
errors::CliError,
11-
fs::{inputs::read_inputs_from_file, witness::save_witness_to_dir},
9+
execution::{self, ExecutionResults},
1210
};
13-
use nargo::{NargoError, PrintOutput, foreign_calls::DefaultForeignCallBuilder};
14-
use noirc_abi::{Abi, input_parser::InputValue};
15-
use noirc_artifacts::debug::DebugArtifact;
11+
use nargo::{PrintOutput, foreign_calls::DefaultForeignCallBuilder};
12+
use noirc_driver::CompiledProgram;
1613

1714
use super::parse_and_normalize_path;
1815

@@ -21,106 +18,84 @@ use super::parse_and_normalize_path;
2118
pub struct ExecuteCommand {
2219
/// Path to the JSON build artifact (either a program or a contract).
2320
#[clap(long, short, value_parser = parse_and_normalize_path)]
24-
artifact_path: PathBuf,
21+
pub artifact_path: PathBuf,
2522

2623
/// Path to the Prover.toml file which contains the inputs and the
2724
/// optional return value in ABI format.
2825
#[clap(long, short, value_parser = parse_and_normalize_path)]
29-
prover_file: PathBuf,
26+
pub prover_file: PathBuf,
3027

3128
/// Path to the directory where the output witness should be saved.
3229
/// If empty then the results are discarded.
3330
#[clap(long, short, value_parser = parse_and_normalize_path)]
34-
output_dir: Option<PathBuf>,
31+
pub output_dir: Option<PathBuf>,
3532

3633
/// Write the execution witness to named file
3734
///
3835
/// Defaults to the name of the circuit being executed.
3936
#[clap(long, short)]
40-
witness_name: Option<String>,
37+
pub witness_name: Option<String>,
4138

4239
/// Name of the function to execute, if the artifact is a contract.
4340
#[clap(long)]
44-
contract_fn: Option<String>,
41+
pub contract_fn: Option<String>,
4542

4643
/// JSON RPC url to solve oracle calls.
4744
#[clap(long)]
48-
oracle_resolver: Option<String>,
45+
pub oracle_resolver: Option<String>,
4946

5047
/// Use pedantic ACVM solving, i.e. double-check some black-box function assumptions when solving.
5148
#[clap(long, default_value_t = false)]
52-
pedantic_solving: bool,
49+
pub pedantic_solving: bool,
5350
}
5451

55-
pub fn run(args: ExecuteCommand) -> eyre::Result<()> {
52+
pub fn run(args: ExecuteCommand) -> Result<(), CliError> {
5653
let artifact = Artifact::read_from_file(&args.artifact_path)?;
54+
let artifact_name = args.artifact_path.file_stem().and_then(|s| s.to_str()).unwrap_or_default();
5755

58-
let circuit = match artifact {
59-
Artifact::Program(program) => Circuit {
60-
name: None,
61-
abi: program.abi,
62-
bytecode: program.bytecode,
63-
debug_symbols: program.debug_symbols,
64-
file_map: program.file_map,
65-
},
56+
let (circuit, circuit_name): (CompiledProgram, String) = match artifact {
57+
Artifact::Program(program) => (program.into(), artifact_name.to_string()),
6658
Artifact::Contract(contract) => {
67-
let names =
68-
contract.functions.iter().map(|f| f.name.clone()).collect::<Vec<_>>().join(",");
59+
let names = || contract.functions.iter().map(|f| f.name.clone()).collect::<Vec<_>>();
6960

7061
let Some(ref name) = args.contract_fn else {
71-
bail!("--contract-fn missing; options: [{names}]");
62+
return Err(CliError::MissingContractFn { names: names() });
7263
};
73-
let Some(function) = contract.functions.into_iter().find(|f| f.name == *name) else {
74-
bail!("unknown --contract-fn '{name}'; options: [{names}]");
64+
let Some(program) = contract.function_as_compiled_program(name) else {
65+
return Err(CliError::UnknownContractFn { name: name.clone(), names: names() });
7566
};
7667

77-
Circuit {
78-
name: Some(name.clone()),
79-
abi: function.abi,
80-
bytecode: function.bytecode,
81-
debug_symbols: function.debug_symbols,
82-
file_map: contract.file_map,
83-
}
68+
(program, format!("{artifact_name}::{name}"))
8469
}
8570
};
8671

8772
match execute(&circuit, &args) {
88-
Ok(solved) => {
89-
save_witness(circuit, args, solved)?;
90-
}
91-
Err(CliError::CircuitExecutionError(err)) => {
92-
show_diagnostic(circuit, err);
73+
Ok(results) => {
74+
execution::save_and_check_witness(
75+
&circuit,
76+
results,
77+
&circuit_name,
78+
args.output_dir.as_deref(),
79+
args.witness_name.as_deref(),
80+
)?;
9381
}
9482
Err(e) => {
95-
bail!("failed to execute the circuit: {e}");
83+
if let CliError::CircuitExecutionError(ref err) = e {
84+
execution::show_diagnostic(&circuit, err);
85+
}
86+
// Still returning the error to facilitate command forwarding, to indicate that the command failed.
87+
return Err(e);
9688
}
9789
}
9890
Ok(())
9991
}
10092

101-
/// Parameters necessary to execute a circuit, display execution failures, etc.
102-
struct Circuit {
103-
name: Option<String>,
104-
abi: Abi,
105-
bytecode: Program<FieldElement>,
106-
debug_symbols: noirc_errors::debug_info::ProgramDebugInfo,
107-
file_map: BTreeMap<fm::FileId, noirc_driver::DebugFile>,
108-
}
109-
110-
struct SolvedWitnesses {
111-
expected_return: Option<InputValue>,
112-
actual_return: Option<InputValue>,
113-
witness_stack: WitnessStack<FieldElement>,
114-
}
115-
11693
/// Execute a circuit and return the output witnesses.
117-
fn execute(circuit: &Circuit, args: &ExecuteCommand) -> Result<SolvedWitnesses, CliError> {
118-
let (input_map, expected_return) = read_inputs_from_file(&args.prover_file, &circuit.abi)?;
119-
120-
let initial_witness = circuit.abi.encode(&input_map, None)?;
121-
94+
fn execute(circuit: &CompiledProgram, args: &ExecuteCommand) -> Result<ExecutionResults, CliError> {
12295
// TODO: Build a custom foreign call executor that reads from the Oracle transcript,
123-
// and use it as a base for the default executor; see `DefaultForeignCallBuilder::build_with_base`
96+
// and use it as a base for the default executor. Using it as the innermost rather
97+
// than top layer so that any extra `print` added for debugging is handled by the
98+
// default, rather than trying to match it to the transcript.
12499
let mut foreign_call_executor = DefaultForeignCallBuilder {
125100
output: PrintOutput::Stdout,
126101
enable_mocks: false,
@@ -130,80 +105,7 @@ fn execute(circuit: &Circuit, args: &ExecuteCommand) -> Result<SolvedWitnesses,
130105
}
131106
.build();
132107

133-
let witness_stack = nargo::ops::execute_program(
134-
&circuit.bytecode,
135-
initial_witness,
136-
&Bn254BlackBoxSolver(args.pedantic_solving),
137-
&mut foreign_call_executor,
138-
)?;
139-
140-
let main_witness =
141-
&witness_stack.peek().expect("Should have at least one witness on the stack").witness;
142-
143-
let (_, actual_return) = circuit.abi.decode(main_witness)?;
108+
let blackbox_solver = Bn254BlackBoxSolver(args.pedantic_solving);
144109

145-
Ok(SolvedWitnesses { expected_return, actual_return, witness_stack })
146-
}
147-
148-
/// Print an error stack trace, if possible.
149-
fn show_diagnostic(circuit: Circuit, err: NargoError<FieldElement>) {
150-
if let Some(diagnostic) = nargo::errors::try_to_diagnose_runtime_error(
151-
&err,
152-
&circuit.abi,
153-
&circuit.debug_symbols.debug_infos,
154-
) {
155-
let debug_artifact = DebugArtifact {
156-
debug_symbols: circuit.debug_symbols.debug_infos,
157-
file_map: circuit.file_map,
158-
};
159-
diagnostic.report(&debug_artifact, false);
160-
}
161-
}
162-
163-
/// Print information about the witness and compare to expectations,
164-
/// returning errors if something isn't right.
165-
fn save_witness(
166-
circuit: Circuit,
167-
args: ExecuteCommand,
168-
solved: SolvedWitnesses,
169-
) -> eyre::Result<()> {
170-
let artifact = args.artifact_path.file_stem().and_then(|s| s.to_str()).unwrap_or_default();
171-
let name = circuit
172-
.name
173-
.as_ref()
174-
.map(|name| format!("{artifact}.{name}"))
175-
.unwrap_or_else(|| artifact.to_string());
176-
177-
println!("[{}] Circuit witness successfully solved", name);
178-
179-
if let Some(ref witness_dir) = args.output_dir {
180-
let witness_path = save_witness_to_dir(
181-
solved.witness_stack,
182-
&args.witness_name.unwrap_or_else(|| name.clone()),
183-
witness_dir,
184-
)?;
185-
println!("[{}] Witness saved to {}", name, witness_path.display());
186-
}
187-
188-
// Check that the circuit returned a non-empty result if the ABI expects a return value.
189-
if let Some(ref expected) = circuit.abi.return_type {
190-
if solved.actual_return.is_none() {
191-
bail!("Missing return witness; expected a value of type {expected:?}");
192-
}
193-
}
194-
195-
// Check that if the prover file contained a `return` entry then that's what we got.
196-
if let Some(expected) = solved.expected_return {
197-
match solved.actual_return {
198-
None => {
199-
bail!("Missing return witness;\nexpected:\n{expected:?}");
200-
}
201-
Some(actual) if actual != expected => {
202-
bail!("Unexpected return witness;\nexpected:\n{expected:?}\ngot:\n{actual:?}");
203-
}
204-
_ => {}
205-
}
206-
}
207-
208-
Ok(())
110+
execution::execute(circuit, &blackbox_solver, &mut foreign_call_executor, &args.prover_file)
209111
}

tooling/artifact_cli/src/commands/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
//! This module is for commands that we might want to invoke from `nargo` as-is.
12
use std::path::PathBuf;
23

34
use color_eyre::eyre;

tooling/artifact_cli/src/errors.rs

+17-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
use acir::FieldElement;
22
use nargo::NargoError;
3-
use noirc_abi::errors::{AbiError, InputParserError};
3+
use noirc_abi::{
4+
AbiReturnType,
5+
errors::{AbiError, InputParserError},
6+
input_parser::InputValue,
7+
};
48
use std::path::PathBuf;
59
use thiserror::Error;
610

@@ -61,4 +65,16 @@ pub enum CliError {
6165

6266
#[error("Failed to serialize output witness: {0}")]
6367
OutputWitnessSerializationFailed(#[from] toml::ser::Error),
68+
69+
#[error("Unexpected return value: expected {expected:?}; got {actual:?}")]
70+
UnexpectedReturn { expected: InputValue, actual: Option<InputValue> },
71+
72+
#[error("Missing return witnesses; expected {expected:?}")]
73+
MissingReturn { expected: AbiReturnType },
74+
75+
#[error("Missing contract function name; options: {names:?}")]
76+
MissingContractFn { names: Vec<String> },
77+
78+
#[error("Unknown contract function '{name}'; options: {names:?}")]
79+
UnknownContractFn { name: String, names: Vec<String> },
6480
}

0 commit comments

Comments
 (0)