Skip to content

Commit

Permalink
feat(nargo): Mock oracle resolver
Browse files Browse the repository at this point in the history
Adds a `--mock-oracle-resolver` option to nargo, that responds to all oracle requests with a single zero-value field. Useful for allowing noop oracle calls, such as debug-log, when running circuits in aztec-packages, without breaking tests run via `nargo test`.

Co-authored-by: Nicolás Venturo <nicolas.venturo@gmail.com>
  • Loading branch information
spalladino and nventuro committed Apr 29, 2024
1 parent 9af68d8 commit c34f073
Show file tree
Hide file tree
Showing 12 changed files with 169 additions and 68 deletions.
6 changes: 3 additions & 3 deletions noir-projects/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ WORKDIR /usr/src/noir-projects
COPY . .
# Build
WORKDIR /usr/src/noir-projects/noir-contracts
RUN ./bootstrap.sh && nargo test --silence-warnings
RUN ./bootstrap.sh && nargo test --silence-warnings --mock-oracle-resolver
WORKDIR /usr/src/noir-projects/noir-protocol-circuits
RUN ./bootstrap.sh && nargo test --silence-warnings
RUN ./bootstrap.sh && nargo test --silence-warnings --mock-oracle-resolver
WORKDIR /usr/src/noir-projects/aztec-nr
RUN nargo test --silence-warnings
RUN nargo test --silence-warnings --mock-oracle-resolver

FROM scratch
COPY --from=builder /usr/src/noir-projects /usr/src/noir-projects
6 changes: 3 additions & 3 deletions noir-projects/Earthfile
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,6 @@ build:

test:
FROM +build
RUN cd noir-protocol-circuits && nargo test --silence-warnings
RUN cd aztec-nr && nargo test --silence-warnings
RUN cd noir-contracts && nargo test --silence-warnings
RUN cd noir-protocol-circuits && nargo test --silence-warnings --mock-oracle-resolver
RUN cd aztec-nr && nargo test --silence-warnings --mock-oracle-resolver
RUN cd noir-contracts && nargo test --silence-warnings --mock-oracle-resolver
9 changes: 5 additions & 4 deletions noir/noir-repo/tooling/acvm_cli/src/cli/execute_cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use clap::Args;

use crate::cli::fs::inputs::{read_bytecode_from_file, read_inputs_from_file};
use crate::errors::CliError;
use nargo::ops::{execute_program, DefaultForeignCallExecutor};
use nargo::ops::{execute_program, DefaultForeignCallExecutor, ResolverOpts};

use super::fs::witness::{create_output_witness_string, save_witness_to_dir};

Expand Down Expand Up @@ -38,7 +38,8 @@ pub(crate) struct ExecuteCommand {
fn run_command(args: ExecuteCommand) -> Result<String, CliError> {
let bytecode = read_bytecode_from_file(&args.working_directory, &args.bytecode)?;
let circuit_inputs = read_inputs_from_file(&args.working_directory, &args.input_witness)?;
let output_witness = execute_program_from_witness(circuit_inputs, &bytecode, None)?;
let output_witness =
execute_program_from_witness(circuit_inputs, &bytecode, &ResolverOpts::none())?;
assert_eq!(output_witness.length(), 1, "ACVM CLI only supports a witness stack of size 1");
let output_witness_string = create_output_witness_string(
&output_witness.peek().expect("Should have a witness stack item").witness,
Expand All @@ -65,7 +66,7 @@ pub(crate) fn run(args: ExecuteCommand) -> Result<String, CliError> {
pub(crate) fn execute_program_from_witness(
inputs_map: WitnessMap,
bytecode: &[u8],
foreign_call_resolver_url: Option<&str>,
resolver_opts: &ResolverOpts,
) -> Result<WitnessStack, CliError> {
let blackbox_solver = Bn254BlackBoxSolver::new();
let program: Program = Program::deserialize_program(bytecode)
Expand All @@ -74,7 +75,7 @@ pub(crate) fn execute_program_from_witness(
&program,
inputs_map,
&blackbox_solver,
&mut DefaultForeignCallExecutor::new(true, foreign_call_resolver_url),
&mut DefaultForeignCallExecutor::new(true, resolver_opts),
)
.map_err(CliError::CircuitExecutionError)
}
4 changes: 2 additions & 2 deletions noir/noir-repo/tooling/debugger/src/foreign_calls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use acvm::{
};
use nargo::{
artifacts::debug::{DebugArtifact, DebugVars, StackFrame},
ops::{DefaultForeignCallExecutor, ForeignCallExecutor},
ops::{DefaultForeignCallExecutor, ForeignCallExecutor, ResolverOpts},
};
use noirc_errors::debug_info::{DebugFnId, DebugVarId};
use noirc_printable_type::ForeignCallError;
Expand Down Expand Up @@ -51,7 +51,7 @@ pub struct DefaultDebugForeignCallExecutor {
impl DefaultDebugForeignCallExecutor {
pub fn new(show_output: bool) -> Self {
Self {
executor: DefaultForeignCallExecutor::new(show_output, None),
executor: DefaultForeignCallExecutor::new(show_output, &ResolverOpts::none()),
debug_vars: DebugVars::default(),
}
}
Expand Down
4 changes: 2 additions & 2 deletions noir/noir-repo/tooling/lsp/src/requests/test_run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::future::{self, Future};
use async_lsp::{ErrorCode, ResponseError};
use nargo::{
insert_all_files_for_workspace_into_file_manager,
ops::{run_test, TestStatus},
ops::{run_test, ResolverOpts, TestStatus},
prepare_package,
};
use nargo_toml::{find_package_manifest, resolve_workspace_from_toml, PackageSelection};
Expand Down Expand Up @@ -86,7 +86,7 @@ fn on_test_run_request_inner(
&mut context,
&test_function,
false,
None,
&ResolverOpts::none(),
&CompileOptions::default(),
);
let result = match test_result {
Expand Down
137 changes: 106 additions & 31 deletions noir/noir-repo/tooling/nargo/src/ops/foreign_calls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,51 @@ impl MockedCall {
}
}

trait ExternalResolver: std::fmt::Debug {
fn execute(&self, call: &ForeignCallWaitInfo) -> ForeignCallResult;
}

#[derive(Debug)]
struct JsonRpcExternalResolver {
client: Client,
}

impl JsonRpcExternalResolver {
fn new(resolver_url: &str) -> Self {
let mut transport_builder =
Builder::new().url(resolver_url).expect("Invalid oracle resolver URL");

if let Some(Ok(timeout)) =
std::env::var("NARGO_FOREIGN_CALL_TIMEOUT").ok().map(|timeout| timeout.parse())
{
let timeout_duration = std::time::Duration::from_millis(timeout);
transport_builder = transport_builder.timeout(timeout_duration);
};

let client = Client::with_transport(transport_builder.build());

JsonRpcExternalResolver { client }
}
}

impl ExternalResolver for JsonRpcExternalResolver {
fn execute(&self, call: &ForeignCallWaitInfo) -> ForeignCallResult {
let encoded_params: Vec<_> = call.inputs.iter().map(build_json_rpc_arg).collect();
let request = self.client.build_request(call.function.as_str(), &encoded_params);
let response = self.client.send_request(request).expect("Failed to send request");
response.result().expect("Failed to parse response")
}
}

#[derive(Debug)]
struct MockExternalResolver;

impl ExternalResolver for MockExternalResolver {
fn execute(&self, _call: &ForeignCallWaitInfo) -> ForeignCallResult {
FieldElement::from(0_usize).into()
}
}

#[derive(Debug, Default)]
pub struct DefaultForeignCallExecutor {
/// Mocks have unique ids used to identify them in Noir, allowing to update or remove them.
Expand All @@ -102,27 +147,54 @@ pub struct DefaultForeignCallExecutor {
mocked_responses: Vec<MockedCall>,
/// Whether to print [`ForeignCall::Print`] output.
show_output: bool,
/// JSON RPC client to resolve foreign calls
external_resolver: Option<Client>,
/// External resolver for foreign calls
external_resolver: Option<Box<dyn ExternalResolver>>,
}

#[derive(Debug)]
pub struct ResolverOpts {
pub resolver_url: Option<String>,
pub mock_resolver: bool,
}

impl ResolverOpts {
pub fn new(resolver_url: Option<&str>, mock_resolver: bool) -> Self {
Self {
resolver_url: resolver_url.map(|resolver_url| resolver_url.to_string()),
mock_resolver,
}
}

pub fn none() -> Self {
Self { resolver_url: None, mock_resolver: false }
}

pub fn rpc(resolver_url: &str) -> Self {
Self { resolver_url: Some(resolver_url.to_string()), mock_resolver: false }
}

pub fn mock() -> Self {
Self { resolver_url: None, mock_resolver: true }
}
}

impl DefaultForeignCallExecutor {
pub fn new(show_output: bool, resolver_url: Option<&str>) -> Self {
let oracle_resolver = resolver_url.map(|resolver_url| {
let mut transport_builder =
Builder::new().url(resolver_url).expect("Invalid oracle resolver URL");

if let Some(Ok(timeout)) =
std::env::var("NARGO_FOREIGN_CALL_TIMEOUT").ok().map(|timeout| timeout.parse())
{
let timeout_duration = std::time::Duration::from_millis(timeout);
transport_builder = transport_builder.timeout(timeout_duration);
pub fn new(show_output: bool, resolver: &ResolverOpts) -> Self {
let ResolverOpts { resolver_url, mock_resolver } = resolver;
let external_resolver: Option<Box<dyn ExternalResolver>> =
match (resolver_url, mock_resolver) {
(Some(_), true) => {
panic!("Cannot have both an external resolver and a mock resolver")
}
(Some(resolver_url), _) => {
Some(Box::new(JsonRpcExternalResolver::new(resolver_url.as_str())))
}
(None, true) => Some(Box::new(MockExternalResolver {})),
_ => None,
};
Client::with_transport(transport_builder.build())
});
DefaultForeignCallExecutor {
show_output,
external_resolver: oracle_resolver,
external_resolver,
..DefaultForeignCallExecutor::default()
}
}
Expand Down Expand Up @@ -269,19 +341,7 @@ impl ForeignCallExecutor for DefaultForeignCallExecutor {

Ok(result.into())
}
(None, Some(external_resolver)) => {
let encoded_params: Vec<_> =
foreign_call.inputs.iter().map(build_json_rpc_arg).collect();

let req =
external_resolver.build_request(foreign_call_name, &encoded_params);

let response = external_resolver.send_request(req)?;

let parsed_response: ForeignCallResult = response.result()?;

Ok(parsed_response)
}
(None, Some(external_resolver)) => Ok(external_resolver.execute(foreign_call)),
(None, None) => panic!(
"No mock for foreign call {}({:?})",
foreign_call_name, &foreign_call.inputs
Expand All @@ -302,7 +362,9 @@ mod tests {
use jsonrpc_derive::rpc;
use jsonrpc_http_server::{Server, ServerBuilder};

use crate::ops::{DefaultForeignCallExecutor, ForeignCallExecutor};
use crate::ops::{
foreign_calls::ResolverOpts, DefaultForeignCallExecutor, ForeignCallExecutor,
};

#[allow(unreachable_pub)]
#[rpc]
Expand Down Expand Up @@ -349,7 +411,7 @@ mod tests {
fn test_oracle_resolver_echo() {
let (server, url) = build_oracle_server();

let mut executor = DefaultForeignCallExecutor::new(false, Some(&url));
let mut executor = DefaultForeignCallExecutor::new(false, &ResolverOpts::rpc(&url));

let foreign_call = ForeignCallWaitInfo {
function: "echo".to_string(),
Expand All @@ -366,7 +428,7 @@ mod tests {
fn test_oracle_resolver_sum() {
let (server, url) = build_oracle_server();

let mut executor = DefaultForeignCallExecutor::new(false, Some(&url));
let mut executor = DefaultForeignCallExecutor::new(false, &ResolverOpts::rpc(&url));

let foreign_call = ForeignCallWaitInfo {
function: "sum".to_string(),
Expand All @@ -378,4 +440,17 @@ mod tests {

server.close();
}

#[test]
fn test_mock_resolver() {
let mut executor = DefaultForeignCallExecutor::new(false, &ResolverOpts::mock());

let foreign_call = ForeignCallWaitInfo {
function: "sum".to_string(),
inputs: vec![ForeignCallParam::Array(vec![1_usize.into(), 2_usize.into()])],
};

let result = executor.execute(&foreign_call);
assert_eq!(result.unwrap(), FieldElement::from(0_usize).into());
}
}
4 changes: 3 additions & 1 deletion noir/noir-repo/tooling/nargo/src/ops/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ pub use self::compile::{
compile_workspace, report_errors,
};
pub use self::execute::execute_program;
pub use self::foreign_calls::{DefaultForeignCallExecutor, ForeignCall, ForeignCallExecutor};
pub use self::foreign_calls::{
DefaultForeignCallExecutor, ForeignCall, ForeignCallExecutor, ResolverOpts,
};
pub use self::optimize::{optimize_contract, optimize_program};
pub use self::transform::{transform_contract, transform_program};

Expand Down
6 changes: 3 additions & 3 deletions noir/noir-repo/tooling/nargo/src/ops/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use noirc_frontend::hir::{def_map::TestFunction, Context};

use crate::{errors::try_to_diagnose_runtime_error, NargoError};

use super::{execute_program, DefaultForeignCallExecutor};
use super::{execute_program, DefaultForeignCallExecutor, ResolverOpts};

pub enum TestStatus {
Pass,
Expand All @@ -28,7 +28,7 @@ pub fn run_test<B: BlackBoxFunctionSolver>(
context: &mut Context,
test_function: &TestFunction,
show_output: bool,
foreign_call_resolver_url: Option<&str>,
resolver_opts: &ResolverOpts,
config: &CompileOptions,
) -> TestStatus {
let compiled_program = compile_no_check(context, config, test_function.get_id(), None, false);
Expand All @@ -40,7 +40,7 @@ pub fn run_test<B: BlackBoxFunctionSolver>(
&compiled_program.program,
WitnessMap::new(),
blackbox_solver,
&mut DefaultForeignCallExecutor::new(show_output, foreign_call_resolver_url),
&mut DefaultForeignCallExecutor::new(show_output, resolver_opts),
);
test_status_program_compile_pass(
test_function,
Expand Down
19 changes: 13 additions & 6 deletions noir/noir-repo/tooling/nargo_cli/src/cli/execute_cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use clap::Args;
use nargo::artifacts::debug::DebugArtifact;
use nargo::constants::PROVER_INPUT_FILE;
use nargo::errors::try_to_diagnose_runtime_error;
use nargo::ops::{compile_program, report_errors, DefaultForeignCallExecutor};
use nargo::ops::{compile_program, report_errors, DefaultForeignCallExecutor, ResolverOpts};
use nargo::package::Package;
use nargo::{insert_all_files_for_workspace_into_file_manager, parse_all};
use nargo_toml::{get_package_manifest, resolve_workspace_from_toml, PackageSelection};
Expand Down Expand Up @@ -46,6 +46,10 @@ pub(crate) struct ExecuteCommand {
/// JSON RPC url to solve oracle calls
#[clap(long)]
oracle_resolver: Option<String>,

/// Use a mock oracle resolver that returns zero to every request
#[clap(long)]
mock_oracle_resolver: bool,
}

pub(crate) fn run(
Expand Down Expand Up @@ -91,11 +95,14 @@ pub(crate) fn run(

let compiled_program = nargo::ops::transform_program(compiled_program, expression_width);

let resolver_opts =
ResolverOpts::new(args.oracle_resolver.as_deref(), args.mock_oracle_resolver);

let (return_value, witness_stack) = execute_program_and_decode(
compiled_program,
package,
&args.prover_name,
args.oracle_resolver.as_deref(),
&resolver_opts,
)?;

println!("[{}] Circuit witness successfully solved", package.name);
Expand All @@ -115,12 +122,12 @@ fn execute_program_and_decode(
program: CompiledProgram,
package: &Package,
prover_name: &str,
foreign_call_resolver_url: Option<&str>,
resolver_opts: &ResolverOpts,
) -> Result<(Option<InputValue>, WitnessStack), CliError> {
// Parse the initial witness values from Prover.toml
let (inputs_map, _) =
read_inputs_from_file(&package.root_dir, prover_name, Format::Toml, &program.abi)?;
let witness_stack = execute_program(&program, &inputs_map, foreign_call_resolver_url)?;
let witness_stack = execute_program(&program, &inputs_map, resolver_opts)?;
let public_abi = program.abi.public_abi();
// Get the entry point witness for the ABI
let main_witness =
Expand All @@ -133,7 +140,7 @@ fn execute_program_and_decode(
pub(crate) fn execute_program(
compiled_program: &CompiledProgram,
inputs_map: &InputMap,
foreign_call_resolver_url: Option<&str>,
resolver_opts: &ResolverOpts,
) -> Result<WitnessStack, CliError> {
let blackbox_solver = Bn254BlackBoxSolver::new();

Expand All @@ -143,7 +150,7 @@ pub(crate) fn execute_program(
&compiled_program.program,
initial_witness,
&blackbox_solver,
&mut DefaultForeignCallExecutor::new(true, foreign_call_resolver_url),
&mut DefaultForeignCallExecutor::new(true, resolver_opts),
);
match solved_witness_stack_err {
Ok(solved_witness_stack) => Ok(solved_witness_stack),
Expand Down
Loading

0 comments on commit c34f073

Please sign in to comment.