Skip to content

Commit

Permalink
1. moving cost model to bank as its private member;
Browse files Browse the repository at this point in the history
2. bank utilizes CostModel when load/execute transaction, if it exceedds limit,
transaction are being put into retry list, and CostExceedsLimit error is
recorded;
3. Add AbiExample to CostModel to address CI error report; i
4. Add code to debug log CostModel stats when a bank is frozen, this is to
help collecting data when running validator on mainnet'
  • Loading branch information
tao-stones committed May 10, 2021
1 parent f39dda0 commit 3ee2296
Show file tree
Hide file tree
Showing 8 changed files with 833 additions and 3 deletions.
9 changes: 9 additions & 0 deletions core/src/replay_stage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1177,6 +1177,15 @@ impl ReplayStage {
);

let tpu_bank = bank_forks.write().unwrap().insert(tpu_bank);

if let Some(bank) = poh_recorder.lock().unwrap().bank() {
debug!(
"final cost model for bank {:?} is {:?}",
bank,
bank.cost_model_stats()
);
}

poh_recorder.lock().unwrap().set_bank(&tpu_bank);
} else {
error!("{} No next leader found", my_pubkey);
Expand Down
111 changes: 108 additions & 3 deletions runtime/src/bank.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ use crate::{
accounts_index::{AccountSecondaryIndexes, Ancestors, IndexKey},
blockhash_queue::BlockhashQueue,
builtins::{self, ActivationType},
cost_model::{CostModel, CostModelStats},
epoch_stakes::{EpochStakes, NodeVoteAccounts},
hashed_transaction::{HashedTransaction, HashedTransactionSlice},
inline_spl_token_v2_0,
Expand Down Expand Up @@ -120,7 +121,7 @@ use std::{
rc::Rc,
sync::{
atomic::{AtomicBool, AtomicU64, Ordering::Relaxed},
LockResult, RwLockWriteGuard, {Arc, RwLock, RwLockReadGuard},
LockResult, RwLockWriteGuard, {Arc, Mutex, RwLock, RwLockReadGuard},
},
time::Duration,
time::Instant,
Expand Down Expand Up @@ -916,6 +917,9 @@ pub struct Bank {
pub drop_callback: RwLock<OptionalDropCallback>,

pub freeze_started: AtomicBool,

/// cost model to be private member of bank
cost_model: Arc<Mutex<CostModel>>,
}

impl Default for BlockhashQueue {
Expand Down Expand Up @@ -1121,6 +1125,7 @@ impl Bank {
.map(|drop_callback| drop_callback.clone_box()),
)),
freeze_started: AtomicBool::new(false),
cost_model: Arc::new(Mutex::new(CostModel::new())),
};

datapoint_info!(
Expand Down Expand Up @@ -1268,6 +1273,7 @@ impl Bank {
feature_set: new(),
drop_callback: RwLock::new(OptionalDropCallback(None)),
freeze_started: AtomicBool::new(fields.hash != Hash::default()),
cost_model: Arc::new(Mutex::new(CostModel::new())),
};
bank.finish_init(genesis_config, additional_builtins);

Expand Down Expand Up @@ -2704,6 +2710,7 @@ impl Bank {
&mut error_counters,
);
let cache_results = self.check_status_cache(hashed_txs, age_results, &mut error_counters);

if self.upgrade_epoch() {
// Reject all non-vote transactions
self.filter_by_vote_transactions(
Expand All @@ -2728,6 +2735,37 @@ impl Bank {
balances
}

pub fn cost_model_stats(&self) -> CostModelStats {
self.cost_model.lock().unwrap().get_stats()
}

fn filter_transactions_by_cost<'a>(
&self,
hashed_txs: impl Iterator<Item = &'a Transaction>,
check_results: Vec<TransactionCheckResult>,
) -> (Vec<usize>, Vec<TransactionCheckResult>) {
let mut retryable_txs: Vec<_> = vec![];
let results = hashed_txs
.zip(check_results)
.enumerate()
.map(|(index, (tx, check_result))| {
if check_result.0.is_ok() {
match self.cost_model.lock().unwrap().try_to_add_transaction(tx) {
Some(_) => {
return check_result;
}
None => {
retryable_txs.push(index);
return (Err(TransactionError::CostExceedsLimit), check_result.1);
}
}
}
check_result
})
.collect();
(retryable_txs, results)
}

#[allow(clippy::cognitive_complexity)]
fn update_error_counters(error_counters: &ErrorCounters) {
if 0 != error_counters.total {
Expand Down Expand Up @@ -2950,7 +2988,7 @@ impl Bank {
inc_new_counter_info!("bank-process_transactions", hashed_txs.len());
let mut error_counters = ErrorCounters::default();

let retryable_txs: Vec<_> = batch
let mut retryable_txs: Vec<_> = batch
.lock_results()
.iter()
.enumerate()
Expand All @@ -2971,13 +3009,19 @@ impl Bank {
max_age,
&mut error_counters,
);

// check transactions against cost_model, those not be accepted will be returned
// as retryable transactions, and excluded from downstream process (eg execution).
let (retryable_txs_by_cost_model, check_and_filtered_results) =
self.filter_transactions_by_cost(hashed_txs.as_transactions_iter(), check_results);
retryable_txs.extend(retryable_txs_by_cost_model);
check_time.stop();

let mut load_time = Measure::start("accounts_load");
let mut loaded_accounts = self.rc.accounts.load_accounts(
&self.ancestors,
hashed_txs.as_transactions_iter(),
check_results,
check_and_filtered_results,
&self.blockhash_queue.read().unwrap(),
&mut error_counters,
&self.rent_collector,
Expand Down Expand Up @@ -12699,4 +12743,65 @@ pub(crate) mod tests {
0
);
}

#[test]
fn test_bank_filter_transactions_by_cost_ok() {
solana_logger::setup();
let GenesisConfigInfo { genesis_config, .. } = create_genesis_config_with_leader(
1_000_000_000_000_000,
&Pubkey::new_unique(),
bootstrap_validator_stake_lamports(),
);
let bank = Bank::new(&genesis_config);

let mock_program_id = Pubkey::new(&[2u8; 32]);
let mock_transaction = create_mock_transaction(
&Keypair::new(),
&Keypair::new(),
&Keypair::new(),
&Keypair::new(),
mock_program_id,
genesis_config.hash(),
);
let input_check_results = vec![(Ok(()), None)];
let (retryable_txs, check_results) =
bank.filter_transactions_by_cost(&mut [mock_transaction].iter(), input_check_results);

// if transactino is booked by cost model, then it will not retry, nor error
assert_eq!(0, retryable_txs.len());
assert_eq!(1, check_results.len());
assert!(check_results[0].0.is_ok());
}

#[test]
fn test_bank_filter_transactions_by_cost_exceed_limit() {
solana_logger::setup();
let GenesisConfigInfo { genesis_config, .. } = create_genesis_config_with_leader(
1_000_000_000_000_000,
&Pubkey::new_unique(),
bootstrap_validator_stake_lamports(),
);
// dirty way of assigning a cost model with min limit to testing bank
let mut bank = Bank::new(&genesis_config);
bank.cost_model = Arc::new(Mutex::new(CostModel::new_with_config(1, 1)));

let mock_program_id = Pubkey::new(&[2u8; 32]);
let mock_transaction = create_mock_transaction(
&Keypair::new(),
&Keypair::new(),
&Keypair::new(),
&Keypair::new(),
mock_program_id,
genesis_config.hash(),
);
let input_check_results = vec![(Ok(()), None)];
let (retryable_txs, check_results) =
bank.filter_transactions_by_cost(&mut [mock_transaction].iter(), input_check_results);

// transaction not allowed by cost model is put into retryable list, and proper err is logged
assert_eq!(1, retryable_txs.len());
assert_eq!(0, retryable_txs[0]);
assert_eq!(1, check_results.len());
assert_eq!(Err(TransactionError::CostExceedsLimit), check_results[0].0);
}
}
Loading

0 comments on commit 3ee2296

Please sign in to comment.