From c98c24bd6d3b987cd94db8e90ce356d8f4010d42 Mon Sep 17 00:00:00 2001 From: steviez Date: Fri, 20 Oct 2023 15:53:45 +0200 Subject: [PATCH] =?UTF-8?q?Revert=20"Split=20compute=20budget=20instructio?= =?UTF-8?q?ns=20process=20from=20struct=20itself=20=E2=80=A6=20(#33784)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Revert "Split compute budget instructions process from struct itself (#33513)" This reverts commit c73bebe9847ecd5a1cbffa96bf03e03a7683232f. This was found to be a consensus breaking change. --- accounts-db/src/accounts.rs | 52 +- cost-model/src/cost_model.rs | 69 +- program-runtime/src/compute_budget.rs | 648 +++++++++++++++++- .../src/compute_budget_processor.rs | 619 ----------------- program-runtime/src/invoke_context.rs | 11 +- program-runtime/src/lib.rs | 1 - programs/sbf/tests/programs.rs | 17 +- runtime/src/bank.rs | 67 +- runtime/src/bank/tests.rs | 34 +- runtime/src/transaction_priority_details.rs | 40 +- 10 files changed, 762 insertions(+), 796 deletions(-) delete mode 100644 program-runtime/src/compute_budget_processor.rs diff --git a/accounts-db/src/accounts.rs b/accounts-db/src/accounts.rs index 4ff891fc8..47b372d98 100644 --- a/accounts-db/src/accounts.rs +++ b/accounts-db/src/accounts.rs @@ -25,7 +25,7 @@ use { itertools::Itertools, log::*, solana_program_runtime::{ - compute_budget_processor::process_compute_budget_instructions, + compute_budget::{self, ComputeBudget}, loaded_programs::LoadedProgramsForTxBatch, }, solana_sdk::{ @@ -35,8 +35,9 @@ use { bpf_loader_upgradeable::{self, UpgradeableLoaderState}, clock::{BankId, Slot}, feature_set::{ - self, include_loaded_accounts_data_size_in_fee_calculation, - remove_congestion_multiplier_from_fee_calculation, + self, add_set_tx_loaded_accounts_data_size_instruction, + include_loaded_accounts_data_size_in_fee_calculation, + remove_congestion_multiplier_from_fee_calculation, remove_deprecated_request_unit_ix, simplify_writable_program_account_check, FeatureSet, }, fee::FeeStructure, @@ -246,16 +247,15 @@ impl Accounts { feature_set: &FeatureSet, ) -> Result> { if feature_set.is_active(&feature_set::cap_transaction_accounts_data_size::id()) { - let compute_budget_limits = process_compute_budget_instructions( + let mut compute_budget = + ComputeBudget::new(compute_budget::MAX_COMPUTE_UNIT_LIMIT as u64); + let _process_transaction_result = compute_budget.process_instructions( tx.message().program_instructions_iter(), - feature_set, - ) - .unwrap_or_default(); + !feature_set.is_active(&remove_deprecated_request_unit_ix::id()), + feature_set.is_active(&add_set_tx_loaded_accounts_data_size_instruction::id()), + ); // sanitize against setting size limit to zero - NonZeroUsize::new( - usize::try_from(compute_budget_limits.loaded_accounts_bytes).unwrap_or_default(), - ) - .map_or( + NonZeroUsize::new(compute_budget.loaded_accounts_data_size_limit).map_or( Err(TransactionError::InvalidLoadedAccountsDataSizeLimit), |v| Ok(Some(v)), ) @@ -722,7 +722,7 @@ impl Accounts { fee_structure.calculate_fee( tx.message(), lamports_per_signature, - &process_compute_budget_instructions(tx.message().program_instructions_iter(), feature_set).unwrap_or_default().into(), + &ComputeBudget::fee_budget_limits(tx.message().program_instructions_iter(), feature_set), feature_set.is_active(&remove_congestion_multiplier_from_fee_calculation::id()), feature_set.is_active(&include_loaded_accounts_data_size_in_fee_calculation::id()), ) @@ -1474,9 +1474,8 @@ mod tests { transaction_results::{DurableNonceFee, TransactionExecutionDetails}, }, assert_matches::assert_matches, - solana_program_runtime::{ - compute_budget_processor, - prioritization_fee::{PrioritizationFeeDetails, PrioritizationFeeType}, + solana_program_runtime::prioritization_fee::{ + PrioritizationFeeDetails, PrioritizationFeeType, }, solana_sdk::{ account::{AccountSharedData, WritableAccount}, @@ -1752,15 +1751,13 @@ mod tests { ); let mut feature_set = FeatureSet::all_enabled(); - feature_set.deactivate(&solana_sdk::feature_set::remove_deprecated_request_unit_ix::id()); + feature_set.deactivate(&remove_deprecated_request_unit_ix::id()); let message = SanitizedMessage::try_from(tx.message().clone()).unwrap(); let fee = FeeStructure::default().calculate_fee( &message, lamports_per_signature, - &process_compute_budget_instructions(message.program_instructions_iter(), &feature_set) - .unwrap_or_default() - .into(), + &ComputeBudget::fee_budget_limits(message.program_instructions_iter(), &feature_set), true, false, ); @@ -4256,11 +4253,7 @@ mod tests { let result_no_limit = Ok(None); let result_default_limit = Ok(Some( - NonZeroUsize::new( - usize::try_from(compute_budget_processor::MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES) - .unwrap(), - ) - .unwrap(), + NonZeroUsize::new(compute_budget::MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES).unwrap(), )); let result_requested_limit: Result> = Ok(Some(NonZeroUsize::new(99).unwrap())); @@ -4288,10 +4281,7 @@ mod tests { // if tx doesn't set limit, then default limit (64MiB) // if tx sets limit, then requested limit // if tx sets limit to zero, then TransactionError::InvalidLoadedAccountsDataSizeLimit - feature_set.activate( - &solana_sdk::feature_set::add_set_tx_loaded_accounts_data_size_instruction::id(), - 0, - ); + feature_set.activate(&add_set_tx_loaded_accounts_data_size_instruction::id(), 0); test(tx_not_set_limit, &feature_set, &result_default_limit); test(tx_set_limit_99, &feature_set, &result_requested_limit); test(tx_set_limit_0, &feature_set, &result_invalid_limit); @@ -4326,15 +4316,13 @@ mod tests { ); let mut feature_set = FeatureSet::all_enabled(); - feature_set.deactivate(&solana_sdk::feature_set::remove_deprecated_request_unit_ix::id()); + feature_set.deactivate(&remove_deprecated_request_unit_ix::id()); let message = SanitizedMessage::try_from(tx.message().clone()).unwrap(); let fee = FeeStructure::default().calculate_fee( &message, lamports_per_signature, - &process_compute_budget_instructions(message.program_instructions_iter(), &feature_set) - .unwrap_or_default() - .into(), + &ComputeBudget::fee_budget_limits(message.program_instructions_iter(), &feature_set), true, false, ); diff --git a/cost-model/src/cost_model.rs b/cost-model/src/cost_model.rs index bb3e296d6..0e8d69542 100644 --- a/cost-model/src/cost_model.rs +++ b/cost-model/src/cost_model.rs @@ -8,17 +8,17 @@ use { crate::{block_cost_limits::*, transaction_cost::*}, log::*, - solana_program_runtime::{ - compute_budget::DEFAULT_HEAP_COST, - compute_budget_processor::{ - process_compute_budget_instructions, ComputeBudgetLimits, - DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT, MAX_COMPUTE_UNIT_LIMIT, - }, + solana_program_runtime::compute_budget::{ + ComputeBudget, DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT, MAX_COMPUTE_UNIT_LIMIT, }, solana_sdk::{ borsh0_10::try_from_slice_unchecked, compute_budget::{self, ComputeBudgetInstruction}, - feature_set::{include_loaded_accounts_data_size_in_fee_calculation, FeatureSet}, + feature_set::{ + add_set_tx_loaded_accounts_data_size_instruction, + include_loaded_accounts_data_size_in_fee_calculation, + remove_deprecated_request_unit_ix, FeatureSet, + }, fee::FeeStructure, instruction::CompiledInstruction, program_utils::limited_deserialize, @@ -62,12 +62,10 @@ impl CostModel { // to set limit, `compute_budget.loaded_accounts_data_size_limit` is set to default // limit of 64MB; which will convert to (64M/32K)*8CU = 16_000 CUs // - pub fn calculate_loaded_accounts_data_size_cost( - compute_budget_limits: &ComputeBudgetLimits, - ) -> u64 { + pub fn calculate_loaded_accounts_data_size_cost(compute_budget: &ComputeBudget) -> u64 { FeeStructure::calculate_memory_usage_cost( - usize::try_from(compute_budget_limits.loaded_accounts_bytes).unwrap(), - DEFAULT_HEAP_COST, + compute_budget.loaded_accounts_data_size_limit, + compute_budget.heap_cost, ) } @@ -130,28 +128,32 @@ impl CostModel { } // calculate bpf cost based on compute budget instructions + let mut compute_budget = ComputeBudget::default(); + + let result = compute_budget.process_instructions( + transaction.message().program_instructions_iter(), + !feature_set.is_active(&remove_deprecated_request_unit_ix::id()), + feature_set.is_active(&add_set_tx_loaded_accounts_data_size_instruction::id()), + ); // if failed to process compute_budget instructions, the transaction will not be executed // by `bank`, therefore it should be considered as no execution cost by cost model. - match process_compute_budget_instructions( - transaction.message().program_instructions_iter(), - feature_set, - ) { - Ok(compute_budget_limits) => { + match result { + Ok(_) => { // if tx contained user-space instructions and a more accurate estimate available correct it, // where "user-space instructions" must be specifically checked by // 'compute_unit_limit_is_set' flag, because compute_budget does not distinguish // builtin and bpf instructions when calculating default compute-unit-limit. (see // compute_budget.rs test `test_process_mixed_instructions_without_compute_budget`) if bpf_costs > 0 && compute_unit_limit_is_set { - bpf_costs = u64::from(compute_budget_limits.compute_unit_limit); + bpf_costs = compute_budget.compute_unit_limit } if feature_set .is_active(&include_loaded_accounts_data_size_in_fee_calculation::id()) { loaded_accounts_data_size_cost = - Self::calculate_loaded_accounts_data_size_cost(&compute_budget_limits); + Self::calculate_loaded_accounts_data_size_cost(&compute_budget); } } Err(_) => { @@ -543,8 +545,7 @@ mod tests { // default loaded_accounts_data_size_limit const DEFAULT_PAGE_COST: u64 = 8; let expected_loaded_accounts_data_size_cost = - solana_program_runtime::compute_budget_processor::MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES - as u64 + solana_program_runtime::compute_budget::MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES as u64 / ACCOUNT_DATA_COST_PAGE_SIZE * DEFAULT_PAGE_COST; @@ -662,36 +663,36 @@ mod tests { #[allow(clippy::field_reassign_with_default)] #[test] fn test_calculate_loaded_accounts_data_size_cost() { - let mut compute_budget_limits = ComputeBudgetLimits::default(); + let mut compute_budget = ComputeBudget::default(); // accounts data size are priced in block of 32K, ... // ... requesting less than 32K should still be charged as one block - compute_budget_limits.loaded_accounts_bytes = 31 * 1024; + compute_budget.loaded_accounts_data_size_limit = 31_usize * 1024; assert_eq!( - DEFAULT_HEAP_COST, - CostModel::calculate_loaded_accounts_data_size_cost(&compute_budget_limits) + compute_budget.heap_cost, + CostModel::calculate_loaded_accounts_data_size_cost(&compute_budget) ); // ... requesting exact 32K should be charged as one block - compute_budget_limits.loaded_accounts_bytes = 32 * 1024; + compute_budget.loaded_accounts_data_size_limit = 32_usize * 1024; assert_eq!( - DEFAULT_HEAP_COST, - CostModel::calculate_loaded_accounts_data_size_cost(&compute_budget_limits) + compute_budget.heap_cost, + CostModel::calculate_loaded_accounts_data_size_cost(&compute_budget) ); // ... requesting slightly above 32K should be charged as 2 block - compute_budget_limits.loaded_accounts_bytes = 33 * 1024; + compute_budget.loaded_accounts_data_size_limit = 33_usize * 1024; assert_eq!( - DEFAULT_HEAP_COST * 2, - CostModel::calculate_loaded_accounts_data_size_cost(&compute_budget_limits) + compute_budget.heap_cost * 2, + CostModel::calculate_loaded_accounts_data_size_cost(&compute_budget) ); // ... requesting exact 64K should be charged as 2 block - compute_budget_limits.loaded_accounts_bytes = 64 * 1024; + compute_budget.loaded_accounts_data_size_limit = 64_usize * 1024; assert_eq!( - DEFAULT_HEAP_COST * 2, - CostModel::calculate_loaded_accounts_data_size_cost(&compute_budget_limits) + compute_budget.heap_cost * 2, + CostModel::calculate_loaded_accounts_data_size_cost(&compute_budget) ); } diff --git a/program-runtime/src/compute_budget.rs b/program-runtime/src/compute_budget.rs index a568162c1..f9239224b 100644 --- a/program-runtime/src/compute_budget.rs +++ b/program-runtime/src/compute_budget.rs @@ -1,11 +1,28 @@ use { - crate::compute_budget_processor::{self, process_compute_budget_instructions}, + crate::prioritization_fee::{PrioritizationFeeDetails, PrioritizationFeeType}, solana_sdk::{ - feature_set::FeatureSet, instruction::CompiledInstruction, pubkey::Pubkey, - transaction::Result, + borsh0_10::try_from_slice_unchecked, + compute_budget::{self, ComputeBudgetInstruction}, + entrypoint::HEAP_LENGTH as MIN_HEAP_FRAME_BYTES, + feature_set::{ + add_set_tx_loaded_accounts_data_size_instruction, remove_deprecated_request_unit_ix, + FeatureSet, + }, + fee::FeeBudgetLimits, + instruction::{CompiledInstruction, InstructionError}, + pubkey::Pubkey, + transaction::TransactionError, }, }; +/// The total accounts data a transaction can load is limited to 64MiB to not break +/// anyone in Mainnet-beta today. It can be set by set_loaded_accounts_data_size_limit instruction +pub const MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES: usize = 64 * 1024 * 1024; + +pub const DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT: u32 = 200_000; +pub const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; +const MAX_HEAP_FRAME_BYTES: u32 = 256 * 1024; + #[cfg(RUSTC_WITH_SPECIALIZATION)] impl ::solana_frozen_abi::abi_example::AbiExample for ComputeBudget { fn example() -> Self { @@ -14,10 +31,6 @@ impl ::solana_frozen_abi::abi_example::AbiExample for ComputeBudget { } } -/// Roughly 0.5us/page, where page is 32K; given roughly 15CU/us, the -/// default heap page cost = 0.5 * 15 ~= 8CU/page -pub const DEFAULT_HEAP_COST: u64 = 8; - #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct ComputeBudget { /// Number of compute units that a transaction or individual instruction is @@ -105,6 +118,9 @@ pub struct ComputeBudget { pub alt_bn128_pairing_one_pair_cost_other: u64, /// Big integer modular exponentiation cost pub big_modular_exponentiation_cost: u64, + /// Maximum accounts data size, in bytes, that a transaction is allowed to load; The + /// value is capped by MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES to prevent overuse of memory. + pub loaded_accounts_data_size_limit: usize, /// Coefficient `a` of the quadratic function which determines the number /// of compute units consumed to call poseidon syscall for a given number /// of inputs. @@ -127,7 +143,7 @@ pub struct ComputeBudget { impl Default for ComputeBudget { fn default() -> Self { - Self::new(compute_budget_processor::MAX_COMPUTE_UNIT_LIMIT as u64) + Self::new(MAX_COMPUTE_UNIT_LIMIT as u64) } } @@ -164,13 +180,14 @@ impl ComputeBudget { curve25519_ristretto_msm_base_cost: 2303, curve25519_ristretto_msm_incremental_cost: 788, heap_size: u32::try_from(solana_sdk::entrypoint::HEAP_LENGTH).unwrap(), - heap_cost: DEFAULT_HEAP_COST, + heap_cost: 8, mem_op_base_cost: 10, alt_bn128_addition_cost: 334, alt_bn128_multiplication_cost: 3_840, alt_bn128_pairing_one_pair_cost_first: 36_364, alt_bn128_pairing_one_pair_cost_other: 12_121, big_modular_exponentiation_cost: 33, + loaded_accounts_data_size_limit: MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES, poseidon_cost_coefficient_a: 61, poseidon_cost_coefficient_c: 542, get_remaining_compute_units_cost: 100, @@ -181,16 +198,127 @@ impl ComputeBudget { } } - pub fn try_from_instructions<'a>( + pub fn process_instructions<'a>( + &mut self, + instructions: impl Iterator, + support_request_units_deprecated: bool, + support_set_loaded_accounts_data_size_limit_ix: bool, + ) -> Result { + let mut num_non_compute_budget_instructions: u32 = 0; + let mut updated_compute_unit_limit = None; + let mut requested_heap_size = None; + let mut prioritization_fee = None; + let mut updated_loaded_accounts_data_size_limit = None; + + for (i, (program_id, instruction)) in instructions.enumerate() { + if compute_budget::check_id(program_id) { + let invalid_instruction_data_error = TransactionError::InstructionError( + i as u8, + InstructionError::InvalidInstructionData, + ); + let duplicate_instruction_error = TransactionError::DuplicateInstruction(i as u8); + + match try_from_slice_unchecked(&instruction.data) { + Ok(ComputeBudgetInstruction::RequestUnitsDeprecated { + units: compute_unit_limit, + additional_fee, + }) if support_request_units_deprecated => { + if updated_compute_unit_limit.is_some() { + return Err(duplicate_instruction_error); + } + if prioritization_fee.is_some() { + return Err(duplicate_instruction_error); + } + updated_compute_unit_limit = Some(compute_unit_limit); + prioritization_fee = + Some(PrioritizationFeeType::Deprecated(additional_fee as u64)); + } + Ok(ComputeBudgetInstruction::RequestHeapFrame(bytes)) => { + if requested_heap_size.is_some() { + return Err(duplicate_instruction_error); + } + requested_heap_size = Some((bytes, i as u8)); + } + Ok(ComputeBudgetInstruction::SetComputeUnitLimit(compute_unit_limit)) => { + if updated_compute_unit_limit.is_some() { + return Err(duplicate_instruction_error); + } + updated_compute_unit_limit = Some(compute_unit_limit); + } + Ok(ComputeBudgetInstruction::SetComputeUnitPrice(micro_lamports)) => { + if prioritization_fee.is_some() { + return Err(duplicate_instruction_error); + } + prioritization_fee = + Some(PrioritizationFeeType::ComputeUnitPrice(micro_lamports)); + } + Ok(ComputeBudgetInstruction::SetLoadedAccountsDataSizeLimit(bytes)) + if support_set_loaded_accounts_data_size_limit_ix => + { + if updated_loaded_accounts_data_size_limit.is_some() { + return Err(duplicate_instruction_error); + } + updated_loaded_accounts_data_size_limit = Some(bytes as usize); + } + _ => return Err(invalid_instruction_data_error), + } + } else { + // only include non-request instructions in default max calc + num_non_compute_budget_instructions = + num_non_compute_budget_instructions.saturating_add(1); + } + } + + if let Some((bytes, i)) = requested_heap_size { + if bytes > MAX_HEAP_FRAME_BYTES + || bytes < MIN_HEAP_FRAME_BYTES as u32 + || bytes % 1024 != 0 + { + return Err(TransactionError::InstructionError( + i, + InstructionError::InvalidInstructionData, + )); + } + self.heap_size = bytes; + } + + let compute_unit_limit = updated_compute_unit_limit + .unwrap_or_else(|| { + num_non_compute_budget_instructions + .saturating_mul(DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT) + }) + .min(MAX_COMPUTE_UNIT_LIMIT); + self.compute_unit_limit = u64::from(compute_unit_limit); + + self.loaded_accounts_data_size_limit = updated_loaded_accounts_data_size_limit + .unwrap_or(MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES) + .min(MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES); + + Ok(prioritization_fee + .map(|fee_type| PrioritizationFeeDetails::new(fee_type, self.compute_unit_limit)) + .unwrap_or_default()) + } + + pub fn fee_budget_limits<'a>( instructions: impl Iterator, feature_set: &FeatureSet, - ) -> Result { - let compute_budget_limits = process_compute_budget_instructions(instructions, feature_set)?; - Ok(ComputeBudget { - compute_unit_limit: u64::from(compute_budget_limits.compute_unit_limit), - heap_size: compute_budget_limits.updated_heap_bytes, - ..ComputeBudget::default() - }) + ) -> FeeBudgetLimits { + let mut compute_budget = Self::default(); + + let prioritization_fee_details = compute_budget + .process_instructions( + instructions, + !feature_set.is_active(&remove_deprecated_request_unit_ix::id()), + feature_set.is_active(&add_set_tx_loaded_accounts_data_size_instruction::id()), + ) + .unwrap_or_default(); + + FeeBudgetLimits { + loaded_accounts_data_size_limit: compute_budget.loaded_accounts_data_size_limit, + heap_cost: compute_budget.heap_cost, + compute_unit_limit: compute_budget.compute_unit_limit, + prioritization_fee: prioritization_fee_details.get_fee(), + } } /// Returns cost of the Poseidon hash function for the given number of @@ -222,3 +350,489 @@ impl ComputeBudget { Some(final_result) } } + +#[cfg(test)] +mod tests { + use { + super::*, + solana_sdk::{ + hash::Hash, + instruction::Instruction, + message::Message, + pubkey::Pubkey, + signature::Keypair, + signer::Signer, + system_instruction::{self}, + transaction::{SanitizedTransaction, Transaction}, + }, + }; + + macro_rules! test { + ( $instructions: expr, $expected_result: expr, $expected_budget: expr, $support_set_loaded_accounts_data_size_limit_ix: expr ) => { + let payer_keypair = Keypair::new(); + let tx = SanitizedTransaction::from_transaction_for_tests(Transaction::new( + &[&payer_keypair], + Message::new($instructions, Some(&payer_keypair.pubkey())), + Hash::default(), + )); + let mut compute_budget = ComputeBudget::default(); + let result = compute_budget.process_instructions( + tx.message().program_instructions_iter(), + false, /*not support request_units_deprecated*/ + $support_set_loaded_accounts_data_size_limit_ix, + ); + assert_eq!($expected_result, result); + assert_eq!(compute_budget, $expected_budget); + }; + ( $instructions: expr, $expected_result: expr, $expected_budget: expr) => { + test!($instructions, $expected_result, $expected_budget, false); + }; + } + + #[test] + fn test_process_instructions() { + // Units + test!( + &[], + Ok(PrioritizationFeeDetails::default()), + ComputeBudget { + compute_unit_limit: 0, + ..ComputeBudget::default() + } + ); + test!( + &[ + ComputeBudgetInstruction::set_compute_unit_limit(1), + Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]), + ], + Ok(PrioritizationFeeDetails::default()), + ComputeBudget { + compute_unit_limit: 1, + ..ComputeBudget::default() + } + ); + test!( + &[ + ComputeBudgetInstruction::set_compute_unit_limit(MAX_COMPUTE_UNIT_LIMIT + 1), + Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]), + ], + Ok(PrioritizationFeeDetails::default()), + ComputeBudget { + compute_unit_limit: MAX_COMPUTE_UNIT_LIMIT as u64, + ..ComputeBudget::default() + } + ); + test!( + &[ + Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]), + ComputeBudgetInstruction::set_compute_unit_limit(MAX_COMPUTE_UNIT_LIMIT), + ], + Ok(PrioritizationFeeDetails::default()), + ComputeBudget { + compute_unit_limit: MAX_COMPUTE_UNIT_LIMIT as u64, + ..ComputeBudget::default() + } + ); + test!( + &[ + Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]), + Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]), + Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]), + ComputeBudgetInstruction::set_compute_unit_limit(1), + ], + Ok(PrioritizationFeeDetails::default()), + ComputeBudget { + compute_unit_limit: 1, + ..ComputeBudget::default() + } + ); + + test!( + &[ + ComputeBudgetInstruction::set_compute_unit_limit(1), + ComputeBudgetInstruction::set_compute_unit_price(42) + ], + Ok(PrioritizationFeeDetails::new( + PrioritizationFeeType::ComputeUnitPrice(42), + 1 + )), + ComputeBudget { + compute_unit_limit: 1, + ..ComputeBudget::default() + } + ); + + // HeapFrame + test!( + &[], + Ok(PrioritizationFeeDetails::default()), + ComputeBudget { + compute_unit_limit: 0, + ..ComputeBudget::default() + } + ); + test!( + &[ + ComputeBudgetInstruction::request_heap_frame(40 * 1024), + Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]), + ], + Ok(PrioritizationFeeDetails::default()), + ComputeBudget { + compute_unit_limit: DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT as u64, + heap_size: 40 * 1024, + ..ComputeBudget::default() + } + ); + test!( + &[ + ComputeBudgetInstruction::request_heap_frame(40 * 1024 + 1), + Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]), + ], + Err(TransactionError::InstructionError( + 0, + InstructionError::InvalidInstructionData, + )), + ComputeBudget::default() + ); + test!( + &[ + ComputeBudgetInstruction::request_heap_frame(31 * 1024), + Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]), + ], + Err(TransactionError::InstructionError( + 0, + InstructionError::InvalidInstructionData, + )), + ComputeBudget::default() + ); + test!( + &[ + ComputeBudgetInstruction::request_heap_frame(MAX_HEAP_FRAME_BYTES + 1), + Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]), + ], + Err(TransactionError::InstructionError( + 0, + InstructionError::InvalidInstructionData, + )), + ComputeBudget::default() + ); + test!( + &[ + Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]), + ComputeBudgetInstruction::request_heap_frame(MAX_HEAP_FRAME_BYTES), + ], + Ok(PrioritizationFeeDetails::default()), + ComputeBudget { + compute_unit_limit: DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT as u64, + heap_size: MAX_HEAP_FRAME_BYTES, + ..ComputeBudget::default() + } + ); + test!( + &[ + Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]), + Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]), + Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]), + ComputeBudgetInstruction::request_heap_frame(1), + ], + Err(TransactionError::InstructionError( + 3, + InstructionError::InvalidInstructionData, + )), + ComputeBudget::default() + ); + + test!( + &[ + Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]), + Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]), + Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]), + Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]), + Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]), + Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]), + Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]), + Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]), + ], + Ok(PrioritizationFeeDetails::default()), + ComputeBudget { + compute_unit_limit: DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT as u64 * 7, + ..ComputeBudget::default() + } + ); + + // Combined + test!( + &[ + Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]), + ComputeBudgetInstruction::request_heap_frame(MAX_HEAP_FRAME_BYTES), + ComputeBudgetInstruction::set_compute_unit_limit(MAX_COMPUTE_UNIT_LIMIT), + ComputeBudgetInstruction::set_compute_unit_price(u64::MAX), + ], + Ok(PrioritizationFeeDetails::new( + PrioritizationFeeType::ComputeUnitPrice(u64::MAX), + MAX_COMPUTE_UNIT_LIMIT as u64, + )), + ComputeBudget { + compute_unit_limit: MAX_COMPUTE_UNIT_LIMIT as u64, + heap_size: MAX_HEAP_FRAME_BYTES, + ..ComputeBudget::default() + } + ); + + test!( + &[ + Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]), + ComputeBudgetInstruction::set_compute_unit_limit(1), + ComputeBudgetInstruction::request_heap_frame(MAX_HEAP_FRAME_BYTES), + ComputeBudgetInstruction::set_compute_unit_price(u64::MAX), + ], + Ok(PrioritizationFeeDetails::new( + PrioritizationFeeType::ComputeUnitPrice(u64::MAX), + 1 + )), + ComputeBudget { + compute_unit_limit: 1, + heap_size: MAX_HEAP_FRAME_BYTES, + ..ComputeBudget::default() + } + ); + + // Duplicates + test!( + &[ + Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]), + ComputeBudgetInstruction::set_compute_unit_limit(MAX_COMPUTE_UNIT_LIMIT), + ComputeBudgetInstruction::set_compute_unit_limit(MAX_COMPUTE_UNIT_LIMIT - 1), + ], + Err(TransactionError::DuplicateInstruction(2)), + ComputeBudget::default() + ); + + test!( + &[ + Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]), + ComputeBudgetInstruction::request_heap_frame(MIN_HEAP_FRAME_BYTES as u32), + ComputeBudgetInstruction::request_heap_frame(MAX_HEAP_FRAME_BYTES), + ], + Err(TransactionError::DuplicateInstruction(2)), + ComputeBudget::default() + ); + + test!( + &[ + Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]), + ComputeBudgetInstruction::set_compute_unit_price(0), + ComputeBudgetInstruction::set_compute_unit_price(u64::MAX), + ], + Err(TransactionError::DuplicateInstruction(2)), + ComputeBudget::default() + ); + + // deprecated + test!( + &[Instruction::new_with_borsh( + compute_budget::id(), + &compute_budget::ComputeBudgetInstruction::RequestUnitsDeprecated { + units: 1_000, + additional_fee: 10 + }, + vec![] + )], + Err(TransactionError::InstructionError( + 0, + InstructionError::InvalidInstructionData, + )), + ComputeBudget::default() + ); + } + + #[test] + fn test_process_loaded_accounts_data_size_limit_instruction() { + // Assert for empty instructions, change value of support_set_loaded_accounts_data_size_limit_ix + // will not change results, which should all be default + for support_set_loaded_accounts_data_size_limit_ix in [true, false] { + test!( + &[], + Ok(PrioritizationFeeDetails::default()), + ComputeBudget { + compute_unit_limit: 0, + ..ComputeBudget::default() + }, + support_set_loaded_accounts_data_size_limit_ix + ); + } + + // Assert when set_loaded_accounts_data_size_limit presents, + // if support_set_loaded_accounts_data_size_limit_ix then + // budget is set with data_size + // else + // return InstructionError + let data_size: usize = 1; + for support_set_loaded_accounts_data_size_limit_ix in [true, false] { + let (expected_result, expected_budget) = + if support_set_loaded_accounts_data_size_limit_ix { + ( + Ok(PrioritizationFeeDetails::default()), + ComputeBudget { + compute_unit_limit: DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT as u64, + loaded_accounts_data_size_limit: data_size, + ..ComputeBudget::default() + }, + ) + } else { + ( + Err(TransactionError::InstructionError( + 0, + InstructionError::InvalidInstructionData, + )), + ComputeBudget::default(), + ) + }; + + test!( + &[ + ComputeBudgetInstruction::set_loaded_accounts_data_size_limit(data_size as u32), + Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]), + ], + expected_result, + expected_budget, + support_set_loaded_accounts_data_size_limit_ix + ); + } + + // Assert when set_loaded_accounts_data_size_limit presents, with greater than max value + // if support_set_loaded_accounts_data_size_limit_ix then + // budget is set to max data size + // else + // return InstructionError + let data_size: usize = MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES + 1; + for support_set_loaded_accounts_data_size_limit_ix in [true, false] { + let (expected_result, expected_budget) = + if support_set_loaded_accounts_data_size_limit_ix { + ( + Ok(PrioritizationFeeDetails::default()), + ComputeBudget { + compute_unit_limit: DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT as u64, + loaded_accounts_data_size_limit: MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES, + ..ComputeBudget::default() + }, + ) + } else { + ( + Err(TransactionError::InstructionError( + 0, + InstructionError::InvalidInstructionData, + )), + ComputeBudget::default(), + ) + }; + + test!( + &[ + ComputeBudgetInstruction::set_loaded_accounts_data_size_limit(data_size as u32), + Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]), + ], + expected_result, + expected_budget, + support_set_loaded_accounts_data_size_limit_ix + ); + } + + // Assert when set_loaded_accounts_data_size_limit is not presented + // if support_set_loaded_accounts_data_size_limit_ix then + // budget is set to default data size + // else + // return + for support_set_loaded_accounts_data_size_limit_ix in [true, false] { + let (expected_result, expected_budget) = ( + Ok(PrioritizationFeeDetails::default()), + ComputeBudget { + compute_unit_limit: DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT as u64, + loaded_accounts_data_size_limit: MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES, + ..ComputeBudget::default() + }, + ); + + test!( + &[Instruction::new_with_bincode( + Pubkey::new_unique(), + &0_u8, + vec![] + ),], + expected_result, + expected_budget, + support_set_loaded_accounts_data_size_limit_ix + ); + } + + // Assert when set_loaded_accounts_data_size_limit presents more than once, + // if support_set_loaded_accounts_data_size_limit_ix then + // return DuplicateInstruction + // else + // return InstructionError + let data_size: usize = MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES; + for support_set_loaded_accounts_data_size_limit_ix in [true, false] { + let (expected_result, expected_budget) = + if support_set_loaded_accounts_data_size_limit_ix { + ( + Err(TransactionError::DuplicateInstruction(2)), + ComputeBudget::default(), + ) + } else { + ( + Err(TransactionError::InstructionError( + 1, + InstructionError::InvalidInstructionData, + )), + ComputeBudget::default(), + ) + }; + + test!( + &[ + Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]), + ComputeBudgetInstruction::set_loaded_accounts_data_size_limit(data_size as u32), + ComputeBudgetInstruction::set_loaded_accounts_data_size_limit(data_size as u32), + ], + expected_result, + expected_budget, + support_set_loaded_accounts_data_size_limit_ix + ); + } + } + + #[test] + fn test_process_mixed_instructions_without_compute_budget() { + let payer_keypair = Keypair::new(); + + let transaction = + SanitizedTransaction::from_transaction_for_tests(Transaction::new_signed_with_payer( + &[ + Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]), + system_instruction::transfer(&payer_keypair.pubkey(), &Pubkey::new_unique(), 2), + ], + Some(&payer_keypair.pubkey()), + &[&payer_keypair], + Hash::default(), + )); + + let mut compute_budget = ComputeBudget::default(); + let result = compute_budget.process_instructions( + transaction.message().program_instructions_iter(), + false, //not support request_units_deprecated + true, //support_set_loaded_accounts_data_size_limit_ix, + ); + + // assert process_instructions will be successful with default, + assert_eq!(Ok(PrioritizationFeeDetails::default()), result); + // assert the default compute_unit_limit is 2 times default: one for bpf ix, one for + // builtin ix. + assert_eq!( + compute_budget, + ComputeBudget { + compute_unit_limit: 2 * DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT as u64, + ..ComputeBudget::default() + } + ); + } +} diff --git a/program-runtime/src/compute_budget_processor.rs b/program-runtime/src/compute_budget_processor.rs deleted file mode 100644 index be5e642fa..000000000 --- a/program-runtime/src/compute_budget_processor.rs +++ /dev/null @@ -1,619 +0,0 @@ -//! Process compute_budget instructions to extract and sanitize limits. -use { - crate::{ - compute_budget::DEFAULT_HEAP_COST, - prioritization_fee::{PrioritizationFeeDetails, PrioritizationFeeType}, - }, - solana_sdk::{ - borsh0_10::try_from_slice_unchecked, - compute_budget::{self, ComputeBudgetInstruction}, - entrypoint::HEAP_LENGTH as MIN_HEAP_FRAME_BYTES, - feature_set::{ - add_set_tx_loaded_accounts_data_size_instruction, remove_deprecated_request_unit_ix, - FeatureSet, - }, - fee::FeeBudgetLimits, - instruction::{CompiledInstruction, InstructionError}, - pubkey::Pubkey, - transaction::TransactionError, - }, -}; - -const MAX_HEAP_FRAME_BYTES: u32 = 256 * 1024; -pub const DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT: u32 = 200_000; -pub const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; - -/// The total accounts data a transaction can load is limited to 64MiB to not break -/// anyone in Mainnet-beta today. It can be set by set_loaded_accounts_data_size_limit instruction -pub const MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES: u32 = 64 * 1024 * 1024; - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct ComputeBudgetLimits { - pub updated_heap_bytes: u32, - pub compute_unit_limit: u32, - pub compute_unit_price: u64, - pub loaded_accounts_bytes: u32, -} - -impl Default for ComputeBudgetLimits { - fn default() -> Self { - ComputeBudgetLimits { - updated_heap_bytes: u32::try_from(MIN_HEAP_FRAME_BYTES).unwrap(), - compute_unit_limit: MAX_COMPUTE_UNIT_LIMIT, - compute_unit_price: 0, - loaded_accounts_bytes: MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES, - } - } -} - -impl From for FeeBudgetLimits { - fn from(val: ComputeBudgetLimits) -> Self { - let prioritization_fee_details = PrioritizationFeeDetails::new( - PrioritizationFeeType::ComputeUnitPrice(val.compute_unit_price), - u64::from(val.compute_unit_limit), - ); - FeeBudgetLimits { - // NOTE - usize::from(u32).unwrap() may fail if target is 16-bit and - // `loaded_accounts_bytes` is greater than u16::MAX. In that case, panic is proper. - loaded_accounts_data_size_limit: usize::try_from(val.loaded_accounts_bytes).unwrap(), - heap_cost: DEFAULT_HEAP_COST, - compute_unit_limit: u64::from(val.compute_unit_limit), - prioritization_fee: prioritization_fee_details.get_fee(), - } - } -} - -/// Processing compute_budget could be part of tx sanitizing, failed to process -/// these instructions will drop the transaction eventually without execution, -/// may as well fail it early. -/// If succeeded, the transaction's specific limits/requests (could be default) -/// are retrieved and returned, -pub fn process_compute_budget_instructions<'a>( - instructions: impl Iterator, - feature_set: &FeatureSet, -) -> Result { - let support_request_units_deprecated = - !feature_set.is_active(&remove_deprecated_request_unit_ix::id()); - let support_set_loaded_accounts_data_size_limit_ix = - feature_set.is_active(&add_set_tx_loaded_accounts_data_size_instruction::id()); - - let mut num_non_compute_budget_instructions: u32 = 0; - let mut updated_compute_unit_limit = None; - let mut updated_compute_unit_price = None; - let mut requested_heap_size = None; - let mut updated_loaded_accounts_data_size_limit = None; - - for (i, (program_id, instruction)) in instructions.enumerate() { - if compute_budget::check_id(program_id) { - let invalid_instruction_data_error = TransactionError::InstructionError( - i as u8, - InstructionError::InvalidInstructionData, - ); - let duplicate_instruction_error = TransactionError::DuplicateInstruction(i as u8); - - match try_from_slice_unchecked(&instruction.data) { - Ok(ComputeBudgetInstruction::RequestUnitsDeprecated { - units: compute_unit_limit, - additional_fee, - }) if support_request_units_deprecated => { - if updated_compute_unit_limit.is_some() { - return Err(duplicate_instruction_error); - } - if updated_compute_unit_price.is_some() { - return Err(duplicate_instruction_error); - } - updated_compute_unit_limit = Some(compute_unit_limit); - updated_compute_unit_price = - support_deprecated_requested_units(additional_fee, compute_unit_limit); - } - Ok(ComputeBudgetInstruction::RequestHeapFrame(bytes)) => { - if requested_heap_size.is_some() { - return Err(duplicate_instruction_error); - } - if sanitize_requested_heap_size(bytes) { - requested_heap_size = Some(bytes); - } else { - return Err(invalid_instruction_data_error); - } - } - Ok(ComputeBudgetInstruction::SetComputeUnitLimit(compute_unit_limit)) => { - if updated_compute_unit_limit.is_some() { - return Err(duplicate_instruction_error); - } - updated_compute_unit_limit = Some(compute_unit_limit); - } - Ok(ComputeBudgetInstruction::SetComputeUnitPrice(micro_lamports)) => { - if updated_compute_unit_price.is_some() { - return Err(duplicate_instruction_error); - } - updated_compute_unit_price = Some(micro_lamports); - } - Ok(ComputeBudgetInstruction::SetLoadedAccountsDataSizeLimit(bytes)) - if support_set_loaded_accounts_data_size_limit_ix => - { - if updated_loaded_accounts_data_size_limit.is_some() { - return Err(duplicate_instruction_error); - } - updated_loaded_accounts_data_size_limit = Some(bytes); - } - _ => return Err(invalid_instruction_data_error), - } - } else { - // only include non-request instructions in default max calc - num_non_compute_budget_instructions = - num_non_compute_budget_instructions.saturating_add(1); - } - } - - // sanitize limits - let updated_heap_bytes = requested_heap_size - .unwrap_or(u32::try_from(MIN_HEAP_FRAME_BYTES).unwrap()) // loader's default heap_size - .min(MAX_HEAP_FRAME_BYTES); - - let compute_unit_limit = updated_compute_unit_limit - .unwrap_or_else(|| { - num_non_compute_budget_instructions - .saturating_mul(DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT) - }) - .min(MAX_COMPUTE_UNIT_LIMIT); - - let compute_unit_price = updated_compute_unit_price.unwrap_or(0); - - let loaded_accounts_bytes = updated_loaded_accounts_data_size_limit - .unwrap_or(MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES) - .min(MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES); - - Ok(ComputeBudgetLimits { - updated_heap_bytes, - compute_unit_limit, - compute_unit_price, - loaded_accounts_bytes, - }) -} - -fn sanitize_requested_heap_size(bytes: u32) -> bool { - (u32::try_from(MIN_HEAP_FRAME_BYTES).unwrap()..=MAX_HEAP_FRAME_BYTES).contains(&bytes) - && bytes % 1024 == 0 -} - -// Supports request_units_derpecated ix, returns cu_price if available. -fn support_deprecated_requested_units(additional_fee: u32, compute_unit_limit: u32) -> Option { - // TODO: remove support of 'Deprecated' after feature remove_deprecated_request_unit_ix::id() is activated - const MICRO_LAMPORTS_PER_LAMPORT: u64 = 1_000_000; - - let micro_lamport_fee = - (additional_fee as u128).saturating_mul(MICRO_LAMPORTS_PER_LAMPORT as u128); - micro_lamport_fee - .checked_div(compute_unit_limit as u128) - .map(|cu_price| u64::try_from(cu_price).unwrap_or(u64::MAX)) -} - -#[cfg(test)] -mod tests { - use { - super::*, - solana_sdk::{ - hash::Hash, - instruction::Instruction, - message::Message, - pubkey::Pubkey, - signature::Keypair, - signer::Signer, - system_instruction::{self}, - transaction::{SanitizedTransaction, Transaction}, - }, - }; - - macro_rules! test { - ( $instructions: expr, $expected_result: expr, $support_set_loaded_accounts_data_size_limit_ix: expr ) => { - let payer_keypair = Keypair::new(); - let tx = SanitizedTransaction::from_transaction_for_tests(Transaction::new( - &[&payer_keypair], - Message::new($instructions, Some(&payer_keypair.pubkey())), - Hash::default(), - )); - let mut feature_set = FeatureSet::default(); - feature_set.activate(&remove_deprecated_request_unit_ix::id(), 0); - if $support_set_loaded_accounts_data_size_limit_ix { - feature_set.activate(&add_set_tx_loaded_accounts_data_size_instruction::id(), 0); - } - let result = process_compute_budget_instructions( - tx.message().program_instructions_iter(), - &feature_set, - ); - assert_eq!($expected_result, result); - }; - ( $instructions: expr, $expected_result: expr ) => { - test!($instructions, $expected_result, false); - }; - } - - #[test] - fn test_process_instructions() { - // Units - test!( - &[], - Ok(ComputeBudgetLimits { - compute_unit_limit: 0, - ..ComputeBudgetLimits::default() - }) - ); - test!( - &[ - ComputeBudgetInstruction::set_compute_unit_limit(1), - Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]), - ], - Ok(ComputeBudgetLimits { - compute_unit_limit: 1, - ..ComputeBudgetLimits::default() - }) - ); - test!( - &[ - ComputeBudgetInstruction::set_compute_unit_limit(MAX_COMPUTE_UNIT_LIMIT + 1), - Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]), - ], - Ok(ComputeBudgetLimits { - compute_unit_limit: MAX_COMPUTE_UNIT_LIMIT, - ..ComputeBudgetLimits::default() - }) - ); - test!( - &[ - Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]), - ComputeBudgetInstruction::set_compute_unit_limit(MAX_COMPUTE_UNIT_LIMIT), - ], - Ok(ComputeBudgetLimits { - compute_unit_limit: MAX_COMPUTE_UNIT_LIMIT, - ..ComputeBudgetLimits::default() - }) - ); - test!( - &[ - Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]), - Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]), - Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]), - ComputeBudgetInstruction::set_compute_unit_limit(1), - ], - Ok(ComputeBudgetLimits { - compute_unit_limit: 1, - ..ComputeBudgetLimits::default() - }) - ); - test!( - &[ - ComputeBudgetInstruction::set_compute_unit_limit(1), - ComputeBudgetInstruction::set_compute_unit_price(42) - ], - Ok(ComputeBudgetLimits { - compute_unit_limit: 1, - compute_unit_price: 42, - ..ComputeBudgetLimits::default() - }) - ); - - // HeapFrame - test!( - &[], - Ok(ComputeBudgetLimits { - compute_unit_limit: 0, - ..ComputeBudgetLimits::default() - }) - ); - test!( - &[ - ComputeBudgetInstruction::request_heap_frame(40 * 1024), - Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]), - ], - Ok(ComputeBudgetLimits { - compute_unit_limit: DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT, - updated_heap_bytes: 40 * 1024, - ..ComputeBudgetLimits::default() - }) - ); - test!( - &[ - ComputeBudgetInstruction::request_heap_frame(40 * 1024 + 1), - Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]), - ], - Err(TransactionError::InstructionError( - 0, - InstructionError::InvalidInstructionData, - )) - ); - test!( - &[ - ComputeBudgetInstruction::request_heap_frame(31 * 1024), - Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]), - ], - Err(TransactionError::InstructionError( - 0, - InstructionError::InvalidInstructionData, - )) - ); - test!( - &[ - ComputeBudgetInstruction::request_heap_frame(MAX_HEAP_FRAME_BYTES + 1), - Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]), - ], - Err(TransactionError::InstructionError( - 0, - InstructionError::InvalidInstructionData, - )) - ); - test!( - &[ - Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]), - ComputeBudgetInstruction::request_heap_frame(MAX_HEAP_FRAME_BYTES), - ], - Ok(ComputeBudgetLimits { - compute_unit_limit: DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT, - updated_heap_bytes: MAX_HEAP_FRAME_BYTES, - ..ComputeBudgetLimits::default() - }) - ); - test!( - &[ - Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]), - Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]), - Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]), - ComputeBudgetInstruction::request_heap_frame(1), - ], - Err(TransactionError::InstructionError( - 3, - InstructionError::InvalidInstructionData, - )) - ); - test!( - &[ - Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]), - Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]), - Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]), - Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]), - Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]), - Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]), - Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]), - Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]), - ], - Ok(ComputeBudgetLimits { - compute_unit_limit: DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT * 7, - ..ComputeBudgetLimits::default() - }) - ); - - // Combined - test!( - &[ - Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]), - ComputeBudgetInstruction::request_heap_frame(MAX_HEAP_FRAME_BYTES), - ComputeBudgetInstruction::set_compute_unit_limit(MAX_COMPUTE_UNIT_LIMIT), - ComputeBudgetInstruction::set_compute_unit_price(u64::MAX), - ], - Ok(ComputeBudgetLimits { - compute_unit_price: u64::MAX, - compute_unit_limit: MAX_COMPUTE_UNIT_LIMIT, - updated_heap_bytes: MAX_HEAP_FRAME_BYTES, - ..ComputeBudgetLimits::default() - }) - ); - test!( - &[ - Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]), - ComputeBudgetInstruction::set_compute_unit_limit(1), - ComputeBudgetInstruction::request_heap_frame(MAX_HEAP_FRAME_BYTES), - ComputeBudgetInstruction::set_compute_unit_price(u64::MAX), - ], - Ok(ComputeBudgetLimits { - compute_unit_price: u64::MAX, - compute_unit_limit: 1, - updated_heap_bytes: MAX_HEAP_FRAME_BYTES, - ..ComputeBudgetLimits::default() - }) - ); - - // Duplicates - test!( - &[ - Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]), - ComputeBudgetInstruction::set_compute_unit_limit(MAX_COMPUTE_UNIT_LIMIT), - ComputeBudgetInstruction::set_compute_unit_limit(MAX_COMPUTE_UNIT_LIMIT - 1), - ], - Err(TransactionError::DuplicateInstruction(2)) - ); - - test!( - &[ - Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]), - ComputeBudgetInstruction::request_heap_frame(MIN_HEAP_FRAME_BYTES as u32), - ComputeBudgetInstruction::request_heap_frame(MAX_HEAP_FRAME_BYTES), - ], - Err(TransactionError::DuplicateInstruction(2)) - ); - test!( - &[ - Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]), - ComputeBudgetInstruction::set_compute_unit_price(0), - ComputeBudgetInstruction::set_compute_unit_price(u64::MAX), - ], - Err(TransactionError::DuplicateInstruction(2)) - ); - - // deprecated - test!( - &[Instruction::new_with_borsh( - compute_budget::id(), - &compute_budget::ComputeBudgetInstruction::RequestUnitsDeprecated { - units: 1_000, - additional_fee: 10 - }, - vec![] - )], - Err(TransactionError::InstructionError( - 0, - InstructionError::InvalidInstructionData, - )) - ); - } - - #[test] - fn test_process_loaded_accounts_data_size_limit_instruction() { - // Assert for empty instructions, change value of support_set_loaded_accounts_data_size_limit_ix - // will not change results, which should all be default - for support_set_loaded_accounts_data_size_limit_ix in [true, false] { - test!( - &[], - Ok(ComputeBudgetLimits { - compute_unit_limit: 0, - ..ComputeBudgetLimits::default() - }), - support_set_loaded_accounts_data_size_limit_ix - ); - } - - // Assert when set_loaded_accounts_data_size_limit presents, - // if support_set_loaded_accounts_data_size_limit_ix then - // budget is set with data_size - // else - // return InstructionError - let data_size = 1; - for support_set_loaded_accounts_data_size_limit_ix in [true, false] { - let expected_result = if support_set_loaded_accounts_data_size_limit_ix { - Ok(ComputeBudgetLimits { - compute_unit_limit: DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT, - loaded_accounts_bytes: data_size, - ..ComputeBudgetLimits::default() - }) - } else { - Err(TransactionError::InstructionError( - 0, - InstructionError::InvalidInstructionData, - )) - }; - - test!( - &[ - ComputeBudgetInstruction::set_loaded_accounts_data_size_limit(data_size), - Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]), - ], - expected_result, - support_set_loaded_accounts_data_size_limit_ix - ); - } - - // Assert when set_loaded_accounts_data_size_limit presents, with greater than max value - // if support_set_loaded_accounts_data_size_limit_ix then - // budget is set to max data size - // else - // return InstructionError - let data_size = MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES + 1; - for support_set_loaded_accounts_data_size_limit_ix in [true, false] { - let expected_result = if support_set_loaded_accounts_data_size_limit_ix { - Ok(ComputeBudgetLimits { - compute_unit_limit: DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT, - loaded_accounts_bytes: MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES, - ..ComputeBudgetLimits::default() - }) - } else { - Err(TransactionError::InstructionError( - 0, - InstructionError::InvalidInstructionData, - )) - }; - - test!( - &[ - ComputeBudgetInstruction::set_loaded_accounts_data_size_limit(data_size), - Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]), - ], - expected_result, - support_set_loaded_accounts_data_size_limit_ix - ); - } - - // Assert when set_loaded_accounts_data_size_limit is not presented - // if support_set_loaded_accounts_data_size_limit_ix then - // budget is set to default data size - // else - // return - for support_set_loaded_accounts_data_size_limit_ix in [true, false] { - let expected_result = Ok(ComputeBudgetLimits { - compute_unit_limit: DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT, - loaded_accounts_bytes: MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES, - ..ComputeBudgetLimits::default() - }); - - test!( - &[Instruction::new_with_bincode( - Pubkey::new_unique(), - &0_u8, - vec![] - ),], - expected_result, - support_set_loaded_accounts_data_size_limit_ix - ); - } - - // Assert when set_loaded_accounts_data_size_limit presents more than once, - // if support_set_loaded_accounts_data_size_limit_ix then - // return DuplicateInstruction - // else - // return InstructionError - let data_size = MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES; - for support_set_loaded_accounts_data_size_limit_ix in [true, false] { - let expected_result = if support_set_loaded_accounts_data_size_limit_ix { - Err(TransactionError::DuplicateInstruction(2)) - } else { - Err(TransactionError::InstructionError( - 1, - InstructionError::InvalidInstructionData, - )) - }; - - test!( - &[ - Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]), - ComputeBudgetInstruction::set_loaded_accounts_data_size_limit(data_size), - ComputeBudgetInstruction::set_loaded_accounts_data_size_limit(data_size), - ], - expected_result, - support_set_loaded_accounts_data_size_limit_ix - ); - } - } - - #[test] - fn test_process_mixed_instructions_without_compute_budget() { - let payer_keypair = Keypair::new(); - - let transaction = - SanitizedTransaction::from_transaction_for_tests(Transaction::new_signed_with_payer( - &[ - Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]), - system_instruction::transfer(&payer_keypair.pubkey(), &Pubkey::new_unique(), 2), - ], - Some(&payer_keypair.pubkey()), - &[&payer_keypair], - Hash::default(), - )); - - let mut feature_set = FeatureSet::default(); - feature_set.activate(&remove_deprecated_request_unit_ix::id(), 0); - feature_set.activate(&add_set_tx_loaded_accounts_data_size_instruction::id(), 0); - - let result = process_compute_budget_instructions( - transaction.message().program_instructions_iter(), - &feature_set, - ); - - // assert process_instructions will be successful with default, - // and the default compute_unit_limit is 2 times default: one for bpf ix, one for - // builtin ix. - assert_eq!( - result, - Ok(ComputeBudgetLimits { - compute_unit_limit: 2 * DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT, - ..ComputeBudgetLimits::default() - }) - ); - } -} diff --git a/program-runtime/src/invoke_context.rs b/program-runtime/src/invoke_context.rs index 566a98dab..9fbe42d8d 100644 --- a/program-runtime/src/invoke_context.rs +++ b/program-runtime/src/invoke_context.rs @@ -756,7 +756,7 @@ pub fn mock_process_instruction TransactionExecutionResult::NotExecuted(e.clone()), (Ok(loaded_transaction), nonce) => { - let compute_budget = - if let Some(compute_budget) = self.runtime_config.compute_budget { - compute_budget - } else { - let mut compute_budget_process_transaction_time = - Measure::start("compute_budget_process_transaction_time"); - let maybe_compute_budget = ComputeBudget::try_from_instructions( - tx.message().program_instructions_iter(), - &self.feature_set, - ); - compute_budget_process_transaction_time.stop(); - saturating_add_assign!( - timings - .execute_accessories - .compute_budget_process_transaction_us, - compute_budget_process_transaction_time.as_us() - ); - if let Err(err) = maybe_compute_budget { - return TransactionExecutionResult::NotExecuted(err); - } - maybe_compute_budget.unwrap() - }; + let compute_budget = if let Some(compute_budget) = + self.runtime_config.compute_budget + { + compute_budget + } else { + let mut compute_budget = + ComputeBudget::new(compute_budget::MAX_COMPUTE_UNIT_LIMIT as u64); + + let mut compute_budget_process_transaction_time = + Measure::start("compute_budget_process_transaction_time"); + let process_transaction_result = compute_budget.process_instructions( + tx.message().program_instructions_iter(), + !self + .feature_set + .is_active(&remove_deprecated_request_unit_ix::id()), + self.feature_set + .is_active(&add_set_tx_loaded_accounts_data_size_instruction::id()), + ); + compute_budget_process_transaction_time.stop(); + saturating_add_assign!( + timings + .execute_accessories + .compute_budget_process_transaction_us, + compute_budget_process_transaction_time.as_us() + ); + if let Err(err) = process_transaction_result { + return TransactionExecutionResult::NotExecuted(err); + } + compute_budget + }; let result = self.execute_loaded_transaction( tx, diff --git a/runtime/src/bank/tests.rs b/runtime/src/bank/tests.rs index cd1e22759..82393ef71 100644 --- a/runtime/src/bank/tests.rs +++ b/runtime/src/bank/tests.rs @@ -46,8 +46,7 @@ use { }, solana_logger, solana_program_runtime::{ - compute_budget::ComputeBudget, - compute_budget_processor::{self, MAX_COMPUTE_UNIT_LIMIT}, + compute_budget::{self, ComputeBudget, MAX_COMPUTE_UNIT_LIMIT}, declare_process_instruction, invoke_context::mock_process_instruction, loaded_programs::{LoadedProgram, LoadedProgramType, DELAY_VISIBILITY_SLOT_OFFSET}, @@ -10121,9 +10120,7 @@ fn test_compute_budget_program_noop() { assert_eq!( *compute_budget, ComputeBudget { - compute_unit_limit: u64::from( - compute_budget_processor::DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT - ), + compute_unit_limit: compute_budget::DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT as u64, heap_size: 48 * 1024, ..ComputeBudget::default() } @@ -10136,7 +10133,7 @@ fn test_compute_budget_program_noop() { let message = Message::new( &[ ComputeBudgetInstruction::set_compute_unit_limit( - compute_budget_processor::DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT, + compute_budget::DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT, ), ComputeBudgetInstruction::request_heap_frame(48 * 1024), Instruction::new_with_bincode(program_id, &0, vec![]), @@ -10166,9 +10163,7 @@ fn test_compute_request_instruction() { assert_eq!( *compute_budget, ComputeBudget { - compute_unit_limit: u64::from( - compute_budget_processor::DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT - ), + compute_unit_limit: compute_budget::DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT as u64, heap_size: 48 * 1024, ..ComputeBudget::default() } @@ -10181,7 +10176,7 @@ fn test_compute_request_instruction() { let message = Message::new( &[ ComputeBudgetInstruction::set_compute_unit_limit( - compute_budget_processor::DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT, + compute_budget::DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT, ), ComputeBudgetInstruction::request_heap_frame(48 * 1024), Instruction::new_with_bincode(program_id, &0, vec![]), @@ -10218,9 +10213,7 @@ fn test_failed_compute_request_instruction() { assert_eq!( *compute_budget, ComputeBudget { - compute_unit_limit: u64::from( - compute_budget_processor::DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT - ), + compute_unit_limit: compute_budget::DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT as u64, heap_size: 48 * 1024, ..ComputeBudget::default() } @@ -10451,19 +10444,14 @@ fn calculate_test_fee( remove_congestion_multiplier: bool, ) -> u64 { let mut feature_set = FeatureSet::all_enabled(); - feature_set.deactivate(&solana_sdk::feature_set::remove_deprecated_request_unit_ix::id()); + feature_set.deactivate(&remove_deprecated_request_unit_ix::id()); if !support_set_accounts_data_size_limit_ix { - feature_set.deactivate( - &solana_sdk::feature_set::include_loaded_accounts_data_size_in_fee_calculation::id(), - ); + feature_set.deactivate(&include_loaded_accounts_data_size_in_fee_calculation::id()); } let budget_limits = - process_compute_budget_instructions(message.program_instructions_iter(), &feature_set) - .unwrap_or_default() - .into(); - + ComputeBudget::fee_budget_limits(message.program_instructions_iter(), &feature_set); fee_structure.calculate_fee( message, lamports_per_signature, @@ -11490,9 +11478,7 @@ fn test_rent_state_list_len() { ); let compute_budget = bank.runtime_config.compute_budget.unwrap_or_else(|| { - ComputeBudget::new(u64::from( - compute_budget_processor::DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT, - )) + ComputeBudget::new(compute_budget::DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT as u64) }); let transaction_context = TransactionContext::new( loaded_txs[0].0.as_ref().unwrap().accounts.clone(), diff --git a/runtime/src/transaction_priority_details.rs b/runtime/src/transaction_priority_details.rs index 401f3e878..0d0a94df4 100644 --- a/runtime/src/transaction_priority_details.rs +++ b/runtime/src/transaction_priority_details.rs @@ -1,7 +1,6 @@ use { - solana_program_runtime::compute_budget_processor::process_compute_budget_instructions, + solana_program_runtime::compute_budget::ComputeBudget, solana_sdk::{ - feature_set::FeatureSet, instruction::CompiledInstruction, pubkey::Pubkey, transaction::{SanitizedTransaction, SanitizedVersionedTransaction}, @@ -24,17 +23,18 @@ pub trait GetTransactionPriorityDetails { instructions: impl Iterator, _round_compute_unit_price_enabled: bool, ) -> Option { - let mut feature_set = FeatureSet::default(); - feature_set.activate( - &solana_sdk::feature_set::add_set_tx_loaded_accounts_data_size_instruction::id(), - 0, - ); - - let compute_budget_limits = - process_compute_budget_instructions(instructions, &feature_set).ok()?; + let mut compute_budget = ComputeBudget::default(); + let prioritization_fee_details = compute_budget + .process_instructions( + instructions, + true, // supports prioritization by request_units_deprecated instruction + true, // enable support set accounts data size instruction + // TODO: round_compute_unit_price_enabled: bool + ) + .ok()?; Some(TransactionPriorityDetails { - priority: compute_budget_limits.compute_unit_price, - compute_unit_limit: u64::from(compute_budget_limits.compute_unit_limit), + priority: prioritization_fee_details.get_priority(), + compute_unit_limit: compute_budget.compute_unit_limit, }) } } @@ -98,8 +98,8 @@ mod tests { Some(TransactionPriorityDetails { priority: 0, compute_unit_limit: - solana_program_runtime::compute_budget_processor::DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT - as u64, + solana_program_runtime::compute_budget::DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT + as u64 }) ); @@ -111,8 +111,8 @@ mod tests { Some(TransactionPriorityDetails { priority: 0, compute_unit_limit: - solana_program_runtime::compute_budget_processor::DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT - as u64, + solana_program_runtime::compute_budget::DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT + as u64 }) ); } @@ -174,8 +174,8 @@ mod tests { Some(TransactionPriorityDetails { priority: requested_price, compute_unit_limit: - solana_program_runtime::compute_budget_processor::DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT - as u64, + solana_program_runtime::compute_budget::DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT + as u64 }) ); @@ -187,8 +187,8 @@ mod tests { Some(TransactionPriorityDetails { priority: requested_price, compute_unit_limit: - solana_program_runtime::compute_budget_processor::DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT - as u64, + solana_program_runtime::compute_budget::DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT + as u64 }) ); }