diff --git a/cli/src/lib.rs b/cli/src/lib.rs index 4fdf34b0ad..2881de07f4 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -33,6 +33,7 @@ use solana_sdk::bpf_loader; use solana_sdk::bpf_loader_deprecated; use solana_sdk::bpf_loader_upgradeable::{self, UpgradeableLoaderState}; use solana_sdk::commitment_config::CommitmentConfig; +use solana_sdk::compute_budget::ComputeBudgetInstruction; use solana_sdk::pubkey::Pubkey; use solana_sdk::signature::Keypair; use solana_sdk::signature::Signer; @@ -360,6 +361,8 @@ pub enum IdlCommand { program_id: Pubkey, #[clap(short, long)] filepath: String, + #[clap(long)] + priority_fee: Option, }, Close { program_id: Pubkey, @@ -367,6 +370,8 @@ pub enum IdlCommand { /// Useful for multisig execution when the local wallet keypair is not available. #[clap(long)] print_only: bool, + #[clap(long)] + priority_fee: Option, }, /// Writes an IDL into a buffer account. This can be used with SetBuffer /// to perform an upgrade. @@ -374,6 +379,8 @@ pub enum IdlCommand { program_id: Pubkey, #[clap(short, long)] filepath: String, + #[clap(long)] + priority_fee: Option, }, /// Sets a new IDL buffer for the program. SetBuffer { @@ -385,6 +392,8 @@ pub enum IdlCommand { /// Useful for multisig execution when the local wallet keypair is not available. #[clap(long)] print_only: bool, + #[clap(long)] + priority_fee: Option, }, /// Upgrades the IDL to the new file. An alias for first writing and then /// then setting the idl buffer account. @@ -392,6 +401,8 @@ pub enum IdlCommand { program_id: Pubkey, #[clap(short, long)] filepath: String, + #[clap(long)] + priority_fee: Option, }, /// Sets a new authority on the IDL account. SetAuthority { @@ -408,6 +419,8 @@ pub enum IdlCommand { /// Useful for multisig execution when the local wallet keypair is not available. #[clap(long)] print_only: bool, + #[clap(long)] + priority_fee: Option, }, /// Command to remove the ability to modify the IDL account. This should /// likely be used in conjection with eliminating an "upgrade authority" on @@ -415,6 +428,8 @@ pub enum IdlCommand { EraseAuthority { #[clap(short, long)] program_id: Pubkey, + #[clap(long)] + priority_fee: Option, }, /// Outputs the authority for the IDL account. Authority { @@ -1963,31 +1978,47 @@ fn idl(cfg_override: &ConfigOverride, subcmd: IdlCommand) -> Result<()> { IdlCommand::Init { program_id, filepath, - } => idl_init(cfg_override, program_id, filepath), + priority_fee, + } => idl_init(cfg_override, program_id, filepath, priority_fee), IdlCommand::Close { program_id, print_only, - } => idl_close(cfg_override, program_id, print_only), + priority_fee, + } => idl_close(cfg_override, program_id, print_only, priority_fee), IdlCommand::WriteBuffer { program_id, filepath, - } => idl_write_buffer(cfg_override, program_id, filepath).map(|_| ()), + priority_fee, + } => idl_write_buffer(cfg_override, program_id, filepath, priority_fee).map(|_| ()), IdlCommand::SetBuffer { program_id, buffer, print_only, - } => idl_set_buffer(cfg_override, program_id, buffer, print_only), + priority_fee, + } => idl_set_buffer(cfg_override, program_id, buffer, print_only, priority_fee), IdlCommand::Upgrade { program_id, filepath, - } => idl_upgrade(cfg_override, program_id, filepath), + priority_fee, + } => idl_upgrade(cfg_override, program_id, filepath, priority_fee), IdlCommand::SetAuthority { program_id, address, new_authority, print_only, - } => idl_set_authority(cfg_override, program_id, address, new_authority, print_only), - IdlCommand::EraseAuthority { program_id } => idl_erase_authority(cfg_override, program_id), + priority_fee, + } => idl_set_authority( + cfg_override, + program_id, + address, + new_authority, + print_only, + priority_fee, + ), + IdlCommand::EraseAuthority { + program_id, + priority_fee, + } => idl_erase_authority(cfg_override, program_id, priority_fee), IdlCommand::Authority { program_id } => idl_authority(cfg_override, program_id), IdlCommand::Parse { file, @@ -2047,24 +2078,34 @@ fn get_idl_account(client: &RpcClient, idl_address: &Pubkey) -> Result Result<()> { +fn idl_init( + cfg_override: &ConfigOverride, + program_id: Pubkey, + idl_filepath: String, + priority_fee: Option, +) -> Result<()> { with_workspace(cfg_override, |cfg| { let keypair = cfg.provider.wallet.to_string(); let bytes = fs::read(idl_filepath)?; let idl: Idl = serde_json::from_reader(&*bytes)?; - let idl_address = create_idl_account(cfg, &keypair, &program_id, &idl)?; + let idl_address = create_idl_account(cfg, &keypair, &program_id, &idl, priority_fee)?; println!("Idl account created: {idl_address:?}"); Ok(()) }) } -fn idl_close(cfg_override: &ConfigOverride, program_id: Pubkey, print_only: bool) -> Result<()> { +fn idl_close( + cfg_override: &ConfigOverride, + program_id: Pubkey, + print_only: bool, + priority_fee: Option, +) -> Result<()> { with_workspace(cfg_override, |cfg| { let idl_address = IdlAccount::address(&program_id); - idl_close_account(cfg, &program_id, idl_address, print_only)?; + idl_close_account(cfg, &program_id, idl_address, print_only, priority_fee)?; if !print_only { println!("Idl account closed: {idl_address:?}"); @@ -2078,6 +2119,7 @@ fn idl_write_buffer( cfg_override: &ConfigOverride, program_id: Pubkey, idl_filepath: String, + priority_fee: Option, ) -> Result { with_workspace(cfg_override, |cfg| { let keypair = cfg.provider.wallet.to_string(); @@ -2085,8 +2127,8 @@ fn idl_write_buffer( let bytes = fs::read(idl_filepath)?; let idl: Idl = serde_json::from_reader(&*bytes)?; - let idl_buffer = create_idl_buffer(cfg, &keypair, &program_id, &idl)?; - idl_write(cfg, &program_id, &idl, idl_buffer)?; + let idl_buffer = create_idl_buffer(cfg, &keypair, &program_id, &idl, priority_fee)?; + idl_write(cfg, &program_id, &idl, idl_buffer, priority_fee)?; println!("Idl buffer created: {idl_buffer:?}"); @@ -2099,6 +2141,7 @@ fn idl_set_buffer( program_id: Pubkey, buffer: Pubkey, print_only: bool, + priority_fee: Option, ) -> Result<()> { with_workspace(cfg_override, |cfg| { let keypair = solana_sdk::signature::read_keypair_file(&cfg.provider.wallet.to_string()) @@ -2132,16 +2175,34 @@ fn idl_set_buffer( print_idl_instruction("SetBuffer", &ix, &idl_address)?; } else { // Build the transaction. - let latest_hash = client.get_latest_blockhash()?; - let tx = Transaction::new_signed_with_payer( - &[ix], - Some(&keypair.pubkey()), - &[&keypair], - latest_hash, - ); + let instructions = prepend_compute_unit_ix(vec![ix], &client, priority_fee)?; + let mut latest_hash = client.get_latest_blockhash()?; // Send the transaction. - client.send_and_confirm_transaction_with_spinner(&tx)?; + for retries in 0..20 { + if !client.is_blockhash_valid(&latest_hash, client.commitment())? { + latest_hash = client.get_latest_blockhash()?; + } + let tx = Transaction::new_signed_with_payer( + &instructions, + Some(&keypair.pubkey()), + &[&keypair], + latest_hash, + ); + + let tx_hash = client.simulate_transaction(&tx)?.value; + println!("tx_hash, {:?} ", tx_hash); + + match client.send_and_confirm_transaction_with_spinner(&tx) { + Ok(_) => break, + Err(e) => { + if retries == 19 { + return Err(anyhow!("Error: {e}. Failed to send transaction.")); + } + println!("Error: {e}. Retrying transaction."); + } + } + } } Ok(()) @@ -2152,9 +2213,10 @@ fn idl_upgrade( cfg_override: &ConfigOverride, program_id: Pubkey, idl_filepath: String, + priority_fee: Option, ) -> Result<()> { - let buffer = idl_write_buffer(cfg_override, program_id, idl_filepath)?; - idl_set_buffer(cfg_override, program_id, buffer, false) + let buffer = idl_write_buffer(cfg_override, program_id, idl_filepath, priority_fee)?; + idl_set_buffer(cfg_override, program_id, buffer, false, priority_fee) } fn idl_authority(cfg_override: &ConfigOverride, program_id: Pubkey) -> Result<()> { @@ -2184,6 +2246,7 @@ fn idl_set_authority( address: Option, new_authority: Pubkey, print_only: bool, + priority_fee: Option, ) -> Result<()> { with_workspace(cfg_override, |cfg| { // Misc. @@ -2222,10 +2285,12 @@ fn idl_set_authority( if print_only { print_idl_instruction("SetAuthority", &ix, &idl_address)?; } else { + let instructions = prepend_compute_unit_ix(vec![ix], &client, priority_fee)?; + // Send transaction. let latest_hash = client.get_latest_blockhash()?; let tx = Transaction::new_signed_with_payer( - &[ix], + &instructions, Some(&keypair.pubkey()), &[&keypair], latest_hash, @@ -2239,7 +2304,11 @@ fn idl_set_authority( }) } -fn idl_erase_authority(cfg_override: &ConfigOverride, program_id: Pubkey) -> Result<()> { +fn idl_erase_authority( + cfg_override: &ConfigOverride, + program_id: Pubkey, + priority_fee: Option, +) -> Result<()> { println!("Are you sure you want to erase the IDL authority: [y/n]"); let stdin = std::io::stdin(); @@ -2250,7 +2319,14 @@ fn idl_erase_authority(cfg_override: &ConfigOverride, program_id: Pubkey) -> Res return Ok(()); } - idl_set_authority(cfg_override, program_id, None, ERASED_AUTHORITY, false)?; + idl_set_authority( + cfg_override, + program_id, + None, + ERASED_AUTHORITY, + false, + priority_fee, + )?; Ok(()) } @@ -2260,6 +2336,7 @@ fn idl_close_account( program_id: &Pubkey, idl_address: Pubkey, print_only: bool, + priority_fee: Option, ) -> Result<()> { let keypair = solana_sdk::signature::read_keypair_file(&cfg.provider.wallet.to_string()) .map_err(|_| anyhow!("Unable to read keypair file"))?; @@ -2287,10 +2364,12 @@ fn idl_close_account( if print_only { print_idl_instruction("Close", &ix, &idl_address)?; } else { + let instructions = prepend_compute_unit_ix(vec![ix], &client, priority_fee)?; + // Send transaction. let latest_hash = client.get_latest_blockhash()?; let tx = Transaction::new_signed_with_payer( - &[ix], + &instructions, Some(&keypair.pubkey()), &[&keypair], latest_hash, @@ -2304,7 +2383,13 @@ fn idl_close_account( // Write the idl to the account buffer, chopping up the IDL into pieces // and sending multiple transactions in the event the IDL doesn't fit into // a single transaction. -fn idl_write(cfg: &Config, program_id: &Pubkey, idl: &Idl, idl_address: Pubkey) -> Result<()> { +fn idl_write( + cfg: &Config, + program_id: &Pubkey, + idl: &Idl, + idl_address: Pubkey, + priority_fee: Option, +) -> Result<()> { // Remove the metadata before deploy. let mut idl = idl.clone(); idl.metadata = None; @@ -2323,9 +2408,12 @@ fn idl_write(cfg: &Config, program_id: &Pubkey, idl: &Idl, idl_address: Pubkey) e.finish()? }; - const MAX_WRITE_SIZE: usize = 1000; + println!("Idl data length: {:?} bytes", idl_data.len()); + + const MAX_WRITE_SIZE: usize = 800; let mut offset = 0; while offset < idl_data.len() { + println!("Step {offset} "); // Instruction data. let data = { let start = offset; @@ -2346,14 +2434,34 @@ fn idl_write(cfg: &Config, program_id: &Pubkey, idl: &Idl, idl_address: Pubkey) data, }; // Send transaction. - let latest_hash = client.get_latest_blockhash()?; - let tx = Transaction::new_signed_with_payer( - &[ix], - Some(&keypair.pubkey()), - &[&keypair], - latest_hash, - ); - client.send_and_confirm_transaction_with_spinner(&tx)?; + let instructions = prepend_compute_unit_ix(vec![ix], &client, priority_fee)?; + + let mut latest_hash = client.get_latest_blockhash()?; + for retries in 0..20 { + if !client.is_blockhash_valid(&latest_hash, client.commitment())? { + latest_hash = client.get_latest_blockhash()?; + } + let tx = Transaction::new_signed_with_payer( + &instructions, + Some(&keypair.pubkey()), + &[&keypair], + latest_hash, + ); + + let tx_hash = client.simulate_transaction(&tx)?.value; + println!("tx_hash, {:?} ", tx_hash); + + match client.send_and_confirm_transaction_with_spinner(&tx) { + Ok(_) => break, + Err(e) => { + if retries == 19 { + return Err(anyhow!("Error: {e}. Failed to send transaction.")); + } + println!("Error: {e}. Retrying transaction."); + } + } + } + offset += MAX_WRITE_SIZE; } Ok(()) @@ -3630,6 +3738,7 @@ fn create_idl_account( keypair_path: &str, program_id: &Pubkey, idl: &Idl, + priority_fee: Option, ) -> Result { // Misc. let idl_address = IdlAccount::address(program_id); @@ -3684,18 +3793,41 @@ fn create_idl_account( data, }); } - let latest_hash = client.get_latest_blockhash()?; - let tx = Transaction::new_signed_with_payer( - &instructions, - Some(&keypair.pubkey()), - &[&keypair], - latest_hash, - ); - client.send_and_confirm_transaction_with_spinner(&tx)?; + instructions = prepend_compute_unit_ix(instructions, &client, priority_fee)?; + + let mut latest_hash = client.get_latest_blockhash()?; + for retries in 0..20 { + if !client.is_blockhash_valid(&latest_hash, client.commitment())? { + latest_hash = client.get_latest_blockhash()?; + } + + let tx = Transaction::new_signed_with_payer( + &instructions, + Some(&keypair.pubkey()), + &[&keypair], + latest_hash, + ); + + match client.send_and_confirm_transaction_with_spinner(&tx) { + Ok(_) => break, + Err(err) => { + if retries == 19 { + return Err(anyhow!("Error creating IDL account: {}", err)); + } + println!("Error creating IDL account: {}. Retrying...", err); + } + } + } } // Write directly to the IDL account buffer. - idl_write(cfg, program_id, idl, IdlAccount::address(program_id))?; + idl_write( + cfg, + program_id, + idl, + IdlAccount::address(program_id), + priority_fee, + )?; Ok(idl_address) } @@ -3705,6 +3837,7 @@ fn create_idl_buffer( keypair_path: &str, program_id: &Pubkey, idl: &Idl, + priority_fee: Option, ) -> Result { let keypair = solana_sdk::signature::read_keypair_file(keypair_path) .map_err(|_| anyhow!("Unable to read keypair file"))?; @@ -3712,6 +3845,7 @@ fn create_idl_buffer( let client = create_client(url); let buffer = Keypair::new(); + println!("Buffer address, {:?} ", buffer.pubkey()); // Creates the new buffer account with the system program. let create_account_ix = { @@ -3742,17 +3876,33 @@ fn create_idl_buffer( } }; - // Build the transaction. - let latest_hash = client.get_latest_blockhash()?; - let tx = Transaction::new_signed_with_payer( - &[create_account_ix, create_buffer_ix], - Some(&keypair.pubkey()), - &[&keypair, &buffer], - latest_hash, - ); + let instructions = prepend_compute_unit_ix( + vec![create_account_ix, create_buffer_ix], + &client, + priority_fee, + )?; - // Send the transaction. - client.send_and_confirm_transaction_with_spinner(&tx)?; + let mut latest_hash = client.get_latest_blockhash()?; + for retries in 0..20 { + if !client.is_blockhash_valid(&latest_hash, client.commitment())? { + latest_hash = client.get_latest_blockhash()?; + } + let tx = Transaction::new_signed_with_payer( + &instructions, + Some(&keypair.pubkey()), + &[&keypair, &buffer], + latest_hash, + ); + match client.send_and_confirm_transaction_with_spinner(&tx) { + Ok(_) => break, + Err(err) => { + if retries == 19 { + return Err(anyhow!("Error creating buffer account: {}", err)); + } + println!("Error creating buffer account: {}. Retrying...", err); + } + } + } Ok(buffer.pubkey()) } @@ -4371,6 +4521,46 @@ 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); + } + + let mut fees = client.get_recent_prioritization_fees(&[])?; + + // Get the median fee from the most recent recent 150 slots' prioritization fee + fees.sort_unstable_by_key(|fee| fee.prioritization_fee); + let median_index = fees.len() / 2; + + let median_priority_fee = if fees.len() % 2 == 0 { + (fees[median_index - 1].prioritization_fee + fees[median_index].prioritization_fee) / 2 + } else { + fees[median_index].prioritization_fee + }; + + Ok(median_priority_fee) +} +/// Prepend a compute unit ix, if the priority fee is greater than 0. +/// This helps to improve the chances that the transaction will land. +fn prepend_compute_unit_ix( + instructions: Vec, + client: &RpcClient, + priority_fee: Option, +) -> Result> { + let priority_fee = get_recommended_micro_lamport_fee(client, priority_fee)?; + + if priority_fee > 0 { + let mut instructions_appended = instructions.clone(); + instructions_appended.insert( + 0, + ComputeBudgetInstruction::set_compute_unit_price(priority_fee), + ); + Ok(instructions_appended) + } else { + Ok(instructions) + } +} + fn get_node_dns_option() -> Result<&'static str> { let version = get_node_version()?; let req = VersionReq::parse(">=16.4.0").unwrap();