diff --git a/banking-bench/src/main.rs b/banking-bench/src/main.rs index baec1c05f31a86..27e8dde3261bff 100644 --- a/banking-bench/src/main.rs +++ b/banking-bench/src/main.rs @@ -16,7 +16,7 @@ use solana_perf::packet::to_packets_chunked; use solana_poh::poh_recorder::{create_test_recorder, PohRecorder, WorkingBankEntry}; use solana_runtime::{ accounts_background_service::AbsRequestSender, bank::Bank, bank_forks::BankForks, - cost_model::CostModel, cost_tracker::CostTracker, + cost_model::CostModel, }; use solana_sdk::{ hash::Hash, @@ -233,9 +233,7 @@ fn main() { vote_receiver, None, replay_vote_sender, - Arc::new(RwLock::new(CostTracker::new(Arc::new(RwLock::new( - CostModel::default(), - ))))), + Arc::new(RwLock::new(CostModel::default())), ); poh_recorder.lock().unwrap().set_bank(&bank); diff --git a/core/benches/banking_stage.rs b/core/benches/banking_stage.rs index 479966538e8862..28a21ebc4297d0 100644 --- a/core/benches/banking_stage.rs +++ b/core/benches/banking_stage.rs @@ -19,7 +19,6 @@ use solana_perf::test_tx::test_tx; use solana_poh::poh_recorder::{create_test_recorder, WorkingBankEntry}; use solana_runtime::bank::Bank; use solana_runtime::cost_model::CostModel; -use solana_runtime::cost_tracker::CostTracker; use solana_runtime::cost_tracker_stats::CostTrackerStats; use solana_sdk::genesis_config::GenesisConfig; use solana_sdk::hash::Hash; @@ -95,9 +94,7 @@ fn bench_consume_buffered(bencher: &mut Bencher) { None::>, &BankingStageStats::default(), &recorder, - &Arc::new(RwLock::new(CostTracker::new(Arc::new(RwLock::new( - CostModel::new(std::u64::MAX, std::u64::MAX), - ))))), + &Arc::new(RwLock::new(CostModel::default())), &mut CostTrackerStats::default(), ); }); @@ -172,6 +169,11 @@ fn bench_banking(bencher: &mut Bencher, tx_type: TransactionType) { bank.ns_per_slot = std::u128::MAX; let bank = Arc::new(Bank::new_for_benches(&genesis_config)); + // set cost tracker limits to MAX so it will not filter out TXs + bank.write_cost_tracker() + .unwrap() + .set_limits(std::u64::MAX, std::u64::MAX); + debug!("threads: {} txs: {}", num_threads, txes); let transactions = match tx_type { @@ -225,9 +227,7 @@ fn bench_banking(bencher: &mut Bencher, tx_type: TransactionType) { vote_receiver, None, s, - Arc::new(RwLock::new(CostTracker::new(Arc::new(RwLock::new( - CostModel::new(std::u64::MAX, std::u64::MAX), - ))))), + Arc::new(RwLock::new(CostModel::default())), ); poh_recorder.lock().unwrap().set_bank(&bank); diff --git a/core/src/banking_stage.rs b/core/src/banking_stage.rs index 5d242f7f780d07..063aff23d0d904 100644 --- a/core/src/banking_stage.rs +++ b/core/src/banking_stage.rs @@ -25,6 +25,7 @@ use solana_runtime::{ TransactionExecutionResult, }, bank_utils, + cost_model::CostModel, cost_tracker::CostTracker, cost_tracker_stats::CostTrackerStats, transaction_batch::TransactionBatch, @@ -55,7 +56,7 @@ use std::{ net::{SocketAddr, UdpSocket}, ops::DerefMut, sync::atomic::{AtomicU64, AtomicUsize, Ordering}, - sync::{Arc, Mutex, RwLock}, + sync::{Arc, Mutex, RwLock, RwLockReadGuard}, thread::{self, Builder, JoinHandle}, time::Duration, time::Instant, @@ -96,7 +97,6 @@ pub struct BankingStageStats { current_buffered_packet_batches_count: AtomicUsize, rebuffered_packets_count: AtomicUsize, consumed_buffered_packets_count: AtomicUsize, - reset_cost_tracker_count: AtomicUsize, cost_tracker_check_count: AtomicUsize, cost_forced_retry_transactions_count: AtomicUsize, @@ -175,11 +175,6 @@ impl BankingStageStats { .swap(0, Ordering::Relaxed) as i64, i64 ), - ( - "reset_cost_tracker_count", - self.reset_cost_tracker_count.swap(0, Ordering::Relaxed) as i64, - i64 - ), ( "cost_tracker_check_count", self.cost_tracker_check_count.swap(0, Ordering::Relaxed) as i64, @@ -288,7 +283,7 @@ impl BankingStage { verified_vote_receiver: CrossbeamReceiver>, transaction_status_sender: Option, gossip_vote_sender: ReplayVoteSender, - cost_tracker: Arc>, + cost_model: Arc>, ) -> Self { Self::new_num_threads( cluster_info, @@ -299,7 +294,7 @@ impl BankingStage { Self::num_threads(), transaction_status_sender, gossip_vote_sender, - cost_tracker, + cost_model, ) } @@ -312,7 +307,7 @@ impl BankingStage { num_threads: u32, transaction_status_sender: Option, gossip_vote_sender: ReplayVoteSender, - cost_tracker: Arc>, + cost_model: Arc>, ) -> Self { let batch_limit = TOTAL_BUFFERED_PACKETS / ((num_threads - 1) as usize * PACKETS_PER_BATCH); // Single thread to generate entries from many banks. @@ -346,8 +341,8 @@ impl BankingStage { let transaction_status_sender = transaction_status_sender.clone(); let gossip_vote_sender = gossip_vote_sender.clone(); let duplicates = duplicates.clone(); - let cost_tracker = cost_tracker.clone(); let data_budget = data_budget.clone(); + let cost_model = cost_model.clone(); Builder::new() .name("solana-banking-stage-tx".to_string()) .spawn(move || { @@ -362,8 +357,8 @@ impl BankingStage { transaction_status_sender, gossip_vote_sender, &duplicates, - &cost_tracker, &data_budget, + cost_model, ); }) .unwrap() @@ -426,24 +421,6 @@ impl BankingStage { has_more_unprocessed_transactions } - fn reset_cost_tracker_if_new_bank( - cost_tracker: &Arc>, - bank_slot: Slot, - banking_stage_stats: &BankingStageStats, - cost_tracker_stats: &mut CostTrackerStats, - ) { - if cost_tracker - .write() - .unwrap() - .reset_if_new_bank(bank_slot, cost_tracker_stats) - { - // only increase counter when bank changed - banking_stage_stats - .reset_cost_tracker_count - .fetch_add(1, Ordering::Relaxed); - } - } - #[allow(clippy::too_many_arguments)] pub fn consume_buffered_packets( my_pubkey: &Pubkey, @@ -455,7 +432,7 @@ impl BankingStage { test_fn: Option, banking_stage_stats: &BankingStageStats, recorder: &TransactionRecorder, - cost_tracker: &Arc>, + cost_model: &Arc>, cost_tracker_stats: &mut CostTrackerStats, ) { let mut rebuffered_packets_len = 0; @@ -474,8 +451,8 @@ impl BankingStage { original_unprocessed_indexes, my_pubkey, *next_leader, - cost_tracker, banking_stage_stats, + cost_model, cost_tracker_stats, ); Self::update_buffered_packets_with_new_unprocessed( @@ -489,12 +466,6 @@ impl BankingStage { bank_creation_time, }) = bank_start { - Self::reset_cost_tracker_if_new_bank( - cost_tracker, - working_bank.slot(), - banking_stage_stats, - cost_tracker_stats, - ); let (processed, verified_txs_len, new_unprocessed_indexes) = Self::process_packets_transactions( &working_bank, @@ -505,7 +476,7 @@ impl BankingStage { transaction_status_sender.clone(), gossip_vote_sender, banking_stage_stats, - cost_tracker, + cost_model, cost_tracker_stats, ); if processed < verified_txs_len @@ -611,8 +582,8 @@ impl BankingStage { gossip_vote_sender: &ReplayVoteSender, banking_stage_stats: &BankingStageStats, recorder: &TransactionRecorder, - cost_tracker: &Arc>, data_budget: &DataBudget, + cost_model: &Arc>, cost_tracker_stats: &mut CostTrackerStats, ) -> BufferedPacketsDecision { let bank_start; @@ -624,15 +595,6 @@ impl BankingStage { ) = { let poh = poh_recorder.lock().unwrap(); bank_start = poh.bank_start(); - if let Some(ref bank_start) = bank_start { - Self::reset_cost_tracker_if_new_bank( - cost_tracker, - bank_start.working_bank.slot(), - banking_stage_stats, - cost_tracker_stats, - ); - }; - ( poh.leader_after_n_slots(FORWARD_TRANSACTIONS_TO_LEADER_AT_SLOT_OFFSET), PohRecorder::get_working_bank_if_not_expired(&bank_start.as_ref()), @@ -663,7 +625,7 @@ impl BankingStage { None::>, banking_stage_stats, recorder, - cost_tracker, + cost_model, cost_tracker_stats, ); } @@ -742,8 +704,8 @@ impl BankingStage { transaction_status_sender: Option, gossip_vote_sender: ReplayVoteSender, duplicates: &Arc, PacketHasher)>>, - cost_tracker: &Arc>, data_budget: &DataBudget, + cost_model: Arc>, ) { let recorder = poh_recorder.lock().unwrap().recorder(); let socket = UdpSocket::bind("0.0.0.0:0").unwrap(); @@ -764,8 +726,8 @@ impl BankingStage { &gossip_vote_sender, &banking_stage_stats, &recorder, - cost_tracker, data_budget, + &cost_model, &mut cost_tracker_stats, ); if matches!(decision, BufferedPacketsDecision::Hold) @@ -801,7 +763,7 @@ impl BankingStage { &banking_stage_stats, duplicates, &recorder, - cost_tracker, + &cost_model, &mut cost_tracker_stats, ) { Ok(()) | Err(RecvTimeoutError::Timeout) => (), @@ -906,7 +868,6 @@ impl BankingStage { }; let mut execute_timings = ExecuteTimings::default(); - let ( mut loaded_accounts, results, @@ -1137,10 +1098,11 @@ impl BankingStage { msgs: &Packets, transaction_indexes: &[usize], feature_set: &Arc, - cost_tracker: &Arc>, + read_cost_tracker: &RwLockReadGuard, banking_stage_stats: &BankingStageStats, demote_program_write_locks: bool, votes_only: bool, + cost_model: &Arc>, cost_tracker_stats: &mut CostTrackerStats, ) -> (Vec, Vec, Vec) { let mut retryable_transaction_packet_indexes: Vec = vec![]; @@ -1171,17 +1133,19 @@ impl BankingStage { let mut cost_tracker_check_time = Measure::start("cost_tracker_check_time"); let (filtered_transactions, filter_transaction_packet_indexes) = { - let cost_tracker_readonly = cost_tracker.read().unwrap(); verified_transactions_with_packet_indexes .into_iter() .filter_map(|(tx, tx_index)| { // excluding vote TX from cost_model, for now let is_vote = &msgs.packets[tx_index].meta.is_simple_vote_tx; if !is_vote - && cost_tracker_readonly + && read_cost_tracker .would_transaction_fit( &tx, - demote_program_write_locks, + &cost_model + .read() + .unwrap() + .calculate_cost(&tx, demote_program_write_locks), cost_tracker_stats, ) .is_err() @@ -1258,7 +1222,7 @@ impl BankingStage { transaction_status_sender: Option, gossip_vote_sender: &ReplayVoteSender, banking_stage_stats: &BankingStageStats, - cost_tracker: &Arc>, + cost_model: &Arc>, cost_tracker_stats: &mut CostTrackerStats, ) -> (usize, usize, Vec) { let mut packet_conversion_time = Measure::start("packet_conversion"); @@ -1267,10 +1231,11 @@ impl BankingStage { msgs, &packet_indexes, &bank.feature_set, - cost_tracker, + &bank.read_cost_tracker().unwrap(), banking_stage_stats, bank.demote_program_write_locks(), bank.vote_only_bank(), + cost_model, cost_tracker_stats, ); packet_conversion_time.stop(); @@ -1308,9 +1273,12 @@ impl BankingStage { let mut cost_tracking_time = Measure::start("cost_tracking_time"); transactions.iter().enumerate().for_each(|(index, tx)| { if unprocessed_tx_indexes.iter().all(|&i| i != index) { - cost_tracker.write().unwrap().add_transaction_cost( + bank.write_cost_tracker().unwrap().add_transaction_cost( tx, - bank.demote_program_write_locks(), + &cost_model + .read() + .unwrap() + .calculate_cost(tx, bank.demote_program_write_locks()), cost_tracker_stats, ); } @@ -1357,8 +1325,8 @@ impl BankingStage { transaction_indexes: &[usize], my_pubkey: &Pubkey, next_leader: Option, - cost_tracker: &Arc>, banking_stage_stats: &BankingStageStats, + cost_model: &Arc>, cost_tracker_stats: &mut CostTrackerStats, ) -> Vec { // Check if we are the next leader. If so, let's not filter the packets @@ -1377,10 +1345,11 @@ impl BankingStage { msgs, transaction_indexes, &bank.feature_set, - cost_tracker, + &bank.read_cost_tracker().unwrap(), banking_stage_stats, bank.demote_program_write_locks(), bank.vote_only_bank(), + cost_model, cost_tracker_stats, ); unprocessed_packet_conversion_time.stop(); @@ -1442,7 +1411,7 @@ impl BankingStage { banking_stage_stats: &BankingStageStats, duplicates: &Arc, PacketHasher)>>, recorder: &TransactionRecorder, - cost_tracker: &Arc>, + cost_model: &Arc>, cost_tracker_stats: &mut CostTrackerStats, ) -> Result<(), RecvTimeoutError> { let mut recv_time = Measure::start("process_packets_recv"); @@ -1490,12 +1459,6 @@ impl BankingStage { working_bank, bank_creation_time, } = &*working_bank_start.unwrap(); - Self::reset_cost_tracker_if_new_bank( - cost_tracker, - working_bank.slot(), - banking_stage_stats, - cost_tracker_stats, - ); let (processed, verified_txs_len, unprocessed_indexes) = Self::process_packets_transactions( @@ -1507,7 +1470,7 @@ impl BankingStage { transaction_status_sender.clone(), gossip_vote_sender, banking_stage_stats, - cost_tracker, + cost_model, cost_tracker_stats, ); @@ -1540,8 +1503,8 @@ impl BankingStage { &packet_indexes, my_pubkey, next_leader, - cost_tracker, banking_stage_stats, + cost_model, cost_tracker_stats, ); Self::push_unprocessed( @@ -1772,9 +1735,7 @@ mod tests { gossip_verified_vote_receiver, None, gossip_vote_sender, - Arc::new(RwLock::new(CostTracker::new(Arc::new(RwLock::new( - CostModel::default(), - ))))), + Arc::new(RwLock::new(CostModel::default())), ); drop(verified_sender); drop(gossip_verified_vote_sender); @@ -1823,9 +1784,7 @@ mod tests { verified_gossip_vote_receiver, None, gossip_vote_sender, - Arc::new(RwLock::new(CostTracker::new(Arc::new(RwLock::new( - CostModel::default(), - ))))), + Arc::new(RwLock::new(CostModel::default())), ); trace!("sending bank"); drop(verified_sender); @@ -1898,9 +1857,7 @@ mod tests { gossip_verified_vote_receiver, None, gossip_vote_sender, - Arc::new(RwLock::new(CostTracker::new(Arc::new(RwLock::new( - CostModel::default(), - ))))), + Arc::new(RwLock::new(CostModel::default())), ); // fund another account so we can send 2 good transactions in a single batch. @@ -2051,9 +2008,7 @@ mod tests { 3, None, gossip_vote_sender, - Arc::new(RwLock::new(CostTracker::new(Arc::new(RwLock::new( - CostModel::default(), - ))))), + Arc::new(RwLock::new(CostModel::default())), ); // wait for banking_stage to eat the packets @@ -2852,9 +2807,7 @@ mod tests { None::>, &BankingStageStats::default(), &recorder, - &Arc::new(RwLock::new(CostTracker::new(Arc::new(RwLock::new( - CostModel::default(), - ))))), + &Arc::new(RwLock::new(CostModel::default())), &mut CostTrackerStats::default(), ); assert_eq!(buffered_packets[0].1.len(), num_conflicting_transactions); @@ -2872,9 +2825,7 @@ mod tests { None::>, &BankingStageStats::default(), &recorder, - &Arc::new(RwLock::new(CostTracker::new(Arc::new(RwLock::new( - CostModel::default(), - ))))), + &Arc::new(RwLock::new(CostModel::default())), &mut CostTrackerStats::default(), ); if num_expected_unprocessed == 0 { @@ -2941,9 +2892,7 @@ mod tests { test_fn, &BankingStageStats::default(), &recorder, - &Arc::new(RwLock::new(CostTracker::new(Arc::new(RwLock::new( - CostModel::default(), - ))))), + &Arc::new(RwLock::new(CostModel::default())), &mut CostTrackerStats::default(), ); @@ -3199,12 +3148,11 @@ mod tests { &packets, &packet_indexes, &Arc::new(FeatureSet::default()), - &Arc::new(RwLock::new(CostTracker::new(Arc::new(RwLock::new( - CostModel::default(), - ))))), + &RwLock::new(CostTracker::default()).read().unwrap(), &BankingStageStats::default(), false, votes_only, + &Arc::new(RwLock::new(CostModel::default())), &mut CostTrackerStats::default(), ); assert_eq!(2, txs.len()); @@ -3216,12 +3164,11 @@ mod tests { &packets, &packet_indexes, &Arc::new(FeatureSet::default()), - &Arc::new(RwLock::new(CostTracker::new(Arc::new(RwLock::new( - CostModel::default(), - ))))), + &RwLock::new(CostTracker::default()).read().unwrap(), &BankingStageStats::default(), false, votes_only, + &Arc::new(RwLock::new(CostModel::default())), &mut CostTrackerStats::default(), ); assert_eq!(0, txs.len()); @@ -3242,12 +3189,11 @@ mod tests { &packets, &packet_indexes, &Arc::new(FeatureSet::default()), - &Arc::new(RwLock::new(CostTracker::new(Arc::new(RwLock::new( - CostModel::default(), - ))))), + &RwLock::new(CostTracker::default()).read().unwrap(), &BankingStageStats::default(), false, votes_only, + &Arc::new(RwLock::new(CostModel::default())), &mut CostTrackerStats::default(), ); assert_eq!(3, txs.len()); @@ -3259,12 +3205,11 @@ mod tests { &packets, &packet_indexes, &Arc::new(FeatureSet::default()), - &Arc::new(RwLock::new(CostTracker::new(Arc::new(RwLock::new( - CostModel::default(), - ))))), + &RwLock::new(CostTracker::default()).read().unwrap(), &BankingStageStats::default(), false, votes_only, + &Arc::new(RwLock::new(CostModel::default())), &mut CostTrackerStats::default(), ); assert_eq!(2, txs.len()); @@ -3285,12 +3230,11 @@ mod tests { &packets, &packet_indexes, &Arc::new(FeatureSet::default()), - &Arc::new(RwLock::new(CostTracker::new(Arc::new(RwLock::new( - CostModel::default(), - ))))), + &RwLock::new(CostTracker::default()).read().unwrap(), &BankingStageStats::default(), false, votes_only, + &Arc::new(RwLock::new(CostModel::default())), &mut CostTrackerStats::default(), ); assert_eq!(3, txs.len()); @@ -3302,12 +3246,11 @@ mod tests { &packets, &packet_indexes, &Arc::new(FeatureSet::default()), - &Arc::new(RwLock::new(CostTracker::new(Arc::new(RwLock::new( - CostModel::default(), - ))))), + &RwLock::new(CostTracker::default()).read().unwrap(), &BankingStageStats::default(), false, votes_only, + &Arc::new(RwLock::new(CostModel::default())), &mut CostTrackerStats::default(), ); assert_eq!(3, txs.len()); diff --git a/core/src/tpu.rs b/core/src/tpu.rs index 0c45edbde536b9..fb895bd084f58d 100644 --- a/core/src/tpu.rs +++ b/core/src/tpu.rs @@ -23,7 +23,6 @@ use solana_rpc::{ use solana_runtime::{ bank_forks::BankForks, cost_model::CostModel, - cost_tracker::CostTracker, vote_sender_types::{ReplayVoteReceiver, ReplayVoteSender}, }; use std::{ @@ -123,7 +122,6 @@ impl Tpu { cluster_confirmed_slot_sender, ); - let cost_tracker = Arc::new(RwLock::new(CostTracker::new(cost_model.clone()))); let banking_stage = BankingStage::new( cluster_info, poh_recorder, @@ -132,7 +130,7 @@ impl Tpu { verified_gossip_vote_packets_receiver, transaction_status_sender, replay_vote_sender, - cost_tracker, + cost_model.clone(), ); let broadcast_stage = broadcast_type.new_broadcast_stage( diff --git a/ledger-tool/src/main.rs b/ledger-tool/src/main.rs index 89b840f984fa08..159051d6740e68 100644 --- a/ledger-tool/src/main.rs +++ b/ledger-tool/src/main.rs @@ -775,13 +775,11 @@ fn compute_slot_cost(blockstore: &Blockstore, slot: Slot) -> Result<(), String> let mut program_ids = HashMap::new(); let mut cost_model = CostModel::default(); cost_model.initialize_cost_table(&blockstore.read_program_costs().unwrap()); - let cost_model = Arc::new(RwLock::new(cost_model)); - let mut cost_tracker = CostTracker::new(cost_model.clone()); + let mut cost_tracker = CostTracker::default(); let mut cost_tracker_stats = CostTrackerStats::default(); for entry in entries { num_transactions += entry.transactions.len(); - let mut cost_model = cost_model.write().unwrap(); entry .transactions .into_iter() @@ -802,7 +800,7 @@ fn compute_slot_cost(blockstore: &Blockstore, slot: Slot) -> Result<(), String> true, // demote_program_write_locks ); if cost_tracker - .try_add(tx_cost, &mut cost_tracker_stats) + .try_add(&transaction, &tx_cost, &mut cost_tracker_stats) .is_err() { println!( diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index 3c2b675f00397b..7e5eabe91bf66b 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -44,6 +44,7 @@ use crate::{ ancestors::{Ancestors, AncestorsForSerialization}, blockhash_queue::BlockhashQueue, builtins::{self, ActivationType, Builtin, Builtins}, + cost_tracker::CostTracker, epoch_stakes::{EpochStakes, NodeVoteAccounts}, inline_spl_token_v2_0, instruction_recorder::InstructionRecorder, @@ -994,6 +995,8 @@ pub struct Bank { pub freeze_started: AtomicBool, vote_only_bank: bool, + + pub cost_tracker: RwLock, } impl Default for BlockhashQueue { @@ -1132,6 +1135,7 @@ impl Bank { drop_callback: RwLock::::default(), freeze_started: AtomicBool::default(), vote_only_bank: false, + cost_tracker: RwLock::::default(), } } @@ -1382,6 +1386,7 @@ impl Bank { .map(|drop_callback| drop_callback.clone_box()), )), freeze_started: AtomicBool::new(false), + cost_tracker: RwLock::new(CostTracker::default()), }; datapoint_info!( @@ -1568,6 +1573,7 @@ impl Bank { drop_callback: RwLock::new(OptionalDropCallback(None)), freeze_started: AtomicBool::new(fields.hash != Hash::default()), vote_only_bank: false, + cost_tracker: RwLock::new(CostTracker::default()), }; bank.finish_init( genesis_config, @@ -5945,6 +5951,14 @@ impl Bank { .is_active(&feature_set::send_to_tpu_vote_port::id()) } + pub fn read_cost_tracker(&self) -> LockResult> { + self.cost_tracker.read() + } + + pub fn write_cost_tracker(&self) -> LockResult> { + self.cost_tracker.write() + } + // Check if the wallclock time from bank creation to now has exceeded the allotted // time for transaction processing pub fn should_bank_still_be_processing_txs( diff --git a/runtime/src/cost_model.rs b/runtime/src/cost_model.rs index a49f7900e71801..b86e65597d45df 100644 --- a/runtime/src/cost_model.rs +++ b/runtime/src/cost_model.rs @@ -11,19 +11,7 @@ use std::collections::HashMap; const MAX_WRITABLE_ACCOUNTS: usize = 256; -#[derive(Debug, Clone)] -pub enum CostModelError { - /// transaction that would fail sanitize, cost model is not able to process - /// such transaction. - InvalidTransaction, - - /// would exceed block max limit - WouldExceedBlockMaxLimit, - - /// would exceed account max limit - WouldExceedAccountMaxLimit, -} - +// costs are stored in number of 'compute unit's #[derive(AbiExample, Default, Debug)] pub struct TransactionCost { pub writable_accounts: Vec, @@ -59,9 +47,6 @@ pub struct CostModel { account_cost_limit: u64, block_cost_limit: u64, instruction_execution_cost_table: ExecuteCostTable, - - // reusable variables - transaction_cost: TransactionCost, } impl Default for CostModel { @@ -71,12 +56,11 @@ impl Default for CostModel { } impl CostModel { - pub fn new(chain_max: u64, block_max: u64) -> Self { + pub fn new(account_max: u64, block_max: u64) -> Self { Self { - account_cost_limit: chain_max, + account_cost_limit: account_max, block_cost_limit: block_max, instruction_execution_cost_table: ExecuteCostTable::default(), - transaction_cost: TransactionCost::new_with_capacity(MAX_WRITABLE_ACCOUNTS), } } @@ -119,22 +103,19 @@ impl CostModel { } pub fn calculate_cost( - &mut self, + &self, transaction: &SanitizedTransaction, demote_program_write_locks: bool, - ) -> &TransactionCost { - self.transaction_cost.reset(); + ) -> TransactionCost { + let mut tx_cost = TransactionCost::new_with_capacity(MAX_WRITABLE_ACCOUNTS); - self.transaction_cost.signature_cost = self.get_signature_cost(transaction); - self.get_write_lock_cost(transaction, demote_program_write_locks); - self.transaction_cost.data_bytes_cost = self.get_data_bytes_cost(transaction); - self.transaction_cost.execution_cost = self.get_transaction_cost(transaction); + tx_cost.signature_cost = self.get_signature_cost(transaction); + self.get_write_lock_cost(&mut tx_cost, transaction, demote_program_write_locks); + tx_cost.data_bytes_cost = self.get_data_bytes_cost(transaction); + tx_cost.execution_cost = self.get_transaction_cost(transaction); - debug!( - "transaction {:?} has cost {:?}", - transaction, self.transaction_cost - ); - &self.transaction_cost + debug!("transaction {:?} has cost {:?}", transaction, tx_cost); + tx_cost } pub fn upsert_instruction_cost( @@ -159,7 +140,8 @@ impl CostModel { } fn get_write_lock_cost( - &mut self, + &self, + tx_cost: &mut TransactionCost, transaction: &SanitizedTransaction, demote_program_write_locks: bool, ) { @@ -168,8 +150,8 @@ impl CostModel { let is_writable = message.is_writable(i, demote_program_write_locks); if is_writable { - self.transaction_cost.writable_accounts.push(*k); - self.transaction_cost.write_lock_cost += WRITE_LOCK_UNITS; + tx_cost.writable_accounts.push(*k); + tx_cost.write_lock_cost += WRITE_LOCK_UNITS; } }); } @@ -381,7 +363,7 @@ mod tests { .try_into() .unwrap(); - let mut cost_model = CostModel::default(); + let cost_model = CostModel::default(); let tx_cost = cost_model.calculate_cost(&tx, /*demote_program_write_locks=*/ true); assert_eq!(2 + 2, tx_cost.writable_accounts.len()); assert_eq!(signer1.pubkey(), tx_cost.writable_accounts[0]); @@ -492,7 +474,7 @@ mod tests { }) } else { thread::spawn(move || { - let mut cost_model = cost_model.write().unwrap(); + let cost_model = cost_model.write().unwrap(); let tx_cost = cost_model .calculate_cost(&tx, /*demote_program_write_locks=*/ true); assert_eq!(3, tx_cost.writable_accounts.len()); diff --git a/runtime/src/cost_tracker.rs b/runtime/src/cost_tracker.rs index bb26856b21daa6..1b2c7b1ee0132b 100644 --- a/runtime/src/cost_tracker.rs +++ b/runtime/src/cost_tracker.rs @@ -1,22 +1,27 @@ //! `cost_tracker` keeps tracking transaction cost per chained accounts as well as for entire block -//! It aggregates `cost_model`, which provides service of calculating transaction cost. //! The main functions are: -//! - would_transaction_fit(&tx), immutable function to test if `tx` would fit into current block -//! - add_transaction_cost(&tx), mutable function to accumulate `tx` cost to tracker. +//! - would_transaction_fit(&tx_cost), immutable function to test if tx with tx_cost would fit into current block +//! - add_transaction_cost(&tx_cost), mutable function to accumulate tx_cost to tracker. //! -use crate::cost_model::{CostModel, CostModelError, TransactionCost}; +use crate::block_cost_limits::*; +use crate::cost_model::TransactionCost; use crate::cost_tracker_stats::CostTrackerStats; use solana_sdk::{clock::Slot, pubkey::Pubkey, transaction::SanitizedTransaction}; -use std::{ - collections::HashMap, - sync::{Arc, RwLock}, -}; +use std::collections::HashMap; const WRITABLE_ACCOUNTS_PER_BLOCK: usize = 512; +#[derive(Debug, Clone)] +pub enum CostTrackerError { + /// would exceed block max limit + WouldExceedBlockMaxLimit, + + /// would exceed account max limit + WouldExceedAccountMaxLimit, +} + #[derive(AbiExample, Debug)] pub struct CostTracker { - cost_model: Arc>, account_cost_limit: u64, block_cost_limit: u64, current_bank_slot: Slot, @@ -26,22 +31,14 @@ pub struct CostTracker { impl Default for CostTracker { fn default() -> Self { - CostTracker::new(Arc::new(RwLock::new(CostModel::default()))) + CostTracker::new(MAX_WRITABLE_ACCOUNT_UNITS, MAX_BLOCK_UNITS) } } impl CostTracker { - pub fn new(cost_model: Arc>) -> Self { - let (account_cost_limit, block_cost_limit) = { - let cost_model = cost_model.read().unwrap(); - ( - cost_model.get_account_cost_limit(), - cost_model.get_block_cost_limit(), - ) - }; + pub fn new(account_cost_limit: u64, block_cost_limit: u64) -> Self { assert!(account_cost_limit <= block_cost_limit); Self { - cost_model, account_cost_limit, block_cost_limit, current_bank_slot: 0, @@ -50,65 +47,43 @@ impl CostTracker { } } + // bench tests needs to reset limits + pub fn set_limits(&mut self, account_cost_limit: u64, block_cost_limit: u64) { + self.account_cost_limit = account_cost_limit; + self.block_cost_limit = block_cost_limit; + } + pub fn would_transaction_fit( &self, - transaction: &SanitizedTransaction, - demote_program_write_locks: bool, + _transaction: &SanitizedTransaction, + tx_cost: &TransactionCost, stats: &mut CostTrackerStats, - ) -> Result<(), CostModelError> { - let mut cost_model = self.cost_model.write().unwrap(); - let tx_cost = cost_model.calculate_cost(transaction, demote_program_write_locks); + ) -> Result<(), CostTrackerError> { self.would_fit(&tx_cost.writable_accounts, &tx_cost.sum(), stats) } pub fn add_transaction_cost( &mut self, - transaction: &SanitizedTransaction, - demote_program_write_locks: bool, + _transaction: &SanitizedTransaction, + tx_cost: &TransactionCost, stats: &mut CostTrackerStats, ) { - let mut cost_model = self.cost_model.write().unwrap(); - let tx_cost = cost_model.calculate_cost(transaction, demote_program_write_locks); let cost = tx_cost.sum(); - for account_key in tx_cost.writable_accounts.iter() { - *self - .cost_by_writable_accounts - .entry(*account_key) - .or_insert(0) += cost; - } - self.block_cost += cost; + self.add_transaction(&tx_cost.writable_accounts, &cost); stats.transaction_count += 1; stats.block_cost += cost; } - pub fn reset_if_new_bank(&mut self, slot: Slot, stats: &mut CostTrackerStats) -> bool { - // report stats when slot changes - if slot != stats.bank_slot { - stats.report(); - *stats = CostTrackerStats::new(stats.id, slot); - } - - if slot != self.current_bank_slot { - self.current_bank_slot = slot; - self.cost_by_writable_accounts.clear(); - self.block_cost = 0; - - true - } else { - false - } - } - pub fn try_add( &mut self, - transaction_cost: &TransactionCost, + _transaction: &SanitizedTransaction, + tx_cost: &TransactionCost, stats: &mut CostTrackerStats, - ) -> Result { - let cost = transaction_cost.sum(); - self.would_fit(&transaction_cost.writable_accounts, &cost, stats)?; - - self.add_transaction(&transaction_cost.writable_accounts, &cost); + ) -> Result { + let cost = tx_cost.sum(); + self.would_fit(&tx_cost.writable_accounts, &cost, stats)?; + self.add_transaction(&tx_cost.writable_accounts, &cost); Ok(self.block_cost) } @@ -117,17 +92,17 @@ impl CostTracker { keys: &[Pubkey], cost: &u64, stats: &mut CostTrackerStats, - ) -> Result<(), CostModelError> { + ) -> Result<(), CostTrackerError> { stats.transaction_cost_histogram.increment(*cost).unwrap(); // check against the total package cost if self.block_cost + cost > self.block_cost_limit { - return Err(CostModelError::WouldExceedBlockMaxLimit); + return Err(CostTrackerError::WouldExceedBlockMaxLimit); } // check if the transaction itself is more costly than the account_cost_limit if *cost > self.account_cost_limit { - return Err(CostModelError::WouldExceedAccountMaxLimit); + return Err(CostTrackerError::WouldExceedAccountMaxLimit); } // check each account against account_cost_limit, @@ -140,7 +115,7 @@ impl CostTracker { .unwrap(); if chained_cost + cost > self.account_cost_limit { - return Err(CostModelError::WouldExceedAccountMaxLimit); + return Err(CostTrackerError::WouldExceedAccountMaxLimit); } else { continue; } @@ -207,7 +182,7 @@ mod tests { system_transaction, transaction::Transaction, }; - use std::{cmp, sync::Arc}; + use std::{cmp, convert::TryFrom, sync::Arc}; fn test_setup() -> (Keypair, Hash) { solana_logger::setup(); @@ -234,7 +209,7 @@ mod tests { #[test] fn test_cost_tracker_initialization() { - let testee = CostTracker::new(Arc::new(RwLock::new(CostModel::new(10, 11)))); + let testee = CostTracker::new(10, 11); assert_eq!(10, testee.account_cost_limit); assert_eq!(11, testee.block_cost_limit); assert_eq!(0, testee.cost_by_writable_accounts.len()); @@ -247,7 +222,7 @@ mod tests { let (_tx, keys, cost) = build_simple_transaction(&mint_keypair, &start_hash); // build testee to have capacity for one simple transaction - let mut testee = CostTracker::new(Arc::new(RwLock::new(CostModel::new(cost, cost)))); + let mut testee = CostTracker::new(cost, cost); assert!(testee .would_fit(&keys, &cost, &mut CostTrackerStats::default()) .is_ok()); @@ -263,10 +238,7 @@ mod tests { let (_tx2, keys2, cost2) = build_simple_transaction(&mint_keypair, &start_hash); // build testee to have capacity for two simple transactions, with same accounts - let mut testee = CostTracker::new(Arc::new(RwLock::new(CostModel::new( - cost1 + cost2, - cost1 + cost2, - )))); + let mut testee = CostTracker::new(cost1 + cost2, cost1 + cost2); { assert!(testee .would_fit(&keys1, &cost1, &mut CostTrackerStats::default()) @@ -292,10 +264,7 @@ mod tests { let (_tx2, keys2, cost2) = build_simple_transaction(&second_account, &start_hash); // build testee to have capacity for two simple transactions, with same accounts - let mut testee = CostTracker::new(Arc::new(RwLock::new(CostModel::new( - cmp::max(cost1, cost2), - cost1 + cost2, - )))); + let mut testee = CostTracker::new(cmp::max(cost1, cost2), cost1 + cost2); { assert!(testee .would_fit(&keys1, &cost1, &mut CostTrackerStats::default()) @@ -320,10 +289,7 @@ mod tests { let (_tx2, keys2, cost2) = build_simple_transaction(&mint_keypair, &start_hash); // build testee to have capacity for two simple transactions, but not for same accounts - let mut testee = CostTracker::new(Arc::new(RwLock::new(CostModel::new( - cmp::min(cost1, cost2), - cost1 + cost2, - )))); + let mut testee = CostTracker::new(cmp::min(cost1, cost2), cost1 + cost2); // should have room for first transaction { assert!(testee @@ -348,10 +314,7 @@ mod tests { let (_tx2, keys2, cost2) = build_simple_transaction(&second_account, &start_hash); // build testee to have capacity for each chain, but not enough room for both transactions - let mut testee = CostTracker::new(Arc::new(RwLock::new(CostModel::new( - cmp::max(cost1, cost2), - cost1 + cost2 - 1, - )))); + let mut testee = CostTracker::new(cmp::max(cost1, cost2), cost1 + cost2 - 1); // should have room for first transaction { assert!(testee @@ -368,48 +331,11 @@ mod tests { } #[test] - fn test_cost_tracker_reset() { + fn test_cost_tracker_try_add_is_atomic() { let (mint_keypair, start_hash) = test_setup(); - // build two transactions with same signed account - let (_tx1, keys1, cost1) = build_simple_transaction(&mint_keypair, &start_hash); - let (_tx2, keys2, cost2) = build_simple_transaction(&mint_keypair, &start_hash); + let (tx, _keys, _cost) = build_simple_transaction(&mint_keypair, &start_hash); + let tx = SanitizedTransaction::try_from(tx).unwrap(); - // build testee to have capacity for two simple transactions, but not for same accounts - let mut testee = CostTracker::new(Arc::new(RwLock::new(CostModel::new( - cmp::min(cost1, cost2), - cost1 + cost2, - )))); - // should have room for first transaction - { - assert!(testee - .would_fit(&keys1, &cost1, &mut CostTrackerStats::default()) - .is_ok()); - testee.add_transaction(&keys1, &cost1); - assert_eq!(1, testee.cost_by_writable_accounts.len()); - assert_eq!(cost1, testee.block_cost); - } - // but no more sapce on the same chain (same signer account) - { - assert!(testee - .would_fit(&keys2, &cost2, &mut CostTrackerStats::default()) - .is_err()); - } - // reset the tracker - { - testee.reset_if_new_bank(100, &mut CostTrackerStats::default()); - assert_eq!(0, testee.cost_by_writable_accounts.len()); - assert_eq!(0, testee.block_cost); - } - //now the second transaction can be added - { - assert!(testee - .would_fit(&keys2, &cost2, &mut CostTrackerStats::default()) - .is_ok()); - } - } - - #[test] - fn test_cost_tracker_try_add_is_atomic() { let acct1 = Pubkey::new_unique(); let acct2 = Pubkey::new_unique(); let acct3 = Pubkey::new_unique(); @@ -417,10 +343,7 @@ mod tests { let account_max = cost * 2; let block_max = account_max * 3; // for three accts - let mut testee = CostTracker::new(Arc::new(RwLock::new(CostModel::new( - account_max, - block_max, - )))); + let mut testee = CostTracker::new(account_max, block_max); // case 1: a tx writes to 3 accounts, should success, we will have: // | acct1 | $cost | @@ -434,7 +357,7 @@ mod tests { ..TransactionCost::default() }; assert!(testee - .try_add(&tx_cost, &mut CostTrackerStats::default()) + .try_add(&tx, &tx_cost, &mut CostTrackerStats::default()) .is_ok()); let stat = testee.get_stats(); assert_eq!(cost, stat.total_cost); @@ -454,7 +377,7 @@ mod tests { ..TransactionCost::default() }; assert!(testee - .try_add(&tx_cost, &mut CostTrackerStats::default()) + .try_add(&tx, &tx_cost, &mut CostTrackerStats::default()) .is_ok()); let stat = testee.get_stats(); assert_eq!(cost * 2, stat.total_cost); @@ -476,7 +399,7 @@ mod tests { ..TransactionCost::default() }; assert!(testee - .try_add(&tx_cost, &mut CostTrackerStats::default()) + .try_add(&tx, &tx_cost, &mut CostTrackerStats::default()) .is_err()); let stat = testee.get_stats(); assert_eq!(cost * 2, stat.total_cost);