Separate vote cost (#33230)

* Separate simple-vote transaction cost from non-vote transaction cost

* remove is_simple_vote flag from transaction UsageCostDetails

* update test and comment

* set static usage cost for SimpleVote transaction
This commit is contained in:
Tao Zhu 2023-09-25 15:02:08 -05:00 committed by GitHub
parent 40f536010f
commit a41c15e47e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 244 additions and 129 deletions

View File

@ -737,7 +737,7 @@ mod tests {
unprocessed_transaction_storage::ThreadType, unprocessed_transaction_storage::ThreadType,
}, },
crossbeam_channel::{unbounded, Receiver}, crossbeam_channel::{unbounded, Receiver},
solana_cost_model::cost_model::CostModel, solana_cost_model::{cost_model::CostModel, transaction_cost::TransactionCost},
solana_entry::entry::{next_entry, next_versioned_entry}, solana_entry::entry::{next_entry, next_versioned_entry},
solana_ledger::{ solana_ledger::{
blockstore::{entries_to_test_shreds, Blockstore}, blockstore::{entries_to_test_shreds, Blockstore},
@ -1264,7 +1264,9 @@ mod tests {
}; };
let mut cost = CostModel::calculate_cost(&transactions[0], &bank.feature_set); let mut cost = CostModel::calculate_cost(&transactions[0], &bank.feature_set);
cost.bpf_execution_cost = actual_bpf_execution_cost; if let TransactionCost::Transaction(ref mut usage_cost) = cost {
usage_cost.bpf_execution_cost = actual_bpf_execution_cost;
}
block_cost + cost.sum() block_cost + cost.sum()
} else { } else {

View File

@ -318,25 +318,25 @@ impl QosService {
Ok(cost) => { Ok(cost) => {
saturating_add_assign!( saturating_add_assign!(
batched_transaction_details.costs.batched_signature_cost, batched_transaction_details.costs.batched_signature_cost,
cost.signature_cost cost.signature_cost()
); );
saturating_add_assign!( saturating_add_assign!(
batched_transaction_details.costs.batched_write_lock_cost, batched_transaction_details.costs.batched_write_lock_cost,
cost.write_lock_cost cost.write_lock_cost()
); );
saturating_add_assign!( saturating_add_assign!(
batched_transaction_details.costs.batched_data_bytes_cost, batched_transaction_details.costs.batched_data_bytes_cost,
cost.data_bytes_cost cost.data_bytes_cost()
); );
saturating_add_assign!( saturating_add_assign!(
batched_transaction_details batched_transaction_details
.costs .costs
.batched_builtins_execute_cost, .batched_builtins_execute_cost,
cost.builtins_execution_cost cost.builtins_execution_cost()
); );
saturating_add_assign!( saturating_add_assign!(
batched_transaction_details.costs.batched_bpf_execute_cost, batched_transaction_details.costs.batched_bpf_execute_cost,
cost.bpf_execution_cost cost.bpf_execution_cost()
); );
} }
Err(transaction_error) => match transaction_error { Err(transaction_error) => match transaction_error {
@ -589,6 +589,7 @@ mod tests {
use { use {
super::*, super::*,
itertools::Itertools, itertools::Itertools,
solana_cost_model::transaction_cost::UsageCostDetails,
solana_runtime::genesis_utils::{create_genesis_config, GenesisConfigInfo}, solana_runtime::genesis_utils::{create_genesis_config, GenesisConfigInfo},
solana_sdk::{ solana_sdk::{
hash::Hash, hash::Hash,
@ -734,7 +735,7 @@ mod tests {
let commited_status: Vec<CommitTransactionDetails> = qos_cost_results let commited_status: Vec<CommitTransactionDetails> = qos_cost_results
.iter() .iter()
.map(|tx_cost| CommitTransactionDetails::Committed { .map(|tx_cost| CommitTransactionDetails::Committed {
compute_units: tx_cost.as_ref().unwrap().bpf_execution_cost compute_units: tx_cost.as_ref().unwrap().bpf_execution_cost()
+ execute_units_adjustment, + execute_units_adjustment,
}) })
.collect(); .collect();
@ -861,7 +862,7 @@ mod tests {
CommitTransactionDetails::NotCommitted CommitTransactionDetails::NotCommitted
} else { } else {
CommitTransactionDetails::Committed { CommitTransactionDetails::Committed {
compute_units: tx_cost.as_ref().unwrap().bpf_execution_cost compute_units: tx_cost.as_ref().unwrap().bpf_execution_cost()
+ execute_units_adjustment, + execute_units_adjustment,
} }
} }
@ -904,14 +905,14 @@ mod tests {
let tx_cost_results: Vec<_> = (0..num_txs) let tx_cost_results: Vec<_> = (0..num_txs)
.map(|n| { .map(|n| {
if n % 2 == 0 { if n % 2 == 0 {
Ok(TransactionCost { Ok(TransactionCost::Transaction(UsageCostDetails {
signature_cost, signature_cost,
write_lock_cost, write_lock_cost,
data_bytes_cost, data_bytes_cost,
builtins_execution_cost, builtins_execution_cost,
bpf_execution_cost, bpf_execution_cost,
..TransactionCost::default() ..UsageCostDetails::default()
}) }))
} else { } else {
Err(TransactionError::WouldExceedMaxBlockCostLimit) Err(TransactionError::WouldExceedMaxBlockCostLimit)
} }

View File

@ -6,7 +6,7 @@
//! //!
use { use {
crate::{block_cost_limits::*, transaction_cost::TransactionCost}, crate::{block_cost_limits::*, transaction_cost::*},
log::*, log::*,
solana_program_runtime::compute_budget::{ solana_program_runtime::compute_budget::{
ComputeBudget, DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT, MAX_COMPUTE_UNIT_LIMIT, ComputeBudget, DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT, MAX_COMPUTE_UNIT_LIMIT,
@ -36,16 +36,21 @@ impl CostModel {
transaction: &SanitizedTransaction, transaction: &SanitizedTransaction,
feature_set: &FeatureSet, feature_set: &FeatureSet,
) -> TransactionCost { ) -> TransactionCost {
let mut tx_cost = TransactionCost::new_with_default_capacity(); if transaction.is_simple_vote_transaction() {
TransactionCost::SimpleVote {
writable_accounts: Self::get_writable_accounts(transaction),
}
} else {
let mut tx_cost = UsageCostDetails::new_with_default_capacity();
tx_cost.signature_cost = Self::get_signature_cost(transaction); tx_cost.signature_cost = Self::get_signature_cost(transaction);
Self::get_write_lock_cost(&mut tx_cost, transaction); Self::get_write_lock_cost(&mut tx_cost, transaction);
Self::get_transaction_cost(&mut tx_cost, transaction, feature_set); Self::get_transaction_cost(&mut tx_cost, transaction, feature_set);
tx_cost.account_data_size = Self::calculate_account_data_size(transaction); tx_cost.account_data_size = Self::calculate_account_data_size(transaction);
tx_cost.is_simple_vote = transaction.is_simple_vote_transaction();
debug!("transaction {:?} has cost {:?}", transaction, tx_cost); debug!("transaction {:?} has cost {:?}", transaction, tx_cost);
tx_cost TransactionCost::Transaction(tx_cost)
}
} }
// Calculate cost of loaded accounts size in the same way heap cost is charged at // Calculate cost of loaded accounts size in the same way heap cost is charged at
@ -68,24 +73,30 @@ impl CostModel {
transaction.signatures().len() as u64 * SIGNATURE_COST transaction.signatures().len() as u64 * SIGNATURE_COST
} }
fn get_write_lock_cost(tx_cost: &mut TransactionCost, transaction: &SanitizedTransaction) { fn get_writable_accounts(transaction: &SanitizedTransaction) -> Vec<Pubkey> {
let message = transaction.message(); let message = transaction.message();
message message
.account_keys() .account_keys()
.iter() .iter()
.enumerate() .enumerate()
.for_each(|(i, k)| { .filter_map(|(i, k)| {
let is_writable = message.is_writable(i); if message.is_writable(i) {
Some(*k)
if is_writable { } else {
tx_cost.writable_accounts.push(*k); None
tx_cost.write_lock_cost += WRITE_LOCK_UNITS;
} }
}); })
.collect()
}
fn get_write_lock_cost(tx_cost: &mut UsageCostDetails, transaction: &SanitizedTransaction) {
tx_cost.writable_accounts = Self::get_writable_accounts(transaction);
tx_cost.write_lock_cost =
WRITE_LOCK_UNITS.saturating_mul(tx_cost.writable_accounts.len() as u64);
} }
fn get_transaction_cost( fn get_transaction_cost(
tx_cost: &mut TransactionCost, tx_cost: &mut UsageCostDetails,
transaction: &SanitizedTransaction, transaction: &SanitizedTransaction,
feature_set: &FeatureSet, feature_set: &FeatureSet,
) { ) {
@ -298,7 +309,7 @@ mod tests {
.get(&system_program::id()) .get(&system_program::id())
.unwrap(); .unwrap();
let mut tx_cost = TransactionCost::default(); let mut tx_cost = UsageCostDetails::default();
CostModel::get_transaction_cost( CostModel::get_transaction_cost(
&mut tx_cost, &mut tx_cost,
&simple_transaction, &simple_transaction,
@ -327,7 +338,7 @@ mod tests {
let token_transaction = SanitizedTransaction::from_transaction_for_tests(tx); let token_transaction = SanitizedTransaction::from_transaction_for_tests(tx);
debug!("token_transaction {:?}", token_transaction); debug!("token_transaction {:?}", token_transaction);
let mut tx_cost = TransactionCost::default(); let mut tx_cost = UsageCostDetails::default();
CostModel::get_transaction_cost( CostModel::get_transaction_cost(
&mut tx_cost, &mut tx_cost,
&token_transaction, &token_transaction,
@ -364,7 +375,7 @@ mod tests {
); );
let token_transaction = SanitizedTransaction::from_transaction_for_tests(tx); let token_transaction = SanitizedTransaction::from_transaction_for_tests(tx);
let mut tx_cost = TransactionCost::default(); let mut tx_cost = UsageCostDetails::default();
CostModel::get_transaction_cost( CostModel::get_transaction_cost(
&mut tx_cost, &mut tx_cost,
&token_transaction, &token_transaction,
@ -414,7 +425,7 @@ mod tests {
); );
let token_transaction = SanitizedTransaction::from_transaction_for_tests(tx); let token_transaction = SanitizedTransaction::from_transaction_for_tests(tx);
let mut tx_cost = TransactionCost::default(); let mut tx_cost = UsageCostDetails::default();
CostModel::get_transaction_cost( CostModel::get_transaction_cost(
&mut tx_cost, &mut tx_cost,
&token_transaction, &token_transaction,
@ -446,7 +457,7 @@ mod tests {
.unwrap(); .unwrap();
let expected_cost = program_cost * 2; let expected_cost = program_cost * 2;
let mut tx_cost = TransactionCost::default(); let mut tx_cost = UsageCostDetails::default();
CostModel::get_transaction_cost(&mut tx_cost, &tx, &FeatureSet::all_enabled()); CostModel::get_transaction_cost(&mut tx_cost, &tx, &FeatureSet::all_enabled());
assert_eq!(expected_cost, tx_cost.builtins_execution_cost); assert_eq!(expected_cost, tx_cost.builtins_execution_cost);
assert_eq!(0, tx_cost.bpf_execution_cost); assert_eq!(0, tx_cost.bpf_execution_cost);
@ -478,7 +489,7 @@ mod tests {
debug!("many random transaction {:?}", tx); debug!("many random transaction {:?}", tx);
let expected_cost = DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT as u64 * 2; let expected_cost = DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT as u64 * 2;
let mut tx_cost = TransactionCost::default(); let mut tx_cost = UsageCostDetails::default();
CostModel::get_transaction_cost(&mut tx_cost, &tx, &FeatureSet::all_enabled()); CostModel::get_transaction_cost(&mut tx_cost, &tx, &FeatureSet::all_enabled());
assert_eq!(0, tx_cost.builtins_execution_cost); assert_eq!(0, tx_cost.builtins_execution_cost);
assert_eq!(expected_cost, tx_cost.bpf_execution_cost); assert_eq!(expected_cost, tx_cost.bpf_execution_cost);
@ -509,11 +520,11 @@ mod tests {
); );
let tx_cost = CostModel::calculate_cost(&tx, &FeatureSet::all_enabled()); let tx_cost = CostModel::calculate_cost(&tx, &FeatureSet::all_enabled());
assert_eq!(2 + 2, tx_cost.writable_accounts.len()); assert_eq!(2 + 2, tx_cost.writable_accounts().len());
assert_eq!(signer1.pubkey(), tx_cost.writable_accounts[0]); assert_eq!(signer1.pubkey(), tx_cost.writable_accounts()[0]);
assert_eq!(signer2.pubkey(), tx_cost.writable_accounts[1]); assert_eq!(signer2.pubkey(), tx_cost.writable_accounts()[1]);
assert_eq!(key1, tx_cost.writable_accounts[2]); assert_eq!(key1, tx_cost.writable_accounts()[2]);
assert_eq!(key2, tx_cost.writable_accounts[3]); assert_eq!(key2, tx_cost.writable_accounts()[3]);
} }
#[test] #[test]
@ -539,12 +550,12 @@ mod tests {
* DEFAULT_PAGE_COST; * DEFAULT_PAGE_COST;
let tx_cost = CostModel::calculate_cost(&tx, &FeatureSet::all_enabled()); let tx_cost = CostModel::calculate_cost(&tx, &FeatureSet::all_enabled());
assert_eq!(expected_account_cost, tx_cost.write_lock_cost); assert_eq!(expected_account_cost, tx_cost.write_lock_cost());
assert_eq!(*expected_execution_cost, tx_cost.builtins_execution_cost); assert_eq!(*expected_execution_cost, tx_cost.builtins_execution_cost());
assert_eq!(2, tx_cost.writable_accounts.len()); assert_eq!(2, tx_cost.writable_accounts().len());
assert_eq!( assert_eq!(
expected_loaded_accounts_data_size_cost, expected_loaded_accounts_data_size_cost,
tx_cost.loaded_accounts_data_size_cost tx_cost.loaded_accounts_data_size_cost()
); );
} }
@ -568,12 +579,12 @@ mod tests {
let expected_loaded_accounts_data_size_cost = 0; let expected_loaded_accounts_data_size_cost = 0;
let tx_cost = CostModel::calculate_cost(&tx, &feature_set); let tx_cost = CostModel::calculate_cost(&tx, &feature_set);
assert_eq!(expected_account_cost, tx_cost.write_lock_cost); assert_eq!(expected_account_cost, tx_cost.write_lock_cost());
assert_eq!(*expected_execution_cost, tx_cost.builtins_execution_cost); assert_eq!(*expected_execution_cost, tx_cost.builtins_execution_cost());
assert_eq!(2, tx_cost.writable_accounts.len()); assert_eq!(2, tx_cost.writable_accounts().len());
assert_eq!( assert_eq!(
expected_loaded_accounts_data_size_cost, expected_loaded_accounts_data_size_cost,
tx_cost.loaded_accounts_data_size_cost tx_cost.loaded_accounts_data_size_cost()
); );
} }
@ -607,12 +618,12 @@ mod tests {
let expected_loaded_accounts_data_size_cost = (data_limit as u64) / (32 * 1024) * 8; let expected_loaded_accounts_data_size_cost = (data_limit as u64) / (32 * 1024) * 8;
let tx_cost = CostModel::calculate_cost(&tx, &feature_set); let tx_cost = CostModel::calculate_cost(&tx, &feature_set);
assert_eq!(expected_account_cost, tx_cost.write_lock_cost); assert_eq!(expected_account_cost, tx_cost.write_lock_cost());
assert_eq!(expected_execution_cost, tx_cost.builtins_execution_cost); assert_eq!(expected_execution_cost, tx_cost.builtins_execution_cost());
assert_eq!(2, tx_cost.writable_accounts.len()); assert_eq!(2, tx_cost.writable_accounts().len());
assert_eq!( assert_eq!(
expected_loaded_accounts_data_size_cost, expected_loaded_accounts_data_size_cost,
tx_cost.loaded_accounts_data_size_cost tx_cost.loaded_accounts_data_size_cost()
); );
} }
@ -640,12 +651,12 @@ mod tests {
let expected_loaded_accounts_data_size_cost = 0; let expected_loaded_accounts_data_size_cost = 0;
let tx_cost = CostModel::calculate_cost(&tx, &feature_set); let tx_cost = CostModel::calculate_cost(&tx, &feature_set);
assert_eq!(expected_account_cost, tx_cost.write_lock_cost); assert_eq!(expected_account_cost, tx_cost.write_lock_cost());
assert_eq!(expected_execution_cost, tx_cost.builtins_execution_cost); assert_eq!(expected_execution_cost, tx_cost.builtins_execution_cost());
assert_eq!(2, tx_cost.writable_accounts.len()); assert_eq!(2, tx_cost.writable_accounts().len());
assert_eq!( assert_eq!(
expected_loaded_accounts_data_size_cost, expected_loaded_accounts_data_size_cost,
tx_cost.loaded_accounts_data_size_cost tx_cost.loaded_accounts_data_size_cost()
); );
} }
@ -705,7 +716,7 @@ mod tests {
.unwrap(); .unwrap();
let expected_bpf_cost = DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT; let expected_bpf_cost = DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT;
let mut tx_cost = TransactionCost::default(); let mut tx_cost = UsageCostDetails::default();
CostModel::get_transaction_cost(&mut tx_cost, &transaction, &FeatureSet::all_enabled()); CostModel::get_transaction_cost(&mut tx_cost, &transaction, &FeatureSet::all_enabled());
assert_eq!(expected_builtin_cost, tx_cost.builtins_execution_cost); assert_eq!(expected_builtin_cost, tx_cost.builtins_execution_cost);

View File

@ -119,7 +119,7 @@ impl CostTracker {
estimated_tx_cost: &TransactionCost, estimated_tx_cost: &TransactionCost,
actual_execution_units: u64, actual_execution_units: u64,
) { ) {
let estimated_execution_units = estimated_tx_cost.bpf_execution_cost; let estimated_execution_units = estimated_tx_cost.bpf_execution_cost();
match actual_execution_units.cmp(&estimated_execution_units) { match actual_execution_units.cmp(&estimated_execution_units) {
Ordering::Equal => (), Ordering::Equal => (),
Ordering::Greater => { Ordering::Greater => {
@ -180,17 +180,16 @@ impl CostTracker {
fn would_fit(&self, tx_cost: &TransactionCost) -> Result<(), CostTrackerError> { fn would_fit(&self, tx_cost: &TransactionCost) -> Result<(), CostTrackerError> {
let cost: u64 = tx_cost.sum(); let cost: u64 = tx_cost.sum();
let vote_cost = if tx_cost.is_simple_vote { cost } else { 0 };
// check against the total package cost
if self.block_cost.saturating_add(cost) > self.block_cost_limit {
return Err(CostTrackerError::WouldExceedBlockMaxLimit);
}
if tx_cost.is_simple_vote() {
// if vote transaction, check if it exceeds vote_transaction_limit // if vote transaction, check if it exceeds vote_transaction_limit
if self.vote_cost.saturating_add(vote_cost) > self.vote_cost_limit { if self.vote_cost.saturating_add(cost) > self.vote_cost_limit {
return Err(CostTrackerError::WouldExceedVoteMaxLimit); return Err(CostTrackerError::WouldExceedVoteMaxLimit);
} }
} else if self.block_cost.saturating_add(cost) > self.block_cost_limit {
// check against the total package cost
return Err(CostTrackerError::WouldExceedBlockMaxLimit);
}
// check if the transaction itself is more costly than the account_cost_limit // check if the transaction itself is more costly than the account_cost_limit
if cost > self.account_cost_limit { if cost > self.account_cost_limit {
@ -201,7 +200,7 @@ impl CostTracker {
// size. This way, transactions are not unnecessarily retried. // size. This way, transactions are not unnecessarily retried.
let account_data_size = self let account_data_size = self
.account_data_size .account_data_size
.saturating_add(tx_cost.account_data_size); .saturating_add(tx_cost.account_data_size());
if let Some(account_data_size_limit) = self.account_data_size_limit { if let Some(account_data_size_limit) = self.account_data_size_limit {
if account_data_size > account_data_size_limit { if account_data_size > account_data_size_limit {
return Err(CostTrackerError::WouldExceedAccountDataTotalLimit); return Err(CostTrackerError::WouldExceedAccountDataTotalLimit);
@ -213,7 +212,7 @@ impl CostTracker {
} }
// check each account against account_cost_limit, // check each account against account_cost_limit,
for account_key in tx_cost.writable_accounts.iter() { for account_key in tx_cost.writable_accounts().iter() {
match self.cost_by_writable_accounts.get(account_key) { match self.cost_by_writable_accounts.get(account_key) {
Some(chained_cost) => { Some(chained_cost) => {
if chained_cost.saturating_add(cost) > self.account_cost_limit { if chained_cost.saturating_add(cost) > self.account_cost_limit {
@ -231,7 +230,7 @@ impl CostTracker {
fn add_transaction_cost(&mut self, tx_cost: &TransactionCost) { fn add_transaction_cost(&mut self, tx_cost: &TransactionCost) {
self.add_transaction_execution_cost(tx_cost, tx_cost.sum()); self.add_transaction_execution_cost(tx_cost, tx_cost.sum());
saturating_add_assign!(self.account_data_size, tx_cost.account_data_size); saturating_add_assign!(self.account_data_size, tx_cost.account_data_size());
saturating_add_assign!(self.transaction_count, 1); saturating_add_assign!(self.transaction_count, 1);
} }
@ -240,13 +239,13 @@ impl CostTracker {
self.sub_transaction_execution_cost(tx_cost, cost); self.sub_transaction_execution_cost(tx_cost, cost);
self.account_data_size = self self.account_data_size = self
.account_data_size .account_data_size
.saturating_sub(tx_cost.account_data_size); .saturating_sub(tx_cost.account_data_size());
self.transaction_count = self.transaction_count.saturating_sub(1); self.transaction_count = self.transaction_count.saturating_sub(1);
} }
/// Apply additional actual execution units to cost_tracker /// Apply additional actual execution units to cost_tracker
fn add_transaction_execution_cost(&mut self, tx_cost: &TransactionCost, adjustment: u64) { fn add_transaction_execution_cost(&mut self, tx_cost: &TransactionCost, adjustment: u64) {
for account_key in tx_cost.writable_accounts.iter() { for account_key in tx_cost.writable_accounts().iter() {
let account_cost = self let account_cost = self
.cost_by_writable_accounts .cost_by_writable_accounts
.entry(*account_key) .entry(*account_key)
@ -254,14 +253,14 @@ impl CostTracker {
*account_cost = account_cost.saturating_add(adjustment); *account_cost = account_cost.saturating_add(adjustment);
} }
self.block_cost = self.block_cost.saturating_add(adjustment); self.block_cost = self.block_cost.saturating_add(adjustment);
if tx_cost.is_simple_vote { if tx_cost.is_simple_vote() {
self.vote_cost = self.vote_cost.saturating_add(adjustment); self.vote_cost = self.vote_cost.saturating_add(adjustment);
} }
} }
/// Subtract extra execution units from cost_tracker /// Subtract extra execution units from cost_tracker
fn sub_transaction_execution_cost(&mut self, tx_cost: &TransactionCost, adjustment: u64) { fn sub_transaction_execution_cost(&mut self, tx_cost: &TransactionCost, adjustment: u64) {
for account_key in tx_cost.writable_accounts.iter() { for account_key in tx_cost.writable_accounts().iter() {
let account_cost = self let account_cost = self
.cost_by_writable_accounts .cost_by_writable_accounts
.entry(*account_key) .entry(*account_key)
@ -269,7 +268,7 @@ impl CostTracker {
*account_cost = account_cost.saturating_sub(adjustment); *account_cost = account_cost.saturating_sub(adjustment);
} }
self.block_cost = self.block_cost.saturating_sub(adjustment); self.block_cost = self.block_cost.saturating_sub(adjustment);
if tx_cost.is_simple_vote { if tx_cost.is_simple_vote() {
self.vote_cost = self.vote_cost.saturating_sub(adjustment); self.vote_cost = self.vote_cost.saturating_sub(adjustment);
} }
} }
@ -287,6 +286,7 @@ impl CostTracker {
mod tests { mod tests {
use { use {
super::*, super::*,
crate::transaction_cost::*,
solana_sdk::{ solana_sdk::{
hash::Hash, hash::Hash,
signature::{Keypair, Signer}, signature::{Keypair, Signer},
@ -331,11 +331,11 @@ mod tests {
let simple_transaction = SanitizedTransaction::from_transaction_for_tests( let simple_transaction = SanitizedTransaction::from_transaction_for_tests(
system_transaction::transfer(mint_keypair, &keypair.pubkey(), 2, *start_hash), system_transaction::transfer(mint_keypair, &keypair.pubkey(), 2, *start_hash),
); );
let mut tx_cost = TransactionCost::new_with_capacity(1); let mut tx_cost = UsageCostDetails::new_with_capacity(1);
tx_cost.bpf_execution_cost = 5; tx_cost.bpf_execution_cost = 5;
tx_cost.writable_accounts.push(mint_keypair.pubkey()); tx_cost.writable_accounts.push(mint_keypair.pubkey());
(simple_transaction, tx_cost) (simple_transaction, TransactionCost::Transaction(tx_cost))
} }
fn build_simple_vote_transaction( fn build_simple_vote_transaction(
@ -359,12 +359,12 @@ mod tests {
SimpleAddressLoader::Disabled, SimpleAddressLoader::Disabled,
) )
.unwrap(); .unwrap();
let mut tx_cost = TransactionCost::new_with_capacity(1);
tx_cost.builtins_execution_cost = 10;
tx_cost.writable_accounts.push(mint_keypair.pubkey());
tx_cost.is_simple_vote = true;
(vote_transaction, tx_cost) let writable_accounts = vec![mint_keypair.pubkey()];
(
vote_transaction,
TransactionCost::SimpleVote { writable_accounts },
)
} }
#[test] #[test]
@ -413,7 +413,11 @@ mod tests {
fn test_cost_tracker_add_data() { fn test_cost_tracker_add_data() {
let (mint_keypair, start_hash) = test_setup(); let (mint_keypair, start_hash) = test_setup();
let (_tx, mut tx_cost) = build_simple_transaction(&mint_keypair, &start_hash); let (_tx, mut tx_cost) = build_simple_transaction(&mint_keypair, &start_hash);
tx_cost.account_data_size = 1; if let TransactionCost::Transaction(ref mut usage_cost) = tx_cost {
usage_cost.account_data_size = 1;
} else {
unreachable!();
}
let cost = tx_cost.sum(); let cost = tx_cost.sum();
// build testee to have capacity for one simple transaction // build testee to have capacity for one simple transaction
@ -568,8 +572,16 @@ mod tests {
let second_account = Keypair::new(); let second_account = Keypair::new();
let (_tx1, mut tx_cost1) = build_simple_transaction(&mint_keypair, &start_hash); let (_tx1, mut tx_cost1) = build_simple_transaction(&mint_keypair, &start_hash);
let (_tx2, mut tx_cost2) = build_simple_transaction(&second_account, &start_hash); let (_tx2, mut tx_cost2) = build_simple_transaction(&second_account, &start_hash);
tx_cost1.account_data_size = MAX_BLOCK_ACCOUNTS_DATA_SIZE_DELTA; if let TransactionCost::Transaction(ref mut usage_cost) = tx_cost1 {
tx_cost2.account_data_size = MAX_BLOCK_ACCOUNTS_DATA_SIZE_DELTA + 1; usage_cost.account_data_size = MAX_BLOCK_ACCOUNTS_DATA_SIZE_DELTA;
} else {
unreachable!();
}
if let TransactionCost::Transaction(ref mut usage_cost) = tx_cost2 {
usage_cost.account_data_size = MAX_BLOCK_ACCOUNTS_DATA_SIZE_DELTA + 1;
} else {
unreachable!();
}
let cost1 = tx_cost1.sum(); let cost1 = tx_cost1.sum();
let cost2 = tx_cost2.sum(); let cost2 = tx_cost2.sum();
@ -596,8 +608,16 @@ mod tests {
let (_tx1, mut tx_cost1) = build_simple_transaction(&mint_keypair, &start_hash); let (_tx1, mut tx_cost1) = build_simple_transaction(&mint_keypair, &start_hash);
let (_tx2, mut tx_cost2) = build_simple_transaction(&second_account, &start_hash); let (_tx2, mut tx_cost2) = build_simple_transaction(&second_account, &start_hash);
let remaining_account_data_size = 1234; let remaining_account_data_size = 1234;
tx_cost1.account_data_size = remaining_account_data_size; if let TransactionCost::Transaction(ref mut usage_cost) = tx_cost1 {
tx_cost2.account_data_size = remaining_account_data_size + 1; usage_cost.account_data_size = remaining_account_data_size;
} else {
unreachable!();
}
if let TransactionCost::Transaction(ref mut usage_cost) = tx_cost2 {
usage_cost.account_data_size = remaining_account_data_size + 1;
} else {
unreachable!();
}
let cost1 = tx_cost1.sum(); let cost1 = tx_cost1.sum();
let cost2 = tx_cost2.sum(); let cost2 = tx_cost2.sum();
@ -661,11 +681,11 @@ mod tests {
// | acct3 | $cost | // | acct3 | $cost |
// and block_cost = $cost // and block_cost = $cost
{ {
let tx_cost = TransactionCost { let tx_cost = TransactionCost::Transaction(UsageCostDetails {
writable_accounts: vec![acct1, acct2, acct3], writable_accounts: vec![acct1, acct2, acct3],
bpf_execution_cost: cost, bpf_execution_cost: cost,
..TransactionCost::default() ..UsageCostDetails::default()
}; });
assert!(testee.try_add(&tx_cost).is_ok()); assert!(testee.try_add(&tx_cost).is_ok());
let (_costliest_account, costliest_account_cost) = testee.find_costliest_account(); let (_costliest_account, costliest_account_cost) = testee.find_costliest_account();
assert_eq!(cost, testee.block_cost); assert_eq!(cost, testee.block_cost);
@ -679,11 +699,11 @@ mod tests {
// | acct3 | $cost | // | acct3 | $cost |
// and block_cost = $cost * 2 // and block_cost = $cost * 2
{ {
let tx_cost = TransactionCost { let tx_cost = TransactionCost::Transaction(UsageCostDetails {
writable_accounts: vec![acct2], writable_accounts: vec![acct2],
bpf_execution_cost: cost, bpf_execution_cost: cost,
..TransactionCost::default() ..UsageCostDetails::default()
}; });
assert!(testee.try_add(&tx_cost).is_ok()); assert!(testee.try_add(&tx_cost).is_ok());
let (costliest_account, costliest_account_cost) = testee.find_costliest_account(); let (costliest_account, costliest_account_cost) = testee.find_costliest_account();
assert_eq!(cost * 2, testee.block_cost); assert_eq!(cost * 2, testee.block_cost);
@ -699,11 +719,11 @@ mod tests {
// | acct3 | $cost | // | acct3 | $cost |
// and block_cost = $cost * 2 // and block_cost = $cost * 2
{ {
let tx_cost = TransactionCost { let tx_cost = TransactionCost::Transaction(UsageCostDetails {
writable_accounts: vec![acct1, acct2], writable_accounts: vec![acct1, acct2],
bpf_execution_cost: cost, bpf_execution_cost: cost,
..TransactionCost::default() ..UsageCostDetails::default()
}; });
assert!(testee.try_add(&tx_cost).is_err()); assert!(testee.try_add(&tx_cost).is_err());
let (costliest_account, costliest_account_cost) = testee.find_costliest_account(); let (costliest_account, costliest_account_cost) = testee.find_costliest_account();
assert_eq!(cost * 2, testee.block_cost); assert_eq!(cost * 2, testee.block_cost);
@ -723,11 +743,11 @@ mod tests {
let block_max = account_max * 3; // for three accts let block_max = account_max * 3; // for three accts
let mut testee = CostTracker::new(account_max, block_max, block_max, None); let mut testee = CostTracker::new(account_max, block_max, block_max, None);
let tx_cost = TransactionCost { let tx_cost = TransactionCost::Transaction(UsageCostDetails {
writable_accounts: vec![acct1, acct2, acct3], writable_accounts: vec![acct1, acct2, acct3],
bpf_execution_cost: cost, bpf_execution_cost: cost,
..TransactionCost::default() ..UsageCostDetails::default()
}; });
let mut expected_block_cost = tx_cost.sum(); let mut expected_block_cost = tx_cost.sum();
let expected_tx_count = 1; let expected_tx_count = 1;
assert!(testee.try_add(&tx_cost).is_ok()); assert!(testee.try_add(&tx_cost).is_ok());
@ -810,11 +830,11 @@ mod tests {
let acct3 = Pubkey::new_unique(); let acct3 = Pubkey::new_unique();
let cost = 100; let cost = 100;
let tx_cost = TransactionCost { let tx_cost = TransactionCost::Transaction(UsageCostDetails {
writable_accounts: vec![acct1, acct2, acct3], writable_accounts: vec![acct1, acct2, acct3],
bpf_execution_cost: cost, bpf_execution_cost: cost,
..TransactionCost::default() ..UsageCostDetails::default()
}; });
let mut cost_tracker = CostTracker::default(); let mut cost_tracker = CostTracker::default();
@ -857,11 +877,11 @@ mod tests {
let mut cost_tracker = CostTracker::default(); let mut cost_tracker = CostTracker::default();
let cost = 100u64; let cost = 100u64;
let tx_cost = TransactionCost { let tx_cost = TransactionCost::Transaction(UsageCostDetails {
writable_accounts: vec![Pubkey::new_unique()], writable_accounts: vec![Pubkey::new_unique()],
bpf_execution_cost: cost, bpf_execution_cost: cost,
..TransactionCost::default() ..UsageCostDetails::default()
}; });
cost_tracker.add_transaction_cost(&tx_cost); cost_tracker.add_transaction_cost(&tx_cost);
// assert cost_tracker is reverted to default // assert cost_tracker is reverted to default

View File

@ -1,10 +1,99 @@
use solana_sdk::pubkey::Pubkey; use {crate::block_cost_limits, solana_sdk::pubkey::Pubkey};
/// TransactionCost is used to represent resources required to process
/// a transaction, denominated in CU (eg. Compute Units).
/// Resources required to process a regular transaction often include
/// an array of variables, such as execution cost, loaded bytes, write
/// lock and read lock etc.
/// SimpleVote has a simpler and pre-determined format: it has 1 or 2 signatures,
/// 2 write locks, a vote instruction and less than 32k (page size) accounts to load.
/// It's cost therefore can be static #33269.
const SIMPLE_VOTE_USAGE_COST: u64 = 3428;
#[derive(Debug)]
pub enum TransactionCost {
SimpleVote { writable_accounts: Vec<Pubkey> },
Transaction(UsageCostDetails),
}
impl TransactionCost {
pub fn sum(&self) -> u64 {
match self {
Self::SimpleVote { .. } => SIMPLE_VOTE_USAGE_COST,
Self::Transaction(usage_cost) => usage_cost.sum(),
}
}
pub fn bpf_execution_cost(&self) -> u64 {
match self {
Self::SimpleVote { .. } => 0,
Self::Transaction(usage_cost) => usage_cost.bpf_execution_cost,
}
}
pub fn is_simple_vote(&self) -> bool {
match self {
Self::SimpleVote { .. } => true,
Self::Transaction(_) => false,
}
}
pub fn data_bytes_cost(&self) -> u64 {
match self {
Self::SimpleVote { .. } => 0,
Self::Transaction(usage_cost) => usage_cost.data_bytes_cost,
}
}
pub fn account_data_size(&self) -> u64 {
match self {
Self::SimpleVote { .. } => 0,
Self::Transaction(usage_cost) => usage_cost.account_data_size,
}
}
pub fn loaded_accounts_data_size_cost(&self) -> u64 {
match self {
Self::SimpleVote { .. } => 8, // simple-vote loads less than 32K account data,
// the cost round up to be one page (32K) cost: 8CU
Self::Transaction(usage_cost) => usage_cost.loaded_accounts_data_size_cost,
}
}
pub fn signature_cost(&self) -> u64 {
match self {
Self::SimpleVote { .. } => block_cost_limits::SIGNATURE_COST,
Self::Transaction(usage_cost) => usage_cost.signature_cost,
}
}
pub fn write_lock_cost(&self) -> u64 {
match self {
Self::SimpleVote { .. } => block_cost_limits::WRITE_LOCK_UNITS.saturating_mul(2),
Self::Transaction(usage_cost) => usage_cost.write_lock_cost,
}
}
pub fn builtins_execution_cost(&self) -> u64 {
match self {
Self::SimpleVote { .. } => solana_vote_program::vote_processor::DEFAULT_COMPUTE_UNITS,
Self::Transaction(usage_cost) => usage_cost.builtins_execution_cost,
}
}
pub fn writable_accounts(&self) -> &[Pubkey] {
match self {
Self::SimpleVote { writable_accounts } => writable_accounts,
Self::Transaction(usage_cost) => &usage_cost.writable_accounts,
}
}
}
const MAX_WRITABLE_ACCOUNTS: usize = 256; const MAX_WRITABLE_ACCOUNTS: usize = 256;
// costs are stored in number of 'compute unit's // costs are stored in number of 'compute unit's
#[derive(Debug)] #[derive(Debug)]
pub struct TransactionCost { pub struct UsageCostDetails {
pub writable_accounts: Vec<Pubkey>, pub writable_accounts: Vec<Pubkey>,
pub signature_cost: u64, pub signature_cost: u64,
pub write_lock_cost: u64, pub write_lock_cost: u64,
@ -13,10 +102,9 @@ pub struct TransactionCost {
pub bpf_execution_cost: u64, pub bpf_execution_cost: u64,
pub loaded_accounts_data_size_cost: u64, pub loaded_accounts_data_size_cost: u64,
pub account_data_size: u64, pub account_data_size: u64,
pub is_simple_vote: bool,
} }
impl Default for TransactionCost { impl Default for UsageCostDetails {
fn default() -> Self { fn default() -> Self {
Self { Self {
writable_accounts: Vec::with_capacity(MAX_WRITABLE_ACCOUNTS), writable_accounts: Vec::with_capacity(MAX_WRITABLE_ACCOUNTS),
@ -27,13 +115,12 @@ impl Default for TransactionCost {
bpf_execution_cost: 0u64, bpf_execution_cost: 0u64,
loaded_accounts_data_size_cost: 0u64, loaded_accounts_data_size_cost: 0u64,
account_data_size: 0u64, account_data_size: 0u64,
is_simple_vote: false,
} }
} }
} }
#[cfg(test)] #[cfg(test)]
impl PartialEq for TransactionCost { impl PartialEq for UsageCostDetails {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
fn to_hash_set(v: &[Pubkey]) -> std::collections::HashSet<&Pubkey> { fn to_hash_set(v: &[Pubkey]) -> std::collections::HashSet<&Pubkey> {
v.iter().collect() v.iter().collect()
@ -46,15 +133,15 @@ impl PartialEq for TransactionCost {
&& self.bpf_execution_cost == other.bpf_execution_cost && self.bpf_execution_cost == other.bpf_execution_cost
&& self.loaded_accounts_data_size_cost == other.loaded_accounts_data_size_cost && self.loaded_accounts_data_size_cost == other.loaded_accounts_data_size_cost
&& self.account_data_size == other.account_data_size && 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) && to_hash_set(&self.writable_accounts) == to_hash_set(&other.writable_accounts)
} }
} }
#[cfg(test)] #[cfg(test)]
impl Eq for TransactionCost {} impl Eq for UsageCostDetails {}
impl TransactionCost { impl UsageCostDetails {
#[cfg(test)]
pub fn new_with_capacity(capacity: usize) -> Self { pub fn new_with_capacity(capacity: usize) -> Self {
Self { Self {
writable_accounts: Vec::with_capacity(capacity), writable_accounts: Vec::with_capacity(capacity),
@ -67,12 +154,6 @@ impl TransactionCost {
} }
pub fn sum(&self) -> u64 { pub fn sum(&self) -> u64 {
if self.is_simple_vote {
self.signature_cost
.saturating_add(self.write_lock_cost)
.saturating_add(self.data_bytes_cost)
.saturating_add(self.builtins_execution_cost)
} else {
self.signature_cost self.signature_cost
.saturating_add(self.write_lock_cost) .saturating_add(self.write_lock_cost)
.saturating_add(self.data_bytes_cost) .saturating_add(self.data_bytes_cost)
@ -80,12 +161,12 @@ impl TransactionCost {
.saturating_add(self.bpf_execution_cost) .saturating_add(self.bpf_execution_cost)
.saturating_add(self.loaded_accounts_data_size_cost) .saturating_add(self.loaded_accounts_data_size_cost)
} }
}
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use { use {
super::*,
crate::cost_model::CostModel, crate::cost_model::CostModel,
solana_sdk::{ solana_sdk::{
feature_set::FeatureSet, feature_set::FeatureSet,
@ -131,8 +212,8 @@ mod tests {
) )
.unwrap(); .unwrap();
// expected vote tx cost: 2 write locks, 2 sig, 1 vite ix, and 11 CU tx data cost // expected vote tx cost: 2 write locks, 1 sig, 1 vote ix, 8cu of loaded accounts size,
let expected_vote_cost = 4151; let expected_vote_cost = SIMPLE_VOTE_USAGE_COST;
// expected non-vote tx cost would include default loaded accounts size cost (16384) additionally // expected non-vote tx cost would include default loaded accounts size cost (16384) additionally
let expected_none_vote_cost = 20535; let expected_none_vote_cost = 20535;