From ab14e33c85a03742ccda4910dd52ccd5013422c2 Mon Sep 17 00:00:00 2001 From: NagaprasadVr Date: Tue, 19 Mar 2024 09:59:14 +0530 Subject: [PATCH 1/8] add set compute units arg for program deploy --- cli/src/program.rs | 297 +++++++++++++++++++++--------------- cli/tests/program.rs | 112 ++++++++++++++ transaction-dos/src/main.rs | 1 + 3 files changed, 291 insertions(+), 119 deletions(-) diff --git a/cli/src/program.rs b/cli/src/program.rs index c35871868f0f04..562ba8328c57b9 100644 --- a/cli/src/program.rs +++ b/cli/src/program.rs @@ -13,6 +13,7 @@ use { solana_bpf_loader_program::syscalls::create_program_runtime_environment_v1, solana_clap_utils::{ self, + compute_unit_price::compute_unit_price_arg, fee_payer::{fee_payer_arg, FEE_PAYER_ARG}, hidden_unless_forced, input_parsers::*, @@ -48,6 +49,7 @@ use { account_utils::StateMut, bpf_loader, bpf_loader_deprecated, bpf_loader_upgradeable::{self, UpgradeableLoaderState}, + compute_budget::ComputeBudgetInstruction, feature_set::FeatureSet, instruction::{Instruction, InstructionError}, loader_instruction, @@ -90,6 +92,7 @@ pub enum ProgramCliCommand { max_len: Option, allow_excessive_balance: bool, skip_fee_check: bool, + compute_unit_price: Option, }, Upgrade { fee_payer_signer_index: SignerIndex, @@ -108,6 +111,7 @@ pub enum ProgramCliCommand { buffer_authority_signer_index: SignerIndex, max_len: Option, skip_fee_check: bool, + compute_unit_price: Option, }, SetBufferAuthority { buffer_pubkey: Pubkey, @@ -236,7 +240,8 @@ impl ProgramSubCommands for App<'_, '_> { "Use the designated program id even if the account already \ holds a large balance of SOL", ), - ), + ) + .arg(compute_unit_price_arg()), ) .subcommand( SubCommand::with_name("upgrade") @@ -308,7 +313,8 @@ impl ProgramSubCommands for App<'_, '_> { "Maximum length of the upgradeable program \ [default: the length of the original deployed program]", ), - ), + ) + .arg(compute_unit_price_arg()), ) .subcommand( SubCommand::with_name("set-buffer-authority") @@ -601,6 +607,8 @@ pub fn parse_program_subcommand( let signer_info = default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?; + let compute_unit_price = value_of(matches, "compute_unit_price"); + CliCommandInfo { command: CliCommand::Program(ProgramCliCommand::Deploy { program_location, @@ -616,6 +624,7 @@ pub fn parse_program_subcommand( max_len, allow_excessive_balance: matches.is_present("allow_excessive_balance"), skip_fee_check, + compute_unit_price, }), signers: signer_info.signers, } @@ -687,6 +696,8 @@ pub fn parse_program_subcommand( let signer_info = default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?; + let compute_unit_price = value_of(matches, "compute_unit_price"); + CliCommandInfo { command: CliCommand::Program(ProgramCliCommand::WriteBuffer { program_location: matches.value_of("program_location").unwrap().to_string(), @@ -698,6 +709,7 @@ pub fn parse_program_subcommand( .unwrap(), max_len, skip_fee_check, + compute_unit_price, }), signers: signer_info.signers, } @@ -899,6 +911,7 @@ pub fn process_program_subcommand( max_len, allow_excessive_balance, skip_fee_check, + compute_unit_price, } => process_program_deploy( rpc_client, config, @@ -913,6 +926,7 @@ pub fn process_program_subcommand( *max_len, *allow_excessive_balance, *skip_fee_check, + *compute_unit_price, ), ProgramCliCommand::Upgrade { fee_payer_signer_index, @@ -941,6 +955,7 @@ pub fn process_program_subcommand( buffer_authority_signer_index, max_len, skip_fee_check, + compute_unit_price, } => process_write_buffer( rpc_client, config, @@ -951,6 +966,7 @@ pub fn process_program_subcommand( *buffer_authority_signer_index, *max_len, *skip_fee_check, + *compute_unit_price, ), ProgramCliCommand::SetBufferAuthority { buffer_pubkey, @@ -1082,6 +1098,7 @@ fn process_program_deploy( max_len: Option, allow_excessive_balance: bool, skip_fee_check: bool, + compute_unit_price: Option, ) -> ProcessResult { let fee_payer_signer = config.signers[fee_payer_signer_index]; let upgrade_authority_signer = config.signers[upgrade_authority_signer_index]; @@ -1221,6 +1238,7 @@ fn process_program_deploy( upgrade_authority_signer, allow_excessive_balance, skip_fee_check, + compute_unit_price, ) } else { do_process_program_upgrade( @@ -1235,6 +1253,7 @@ fn process_program_deploy( &buffer_pubkey, buffer_signer, skip_fee_check, + compute_unit_price, ) }; if result.is_ok() && is_final { @@ -1382,6 +1401,7 @@ fn process_write_buffer( buffer_authority_signer_index: SignerIndex, max_len: Option, skip_fee_check: bool, + compute_unit_price: Option, ) -> ProcessResult { let fee_payer_signer = config.signers[fee_payer_signer_index]; let buffer_authority = config.signers[buffer_authority_signer_index]; @@ -1447,6 +1467,7 @@ fn process_write_buffer( buffer_authority, true, skip_fee_check, + compute_unit_price, ); if result.is_err() && buffer_signer_index.is_none() && buffer_signer.is_some() { report_ephemeral_mnemonic(words, mnemonic); @@ -2200,16 +2221,16 @@ fn do_process_program_write_and_deploy( buffer_authority_signer: &dyn Signer, allow_excessive_balance: bool, skip_fee_check: bool, + compute_unit_price: Option, ) -> ProcessResult { let blockhash = rpc_client.get_latest_blockhash()?; // Initialize buffer account or complete if already partially initialized - let (initial_instructions, balance_needed, buffer_program_data) = if let Some(mut account) = - rpc_client - .get_account_with_commitment(buffer_pubkey, config.commitment)? - .value + let (ixs, balance_needed) = if let Some(account) = rpc_client + .get_account_with_commitment(buffer_pubkey, config.commitment)? + .value { - let (ixs, balance_needed) = complete_partial_program_init( + complete_partial_program_init( loader_id, &fee_payer_signer.pubkey(), buffer_pubkey, @@ -2221,11 +2242,7 @@ fn do_process_program_write_and_deploy( }, min_rent_exempt_program_data_balance, allow_excessive_balance, - )?; - let buffer_program_data = account - .data - .split_off(UpgradeableLoaderState::size_of_buffer_metadata()); - (ixs, balance_needed, buffer_program_data) + )? } else if loader_id == &bpf_loader_upgradeable::id() { ( bpf_loader_upgradeable::create_buffer( @@ -2236,7 +2253,6 @@ fn do_process_program_write_and_deploy( program_len, )?, min_rent_exempt_program_data_balance, - vec![0; program_len], ) } else { ( @@ -2248,9 +2264,13 @@ fn do_process_program_write_and_deploy( loader_id, )], min_rent_exempt_program_data_balance, - vec![0; program_len], ) }; + + let mut initial_instructions: Vec = Vec::new(); + + set_compute_budget_ixs_if_needed(&mut initial_instructions, &compute_unit_price); + initial_instructions.extend(ixs); let initial_message = if !initial_instructions.is_empty() { Some(Message::new_with_blockhash( &initial_instructions, @@ -2263,7 +2283,8 @@ fn do_process_program_write_and_deploy( // Create and add write messages let create_msg = |offset: u32, bytes: Vec| { - let instruction = if loader_id == &bpf_loader_upgradeable::id() { + let mut write_ixs: Vec = Vec::new(); + let ix_to_add = if loader_id == &bpf_loader_upgradeable::id() { bpf_loader_upgradeable::write( buffer_pubkey, &buffer_authority_signer.pubkey(), @@ -2273,41 +2294,42 @@ fn do_process_program_write_and_deploy( } else { loader_instruction::write(buffer_pubkey, loader_id, offset, bytes) }; - Message::new_with_blockhash(&[instruction], Some(&fee_payer_signer.pubkey()), &blockhash) + + set_compute_budget_ixs_if_needed(&mut write_ixs, &compute_unit_price); + write_ixs.push(ix_to_add); + Message::new_with_blockhash(&write_ixs, Some(&fee_payer_signer.pubkey()), &blockhash) }; let mut write_messages = vec![]; let chunk_size = calculate_max_chunk_size(&create_msg); for (chunk, i) in program_data.chunks(chunk_size).zip(0..) { - let offset = i * chunk_size; - if chunk != &buffer_program_data[offset..offset + chunk.len()] { - write_messages.push(create_msg(offset as u32, chunk.to_vec())); - } + write_messages.push(create_msg((i * chunk_size) as u32, chunk.to_vec())); } // Create and add final message let final_message = if let Some(program_signers) = program_signers { + let mut final_ixs: Vec = Vec::new(); + let message = if loader_id == &bpf_loader_upgradeable::id() { - Message::new_with_blockhash( - &bpf_loader_upgradeable::deploy_with_max_program_len( - &fee_payer_signer.pubkey(), - &program_signers[0].pubkey(), - buffer_pubkey, - &program_signers[1].pubkey(), - rpc_client.get_minimum_balance_for_rent_exemption( - UpgradeableLoaderState::size_of_program(), - )?, - program_data_max_len, + let ixs_to_add = bpf_loader_upgradeable::deploy_with_max_program_len( + &fee_payer_signer.pubkey(), + &program_signers[0].pubkey(), + buffer_pubkey, + &program_signers[1].pubkey(), + rpc_client.get_minimum_balance_for_rent_exemption( + UpgradeableLoaderState::size_of_program(), )?, - Some(&fee_payer_signer.pubkey()), - &blockhash, - ) + program_data_max_len, + )?; + + set_compute_budget_ixs_if_needed(&mut final_ixs, &compute_unit_price); + final_ixs.extend(ixs_to_add); + Message::new_with_blockhash(&final_ixs, Some(&fee_payer_signer.pubkey()), &blockhash) } else { - Message::new_with_blockhash( - &[loader_instruction::finalize(buffer_pubkey, loader_id)], - Some(&fee_payer_signer.pubkey()), - &blockhash, - ) + set_compute_budget_ixs_if_needed(&mut final_ixs, &compute_unit_price); + + final_ixs.push(loader_instruction::finalize(buffer_pubkey, loader_id)); + Message::new_with_blockhash(&final_ixs, Some(&fee_payer_signer.pubkey()), &blockhash) }; Some(message) } else { @@ -2365,96 +2387,95 @@ fn do_process_program_upgrade( buffer_pubkey: &Pubkey, buffer_signer: Option<&dyn Signer>, skip_fee_check: bool, + compute_unit_price: Option, ) -> ProcessResult { let blockhash = rpc_client.get_latest_blockhash()?; - let (initial_message, write_messages, balance_needed) = - if let Some(buffer_signer) = buffer_signer { - // Check Buffer account to see if partial initialization has occurred - let (initial_instructions, balance_needed, buffer_program_data) = - if let Some(mut account) = rpc_client - .get_account_with_commitment(&buffer_signer.pubkey(), config.commitment)? - .value - { - let (ixs, balance_needed) = complete_partial_program_init( - &bpf_loader_upgradeable::id(), - &fee_payer_signer.pubkey(), - &buffer_signer.pubkey(), - &account, - UpgradeableLoaderState::size_of_buffer(program_len), - min_rent_exempt_program_data_balance, - true, - )?; - let buffer_program_data = account - .data - .split_off(UpgradeableLoaderState::size_of_buffer_metadata()); - (ixs, balance_needed, buffer_program_data) - } else { - ( - bpf_loader_upgradeable::create_buffer( - &fee_payer_signer.pubkey(), - buffer_pubkey, - &upgrade_authority.pubkey(), - min_rent_exempt_program_data_balance, - program_len, - )?, - min_rent_exempt_program_data_balance, - vec![0; program_len], - ) - }; - - let initial_message = if !initial_instructions.is_empty() { - Some(Message::new_with_blockhash( - &initial_instructions, - Some(&fee_payer_signer.pubkey()), - &blockhash, - )) - } else { - None - }; + let (initial_message, write_messages, balance_needed) = if let Some(buffer_signer) = + buffer_signer + { + // Check Buffer account to see if partial initialization has occurred + let (ixs, balance_needed) = if let Some(account) = rpc_client + .get_account_with_commitment(&buffer_signer.pubkey(), config.commitment)? + .value + { + complete_partial_program_init( + &bpf_loader_upgradeable::id(), + &fee_payer_signer.pubkey(), + &buffer_signer.pubkey(), + &account, + UpgradeableLoaderState::size_of_buffer(program_len), + min_rent_exempt_program_data_balance, + true, + )? + } else { + ( + bpf_loader_upgradeable::create_buffer( + &fee_payer_signer.pubkey(), + buffer_pubkey, + &upgrade_authority.pubkey(), + min_rent_exempt_program_data_balance, + program_len, + )?, + min_rent_exempt_program_data_balance, + ) + }; + let mut initial_instructions: Vec = Vec::new(); - let buffer_signer_pubkey = buffer_signer.pubkey(); - let upgrade_authority_pubkey = upgrade_authority.pubkey(); - let create_msg = |offset: u32, bytes: Vec| { - let instruction = bpf_loader_upgradeable::write( - &buffer_signer_pubkey, - &upgrade_authority_pubkey, - offset, - bytes, - ); - Message::new_with_blockhash( - &[instruction], - Some(&fee_payer_signer.pubkey()), - &blockhash, - ) - }; + set_compute_budget_ixs_if_needed(&mut initial_instructions, &compute_unit_price); + initial_instructions.extend(ixs); + let initial_message = if !initial_instructions.is_empty() { + Some(Message::new_with_blockhash( + &initial_instructions, + Some(&fee_payer_signer.pubkey()), + &blockhash, + )) + } else { + None + }; - // Create and add write messages - let mut write_messages = vec![]; - let chunk_size = calculate_max_chunk_size(&create_msg); - for (chunk, i) in program_data.chunks(chunk_size).zip(0..) { - let offset = i * chunk_size; - if chunk != &buffer_program_data[offset..offset + chunk.len()] { - write_messages.push(create_msg(offset as u32, chunk.to_vec())); - } - } + let buffer_signer_pubkey = buffer_signer.pubkey(); + let upgrade_authority_pubkey = upgrade_authority.pubkey(); + let create_msg = |offset: u32, bytes: Vec| { + let mut write_ixs: Vec = Vec::new(); + let ix_to_add = bpf_loader_upgradeable::write( + &buffer_signer_pubkey, + &upgrade_authority_pubkey, + offset, + bytes, + ); - (initial_message, write_messages, balance_needed) - } else { - (None, vec![], 0) + set_compute_budget_ixs_if_needed(&mut write_ixs, &compute_unit_price); + write_ixs.push(ix_to_add); + + Message::new_with_blockhash(&write_ixs, Some(&fee_payer_signer.pubkey()), &blockhash) }; + // Create and add write messages + let mut write_messages = vec![]; + let chunk_size = calculate_max_chunk_size(&create_msg); + for (chunk, i) in program_data.chunks(chunk_size).zip(0..) { + write_messages.push(create_msg((i * chunk_size) as u32, chunk.to_vec())); + } + + (initial_message, write_messages, balance_needed) + } else { + (None, vec![], 0) + }; + // Create and add final message - let final_message = Message::new_with_blockhash( - &[bpf_loader_upgradeable::upgrade( - program_id, - buffer_pubkey, - &upgrade_authority.pubkey(), - &fee_payer_signer.pubkey(), - )], - Some(&fee_payer_signer.pubkey()), - &blockhash, - ); + let mut final_ixs: Vec = Vec::new(); + + set_compute_budget_ixs_if_needed(&mut final_ixs, &compute_unit_price); + + final_ixs.push(bpf_loader_upgradeable::upgrade( + program_id, + buffer_pubkey, + &upgrade_authority.pubkey(), + &fee_payer_signer.pubkey(), + )); + let final_message = + Message::new_with_blockhash(&final_ixs, Some(&fee_payer_signer.pubkey()), &blockhash); let final_message = Some(final_message); if !skip_fee_check { @@ -2650,6 +2671,7 @@ fn send_deploy_messages( .send_and_confirm_messages_with_spinner( write_messages, &[fee_payer_signer, write_signer], + ), ConnectionCache::Quic(cache) => { let tpu_client_fut = solana_client::nonblocking::tpu_client::TpuClient::new_with_connection_cache( @@ -2740,6 +2762,30 @@ fn report_ephemeral_mnemonic(words: usize, mnemonic: bip39::Mnemonic) { eprintln!("[BUFFER_ACCOUNT_ADDRESS] argument to `solana program close`.\n{divider}"); } +fn set_compute_budget_ixs_if_needed(ixs: &mut Vec, compute_unit_price: &Option) { + let mut compute_units_required_for_ixs: u32 = 0; + for ix in ixs.iter() { + if ix.program_id == bpf_loader_upgradeable::id() { + compute_units_required_for_ixs += 2370u32; + } else if ix.program_id == system_program::id() { + compute_units_required_for_ixs += 150u32; + } else { + compute_units_required_for_ixs += 2000u32; + } + } + + let compute_units_for_compute_budget_ixs = 300u32; + + if let Some(compute_unit_price) = compute_unit_price { + ixs.push(ComputeBudgetInstruction::set_compute_unit_limit( + compute_units_required_for_ixs + compute_units_for_compute_budget_ixs, + )); + ixs.push(ComputeBudgetInstruction::set_compute_unit_price( + *compute_unit_price, + )); + } +} + #[cfg(test)] mod tests { use { @@ -2798,6 +2844,7 @@ mod tests { max_len: None, allow_excessive_balance: false, skip_fee_check: false, + compute_unit_price: None }), signers: vec![Box::new(read_keypair_file(&keypair_file).unwrap())], } @@ -2826,6 +2873,7 @@ mod tests { max_len: Some(42), allow_excessive_balance: false, skip_fee_check: false, + compute_unit_price: None }), signers: vec![Box::new(read_keypair_file(&keypair_file).unwrap())], } @@ -2856,6 +2904,7 @@ mod tests { max_len: None, allow_excessive_balance: false, skip_fee_check: false, + compute_unit_price: None }), signers: vec![ Box::new(read_keypair_file(&keypair_file).unwrap()), @@ -2888,6 +2937,7 @@ mod tests { max_len: None, allow_excessive_balance: false, skip_fee_check: false, + compute_unit_price: None }), signers: vec![Box::new(read_keypair_file(&keypair_file).unwrap())], } @@ -2919,6 +2969,7 @@ mod tests { max_len: None, allow_excessive_balance: false, skip_fee_check: false, + compute_unit_price: None }), signers: vec![ Box::new(read_keypair_file(&keypair_file).unwrap()), @@ -2953,6 +3004,7 @@ mod tests { max_len: None, allow_excessive_balance: false, skip_fee_check: false, + compute_unit_price: None }), signers: vec![ Box::new(read_keypair_file(&keypair_file).unwrap()), @@ -2983,6 +3035,7 @@ mod tests { max_len: None, skip_fee_check: false, allow_excessive_balance: false, + compute_unit_price: None }), signers: vec![Box::new(read_keypair_file(&keypair_file).unwrap())], } @@ -3017,6 +3070,7 @@ mod tests { buffer_authority_signer_index: 0, max_len: None, skip_fee_check: false, + compute_unit_price: None }), signers: vec![Box::new(read_keypair_file(&keypair_file).unwrap())], } @@ -3042,6 +3096,7 @@ mod tests { buffer_authority_signer_index: 0, max_len: Some(42), skip_fee_check: false, + compute_unit_price: None }), signers: vec![Box::new(read_keypair_file(&keypair_file).unwrap())], } @@ -3070,6 +3125,7 @@ mod tests { buffer_authority_signer_index: 0, max_len: None, skip_fee_check: false, + compute_unit_price: None }), signers: vec![ Box::new(read_keypair_file(&keypair_file).unwrap()), @@ -3101,6 +3157,7 @@ mod tests { buffer_authority_signer_index: 1, max_len: None, skip_fee_check: false, + compute_unit_price: None }), signers: vec![ Box::new(read_keypair_file(&keypair_file).unwrap()), @@ -3137,6 +3194,7 @@ mod tests { buffer_authority_signer_index: 2, max_len: None, skip_fee_check: false, + compute_unit_price: None }), signers: vec![ Box::new(read_keypair_file(&keypair_file).unwrap()), @@ -3695,6 +3753,7 @@ mod tests { max_len: None, allow_excessive_balance: false, skip_fee_check: false, + compute_unit_price: None, }), signers: vec![&default_keypair], output_format: OutputFormat::JsonCompact, diff --git a/cli/tests/program.rs b/cli/tests/program.rs index 6eb281d65b9e35..9ec7091995375a 100644 --- a/cli/tests/program.rs +++ b/cli/tests/program.rs @@ -19,6 +19,7 @@ use { commitment_config::CommitmentConfig, pubkey::Pubkey, signature::{Keypair, NullSigner, Signer}, + signer::EncodableKey, }, solana_streamer::socket::SocketAddrSpace, solana_test_validator::TestValidator, @@ -28,6 +29,7 @@ use { io::Read, path::{Path, PathBuf}, str::FromStr, + time::Instant, }, test_case::test_case, }; @@ -85,6 +87,7 @@ fn test_cli_program_deploy_non_upgradeable() { is_final: true, max_len: None, skip_fee_check: false, + compute_unit_price: None, }); config.output_format = OutputFormat::JsonCompact; let response = process_command(&config); @@ -131,6 +134,7 @@ fn test_cli_program_deploy_non_upgradeable() { is_final: true, max_len: None, skip_fee_check: false, + compute_unit_price: None, }); process_command(&config).unwrap(); let account1 = rpc_client @@ -186,6 +190,7 @@ fn test_cli_program_deploy_non_upgradeable() { is_final: true, max_len: None, skip_fee_check: false, + compute_unit_price: None, }); let err = process_command(&config).unwrap_err(); assert_eq!( @@ -209,6 +214,7 @@ fn test_cli_program_deploy_non_upgradeable() { is_final: true, max_len: None, skip_fee_check: false, + compute_unit_price: None, }); process_command(&config).unwrap_err(); } @@ -270,6 +276,7 @@ fn test_cli_program_deploy_no_authority() { is_final: true, max_len: None, skip_fee_check: false, + compute_unit_price: None, }); config.output_format = OutputFormat::JsonCompact; let response = process_command(&config); @@ -297,6 +304,7 @@ fn test_cli_program_deploy_no_authority() { is_final: false, max_len: None, skip_fee_check: false, + compute_unit_price: None, }); process_command(&config).unwrap_err(); } @@ -359,6 +367,7 @@ fn test_cli_program_deploy_with_authority() { is_final: false, max_len: Some(max_len), skip_fee_check: false, + compute_unit_price: None, }); config.output_format = OutputFormat::JsonCompact; let response = process_command(&config); @@ -408,6 +417,7 @@ fn test_cli_program_deploy_with_authority() { is_final: false, max_len: Some(max_len), skip_fee_check: false, + compute_unit_price: None, }); let response = process_command(&config); let json: Value = serde_json::from_str(&response.unwrap()).unwrap(); @@ -451,6 +461,7 @@ fn test_cli_program_deploy_with_authority() { is_final: false, max_len: Some(max_len), skip_fee_check: false, + compute_unit_price: None, }); process_command(&config).unwrap(); let program_account = rpc_client.get_account(&program_pubkey).unwrap(); @@ -526,6 +537,7 @@ fn test_cli_program_deploy_with_authority() { is_final: false, max_len: None, skip_fee_check: false, + compute_unit_price: None, }); process_command(&config).unwrap(); let program_account = rpc_client.get_account(&program_pubkey).unwrap(); @@ -605,6 +617,7 @@ fn test_cli_program_deploy_with_authority() { is_final: false, max_len: None, skip_fee_check: false, + compute_unit_price: None, }); process_command(&config).unwrap_err(); @@ -622,6 +635,7 @@ fn test_cli_program_deploy_with_authority() { is_final: true, max_len: None, skip_fee_check: false, + compute_unit_price: None, }); let response = process_command(&config); let json: Value = serde_json::from_str(&response.unwrap()).unwrap(); @@ -726,6 +740,7 @@ fn test_cli_program_close_program() { is_final: false, max_len: Some(max_len), skip_fee_check: false, + compute_unit_price: None, }); config.output_format = OutputFormat::JsonCompact; process_command(&config).unwrap(); @@ -836,6 +851,7 @@ fn test_cli_program_extend_program() { is_final: false, max_len: None, // Use None to check that it defaults to the max length skip_fee_check: false, + compute_unit_price: None, }); config.output_format = OutputFormat::JsonCompact; process_command(&config).unwrap(); @@ -883,6 +899,7 @@ fn test_cli_program_extend_program() { is_final: false, max_len: None, skip_fee_check: false, + compute_unit_price: None, }); process_command(&config).unwrap_err(); @@ -915,6 +932,7 @@ fn test_cli_program_extend_program() { is_final: false, max_len: None, skip_fee_check: false, + compute_unit_price: None, }); process_command(&config).unwrap(); } @@ -979,6 +997,8 @@ fn test_cli_program_write_buffer() { buffer_authority_signer_index: 0, max_len: None, skip_fee_check: false, + + compute_unit_price: None, }); config.output_format = OutputFormat::JsonCompact; let response = process_command(&config); @@ -1015,6 +1035,8 @@ fn test_cli_program_write_buffer() { buffer_authority_signer_index: 0, max_len: Some(max_len), skip_fee_check: false, + + compute_unit_price: None, }); let response = process_command(&config); let json: Value = serde_json::from_str(&response.unwrap()).unwrap(); @@ -1078,6 +1100,8 @@ fn test_cli_program_write_buffer() { buffer_authority_signer_index: 2, max_len: None, skip_fee_check: false, + + compute_unit_price: None, }); let response = process_command(&config); let json: Value = serde_json::from_str(&response.unwrap()).unwrap(); @@ -1117,6 +1141,8 @@ fn test_cli_program_write_buffer() { buffer_authority_signer_index: 2, max_len: None, skip_fee_check: false, + + compute_unit_price: None, }); let response = process_command(&config); let json: Value = serde_json::from_str(&response.unwrap()).unwrap(); @@ -1192,6 +1218,8 @@ fn test_cli_program_write_buffer() { buffer_authority_signer_index: 0, max_len: None, skip_fee_check: false, + + compute_unit_price: None, }); config.output_format = OutputFormat::JsonCompact; let response = process_command(&config); @@ -1234,6 +1262,8 @@ fn test_cli_program_write_buffer() { buffer_authority_signer_index: 0, max_len: None, //Some(max_len), skip_fee_check: false, + + compute_unit_price: None, }); process_command(&config).unwrap(); config.signers = vec![&keypair, &buffer_keypair]; @@ -1249,6 +1279,7 @@ fn test_cli_program_write_buffer() { is_final: true, max_len: None, skip_fee_check: false, + compute_unit_price: None, }); config.output_format = OutputFormat::JsonCompact; let error = process_command(&config).unwrap_err(); @@ -1308,6 +1339,8 @@ fn test_cli_program_set_buffer_authority() { buffer_authority_signer_index: 0, max_len: None, skip_fee_check: false, + + compute_unit_price: None, }); process_command(&config).unwrap(); let buffer_account = rpc_client.get_account(&buffer_keypair.pubkey()).unwrap(); @@ -1360,6 +1393,7 @@ fn test_cli_program_set_buffer_authority() { is_final: false, max_len: None, skip_fee_check: false, + compute_unit_price: None, }); config.output_format = OutputFormat::JsonCompact; process_command(&config).unwrap_err(); @@ -1405,6 +1439,7 @@ fn test_cli_program_set_buffer_authority() { is_final: false, max_len: None, skip_fee_check: false, + compute_unit_price: None, }); config.output_format = OutputFormat::JsonCompact; process_command(&config).unwrap(); @@ -1461,6 +1496,8 @@ fn test_cli_program_mismatch_buffer_authority() { buffer_authority_signer_index: 2, max_len: None, skip_fee_check: false, + + compute_unit_price: None, }); process_command(&config).unwrap(); let buffer_account = rpc_client.get_account(&buffer_keypair.pubkey()).unwrap(); @@ -1485,6 +1522,7 @@ fn test_cli_program_mismatch_buffer_authority() { is_final: true, max_len: None, skip_fee_check: false, + compute_unit_price: None, }); process_command(&config).unwrap_err(); @@ -1502,6 +1540,7 @@ fn test_cli_program_mismatch_buffer_authority() { is_final: true, max_len: None, skip_fee_check: false, + compute_unit_price: None, }); process_command(&config).unwrap(); } @@ -1585,6 +1624,7 @@ fn test_cli_program_deploy_with_offline_signing(use_offline_signer_as_fee_payer: is_final: false, max_len: Some(max_program_data_len), // allows for larger program size with future upgrades skip_fee_check: false, + compute_unit_price: None, }); config.output_format = OutputFormat::JsonCompact; process_command(&config).unwrap(); @@ -1752,6 +1792,8 @@ fn test_cli_program_show() { buffer_authority_signer_index: 2, max_len: None, skip_fee_check: false, + + compute_unit_price: None, }); process_command(&config).unwrap(); @@ -1813,6 +1855,7 @@ fn test_cli_program_show() { is_final: false, max_len: Some(max_len), skip_fee_check: false, + compute_unit_price: None, }); config.output_format = OutputFormat::JsonCompact; let min_slot = rpc_client.get_slot().unwrap(); @@ -1941,6 +1984,8 @@ fn test_cli_program_dump() { buffer_authority_signer_index: 2, max_len: None, skip_fee_check: false, + + compute_unit_price: None, }); process_command(&config).unwrap(); @@ -1984,6 +2029,7 @@ fn create_buffer_with_offline_authority<'a>( buffer_authority_signer_index: 0, max_len: None, skip_fee_check: false, + compute_unit_price: None, }); process_command(config).unwrap(); let buffer_account = rpc_client.get_account(&buffer_signer.pubkey()).unwrap(); @@ -2009,3 +2055,69 @@ fn create_buffer_with_offline_authority<'a>( panic!("not a buffer account"); } } + +fn program_deploy_with_args(compute_unit_price: Option) { + solana_logger::setup(); + + let mut noop_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + noop_path.push("tests"); + noop_path.push("fixtures"); + noop_path.push("noop"); + noop_path.set_extension("so"); + + let mut file = File::open(noop_path.to_str().unwrap()).unwrap(); + let mut program_data = Vec::new(); + file.read_to_end(&mut program_data).unwrap(); + + let mut config = CliConfig::recent_for_tests(); + let keypair = Keypair::read_from_file(&config.keypair_path).unwrap(); + + let upgrade_authority = Keypair::new(); + + config.json_rpc_url = "https://api.devnet.solana.com".to_string(); + + config.signers = vec![&keypair]; + + // Deploy a program + config.signers = vec![&keypair, &upgrade_authority]; + config.command = CliCommand::Program(ProgramCliCommand::Deploy { + program_location: Some(noop_path.to_str().unwrap().to_string()), + fee_payer_signer_index: 0, + program_signer_index: None, + program_pubkey: None, + buffer_signer_index: None, + buffer_pubkey: None, + allow_excessive_balance: false, + upgrade_authority_signer_index: 1, + is_final: true, + max_len: None, + skip_fee_check: false, + compute_unit_price, + }); + config.output_format = OutputFormat::JsonCompact; + let start = Instant::now(); + let response = process_command(&config); + let duration = start.elapsed(); + println!("Time elapsed: {:?}", duration); + let json: Value = serde_json::from_str(&response.unwrap()).unwrap(); + let program_pubkey_str = json + .as_object() + .unwrap() + .get("programId") + .unwrap() + .as_str() + .unwrap(); + println!( + "Program deployed successfully with id: {}", + program_pubkey_str + ); +} + +#[test] +fn test_cli_program_deploy_with_compute_unit_price() { + //test without compute_unit_price + program_deploy_with_args(None); + + //test with 1000 micro lamports as compute_unit_price + program_deploy_with_args(Some(1000)); +} diff --git a/transaction-dos/src/main.rs b/transaction-dos/src/main.rs index dedbcdab27ef79..7d62ec92a58ef7 100644 --- a/transaction-dos/src/main.rs +++ b/transaction-dos/src/main.rs @@ -247,6 +247,7 @@ fn run_transactions_dos( upgrade_authority_signer_index: 0, is_final: true, max_len: None, + compute_unit_price, skip_fee_check: true, // skip_fee_check }); From d36c45139c52282f0b89392ff5dee5d5466f1bcd Mon Sep 17 00:00:00 2001 From: NagaprasadVr Date: Tue, 19 Mar 2024 19:24:17 +0530 Subject: [PATCH 2/8] update master changes --- cli/src/program.rs | 43 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 35 insertions(+), 8 deletions(-) diff --git a/cli/src/program.rs b/cli/src/program.rs index 562ba8328c57b9..b0cefc3321e363 100644 --- a/cli/src/program.rs +++ b/cli/src/program.rs @@ -2226,11 +2226,11 @@ fn do_process_program_write_and_deploy( let blockhash = rpc_client.get_latest_blockhash()?; // Initialize buffer account or complete if already partially initialized - let (ixs, balance_needed) = if let Some(account) = rpc_client + let (ixs, balance_needed, buffer_program_data) = if let Some(mut account) = rpc_client .get_account_with_commitment(buffer_pubkey, config.commitment)? .value { - complete_partial_program_init( + let (ixs, balance_needed) = complete_partial_program_init( loader_id, &fee_payer_signer.pubkey(), buffer_pubkey, @@ -2242,7 +2242,11 @@ fn do_process_program_write_and_deploy( }, min_rent_exempt_program_data_balance, allow_excessive_balance, - )? + )?; + let buffer_program_data = account + .data + .split_off(UpgradeableLoaderState::size_of_buffer_metadata()); + (ixs, balance_needed, buffer_program_data) } else if loader_id == &bpf_loader_upgradeable::id() { ( bpf_loader_upgradeable::create_buffer( @@ -2253,6 +2257,7 @@ fn do_process_program_write_and_deploy( program_len, )?, min_rent_exempt_program_data_balance, + vec![0; program_len], ) } else { ( @@ -2264,6 +2269,7 @@ fn do_process_program_write_and_deploy( loader_id, )], min_rent_exempt_program_data_balance, + vec![0; program_len], ) }; @@ -2303,7 +2309,10 @@ fn do_process_program_write_and_deploy( let mut write_messages = vec![]; let chunk_size = calculate_max_chunk_size(&create_msg); for (chunk, i) in program_data.chunks(chunk_size).zip(0..) { - write_messages.push(create_msg((i * chunk_size) as u32, chunk.to_vec())); + let offset = i * chunk_size; + if chunk != &buffer_program_data[offset..offset + chunk.len()] { + write_messages.push(create_msg(offset as u32, chunk.to_vec())); + } } // Create and add final message @@ -2395,11 +2404,11 @@ fn do_process_program_upgrade( buffer_signer { // Check Buffer account to see if partial initialization has occurred - let (ixs, balance_needed) = if let Some(account) = rpc_client + let (ixs, balance_needed, buffer_program_data) = if let Some(mut account) = rpc_client .get_account_with_commitment(&buffer_signer.pubkey(), config.commitment)? .value { - complete_partial_program_init( + let (ixs, balance_needed) = complete_partial_program_init( &bpf_loader_upgradeable::id(), &fee_payer_signer.pubkey(), &buffer_signer.pubkey(), @@ -2407,7 +2416,11 @@ fn do_process_program_upgrade( UpgradeableLoaderState::size_of_buffer(program_len), min_rent_exempt_program_data_balance, true, - )? + )?; + let buffer_program_data = account + .data + .split_off(UpgradeableLoaderState::size_of_buffer_metadata()); + (ixs, balance_needed, buffer_program_data) } else { ( bpf_loader_upgradeable::create_buffer( @@ -2418,8 +2431,19 @@ fn do_process_program_upgrade( program_len, )?, min_rent_exempt_program_data_balance, + vec![0; program_len], ) }; + + let initial_message = if !ixs.is_empty() { + Some(Message::new_with_blockhash( + &ixs, + Some(&fee_payer_signer.pubkey()), + &blockhash, + )) + } else { + None + }; let mut initial_instructions: Vec = Vec::new(); set_compute_budget_ixs_if_needed(&mut initial_instructions, &compute_unit_price); @@ -2455,7 +2479,10 @@ fn do_process_program_upgrade( let mut write_messages = vec![]; let chunk_size = calculate_max_chunk_size(&create_msg); for (chunk, i) in program_data.chunks(chunk_size).zip(0..) { - write_messages.push(create_msg((i * chunk_size) as u32, chunk.to_vec())); + let offset = i * chunk_size; + if chunk != &buffer_program_data[offset..offset + chunk.len()] { + write_messages.push(create_msg(offset as u32, chunk.to_vec())); + } } (initial_message, write_messages, balance_needed) From 9587c2a8940b098ba84a311f5b7c358ac6da0bee Mon Sep 17 00:00:00 2001 From: NagaprasadVr Date: Tue, 19 Mar 2024 19:37:10 +0530 Subject: [PATCH 3/8] remove duplicates --- cli/src/program.rs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/cli/src/program.rs b/cli/src/program.rs index b0cefc3321e363..888598963f2f36 100644 --- a/cli/src/program.rs +++ b/cli/src/program.rs @@ -2435,15 +2435,6 @@ fn do_process_program_upgrade( ) }; - let initial_message = if !ixs.is_empty() { - Some(Message::new_with_blockhash( - &ixs, - Some(&fee_payer_signer.pubkey()), - &blockhash, - )) - } else { - None - }; let mut initial_instructions: Vec = Vec::new(); set_compute_budget_ixs_if_needed(&mut initial_instructions, &compute_unit_price); From 1ee834e4ef93f0d73b14f1db2bd37e0804ba1e0b Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Thu, 21 Mar 2024 15:50:52 +0000 Subject: [PATCH 4/8] fixes and tests --- Cargo.lock | 1 + cli/Cargo.toml | 1 + cli/src/program.rs | 173 +++++++++++++++++----------------- cli/tests/program.rs | 180 ++++++++++++++++++++++++++++++------ transaction-dos/src/main.rs | 2 +- 5 files changed, 241 insertions(+), 116 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2fb61646b042b1..019d37d52f9f02 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5780,6 +5780,7 @@ dependencies = [ "solana-program-runtime", "solana-pubsub-client", "solana-remote-wallet", + "solana-rpc", "solana-rpc-client", "solana-rpc-client-api", "solana-rpc-client-nonce-utils", diff --git a/cli/Cargo.toml b/cli/Cargo.toml index b9170ac79ab07c..b5e444ba81a00b 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -56,6 +56,7 @@ tiny-bip39 = { workspace = true } [dev-dependencies] assert_matches = { workspace = true } +solana-rpc = { workspace = true } solana-streamer = { workspace = true } solana-test-validator = { workspace = true } tempfile = { workspace = true } diff --git a/cli/src/program.rs b/cli/src/program.rs index 888598963f2f36..5ab844d146af71 100644 --- a/cli/src/program.rs +++ b/cli/src/program.rs @@ -1391,6 +1391,7 @@ fn process_program_upgrade( } } +#[allow(clippy::too_many_arguments)] fn process_write_buffer( rpc_client: Arc, config: &CliConfig, @@ -2226,9 +2227,10 @@ fn do_process_program_write_and_deploy( let blockhash = rpc_client.get_latest_blockhash()?; // Initialize buffer account or complete if already partially initialized - let (ixs, balance_needed, buffer_program_data) = if let Some(mut account) = rpc_client - .get_account_with_commitment(buffer_pubkey, config.commitment)? - .value + let (mut initial_instructions, balance_needed, buffer_program_data) = if let Some(mut account) = + rpc_client + .get_account_with_commitment(buffer_pubkey, config.commitment)? + .value { let (ixs, balance_needed) = complete_partial_program_init( loader_id, @@ -2273,11 +2275,8 @@ fn do_process_program_write_and_deploy( ) }; - let mut initial_instructions: Vec = Vec::new(); - - set_compute_budget_ixs_if_needed(&mut initial_instructions, &compute_unit_price); - initial_instructions.extend(ixs); let initial_message = if !initial_instructions.is_empty() { + set_compute_budget_ixs_if_needed(&mut initial_instructions, compute_unit_price); Some(Message::new_with_blockhash( &initial_instructions, Some(&fee_payer_signer.pubkey()), @@ -2289,8 +2288,7 @@ fn do_process_program_write_and_deploy( // Create and add write messages let create_msg = |offset: u32, bytes: Vec| { - let mut write_ixs: Vec = Vec::new(); - let ix_to_add = if loader_id == &bpf_loader_upgradeable::id() { + let instruction = if loader_id == &bpf_loader_upgradeable::id() { bpf_loader_upgradeable::write( buffer_pubkey, &buffer_authority_signer.pubkey(), @@ -2301,9 +2299,9 @@ fn do_process_program_write_and_deploy( loader_instruction::write(buffer_pubkey, loader_id, offset, bytes) }; - set_compute_budget_ixs_if_needed(&mut write_ixs, &compute_unit_price); - write_ixs.push(ix_to_add); - Message::new_with_blockhash(&write_ixs, Some(&fee_payer_signer.pubkey()), &blockhash) + let mut instructions = vec![instruction]; + set_compute_budget_ixs_if_needed(&mut instructions, compute_unit_price); + Message::new_with_blockhash(&instructions, Some(&fee_payer_signer.pubkey()), &blockhash) }; let mut write_messages = vec![]; @@ -2317,10 +2315,8 @@ fn do_process_program_write_and_deploy( // Create and add final message let final_message = if let Some(program_signers) = program_signers { - let mut final_ixs: Vec = Vec::new(); - let message = if loader_id == &bpf_loader_upgradeable::id() { - let ixs_to_add = bpf_loader_upgradeable::deploy_with_max_program_len( + let mut instructions = bpf_loader_upgradeable::deploy_with_max_program_len( &fee_payer_signer.pubkey(), &program_signers[0].pubkey(), buffer_pubkey, @@ -2331,14 +2327,12 @@ fn do_process_program_write_and_deploy( program_data_max_len, )?; - set_compute_budget_ixs_if_needed(&mut final_ixs, &compute_unit_price); - final_ixs.extend(ixs_to_add); - Message::new_with_blockhash(&final_ixs, Some(&fee_payer_signer.pubkey()), &blockhash) + set_compute_budget_ixs_if_needed(&mut instructions, compute_unit_price); + Message::new_with_blockhash(&instructions, Some(&fee_payer_signer.pubkey()), &blockhash) } else { - set_compute_budget_ixs_if_needed(&mut final_ixs, &compute_unit_price); - - final_ixs.push(loader_instruction::finalize(buffer_pubkey, loader_id)); - Message::new_with_blockhash(&final_ixs, Some(&fee_payer_signer.pubkey()), &blockhash) + let mut instructions = vec![loader_instruction::finalize(buffer_pubkey, loader_id)]; + set_compute_budget_ixs_if_needed(&mut instructions, compute_unit_price); + Message::new_with_blockhash(&instructions, Some(&fee_payer_signer.pubkey()), &blockhash) }; Some(message) } else { @@ -2404,42 +2398,40 @@ fn do_process_program_upgrade( buffer_signer { // Check Buffer account to see if partial initialization has occurred - let (ixs, balance_needed, buffer_program_data) = if let Some(mut account) = rpc_client - .get_account_with_commitment(&buffer_signer.pubkey(), config.commitment)? - .value - { - let (ixs, balance_needed) = complete_partial_program_init( - &bpf_loader_upgradeable::id(), - &fee_payer_signer.pubkey(), - &buffer_signer.pubkey(), - &account, - UpgradeableLoaderState::size_of_buffer(program_len), - min_rent_exempt_program_data_balance, - true, - )?; - let buffer_program_data = account - .data - .split_off(UpgradeableLoaderState::size_of_buffer_metadata()); - (ixs, balance_needed, buffer_program_data) - } else { - ( - bpf_loader_upgradeable::create_buffer( + let (mut initial_instructions, balance_needed, buffer_program_data) = + if let Some(mut account) = rpc_client + .get_account_with_commitment(&buffer_signer.pubkey(), config.commitment)? + .value + { + let (ixs, balance_needed) = complete_partial_program_init( + &bpf_loader_upgradeable::id(), &fee_payer_signer.pubkey(), - buffer_pubkey, - &upgrade_authority.pubkey(), + &buffer_signer.pubkey(), + &account, + UpgradeableLoaderState::size_of_buffer(program_len), min_rent_exempt_program_data_balance, - program_len, - )?, - min_rent_exempt_program_data_balance, - vec![0; program_len], - ) - }; - - let mut initial_instructions: Vec = Vec::new(); + true, + )?; + let buffer_program_data = account + .data + .split_off(UpgradeableLoaderState::size_of_buffer_metadata()); + (ixs, balance_needed, buffer_program_data) + } else { + ( + bpf_loader_upgradeable::create_buffer( + &fee_payer_signer.pubkey(), + buffer_pubkey, + &upgrade_authority.pubkey(), + min_rent_exempt_program_data_balance, + program_len, + )?, + min_rent_exempt_program_data_balance, + vec![0; program_len], + ) + }; - set_compute_budget_ixs_if_needed(&mut initial_instructions, &compute_unit_price); - initial_instructions.extend(ixs); let initial_message = if !initial_instructions.is_empty() { + set_compute_budget_ixs_if_needed(&mut initial_instructions, compute_unit_price); Some(Message::new_with_blockhash( &initial_instructions, Some(&fee_payer_signer.pubkey()), @@ -2452,18 +2444,15 @@ fn do_process_program_upgrade( let buffer_signer_pubkey = buffer_signer.pubkey(); let upgrade_authority_pubkey = upgrade_authority.pubkey(); let create_msg = |offset: u32, bytes: Vec| { - let mut write_ixs: Vec = Vec::new(); - let ix_to_add = bpf_loader_upgradeable::write( + let mut instructions = vec![bpf_loader_upgradeable::write( &buffer_signer_pubkey, &upgrade_authority_pubkey, offset, bytes, - ); - - set_compute_budget_ixs_if_needed(&mut write_ixs, &compute_unit_price); - write_ixs.push(ix_to_add); + )]; - Message::new_with_blockhash(&write_ixs, Some(&fee_payer_signer.pubkey()), &blockhash) + set_compute_budget_ixs_if_needed(&mut instructions, compute_unit_price); + Message::new_with_blockhash(&instructions, Some(&fee_payer_signer.pubkey()), &blockhash) }; // Create and add write messages @@ -2482,18 +2471,19 @@ fn do_process_program_upgrade( }; // Create and add final message - let mut final_ixs: Vec = Vec::new(); - - set_compute_budget_ixs_if_needed(&mut final_ixs, &compute_unit_price); - - final_ixs.push(bpf_loader_upgradeable::upgrade( + let mut final_instructions = vec![bpf_loader_upgradeable::upgrade( program_id, buffer_pubkey, &upgrade_authority.pubkey(), &fee_payer_signer.pubkey(), - )); - let final_message = - Message::new_with_blockhash(&final_ixs, Some(&fee_payer_signer.pubkey()), &blockhash); + )]; + + set_compute_budget_ixs_if_needed(&mut final_instructions, compute_unit_price); + let final_message = Message::new_with_blockhash( + &final_instructions, + Some(&fee_payer_signer.pubkey()), + &blockhash, + ); let final_message = Some(final_message); if !skip_fee_check { @@ -2689,7 +2679,6 @@ fn send_deploy_messages( .send_and_confirm_messages_with_spinner( write_messages, &[fee_payer_signer, write_signer], - ), ConnectionCache::Quic(cache) => { let tpu_client_fut = solana_client::nonblocking::tpu_client::TpuClient::new_with_connection_cache( @@ -2780,28 +2769,42 @@ fn report_ephemeral_mnemonic(words: usize, mnemonic: bip39::Mnemonic) { eprintln!("[BUFFER_ACCOUNT_ADDRESS] argument to `solana program close`.\n{divider}"); } -fn set_compute_budget_ixs_if_needed(ixs: &mut Vec, compute_unit_price: &Option) { - let mut compute_units_required_for_ixs: u32 = 0; +fn set_compute_budget_ixs_if_needed(ixs: &mut Vec, compute_unit_price: Option) { + let Some(compute_unit_price) = compute_unit_price else { + return; + }; + + let mut compute_unit_limit: u32 = 300; // 2 * 150 for compute budget ixs for ix in ixs.iter() { if ix.program_id == bpf_loader_upgradeable::id() { - compute_units_required_for_ixs += 2370u32; + compute_unit_limit += 2370; + match ix.data.first() { + Some(2) // DeployWithMaxDataLen + | Some(6) // ExtendProgram + => { + // add compute for native system program invocation + compute_unit_limit += 150; + } + _ => {} + } } else if ix.program_id == system_program::id() { - compute_units_required_for_ixs += 150u32; + compute_unit_limit += 150; } else { - compute_units_required_for_ixs += 2000u32; + panic!( + "Couldn't estimate compute unit limit for unexpected program {}", + ix.program_id + ); } } - let compute_units_for_compute_budget_ixs = 300u32; - - if let Some(compute_unit_price) = compute_unit_price { - ixs.push(ComputeBudgetInstruction::set_compute_unit_limit( - compute_units_required_for_ixs + compute_units_for_compute_budget_ixs, - )); - ixs.push(ComputeBudgetInstruction::set_compute_unit_price( - *compute_unit_price, - )); - } + ixs.insert( + 0, + ComputeBudgetInstruction::set_compute_unit_limit(compute_unit_limit), + ); + ixs.insert( + 0, + ComputeBudgetInstruction::set_compute_unit_price(compute_unit_price), + ); } #[cfg(test)] diff --git a/cli/tests/program.rs b/cli/tests/program.rs index 9ec7091995375a..716be253f57e94 100644 --- a/cli/tests/program.rs +++ b/cli/tests/program.rs @@ -10,26 +10,35 @@ use { test_utils::wait_n_slots, }, solana_cli_output::{parse_sign_only_reply_string, OutputFormat}, + solana_client::{ + rpc_client::GetConfirmedSignaturesForAddress2Config, rpc_config::RpcTransactionConfig, + }, solana_faucet::faucet::run_local_faucet, + solana_rpc::rpc::JsonRpcConfig, solana_rpc_client::rpc_client::RpcClient, solana_rpc_client_nonce_utils::blockhash_query::BlockhashQuery, solana_sdk::{ account_utils::StateMut, + borsh1::try_from_slice_unchecked, bpf_loader_upgradeable::{self, UpgradeableLoaderState}, commitment_config::CommitmentConfig, + compute_budget::{self, ComputeBudgetInstruction}, + fee_calculator::FeeRateGovernor, pubkey::Pubkey, - signature::{Keypair, NullSigner, Signer}, - signer::EncodableKey, + rent::Rent, + signature::{Keypair, NullSigner, Signature, Signer}, + system_program, + transaction::Transaction, }, solana_streamer::socket::SocketAddrSpace, - solana_test_validator::TestValidator, + solana_test_validator::{TestValidator, TestValidatorGenesis}, + solana_transaction_status::UiTransactionEncoding, std::{ env, fs::File, io::Read, path::{Path, PathBuf}, str::FromStr, - time::Instant, }, test_case::test_case, }; @@ -2056,49 +2065,78 @@ fn create_buffer_with_offline_authority<'a>( } } -fn program_deploy_with_args(compute_unit_price: Option) { - solana_logger::setup(); - +#[allow(clippy::assertions_on_constants)] +fn cli_program_deploy_with_args(compute_unit_price: Option) { let mut noop_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); noop_path.push("tests"); noop_path.push("fixtures"); noop_path.push("noop"); noop_path.set_extension("so"); + let mint_keypair = Keypair::new(); + let mint_pubkey = mint_keypair.pubkey(); + let faucet_addr = run_local_faucet(mint_keypair, None); + let test_validator = TestValidatorGenesis::default() + .fee_rate_governor(FeeRateGovernor::new(0, 0)) + .rent(Rent { + lamports_per_byte_year: 1, + exemption_threshold: 1.0, + ..Rent::default() + }) + .rpc_config(JsonRpcConfig { + enable_rpc_transaction_history: true, + faucet_addr: Some(faucet_addr), + ..JsonRpcConfig::default_for_test() + }) + .start_with_mint_address(mint_pubkey, SocketAddrSpace::Unspecified) + .expect("validator start failed"); + + let rpc_client = + RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::confirmed()); + let mut file = File::open(noop_path.to_str().unwrap()).unwrap(); let mut program_data = Vec::new(); file.read_to_end(&mut program_data).unwrap(); - - let mut config = CliConfig::recent_for_tests(); - let keypair = Keypair::read_from_file(&config.keypair_path).unwrap(); - + let max_len = program_data.len(); + let minimum_balance_for_programdata = rpc_client + .get_minimum_balance_for_rent_exemption(UpgradeableLoaderState::size_of_programdata( + max_len, + )) + .unwrap(); + let minimum_balance_for_program = rpc_client + .get_minimum_balance_for_rent_exemption(UpgradeableLoaderState::size_of_program()) + .unwrap(); let upgrade_authority = Keypair::new(); - config.json_rpc_url = "https://api.devnet.solana.com".to_string(); - + let mut config = CliConfig::recent_for_tests(); + let keypair = Keypair::new(); + config.json_rpc_url = test_validator.rpc_url(); config.signers = vec![&keypair]; + config.command = CliCommand::Airdrop { + pubkey: None, + lamports: 100 * minimum_balance_for_programdata + minimum_balance_for_program, + }; + process_command(&config).unwrap(); - // Deploy a program - config.signers = vec![&keypair, &upgrade_authority]; + // Deploy the upgradeable program with specified program_id + let program_keypair = Keypair::new(); + config.signers = vec![&keypair, &upgrade_authority, &program_keypair]; config.command = CliCommand::Program(ProgramCliCommand::Deploy { program_location: Some(noop_path.to_str().unwrap().to_string()), fee_payer_signer_index: 0, - program_signer_index: None, - program_pubkey: None, + program_signer_index: Some(2), + program_pubkey: Some(program_keypair.pubkey()), buffer_signer_index: None, buffer_pubkey: None, allow_excessive_balance: false, upgrade_authority_signer_index: 1, - is_final: true, - max_len: None, + is_final: false, + max_len: Some(max_len), skip_fee_check: false, compute_unit_price, }); config.output_format = OutputFormat::JsonCompact; - let start = Instant::now(); let response = process_command(&config); - let duration = start.elapsed(); - println!("Time elapsed: {:?}", duration); let json: Value = serde_json::from_str(&response.unwrap()).unwrap(); let program_pubkey_str = json .as_object() @@ -2107,17 +2145,99 @@ fn program_deploy_with_args(compute_unit_price: Option) { .unwrap() .as_str() .unwrap(); - println!( - "Program deployed successfully with id: {}", - program_pubkey_str + assert_eq!( + program_keypair.pubkey(), + Pubkey::from_str(program_pubkey_str).unwrap() ); + let program_account = rpc_client.get_account(&program_keypair.pubkey()).unwrap(); + assert_eq!(program_account.lamports, minimum_balance_for_program); + assert_eq!(program_account.owner, bpf_loader_upgradeable::id()); + assert!(program_account.executable); + let signature_statuses = rpc_client + .get_signatures_for_address_with_config( + &keypair.pubkey(), + GetConfirmedSignaturesForAddress2Config { + commitment: Some(CommitmentConfig::confirmed()), + ..GetConfirmedSignaturesForAddress2Config::default() + }, + ) + .unwrap(); + let signatures: Vec<_> = signature_statuses + .into_iter() + .rev() + .map(|status| Signature::from_str(&status.signature).unwrap()) + .collect(); + + fn fetch_and_decode_transaction(rpc_client: &RpcClient, signature: &Signature) -> Transaction { + rpc_client + .get_transaction_with_config( + signature, + RpcTransactionConfig { + encoding: Some(UiTransactionEncoding::Base64), + commitment: Some(CommitmentConfig::confirmed()), + ..RpcTransactionConfig::default() + }, + ) + .unwrap() + .transaction + .transaction + .decode() + .unwrap() + .into_legacy_transaction() + .unwrap() + } + + assert!(signatures.len() >= 4); + let initial_tx = fetch_and_decode_transaction(&rpc_client, &signatures[1]); + let write_tx = fetch_and_decode_transaction(&rpc_client, &signatures[2]); + let final_tx = fetch_and_decode_transaction(&rpc_client, signatures.last().unwrap()); + + if let Some(compute_unit_price) = compute_unit_price { + for tx in [&initial_tx, &write_tx, &final_tx] { + for i in [0, 1] { + assert_eq!( + tx.message.instructions[i].program_id(&tx.message.account_keys), + &compute_budget::id() + ); + } + + match try_from_slice_unchecked(&tx.message.instructions[0].data) { + Ok(ComputeBudgetInstruction::SetComputeUnitPrice(price)) + if price == compute_unit_price => {} + ix => assert!(false, "unexpected ix {ix:?}"), + } + } + + match try_from_slice_unchecked(&initial_tx.message.instructions[1].data) { + Ok(ComputeBudgetInstruction::SetComputeUnitLimit(2820)) => {} + ix => assert!(false, "unexpected ix {ix:?}"), + } + match try_from_slice_unchecked(&write_tx.message.instructions[1].data) { + Ok(ComputeBudgetInstruction::SetComputeUnitLimit(2670)) => {} + ix => assert!(false, "unexpected ix {ix:?}"), + } + match try_from_slice_unchecked(&final_tx.message.instructions[1].data) { + Ok(ComputeBudgetInstruction::SetComputeUnitLimit(2970)) => {} + ix => assert!(false, "unexpected ix {ix:?}"), + } + } else { + assert_eq!( + initial_tx.message.instructions[0].program_id(&initial_tx.message.account_keys), + &system_program::id() + ); + assert_eq!( + write_tx.message.instructions[0].program_id(&write_tx.message.account_keys), + &bpf_loader_upgradeable::id() + ); + assert_eq!( + final_tx.message.instructions[0].program_id(&final_tx.message.account_keys), + &system_program::id() + ); + } } #[test] fn test_cli_program_deploy_with_compute_unit_price() { - //test without compute_unit_price - program_deploy_with_args(None); - - //test with 1000 micro lamports as compute_unit_price - program_deploy_with_args(Some(1000)); + cli_program_deploy_with_args(Some(1000)); + cli_program_deploy_with_args(None); } diff --git a/transaction-dos/src/main.rs b/transaction-dos/src/main.rs index 7d62ec92a58ef7..3cf835c578382e 100644 --- a/transaction-dos/src/main.rs +++ b/transaction-dos/src/main.rs @@ -247,7 +247,7 @@ fn run_transactions_dos( upgrade_authority_signer_index: 0, is_final: true, max_len: None, - compute_unit_price, + compute_unit_price: None, skip_fee_check: true, // skip_fee_check }); From 498fb0ed37b189a4a6ccf28c5723f3de99afb83a Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Thu, 21 Mar 2024 16:24:47 +0000 Subject: [PATCH 5/8] remove extra lines --- cli/tests/program.rs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/cli/tests/program.rs b/cli/tests/program.rs index 716be253f57e94..a9527b2b207c40 100644 --- a/cli/tests/program.rs +++ b/cli/tests/program.rs @@ -1006,7 +1006,6 @@ fn test_cli_program_write_buffer() { buffer_authority_signer_index: 0, max_len: None, skip_fee_check: false, - compute_unit_price: None, }); config.output_format = OutputFormat::JsonCompact; @@ -1044,7 +1043,6 @@ fn test_cli_program_write_buffer() { buffer_authority_signer_index: 0, max_len: Some(max_len), skip_fee_check: false, - compute_unit_price: None, }); let response = process_command(&config); @@ -1109,7 +1107,6 @@ fn test_cli_program_write_buffer() { buffer_authority_signer_index: 2, max_len: None, skip_fee_check: false, - compute_unit_price: None, }); let response = process_command(&config); @@ -1150,7 +1147,6 @@ fn test_cli_program_write_buffer() { buffer_authority_signer_index: 2, max_len: None, skip_fee_check: false, - compute_unit_price: None, }); let response = process_command(&config); @@ -1227,7 +1223,6 @@ fn test_cli_program_write_buffer() { buffer_authority_signer_index: 0, max_len: None, skip_fee_check: false, - compute_unit_price: None, }); config.output_format = OutputFormat::JsonCompact; @@ -1271,7 +1266,6 @@ fn test_cli_program_write_buffer() { buffer_authority_signer_index: 0, max_len: None, //Some(max_len), skip_fee_check: false, - compute_unit_price: None, }); process_command(&config).unwrap(); @@ -1348,7 +1342,6 @@ fn test_cli_program_set_buffer_authority() { buffer_authority_signer_index: 0, max_len: None, skip_fee_check: false, - compute_unit_price: None, }); process_command(&config).unwrap(); @@ -1505,7 +1498,6 @@ fn test_cli_program_mismatch_buffer_authority() { buffer_authority_signer_index: 2, max_len: None, skip_fee_check: false, - compute_unit_price: None, }); process_command(&config).unwrap(); @@ -1801,7 +1793,6 @@ fn test_cli_program_show() { buffer_authority_signer_index: 2, max_len: None, skip_fee_check: false, - compute_unit_price: None, }); process_command(&config).unwrap(); @@ -1993,7 +1984,6 @@ fn test_cli_program_dump() { buffer_authority_signer_index: 2, max_len: None, skip_fee_check: false, - compute_unit_price: None, }); process_command(&config).unwrap(); From 01faab42f1d492144194673fa9fbd329ded3835d Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Fri, 22 Mar 2024 08:11:30 +0000 Subject: [PATCH 6/8] feedback --- cli/tests/program.rs | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/cli/tests/program.rs b/cli/tests/program.rs index a9527b2b207c40..240a01567a409e 100644 --- a/cli/tests/program.rs +++ b/cli/tests/program.rs @@ -3,6 +3,7 @@ #![allow(clippy::items_after_test_module)] use { + assert_matches::assert_matches, serde_json::Value, solana_cli::{ cli::{process_command, CliCommand, CliConfig}, @@ -2191,25 +2192,24 @@ fn cli_program_deploy_with_args(compute_unit_price: Option) { ); } - match try_from_slice_unchecked(&tx.message.instructions[0].data) { - Ok(ComputeBudgetInstruction::SetComputeUnitPrice(price)) - if price == compute_unit_price => {} - ix => assert!(false, "unexpected ix {ix:?}"), - } + assert_matches!( + try_from_slice_unchecked(&tx.message.instructions[0].data), + Ok(ComputeBudgetInstruction::SetComputeUnitPrice(price)) if price == compute_unit_price + ); } - match try_from_slice_unchecked(&initial_tx.message.instructions[1].data) { - Ok(ComputeBudgetInstruction::SetComputeUnitLimit(2820)) => {} - ix => assert!(false, "unexpected ix {ix:?}"), - } - match try_from_slice_unchecked(&write_tx.message.instructions[1].data) { - Ok(ComputeBudgetInstruction::SetComputeUnitLimit(2670)) => {} - ix => assert!(false, "unexpected ix {ix:?}"), - } - match try_from_slice_unchecked(&final_tx.message.instructions[1].data) { - Ok(ComputeBudgetInstruction::SetComputeUnitLimit(2970)) => {} - ix => assert!(false, "unexpected ix {ix:?}"), - } + assert_matches!( + try_from_slice_unchecked(&initial_tx.message.instructions[1].data), + Ok(ComputeBudgetInstruction::SetComputeUnitLimit(2820)) + ); + assert_matches!( + try_from_slice_unchecked(&write_tx.message.instructions[1].data), + Ok(ComputeBudgetInstruction::SetComputeUnitLimit(2670)) + ); + assert_matches!( + try_from_slice_unchecked(&final_tx.message.instructions[1].data), + Ok(ComputeBudgetInstruction::SetComputeUnitLimit(2970)) + ); } else { assert_eq!( initial_tx.message.instructions[0].program_id(&initial_tx.message.account_keys), From 083223e03877d20925ffa1da46c5d26fb0f8461a Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Fri, 22 Mar 2024 10:36:05 +0000 Subject: [PATCH 7/8] Use simulation to determine compute units consumed --- cli/src/program.rs | 137 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 107 insertions(+), 30 deletions(-) diff --git a/cli/src/program.rs b/cli/src/program.rs index 5ab844d146af71..15025bba98a2c8 100644 --- a/cli/src/program.rs +++ b/cli/src/program.rs @@ -29,12 +29,16 @@ use { }, solana_client::{ connection_cache::ConnectionCache, + rpc_config::RpcSimulateTransactionConfig, send_and_confirm_transactions_in_parallel::{ send_and_confirm_transactions_in_parallel_blocking, SendAndConfirmConfig, }, tpu_client::{TpuClient, TpuClientConfig}, }, - solana_program_runtime::{compute_budget::ComputeBudget, invoke_context::InvokeContext}, + solana_program_runtime::{ + compute_budget::ComputeBudget, compute_budget_processor::MAX_COMPUTE_UNIT_LIMIT, + invoke_context::InvokeContext, + }, solana_rbpf::{elf::Executable, verifier::RequisiteVerifier}, solana_remote_wallet::remote_wallet::RemoteWalletManager, solana_rpc_client::rpc_client::RpcClient, @@ -47,9 +51,10 @@ use { solana_sdk::{ account::Account, account_utils::StateMut, + borsh1::try_from_slice_unchecked, bpf_loader, bpf_loader_deprecated, bpf_loader_upgradeable::{self, UpgradeableLoaderState}, - compute_budget::ComputeBudgetInstruction, + compute_budget::{self, ComputeBudgetInstruction}, feature_set::FeatureSet, instruction::{Instruction, InstructionError}, loader_instruction, @@ -2627,6 +2632,70 @@ fn check_payer( Ok(()) } +// This enum is equivalent to an Option but was added to self-document +// the ok variants and has the benefit of not forcing the caller to use +// the result if they don't care about it. +enum UpdateComputeUnitLimitResult { + UpdatedInstructionIndex(usize), + NoInstructionFound, +} + +// Returns the index of the compute unit limit instruction +fn simulate_and_update_compute_unit_limit( + rpc_client: &RpcClient, + transaction: &mut Transaction, +) -> Result> { + let Some(compute_unit_limit_ix_index) = transaction + .message + .instructions + .iter() + .enumerate() + .filter_map(|(ix_index, instruction)| { + let ix_program_id = transaction.message.program_id(ix_index)?; + if ix_program_id != &compute_budget::id() { + return None; + } + + match try_from_slice_unchecked(&instruction.data).ok()? { + ComputeBudgetInstruction::SetComputeUnitLimit(_) => Some(ix_index), + _ => None, + } + }) + .next() + else { + return Ok(UpdateComputeUnitLimitResult::NoInstructionFound); + }; + + let simulate_result = rpc_client + .simulate_transaction_with_config( + transaction, + RpcSimulateTransactionConfig { + replace_recent_blockhash: true, + commitment: Some(rpc_client.commitment()), + ..RpcSimulateTransactionConfig::default() + }, + )? + .value; + + // Bail if the simulated transaction failed + if let Some(err) = simulate_result.err { + return Err(err.into()); + } + + let units_consumed = simulate_result + .units_consumed + .expect("compute units unavailable"); + + // Overwrite the compute unit limit instruction with the actual units consumed + let compute_unit_limit = u32::try_from(units_consumed)?; + transaction.message.instructions[compute_unit_limit_ix_index].data = + ComputeBudgetInstruction::set_compute_unit_limit(compute_unit_limit).data; + + Ok(UpdateComputeUnitLimitResult::UpdatedInstructionIndex( + compute_unit_limit_ix_index, + )) +} + fn send_deploy_messages( rpc_client: Arc, config: &CliConfig, @@ -2641,9 +2710,12 @@ fn send_deploy_messages( if let Some(message) = initial_message { if let Some(initial_signer) = initial_signer { trace!("Preparing the required accounts"); - let blockhash = rpc_client.get_latest_blockhash()?; let mut initial_transaction = Transaction::new_unsigned(message.clone()); + simulate_and_update_compute_unit_limit(&rpc_client, &mut initial_transaction)?; + + let blockhash = rpc_client.get_latest_blockhash()?; + // Most of the initial_transaction combinations require both the fee-payer and new program // account to sign the transaction. One (transfer) only requires the fee-payer signature. // This check is to ensure signing does not fail on a KeypairPubkeyMismatch error from an @@ -2664,6 +2736,29 @@ fn send_deploy_messages( if !write_messages.is_empty() { if let Some(write_signer) = write_signer { trace!("Writing program data"); + + // Simulate the first write message to get the number of compute units + // consumed and then reuse that value as the compute unit limit for all + // write messages. + let mut write_messages = write_messages.to_vec(); + { + let mut transaction = Transaction::new_unsigned(write_messages[0].clone()); + if let UpdateComputeUnitLimitResult::UpdatedInstructionIndex(ix_index) = + simulate_and_update_compute_unit_limit(&rpc_client, &mut transaction)? + { + for msg in &mut write_messages { + // Write messages are all assumed to be identical except + // the program data being written. But just in case that + // assumption is broken, assert that we are only ever + // changing the instruction data for a compute budget + // instruction. + assert_eq!(msg.program_id(ix_index), Some(&compute_budget::id())); + msg.instructions[ix_index].data = + transaction.message.instructions[ix_index].data.clone(); + } + } + } + let connection_cache = if config.use_quic { ConnectionCache::new_quic("connection_cache_cli_program_quic", 1) } else { @@ -2677,7 +2772,7 @@ fn send_deploy_messages( cache, )? .send_and_confirm_messages_with_spinner( - write_messages, + &write_messages, &[fee_payer_signer, write_signer], ), ConnectionCache::Quic(cache) => { @@ -2695,7 +2790,7 @@ fn send_deploy_messages( send_and_confirm_transactions_in_parallel_blocking( rpc_client.clone(), Some(tpu_client), - write_messages, + &write_messages, &[fee_payer_signer, write_signer], SendAndConfirmConfig { resign_txs_count: Some(5), @@ -2723,9 +2818,11 @@ fn send_deploy_messages( if let Some(message) = final_message { if let Some(final_signers) = final_signers { trace!("Deploying program"); - let blockhash = rpc_client.get_latest_blockhash()?; let mut final_tx = Transaction::new_unsigned(message.clone()); + simulate_and_update_compute_unit_limit(&rpc_client, &mut final_tx)?; + + let blockhash = rpc_client.get_latest_blockhash()?; let mut signers = final_signers.to_vec(); signers.push(fee_payer_signer); final_tx.try_sign(&signers, blockhash)?; @@ -2774,33 +2871,13 @@ fn set_compute_budget_ixs_if_needed(ixs: &mut Vec, compute_unit_pri return; }; - let mut compute_unit_limit: u32 = 300; // 2 * 150 for compute budget ixs - for ix in ixs.iter() { - if ix.program_id == bpf_loader_upgradeable::id() { - compute_unit_limit += 2370; - match ix.data.first() { - Some(2) // DeployWithMaxDataLen - | Some(6) // ExtendProgram - => { - // add compute for native system program invocation - compute_unit_limit += 150; - } - _ => {} - } - } else if ix.program_id == system_program::id() { - compute_unit_limit += 150; - } else { - panic!( - "Couldn't estimate compute unit limit for unexpected program {}", - ix.program_id - ); - } - } - + // Default to the max compute unit limit because later transactions will be + // simulated to get the exact compute units consumed. ixs.insert( 0, - ComputeBudgetInstruction::set_compute_unit_limit(compute_unit_limit), + ComputeBudgetInstruction::set_compute_unit_limit(MAX_COMPUTE_UNIT_LIMIT), ); + ixs.insert( 0, ComputeBudgetInstruction::set_compute_unit_price(compute_unit_price), From 5c355ad51c1c7794450e33a4dee3437b3d9f11d8 Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Fri, 22 Mar 2024 15:14:11 +0000 Subject: [PATCH 8/8] feedback --- cli/src/program.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/cli/src/program.rs b/cli/src/program.rs index 15025bba98a2c8..0aec785fa445ea 100644 --- a/cli/src/program.rs +++ b/cli/src/program.rs @@ -2650,18 +2650,18 @@ fn simulate_and_update_compute_unit_limit( .instructions .iter() .enumerate() - .filter_map(|(ix_index, instruction)| { + .find_map(|(ix_index, instruction)| { let ix_program_id = transaction.message.program_id(ix_index)?; if ix_program_id != &compute_budget::id() { return None; } - match try_from_slice_unchecked(&instruction.data).ok()? { - ComputeBudgetInstruction::SetComputeUnitLimit(_) => Some(ix_index), - _ => None, - } + matches!( + try_from_slice_unchecked(&instruction.data), + Ok(ComputeBudgetInstruction::SetComputeUnitLimit(_)) + ) + .then_some(ix_index) }) - .next() else { return Ok(UpdateComputeUnitLimitResult::NoInstructionFound); };