Merge pull request #27928 from AleoHQ/feat/deploy-mainnet

[Feat] Deploy new programs and execute on-chain programs from the command line
This commit is contained in:
d0cd 2024-05-06 12:10:29 -04:00 committed by GitHub
commit f29b6b01ee
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 2648 additions and 546 deletions

2869
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -43,6 +43,10 @@ members = [
"utils/retriever"
]
[workspace.dependencies.snarkos-cli]
git = "https://github.com/AleoHQ/snarkos"
rev = "d69e417"
[workspace.dependencies.snarkvm]
#version = "0.16.19"
git = "https://github.com/AleoHQ/snarkVM"
@ -149,6 +153,9 @@ version = "1.0"
[dependencies.serial_test]
version = "3.0.0"
[dependencies.snarkos-cli]
workspace = true
[dependencies.snarkvm]
workspace = true
features = [ "circuit", "console" ]

View File

@ -215,4 +215,11 @@ create_messages!(
msg: format!("Failed to read private key from environment.\nIO Error: {error}"),
help: Some("Pass in private key using `--private-key <PRIVATE-KEY>` or create a .env file with your private key information. See examples for formatting information.".to_string()),
}
@backtraced
recursive_deploy_with_record {
args: (),
msg: "Cannot combine recursive deploy with private fee.".to_string(),
help: None,
}
);

View File

@ -370,4 +370,17 @@ create_messages!(
help: None,
}
@backtraced
missing_on_chain_program_name {
args: (),
msg: "The name of the program to execute on-chain is missing.".to_string(),
help: Some("Either set `--local` to execute the local program on chain, or set `--program <PROGRAM>`.".to_string()),
}
@backtraced
conflicting_on_chain_program_name {
args: (first: impl Display, second: impl Display),
msg: format!("Conflicting program names given to execute on chain: `{first}` and `{second}`."),
help: Some("Either set `--local` to execute the local program on chain, or set `--program <PROGRAM>`.".to_string()),
}
);

View File

@ -15,25 +15,27 @@
// along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
use super::*;
//use snarkos_cli::commands::{Deploy as SnarkOSDeploy, Developer};
use snarkos_cli::commands::{Deploy as SnarkOSDeploy, Developer};
use snarkvm::cli::helpers::dotenv_private_key;
use std::path::PathBuf;
/// Deploys an Aleo program.
#[derive(Parser, Debug)]
pub struct Deploy {
#[clap(long, help = "Custom priority fee in microcredits", default_value = "1000000")]
pub(crate) priority_fee: String,
#[clap(long, help = "Custom query endpoint", default_value = "http://api.explorer.aleo.org/v1")]
pub(crate) endpoint: String,
#[clap(long, help = "Custom network", default_value = "testnet3")]
pub(crate) network: String,
#[clap(long, help = "Custom private key")]
pub(crate) private_key: Option<String>,
#[clap(long, help = "Disables building of the project before deployment", default_value = "false")]
#[clap(long, help = "Endpoint to retrieve network state from.", default_value = "http://api.explorer.aleo.org/v1")]
pub endpoint: String,
#[clap(flatten)]
pub(crate) fee_options: FeeOptions,
#[clap(long, help = "Disables building of the project before deployment.", default_value = "false")]
pub(crate) no_build: bool,
#[clap(long, help = "Disables recursive deployment of dependencies", default_value = "false")]
pub(crate) non_recursive: bool,
#[clap(long, help = "Custom wait gap between consecutive deployments", default_value = "12")]
pub(crate) wait_gap: u64,
#[clap(long, help = "Enables recursive deployment of dependencies.", default_value = "false")]
pub(crate) recursive: bool,
#[clap(
long,
help = "Time in seconds to wait between consecutive deployments. This is to help prevent a program from trying to be included in an earlier block than its dependency program.",
default_value = "12"
)]
pub(crate) wait: u64,
}
impl Command for Deploy {
@ -51,55 +53,66 @@ impl Command for Deploy {
Ok(())
}
fn apply(self, _context: Context, _: Self::Input) -> Result<Self::Output> {
// // Get the program name
// let project_name = context.open_manifest()?.program_id().to_string();
//
// // Get the private key
// let mut private_key = self.private_key;
// if private_key.is_none() {
// private_key =
// Some(dotenv_private_key().map_err(CliError::failed_to_read_environment_private_key)?.to_string());
// }
//
// let mut all_paths: Vec<(String, PathBuf)> = Vec::new();
//
// // Extract post-ordered list of local dependencies' paths from `leo.lock`
// if !self.non_recursive {
// all_paths = context.local_dependency_paths()?;
// }
//
// // Add the parent program to be deployed last
// all_paths.push((project_name, context.dir()?.join("build")));
//
// for (index, (name, path)) in all_paths.iter().enumerate() {
// // Set deploy arguments
// let deploy = SnarkOSDeploy::try_parse_from([
// "snarkos",
// "--private-key",
// private_key.as_ref().unwrap(),
// "--query",
// self.endpoint.as_str(),
// "--priority-fee",
// self.priority_fee.as_str(),
// "--path",
// path.to_str().unwrap(),
// "--broadcast",
// format!("{}/{}/transaction/broadcast", self.endpoint, self.network).as_str(),
// &name,
// ])
// .unwrap();
//
// // Deploy program
// Developer::Deploy(deploy).parse().map_err(CliError::failed_to_execute_deploy)?;
//
// // Sleep for `wait_gap` seconds.
// // This helps avoid parents from being serialized before children.
// if index < all_paths.len() - 1 {
// std::thread::sleep(std::time::Duration::from_secs(self.wait_gap));
// }
// }
fn apply(self, context: Context, _: Self::Input) -> Result<Self::Output> {
// Get the program name.
let project_name = context.open_manifest()?.program_id().to_string();
Err(PackageError::unimplemented_command("leo deploy").into())
// Get the private key.
let mut private_key = self.fee_options.private_key;
if private_key.is_none() {
private_key =
Some(dotenv_private_key().map_err(CliError::failed_to_read_environment_private_key)?.to_string());
}
let mut all_paths: Vec<(String, PathBuf)> = Vec::new();
// Extract post-ordered list of local dependencies' paths from `leo.lock`.
if self.recursive {
// Cannot combine with private fee.
if self.fee_options.record.is_some() {
return Err(CliError::recursive_deploy_with_record().into());
}
all_paths = context.local_dependency_paths()?;
}
// Add the parent program to be deployed last.
all_paths.push((project_name, context.dir()?.join("build")));
for (index, (name, path)) in all_paths.iter().enumerate() {
// Set the deploy arguments.
let mut deploy_args = vec![
"snarkos".to_string(),
"--private-key".to_string(),
private_key.as_ref().unwrap().clone(),
"--query".to_string(),
self.endpoint.clone(),
"--priority-fee".to_string(),
self.fee_options.priority_fee.to_string(),
"--path".to_string(),
path.to_str().unwrap().parse().unwrap(),
"--broadcast".to_string(),
format!("{}/{}/transaction/broadcast", self.endpoint, self.fee_options.network).to_string(),
name.clone(),
];
// Use record as payment option if it is provided.
if let Some(record) = self.fee_options.record.clone() {
deploy_args.push("--record".to_string());
deploy_args.push(record);
};
let deploy = SnarkOSDeploy::try_parse_from(deploy_args).unwrap();
// Deploy program.
Developer::Deploy(deploy).parse().map_err(CliError::failed_to_execute_deploy)?;
// Sleep for `wait_gap` seconds.
// This helps avoid parents from being serialized before children.
if index < all_paths.len() - 1 {
std::thread::sleep(std::time::Duration::from_secs(self.wait));
}
}
Ok(())
}
}

View File

@ -16,28 +16,31 @@
use super::*;
use clap::Parser;
// use snarkos_cli::commands::{Developer, Execute as SnarkOSExecute};
use snarkvm::{cli::Execute as SnarkVMExecute, prelude::Parser as SnarkVMParser};
use snarkos_cli::commands::{Developer, Execute as SnarkOSExecute};
use snarkvm::{
cli::{helpers::dotenv_private_key, Execute as SnarkVMExecute},
prelude::Parser as SnarkVMParser,
};
/// Build, Prove and Run Leo program with inputs
#[derive(Parser, Debug)]
pub struct Execute {
#[clap(name = "NAME", help = "The name of the function to execute.", default_value = "main")]
name: String,
#[clap(name = "INPUTS", help = "The inputs to the program. If none are provided, the input file is used.")]
#[clap(name = "INPUTS", help = "The inputs to the program.")]
inputs: Vec<String>,
#[clap(long, help = "Execute the transition on chain", default_value = "false")]
#[clap(short, long, help = "Execute the transition on-chain.", default_value = "false")]
broadcast: bool,
#[clap(long, help = "Custom priority fee in microcredits", default_value = "1000000")]
priority_fee: String,
#[clap(long, help = "Custom network", default_value = "testnet3")]
network: String,
#[clap(long, help = "Custom private key")]
private_key: Option<String>,
#[arg(short, long, help = "The inputs to the program, from a file. Overrides the INPUTS argument.")]
file: Option<String>,
#[clap(short, long, help = "Execute the local program on-chain.", default_value = "false")]
local: bool,
#[clap(short, long, help = "The program to execute on-chain.")]
program: Option<String>,
#[clap(flatten)]
fee_options: FeeOptions,
#[clap(flatten)]
compiler_options: BuildOptions,
#[arg(short, long, help = "The inputs to the program, from a file. Overrides the INPUTS argument.")]
file: Option<String>,
}
impl Command for Execute {
@ -49,50 +52,77 @@ impl Command for Execute {
}
fn prelude(&self, context: Context) -> Result<Self::Input> {
// No need to build if we are executing an external program.
if self.program.is_some() {
return Ok(());
}
(Build { options: self.compiler_options.clone() }).execute(context)
}
fn apply(self, context: Context, _input: Self::Input) -> Result<Self::Output> {
// If the `broadcast` flag is set, then broadcast the transaction.
if self.broadcast {
// // Get the program name
// let project_name = context.open_manifest()?.program_id().to_string();
//
// // Get the private key
// let mut private_key = self.private_key;
// if private_key.is_none() {
// private_key =
// Some(dotenv_private_key().map_err(CliError::failed_to_read_environment_private_key)?.to_string());
// }
//
// // Execute program
// Developer::Execute(
// SnarkOSExecute::try_parse_from(
// [
// vec![
// "snarkos",
// "--private-key",
// private_key.as_ref().unwrap(),
// "--query",
// self.compiler_options.endpoint.as_str(),
// "--priority-fee",
// self.priority_fee.as_str(),
// "--broadcast",
// format!("{}/{}/transaction/broadcast", self.compiler_options.endpoint, self.network)
// .as_str(),
// project_name.as_str(),
// &self.name,
// ],
// self.inputs.iter().map(|input| input.as_str()).collect(),
// ]
// .concat(),
// )
// .unwrap(),
// )
// .parse()
// .map_err(CliError::failed_to_execute_deploy)?;
// Get the program name.
let program_name = match (self.program, self.local) {
(Some(name), true) => {
let local = context.open_manifest()?.program_id().to_string();
// Throw error if local name doesn't match the specified name.
if name == local {
local
} else {
return Err(PackageError::conflicting_on_chain_program_name(local, name).into());
}
}
(Some(name), false) => name,
(None, true) => context.open_manifest()?.program_id().to_string(),
(None, false) => return Err(PackageError::missing_on_chain_program_name().into()),
};
return Err(PackageError::unimplemented_command("leo execute --broadcast").into());
// Get the private key.
let private_key = match self.fee_options.private_key {
Some(private_key) => private_key,
None => dotenv_private_key().map_err(CliError::failed_to_read_environment_private_key)?.to_string(),
};
// Set deploy arguments.
let mut fee_args = vec![
"snarkos".to_string(),
"--private-key".to_string(),
private_key.clone(),
"--query".to_string(),
self.compiler_options.endpoint.clone(),
"--priority-fee".to_string(),
self.fee_options.priority_fee.to_string(),
"--broadcast".to_string(),
format!("{}/{}/transaction/broadcast", self.compiler_options.endpoint, self.fee_options.network)
.to_string(),
];
// Use record as payment option if it is provided.
if let Some(record) = self.fee_options.record.clone() {
fee_args.push("--record".to_string());
fee_args.push(record);
};
// Execute program.
Developer::Execute(
SnarkOSExecute::try_parse_from(
[
// The arguments for determining fee.
fee_args,
// The program ID and function name.
vec![program_name, self.name],
// The function inputs.
self.inputs,
]
.concat(),
)
.unwrap(),
)
.parse()
.map_err(CliError::failed_to_execute_deploy)?;
return Ok(());
}
// If input values are provided, then run the program with those inputs.
@ -137,7 +167,7 @@ impl Command for Execute {
arguments.push(String::from("--endpoint"));
arguments.push(self.compiler_options.endpoint.clone());
// Open the Leo build/ directory
// Open the Leo build/ directory.
let path = context.dir()?;
let build_directory = BuildDirectory::open(&path)?;
@ -145,7 +175,7 @@ impl Command for Execute {
std::env::set_current_dir(&build_directory)
.map_err(|err| PackageError::failed_to_set_cwd(build_directory.display(), err))?;
// Unset the Leo panic hook
// Unset the Leo panic hook.
let _ = std::panic::take_hook();
// Call the `execute` command.

View File

@ -122,11 +122,7 @@ pub trait Command {
/// require Build command output as their input.
#[derive(Parser, Clone, Debug, Default)]
pub struct BuildOptions {
#[clap(
long,
help = "Endpoint to retrieve on-chain dependencies from.",
default_value = "http://api.explorer.aleo.org/v1"
)]
#[clap(long, help = "Endpoint to retrieve network state from.", default_value = "http://api.explorer.aleo.org/v1")]
pub endpoint: String,
#[clap(long, help = "Does not recursively compile dependencies.")]
pub non_recursive: bool,
@ -165,3 +161,21 @@ pub struct BuildOptions {
#[clap(long, help = "Disable type checking of nested conditional branches in finalize scope.")]
pub disable_conditional_branch_type_checking: bool,
}
/// On Chain Execution Options to set preferences for keys, fees and networks.
/// Used by Execute and Deploy commands.
#[derive(Parser, Clone, Debug, Default)]
pub struct FeeOptions {
#[clap(long, help = "Priority fee in microcredits. Defaults to 0.", default_value = "0")]
pub(crate) priority_fee: String,
#[clap(long, help = "Network to broadcast to. Defaults to testnet3.", default_value = "testnet3")]
pub(crate) network: String,
#[clap(long, help = "Private key to authorize fee expenditure.")]
pub(crate) private_key: Option<String>,
#[clap(
short,
help = "Record to pay for fee privately. If one is not specified, a public fee will be taken.",
long
)]
record: Option<String>,
}

View File

@ -26,6 +26,9 @@ version = "=1.11.0"
path = "../../utils/retriever"
version = "1.11.0"
[dependencies.snarkos-cli]
workspace = true
[dependencies.snarkvm]
workspace = true