From 9eaddfcea9e6b31f6fc732adb3b9b63cb0d6c711 Mon Sep 17 00:00:00 2001 From: evan-schott <53463459+evan-schott@users.noreply.github.com> Date: Fri, 7 Jun 2024 16:15:19 -0700 Subject: [PATCH] local execution working for both `testnet` and `mainnet` --- Cargo.lock | 2 + Cargo.toml | 1 + errors/src/errors/package/package_errors.rs | 7 + leo/cli/commands/execute.rs | 153 +++++++++++++------- leo/package/Cargo.toml | 3 + 5 files changed, 110 insertions(+), 56 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9bca9db2f1..a99b61ba81 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1555,6 +1555,7 @@ dependencies = [ "leo-parser", "leo-retriever", "leo-span", + "num-format", "rand", "rand_chacha", "rand_core", @@ -1585,6 +1586,7 @@ dependencies = [ "lazy_static", "leo-errors", "leo-retriever", + "num-format", "rand", "serde", "serial_test", diff --git a/Cargo.toml b/Cargo.toml index 5e015daa67..08e094dd42 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -61,6 +61,7 @@ ci_skip = [ "leo-compiler/ci_skip" ] noconfig = [ ] [dependencies] +num-format = "0.4.4" text-tables = "0.3.1" ureq = "2.9.7" diff --git a/errors/src/errors/package/package_errors.rs b/errors/src/errors/package/package_errors.rs index e8157b89d3..7346d39705 100644 --- a/errors/src/errors/package/package_errors.rs +++ b/errors/src/errors/package/package_errors.rs @@ -411,4 +411,11 @@ create_messages!( msg: format!("❌ The public balance of {balance} is insufficient to pay the base fee of {fee}"), help: None, } + + @backtraced + execution_error { + args: (error: impl Display), + msg: format!("❌ Execution error: {error}"), + help: None, + } ); diff --git a/leo/cli/commands/execute.rs b/leo/cli/commands/execute.rs index e40df6b49c..beed7cc6dd 100644 --- a/leo/cli/commands/execute.rs +++ b/leo/cli/commands/execute.rs @@ -14,6 +14,7 @@ // You should have received a copy of the GNU General Public License // along with the Leo library. If not, see . +use std::collections::HashMap; use super::*; use aleo_std::StorageMode; use clap::Parser; @@ -38,8 +39,12 @@ use snarkvm::{ Program as SnarkVMProgram, ProgramID, VM, - }, + }, + package::Package as SnarkVMPackage, }; +use snarkvm::circuit::{Aleo, AleoTestnetV0, AleoV0}; +use snarkvm::cli::LOCALE; +use snarkvm::prelude::{Identifier, Locator, Value}; /// Build, Prove and Run Leo program with inputs #[derive(Parser, Debug)] @@ -82,21 +87,18 @@ impl Command for Execute { // Parse the network. let network = NetworkName::try_from(self.compiler_options.network.as_str())?; match network { - NetworkName::MainnetV0 => handle_execute::(self, context), - NetworkName::TestnetV0 => handle_execute::(self, context), + NetworkName::MainnetV0 => handle_execute::(self, context), + NetworkName::TestnetV0 => handle_execute::(self, context), } } } // A helper function to handle the `execute` command. -fn handle_execute(command: Execute, context: Context) -> Result<::Output> { +fn handle_execute(command: Execute, context: Context) -> Result<::Output> { // If input values are provided, then run the program with those inputs. // Otherwise, use the input file. let mut inputs = command.inputs.clone(); - // Compose the `execute` command. - let mut arguments = vec![SNARKVM_COMMAND.to_string(), command.name.clone()]; - // Add the inputs to the arguments. match command.file.clone() { Some(file) => { @@ -107,7 +109,7 @@ fn handle_execute(command: Execute, context: Context) -> Result<::parse(content) { + while let Ok((remaining, value)) = snarkvm::prelude::Value::::parse(content) { content = remaining; values.push(value); } @@ -118,17 +120,28 @@ fn handle_execute(command: Execute, context: Context) -> Result<>(); // Add the inputs from the file to the arguments. - arguments.append(&mut inputs_from_file); + inputs.append(&mut inputs_from_file); } - None => arguments.append(&mut inputs), + None => {}, } + // Initialize an RNG. + let rng = &mut rand::thread_rng(); + + // Get the private key. + let private_key = match command.fee_options.private_key.clone() { + Some(key) => PrivateKey::from_str(&key)?, + None => PrivateKey::from_str( + &dotenv_private_key().map_err(CliError::failed_to_read_environment_private_key)?.to_string(), + )?, + }; + // If the `broadcast` flag is set, then broadcast the transaction. if command.broadcast { // Get the program name. let program_name = match (command.program.clone(), command.local) { (Some(name), true) => { - let local = context.open_manifest::()?.program_id().to_string(); + let local = context.open_manifest::()?.program_id().to_string(); // Throw error if local name doesn't match the specified name. if name == local { local @@ -137,33 +150,22 @@ fn handle_execute(command: Execute, context: Context) -> Result< name.clone(), - (None, true) => context.open_manifest::()?.program_id().to_string(), + (None, true) => context.open_manifest::()?.program_id().to_string(), (None, false) => return Err(PackageError::missing_on_chain_program_name().into()), }; - // Get the private key. - let private_key = match command.fee_options.private_key.clone() { - Some(key) => PrivateKey::from_str(&key)?, - None => PrivateKey::from_str( - &dotenv_private_key().map_err(CliError::failed_to_read_environment_private_key)?.to_string(), - )?, - }; - // Specify the query - let query = SnarkVMQuery::>::from(command.compiler_options.endpoint.clone()); - - // Initialize an RNG. - let rng = &mut rand::thread_rng(); + let query = SnarkVMQuery::>::from(command.compiler_options.endpoint.clone()); // Initialize the storage. - let store = ConsensusStore::>::open(StorageMode::Production)?; + let store = ConsensusStore::>::open(StorageMode::Production)?; // Initialize the VM. let vm = VM::from(store)?; // Load the main program, and all of its imports. - let program_id = &ProgramID::::from_str(&format!("{}.aleo", program_name))?; - // TODO: create a local version too + let program_id = &ProgramID::::from_str(&format!("{}.aleo", program_name))?; + // TODO: X load_program_from_network(&command, context.clone(), &mut vm.process().write(), program_id)?; let fee_record = if let Some(record) = command.fee_options.record { @@ -193,7 +195,7 @@ fn handle_execute(command: Execute, context: Context) -> Result<::try_from(ViewKey::try_from(&private_key)?)?; + let address = Address::::try_from(ViewKey::try_from(&private_key)?)?; // Query the public balance of the address on the `account` mapping from `credits.aleo`. let mut public_balance = Query { endpoint: command.compiler_options.endpoint.clone(), @@ -215,14 +217,6 @@ fn handle_execute(command: Execute, context: Context) -> Result<(command: Execute, context: Context) -> Result<>>(); + // Execute the request. + let (response, execution, metrics) = + package.execute::(command.compiler_options.endpoint.clone(), &private_key, Identifier::try_from(command.name.clone())?, &inputs, rng).map_err(|err| PackageError::execution_error(err))?; + + let fee = None; + + // Construct the transaction. + let transaction = Transaction::from_execution(execution, fee)?; + + // Log the metrics. + use num_format::ToFormattedString; + + // Count the number of times a function is called. + let mut program_frequency = HashMap::::new(); + for metric in metrics.iter() { + // Prepare the function name string. + let function_name_string = format!("'{}/{}'", metric.program_id, metric.function_name).bold(); + + // Prepare the function constraints string + let function_constraints_string = format!( + "{function_name_string} - {} constraints", + metric.num_function_constraints.to_formatted_string(LOCALE) + ); + + // Increment the counter for the function call. + match program_frequency.get_mut(&function_constraints_string) { + Some(counter) => *counter += 1, + None => { + let _ = program_frequency.insert(function_constraints_string, 1); + } + } + } + + println!("⛓ Constraints\n"); + for (function_constraints, counter) in program_frequency { + // Log the constraints + let counter_string = match counter { + 1 => "(called 1 time)".to_string().dimmed(), + counter => format!("(called {counter} times)").dimmed(), + }; + + println!(" • {function_constraints} {counter_string}",) + } + + // Log the outputs. + match response.outputs().len() { + 0 => (), + 1 => println!("\n➡️ Output\n"), + _ => println!("\n➡️ Outputs\n"), + }; + for output in response.outputs() { + println!("{}", format!(" • {output}")); + } println!(); - let command = SnarkVMExecute::try_parse_from(&arguments).map_err(CliError::failed_to_parse_execute)?; - let res = command.parse().map_err(CliError::failed_to_execute_execute)?; - // Log the output of the `execute` command. - tracing::info!("{}", res); + // Print the transaction. + println!("{transaction}\n"); + // Prepare the locator. + let locator = Locator::::from_str(&format!("{}/{}", package.program_id(), command.name))?; + // Prepare the path string. + let path_string = format!("(in \"{}\")", path.display()); + + println!("✅ Executed '{}' {}", locator.to_string().bold(), path_string.dimmed()); Ok(()) } diff --git a/leo/package/Cargo.toml b/leo/package/Cargo.toml index 7369098ec7..ed2c7a6299 100644 --- a/leo/package/Cargo.toml +++ b/leo/package/Cargo.toml @@ -37,6 +37,9 @@ default-features = false version = "1.9" features = [ "serde" ] +[dependencies.num-format] +version = "0.4.4" + [dependencies.rand] version = "0.8"