diff --git a/cli/src/lib.rs b/cli/src/lib.rs index de9d569654..daf71f25c3 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -35,11 +35,13 @@ use solana_sdk::compute_budget::ComputeBudgetInstruction; use solana_sdk::pubkey::Pubkey; use solana_sdk::signature::Keypair; use solana_sdk::signature::Signer; +use solana_sdk::signer::EncodableKey; use solana_sdk::sysvar; use solana_sdk::transaction::Transaction; use std::collections::BTreeMap; use std::collections::HashMap; use std::collections::HashSet; +use std::error::Error; use std::ffi::OsString; use std::fs::{self, File}; use std::io::prelude::*; @@ -280,6 +282,9 @@ pub enum Command { program_id: Pubkey, /// Filepath to the new program binary. program_filepath: String, + /// Max times to retry on failure. + #[clap(long, default_value = "0")] + max_retries: u32, /// Arguments to pass to the underlying `solana program deploy` command. #[clap(required = false, last = true)] solana_args: Vec, @@ -856,11 +861,13 @@ fn process_command(opts: Opts) -> Result<()> { Command::Upgrade { program_id, program_filepath, + max_retries, solana_args, } => upgrade( &opts.cfg_override, program_id, program_filepath, + max_retries, solana_args, ), Command::Idl { subcmd } => idl(&opts.cfg_override, subcmd), @@ -3552,7 +3559,7 @@ fn validator_flags( // Use a different flag for program accounts to fix the problem // described in https://github.com/anza-xyz/agave/issues/522 if account.owner == bpf_loader_upgradeable::id() - // Only programs are supported with `--clone-upgradeable-program` + // Only programs are supported with `--clone-upgradeable-program` && matches!( account.deserialize_data::()?, UpgradeableLoaderState::Program { .. } @@ -3837,6 +3844,10 @@ fn deploy( let url = cluster_url(cfg, &cfg.test_validator); let keypair = cfg.provider.wallet.to_string(); + // Augment the given solana args with recommended defaults. + let client = create_client(&url); + let solana_args = add_recommended_deployment_solana_args(&client, solana_args)?; + // Deploy the programs. println!("Deploying cluster: {}", url); println!("Upgrade authority: {}", keypair); @@ -3901,6 +3912,7 @@ fn upgrade( cfg_override: &ConfigOverride, program_id: Pubkey, program_filepath: String, + max_retries: u32, solana_args: Vec, ) -> Result<()> { let path: PathBuf = program_filepath.parse().unwrap(); @@ -3908,24 +3920,35 @@ fn upgrade( with_workspace(cfg_override, |cfg| { let url = cluster_url(cfg, &cfg.test_validator); - let exit = std::process::Command::new("solana") - .arg("program") - .arg("deploy") - .arg("--url") - .arg(url) - .arg("--keypair") - .arg(cfg.provider.wallet.to_string()) - .arg("--program-id") - .arg(program_id.to_string()) - .arg(strip_workspace_prefix(program_filepath)) - .args(&solana_args) - .stdout(Stdio::inherit()) - .stderr(Stdio::inherit()) - .output() - .expect("Must deploy"); - if !exit.status.success() { + let client = create_client(&url); + let solana_args = add_recommended_deployment_solana_args(&client, solana_args)?; + + for retry in 0..(1 + max_retries) { + let exit = std::process::Command::new("solana") + .arg("program") + .arg("deploy") + .arg("--url") + .arg(url.clone()) + .arg("--keypair") + .arg(cfg.provider.wallet.to_string()) + .arg("--program-id") + .arg(program_id.to_string()) + .arg(strip_workspace_prefix(program_filepath.clone())) + .args(&solana_args) + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()) + .output() + .expect("Must deploy"); + if exit.status.success() { + break; + } + println!("There was a problem deploying: {exit:?}."); - std::process::exit(exit.status.code().unwrap_or(1)); + if retry < max_retries { + println!("Retrying {} more time(s)...", max_retries - retry); + } else { + std::process::exit(exit.status.code().unwrap_or(1)); + } } Ok(()) }) @@ -4742,18 +4765,54 @@ fn get_node_version() -> Result { Version::parse(output).map_err(Into::into) } -fn get_recommended_micro_lamport_fee(client: &RpcClient, priority_fee: Option) -> Result { - if let Some(priority_fee) = priority_fee { - return Ok(priority_fee); +fn add_recommended_deployment_solana_args( + client: &RpcClient, + args: Vec, +) -> Result> { + let mut augmented_args = args.clone(); + + // If no priority fee is provided, calculate a recommended fee based on recent txs. + if !args.contains(&"--with-compute-unit-price".to_string()) { + let priority_fee = get_recommended_micro_lamport_fee(&client)?; + augmented_args.push("--with-compute-unit-price".to_string()); + augmented_args.push(priority_fee.to_string()); + } + + const DEFAULT_MAX_SIGN_ATTEMPTS: u8 = 30; + if !args.contains(&"--max-sign-attempts".to_string()) { + augmented_args.push("--max-sign-attempts".to_string()); + augmented_args.push(DEFAULT_MAX_SIGN_ATTEMPTS.to_string()); + } + + // If no buffer keypair is provided, create a temporary one to reuse across deployments. + // This is particularly useful for upgrading larger programs, which suffer from an increased + // likelihood of some write transactions failing during any single deployment. + if !args.contains(&"--buffer".to_owned()) { + let tmp_keypair_path = std::env::temp_dir().join("anchor-upgrade-buffer.json"); + if !tmp_keypair_path.exists() { + if let Err(err) = Keypair::new().write_to_file(&tmp_keypair_path) { + return Err(anyhow!( + "Error creating keypair for buffer account, {:?}", + err + )); + } + } + + augmented_args.push("--buffer".to_owned()); + augmented_args.push(tmp_keypair_path.to_string_lossy().to_string()); } + Ok(augmented_args) +} + +fn get_recommended_micro_lamport_fee(client: &RpcClient) -> Result { let mut fees = client.get_recent_prioritization_fees(&[])?; if fees.is_empty() { // Fees may be empty, e.g. on localnet return Ok(0); } - // Get the median fee from the most recent recent 150 slots' prioritization fee + // Get the median fee from the most recent 150 slots' prioritization fee fees.sort_unstable_by_key(|fee| fee.prioritization_fee); let median_index = fees.len() / 2; @@ -4765,6 +4824,7 @@ fn get_recommended_micro_lamport_fee(client: &RpcClient, priority_fee: Option, ) -> Result> { - let priority_fee = get_recommended_micro_lamport_fee(client, priority_fee)?; + let priority_fee = match priority_fee { + Some(fee) => fee, + None => get_recommended_micro_lamport_fee(client)?, + }; if priority_fee > 0 { let mut instructions_appended = instructions.clone();