diff --git a/programs/sbf/tests/programs.rs b/programs/sbf/tests/programs.rs index 428d3ae1a..ce69d8482 100644 --- a/programs/sbf/tests/programs.rs +++ b/programs/sbf/tests/programs.rs @@ -3634,6 +3634,7 @@ fn test_program_fees() { true, true, true, + false, ); bank_client .send_and_confirm_message(&[&mint_keypair], message) @@ -3659,6 +3660,7 @@ fn test_program_fees() { true, true, true, + false, ); assert!(expected_normal_fee < expected_prioritized_fee); diff --git a/runtime/src/accounts.rs b/runtime/src/accounts.rs index 19c2f6449..87f85e180 100644 --- a/runtime/src/accounts.rs +++ b/runtime/src/accounts.rs @@ -34,6 +34,7 @@ use { clock::{BankId, Slot}, feature_set::{ self, add_set_tx_loaded_accounts_data_size_instruction, enable_request_heap_frame_ix, + include_loaded_accounts_data_size_in_fee_calculation, remove_congestion_multiplier_from_fee_calculation, remove_deprecated_request_unit_ix, use_default_units_in_fee_calculation, FeatureSet, }, @@ -661,6 +662,7 @@ impl Accounts { feature_set.is_active(&remove_congestion_multiplier_from_fee_calculation::id()), feature_set.is_active(&enable_request_heap_frame_ix::id()) || self.accounts_db.expected_cluster_type() != ClusterType::MainnetBeta, feature_set.is_active(&add_set_tx_loaded_accounts_data_size_instruction::id()), + feature_set.is_active(&include_loaded_accounts_data_size_in_fee_calculation::id()), ) } else { return (Err(TransactionError::BlockhashNotFound), None); @@ -1690,6 +1692,7 @@ mod tests { true, true, true, + false, ); assert_eq!(fee, lamports_per_signature); diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index 38313531b..fae5d4cb0 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -121,6 +121,7 @@ use { feature_set::{ self, add_set_tx_loaded_accounts_data_size_instruction, disable_fee_calculator, enable_early_verification_of_account_modifications, enable_request_heap_frame_ix, + include_loaded_accounts_data_size_in_fee_calculation, remove_congestion_multiplier_from_fee_calculation, remove_deprecated_request_unit_ix, use_default_units_in_fee_calculation, FeatureSet, }, @@ -3505,6 +3506,8 @@ impl Bank { self.enable_request_heap_frame_ix(), self.feature_set .is_active(&add_set_tx_loaded_accounts_data_size_instruction::id()), + self.feature_set + .is_active(&include_loaded_accounts_data_size_in_fee_calculation::id()), )) } @@ -3554,6 +3557,8 @@ impl Bank { self.enable_request_heap_frame_ix(), self.feature_set .is_active(&add_set_tx_loaded_accounts_data_size_instruction::id()), + self.feature_set + .is_active(&include_loaded_accounts_data_size_in_fee_calculation::id()), ) } @@ -4894,6 +4899,7 @@ impl Bank { remove_congestion_multiplier: bool, enable_request_heap_frame_ix: bool, support_set_accounts_data_size_limit_ix: bool, + include_loaded_account_data_size_in_fee: bool, ) -> u64 { // Fee based on compute units and signatures let congestion_multiplier = if lamports_per_signature == 0 { @@ -4921,10 +4927,20 @@ impl Bank { .saturating_mul(fee_structure.lamports_per_signature); let write_lock_fee = Self::get_num_write_locks_in_message(message) .saturating_mul(fee_structure.lamports_per_write_lock); + + // `compute_fee` covers costs for both requested_compute_units and + // requested_loaded_account_data_size + let loaded_accounts_data_size_cost = if include_loaded_account_data_size_in_fee { + Self::calculate_loaded_accounts_data_size_cost(&compute_budget) + } else { + 0_u64 + }; + let total_compute_units = + loaded_accounts_data_size_cost.saturating_add(compute_budget.compute_unit_limit); let compute_fee = fee_structure .compute_fee_bins .iter() - .find(|bin| compute_budget.compute_unit_limit <= bin.limit) + .find(|bin| total_compute_units <= bin.limit) .map(|bin| bin.fee) .unwrap_or_else(|| { fee_structure @@ -4942,6 +4958,23 @@ impl Bank { .round() as u64 } + // Calculate cost of loaded accounts size in the same way heap cost is charged at + // rate of 8cu per 32K. Citing `program_runtime\src\compute_budget.rs`: "(cost of + // heap is about) 0.5us per 32k at 15 units/us rounded up" + // + // Before feature `support_set_loaded_accounts_data_size_limit_ix` is enabled, or + // if user doesn't use compute budget ix `set_loaded_accounts_data_size_limit_ix` + // 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 + // + fn calculate_loaded_accounts_data_size_cost(compute_budget: &ComputeBudget) -> u64 { + const ACCOUNT_DATA_COST_PAGE_SIZE: u64 = 32_u64.saturating_mul(1024); + (compute_budget.loaded_accounts_data_size_limit as u64) + .saturating_add(ACCOUNT_DATA_COST_PAGE_SIZE.saturating_sub(1)) + .saturating_div(ACCOUNT_DATA_COST_PAGE_SIZE) + .saturating_mul(compute_budget.heap_cost) + } + fn filter_program_errors_and_collect_fee( &self, txs: &[SanitizedTransaction], @@ -4987,6 +5020,8 @@ impl Bank { self.enable_request_heap_frame_ix(), self.feature_set .is_active(&add_set_tx_loaded_accounts_data_size_instruction::id()), + self.feature_set + .is_active(&include_loaded_accounts_data_size_in_fee_calculation::id()), ); // In case of instruction error, even though no accounts diff --git a/runtime/src/bank/tests.rs b/runtime/src/bank/tests.rs index 04d3b12c9..d127287d4 100644 --- a/runtime/src/bank/tests.rs +++ b/runtime/src/bank/tests.rs @@ -3221,6 +3221,7 @@ fn test_bank_tx_compute_unit_fee() { true, true, true, + false, ); let (expected_fee_collected, expected_fee_burned) = @@ -3405,6 +3406,7 @@ fn test_bank_blockhash_compute_unit_fee_structure() { true, true, true, + false, ); assert_eq!( bank.get_balance(&mint_keypair.pubkey()), @@ -3426,6 +3428,7 @@ fn test_bank_blockhash_compute_unit_fee_structure() { true, true, true, + false, ); assert_eq!( bank.get_balance(&mint_keypair.pubkey()), @@ -3542,6 +3545,7 @@ fn test_filter_program_errors_and_collect_compute_unit_fee() { true, true, true, + false, ) * 2 ) .0 @@ -10713,6 +10717,7 @@ fn test_calculate_fee() { true, true, support_set_accounts_data_size_limit_ix, + false, ), 0 ); @@ -10733,6 +10738,7 @@ fn test_calculate_fee() { true, true, support_set_accounts_data_size_limit_ix, + false, ), 1 ); @@ -10758,6 +10764,7 @@ fn test_calculate_fee() { true, true, support_set_accounts_data_size_limit_ix, + false, ), 4 ); @@ -10787,7 +10794,8 @@ fn test_calculate_fee_compute_units() { false, true, true, - support_set_accounts_data_size_limit_ix + support_set_accounts_data_size_limit_ix, + false, ), max_fee + lamports_per_signature ); @@ -10809,7 +10817,8 @@ fn test_calculate_fee_compute_units() { false, true, true, - support_set_accounts_data_size_limit_ix + support_set_accounts_data_size_limit_ix, + false, ), max_fee + 3 * lamports_per_signature ); @@ -10854,6 +10863,7 @@ fn test_calculate_fee_compute_units() { true, true, support_set_accounts_data_size_limit_ix, + false, ); assert_eq!( fee, @@ -10903,7 +10913,8 @@ fn test_calculate_fee_secp256k1() { false, true, true, - support_set_accounts_data_size_limit_ix + support_set_accounts_data_size_limit_ix, + false, ), 2 ); @@ -10926,7 +10937,8 @@ fn test_calculate_fee_secp256k1() { false, true, true, - support_set_accounts_data_size_limit_ix + support_set_accounts_data_size_limit_ix, + false, ), 11 ); @@ -12659,6 +12671,7 @@ fn test_calculate_fee_with_congestion_multiplier() { remove_congestion_multiplier, true, true, + false, ), signature_fee * signature_count ); @@ -12683,6 +12696,7 @@ fn test_calculate_fee_with_congestion_multiplier() { remove_congestion_multiplier, true, true, + false, ), signature_fee * signature_count / denominator ); @@ -12725,6 +12739,7 @@ fn test_calculate_fee_with_request_heap_frame_flag() { true, enable_request_heap_frame_ix, true, + false, ), signature_fee + request_cu * lamports_per_cu ); @@ -12742,6 +12757,7 @@ fn test_calculate_fee_with_request_heap_frame_flag() { true, enable_request_heap_frame_ix, true, + false, ), signature_fee ); @@ -12914,3 +12930,39 @@ fn test_bank_verify_accounts_hash_with_base() { }, )); } + +#[allow(clippy::field_reassign_with_default)] +#[test] +fn test_calculate_loaded_accounts_data_size_cost() { + 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.loaded_accounts_data_size_limit = 31_usize * 1024; + assert_eq!( + compute_budget.heap_cost, + Bank::calculate_loaded_accounts_data_size_cost(&compute_budget) + ); + + // ... requesting exact 32K should be charged as one block + compute_budget.loaded_accounts_data_size_limit = 32_usize * 1024; + assert_eq!( + compute_budget.heap_cost, + Bank::calculate_loaded_accounts_data_size_cost(&compute_budget) + ); + + // ... requesting slightly above 32K should be charged as 2 block + compute_budget.loaded_accounts_data_size_limit = 33_usize * 1024; + assert_eq!( + compute_budget.heap_cost * 2, + Bank::calculate_loaded_accounts_data_size_cost(&compute_budget) + ); + + // ... requesting exact 64K should be charged as 2 block + compute_budget.loaded_accounts_data_size_limit = 64_usize * 1024; + assert_eq!( + compute_budget.heap_cost * 2, + Bank::calculate_loaded_accounts_data_size_cost(&compute_budget) + ); +} diff --git a/sdk/src/feature_set.rs b/sdk/src/feature_set.rs index 35a6ae1ff..dfe637c86 100644 --- a/sdk/src/feature_set.rs +++ b/sdk/src/feature_set.rs @@ -634,6 +634,10 @@ pub mod remove_bpf_loader_incorrect_program_id { solana_sdk::declare_id!("2HmTkCj9tXuPE4ueHzdD7jPeMf9JGCoZh5AsyoATiWEe"); } +pub mod include_loaded_accounts_data_size_in_fee_calculation { + solana_sdk::declare_id!("EaQpmC6GtRssaZ3PCUM5YksGqUdMLeZ46BQXYtHYakDS"); +} + lazy_static! { /// Map of feature identifiers to user-visible description pub static ref FEATURE_NAMES: HashMap = [ @@ -787,6 +791,7 @@ lazy_static! { (switch_to_new_elf_parser::id(), "switch to new ELF parser #30497"), (round_up_heap_size::id(), "round up heap size when calculating heap cost #30679"), (remove_bpf_loader_incorrect_program_id::id(), "stop incorrectly throwing IncorrectProgramId in bpf_loader #30747"), + (include_loaded_accounts_data_size_in_fee_calculation::id(), "include transaction loaded accounts data size in base fee calculation #30657"), /*************** ADD NEW FEATURES HERE ***************/ ] .iter()