include cost of transaction's requested loaded accounts size in cost model (#31905)

* include transaction requested loaded accounts size cost in cost model

* move function to avoid circular dependency
This commit is contained in:
Tao Zhu 2023-06-08 12:49:42 -05:00 committed by GitHub
parent f2f1dab5ea
commit 37a759045a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 155 additions and 28 deletions

View File

@ -57,6 +57,7 @@ use {
bank::metrics::*,
blockhash_queue::BlockhashQueue,
builtins::{BuiltinPrototype, BUILTINS},
cost_model::CostModel,
cost_tracker::CostTracker,
epoch_accounts_hash::{self, EpochAccountsHash},
epoch_stakes::{EpochStakes, NodeVoteAccounts},
@ -4915,7 +4916,7 @@ impl Bank {
// `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)
CostModel::calculate_loaded_accounts_data_size_cost(&compute_budget)
} else {
0_u64
};
@ -4942,23 +4943,6 @@ 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],

View File

@ -12435,28 +12435,28 @@ fn test_calculate_loaded_accounts_data_size_cost() {
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)
CostModel::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)
CostModel::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)
CostModel::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)
CostModel::calculate_loaded_accounts_data_size_cost(&compute_budget)
);
}

View File

@ -13,8 +13,9 @@ use {
},
solana_sdk::{
feature_set::{
add_set_tx_loaded_accounts_data_size_instruction, remove_deprecated_request_unit_ix,
use_default_units_in_fee_calculation, FeatureSet,
add_set_tx_loaded_accounts_data_size_instruction,
include_loaded_accounts_data_size_in_fee_calculation,
remove_deprecated_request_unit_ix, use_default_units_in_fee_calculation, FeatureSet,
},
instruction::CompiledInstruction,
program_utils::limited_deserialize,
@ -25,6 +26,8 @@ use {
},
};
const ACCOUNT_DATA_COST_PAGE_SIZE: u64 = 32_u64.saturating_mul(1024);
pub struct CostModel;
impl CostModel {
@ -44,6 +47,22 @@ impl CostModel {
tx_cost
}
// 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
//
pub fn calculate_loaded_accounts_data_size_cost(compute_budget: &ComputeBudget) -> u64 {
(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 get_signature_cost(transaction: &SanitizedTransaction) -> u64 {
transaction.signatures().len() as u64 * SIGNATURE_COST
}
@ -71,6 +90,7 @@ impl CostModel {
) {
let mut builtin_costs = 0u64;
let mut bpf_costs = 0u64;
let mut loaded_accounts_data_size_cost = 0u64;
let mut data_bytes_len_total = 0u64;
for (program_id, instruction) in transaction.message().program_instructions_iter() {
@ -85,7 +105,7 @@ impl CostModel {
}
// calculate bpf cost based on compute budget instructions
let mut budget = ComputeBudget::default();
let mut compute_budget = ComputeBudget::default();
// Starting from v1.15, cost model uses compute_budget.set_compute_unit_limit to
// measure bpf_costs (code below), vs earlier versions that use estimated
@ -94,7 +114,7 @@ impl CostModel {
// will not impact consensus. So for v1.15+, should call compute budget with
// the feature gate `enable_request_heap_frame_ix` enabled.
let enable_request_heap_frame_ix = true;
let result = budget.process_instructions(
let result = compute_budget.process_instructions(
transaction.message().program_instructions_iter(),
feature_set.is_active(&use_default_units_in_fee_calculation::id()),
!feature_set.is_active(&remove_deprecated_request_unit_ix::id()),
@ -108,7 +128,13 @@ impl CostModel {
Ok(_) => {
// if tx contained user-space instructions and a more accurate estimate available correct it
if bpf_costs > 0 {
bpf_costs = budget.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);
}
}
Err(_) => {
@ -119,6 +145,7 @@ impl CostModel {
tx_cost.builtins_execution_cost = builtin_costs;
tx_cost.bpf_execution_cost = bpf_costs;
tx_cost.loaded_accounts_data_size_cost = loaded_accounts_data_size_cost;
tx_cost.data_bytes_cost = data_bytes_len_total / INSTRUCTION_DATA_BYTES_COST;
}
@ -494,7 +521,7 @@ mod tests {
}
#[test]
fn test_cost_model_calculate_cost() {
fn test_cost_model_calculate_cost_all_default() {
let (mint_keypair, start_hash) = test_setup();
let tx = SanitizedTransaction::from_transaction_for_tests(system_transaction::transfer(
&mint_keypair,
@ -507,10 +534,122 @@ mod tests {
let expected_execution_cost = BUILT_IN_INSTRUCTION_COSTS
.get(&system_program::id())
.unwrap();
// feature `include_loaded_accounts_data_size_in_fee_calculation` enabled, using
// default loaded_accounts_data_size_limit
const DEFAULT_PAGE_COST: u64 = 8;
let expected_loaded_accounts_data_size_cost =
solana_program_runtime::compute_budget::MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES as u64
/ ACCOUNT_DATA_COST_PAGE_SIZE
* DEFAULT_PAGE_COST;
let tx_cost = CostModel::calculate_cost(&tx, &FeatureSet::all_enabled());
assert_eq!(expected_account_cost, tx_cost.write_lock_cost);
assert_eq!(*expected_execution_cost, tx_cost.builtins_execution_cost);
assert_eq!(2, tx_cost.writable_accounts.len());
assert_eq!(
expected_loaded_accounts_data_size_cost,
tx_cost.loaded_accounts_data_size_cost
);
}
#[test]
fn test_cost_model_calculate_cost_disabled_feature() {
let (mint_keypair, start_hash) = test_setup();
let tx = SanitizedTransaction::from_transaction_for_tests(system_transaction::transfer(
&mint_keypair,
&Keypair::new().pubkey(),
2,
start_hash,
));
let feature_set = FeatureSet::default();
assert!(!feature_set.is_active(&include_loaded_accounts_data_size_in_fee_calculation::id()));
let expected_account_cost = WRITE_LOCK_UNITS * 2;
let expected_execution_cost = BUILT_IN_INSTRUCTION_COSTS
.get(&system_program::id())
.unwrap();
// feature `include_loaded_accounts_data_size_in_fee_calculation` not enabled
let expected_loaded_accounts_data_size_cost = 0;
let tx_cost = CostModel::calculate_cost(&tx, &feature_set);
assert_eq!(expected_account_cost, tx_cost.write_lock_cost);
assert_eq!(*expected_execution_cost, tx_cost.builtins_execution_cost);
assert_eq!(2, tx_cost.writable_accounts.len());
assert_eq!(
expected_loaded_accounts_data_size_cost,
tx_cost.loaded_accounts_data_size_cost
);
}
#[test]
fn test_cost_model_calculate_cost_enabled_feature_with_limit() {
let (mint_keypair, start_hash) = test_setup();
let to_keypair = Keypair::new();
let data_limit = 32 * 1024u32;
let tx =
SanitizedTransaction::from_transaction_for_tests(Transaction::new_signed_with_payer(
&[
system_instruction::transfer(&mint_keypair.pubkey(), &to_keypair.pubkey(), 2),
ComputeBudgetInstruction::set_loaded_accounts_data_size_limit(data_limit),
],
Some(&mint_keypair.pubkey()),
&[&mint_keypair],
start_hash,
));
let feature_set = FeatureSet::all_enabled();
assert!(feature_set.is_active(&include_loaded_accounts_data_size_in_fee_calculation::id()));
let expected_account_cost = WRITE_LOCK_UNITS * 2;
let expected_execution_cost = BUILT_IN_INSTRUCTION_COSTS
.get(&system_program::id())
.unwrap()
+ BUILT_IN_INSTRUCTION_COSTS
.get(&compute_budget::id())
.unwrap();
// feature `include_loaded_accounts_data_size_in_fee_calculation` is enabled, accounts data
// size limit is set.
let expected_loaded_accounts_data_size_cost = (data_limit as u64) / (32 * 1024) * 8;
let tx_cost = CostModel::calculate_cost(&tx, &feature_set);
assert_eq!(expected_account_cost, tx_cost.write_lock_cost);
assert_eq!(expected_execution_cost, tx_cost.builtins_execution_cost);
assert_eq!(2, tx_cost.writable_accounts.len());
assert_eq!(
expected_loaded_accounts_data_size_cost,
tx_cost.loaded_accounts_data_size_cost
);
}
#[test]
fn test_cost_model_calculate_cost_disabled_feature_with_limit() {
let (mint_keypair, start_hash) = test_setup();
let to_keypair = Keypair::new();
let data_limit = 32 * 1024u32;
let tx =
SanitizedTransaction::from_transaction_for_tests(Transaction::new_signed_with_payer(
&[
system_instruction::transfer(&mint_keypair.pubkey(), &to_keypair.pubkey(), 2),
ComputeBudgetInstruction::set_loaded_accounts_data_size_limit(data_limit),
],
Some(&mint_keypair.pubkey()),
&[&mint_keypair],
start_hash,
));
let feature_set = FeatureSet::default();
assert!(!feature_set.is_active(&include_loaded_accounts_data_size_in_fee_calculation::id()));
let expected_account_cost = WRITE_LOCK_UNITS * 2;
// with features all disabled, builtins and loaded account size don't cost CU
let expected_execution_cost = 0;
let expected_loaded_accounts_data_size_cost = 0;
let tx_cost = CostModel::calculate_cost(&tx, &feature_set);
assert_eq!(expected_account_cost, tx_cost.write_lock_cost);
assert_eq!(expected_execution_cost, tx_cost.builtins_execution_cost);
assert_eq!(2, tx_cost.writable_accounts.len());
assert_eq!(
expected_loaded_accounts_data_size_cost,
tx_cost.loaded_accounts_data_size_cost
);
}
}

View File

@ -11,6 +11,7 @@ pub struct TransactionCost {
pub data_bytes_cost: u64,
pub builtins_execution_cost: u64,
pub bpf_execution_cost: u64,
pub loaded_accounts_data_size_cost: u64,
pub account_data_size: u64,
pub is_simple_vote: bool,
}
@ -24,6 +25,7 @@ impl Default for TransactionCost {
data_bytes_cost: 0u64,
builtins_execution_cost: 0u64,
bpf_execution_cost: 0u64,
loaded_accounts_data_size_cost: 0u64,
account_data_size: 0u64,
is_simple_vote: false,
}
@ -42,6 +44,7 @@ impl PartialEq for TransactionCost {
&& self.data_bytes_cost == other.data_bytes_cost
&& self.builtins_execution_cost == other.builtins_execution_cost
&& self.bpf_execution_cost == other.bpf_execution_cost
&& self.loaded_accounts_data_size_cost == other.loaded_accounts_data_size_cost
&& self.account_data_size == other.account_data_size
&& self.is_simple_vote == other.is_simple_vote
&& to_hash_set(&self.writable_accounts) == to_hash_set(&other.writable_accounts)
@ -69,5 +72,6 @@ impl TransactionCost {
.saturating_add(self.data_bytes_cost)
.saturating_add(self.builtins_execution_cost)
.saturating_add(self.bpf_execution_cost)
.saturating_add(self.loaded_accounts_data_size_cost)
}
}