-
Notifications
You must be signed in to change notification settings - Fork 702
[Feature] Execution alternatives. #28856
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -21,10 +21,11 @@ use leo_ast::NetworkName; | |
| use leo_package::{Package, ProgramData, fetch_program_from_network}; | ||
|
|
||
| use aleo_std::StorageMode; | ||
| use snarkvm::prelude::{Execution, Itertools, Network, Program, execution_cost}; | ||
| use snarkvm::prelude::{Authorization, Execution, Itertools, Network, Program, execution_cost}; | ||
|
|
||
| use clap::Parser; | ||
| use colored::*; | ||
| use serde::{Serialize, ser::SerializeStruct}; | ||
| use std::{convert::TryFrom, path::PathBuf}; | ||
|
|
||
| #[cfg(not(feature = "only_testnet"))] | ||
|
|
@@ -33,10 +34,11 @@ use snarkvm::{ | |
| circuit::{Aleo, AleoTestnetV0}, | ||
| prelude::{ | ||
| ConsensusVersion, | ||
| Fee, | ||
| Identifier, | ||
| ProgramID, | ||
| VM, | ||
| query::Query as SnarkVMQuery, | ||
| query::{Query as SnarkVMQuery, QueryTrait}, | ||
| store::{ | ||
| ConsensusStore, | ||
| helpers::memory::{BlockMemory, ConsensusMemory}, | ||
|
|
@@ -58,6 +60,10 @@ pub struct LeoExecute { | |
| help = "The program inputs e.g. `1u32`, `record1...` (record ciphertext), or `{ owner: ...}` " | ||
| )] | ||
| inputs: Vec<String>, | ||
| #[clap(long, help = "Generate the authorization only.", conflicts_with = "broadcast")] | ||
| pub(crate) authorization_only: bool, | ||
| #[clap(long, help = "Skips proving.")] | ||
| pub(crate) skip_proving: bool, | ||
| #[clap(flatten)] | ||
| pub(crate) fee_options: FeeOptions, | ||
| #[clap(flatten)] | ||
|
|
@@ -179,7 +185,7 @@ fn handle_execute<A: Aleo>( | |
| "{}.aleo", | ||
| package.programs.last().expect("There must be at least one program in a Leo package").name | ||
| ), | ||
| command.name, | ||
| command.name.clone(), | ||
| ), | ||
| None => { | ||
| return Err(CliError::custom(format!( | ||
|
|
@@ -265,11 +271,10 @@ fn handle_execute<A: Aleo>( | |
| } | ||
| } | ||
|
|
||
| let inputs = | ||
| command.inputs.into_iter().map(|string| parse_input(&string, &private_key)).collect::<Result<Vec<_>>>()?; | ||
| let inputs = command.inputs.iter().map(|string| parse_input(string, &private_key)).collect::<Result<Vec<_>>>()?; | ||
|
|
||
| // Get the first fee option. | ||
| let (_, priority_fee, record) = | ||
| let (base_fee, priority_fee, record) = | ||
| parse_fee_options(&private_key, &command.fee_options, 1)?.into_iter().next().unwrap_or((None, None, None)); | ||
|
|
||
| // Get the consensus version. | ||
|
|
@@ -287,9 +292,9 @@ fn handle_execute<A: Aleo>( | |
| is_local, | ||
| priority_fee.unwrap_or(0), | ||
| record.is_some(), | ||
| &command.action, | ||
| consensus_version, | ||
| &check_task_for_warnings(&endpoint, network, &programs, consensus_version), | ||
| &command, | ||
| ); | ||
|
|
||
| // Prompt the user to confirm the plan. | ||
|
|
@@ -337,32 +342,108 @@ fn handle_execute<A: Aleo>( | |
| vm.process().write().add_programs_with_editions(&programs_and_editions)?; | ||
|
|
||
| // Execute the program and produce a transaction. | ||
| let (transaction, response) = vm.execute_with_response( | ||
| &private_key, | ||
| (&program_name, &function_name), | ||
| inputs.iter(), | ||
| record, | ||
| priority_fee.unwrap_or(0), | ||
| Some(&query), | ||
| rng, | ||
| )?; | ||
| let (output_name, output, response) = if command.authorization_only { | ||
| println!("\n🛂 Generating authorization for {program_name}/{function_name}..."); | ||
|
|
||
| // Get the base fee. | ||
| let Some(base_fee) = base_fee else { | ||
| return Err(CliError::custom( | ||
| "When generating an authorization, a base fee must be provided with `--base-fee`.", | ||
| ) | ||
| .into()); | ||
| }; | ||
|
|
||
| // Print the execution stats. | ||
| print_execution_stats::<A::Network>( | ||
| &vm, | ||
| &program_name, | ||
| transaction.execution().expect("Expected execution"), | ||
| priority_fee, | ||
| consensus_version, | ||
| )?; | ||
| // Generate the authorizations. | ||
| let execution = | ||
| vm.process().read().authorize::<A, _>(&private_key, &program_name, &function_name, inputs.iter(), rng)?; | ||
|
|
||
| let id = execution.to_execution_id()?; | ||
| let fee = match record { | ||
| None => vm.authorize_fee_public(&private_key, base_fee, priority_fee.unwrap_or(0), id, rng)?, | ||
| Some(record) => { | ||
| vm.authorize_fee_private(&private_key, record, base_fee, priority_fee.unwrap_or(0), id, rng)? | ||
| } | ||
| }; | ||
|
|
||
| // Evaluate the authorization to get the response. | ||
| let response = vm.process().read().evaluate::<A>(execution.clone())?; | ||
|
|
||
| ("authorization", ExecutionOutput::AuthorizationData { execution, fee }, response) | ||
| } else if command.skip_proving { | ||
| println!("\n⚙️ Generating transaction WITHOUT a proof for {program_name}/{function_name}..."); | ||
|
|
||
| // Generate the authorization. | ||
| let authorization = | ||
| vm.process().read().authorize::<A, _>(&private_key, &program_name, &function_name, inputs.iter(), rng)?; | ||
|
|
||
| // Get the state root. | ||
| let state_root = query.current_state_root()?; | ||
|
|
||
| // Create an execution without the proof. | ||
| let execution = Execution::from(authorization.transitions().values().cloned(), state_root, None)?; | ||
|
|
||
| // Calculate the cost. | ||
| let (cost, _) = execution_cost(&vm.process().read(), &execution, consensus_version)?; | ||
|
|
||
| // Generate the fee authorization. | ||
| let id = authorization.to_execution_id()?; | ||
| let fee_authorization = match record { | ||
| None => { | ||
| vm.authorize_fee_public(&private_key, base_fee.unwrap_or(cost), priority_fee.unwrap_or(0), id, rng)? | ||
| } | ||
| Some(record) => vm.authorize_fee_private( | ||
| &private_key, | ||
| record, | ||
| base_fee.unwrap_or(cost), | ||
| priority_fee.unwrap_or(0), | ||
| id, | ||
| rng, | ||
| )?, | ||
| }; | ||
|
|
||
| // Create a fee transition without a proof. | ||
| let fee = Fee::from(fee_authorization.transitions().into_iter().next().unwrap().1, state_root, None)?; | ||
|
|
||
| // Create the transaction. | ||
| let transaction = Transaction::from_execution(execution, Some(fee))?; | ||
|
|
||
| // Evaluate the transaction to get the response. | ||
| let response = vm.process().read().evaluate::<A>(authorization)?; | ||
|
|
||
| ("transaction", ExecutionOutput::Transaction(Box::new(transaction)), response) | ||
| } else { | ||
| println!("\n⚙️ Generating transaction for {program_name}/{function_name}..."); | ||
|
|
||
| // Generate the transaction and get the response. | ||
| let (transaction, response) = vm.execute_with_response( | ||
| &private_key, | ||
| (&program_name, &function_name), | ||
| inputs.iter(), | ||
| record, | ||
| priority_fee.unwrap_or(0), | ||
| Some(&query), | ||
| rng, | ||
| )?; | ||
|
|
||
| // Print the execution stats. | ||
| print_execution_stats::<A::Network>( | ||
| &vm, | ||
| &program_name, | ||
| transaction.execution().expect("Expected execution"), | ||
| priority_fee, | ||
| consensus_version, | ||
| )?; | ||
|
|
||
| ("execution", ExecutionOutput::Transaction(Box::new(transaction)), response) | ||
| }; | ||
|
|
||
| // Print the transaction. | ||
| // If the `print` option is set, print the execution transaction to the console. | ||
| // The transaction is printed in JSON format. | ||
| if command.action.print { | ||
| let transaction_json = serde_json::to_string_pretty(&transaction) | ||
| let json = serde_json::to_string_pretty(&output) | ||
| .map_err(|e| CliError::custom(format!("Failed to serialize transaction: {e}")))?; | ||
| println!("🖨️ Printing execution for {program_name}\n{transaction_json}"); | ||
| println!("🖨️ Printing {output_name} for {program_name}\n{json}"); | ||
| } | ||
|
|
||
| // If the `save` option is set, save the execution transaction to a file in the specified directory. | ||
|
|
@@ -373,11 +454,11 @@ fn handle_execute<A: Aleo>( | |
| std::fs::create_dir_all(path).map_err(|e| CliError::custom(format!("Failed to create directory: {e}")))?; | ||
| // Save the transaction to a file. | ||
| let file_path = PathBuf::from(path).join(format!("{program_name}.execution.json")); | ||
| println!("💾 Saving execution for {program_name} at {}", file_path.display()); | ||
| let transaction_json = serde_json::to_string_pretty(&transaction) | ||
| .map_err(|e| CliError::custom(format!("Failed to serialize transaction: {e}")))?; | ||
| println!("💾 Saving {output_name} for {program_name} at {}", file_path.display()); | ||
| let transaction_json = serde_json::to_string_pretty(&output) | ||
| .map_err(|e| CliError::custom(format!("Failed to serialize {output_name}: {e}")))?; | ||
| std::fs::write(file_path, transaction_json) | ||
| .map_err(|e| CliError::custom(format!("Failed to write transaction to file: {e}")))?; | ||
| .map_err(|e| CliError::custom(format!("Failed to write {output_name} to file: {e}")))?; | ||
| } | ||
|
|
||
| match response.outputs().len() { | ||
|
|
@@ -390,9 +471,14 @@ fn handle_execute<A: Aleo>( | |
| } | ||
| println!(); | ||
|
|
||
| // If the `broadcast` option is set, broadcast each deployment transaction to the network. | ||
| // If the `broadcast` option is set, broadcast each execution to the network. | ||
| if command.action.broadcast { | ||
| println!("📡 Broadcasting execution for {program_name}..."); | ||
| println!("📡 Broadcasting {output_name} for {program_name}..."); | ||
| // Get the transaction. | ||
| let ExecutionOutput::Transaction(transaction) = output else { | ||
| println!("❌ Cannot broadcast an authorization. Please run without `--authorization-only`."); | ||
| return Ok(()); | ||
| }; | ||
|
Comment on lines
+476
to
+481
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this error required given that |
||
| // Get and confirm the fee with the user. | ||
| let mut fee_id = None; | ||
| if let Some(fee) = transaction.fee_transition() { | ||
|
|
@@ -492,9 +578,9 @@ fn print_execution_plan<N: Network>( | |
| is_local: bool, | ||
| priority_fee: u64, | ||
| fee_record: bool, | ||
| action: &TransactionAction, | ||
| consensus_version: ConsensusVersion, | ||
| warnings: &[String], | ||
| command: &LeoExecute, | ||
| ) { | ||
| println!("\n{}", "🚀 Execution Plan Summary".bold().underline()); | ||
| println!("{}", "──────────────────────────────────────────────".dimmed()); | ||
|
|
@@ -519,17 +605,23 @@ fn print_execution_plan<N: Network>( | |
| if !is_local { | ||
| println!(" - Program and its dependencies will be downloaded from the network."); | ||
| } | ||
| if action.print { | ||
| if command.authorization_only { | ||
| println!(" - Only the authorization will be generated."); | ||
| } | ||
| if command.skip_proving { | ||
| println!(" - A transaction will be generated, WITHOUT a proof."); | ||
| } | ||
| if command.action.print { | ||
| println!(" - Transaction will be printed to the console."); | ||
| } else { | ||
| println!(" - Transaction will NOT be printed to the console."); | ||
| } | ||
| if let Some(path) = &action.save { | ||
| if let Some(path) = &command.action.save { | ||
| println!(" - Transaction will be saved to {}", path.bold()); | ||
| } else { | ||
| println!(" - Transaction will NOT be saved to a file."); | ||
| } | ||
| if action.broadcast { | ||
| if command.action.broadcast { | ||
| println!(" - Transaction will be broadcast to {}", endpoint.bold()); | ||
| } else { | ||
| println!(" - Transaction will NOT be broadcast to the network."); | ||
|
|
@@ -580,3 +672,29 @@ fn print_execution_stats<N: Network>( | |
| println!("{}", "──────────────────────────────────────────────".dimmed()); | ||
| Ok(()) | ||
| } | ||
|
|
||
| // Possible outputs of an `execute` command. | ||
| enum ExecutionOutput<N: Network> { | ||
| // Produced if `--authorization-only` is set. | ||
| AuthorizationData { execution: Authorization<N>, fee: Authorization<N> }, | ||
| // Default output, a transaction. | ||
| // Note. The transaction may not have a proof if `--skip-proving` is set. | ||
| Transaction(Box<Transaction<N>>), | ||
| } | ||
|
|
||
| impl<N: Network> Serialize for ExecutionOutput<N> { | ||
| fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> | ||
| where | ||
| S: serde::Serializer, | ||
| { | ||
| match self { | ||
| ExecutionOutput::AuthorizationData { execution, fee } => { | ||
| let mut state = serializer.serialize_struct("AuthorizationData", 2)?; | ||
| state.serialize_field("execution", execution)?; | ||
| state.serialize_field("fee", fee)?; | ||
| state.end() | ||
| } | ||
| ExecutionOutput::Transaction(transaction) => transaction.serialize(serializer), | ||
| } | ||
| } | ||
| } | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is part of this logic common with the
if command.authorization_only?