diff --git a/bench-tps/src/bench.rs b/bench-tps/src/bench.rs index bddce402ac6382..c8a19683e21358 100644 --- a/bench-tps/src/bench.rs +++ b/bench-tps/src/bench.rs @@ -1149,7 +1149,7 @@ pub fn fund_keypairs( mod tests { use { super::*, - solana_runtime::{bank::Bank, bank_client::BankClient}, + solana_runtime::{bank::Bank, bank_client::BankClient, bank_forks::BankForks}, solana_sdk::{ commitment_config::CommitmentConfig, feature_set::FeatureSet, @@ -1160,16 +1160,18 @@ mod tests { }, }; - fn bank_with_all_features(genesis_config: &GenesisConfig) -> Arc { + fn bank_with_all_features( + genesis_config: &GenesisConfig, + ) -> (Arc, Arc>) { let mut bank = Bank::new_for_tests(genesis_config); bank.feature_set = Arc::new(FeatureSet::all_enabled()); - bank.wrap_with_bank_forks_for_tests().0 + bank.wrap_with_bank_forks_for_tests() } #[test] fn test_bench_tps_bank_client() { let (genesis_config, id) = create_genesis_config(sol_to_lamports(10_000.0)); - let bank = bank_with_all_features(&genesis_config); + let (bank, _bank_forks) = bank_with_all_features(&genesis_config); let client = Arc::new(BankClient::new_shared(bank)); let config = Config { @@ -1190,7 +1192,7 @@ mod tests { #[test] fn test_bench_tps_fund_keys() { let (genesis_config, id) = create_genesis_config(sol_to_lamports(10_000.0)); - let bank = bank_with_all_features(&genesis_config); + let (bank, _bank_forks) = bank_with_all_features(&genesis_config); let client = Arc::new(BankClient::new_shared(bank)); let keypair_count = 20; let lamports = 20; @@ -1215,7 +1217,7 @@ mod tests { let (mut genesis_config, id) = create_genesis_config(sol_to_lamports(10_000.0)); let fee_rate_governor = FeeRateGovernor::new(11, 0); genesis_config.fee_rate_governor = fee_rate_governor; - let bank = bank_with_all_features(&genesis_config); + let (bank, _bank_forks) = bank_with_all_features(&genesis_config); let client = Arc::new(BankClient::new_shared(bank)); let keypair_count = 20; let lamports = 20; @@ -1233,7 +1235,7 @@ mod tests { #[test] fn test_bench_tps_create_durable_nonce() { let (genesis_config, id) = create_genesis_config(sol_to_lamports(10_000.0)); - let bank = bank_with_all_features(&genesis_config); + let (bank, _bank_forks) = bank_with_all_features(&genesis_config); let client = Arc::new(BankClient::new_shared(bank)); let keypair_count = 10; let lamports = 10_000_000; diff --git a/core/benches/banking_stage.rs b/core/benches/banking_stage.rs index 242d3b0ed6b530..22f5d01d9900f1 100644 --- a/core/benches/banking_stage.rs +++ b/core/benches/banking_stage.rs @@ -398,10 +398,14 @@ fn simulate_process_entries( let bank_fork = BankForks::new_rw_arc(bank); let bank = bank_fork.read().unwrap().get_with_scheduler(slot).unwrap(); bank.clone_without_scheduler() +<<<<<<< HEAD .loaded_programs_cache .write() .unwrap() .set_fork_graph(bank_fork.clone()); +======= + .set_fork_graph_in_program_cache(Arc::downgrade(&bank_fork)); +>>>>>>> d441c0f577 (Fix BankForks::new_rw_arc memory leak (#1893)) for i in 0..(num_accounts / 2) { bank.transfer(initial_lamports, mint_keypair, &keypairs[i * 2].pubkey()) diff --git a/core/benches/consumer.rs b/core/benches/consumer.rs index f056fdd0d49a34..264c7b58809f5b 100644 --- a/core/benches/consumer.rs +++ b/core/benches/consumer.rs @@ -19,7 +19,7 @@ use { poh_recorder::{create_test_recorder, PohRecorder}, poh_service::PohService, }, - solana_runtime::bank::Bank, + solana_runtime::{bank::Bank, bank_forks::BankForks}, solana_sdk::{ account::Account, feature_set::apply_cost_tracker_during_replay, signature::Keypair, signer::Signer, stake_history::Epoch, system_program, system_transaction, @@ -85,6 +85,7 @@ fn create_consumer(poh_recorder: &RwLock) -> Consumer { struct BenchFrame { bank: Arc, + _bank_forks: Arc>, ledger_path: TempDir, exit: Arc, poh_recorder: Arc>, @@ -114,8 +115,13 @@ fn setup(apply_cost_tracker_during_replay: bool) -> BenchFrame { // set cost tracker limits to MAX so it will not filter out TXs bank.write_cost_tracker() .unwrap() +<<<<<<< HEAD .set_limits(std::u64::MAX, std::u64::MAX, std::u64::MAX); let bank = bank.wrap_with_bank_forks_for_tests().0; +======= + .set_limits(u64::MAX, u64::MAX, u64::MAX); + let (bank, bank_forks) = bank.wrap_with_bank_forks_for_tests(); +>>>>>>> d441c0f577 (Fix BankForks::new_rw_arc memory leak (#1893)) let ledger_path = TempDir::new().unwrap(); let blockstore = Arc::new( @@ -126,6 +132,7 @@ fn setup(apply_cost_tracker_during_replay: bool) -> BenchFrame { BenchFrame { bank, + _bank_forks: bank_forks, ledger_path, exit, poh_recorder, @@ -151,6 +158,7 @@ fn bench_process_and_record_transactions( let BenchFrame { bank, + _bank_forks, ledger_path: _ledger_path, exit, poh_recorder, diff --git a/core/src/banking_stage.rs b/core/src/banking_stage.rs index 82acf209f1731c..2490e8f9685bd7 100644 --- a/core/src/banking_stage.rs +++ b/core/src/banking_stage.rs @@ -1050,7 +1050,7 @@ mod tests { drop(poh_recorder); let mut blockhash = start_hash; - let bank = Bank::new_no_wallclock_throttle_for_tests(&genesis_config).0; + let (bank, _bank_forks) = Bank::new_no_wallclock_throttle_for_tests(&genesis_config); bank.process_transaction(&fund_tx).unwrap(); //receive entries + ticks loop { @@ -1194,7 +1194,7 @@ mod tests { .map(|(_bank, (entry, _tick_height))| entry) .collect(); - let bank = Bank::new_no_wallclock_throttle_for_tests(&genesis_config).0; + let (bank, _bank_forks) = Bank::new_no_wallclock_throttle_for_tests(&genesis_config); for entry in entries { bank.process_entry_transactions(entry.transactions) .iter() @@ -1218,7 +1218,7 @@ mod tests { mint_keypair, .. } = create_genesis_config(10_000); - let bank = Bank::new_no_wallclock_throttle_for_tests(&genesis_config).0; + let (bank, _bank_forks) = Bank::new_no_wallclock_throttle_for_tests(&genesis_config); let ledger_path = get_tmp_ledger_path_auto_delete!(); { let blockstore = Blockstore::open(ledger_path.path()) diff --git a/core/src/banking_stage/consume_worker.rs b/core/src/banking_stage/consume_worker.rs index 404554a48eea14..2f8d36ae9c95a3 100644 --- a/core/src/banking_stage/consume_worker.rs +++ b/core/src/banking_stage/consume_worker.rs @@ -626,7 +626,14 @@ mod tests { get_tmp_ledger_path_auto_delete, leader_schedule_cache::LeaderScheduleCache, }, solana_poh::poh_recorder::{PohRecorder, WorkingBankEntry}, +<<<<<<< HEAD solana_runtime::prioritization_fee_cache::PrioritizationFeeCache, +======= + solana_runtime::{ + bank_forks::BankForks, prioritization_fee_cache::PrioritizationFeeCache, + vote_sender_types::ReplayVoteReceiver, + }, +>>>>>>> d441c0f577 (Fix BankForks::new_rw_arc memory leak (#1893)) solana_sdk::{ genesis_config::GenesisConfig, poh_config::PohConfig, pubkey::Pubkey, signature::Keypair, system_transaction, @@ -645,6 +652,7 @@ mod tests { mint_keypair: Keypair, genesis_config: GenesisConfig, bank: Arc, + _bank_forks: Arc>, _ledger_path: TempDir, _entry_receiver: Receiver, poh_recorder: Arc>, @@ -661,7 +669,7 @@ mod tests { mint_keypair, .. } = create_slow_genesis_config(10_000); - let bank = Bank::new_no_wallclock_throttle_for_tests(&genesis_config).0; + let (bank, bank_forks) = Bank::new_no_wallclock_throttle_for_tests(&genesis_config); let ledger_path = get_tmp_ledger_path_auto_delete!(); let blockstore = Blockstore::open(ledger_path.path()) @@ -704,6 +712,7 @@ mod tests { mint_keypair, genesis_config, bank, + _bank_forks: bank_forks, _ledger_path: ledger_path, _entry_receiver: entry_receiver, poh_recorder, diff --git a/core/src/banking_stage/consumer.rs b/core/src/banking_stage/consumer.rs index 1b8487a923b972..2052cd7b47ee75 100644 --- a/core/src/banking_stage/consumer.rs +++ b/core/src/banking_stage/consumer.rs @@ -829,7 +829,7 @@ mod tests { solana_poh::poh_recorder::{PohRecorder, Record, WorkingBankEntry}, solana_program_runtime::timings::ProgramTiming, solana_rpc::transaction_status_service::TransactionStatusService, - solana_runtime::prioritization_fee_cache::PrioritizationFeeCache, + solana_runtime::{bank_forks::BankForks, prioritization_fee_cache::PrioritizationFeeCache}, solana_sdk::{ account::AccountSharedData, account_utils::StateMut, @@ -965,6 +965,7 @@ mod tests { ) -> ( Vec, Arc, + Arc>, Arc>, Receiver, GenesisConfigInfo, @@ -979,7 +980,7 @@ mod tests { } = &genesis_config_info; let blockstore = Blockstore::open(ledger_path).expect("Expected to be able to open database ledger"); - let bank = Bank::new_no_wallclock_throttle_for_tests(genesis_config).0; + let (bank, bank_forks) = Bank::new_no_wallclock_throttle_for_tests(genesis_config); let exit = Arc::new(AtomicBool::default()); let (poh_recorder, entry_receiver, record_receiver) = PohRecorder::new( bank.tick_height(), @@ -1008,6 +1009,7 @@ mod tests { ( transactions, bank, + bank_forks, poh_recorder, entry_receiver, genesis_config_info, @@ -1035,7 +1037,7 @@ mod tests { mint_keypair, .. } = create_slow_genesis_config(10_000); - let bank = Bank::new_no_wallclock_throttle_for_tests(&genesis_config).0; + let (bank, _bank_forks) = Bank::new_no_wallclock_throttle_for_tests(&genesis_config); let pubkey = solana_sdk::pubkey::new_rand(); let transactions = sanitize_transactions(vec![system_transaction::transfer( @@ -1163,7 +1165,7 @@ mod tests { mint_keypair, .. } = create_slow_genesis_config(10_000); - let bank = Bank::new_no_wallclock_throttle_for_tests(&genesis_config).0; + let (bank, _bank_forks) = Bank::new_no_wallclock_throttle_for_tests(&genesis_config); let pubkey = Pubkey::new_unique(); // setup nonce account with a durable nonce different from the current @@ -1319,7 +1321,7 @@ mod tests { mint_keypair, .. } = create_slow_genesis_config(10_000); - let bank = Bank::new_no_wallclock_throttle_for_tests(&genesis_config).0; + let (bank, _bank_forks) = Bank::new_no_wallclock_throttle_for_tests(&genesis_config); let pubkey = solana_sdk::pubkey::new_rand(); let transactions = { @@ -1412,11 +1414,16 @@ mod tests { .. } = create_slow_genesis_config(10_000); let mut bank = Bank::new_for_tests(&genesis_config); +<<<<<<< HEAD bank.ns_per_slot = std::u128::MAX; if !apply_cost_tracker_during_replay_enabled { bank.deactivate_feature(&feature_set::apply_cost_tracker_during_replay::id()); } let bank = bank.wrap_with_bank_forks_for_tests().0; +======= + bank.ns_per_slot = u128::MAX; + let (bank, _bank_forks) = bank.wrap_with_bank_forks_for_tests(); +>>>>>>> d441c0f577 (Fix BankForks::new_rw_arc memory leak (#1893)) let pubkey = solana_sdk::pubkey::new_rand(); let ledger_path = get_tmp_ledger_path_auto_delete!(); @@ -1563,7 +1570,7 @@ mod tests { mint_keypair, .. } = create_slow_genesis_config(10_000); - let bank = Bank::new_no_wallclock_throttle_for_tests(&genesis_config).0; + let (bank, _bank_forks) = Bank::new_no_wallclock_throttle_for_tests(&genesis_config); let pubkey = solana_sdk::pubkey::new_rand(); let pubkey1 = solana_sdk::pubkey::new_rand(); @@ -1640,7 +1647,7 @@ mod tests { mint_keypair, .. } = create_slow_genesis_config(lamports); - let bank = Bank::new_no_wallclock_throttle_for_tests(&genesis_config).0; + let (bank, _bank_forks) = Bank::new_no_wallclock_throttle_for_tests(&genesis_config); // set cost tracker limits to MAX so it will not filter out TXs bank.write_cost_tracker() .unwrap() @@ -1701,7 +1708,7 @@ mod tests { mint_keypair, .. } = create_slow_genesis_config(10_000); - let bank = Bank::new_no_wallclock_throttle_for_tests(&genesis_config).0; + let (bank, _bank_forks) = Bank::new_no_wallclock_throttle_for_tests(&genesis_config); // set cost tracker limits to MAX so it will not filter out TXs bank.write_cost_tracker() .unwrap() @@ -1760,7 +1767,7 @@ mod tests { mint_keypair, .. } = create_slow_genesis_config(10_000); - let bank = Bank::new_no_wallclock_throttle_for_tests(&genesis_config).0; + let (bank, _bank_forks) = Bank::new_no_wallclock_throttle_for_tests(&genesis_config); let pubkey = solana_sdk::pubkey::new_rand(); @@ -1841,7 +1848,7 @@ mod tests { } = create_slow_genesis_config(solana_sdk::native_token::sol_to_lamports(1000.0)); genesis_config.rent.lamports_per_byte_year = 50; genesis_config.rent.exemption_threshold = 2.0; - let bank = Bank::new_no_wallclock_throttle_for_tests(&genesis_config).0; + let (bank, _bank_forks) = Bank::new_no_wallclock_throttle_for_tests(&genesis_config); let pubkey = solana_sdk::pubkey::new_rand(); let pubkey1 = solana_sdk::pubkey::new_rand(); let keypair1 = Keypair::new(); @@ -2109,7 +2116,7 @@ mod tests { fn test_consume_buffered_packets() { let ledger_path = get_tmp_ledger_path_auto_delete!(); { - let (transactions, bank, poh_recorder, _entry_receiver, _, poh_simulator) = + let (transactions, bank, _bank_forks, poh_recorder, _entry_receiver, _, poh_simulator) = setup_conflicting_transactions(ledger_path.path()); let recorder: TransactionRecorder = poh_recorder.read().unwrap().new_recorder(); let num_conflicting_transactions = transactions.len(); @@ -2182,8 +2189,15 @@ mod tests { fn test_consume_buffered_packets_sanitization_error() { let ledger_path = get_tmp_ledger_path_auto_delete!(); { - let (mut transactions, bank, poh_recorder, _entry_receiver, _, poh_simulator) = - setup_conflicting_transactions(ledger_path.path()); + let ( + mut transactions, + bank, + _bank_forks, + poh_recorder, + _entry_receiver, + _, + poh_simulator, + ) = setup_conflicting_transactions(ledger_path.path()); let duplicate_account_key = transactions[0].message.account_keys[0]; transactions[0] .message @@ -2238,7 +2252,7 @@ mod tests { fn test_consume_buffered_packets_retryable() { let ledger_path = get_tmp_ledger_path_auto_delete!(); { - let (transactions, bank, poh_recorder, _entry_receiver, _, poh_simulator) = + let (transactions, bank, _bank_forks, poh_recorder, _entry_receiver, _, poh_simulator) = setup_conflicting_transactions(ledger_path.path()); let recorder = poh_recorder.read().unwrap().new_recorder(); let num_conflicting_transactions = transactions.len(); @@ -2334,8 +2348,15 @@ mod tests { fn test_consume_buffered_packets_batch_priority_guard() { let ledger_path = get_tmp_ledger_path_auto_delete!(); { - let (_, bank, poh_recorder, _entry_receiver, genesis_config_info, poh_simulator) = - setup_conflicting_transactions(ledger_path.path()); + let ( + _, + bank, + _bank_forks, + poh_recorder, + _entry_receiver, + genesis_config_info, + poh_simulator, + ) = setup_conflicting_transactions(ledger_path.path()); let recorder = poh_recorder.read().unwrap().new_recorder(); // Setup transactions: diff --git a/core/src/banking_stage/decision_maker.rs b/core/src/banking_stage/decision_maker.rs index 6ad2c3042b254f..1bd0b224fdf034 100644 --- a/core/src/banking_stage/decision_maker.rs +++ b/core/src/banking_stage/decision_maker.rs @@ -148,7 +148,7 @@ mod tests { #[test] fn test_make_consume_or_forward_decision() { let genesis_config = create_genesis_config(2).genesis_config; - let bank = Bank::new_no_wallclock_throttle_for_tests(&genesis_config).0; + let (bank, _bank_forks) = Bank::new_no_wallclock_throttle_for_tests(&genesis_config); let ledger_path = temp_dir(); let blockstore = Arc::new(Blockstore::open(ledger_path.as_path()).unwrap()); let (exit, poh_recorder, poh_service, _entry_receiver) = diff --git a/core/src/banking_stage/read_write_account_set.rs b/core/src/banking_stage/read_write_account_set.rs index b9d65ff4756857..b38592b84203f8 100644 --- a/core/src/banking_stage/read_write_account_set.rs +++ b/core/src/banking_stage/read_write_account_set.rs @@ -85,7 +85,7 @@ mod tests { use { super::ReadWriteAccountSet, solana_ledger::genesis_utils::GenesisConfigInfo, - solana_runtime::{bank::Bank, genesis_utils::create_genesis_config}, + solana_runtime::{bank::Bank, bank_forks::BankForks, genesis_utils::create_genesis_config}, solana_sdk::{ account::AccountSharedData, address_lookup_table::{ @@ -102,7 +102,10 @@ mod tests { signer::Signer, transaction::{MessageHash, SanitizedTransaction, VersionedTransaction}, }, - std::{borrow::Cow, sync::Arc}, + std::{ + borrow::Cow, + sync::{Arc, RwLock}, + }, }; fn create_test_versioned_message( @@ -171,9 +174,9 @@ mod tests { ) } - fn create_test_bank() -> Arc { + fn create_test_bank() -> (Arc, Arc>) { let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(10_000); - Bank::new_no_wallclock_throttle_for_tests(&genesis_config).0 + Bank::new_no_wallclock_throttle_for_tests(&genesis_config) } // Helper function (could potentially use test_case in future). @@ -182,7 +185,7 @@ mod tests { // conflict_index = 2 means write lock conflict with address table key // conflict_index = 3 means read lock conflict with address table key fn test_check_and_take_locks(conflict_index: usize, add_write: bool, expectation: bool) { - let bank = create_test_bank(); + let (bank, _bank_forks) = create_test_bank(); let (bank, table_address) = create_test_address_lookup_table(bank, 2); let tx = create_test_sanitized_transaction( &Keypair::new(), diff --git a/core/src/banking_stage/unprocessed_transaction_storage.rs b/core/src/banking_stage/unprocessed_transaction_storage.rs index f8d99c77900c51..09c2a8c58f565c 100644 --- a/core/src/banking_stage/unprocessed_transaction_storage.rs +++ b/core/src/banking_stage/unprocessed_transaction_storage.rs @@ -1039,7 +1039,7 @@ mod tests { mint_keypair, .. } = create_genesis_config(10); - let current_bank = Bank::new_with_bank_forks_for_tests(&genesis_config).0; + let (current_bank, _bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config); let simple_transactions: Vec = (0..256) .map(|_id| { diff --git a/core/tests/unified_scheduler.rs b/core/tests/unified_scheduler.rs new file mode 100644 index 00000000000000..141e2fd822813e --- /dev/null +++ b/core/tests/unified_scheduler.rs @@ -0,0 +1,190 @@ +use { + crossbeam_channel::unbounded, + itertools::Itertools, + log::*, + solana_core::{ + consensus::{ + heaviest_subtree_fork_choice::HeaviestSubtreeForkChoice, + progress_map::{ForkProgress, ProgressMap}, + }, + drop_bank_service::DropBankService, + repair::cluster_slot_state_verifier::{ + DuplicateConfirmedSlots, DuplicateSlotsTracker, EpochSlotsFrozenSlots, + }, + replay_stage::ReplayStage, + unfrozen_gossip_verified_vote_hashes::UnfrozenGossipVerifiedVoteHashes, + }, + solana_ledger::genesis_utils::create_genesis_config, + solana_program_runtime::timings::ExecuteTimings, + solana_runtime::{ + accounts_background_service::AbsRequestSender, bank::Bank, bank_forks::BankForks, + genesis_utils::GenesisConfigInfo, prioritization_fee_cache::PrioritizationFeeCache, + }, + solana_sdk::{ + hash::Hash, + pubkey::Pubkey, + system_transaction, + transaction::{Result, SanitizedTransaction}, + }, + solana_unified_scheduler_pool::{ + DefaultTaskHandler, HandlerContext, PooledScheduler, SchedulerPool, TaskHandler, + }, + std::{ + collections::HashMap, + sync::{Arc, Mutex}, + }, +}; + +#[test] +fn test_scheduler_waited_by_drop_bank_service() { + solana_logger::setup(); + + static LOCK_TO_STALL: Mutex<()> = Mutex::new(()); + + #[derive(Debug)] + struct StallingHandler; + impl TaskHandler for StallingHandler { + fn handle( + result: &mut Result<()>, + timings: &mut ExecuteTimings, + bank: &Arc, + transaction: &SanitizedTransaction, + index: usize, + handler_context: &HandlerContext, + ) { + info!("Stalling at StallingHandler::handle()..."); + *LOCK_TO_STALL.lock().unwrap(); + // Wait a bit for the replay stage to prune banks + std::thread::sleep(std::time::Duration::from_secs(3)); + info!("Now entering into DefaultTaskHandler::handle()..."); + + DefaultTaskHandler::handle(result, timings, bank, transaction, index, handler_context); + } + } + + let GenesisConfigInfo { + genesis_config, + mint_keypair, + .. + } = create_genesis_config(10_000); + + // Setup bankforks with unified scheduler enabled + let genesis_bank = Bank::new_for_tests(&genesis_config); + let bank_forks = BankForks::new_rw_arc(genesis_bank); + let ignored_prioritization_fee_cache = Arc::new(PrioritizationFeeCache::new(0u64)); + let pool_raw = SchedulerPool::, _>::new( + None, + None, + None, + None, + ignored_prioritization_fee_cache, + ); + let pool = pool_raw.clone(); + bank_forks.write().unwrap().install_scheduler_pool(pool); + let genesis = 0; + let genesis_bank = &bank_forks.read().unwrap().get(genesis).unwrap(); + genesis_bank.set_fork_graph_in_program_cache(Arc::downgrade(&bank_forks)); + + // Create bank, which is pruned later + let pruned = 2; + let pruned_bank = Bank::new_from_parent(genesis_bank.clone(), &Pubkey::default(), pruned); + let pruned_bank = bank_forks.write().unwrap().insert(pruned_bank); + + // Create new root bank + let root = 3; + let root_bank = Bank::new_from_parent(genesis_bank.clone(), &Pubkey::default(), root); + root_bank.freeze(); + let root_hash = root_bank.hash(); + bank_forks.write().unwrap().insert(root_bank); + + let tx = SanitizedTransaction::from_transaction_for_tests(system_transaction::transfer( + &mint_keypair, + &solana_sdk::pubkey::new_rand(), + 2, + genesis_config.hash(), + )); + + // Delay transaction execution to ensure transaction execution happens after termintion has + // been started + let lock_to_stall = LOCK_TO_STALL.lock().unwrap(); + pruned_bank + .schedule_transaction_executions([(&tx, &0)].into_iter()) + .unwrap(); + drop(pruned_bank); + assert_eq!(pool_raw.pooled_scheduler_count(), 0); + drop(lock_to_stall); + + // Create 2 channels to check actual pruned banks + let (drop_bank_sender1, drop_bank_receiver1) = unbounded(); + let (drop_bank_sender2, drop_bank_receiver2) = unbounded(); + let drop_bank_service = DropBankService::new(drop_bank_receiver2); + + info!("calling handle_new_root()..."); + // Mostly copied from: test_handle_new_root() + { + let mut heaviest_subtree_fork_choice = HeaviestSubtreeForkChoice::new((root, root_hash)); + + let mut progress = ProgressMap::default(); + for i in genesis..=root { + progress.insert(i, ForkProgress::new(Hash::default(), None, None, 0, 0)); + } + + let mut duplicate_slots_tracker: DuplicateSlotsTracker = + vec![root - 1, root, root + 1].into_iter().collect(); + let mut duplicate_confirmed_slots: DuplicateConfirmedSlots = vec![root - 1, root, root + 1] + .into_iter() + .map(|s| (s, Hash::default())) + .collect(); + let mut unfrozen_gossip_verified_vote_hashes: UnfrozenGossipVerifiedVoteHashes = + UnfrozenGossipVerifiedVoteHashes { + votes_per_slot: vec![root - 1, root, root + 1] + .into_iter() + .map(|s| (s, HashMap::new())) + .collect(), + }; + let mut epoch_slots_frozen_slots: EpochSlotsFrozenSlots = vec![root - 1, root, root + 1] + .into_iter() + .map(|slot| (slot, Hash::default())) + .collect(); + ReplayStage::handle_new_root( + root, + &bank_forks, + &mut progress, + &AbsRequestSender::default(), + None, + &mut heaviest_subtree_fork_choice, + &mut duplicate_slots_tracker, + &mut duplicate_confirmed_slots, + &mut unfrozen_gossip_verified_vote_hashes, + &mut true, + &mut Vec::new(), + &mut epoch_slots_frozen_slots, + &drop_bank_sender1, + ) + .unwrap(); + } + + // Receive pruned banks from the above handle_new_root + let pruned_banks = drop_bank_receiver1.recv().unwrap(); + assert_eq!( + pruned_banks + .iter() + .map(|b| b.slot()) + .sorted() + .collect::>(), + vec![genesis, pruned] + ); + info!("sending pruned banks to DropBankService..."); + drop_bank_sender2.send(pruned_banks).unwrap(); + + info!("joining the drop bank service..."); + drop(( + (drop_bank_sender1, drop_bank_receiver1), + (drop_bank_sender2,), + )); + drop_bank_service.join().unwrap(); + info!("finally joined the drop bank service!"); + + // the scheduler used by the pruned_bank have been returned now. + assert_eq!(pool_raw.pooled_scheduler_count(), 1); +} diff --git a/ledger/benches/blockstore_processor.rs b/ledger/benches/blockstore_processor.rs index f1c335596140b0..617bd167474810 100644 --- a/ledger/benches/blockstore_processor.rs +++ b/ledger/benches/blockstore_processor.rs @@ -12,7 +12,7 @@ use { }, solana_program_runtime::timings::ExecuteTimings, solana_runtime::{ - bank::Bank, prioritization_fee_cache::PrioritizationFeeCache, + bank::Bank, bank_forks::BankForks, prioritization_fee_cache::PrioritizationFeeCache, transaction_batch::TransactionBatch, }, solana_sdk::{ @@ -20,7 +20,10 @@ use { signer::Signer, stake_history::Epoch, system_program, system_transaction, transaction::SanitizedTransaction, }, - std::{borrow::Cow, sync::Arc}, + std::{ + borrow::Cow, + sync::{Arc, RwLock}, + }, test::Bencher, }; @@ -69,6 +72,7 @@ fn create_transactions(bank: &Bank, num: usize) -> Vec { struct BenchFrame { bank: Arc, + _bank_forks: Arc>, prioritization_fee_cache: PrioritizationFeeCache, } @@ -94,11 +98,17 @@ fn setup(apply_cost_tracker_during_replay: bool) -> BenchFrame { // set cost tracker limits to MAX so it will not filter out TXs bank.write_cost_tracker() .unwrap() +<<<<<<< HEAD .set_limits(std::u64::MAX, std::u64::MAX, std::u64::MAX); let bank = bank.wrap_with_bank_forks_for_tests().0; +======= + .set_limits(u64::MAX, u64::MAX, u64::MAX); + let (bank, bank_forks) = bank.wrap_with_bank_forks_for_tests(); +>>>>>>> d441c0f577 (Fix BankForks::new_rw_arc memory leak (#1893)) let prioritization_fee_cache = PrioritizationFeeCache::default(); BenchFrame { bank, + _bank_forks: bank_forks, prioritization_fee_cache, } } @@ -120,6 +130,7 @@ fn bench_execute_batch( let BenchFrame { bank, + _bank_forks, prioritization_fee_cache, } = setup(apply_cost_tracker_during_replay); let transactions = create_transactions(&bank, 2_usize.pow(20)); diff --git a/ledger/src/blockstore_processor.rs b/ledger/src/blockstore_processor.rs index 4e093814b9bb3a..1a2fca42c609a9 100644 --- a/ledger/src/blockstore_processor.rs +++ b/ledger/src/blockstore_processor.rs @@ -2696,7 +2696,7 @@ pub mod tests { mint_keypair, .. } = create_genesis_config(2); - let bank = Bank::new_with_bank_forks_for_tests(&genesis_config).0; + let (bank, _bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config); let keypair = Keypair::new(); let slot_entries = create_ticks(genesis_config.ticks_per_slot, 1, genesis_config.hash()); let tx = system_transaction::transfer( @@ -2861,7 +2861,7 @@ pub mod tests { mint_keypair, .. } = create_genesis_config(1000); - let bank = Bank::new_with_bank_forks_for_tests(&genesis_config).0; + let (bank, _bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config); let keypair1 = Keypair::new(); let keypair2 = Keypair::new(); @@ -2898,7 +2898,7 @@ pub mod tests { mint_keypair, .. } = create_genesis_config(1000); - let bank = Bank::new_with_bank_forks_for_tests(&genesis_config).0; + let (bank, _bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config); let keypair1 = Keypair::new(); let keypair2 = Keypair::new(); let keypair3 = Keypair::new(); @@ -2958,7 +2958,7 @@ pub mod tests { mint_keypair, .. } = create_genesis_config(1000); - let bank = Bank::new_with_bank_forks_for_tests(&genesis_config).0; + let (bank, _bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config); let keypair1 = Keypair::new(); let keypair2 = Keypair::new(); let keypair3 = Keypair::new(); @@ -3108,12 +3108,11 @@ pub mod tests { let mock_program_id = solana_sdk::pubkey::new_rand(); - let bank = Bank::new_with_mockup_builtin_for_tests( + let (bank, _bank_forks) = Bank::new_with_mockup_builtin_for_tests( &genesis_config, mock_program_id, MockBuiltinOk::vm, - ) - .0; + ); let tx = Transaction::new_signed_with_payer( &[Instruction::new_with_bincode( @@ -3152,12 +3151,11 @@ pub mod tests { let mut bankhash_err = None; (0..get_instruction_errors().len()).for_each(|err| { - let bank = Bank::new_with_mockup_builtin_for_tests( + let (bank, _bank_forks) = Bank::new_with_mockup_builtin_for_tests( &genesis_config, mock_program_id, MockBuiltinErr::vm, - ) - .0; + ); let tx = Transaction::new_signed_with_payer( &[Instruction::new_with_bincode( @@ -3193,7 +3191,7 @@ pub mod tests { mint_keypair, .. } = create_genesis_config(1000); - let bank = Bank::new_with_bank_forks_for_tests(&genesis_config).0; + let (bank, _bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config); let keypair1 = Keypair::new(); let keypair2 = Keypair::new(); let keypair3 = Keypair::new(); @@ -3287,7 +3285,7 @@ pub mod tests { mint_keypair, .. } = create_genesis_config(1000); - let bank = Bank::new_with_bank_forks_for_tests(&genesis_config).0; + let (bank, _bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config); let keypair1 = Keypair::new(); let keypair2 = Keypair::new(); let keypair3 = Keypair::new(); @@ -3333,7 +3331,7 @@ pub mod tests { mint_keypair, .. } = create_genesis_config(1_000_000_000); - let bank = Bank::new_with_bank_forks_for_tests(&genesis_config).0; + let (bank, _bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config); const NUM_TRANSFERS_PER_ENTRY: usize = 8; const NUM_TRANSFERS: usize = NUM_TRANSFERS_PER_ENTRY * 32; @@ -3400,7 +3398,7 @@ pub mod tests { .. } = create_genesis_config((num_accounts + 1) as u64 * initial_lamports); - let bank = Bank::new_with_bank_forks_for_tests(&genesis_config).0; + let (bank, _bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config); let mut keypairs: Vec = vec![]; @@ -3467,7 +3465,7 @@ pub mod tests { mint_keypair, .. } = create_genesis_config(1000); - let bank = Bank::new_with_bank_forks_for_tests(&genesis_config).0; + let (bank, _bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config); let keypair1 = Keypair::new(); let keypair2 = Keypair::new(); let keypair3 = Keypair::new(); @@ -3529,7 +3527,7 @@ pub mod tests { mint_keypair, .. } = create_genesis_config(11_000); - let bank = Bank::new_with_bank_forks_for_tests(&genesis_config).0; + let (bank, _bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config); let pubkey = solana_sdk::pubkey::new_rand(); bank.transfer(1_000, &mint_keypair, &pubkey).unwrap(); assert_eq!(bank.transaction_count(), 1); @@ -3570,7 +3568,7 @@ pub mod tests { mint_keypair, .. } = create_genesis_config(11_000); - let bank = Bank::new_with_bank_forks_for_tests(&genesis_config).0; + let (bank, _bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config); let keypair1 = Keypair::new(); let keypair2 = Keypair::new(); let success_tx = system_transaction::transfer( @@ -3856,7 +3854,7 @@ pub mod tests { mint_keypair, .. } = create_genesis_config(100); - let bank0 = Bank::new_with_bank_forks_for_tests(&genesis_config).0; + let (bank0, _bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config); let genesis_hash = genesis_config.hash(); let keypair = Keypair::new(); @@ -3921,7 +3919,7 @@ pub mod tests { mint_keypair, .. } = create_genesis_config(1_000_000_000); - let bank = Bank::new_with_bank_forks_for_tests(&genesis_config).0; + let (bank, _bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config); let present_account_key = Keypair::new(); let present_account = AccountSharedData::new(1, 10, &Pubkey::default()); @@ -4390,9 +4388,15 @@ pub mod tests { .. } = create_genesis_config(100 * LAMPORTS_PER_SOL); let genesis_hash = genesis_config.hash(); +<<<<<<< HEAD let bank = BankWithScheduler::new_without_scheduler( Bank::new_with_bank_forks_for_tests(&genesis_config).0, ); +======= + let (bank, _bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config); + let bank = BankWithScheduler::new_without_scheduler(bank); + let replay_tx_thread_pool = create_thread_pool(1); +>>>>>>> d441c0f577 (Fix BankForks::new_rw_arc memory leak (#1893)) let mut timing = ConfirmationTiming::default(); let mut progress = ConfirmationProgress::new(genesis_hash); let amount = genesis_config.rent.minimum_balance(0); diff --git a/poh/src/poh_service.rs b/poh/src/poh_service.rs index 8cd0d40266b37d..04dc8d4c5a1fed 100644 --- a/poh/src/poh_service.rs +++ b/poh/src/poh_service.rs @@ -400,7 +400,7 @@ mod tests { fn test_poh_service() { solana_logger::setup(); let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(2); - let bank = Bank::new_no_wallclock_throttle_for_tests(&genesis_config).0; + let (bank, _bank_forks) = Bank::new_no_wallclock_throttle_for_tests(&genesis_config); let prev_hash = bank.last_blockhash(); let ledger_path = get_tmp_ledger_path_auto_delete!(); let blockstore = Blockstore::open(ledger_path.path()) diff --git a/program-runtime/src/loaded_programs.rs b/program-runtime/src/loaded_programs.rs index b9c7d97cdbc251..91e4b0915851e6 100644 --- a/program-runtime/src/loaded_programs.rs +++ b/program-runtime/src/loaded_programs.rs @@ -27,6 +27,15 @@ use { atomic::{AtomicU64, Ordering}, Arc, Condvar, Mutex, RwLock, }, +<<<<<<< HEAD +======= + thread, + }, + std::{ + collections::{hash_map::Entry, HashMap}, + fmt::{Debug, Formatter}, + sync::Weak, +>>>>>>> d441c0f577 (Fix BankForks::new_rw_arc memory leak (#1893)) }, }; @@ -547,9 +556,18 @@ pub struct LoadedPrograms { /// and it ends with the first rerooting after the epoch boundary. pub upcoming_environments: Option, /// List of loaded programs which should be recompiled before the next epoch (but don't have to). +<<<<<<< HEAD pub programs_to_recompile: Vec<(Pubkey, Arc)>, pub stats: Stats, pub fork_graph: Option>>, +======= + pub programs_to_recompile: Vec<(Pubkey, Arc)>, + /// Statistics counters + pub stats: ProgramCacheStats, + /// Reference to the block store + pub fork_graph: Option>>, + /// Coordinates TX batches waiting for others to complete their task during cooperative loading +>>>>>>> d441c0f577 (Fix BankForks::new_rw_arc memory leak (#1893)) pub loading_task_waiter: Arc, } @@ -691,7 +709,7 @@ impl LoadedPrograms { } } - pub fn set_fork_graph(&mut self, fork_graph: Arc>) { + pub fn set_fork_graph(&mut self, fork_graph: Weak>) { self.fork_graph = Some(fork_graph); } @@ -797,6 +815,7 @@ impl LoadedPrograms { error!("Program cache doesn't have fork graph."); return; }; + let fork_graph = fork_graph.upgrade().unwrap(); let Ok(fork_graph) = fork_graph.read() else { error!("Failed to lock fork graph for reading."); return; @@ -929,7 +948,8 @@ impl LoadedPrograms { is_first_round: bool, ) -> Option<(Pubkey, u64)> { debug_assert!(self.fork_graph.is_some()); - let locked_fork_graph = self.fork_graph.as_ref().unwrap().read().unwrap(); + let fork_graph = self.fork_graph.as_ref().unwrap().upgrade().unwrap(); + let locked_fork_graph = fork_graph.read().unwrap(); let mut cooperative_loading_task = None; search_for.retain(|(key, (match_criteria, usage_count))| { if let Some(second_level) = self.entries.get_mut(key) { @@ -1019,6 +1039,7 @@ impl LoadedPrograms { key: Pubkey, loaded_program: Arc, ) -> bool { +<<<<<<< HEAD let second_level = self.entries.entry(key).or_default(); debug_assert_eq!( second_level.cooperative_loading_lock, @@ -1038,6 +1059,34 @@ impl LoadedPrograms { ) { self.stats.lost_insertions.fetch_add(1, Ordering::Relaxed); +======= + match &mut self.index { + IndexImplementation::V1 { + loading_entries, .. + } => { + let loading_thread = loading_entries.get_mut().unwrap().remove(&key); + debug_assert_eq!(loading_thread, Some((slot, thread::current().id()))); + // Check that it will be visible to our own fork once inserted + if loaded_program.deployment_slot > self.latest_root_slot + && !matches!( + self.fork_graph + .as_ref() + .unwrap() + .upgrade() + .unwrap() + .read() + .unwrap() + .relationship(loaded_program.deployment_slot, slot), + BlockRelation::Equal | BlockRelation::Ancestor + ) + { + self.stats.lost_insertions.fetch_add(1, Ordering::Relaxed); + } + let was_occupied = self.assign_program(key, loaded_program); + self.loading_task_waiter.notify(); + was_occupied + } +>>>>>>> d441c0f577 (Fix BankForks::new_rw_arc memory leak (#1893)) } let was_occupied = self.assign_program(key, loaded_program); debug_assert!(!was_occupied, "Unexpected replacement of an entry"); @@ -1899,7 +1948,7 @@ mod tests { relation: BlockRelation::Unrelated, })); - cache.set_fork_graph(fork_graph); + cache.set_fork_graph(Arc::downgrade(&fork_graph)); cache.prune(0, 0); assert!(cache.entries.is_empty()); @@ -1912,7 +1961,7 @@ mod tests { relation: BlockRelation::Ancestor, })); - cache.set_fork_graph(fork_graph); + cache.set_fork_graph(Arc::downgrade(&fork_graph)); cache.prune(0, 0); assert!(cache.entries.is_empty()); @@ -1925,7 +1974,7 @@ mod tests { relation: BlockRelation::Descendant, })); - cache.set_fork_graph(fork_graph); + cache.set_fork_graph(Arc::downgrade(&fork_graph)); cache.prune(0, 0); assert!(cache.entries.is_empty()); @@ -1937,7 +1986,7 @@ mod tests { let fork_graph = Arc::new(RwLock::new(TestForkGraph { relation: BlockRelation::Unknown, })); - cache.set_fork_graph(fork_graph); + cache.set_fork_graph(Arc::downgrade(&fork_graph)); cache.prune(0, 0); assert!(cache.entries.is_empty()); @@ -1954,7 +2003,7 @@ mod tests { relation: BlockRelation::Ancestor, })); - cache.set_fork_graph(fork_graph); + cache.set_fork_graph(Arc::downgrade(&fork_graph)); let program1 = Pubkey::new_unique(); cache.assign_program(program1, new_test_loaded_program(10, 10)); @@ -2059,6 +2108,36 @@ mod tests { } } +<<<<<<< HEAD +======= + fn get_entries_to_load( + cache: &ProgramCache, + loading_slot: Slot, + keys: &[Pubkey], + ) -> Vec<(Pubkey, (ProgramCacheMatchCriteria, u64))> { + let fork_graph = cache.fork_graph.as_ref().unwrap().upgrade().unwrap(); + let locked_fork_graph = fork_graph.read().unwrap(); + let entries = cache.get_flattened_entries_for_tests(); + keys.iter() + .filter_map(|key| { + entries + .iter() + .rev() + .find(|(program_id, entry)| { + program_id == key + && matches!( + locked_fork_graph.relationship(entry.deployment_slot, loading_slot), + BlockRelation::Equal | BlockRelation::Ancestor, + ) + }) + .map(|(program_id, _entry)| { + (*program_id, (ProgramCacheMatchCriteria::NoCriteria, 1)) + }) + }) + .collect() + } + +>>>>>>> d441c0f577 (Fix BankForks::new_rw_arc memory leak (#1893)) fn match_slot( extracted: &LoadedProgramsForTxBatch, program: &Pubkey, @@ -2106,7 +2185,7 @@ mod tests { fork_graph.insert_fork(&[0, 5, 11, 25, 27]); let fork_graph = Arc::new(RwLock::new(fork_graph)); - cache.set_fork_graph(fork_graph); + cache.set_fork_graph(Arc::downgrade(&fork_graph)); let program1 = Pubkey::new_unique(); cache.assign_program(program1, new_test_loaded_program(0, 1)); @@ -2404,7 +2483,7 @@ mod tests { fork_graph.insert_fork(&[0, 5, 11, 25, 27]); let fork_graph = Arc::new(RwLock::new(fork_graph)); - cache.set_fork_graph(fork_graph); + cache.set_fork_graph(Arc::downgrade(&fork_graph)); let program1 = Pubkey::new_unique(); cache.assign_program(program1, new_test_loaded_program(0, 1)); @@ -2477,7 +2556,7 @@ mod tests { fork_graph.insert_fork(&[0, 5, 11, 25, 27]); let fork_graph = Arc::new(RwLock::new(fork_graph)); - cache.set_fork_graph(fork_graph); + cache.set_fork_graph(Arc::downgrade(&fork_graph)); let program1 = Pubkey::new_unique(); cache.assign_program(program1, new_test_loaded_program(0, 1)); @@ -2570,7 +2649,7 @@ mod tests { fork_graph.insert_fork(&[0, 5, 11, 12, 15, 16, 18, 19, 21, 23]); fork_graph.insert_fork(&[0, 5, 11, 25, 27]); let fork_graph = Arc::new(RwLock::new(fork_graph)); - cache.set_fork_graph(fork_graph); + cache.set_fork_graph(Arc::downgrade(&fork_graph)); let program1 = Pubkey::new_unique(); assert!(!cache.assign_program(program1, new_test_loaded_program(10, 11))); @@ -2675,7 +2754,7 @@ mod tests { fork_graph.insert_fork(&[0, 10, 20]); fork_graph.insert_fork(&[0, 5]); let fork_graph = Arc::new(RwLock::new(fork_graph)); - cache.set_fork_graph(fork_graph); + cache.set_fork_graph(Arc::downgrade(&fork_graph)); let program1 = Pubkey::new_unique(); cache.assign_program(program1, new_test_loaded_program(0, 1)); @@ -2715,7 +2794,7 @@ mod tests { fork_graph.insert_fork(&[0, 10, 20]); fork_graph.insert_fork(&[0, 5, 6]); let fork_graph = Arc::new(RwLock::new(fork_graph)); - cache.set_fork_graph(fork_graph); + cache.set_fork_graph(Arc::downgrade(&fork_graph)); let program1 = Pubkey::new_unique(); cache.assign_program(program1, new_test_loaded_program(0, 1)); diff --git a/programs/sbf/tests/programs.rs b/programs/sbf/tests/programs.rs index 13f05fca3ef9f8..32b1c21ae44bd8 100644 --- a/programs/sbf/tests/programs.rs +++ b/programs/sbf/tests/programs.rs @@ -26,7 +26,17 @@ use { }, solana_rbpf::vm::ContextObject, solana_runtime::{ +<<<<<<< HEAD bank::TransactionBalancesSet, +======= + bank::{Bank, TransactionBalancesSet}, + bank_client::BankClient, + bank_forks::BankForks, + genesis_utils::{ + bootstrap_validator_stake_lamports, create_genesis_config, + create_genesis_config_with_leader_ex, GenesisConfigInfo, + }, +>>>>>>> d441c0f577 (Fix BankForks::new_rw_arc memory leak (#1893)) loader_utils::{ create_program, load_program_from_file, load_upgradeable_buffer, load_upgradeable_program, load_upgradeable_program_and_advance_slot, @@ -56,6 +66,7 @@ use { map_inner_instructions, ConfirmedTransactionWithStatusMeta, TransactionStatusMeta, TransactionWithStatusMeta, VersionedTransactionWithStatusMeta, }, +<<<<<<< HEAD std::collections::HashMap, }; use { @@ -67,6 +78,15 @@ use { bootstrap_validator_stake_lamports, create_genesis_config, create_genesis_config_with_leader_ex, GenesisConfigInfo, }, +======= + std::{ + assert_eq, + cell::RefCell, + collections::HashMap, + str::FromStr, + sync::{Arc, RwLock}, + time::Duration, +>>>>>>> d441c0f577 (Fix BankForks::new_rw_arc memory leak (#1893)) }, solana_sdk::{ account::AccountSharedData, @@ -2551,7 +2571,7 @@ fn test_program_upgradeable_locks() { payer_keypair: &Keypair, buffer_keypair: &Keypair, program_keypair: &Keypair, - ) -> (Arc, Transaction, Transaction) { + ) -> (Arc, Arc>, Transaction, Transaction) { solana_logger::setup(); let GenesisConfigInfo { @@ -2621,7 +2641,7 @@ fn test_program_upgradeable_locks() { bank.last_blockhash(), ); - (bank, invoke_tx, upgrade_tx) + (bank, bank_forks, invoke_tx, upgrade_tx) } let payer_keypair = keypair_from_seed(&[56u8; 32]).unwrap(); @@ -2629,13 +2649,13 @@ fn test_program_upgradeable_locks() { let program_keypair = keypair_from_seed(&[77u8; 32]).unwrap(); let results1 = { - let (bank, invoke_tx, upgrade_tx) = + let (bank, _bank_forks, invoke_tx, upgrade_tx) = setup_program_upgradeable_locks(&payer_keypair, &buffer_keypair, &program_keypair); execute_transactions(&bank, vec![upgrade_tx, invoke_tx]) }; let results2 = { - let (bank, invoke_tx, upgrade_tx) = + let (bank, _bank_forks, invoke_tx, upgrade_tx) = setup_program_upgradeable_locks(&payer_keypair, &buffer_keypair, &program_keypair); execute_transactions(&bank, vec![invoke_tx, upgrade_tx]) }; diff --git a/rpc/src/transaction_status_service.rs b/rpc/src/transaction_status_service.rs index eca53c66658766..217672c8342bad 100644 --- a/rpc/src/transaction_status_service.rs +++ b/rpc/src/transaction_status_service.rs @@ -322,7 +322,7 @@ pub(crate) mod tests { #[test] fn test_notify_transaction() { let genesis_config = create_genesis_config(2).genesis_config; - let bank = Bank::new_no_wallclock_throttle_for_tests(&genesis_config).0; + let (bank, _bank_forks) = Bank::new_no_wallclock_throttle_for_tests(&genesis_config); let (transaction_status_sender, transaction_status_receiver) = unbounded(); let ledger_path = get_tmp_ledger_path_auto_delete!(); diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index 8b217d3b8cf679..0a3a8cbdf02017 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -198,7 +198,7 @@ use { AtomicBool, AtomicI64, AtomicU64, AtomicUsize, Ordering::{self, AcqRel, Acquire, Relaxed}, }, - Arc, LockResult, Mutex, RwLock, RwLockReadGuard, RwLockWriteGuard, + Arc, LockResult, Mutex, RwLock, RwLockReadGuard, RwLockWriteGuard, Weak, }, thread::Builder, time::{Duration, Instant}, @@ -1473,6 +1473,33 @@ impl Bank { new } +<<<<<<< HEAD +======= + pub fn set_fork_graph_in_program_cache(&self, fork_graph: Weak>) { + self.transaction_processor + .program_cache + .write() + .unwrap() + .set_fork_graph(fork_graph); + } + + pub fn prune_program_cache(&self, new_root_slot: Slot, new_root_epoch: Epoch) { + self.transaction_processor + .program_cache + .write() + .unwrap() + .prune(new_root_slot, new_root_epoch); + } + + pub fn prune_program_cache_by_deployment_slot(&self, deployment_slot: Slot) { + self.transaction_processor + .program_cache + .write() + .unwrap() + .prune_by_deployment_slot(deployment_slot); + } + +>>>>>>> d441c0f577 (Fix BankForks::new_rw_arc memory leak (#1893)) /// Epoch in which the new cooldown warmup rate for stake was activated pub fn new_warmup_cooldown_rate_epoch(&self) -> Option { self.feature_set diff --git a/runtime/src/bank/tests.rs b/runtime/src/bank/tests.rs index 18c9727a991d73..2c848f955fafc5 100644 --- a/runtime/src/bank/tests.rs +++ b/runtime/src/bank/tests.rs @@ -320,7 +320,7 @@ fn create_simple_test_arc_bank(lamports: u64) -> (Arc, Arc Vec { #[test] fn test_rent_eager_across_epoch_without_gap() { - let mut bank = create_simple_test_arc_bank(1).0; + let (mut bank, _bank_forks) = create_simple_test_arc_bank(1); assert_eq!(bank.rent_collection_partitions(), vec![(0, 0, 32)]); bank = Arc::new(new_from_parent(bank)); @@ -2243,7 +2243,7 @@ fn test_purge_empty_accounts() { fn test_two_payments_to_one_party() { let (genesis_config, mint_keypair) = create_genesis_config(sol_to_lamports(1.)); let pubkey = solana_sdk::pubkey::new_rand(); - let bank = Bank::new_with_bank_forks_for_tests(&genesis_config).0; + let (bank, _bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config); let amount = genesis_config.rent.minimum_balance(0); assert_eq!(bank.last_blockhash(), genesis_config.hash()); @@ -2261,7 +2261,7 @@ fn test_one_source_two_tx_one_batch() { let (genesis_config, mint_keypair) = create_genesis_config_no_tx_fee(sol_to_lamports(1.)); let key1 = solana_sdk::pubkey::new_rand(); let key2 = solana_sdk::pubkey::new_rand(); - let bank = Bank::new_with_bank_forks_for_tests(&genesis_config).0; + let (bank, _bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config); let amount = genesis_config.rent.minimum_balance(0); assert_eq!(bank.last_blockhash(), genesis_config.hash()); @@ -2291,7 +2291,7 @@ fn test_one_tx_two_out_atomic_fail() { let (genesis_config, mint_keypair) = create_genesis_config_no_tx_fee_no_rent(amount); let key1 = solana_sdk::pubkey::new_rand(); let key2 = solana_sdk::pubkey::new_rand(); - let bank = Bank::new_with_bank_forks_for_tests(&genesis_config).0; + let (bank, _bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config); let instructions = system_instruction::transfer_many( &mint_keypair.pubkey(), &[(key1, amount), (key2, amount)], @@ -2312,7 +2312,7 @@ fn test_one_tx_two_out_atomic_pass() { let (genesis_config, mint_keypair) = create_genesis_config_no_tx_fee(sol_to_lamports(1.)); let key1 = solana_sdk::pubkey::new_rand(); let key2 = solana_sdk::pubkey::new_rand(); - let bank = Bank::new_with_bank_forks_for_tests(&genesis_config).0; + let (bank, _bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config); let amount = genesis_config.rent.minimum_balance(0); let instructions = system_instruction::transfer_many( &mint_keypair.pubkey(), @@ -2334,7 +2334,7 @@ fn test_one_tx_two_out_atomic_pass() { fn test_detect_failed_duplicate_transactions() { let (mut genesis_config, mint_keypair) = create_genesis_config(10_000); genesis_config.fee_rate_governor = FeeRateGovernor::new(5_000, 0); - let bank = Bank::new_with_bank_forks_for_tests(&genesis_config).0; + let (bank, _bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config); let dest = Keypair::new(); @@ -2363,7 +2363,7 @@ fn test_detect_failed_duplicate_transactions() { fn test_account_not_found() { solana_logger::setup(); let (genesis_config, mint_keypair) = create_genesis_config(0); - let bank = Bank::new_with_bank_forks_for_tests(&genesis_config).0; + let (bank, _bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config); let keypair = Keypair::new(); assert_eq!( bank.transfer( @@ -2381,7 +2381,7 @@ fn test_account_not_found() { fn test_insufficient_funds() { let mint_amount = sol_to_lamports(1.); let (genesis_config, mint_keypair) = create_genesis_config_no_tx_fee(mint_amount); - let bank = Bank::new_with_bank_forks_for_tests(&genesis_config).0; + let (bank, _bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config); let pubkey = solana_sdk::pubkey::new_rand(); let amount = genesis_config.rent.minimum_balance(0); bank.transfer(amount, &mint_keypair, &pubkey).unwrap(); @@ -2452,7 +2452,7 @@ fn test_executed_transaction_count_post_bank_transaction_count_fix() { fn test_transfer_to_newb() { solana_logger::setup(); let (genesis_config, mint_keypair) = create_genesis_config(sol_to_lamports(1.)); - let bank = Bank::new_with_bank_forks_for_tests(&genesis_config).0; + let (bank, _bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config); let amount = genesis_config.rent.minimum_balance(0); let pubkey = solana_sdk::pubkey::new_rand(); bank.transfer(amount, &mint_keypair, &pubkey).unwrap(); @@ -2998,7 +2998,7 @@ fn test_filter_program_errors_and_collect_compute_unit_fee() { fn test_debits_before_credits() { let (genesis_config, mint_keypair) = create_genesis_config_no_tx_fee_no_rent(sol_to_lamports(2.)); - let bank = Bank::new_with_bank_forks_for_tests(&genesis_config).0; + let (bank, _bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config); let keypair = Keypair::new(); let tx0 = system_transaction::transfer( &mint_keypair, @@ -3028,7 +3028,7 @@ fn test_readonly_accounts() { mint_keypair, .. } = create_genesis_config_with_leader(500, &solana_sdk::pubkey::new_rand(), 0); - let bank = Bank::new_with_bank_forks_for_tests(&genesis_config).0; + let (bank, _bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config); let vote_pubkey0 = solana_sdk::pubkey::new_rand(); let vote_pubkey1 = solana_sdk::pubkey::new_rand(); @@ -3100,7 +3100,7 @@ fn test_readonly_accounts() { #[test] fn test_interleaving_locks() { let (genesis_config, mint_keypair) = create_genesis_config(sol_to_lamports(1.)); - let bank = Bank::new_with_bank_forks_for_tests(&genesis_config).0; + let (bank, _bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config); let alice = Keypair::new(); let bob = Keypair::new(); let amount = genesis_config.rent.minimum_balance(0); @@ -3237,7 +3237,7 @@ fn test_bank_invalid_account_index() { fn test_bank_pay_to_self() { let (genesis_config, mint_keypair) = create_genesis_config_no_tx_fee(sol_to_lamports(1.)); let key1 = Keypair::new(); - let bank = Bank::new_with_bank_forks_for_tests(&genesis_config).0; + let (bank, _bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config); let amount = genesis_config.rent.minimum_balance(0); bank.transfer(amount, &mint_keypair, &key1.pubkey()) @@ -3277,7 +3277,7 @@ fn test_bank_parents() { #[test] fn test_tx_already_processed() { let (genesis_config, mint_keypair) = create_genesis_config(sol_to_lamports(1.)); - let bank = Bank::new_with_bank_forks_for_tests(&genesis_config).0; + let (bank, _bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config); let key1 = Keypair::new(); let mut tx = system_transaction::transfer( @@ -3348,7 +3348,7 @@ fn test_bank_parent_account_spend() { fn test_bank_hash_internal_state() { let (genesis_config, mint_keypair) = create_genesis_config_no_tx_fee_no_rent(sol_to_lamports(1.)); - let (bank0, _) = Bank::new_with_bank_forks_for_tests(&genesis_config); + let (bank0, _bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config); let (bank1, bank_forks_1) = Bank::new_with_bank_forks_for_tests(&genesis_config); let amount = genesis_config.rent.minimum_balance(0); let initial_state = bank0.hash_internal_state(); @@ -3454,7 +3454,7 @@ fn test_verify_snapshot_bank() { solana_logger::setup(); let pubkey = solana_sdk::pubkey::new_rand(); let (genesis_config, mint_keypair) = create_genesis_config(sol_to_lamports(1.)); - let bank = Bank::new_with_bank_forks_for_tests(&genesis_config).0; + let (bank, _bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config); bank.transfer( genesis_config.rent.minimum_balance(0), &mint_keypair, @@ -3514,8 +3514,8 @@ fn test_hash_internal_state_genesis() { fn test_hash_internal_state_order() { let (genesis_config, mint_keypair) = create_genesis_config(sol_to_lamports(1.)); let amount = genesis_config.rent.minimum_balance(0); - let bank0 = Bank::new_with_bank_forks_for_tests(&genesis_config).0; - let bank1 = Bank::new_with_bank_forks_for_tests(&genesis_config).0; + let (bank0, _bank_forks0) = Bank::new_with_bank_forks_for_tests(&genesis_config); + let (bank1, _bank_forks1) = Bank::new_with_bank_forks_for_tests(&genesis_config); assert_eq!(bank0.hash_internal_state(), bank1.hash_internal_state()); let key0 = solana_sdk::pubkey::new_rand(); let key1 = solana_sdk::pubkey::new_rand(); @@ -3533,7 +3533,7 @@ fn test_hash_internal_state_error() { solana_logger::setup(); let (genesis_config, mint_keypair) = create_genesis_config(sol_to_lamports(1.)); let amount = genesis_config.rent.minimum_balance(0); - let bank = Bank::new_with_bank_forks_for_tests(&genesis_config).0; + let (bank, _bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config); let key0 = solana_sdk::pubkey::new_rand(); bank.transfer(amount, &mint_keypair, &key0).unwrap(); let orig = bank.hash_internal_state(); @@ -3644,7 +3644,7 @@ fn test_bank_squash() { #[test] fn test_bank_get_account_in_parent_after_squash() { let (genesis_config, mint_keypair) = create_genesis_config(sol_to_lamports(1.)); - let parent = Bank::new_with_bank_forks_for_tests(&genesis_config).0; + let (parent, _bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config); let amount = genesis_config.rent.minimum_balance(0); let key1 = Keypair::new(); @@ -4082,7 +4082,7 @@ fn test_bank_get_slots_in_epoch() { #[test] fn test_is_delta_true() { let (genesis_config, mint_keypair) = create_genesis_config(sol_to_lamports(1.0)); - let bank = Bank::new_with_bank_forks_for_tests(&genesis_config).0; + let (bank, _bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config); let key1 = Keypair::new(); let tx_transfer_mint_to_1 = system_transaction::transfer( &mint_keypair, @@ -4106,7 +4106,7 @@ fn test_is_delta_true() { #[test] fn test_is_empty() { let (genesis_config, mint_keypair) = create_genesis_config(sol_to_lamports(1.0)); - let bank0 = Bank::new_with_bank_forks_for_tests(&genesis_config).0; + let (bank0, _bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config); let key1 = Keypair::new(); // The zeroth bank is empty becasue there are no transactions @@ -4211,7 +4211,7 @@ fn test_bank_vote_accounts() { mint_keypair, .. } = create_genesis_config_with_leader(500, &solana_sdk::pubkey::new_rand(), 1); - let bank = Bank::new_with_bank_forks_for_tests(&genesis_config).0; + let (bank, _bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config); let vote_accounts = bank.vote_accounts(); assert_eq!(vote_accounts.len(), 1); // bootstrap validator has @@ -4268,7 +4268,7 @@ fn test_bank_cloned_stake_delegations() { 123_000_000_000, ); genesis_config.rent = Rent::default(); - let bank = Bank::new_with_bank_forks_for_tests(&genesis_config).0; + let (bank, _bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config); let stake_delegations = bank.stakes_cache.stakes().stake_delegations().clone(); assert_eq!(stake_delegations.len(), 1); // bootstrap validator has @@ -4345,7 +4345,7 @@ fn test_bank_fees_account() { #[test] fn test_is_delta_with_no_committables() { let (genesis_config, mint_keypair) = create_genesis_config(8000); - let bank = Bank::new_with_bank_forks_for_tests(&genesis_config).0; + let (bank, _bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config); bank.is_delta.store(false, Relaxed); let keypair1 = Keypair::new(); @@ -4561,7 +4561,7 @@ fn test_get_filtered_indexed_accounts() { #[test] fn test_status_cache_ancestors() { solana_logger::setup(); - let parent = create_simple_test_arc_bank(500).0; + let (parent, _bank_forks) = create_simple_test_arc_bank(500); let bank1 = Arc::new(new_from_parent(parent)); let mut bank = bank1; for _ in 0..MAX_CACHE_ENTRIES * 2 { @@ -4622,7 +4622,7 @@ fn test_add_builtin() { bank.last_blockhash(), ); - let bank = bank.wrap_with_bank_forks_for_tests().0; + let (bank, _bank_forks) = bank.wrap_with_bank_forks_for_tests(); assert_eq!( bank.process_transaction(&transaction), Err(TransactionError::InstructionError( @@ -4786,7 +4786,7 @@ fn test_add_instruction_processor_for_existing_unrelated_accounts() { #[allow(deprecated)] #[test] fn test_recent_blockhashes_sysvar() { - let mut bank = create_simple_test_arc_bank(500).0; + let (mut bank, _bank_forks) = create_simple_test_arc_bank(500); for i in 1..5 { let bhq_account = bank.get_account(&sysvar::recent_blockhashes::id()).unwrap(); let recent_blockhashes = @@ -4804,7 +4804,7 @@ fn test_recent_blockhashes_sysvar() { #[allow(deprecated)] #[test] fn test_blockhash_queue_sysvar_consistency() { - let bank = create_simple_test_arc_bank(100_000).0; + let (bank, _bank_forks) = create_simple_test_arc_bank(100_000); goto_end_of_slot(bank.clone()); let bhq_account = bank.get_account(&sysvar::recent_blockhashes::id()).unwrap(); @@ -5166,7 +5166,7 @@ fn test_check_transaction_for_nonce_bad_tx_hash_fail() { #[test] fn test_assign_from_nonce_account_fail() { - let bank = create_simple_test_arc_bank(100_000_000).0; + let (bank, _bank_forks) = create_simple_test_arc_bank(100_000_000); let nonce = Keypair::new(); let nonce_account = AccountSharedData::new_data( 42_424_242, @@ -5192,7 +5192,7 @@ fn test_assign_from_nonce_account_fail() { fn test_nonce_must_be_advanceable() { let mut bank = create_simple_test_bank(100_000_000); bank.feature_set = Arc::new(FeatureSet::all_enabled()); - let bank = bank.wrap_with_bank_forks_for_tests().0; + let (bank, _bank_forks) = bank.wrap_with_bank_forks_for_tests(); let nonce_keypair = Keypair::new(); let nonce_authority = nonce_keypair.pubkey(); let durable_nonce = DurableNonce::from_blockhash(&bank.last_blockhash()); @@ -5868,7 +5868,7 @@ fn test_check_ro_durable_nonce_fails() { #[test] fn test_collect_balances() { - let parent = create_simple_test_arc_bank(500).0; + let (parent, _bank_forks) = create_simple_test_arc_bank(500); let bank0 = Arc::new(new_from_parent(parent)); let keypair = Keypair::new(); @@ -6005,9 +6005,8 @@ fn test_transaction_with_duplicate_accounts_in_instruction() { let (genesis_config, mint_keypair) = create_genesis_config_no_tx_fee_no_rent(500); let mock_program_id = Pubkey::from([2u8; 32]); - let bank = - Bank::new_with_mockup_builtin_for_tests(&genesis_config, mock_program_id, MockBuiltin::vm) - .0; + let (bank, _bank_forks) = + Bank::new_with_mockup_builtin_for_tests(&genesis_config, mock_program_id, MockBuiltin::vm); declare_process_instruction!(MockBuiltin, 1, |invoke_context| { let transaction_context = &invoke_context.transaction_context; @@ -6062,9 +6061,8 @@ fn test_transaction_with_program_ids_passed_to_programs() { let (genesis_config, mint_keypair) = create_genesis_config_no_tx_fee_no_rent(500); let mock_program_id = Pubkey::from([2u8; 32]); - let bank = - Bank::new_with_mockup_builtin_for_tests(&genesis_config, mock_program_id, MockBuiltin::vm) - .0; + let (bank, _bank_forks) = + Bank::new_with_mockup_builtin_for_tests(&genesis_config, mock_program_id, MockBuiltin::vm); let from_pubkey = solana_sdk::pubkey::new_rand(); let to_pubkey = solana_sdk::pubkey::new_rand(); @@ -6165,12 +6163,11 @@ fn test_incinerator() { fn test_duplicate_account_key() { solana_logger::setup(); let (genesis_config, mint_keypair) = create_genesis_config(500); - let bank = Bank::new_with_mockup_builtin_for_tests( + let (bank, _bank_forks) = Bank::new_with_mockup_builtin_for_tests( &genesis_config, solana_vote_program::id(), MockBuiltin::vm, - ) - .0; + ); let from_pubkey = solana_sdk::pubkey::new_rand(); let to_pubkey = solana_sdk::pubkey::new_rand(); @@ -6197,12 +6194,11 @@ fn test_duplicate_account_key() { fn test_process_transaction_with_too_many_account_locks() { solana_logger::setup(); let (genesis_config, mint_keypair) = create_genesis_config(500); - let bank = Bank::new_with_mockup_builtin_for_tests( + let (bank, _bank_forks) = Bank::new_with_mockup_builtin_for_tests( &genesis_config, solana_vote_program::id(), MockBuiltin::vm, - ) - .0; + ); let from_pubkey = solana_sdk::pubkey::new_rand(); let to_pubkey = solana_sdk::pubkey::new_rand(); @@ -6332,7 +6328,7 @@ fn test_fuzz_instructions() { (key, name.as_bytes().to_vec()) }) .collect(); - let bank = bank.wrap_with_bank_forks_for_tests().0; + let (bank, _bank_forks) = bank.wrap_with_bank_forks_for_tests(); let max_keys = 100; let keys: Vec<_> = (0..max_keys) .enumerate() @@ -6534,9 +6530,8 @@ fn test_same_program_id_uses_unique_executable_accounts() { let (genesis_config, mint_keypair) = create_genesis_config(50000); let program1_pubkey = solana_sdk::pubkey::new_rand(); - let bank = - Bank::new_with_mockup_builtin_for_tests(&genesis_config, program1_pubkey, MockBuiltin::vm) - .0; + let (bank, _bank_forks) = + Bank::new_with_mockup_builtin_for_tests(&genesis_config, program1_pubkey, MockBuiltin::vm); // Add a new program owned by the first let program2_pubkey = solana_sdk::pubkey::new_rand(); @@ -6744,11 +6739,8 @@ fn test_add_builtin_no_overwrite() { let slot = 123; let program_id = solana_sdk::pubkey::new_rand(); - let mut bank = Arc::new(Bank::new_from_parent( - create_simple_test_arc_bank(100_000).0, - &Pubkey::default(), - slot, - )); + let (parent_bank, _bank_forks) = create_simple_test_arc_bank(100_000); + let mut bank = Arc::new(Bank::new_from_parent(parent_bank, &Pubkey::default(), slot)); assert_eq!(bank.get_account_modified_slot(&program_id), None); Arc::get_mut(&mut bank) @@ -6768,11 +6760,8 @@ fn test_add_builtin_loader_no_overwrite() { let slot = 123; let loader_id = solana_sdk::pubkey::new_rand(); - let mut bank = Arc::new(Bank::new_from_parent( - create_simple_test_arc_bank(100_000).0, - &Pubkey::default(), - slot, - )); + let (parent_bank, _bank_forks) = create_simple_test_arc_bank(100_000); + let mut bank = Arc::new(Bank::new_from_parent(parent_bank, &Pubkey::default(), slot)); assert_eq!(bank.get_account_modified_slot(&loader_id), None); Arc::get_mut(&mut bank) @@ -6955,11 +6944,8 @@ fn test_add_builtin_account_after_frozen() { let slot = 123; let program_id = Pubkey::from_str("CiXgo2KHKSDmDnV1F6B69eWFgNAPiSBjjYvfB4cvRNre").unwrap(); - let bank = Bank::new_from_parent( - create_simple_test_arc_bank(100_000).0, - &Pubkey::default(), - slot, - ); + let (parent_bank, _bank_forks) = create_simple_test_arc_bank(100_000); + let bank = Bank::new_from_parent(parent_bank, &Pubkey::default(), slot); bank.freeze(); bank.add_builtin_account("mock_program", &program_id, false); @@ -7104,11 +7090,8 @@ fn test_add_precompiled_account_after_frozen() { let slot = 123; let program_id = Pubkey::from_str("CiXgo2KHKSDmDnV1F6B69eWFgNAPiSBjjYvfB4cvRNre").unwrap(); - let bank = Bank::new_from_parent( - create_simple_test_arc_bank(100_000).0, - &Pubkey::default(), - slot, - ); + let (parent_bank, _bank_forks) = create_simple_test_arc_bank(100_000); + let bank = Bank::new_from_parent(parent_bank, &Pubkey::default(), slot); bank.freeze(); bank.add_precompiled_account(&program_id); @@ -7929,7 +7912,7 @@ fn test_bpf_loader_upgradeable_deploy_with_max_len() { #[test] fn test_compute_active_feature_set() { - let bank0 = create_simple_test_arc_bank(100_000).0; + let (bank0, _bank_forks) = create_simple_test_arc_bank(100_000); let mut bank = Bank::new_from_parent(bank0, &Pubkey::default(), 1); let test_feature = "TestFeature11111111111111111111111111111111" @@ -7979,6 +7962,35 @@ fn test_compute_active_feature_set() { } #[test] +<<<<<<< HEAD +======= +fn test_reserved_account_keys() { + let (bank0, _bank_forks) = create_simple_test_arc_bank(100_000); + let mut bank = Bank::new_from_parent(bank0, &Pubkey::default(), 1); + bank.feature_set = Arc::new(FeatureSet::default()); + + assert_eq!( + bank.get_reserved_account_keys().len(), + 20, + "before activating the new feature, bank should already have active reserved keys" + ); + + // Activate `add_new_reserved_account_keys` feature + bank.store_account( + &feature_set::add_new_reserved_account_keys::id(), + &feature::create_account(&Feature::default(), 42), + ); + bank.apply_feature_activations(ApplyFeatureActivationsCaller::NewFromParent, true); + + assert_eq!( + bank.get_reserved_account_keys().len(), + 30, + "after activating the new feature, bank should have new active reserved keys" + ); +} + +#[test] +>>>>>>> d441c0f577 (Fix BankForks::new_rw_arc memory leak (#1893)) fn test_program_replacement() { let mut bank = create_simple_test_bank(0); @@ -8236,7 +8248,7 @@ fn test_timestamp_fast() { #[test] fn test_program_is_native_loader() { let (genesis_config, mint_keypair) = create_genesis_config(50000); - let bank = Bank::new_with_bank_forks_for_tests(&genesis_config).0; + let (bank, _bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config); let tx = Transaction::new_signed_with_payer( &[Instruction::new_with_bincode( @@ -9225,7 +9237,7 @@ fn test_tx_log_order() { &Pubkey::new_unique(), bootstrap_validator_stake_lamports(), ); - let bank = Bank::new_with_bank_forks_for_tests(&genesis_config).0; + let (bank, _bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config); *bank.transaction_log_collector_config.write().unwrap() = TransactionLogCollectorConfig { mentioned_addresses: HashSet::new(), filter: TransactionLogCollectorFilter::All, @@ -9313,9 +9325,8 @@ fn test_tx_return_data() { bootstrap_validator_stake_lamports(), ); let mock_program_id = Pubkey::from([2u8; 32]); - let bank = - Bank::new_with_mockup_builtin_for_tests(&genesis_config, mock_program_id, MockBuiltin::vm) - .0; + let (bank, _bank_forks) = + Bank::new_with_mockup_builtin_for_tests(&genesis_config, mock_program_id, MockBuiltin::vm); declare_process_instruction!(MockBuiltin, 1, |invoke_context| { let mock_program_id = Pubkey::from([2u8; 32]); @@ -9513,8 +9524,8 @@ fn test_transfer_sysvar() { ); let program_id = solana_sdk::pubkey::new_rand(); - let bank = - Bank::new_with_mockup_builtin_for_tests(&genesis_config, program_id, MockBuiltin::vm).0; + let (bank, _bank_forks) = + Bank::new_with_mockup_builtin_for_tests(&genesis_config, program_id, MockBuiltin::vm); declare_process_instruction!(MockBuiltin, 1, |invoke_context| { let transaction_context = &invoke_context.transaction_context; @@ -9723,8 +9734,8 @@ fn test_compute_budget_program_noop() { bootstrap_validator_stake_lamports(), ); let program_id = solana_sdk::pubkey::new_rand(); - let bank = - Bank::new_with_mockup_builtin_for_tests(&genesis_config, program_id, MockBuiltin::vm).0; + let (bank, _bank_forks) = + Bank::new_with_mockup_builtin_for_tests(&genesis_config, program_id, MockBuiltin::vm); declare_process_instruction!(MockBuiltin, 1, |invoke_context| { let compute_budget = invoke_context.get_compute_budget(); @@ -9768,8 +9779,8 @@ fn test_compute_request_instruction() { bootstrap_validator_stake_lamports(), ); let program_id = solana_sdk::pubkey::new_rand(); - let bank = - Bank::new_with_mockup_builtin_for_tests(&genesis_config, program_id, MockBuiltin::vm).0; + let (bank, _bank_forks) = + Bank::new_with_mockup_builtin_for_tests(&genesis_config, program_id, MockBuiltin::vm); declare_process_instruction!(MockBuiltin, 1, |invoke_context| { let compute_budget = invoke_context.get_compute_budget(); @@ -9814,8 +9825,8 @@ fn test_failed_compute_request_instruction() { ); let program_id = solana_sdk::pubkey::new_rand(); - let bank = - Bank::new_with_mockup_builtin_for_tests(&genesis_config, program_id, MockBuiltin::vm).0; + let (bank, _bank_forks) = + Bank::new_with_mockup_builtin_for_tests(&genesis_config, program_id, MockBuiltin::vm); let payer0_keypair = Keypair::new(); let payer1_keypair = Keypair::new(); @@ -9987,7 +9998,7 @@ fn test_call_precomiled_program() { .. } = create_genesis_config_with_leader(42, &Pubkey::new_unique(), 42); activate_all_features(&mut genesis_config); - let bank = Bank::new_with_bank_forks_for_tests(&genesis_config).0; + let (bank, _bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config); // libsecp256k1 // Since libsecp256k1 is still using the old version of rand, this test @@ -10259,7 +10270,7 @@ fn test_an_empty_instruction_without_program() { let message = Message::new(&[ix], Some(&mint_keypair.pubkey())); let tx = Transaction::new(&[&mint_keypair], message, genesis_config.hash()); - let bank = Bank::new_with_bank_forks_for_tests(&genesis_config).0; + let (bank, _bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config); assert_eq!( bank.process_transaction(&tx).unwrap_err(), TransactionError::InstructionError(0, InstructionError::UnsupportedProgramId), @@ -10287,7 +10298,7 @@ fn test_accounts_data_size_with_good_transaction() { const ACCOUNT_SIZE: u64 = MAX_PERMITTED_DATA_LENGTH; let (genesis_config, mint_keypair) = create_genesis_config(1_000 * LAMPORTS_PER_SOL); let bank = Bank::new_for_tests(&genesis_config); - let bank = bank.wrap_with_bank_forks_for_tests().0; + let (bank, _bank_forks) = bank.wrap_with_bank_forks_for_tests(); let transaction = system_transaction::create_account( &mint_keypair, &Keypair::new(), @@ -10328,7 +10339,7 @@ fn test_accounts_data_size_with_bad_transaction() { const ACCOUNT_SIZE: u64 = MAX_PERMITTED_DATA_LENGTH; let (genesis_config, _mint_keypair) = create_genesis_config(1_000 * LAMPORTS_PER_SOL); let bank = Bank::new_for_tests(&genesis_config); - let bank = bank.wrap_with_bank_forks_for_tests().0; + let (bank, _bank_forks) = bank.wrap_with_bank_forks_for_tests(); let transaction = system_transaction::create_account( &Keypair::new(), &Keypair::new(), @@ -10445,12 +10456,11 @@ fn test_invalid_rent_state_changes_existing_accounts() { ), ); - let bank = Bank::new_with_mockup_builtin_for_tests( + let (bank, _bank_forks) = Bank::new_with_mockup_builtin_for_tests( &genesis_config, mock_program_id, MockTransferBuiltin::vm, - ) - .0; + ); let recent_blockhash = bank.last_blockhash(); let check_account_is_rent_exempt = |pubkey: &Pubkey| -> bool { @@ -10532,12 +10542,11 @@ fn test_invalid_rent_state_changes_new_accounts() { let account_data_size = 100; let rent_exempt_minimum = genesis_config.rent.minimum_balance(account_data_size); - let bank = Bank::new_with_mockup_builtin_for_tests( + let (bank, _bank_forks) = Bank::new_with_mockup_builtin_for_tests( &genesis_config, mock_program_id, MockTransferBuiltin::vm, - ) - .0; + ); let recent_blockhash = bank.last_blockhash(); let check_account_is_rent_exempt = |pubkey: &Pubkey| -> bool { @@ -10595,12 +10604,11 @@ fn test_drained_created_account() { // Create legacy accounts of various kinds let created_keypair = Keypair::new(); - let bank = Bank::new_with_mockup_builtin_for_tests( + let (bank, _bank_forks) = Bank::new_with_mockup_builtin_for_tests( &genesis_config, mock_program_id, MockTransferBuiltin::vm, - ) - .0; + ); let recent_blockhash = bank.last_blockhash(); // Create and drain a small data size account @@ -10711,7 +10719,7 @@ fn test_rent_state_changes_sysvars() { Account::from(validator_vote_account), ); - let bank = Bank::new_with_bank_forks_for_tests(&genesis_config).0; + let (bank, _bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config); // Ensure transactions with sysvars succeed, even though sysvars appear RentPaying by balance let tx = Transaction::new_signed_with_payer( @@ -10754,7 +10762,7 @@ fn test_invalid_rent_state_changes_fee_payer() { Account::new(rent_exempt_minimum, 0, &system_program::id()), ); - let bank = Bank::new_with_bank_forks_for_tests(&genesis_config).0; + let (bank, _bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config); let recent_blockhash = bank.last_blockhash(); let check_account_is_rent_exempt = |pubkey: &Pubkey| -> bool { @@ -10984,7 +10992,7 @@ fn test_rent_state_incinerator() { genesis_config.rent = Rent::default(); let rent_exempt_minimum = genesis_config.rent.minimum_balance(0); - let bank = Bank::new_with_bank_forks_for_tests(&genesis_config).0; + let (bank, _bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config); for amount in [rent_exempt_minimum - 1, rent_exempt_minimum] { bank.transfer(amount, &mint_keypair, &solana_sdk::incinerator::id()) @@ -11210,12 +11218,11 @@ fn test_resize_and_rent() { activate_all_features(&mut genesis_config); let mock_program_id = Pubkey::new_unique(); - let bank = Bank::new_with_mockup_builtin_for_tests( + let (bank, _bank_forks) = Bank::new_with_mockup_builtin_for_tests( &genesis_config, mock_program_id, MockReallocBuiltin::vm, - ) - .0; + ); let recent_blockhash = bank.last_blockhash(); @@ -11486,12 +11493,11 @@ fn test_accounts_data_size_and_resize_transactions() { .. } = genesis_utils::create_genesis_config(100 * LAMPORTS_PER_SOL); let mock_program_id = Pubkey::new_unique(); - let bank = Bank::new_with_mockup_builtin_for_tests( + let (bank, _bank_forks) = Bank::new_with_mockup_builtin_for_tests( &genesis_config, mock_program_id, MockReallocBuiltin::vm, - ) - .0; + ); let recent_blockhash = bank.last_blockhash(); @@ -11745,7 +11751,7 @@ fn test_cap_accounts_data_allocations_per_transaction() { / MAX_PERMITTED_DATA_LENGTH as usize; let (genesis_config, mint_keypair) = create_genesis_config(1_000_000 * LAMPORTS_PER_SOL); - let bank = Bank::new_with_bank_forks_for_tests(&genesis_config).0; + let (bank, _bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config); let mut instructions = Vec::new(); let mut keypairs = vec![mint_keypair.insecure_clone()]; @@ -12034,7 +12040,7 @@ fn test_calculate_fee_with_request_heap_frame_flag() { fn test_is_in_slot_hashes_history() { use solana_sdk::slot_hashes::MAX_ENTRIES; - let bank0 = create_simple_test_arc_bank(1).0; + let (bank0, _bank_forks) = create_simple_test_arc_bank(1); assert!(!bank0.is_in_slot_hashes_history(&0)); assert!(!bank0.is_in_slot_hashes_history(&1)); let mut last_bank = bank0; @@ -13168,7 +13174,7 @@ fn test_store_vote_accounts_partitioned_empty() { #[test] fn test_system_instruction_allocate() { let (genesis_config, mint_keypair) = create_genesis_config_no_tx_fee(sol_to_lamports(1.0)); - let bank = Bank::new_with_bank_forks_for_tests(&genesis_config).0; + let (bank, _bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config); let bank_client = BankClient::new_shared(bank); let data_len = 2; let amount = genesis_config.rent.minimum_balance(data_len); @@ -13295,7 +13301,7 @@ fn test_create_zero_lamport_without_clean() { #[test] fn test_system_instruction_assign_with_seed() { let (genesis_config, mint_keypair) = create_genesis_config_no_tx_fee(sol_to_lamports(1.0)); - let bank = Bank::new_with_bank_forks_for_tests(&genesis_config).0; + let (bank, _bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config); let bank_client = BankClient::new_shared(bank); let alice_keypair = Keypair::new(); @@ -13336,7 +13342,7 @@ fn test_system_instruction_unsigned_transaction() { let amount = genesis_config.rent.minimum_balance(0); // Fund to account to bypass AccountNotFound error - let bank = Bank::new_with_bank_forks_for_tests(&genesis_config).0; + let (bank, _bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config); let bank_client = BankClient::new_shared(bank); bank_client .transfer_and_confirm(amount, &alice_keypair, &mallory_pubkey) @@ -14173,8 +14179,8 @@ fn test_rebuild_skipped_rewrites() { fn test_failed_simulation_compute_units() { let (genesis_config, mint_keypair) = create_genesis_config(LAMPORTS_PER_SOL); let program_id = Pubkey::new_unique(); - let bank = - Bank::new_with_mockup_builtin_for_tests(&genesis_config, program_id, MockBuiltin::vm).0; + let (bank, _bank_forks) = + Bank::new_with_mockup_builtin_for_tests(&genesis_config, program_id, MockBuiltin::vm); const TEST_UNITS: u64 = 10_000; const MOCK_BUILTIN_UNITS: u64 = 1; diff --git a/runtime/src/bank_client.rs b/runtime/src/bank_client.rs index 7fe6418d4110b2..7b3bc8c2dd3855 100644 --- a/runtime/src/bank_client.rs +++ b/runtime/src/bank_client.rs @@ -374,7 +374,7 @@ mod tests { let jane_doe_keypair = Keypair::new(); let jane_pubkey = jane_doe_keypair.pubkey(); let doe_keypairs = vec![&john_doe_keypair, &jane_doe_keypair]; - let bank = Bank::new_with_bank_forks_for_tests(&genesis_config).0; + let (bank, _bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config); let bank_client = BankClient::new_shared(bank); let amount = genesis_config.rent.minimum_balance(0); diff --git a/runtime/src/bank_forks.rs b/runtime/src/bank_forks.rs index d481bf1b43bda8..6df7575928988f 100644 --- a/runtime/src/bank_forks.rs +++ b/runtime/src/bank_forks.rs @@ -126,12 +126,16 @@ impl BankForks { scheduler_pool: None, })); +<<<<<<< HEAD root_bank .loaded_programs_cache .write() .unwrap() .set_fork_graph(bank_forks.clone()); +======= + root_bank.set_fork_graph_in_program_cache(Arc::downgrade(&bank_forks)); +>>>>>>> d441c0f577 (Fix BankForks::new_rw_arc memory leak (#1893)) bank_forks } @@ -734,6 +738,14 @@ mod tests { std::{sync::atomic::Ordering::Relaxed, time::Duration}, }; + #[test] + fn test_bank_forks_new_rw_arc_memory_leak() { + for _ in 0..1000 { + let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(10_000); + BankForks::new_rw_arc(Bank::new_for_tests(&genesis_config)); + } + } + #[test] fn test_bank_forks_new() { let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(10_000); diff --git a/runtime/tests/stake.rs b/runtime/tests/stake.rs index 7c53e1e44a3af3..9922b8c9a5d075 100755 --- a/runtime/tests/stake.rs +++ b/runtime/tests/stake.rs @@ -142,7 +142,7 @@ fn test_stake_create_and_split_single_signature() { let staker_pubkey = staker_keypair.pubkey(); - let bank = Bank::new_with_bank_forks_for_tests(&genesis_config).0; + let (bank, _bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config); let bank_client = BankClient::new_shared(bank.clone()); let stake_address = @@ -218,7 +218,7 @@ fn test_stake_create_and_split_to_existing_system_account() { let staker_pubkey = staker_keypair.pubkey(); - let bank = Bank::new_with_bank_forks_for_tests(&genesis_config).0; + let (bank, _bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config); let bank_client = BankClient::new_shared(bank.clone()); let stake_address = @@ -593,7 +593,7 @@ fn test_create_stake_account_from_seed() { &solana_sdk::pubkey::new_rand(), 1_000_000, ); - let bank = Bank::new_with_bank_forks_for_tests(&genesis_config).0; + let (bank, _bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config); let mint_pubkey = mint_keypair.pubkey(); let bank_client = BankClient::new_shared(bank.clone()); diff --git a/stake-accounts/src/stake_accounts.rs b/stake-accounts/src/stake_accounts.rs index 084b7837cd99f4..bcea83eee9a627 100644 --- a/stake-accounts/src/stake_accounts.rs +++ b/stake-accounts/src/stake_accounts.rs @@ -283,7 +283,7 @@ pub(crate) fn move_stake_accounts( mod tests { use { super::*, - solana_runtime::{bank::Bank, bank_client::BankClient}, + solana_runtime::{bank::Bank, bank_client::BankClient, bank_forks::BankForks}, solana_sdk::{ account::{AccountSharedData, ReadableAccount}, client::SyncClient, @@ -292,16 +292,16 @@ mod tests { stake::state::StakeStateV2, }, solana_stake_program::stake_state, - std::sync::Arc, + std::sync::{Arc, RwLock}, }; - fn create_bank(lamports: u64) -> (Arc, Keypair, u64, u64) { + fn create_bank(lamports: u64) -> (Arc, Arc>, Keypair, u64, u64) { let (mut genesis_config, mint_keypair) = create_genesis_config(lamports); genesis_config.fee_rate_governor = solana_sdk::fee_calculator::FeeRateGovernor::new(0, 0); - let bank = Bank::new_with_bank_forks_for_tests(&genesis_config).0; + let (bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config); let stake_rent = bank.get_minimum_balance_for_rent_exemption(StakeStateV2::size_of()); let system_rent = bank.get_minimum_balance_for_rent_exemption(0); - (bank, mint_keypair, stake_rent, system_rent) + (bank, bank_forks, mint_keypair, stake_rent, system_rent) } fn create_account( @@ -355,7 +355,7 @@ mod tests { #[test] fn test_new_derived_stake_account() { - let (bank, funding_keypair, stake_rent, system_rent) = create_bank(10_000_000); + let (bank, _bank_forks, funding_keypair, stake_rent, system_rent) = create_bank(10_000_000); let funding_pubkey = funding_keypair.pubkey(); let bank_client = BankClient::new_shared(bank); let fee_payer_keypair = create_account(&bank_client, &funding_keypair, system_rent); @@ -392,7 +392,7 @@ mod tests { #[test] fn test_authorize_stake_accounts() { - let (bank, funding_keypair, stake_rent, system_rent) = create_bank(10_000_000); + let (bank, _bank_forks, funding_keypair, stake_rent, system_rent) = create_bank(10_000_000); let funding_pubkey = funding_keypair.pubkey(); let bank_client = BankClient::new_shared(bank); let fee_payer_keypair = create_account(&bank_client, &funding_keypair, system_rent); @@ -454,7 +454,7 @@ mod tests { #[test] fn test_lockup_stake_accounts() { - let (bank, funding_keypair, stake_rent, system_rent) = create_bank(10_000_000); + let (bank, _bank_forks, funding_keypair, stake_rent, system_rent) = create_bank(10_000_000); let funding_pubkey = funding_keypair.pubkey(); let bank_client = BankClient::new_shared(bank); let fee_payer_keypair = create_account(&bank_client, &funding_keypair, system_rent); @@ -545,7 +545,7 @@ mod tests { #[test] fn test_rebase_stake_accounts() { - let (bank, funding_keypair, stake_rent, system_rent) = create_bank(10_000_000); + let (bank, _bank_forks, funding_keypair, stake_rent, system_rent) = create_bank(10_000_000); let funding_pubkey = funding_keypair.pubkey(); let bank_client = BankClient::new_shared(bank); let fee_payer_keypair = create_account(&bank_client, &funding_keypair, system_rent); @@ -608,7 +608,7 @@ mod tests { #[test] fn test_move_stake_accounts() { - let (bank, funding_keypair, stake_rent, system_rent) = create_bank(10_000_000); + let (bank, _bank_forks, funding_keypair, stake_rent, system_rent) = create_bank(10_000_000); let funding_pubkey = funding_keypair.pubkey(); let bank_client = BankClient::new_shared(bank); let fee_payer_keypair = create_account(&bank_client, &funding_keypair, system_rent); diff --git a/svm/src/transaction_processor.rs b/svm/src/transaction_processor.rs new file mode 100644 index 00000000000000..73afc8cac772db --- /dev/null +++ b/svm/src/transaction_processor.rs @@ -0,0 +1,2383 @@ +use { + crate::{ + account_loader::{ + collect_rent_from_account, load_accounts, validate_fee_payer, + CheckedTransactionDetails, LoadedTransaction, TransactionCheckResult, + TransactionLoadResult, TransactionValidationResult, ValidatedTransactionDetails, + }, + account_overrides::AccountOverrides, + message_processor::MessageProcessor, + program_loader::{get_program_modification_slot, load_program_with_pubkey}, + rollback_accounts::RollbackAccounts, + transaction_account_state_info::TransactionAccountStateInfo, + transaction_error_metrics::TransactionErrorMetrics, + transaction_processing_callback::TransactionProcessingCallback, + transaction_results::{TransactionExecutionDetails, TransactionExecutionResult}, + }, + log::debug, + percentage::Percentage, + solana_bpf_loader_program::syscalls::create_program_runtime_environment_v1, + solana_compute_budget::{ + compute_budget::ComputeBudget, + compute_budget_processor::process_compute_budget_instructions, + }, + solana_loader_v4_program::create_program_runtime_environment_v2, + solana_log_collector::LogCollector, + solana_measure::{measure, measure::Measure}, + solana_program_runtime::{ + invoke_context::{EnvironmentConfig, InvokeContext}, + loaded_programs::{ + ForkGraph, ProgramCache, ProgramCacheEntry, ProgramCacheForTxBatch, + ProgramCacheMatchCriteria, + }, + sysvar_cache::SysvarCache, + timings::{ExecuteTimingType, ExecuteTimings}, + }, + solana_sdk::{ + account::{AccountSharedData, ReadableAccount, PROGRAM_OWNERS}, + clock::{Epoch, Slot}, + feature_set::{ + include_loaded_accounts_data_size_in_fee_calculation, + remove_rounding_in_fee_calculation, FeatureSet, + }, + fee::{FeeBudgetLimits, FeeStructure}, + hash::Hash, + inner_instruction::{InnerInstruction, InnerInstructionsList}, + instruction::{CompiledInstruction, TRANSACTION_LEVEL_STACK_HEIGHT}, + message::SanitizedMessage, + pubkey::Pubkey, + rent_collector::RentCollector, + saturating_add_assign, + transaction::{self, SanitizedTransaction, TransactionError}, + transaction_context::{ExecutionRecord, TransactionContext}, + }, + solana_type_overrides::sync::{atomic::Ordering, Arc, RwLock, RwLockReadGuard}, + solana_vote::vote_account::VoteAccountsHashMap, + std::{ + cell::RefCell, + collections::{hash_map::Entry, HashMap, HashSet}, + fmt::{Debug, Formatter}, + rc::Rc, + }, +}; + +/// A list of log messages emitted during a transaction +pub type TransactionLogMessages = Vec; + +/// The output of the transaction batch processor's +/// `load_and_execute_sanitized_transactions` method. +pub struct LoadAndExecuteSanitizedTransactionsOutput { + /// Error metrics for transactions that were processed. + pub error_metrics: TransactionErrorMetrics, + /// Timings for transaction batch execution. + pub execute_timings: ExecuteTimings, + // Vector of results indicating whether a transaction was executed or could not + // be executed. Note executed transactions can still have failed! + pub execution_results: Vec, + // Vector of loaded transactions from transactions that were processed. + pub loaded_transactions: Vec, +} + +/// Configuration of the recording capabilities for transaction execution +#[derive(Copy, Clone, Default)] +pub struct ExecutionRecordingConfig { + pub enable_cpi_recording: bool, + pub enable_log_recording: bool, + pub enable_return_data_recording: bool, +} + +impl ExecutionRecordingConfig { + pub fn new_single_setting(option: bool) -> Self { + ExecutionRecordingConfig { + enable_return_data_recording: option, + enable_log_recording: option, + enable_cpi_recording: option, + } + } +} + +/// Configurations for processing transactions. +#[derive(Default)] +pub struct TransactionProcessingConfig<'a> { + /// Encapsulates overridden accounts, typically used for transaction + /// simulation. + pub account_overrides: Option<&'a AccountOverrides>, + /// Whether or not to check a program's modification slot when replenishing + /// a program cache instance. + pub check_program_modification_slot: bool, + /// The compute budget to use for transaction execution. + pub compute_budget: Option, + /// The maximum number of bytes that log messages can consume. + pub log_messages_bytes_limit: Option, + /// Whether to limit the number of programs loaded for the transaction + /// batch. + pub limit_to_load_programs: bool, + /// Recording capabilities for transaction execution. + pub recording_config: ExecutionRecordingConfig, + /// The max number of accounts that a transaction may lock. + pub transaction_account_lock_limit: Option, +} + +/// Runtime environment for transaction batch processing. +#[derive(Default)] +pub struct TransactionProcessingEnvironment<'a> { + /// The blockhash to use for the transaction batch. + pub blockhash: Hash, + /// The total stake for the current epoch. + pub epoch_total_stake: Option, + /// The vote accounts for the current epoch. + pub epoch_vote_accounts: Option<&'a VoteAccountsHashMap>, + /// Runtime feature set to use for the transaction batch. + pub feature_set: Arc, + /// Fee structure to use for assessing transaction fees. + pub fee_structure: Option<&'a FeeStructure>, + /// Lamports per signature to charge per transaction. + pub lamports_per_signature: u64, + /// Rent collector to use for the transaction batch. + pub rent_collector: Option<&'a RentCollector>, +} + +#[cfg_attr(feature = "frozen-abi", derive(AbiExample))] +pub struct TransactionBatchProcessor { + /// Bank slot (i.e. block) + slot: Slot, + + /// Bank epoch + epoch: Epoch, + + /// SysvarCache is a collection of system variables that are + /// accessible from on chain programs. It is passed to SVM from + /// client code (e.g. Bank) and forwarded to the MessageProcessor. + sysvar_cache: RwLock, + + /// Programs required for transaction batch processing + pub program_cache: Arc>>, + + /// Builtin program ids + pub builtin_program_ids: RwLock>, +} + +impl Debug for TransactionBatchProcessor { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("TransactionBatchProcessor") + .field("slot", &self.slot) + .field("epoch", &self.epoch) + .field("sysvar_cache", &self.sysvar_cache) + .field("program_cache", &self.program_cache) + .finish() + } +} + +impl Default for TransactionBatchProcessor { + fn default() -> Self { + Self { + slot: Slot::default(), + epoch: Epoch::default(), + sysvar_cache: RwLock::::default(), + program_cache: Arc::new(RwLock::new(ProgramCache::new( + Slot::default(), + Epoch::default(), + ))), + builtin_program_ids: RwLock::new(HashSet::new()), + } + } +} + +impl TransactionBatchProcessor { + pub fn new(slot: Slot, epoch: Epoch, builtin_program_ids: HashSet) -> Self { + Self { + slot, + epoch, + sysvar_cache: RwLock::::default(), + program_cache: Arc::new(RwLock::new(ProgramCache::new(slot, epoch))), + builtin_program_ids: RwLock::new(builtin_program_ids), + } + } + + pub fn new_from(&self, slot: Slot, epoch: Epoch) -> Self { + Self { + slot, + epoch, + sysvar_cache: RwLock::::default(), + program_cache: self.program_cache.clone(), + builtin_program_ids: RwLock::new(self.builtin_program_ids.read().unwrap().clone()), + } + } + + /// Returns the current environments depending on the given epoch + /// Returns None if the call could result in a deadlock + #[cfg(feature = "dev-context-only-utils")] + pub fn get_environments_for_epoch( + &self, + epoch: Epoch, + ) -> Option { + self.program_cache + .try_read() + .ok() + .map(|cache| cache.get_environments_for_epoch(epoch)) + } + + pub fn sysvar_cache(&self) -> RwLockReadGuard { + self.sysvar_cache.read().unwrap() + } + + /// Main entrypoint to the SVM. + pub fn load_and_execute_sanitized_transactions( + &self, + callbacks: &CB, + sanitized_txs: &[SanitizedTransaction], + check_results: Vec, + environment: &TransactionProcessingEnvironment, + config: &TransactionProcessingConfig, + ) -> LoadAndExecuteSanitizedTransactionsOutput { + // Initialize metrics. + let mut error_metrics = TransactionErrorMetrics::default(); + let mut execute_timings = ExecuteTimings::default(); + + let (validation_results, validate_fees_time) = measure!(self.validate_fees( + callbacks, + sanitized_txs, + check_results, + &environment.feature_set, + environment + .fee_structure + .unwrap_or(&FeeStructure::default()), + environment + .rent_collector + .unwrap_or(&RentCollector::default()), + &mut error_metrics + )); + + let mut program_cache_time = Measure::start("program_cache"); + let mut program_accounts_map = Self::filter_executable_program_accounts( + callbacks, + sanitized_txs, + &validation_results, + PROGRAM_OWNERS, + ); + for builtin_program in self.builtin_program_ids.read().unwrap().iter() { + program_accounts_map.insert(*builtin_program, 0); + } + + let program_cache_for_tx_batch = Rc::new(RefCell::new(self.replenish_program_cache( + callbacks, + &program_accounts_map, + config.check_program_modification_slot, + config.limit_to_load_programs, + ))); + + if program_cache_for_tx_batch.borrow().hit_max_limit { + const ERROR: TransactionError = TransactionError::ProgramCacheHitMaxLimit; + let loaded_transactions = vec![Err(ERROR); sanitized_txs.len()]; + let execution_results = + vec![TransactionExecutionResult::NotExecuted(ERROR); sanitized_txs.len()]; + return LoadAndExecuteSanitizedTransactionsOutput { + error_metrics, + execute_timings, + execution_results, + loaded_transactions, + }; + } + program_cache_time.stop(); + + let mut load_time = Measure::start("accounts_load"); + let mut loaded_transactions = load_accounts( + callbacks, + sanitized_txs, + validation_results, + &mut error_metrics, + config.account_overrides, + &environment.feature_set, + environment + .rent_collector + .unwrap_or(&RentCollector::default()), + &program_cache_for_tx_batch.borrow(), + ); + load_time.stop(); + + let mut execution_time = Measure::start("execution_time"); + + let execution_results: Vec = loaded_transactions + .iter_mut() + .zip(sanitized_txs.iter()) + .map(|(load_result, tx)| match load_result { + Err(e) => TransactionExecutionResult::NotExecuted(e.clone()), + Ok(loaded_transaction) => { + let result = self.execute_loaded_transaction( + tx, + loaded_transaction, + &mut execute_timings, + &mut error_metrics, + &mut program_cache_for_tx_batch.borrow_mut(), + environment, + config, + ); + + if let TransactionExecutionResult::Executed { + details, + programs_modified_by_tx, + } = &result + { + // Update batch specific cache of the loaded programs with the modifications + // made by the transaction, if it executed successfully. + if details.status.is_ok() { + program_cache_for_tx_batch + .borrow_mut() + .merge(programs_modified_by_tx); + } + } + + result + } + }) + .collect(); + + execution_time.stop(); + + // Skip eviction when there's no chance this particular tx batch has increased the size of + // ProgramCache entries. Note that loaded_missing is deliberately defined, so that there's + // still at least one other batch, which will evict the program cache, even after the + // occurrences of cooperative loading. + if program_cache_for_tx_batch.borrow().loaded_missing + || program_cache_for_tx_batch.borrow().merged_modified + { + const SHRINK_LOADED_PROGRAMS_TO_PERCENTAGE: u8 = 90; + self.program_cache + .write() + .unwrap() + .evict_using_2s_random_selection( + Percentage::from(SHRINK_LOADED_PROGRAMS_TO_PERCENTAGE), + self.slot, + ); + } + + debug!( + "load: {}us execute: {}us txs_len={}", + load_time.as_us(), + execution_time.as_us(), + sanitized_txs.len(), + ); + + execute_timings.saturating_add_in_place( + ExecuteTimingType::ValidateFeesUs, + validate_fees_time.as_us(), + ); + execute_timings.saturating_add_in_place( + ExecuteTimingType::ProgramCacheUs, + program_cache_time.as_us(), + ); + execute_timings.saturating_add_in_place(ExecuteTimingType::LoadUs, load_time.as_us()); + execute_timings + .saturating_add_in_place(ExecuteTimingType::ExecuteUs, execution_time.as_us()); + + LoadAndExecuteSanitizedTransactionsOutput { + error_metrics, + execute_timings, + execution_results, + loaded_transactions, + } + } + + fn validate_fees( + &self, + callbacks: &CB, + sanitized_txs: &[impl core::borrow::Borrow], + check_results: Vec, + feature_set: &FeatureSet, + fee_structure: &FeeStructure, + rent_collector: &RentCollector, + error_counters: &mut TransactionErrorMetrics, + ) -> Vec { + sanitized_txs + .iter() + .zip(check_results) + .map(|(sanitized_tx, check_result)| { + check_result.and_then(|checked_details| { + let message = sanitized_tx.borrow().message(); + self.validate_transaction_fee_payer( + callbacks, + message, + checked_details, + feature_set, + fee_structure, + rent_collector, + error_counters, + ) + }) + }) + .collect() + } + + // Loads transaction fee payer, collects rent if necessary, then calculates + // transaction fees, and deducts them from the fee payer balance. If the + // account is not found or has insufficient funds, an error is returned. + fn validate_transaction_fee_payer( + &self, + callbacks: &CB, + message: &SanitizedMessage, + checked_details: CheckedTransactionDetails, + feature_set: &FeatureSet, + fee_structure: &FeeStructure, + rent_collector: &RentCollector, + error_counters: &mut TransactionErrorMetrics, + ) -> transaction::Result { + let compute_budget_limits = process_compute_budget_instructions( + message.program_instructions_iter(), + ) + .map_err(|err| { + error_counters.invalid_compute_budget += 1; + err + })?; + + let fee_payer_address = message.fee_payer(); + let Some(mut fee_payer_account) = callbacks.get_account_shared_data(fee_payer_address) + else { + error_counters.account_not_found += 1; + return Err(TransactionError::AccountNotFound); + }; + + let fee_payer_loaded_rent_epoch = fee_payer_account.rent_epoch(); + let fee_payer_rent_debit = collect_rent_from_account( + feature_set, + rent_collector, + fee_payer_address, + &mut fee_payer_account, + ) + .rent_amount; + + let CheckedTransactionDetails { + nonce, + lamports_per_signature, + } = checked_details; + + let fee_budget_limits = FeeBudgetLimits::from(compute_budget_limits); + let fee_details = fee_structure.calculate_fee_details( + message, + lamports_per_signature, + &fee_budget_limits, + feature_set.is_active(&include_loaded_accounts_data_size_in_fee_calculation::id()), + feature_set.is_active(&remove_rounding_in_fee_calculation::id()), + ); + + let fee_payer_index = 0; + validate_fee_payer( + fee_payer_address, + &mut fee_payer_account, + fee_payer_index, + error_counters, + rent_collector, + fee_details.total_fee(), + )?; + + // Capture fee-subtracted fee payer account and original nonce account state + // to rollback to if transaction execution fails. + let rollback_accounts = RollbackAccounts::new( + nonce, + *fee_payer_address, + fee_payer_account.clone(), + fee_payer_rent_debit, + fee_payer_loaded_rent_epoch, + ); + + Ok(ValidatedTransactionDetails { + fee_details, + fee_payer_account, + fee_payer_rent_debit, + rollback_accounts, + compute_budget_limits, + }) + } + + /// Returns a map from executable program accounts (all accounts owned by any loader) + /// to their usage counters, for the transactions with a valid blockhash or nonce. + fn filter_executable_program_accounts( + callbacks: &CB, + txs: &[SanitizedTransaction], + validation_results: &[TransactionValidationResult], + program_owners: &[Pubkey], + ) -> HashMap { + let mut result: HashMap = HashMap::new(); + validation_results.iter().zip(txs).for_each(|etx| { + if let (Ok(_), tx) = etx { + tx.message() + .account_keys() + .iter() + .for_each(|key| match result.entry(*key) { + Entry::Occupied(mut entry) => { + let count = entry.get_mut(); + saturating_add_assign!(*count, 1); + } + Entry::Vacant(entry) => { + if callbacks + .account_matches_owners(key, program_owners) + .is_some() + { + entry.insert(1); + } + } + }); + } + }); + result + } + + fn replenish_program_cache( + &self, + callback: &CB, + program_accounts_map: &HashMap, + check_program_modification_slot: bool, + limit_to_load_programs: bool, + ) -> ProgramCacheForTxBatch { + let mut missing_programs: Vec<(Pubkey, (ProgramCacheMatchCriteria, u64))> = + program_accounts_map + .iter() + .map(|(pubkey, count)| { + let match_criteria = if check_program_modification_slot { + get_program_modification_slot(callback, pubkey) + .map_or(ProgramCacheMatchCriteria::Tombstone, |slot| { + ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(slot) + }) + } else { + ProgramCacheMatchCriteria::NoCriteria + }; + (*pubkey, (match_criteria, *count)) + }) + .collect(); + + let mut loaded_programs_for_txs = None; + loop { + let (program_to_store, task_cookie, task_waiter) = { + // Lock the global cache. + let program_cache = self.program_cache.read().unwrap(); + // Initialize our local cache. + let is_first_round = loaded_programs_for_txs.is_none(); + if is_first_round { + loaded_programs_for_txs = Some(ProgramCacheForTxBatch::new_from_cache( + self.slot, + self.epoch, + &program_cache, + )); + } + // Figure out which program needs to be loaded next. + let program_to_load = program_cache.extract( + &mut missing_programs, + loaded_programs_for_txs.as_mut().unwrap(), + is_first_round, + ); + + let program_to_store = program_to_load.map(|(key, count)| { + // Load, verify and compile one program. + let program = load_program_with_pubkey( + callback, + &program_cache.get_environments_for_epoch(self.epoch), + &key, + self.slot, + false, + ) + .expect("called load_program_with_pubkey() with nonexistent account"); + program.tx_usage_counter.store(count, Ordering::Relaxed); + (key, program) + }); + + let task_waiter = Arc::clone(&program_cache.loading_task_waiter); + (program_to_store, task_waiter.cookie(), task_waiter) + // Unlock the global cache again. + }; + + if let Some((key, program)) = program_to_store { + let mut program_cache = self.program_cache.write().unwrap(); + // Submit our last completed loading task. + if program_cache.finish_cooperative_loading_task(self.slot, key, program) + && limit_to_load_programs + { + // This branch is taken when there is an error in assigning a program to a + // cache slot. It is not possible to mock this error for SVM unit + // tests purposes. + let mut ret = ProgramCacheForTxBatch::new_from_cache( + self.slot, + self.epoch, + &program_cache, + ); + ret.hit_max_limit = true; + return ret; + } + } else if missing_programs.is_empty() { + break; + } else { + // Sleep until the next finish_cooperative_loading_task() call. + // Once a task completes we'll wake up and try to load the + // missing programs inside the tx batch again. + let _new_cookie = task_waiter.wait(task_cookie); + } + } + + loaded_programs_for_txs.unwrap() + } + + pub fn prepare_program_cache_for_upcoming_feature_set( + &self, + callbacks: &CB, + upcoming_feature_set: &FeatureSet, + compute_budget: &ComputeBudget, + slot_index: u64, + slots_in_epoch: u64, + ) { + // Recompile loaded programs one at a time before the next epoch hits + let slots_in_recompilation_phase = + (solana_program_runtime::loaded_programs::MAX_LOADED_ENTRY_COUNT as u64) + .min(slots_in_epoch) + .checked_div(2) + .unwrap(); + let mut program_cache = self.program_cache.write().unwrap(); + if program_cache.upcoming_environments.is_some() { + if let Some((key, program_to_recompile)) = program_cache.programs_to_recompile.pop() { + let effective_epoch = program_cache.latest_root_epoch.saturating_add(1); + drop(program_cache); + let environments_for_epoch = self + .program_cache + .read() + .unwrap() + .get_environments_for_epoch(effective_epoch); + if let Some(recompiled) = load_program_with_pubkey( + callbacks, + &environments_for_epoch, + &key, + self.slot, + false, + ) { + recompiled.tx_usage_counter.fetch_add( + program_to_recompile + .tx_usage_counter + .load(Ordering::Relaxed), + Ordering::Relaxed, + ); + recompiled.ix_usage_counter.fetch_add( + program_to_recompile + .ix_usage_counter + .load(Ordering::Relaxed), + Ordering::Relaxed, + ); + let mut program_cache = self.program_cache.write().unwrap(); + program_cache.assign_program(key, recompiled); + } + } + } else if self.epoch != program_cache.latest_root_epoch + || slot_index.saturating_add(slots_in_recompilation_phase) >= slots_in_epoch + { + // Anticipate the upcoming program runtime environment for the next epoch, + // so we can try to recompile loaded programs before the feature transition hits. + drop(program_cache); + let mut program_cache = self.program_cache.write().unwrap(); + let program_runtime_environment_v1 = create_program_runtime_environment_v1( + upcoming_feature_set, + compute_budget, + false, /* deployment */ + false, /* debugging_features */ + ) + .unwrap(); + let program_runtime_environment_v2 = create_program_runtime_environment_v2( + compute_budget, + false, /* debugging_features */ + ); + let mut upcoming_environments = program_cache.environments.clone(); + let changed_program_runtime_v1 = + *upcoming_environments.program_runtime_v1 != program_runtime_environment_v1; + let changed_program_runtime_v2 = + *upcoming_environments.program_runtime_v2 != program_runtime_environment_v2; + if changed_program_runtime_v1 { + upcoming_environments.program_runtime_v1 = Arc::new(program_runtime_environment_v1); + } + if changed_program_runtime_v2 { + upcoming_environments.program_runtime_v2 = Arc::new(program_runtime_environment_v2); + } + program_cache.upcoming_environments = Some(upcoming_environments); + program_cache.programs_to_recompile = program_cache + .get_flattened_entries(changed_program_runtime_v1, changed_program_runtime_v2); + program_cache + .programs_to_recompile + .sort_by_cached_key(|(_id, program)| program.decayed_usage_counter(self.slot)); + } + } + + /// Execute a transaction using the provided loaded accounts and update + /// the executors cache if the transaction was successful. + #[allow(clippy::too_many_arguments)] + fn execute_loaded_transaction( + &self, + tx: &SanitizedTransaction, + loaded_transaction: &mut LoadedTransaction, + execute_timings: &mut ExecuteTimings, + error_metrics: &mut TransactionErrorMetrics, + program_cache_for_tx_batch: &mut ProgramCacheForTxBatch, + environment: &TransactionProcessingEnvironment, + config: &TransactionProcessingConfig, + ) -> TransactionExecutionResult { + let transaction_accounts = std::mem::take(&mut loaded_transaction.accounts); + + fn transaction_accounts_lamports_sum( + accounts: &[(Pubkey, AccountSharedData)], + message: &SanitizedMessage, + ) -> Option { + let mut lamports_sum = 0u128; + for i in 0..message.account_keys().len() { + let (_, account) = accounts.get(i)?; + lamports_sum = lamports_sum.checked_add(u128::from(account.lamports()))?; + } + Some(lamports_sum) + } + + let rent = environment + .rent_collector + .map(|rent_collector| rent_collector.rent.clone()) + .unwrap_or_default(); + + let lamports_before_tx = + transaction_accounts_lamports_sum(&transaction_accounts, tx.message()).unwrap_or(0); + + let compute_budget = config + .compute_budget + .unwrap_or_else(|| ComputeBudget::from(loaded_transaction.compute_budget_limits)); + + let mut transaction_context = TransactionContext::new( + transaction_accounts, + rent.clone(), + compute_budget.max_instruction_stack_depth, + compute_budget.max_instruction_trace_length, + ); + #[cfg(debug_assertions)] + transaction_context.set_signature(tx.signature()); + + let pre_account_state_info = + TransactionAccountStateInfo::new(&rent, &transaction_context, tx.message()); + + let log_collector = if config.recording_config.enable_log_recording { + match config.log_messages_bytes_limit { + None => Some(LogCollector::new_ref()), + Some(log_messages_bytes_limit) => Some(LogCollector::new_ref_with_limit(Some( + log_messages_bytes_limit, + ))), + } + } else { + None + }; + + let blockhash = environment.blockhash; + let lamports_per_signature = environment.lamports_per_signature; + + let mut executed_units = 0u64; + let sysvar_cache = &self.sysvar_cache.read().unwrap(); + + let mut invoke_context = InvokeContext::new( + &mut transaction_context, + program_cache_for_tx_batch, + EnvironmentConfig::new( + blockhash, + environment.epoch_total_stake, + environment.epoch_vote_accounts, + Arc::clone(&environment.feature_set), + lamports_per_signature, + sysvar_cache, + ), + log_collector.clone(), + compute_budget, + ); + + let mut process_message_time = Measure::start("process_message_time"); + let process_result = MessageProcessor::process_message( + tx.message(), + &loaded_transaction.program_indices, + &mut invoke_context, + execute_timings, + &mut executed_units, + ); + process_message_time.stop(); + + drop(invoke_context); + + saturating_add_assign!( + execute_timings.execute_accessories.process_message_us, + process_message_time.as_us() + ); + + let mut status = process_result + .and_then(|info| { + let post_account_state_info = + TransactionAccountStateInfo::new(&rent, &transaction_context, tx.message()); + TransactionAccountStateInfo::verify_changes( + &pre_account_state_info, + &post_account_state_info, + &transaction_context, + ) + .map(|_| info) + }) + .map_err(|err| { + match err { + TransactionError::InvalidRentPayingAccount + | TransactionError::InsufficientFundsForRent { .. } => { + error_metrics.invalid_rent_paying_account += 1; + } + TransactionError::InvalidAccountIndex => { + error_metrics.invalid_account_index += 1; + } + _ => { + error_metrics.instruction_error += 1; + } + } + err + }); + + let log_messages: Option = + log_collector.and_then(|log_collector| { + Rc::try_unwrap(log_collector) + .map(|log_collector| log_collector.into_inner().into_messages()) + .ok() + }); + + let inner_instructions = if config.recording_config.enable_cpi_recording { + Some(Self::inner_instructions_list_from_instruction_trace( + &transaction_context, + )) + } else { + None + }; + + let ExecutionRecord { + accounts, + return_data, + touched_account_count, + accounts_resize_delta: accounts_data_len_delta, + } = transaction_context.into(); + + if status.is_ok() + && transaction_accounts_lamports_sum(&accounts, tx.message()) + .filter(|lamports_after_tx| lamports_before_tx == *lamports_after_tx) + .is_none() + { + status = Err(TransactionError::UnbalancedTransaction); + } + let status = status.map(|_| ()); + + loaded_transaction.accounts = accounts; + saturating_add_assign!( + execute_timings.details.total_account_count, + loaded_transaction.accounts.len() as u64 + ); + saturating_add_assign!( + execute_timings.details.changed_account_count, + touched_account_count + ); + + let return_data = if config.recording_config.enable_return_data_recording + && !return_data.data.is_empty() + { + Some(return_data) + } else { + None + }; + + TransactionExecutionResult::Executed { + details: TransactionExecutionDetails { + status, + log_messages, + inner_instructions, + fee_details: loaded_transaction.fee_details, + return_data, + executed_units, + accounts_data_len_delta, + }, + programs_modified_by_tx: program_cache_for_tx_batch.drain_modified_entries(), + } + } + + /// Extract the InnerInstructionsList from a TransactionContext + fn inner_instructions_list_from_instruction_trace( + transaction_context: &TransactionContext, + ) -> InnerInstructionsList { + debug_assert!(transaction_context + .get_instruction_context_at_index_in_trace(0) + .map(|instruction_context| instruction_context.get_stack_height() + == TRANSACTION_LEVEL_STACK_HEIGHT) + .unwrap_or(true)); + let mut outer_instructions = Vec::new(); + for index_in_trace in 0..transaction_context.get_instruction_trace_length() { + if let Ok(instruction_context) = + transaction_context.get_instruction_context_at_index_in_trace(index_in_trace) + { + let stack_height = instruction_context.get_stack_height(); + if stack_height == TRANSACTION_LEVEL_STACK_HEIGHT { + outer_instructions.push(Vec::new()); + } else if let Some(inner_instructions) = outer_instructions.last_mut() { + let stack_height = u8::try_from(stack_height).unwrap_or(u8::MAX); + let instruction = CompiledInstruction::new_from_raw_parts( + instruction_context + .get_index_of_program_account_in_transaction( + instruction_context + .get_number_of_program_accounts() + .saturating_sub(1), + ) + .unwrap_or_default() as u8, + instruction_context.get_instruction_data().to_vec(), + (0..instruction_context.get_number_of_instruction_accounts()) + .map(|instruction_account_index| { + instruction_context + .get_index_of_instruction_account_in_transaction( + instruction_account_index, + ) + .unwrap_or_default() as u8 + }) + .collect(), + ); + inner_instructions.push(InnerInstruction { + instruction, + stack_height, + }); + } else { + debug_assert!(false); + } + } else { + debug_assert!(false); + } + } + outer_instructions + } + + pub fn fill_missing_sysvar_cache_entries( + &self, + callbacks: &CB, + ) { + let mut sysvar_cache = self.sysvar_cache.write().unwrap(); + sysvar_cache.fill_missing_entries(|pubkey, set_sysvar| { + if let Some(account) = callbacks.get_account_shared_data(pubkey) { + set_sysvar(account.data()); + } + }); + } + + pub fn reset_sysvar_cache(&self) { + let mut sysvar_cache = self.sysvar_cache.write().unwrap(); + sysvar_cache.reset(); + } + + pub fn get_sysvar_cache_for_tests(&self) -> SysvarCache { + self.sysvar_cache.read().unwrap().clone() + } + + /// Add a built-in program + pub fn add_builtin( + &self, + callbacks: &CB, + program_id: Pubkey, + name: &str, + builtin: ProgramCacheEntry, + ) { + debug!("Adding program {} under {:?}", name, program_id); + callbacks.add_builtin_account(name, &program_id); + self.builtin_program_ids.write().unwrap().insert(program_id); + self.program_cache + .write() + .unwrap() + .assign_program(program_id, Arc::new(builtin)); + debug!("Added program {} under {:?}", name, program_id); + } +} + +#[cfg(test)] +mod tests { + use { + super::*, + crate::{ + account_loader::ValidatedTransactionDetails, nonce_info::NoncePartial, + rollback_accounts::RollbackAccounts, + }, + solana_compute_budget::compute_budget_processor::ComputeBudgetLimits, + solana_program_runtime::loaded_programs::{BlockRelation, ProgramCacheEntryType}, + solana_sdk::{ + account::{create_account_shared_data_for_test, WritableAccount}, + bpf_loader, + bpf_loader_upgradeable::{self, UpgradeableLoaderState}, + compute_budget::ComputeBudgetInstruction, + epoch_schedule::EpochSchedule, + feature_set::FeatureSet, + fee::FeeDetails, + fee_calculator::FeeCalculator, + hash::Hash, + message::{LegacyMessage, Message, MessageHeader}, + nonce, + rent_collector::{RentCollector, RENT_EXEMPT_RENT_EPOCH}, + rent_debits::RentDebits, + reserved_account_keys::ReservedAccountKeys, + signature::{Keypair, Signature}, + system_program, + sysvar::{self, rent::Rent}, + transaction::{SanitizedTransaction, Transaction, TransactionError}, + transaction_context::TransactionContext, + }, + std::{ + env, + fs::{self, File}, + io::Read, + thread, + }, + }; + + fn new_unchecked_sanitized_message(message: Message) -> SanitizedMessage { + SanitizedMessage::Legacy(LegacyMessage::new( + message, + &ReservedAccountKeys::empty_key_set(), + )) + } + + struct TestForkGraph {} + + impl ForkGraph for TestForkGraph { + fn relationship(&self, _a: Slot, _b: Slot) -> BlockRelation { + BlockRelation::Unknown + } + } + + #[derive(Default, Clone)] + pub struct MockBankCallback { + pub account_shared_data: Arc>>, + } + + impl TransactionProcessingCallback for MockBankCallback { + fn account_matches_owners(&self, account: &Pubkey, owners: &[Pubkey]) -> Option { + if let Some(data) = self.account_shared_data.read().unwrap().get(account) { + if data.lamports() == 0 { + None + } else { + owners.iter().position(|entry| data.owner() == entry) + } + } else { + None + } + } + + fn get_account_shared_data(&self, pubkey: &Pubkey) -> Option { + self.account_shared_data + .read() + .unwrap() + .get(pubkey) + .cloned() + } + + fn add_builtin_account(&self, name: &str, program_id: &Pubkey) { + let mut account_data = AccountSharedData::default(); + account_data.set_data(name.as_bytes().to_vec()); + self.account_shared_data + .write() + .unwrap() + .insert(*program_id, account_data); + } + } + + #[test] + fn test_inner_instructions_list_from_instruction_trace() { + let instruction_trace = [1, 2, 1, 1, 2, 3, 2]; + let mut transaction_context = + TransactionContext::new(vec![], Rent::default(), 3, instruction_trace.len()); + for (index_in_trace, stack_height) in instruction_trace.into_iter().enumerate() { + while stack_height <= transaction_context.get_instruction_context_stack_height() { + transaction_context.pop().unwrap(); + } + if stack_height > transaction_context.get_instruction_context_stack_height() { + transaction_context + .get_next_instruction_context() + .unwrap() + .configure(&[], &[], &[index_in_trace as u8]); + transaction_context.push().unwrap(); + } + } + let inner_instructions = + TransactionBatchProcessor::::inner_instructions_list_from_instruction_trace( + &transaction_context, + ); + + assert_eq!( + inner_instructions, + vec![ + vec![InnerInstruction { + instruction: CompiledInstruction::new_from_raw_parts(0, vec![1], vec![]), + stack_height: 2, + }], + vec![], + vec![ + InnerInstruction { + instruction: CompiledInstruction::new_from_raw_parts(0, vec![4], vec![]), + stack_height: 2, + }, + InnerInstruction { + instruction: CompiledInstruction::new_from_raw_parts(0, vec![5], vec![]), + stack_height: 3, + }, + InnerInstruction { + instruction: CompiledInstruction::new_from_raw_parts(0, vec![6], vec![]), + stack_height: 2, + }, + ] + ] + ); + } + + #[test] + fn test_execute_loaded_transaction_recordings() { + // Setting all the arguments correctly is too burdensome for testing + // execute_loaded_transaction separately.This function will be tested in an integration + // test with load_and_execute_sanitized_transactions + let message = Message { + account_keys: vec![Pubkey::new_from_array([0; 32])], + header: MessageHeader::default(), + instructions: vec![CompiledInstruction { + program_id_index: 0, + accounts: vec![], + data: vec![], + }], + recent_blockhash: Hash::default(), + }; + + let sanitized_message = new_unchecked_sanitized_message(message); + let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default(); + let batch_processor = TransactionBatchProcessor::::default(); + + let sanitized_transaction = SanitizedTransaction::new_for_tests( + sanitized_message, + vec![Signature::new_unique()], + false, + ); + + let mut loaded_transaction = LoadedTransaction { + accounts: vec![(Pubkey::new_unique(), AccountSharedData::default())], + program_indices: vec![vec![0]], + fee_details: FeeDetails::default(), + rollback_accounts: RollbackAccounts::default(), + compute_budget_limits: ComputeBudgetLimits::default(), + rent: 0, + rent_debits: RentDebits::default(), + loaded_accounts_data_size: 32, + }; + + let processing_environment = TransactionProcessingEnvironment::default(); + + let mut processing_config = TransactionProcessingConfig::default(); + processing_config.recording_config.enable_log_recording = true; + + let result = batch_processor.execute_loaded_transaction( + &sanitized_transaction, + &mut loaded_transaction, + &mut ExecuteTimings::default(), + &mut TransactionErrorMetrics::default(), + &mut program_cache_for_tx_batch, + &processing_environment, + &processing_config, + ); + + let TransactionExecutionResult::Executed { + details: TransactionExecutionDetails { log_messages, .. }, + .. + } = result + else { + panic!("Unexpected result") + }; + assert!(log_messages.is_some()); + + processing_config.log_messages_bytes_limit = Some(2); + + let result = batch_processor.execute_loaded_transaction( + &sanitized_transaction, + &mut loaded_transaction, + &mut ExecuteTimings::default(), + &mut TransactionErrorMetrics::default(), + &mut program_cache_for_tx_batch, + &processing_environment, + &processing_config, + ); + + let TransactionExecutionResult::Executed { + details: + TransactionExecutionDetails { + log_messages, + inner_instructions, + .. + }, + .. + } = result + else { + panic!("Unexpected result") + }; + assert!(log_messages.is_some()); + assert!(inner_instructions.is_none()); + + processing_config.recording_config.enable_log_recording = false; + processing_config.recording_config.enable_cpi_recording = true; + processing_config.log_messages_bytes_limit = None; + + let result = batch_processor.execute_loaded_transaction( + &sanitized_transaction, + &mut loaded_transaction, + &mut ExecuteTimings::default(), + &mut TransactionErrorMetrics::default(), + &mut program_cache_for_tx_batch, + &processing_environment, + &processing_config, + ); + + let TransactionExecutionResult::Executed { + details: + TransactionExecutionDetails { + log_messages, + inner_instructions, + .. + }, + .. + } = result + else { + panic!("Unexpected result") + }; + assert!(log_messages.is_none()); + assert!(inner_instructions.is_some()); + } + + #[test] + fn test_execute_loaded_transaction_error_metrics() { + // Setting all the arguments correctly is too burdensome for testing + // execute_loaded_transaction separately.This function will be tested in an integration + // test with load_and_execute_sanitized_transactions + let key1 = Pubkey::new_unique(); + let key2 = Pubkey::new_unique(); + let message = Message { + account_keys: vec![key1, key2], + header: MessageHeader::default(), + instructions: vec![CompiledInstruction { + program_id_index: 0, + accounts: vec![2], + data: vec![], + }], + recent_blockhash: Hash::default(), + }; + + let sanitized_message = new_unchecked_sanitized_message(message); + let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default(); + let batch_processor = TransactionBatchProcessor::::default(); + + let sanitized_transaction = SanitizedTransaction::new_for_tests( + sanitized_message, + vec![Signature::new_unique()], + false, + ); + + let mut account_data = AccountSharedData::default(); + account_data.set_owner(bpf_loader::id()); + let mut loaded_transaction = LoadedTransaction { + accounts: vec![ + (key1, AccountSharedData::default()), + (key2, AccountSharedData::default()), + ], + program_indices: vec![vec![0]], + fee_details: FeeDetails::default(), + rollback_accounts: RollbackAccounts::default(), + compute_budget_limits: ComputeBudgetLimits::default(), + rent: 0, + rent_debits: RentDebits::default(), + loaded_accounts_data_size: 0, + }; + + let processing_config = TransactionProcessingConfig { + recording_config: ExecutionRecordingConfig::new_single_setting(false), + ..Default::default() + }; + let mut error_metrics = TransactionErrorMetrics::new(); + + let _ = batch_processor.execute_loaded_transaction( + &sanitized_transaction, + &mut loaded_transaction, + &mut ExecuteTimings::default(), + &mut error_metrics, + &mut program_cache_for_tx_batch, + &TransactionProcessingEnvironment::default(), + &processing_config, + ); + + assert_eq!(error_metrics.instruction_error, 1); + } + + #[test] + #[should_panic = "called load_program_with_pubkey() with nonexistent account"] + fn test_replenish_program_cache_with_nonexistent_accounts() { + let mock_bank = MockBankCallback::default(); + let batch_processor = TransactionBatchProcessor::::default(); + let fork_graph = Arc::new(RwLock::new(TestForkGraph {})); + batch_processor.program_cache.write().unwrap().fork_graph = + Some(Arc::downgrade(&fork_graph)); + let key = Pubkey::new_unique(); + + let mut account_maps: HashMap = HashMap::new(); + account_maps.insert(key, 4); + + batch_processor.replenish_program_cache(&mock_bank, &account_maps, false, true); + } + + #[test] + fn test_replenish_program_cache() { + let mock_bank = MockBankCallback::default(); + let batch_processor = TransactionBatchProcessor::::default(); + let fork_graph = Arc::new(RwLock::new(TestForkGraph {})); + batch_processor.program_cache.write().unwrap().fork_graph = + Some(Arc::downgrade(&fork_graph)); + let key = Pubkey::new_unique(); + + let mut account_data = AccountSharedData::default(); + account_data.set_owner(bpf_loader::id()); + mock_bank + .account_shared_data + .write() + .unwrap() + .insert(key, account_data); + + let mut account_maps: HashMap = HashMap::new(); + account_maps.insert(key, 4); + + for limit_to_load_programs in [false, true] { + let result = batch_processor.replenish_program_cache( + &mock_bank, + &account_maps, + false, + limit_to_load_programs, + ); + assert!(!result.hit_max_limit); + let program = result.find(&key).unwrap(); + assert!(matches!( + program.program, + ProgramCacheEntryType::FailedVerification(_) + )); + } + } + + #[test] + fn test_filter_executable_program_accounts() { + let mock_bank = MockBankCallback::default(); + let key1 = Pubkey::new_unique(); + let owner1 = Pubkey::new_unique(); + + let mut data = AccountSharedData::default(); + data.set_owner(owner1); + data.set_lamports(93); + mock_bank + .account_shared_data + .write() + .unwrap() + .insert(key1, data); + + let message = Message { + account_keys: vec![key1], + header: MessageHeader::default(), + instructions: vec![CompiledInstruction { + program_id_index: 0, + accounts: vec![], + data: vec![], + }], + recent_blockhash: Hash::default(), + }; + + let sanitized_message = new_unchecked_sanitized_message(message); + + let sanitized_transaction_1 = SanitizedTransaction::new_for_tests( + sanitized_message, + vec![Signature::new_unique()], + false, + ); + + let key2 = Pubkey::new_unique(); + let owner2 = Pubkey::new_unique(); + + let mut account_data = AccountSharedData::default(); + account_data.set_owner(owner2); + account_data.set_lamports(90); + mock_bank + .account_shared_data + .write() + .unwrap() + .insert(key2, account_data); + + let message = Message { + account_keys: vec![key1, key2], + header: MessageHeader::default(), + instructions: vec![CompiledInstruction { + program_id_index: 0, + accounts: vec![], + data: vec![], + }], + recent_blockhash: Hash::default(), + }; + + let sanitized_message = new_unchecked_sanitized_message(message); + + let sanitized_transaction_2 = SanitizedTransaction::new_for_tests( + sanitized_message, + vec![Signature::new_unique()], + false, + ); + + let transactions = vec![ + sanitized_transaction_1.clone(), + sanitized_transaction_2.clone(), + sanitized_transaction_1, + ]; + let validation_results = vec![ + Ok(ValidatedTransactionDetails::default()), + Ok(ValidatedTransactionDetails::default()), + Err(TransactionError::ProgramAccountNotFound), + ]; + let owners = vec![owner1, owner2]; + + let result = TransactionBatchProcessor::::filter_executable_program_accounts( + &mock_bank, + &transactions, + &validation_results, + &owners, + ); + + assert_eq!(result.len(), 2); + assert_eq!(result[&key1], 2); + assert_eq!(result[&key2], 1); + } + + #[test] + fn test_filter_executable_program_accounts_no_errors() { + let keypair1 = Keypair::new(); + let keypair2 = Keypair::new(); + + let non_program_pubkey1 = Pubkey::new_unique(); + let non_program_pubkey2 = Pubkey::new_unique(); + let program1_pubkey = Pubkey::new_unique(); + let program2_pubkey = Pubkey::new_unique(); + let account1_pubkey = Pubkey::new_unique(); + let account2_pubkey = Pubkey::new_unique(); + let account3_pubkey = Pubkey::new_unique(); + let account4_pubkey = Pubkey::new_unique(); + + let account5_pubkey = Pubkey::new_unique(); + + let bank = MockBankCallback::default(); + bank.account_shared_data.write().unwrap().insert( + non_program_pubkey1, + AccountSharedData::new(1, 10, &account5_pubkey), + ); + bank.account_shared_data.write().unwrap().insert( + non_program_pubkey2, + AccountSharedData::new(1, 10, &account5_pubkey), + ); + bank.account_shared_data.write().unwrap().insert( + program1_pubkey, + AccountSharedData::new(40, 1, &account5_pubkey), + ); + bank.account_shared_data.write().unwrap().insert( + program2_pubkey, + AccountSharedData::new(40, 1, &account5_pubkey), + ); + bank.account_shared_data.write().unwrap().insert( + account1_pubkey, + AccountSharedData::new(1, 10, &non_program_pubkey1), + ); + bank.account_shared_data.write().unwrap().insert( + account2_pubkey, + AccountSharedData::new(1, 10, &non_program_pubkey2), + ); + bank.account_shared_data.write().unwrap().insert( + account3_pubkey, + AccountSharedData::new(40, 1, &program1_pubkey), + ); + bank.account_shared_data.write().unwrap().insert( + account4_pubkey, + AccountSharedData::new(40, 1, &program2_pubkey), + ); + + let tx1 = Transaction::new_with_compiled_instructions( + &[&keypair1], + &[non_program_pubkey1], + Hash::new_unique(), + vec![account1_pubkey, account2_pubkey, account3_pubkey], + vec![CompiledInstruction::new(1, &(), vec![0])], + ); + let sanitized_tx1 = SanitizedTransaction::from_transaction_for_tests(tx1); + + let tx2 = Transaction::new_with_compiled_instructions( + &[&keypair2], + &[non_program_pubkey2], + Hash::new_unique(), + vec![account4_pubkey, account3_pubkey, account2_pubkey], + vec![CompiledInstruction::new(1, &(), vec![0])], + ); + let sanitized_tx2 = SanitizedTransaction::from_transaction_for_tests(tx2); + + let owners = &[program1_pubkey, program2_pubkey]; + let programs = + TransactionBatchProcessor::::filter_executable_program_accounts( + &bank, + &[sanitized_tx1, sanitized_tx2], + &[ + Ok(ValidatedTransactionDetails::default()), + Ok(ValidatedTransactionDetails::default()), + ], + owners, + ); + + // The result should contain only account3_pubkey, and account4_pubkey as the program accounts + assert_eq!(programs.len(), 2); + assert_eq!( + programs + .get(&account3_pubkey) + .expect("failed to find the program account"), + &2 + ); + assert_eq!( + programs + .get(&account4_pubkey) + .expect("failed to find the program account"), + &1 + ); + } + + #[test] + fn test_filter_executable_program_accounts_invalid_blockhash() { + let keypair1 = Keypair::new(); + let keypair2 = Keypair::new(); + + let non_program_pubkey1 = Pubkey::new_unique(); + let non_program_pubkey2 = Pubkey::new_unique(); + let program1_pubkey = Pubkey::new_unique(); + let program2_pubkey = Pubkey::new_unique(); + let account1_pubkey = Pubkey::new_unique(); + let account2_pubkey = Pubkey::new_unique(); + let account3_pubkey = Pubkey::new_unique(); + let account4_pubkey = Pubkey::new_unique(); + + let account5_pubkey = Pubkey::new_unique(); + + let bank = MockBankCallback::default(); + bank.account_shared_data.write().unwrap().insert( + non_program_pubkey1, + AccountSharedData::new(1, 10, &account5_pubkey), + ); + bank.account_shared_data.write().unwrap().insert( + non_program_pubkey2, + AccountSharedData::new(1, 10, &account5_pubkey), + ); + bank.account_shared_data.write().unwrap().insert( + program1_pubkey, + AccountSharedData::new(40, 1, &account5_pubkey), + ); + bank.account_shared_data.write().unwrap().insert( + program2_pubkey, + AccountSharedData::new(40, 1, &account5_pubkey), + ); + bank.account_shared_data.write().unwrap().insert( + account1_pubkey, + AccountSharedData::new(1, 10, &non_program_pubkey1), + ); + bank.account_shared_data.write().unwrap().insert( + account2_pubkey, + AccountSharedData::new(1, 10, &non_program_pubkey2), + ); + bank.account_shared_data.write().unwrap().insert( + account3_pubkey, + AccountSharedData::new(40, 1, &program1_pubkey), + ); + bank.account_shared_data.write().unwrap().insert( + account4_pubkey, + AccountSharedData::new(40, 1, &program2_pubkey), + ); + + let tx1 = Transaction::new_with_compiled_instructions( + &[&keypair1], + &[non_program_pubkey1], + Hash::new_unique(), + vec![account1_pubkey, account2_pubkey, account3_pubkey], + vec![CompiledInstruction::new(1, &(), vec![0])], + ); + let sanitized_tx1 = SanitizedTransaction::from_transaction_for_tests(tx1); + + let tx2 = Transaction::new_with_compiled_instructions( + &[&keypair2], + &[non_program_pubkey2], + Hash::new_unique(), + vec![account4_pubkey, account3_pubkey, account2_pubkey], + vec![CompiledInstruction::new(1, &(), vec![0])], + ); + // Let's not register blockhash from tx2. This should cause the tx2 to fail + let sanitized_tx2 = SanitizedTransaction::from_transaction_for_tests(tx2); + + let owners = &[program1_pubkey, program2_pubkey]; + let validation_results = vec![ + Ok(ValidatedTransactionDetails::default()), + Err(TransactionError::BlockhashNotFound), + ]; + let programs = + TransactionBatchProcessor::::filter_executable_program_accounts( + &bank, + &[sanitized_tx1, sanitized_tx2], + &validation_results, + owners, + ); + + // The result should contain only account3_pubkey as the program accounts + assert_eq!(programs.len(), 1); + assert_eq!( + programs + .get(&account3_pubkey) + .expect("failed to find the program account"), + &1 + ); + } + + #[test] + #[allow(deprecated)] + fn test_sysvar_cache_initialization1() { + let mock_bank = MockBankCallback::default(); + + let clock = sysvar::clock::Clock { + slot: 1, + epoch_start_timestamp: 2, + epoch: 3, + leader_schedule_epoch: 4, + unix_timestamp: 5, + }; + let clock_account = create_account_shared_data_for_test(&clock); + mock_bank + .account_shared_data + .write() + .unwrap() + .insert(sysvar::clock::id(), clock_account); + + let epoch_schedule = EpochSchedule::custom(64, 2, true); + let epoch_schedule_account = create_account_shared_data_for_test(&epoch_schedule); + mock_bank + .account_shared_data + .write() + .unwrap() + .insert(sysvar::epoch_schedule::id(), epoch_schedule_account); + + let fees = sysvar::fees::Fees { + fee_calculator: FeeCalculator { + lamports_per_signature: 123, + }, + }; + let fees_account = create_account_shared_data_for_test(&fees); + mock_bank + .account_shared_data + .write() + .unwrap() + .insert(sysvar::fees::id(), fees_account); + + let rent = Rent::with_slots_per_epoch(2048); + let rent_account = create_account_shared_data_for_test(&rent); + mock_bank + .account_shared_data + .write() + .unwrap() + .insert(sysvar::rent::id(), rent_account); + + let transaction_processor = TransactionBatchProcessor::::default(); + transaction_processor.fill_missing_sysvar_cache_entries(&mock_bank); + + let sysvar_cache = transaction_processor.sysvar_cache.read().unwrap(); + let cached_clock = sysvar_cache.get_clock(); + let cached_epoch_schedule = sysvar_cache.get_epoch_schedule(); + let cached_fees = sysvar_cache.get_fees(); + let cached_rent = sysvar_cache.get_rent(); + + assert_eq!( + cached_clock.expect("clock sysvar missing in cache"), + clock.into() + ); + assert_eq!( + cached_epoch_schedule.expect("epoch_schedule sysvar missing in cache"), + epoch_schedule.into() + ); + assert_eq!( + cached_fees.expect("fees sysvar missing in cache"), + fees.into() + ); + assert_eq!( + cached_rent.expect("rent sysvar missing in cache"), + rent.into() + ); + assert!(sysvar_cache.get_slot_hashes().is_err()); + assert!(sysvar_cache.get_epoch_rewards().is_err()); + } + + #[test] + #[allow(deprecated)] + fn test_reset_and_fill_sysvar_cache() { + let mock_bank = MockBankCallback::default(); + + let clock = sysvar::clock::Clock { + slot: 1, + epoch_start_timestamp: 2, + epoch: 3, + leader_schedule_epoch: 4, + unix_timestamp: 5, + }; + let clock_account = create_account_shared_data_for_test(&clock); + mock_bank + .account_shared_data + .write() + .unwrap() + .insert(sysvar::clock::id(), clock_account); + + let epoch_schedule = EpochSchedule::custom(64, 2, true); + let epoch_schedule_account = create_account_shared_data_for_test(&epoch_schedule); + mock_bank + .account_shared_data + .write() + .unwrap() + .insert(sysvar::epoch_schedule::id(), epoch_schedule_account); + + let fees = sysvar::fees::Fees { + fee_calculator: FeeCalculator { + lamports_per_signature: 123, + }, + }; + let fees_account = create_account_shared_data_for_test(&fees); + mock_bank + .account_shared_data + .write() + .unwrap() + .insert(sysvar::fees::id(), fees_account); + + let rent = Rent::with_slots_per_epoch(2048); + let rent_account = create_account_shared_data_for_test(&rent); + mock_bank + .account_shared_data + .write() + .unwrap() + .insert(sysvar::rent::id(), rent_account); + + let transaction_processor = TransactionBatchProcessor::::default(); + // Fill the sysvar cache + transaction_processor.fill_missing_sysvar_cache_entries(&mock_bank); + // Reset the sysvar cache + transaction_processor.reset_sysvar_cache(); + + { + let sysvar_cache = transaction_processor.sysvar_cache.read().unwrap(); + // Test that sysvar cache is empty and none of the values are found + assert!(sysvar_cache.get_clock().is_err()); + assert!(sysvar_cache.get_epoch_schedule().is_err()); + assert!(sysvar_cache.get_fees().is_err()); + assert!(sysvar_cache.get_epoch_rewards().is_err()); + assert!(sysvar_cache.get_rent().is_err()); + assert!(sysvar_cache.get_epoch_rewards().is_err()); + } + + // Refill the cache and test the values are available. + transaction_processor.fill_missing_sysvar_cache_entries(&mock_bank); + + let sysvar_cache = transaction_processor.sysvar_cache.read().unwrap(); + let cached_clock = sysvar_cache.get_clock(); + let cached_epoch_schedule = sysvar_cache.get_epoch_schedule(); + let cached_fees = sysvar_cache.get_fees(); + let cached_rent = sysvar_cache.get_rent(); + + assert_eq!( + cached_clock.expect("clock sysvar missing in cache"), + clock.into() + ); + assert_eq!( + cached_epoch_schedule.expect("epoch_schedule sysvar missing in cache"), + epoch_schedule.into() + ); + assert_eq!( + cached_fees.expect("fees sysvar missing in cache"), + fees.into() + ); + assert_eq!( + cached_rent.expect("rent sysvar missing in cache"), + rent.into() + ); + assert!(sysvar_cache.get_slot_hashes().is_err()); + assert!(sysvar_cache.get_epoch_rewards().is_err()); + } + + #[test] + fn test_add_builtin() { + let mock_bank = MockBankCallback::default(); + let batch_processor = TransactionBatchProcessor::::default(); + let fork_graph = Arc::new(RwLock::new(TestForkGraph {})); + batch_processor.program_cache.write().unwrap().fork_graph = + Some(Arc::downgrade(&fork_graph)); + + let key = Pubkey::new_unique(); + let name = "a_builtin_name"; + let program = ProgramCacheEntry::new_builtin( + 0, + name.len(), + |_invoke_context, _param0, _param1, _param2, _param3, _param4| {}, + ); + + batch_processor.add_builtin(&mock_bank, key, name, program); + + assert_eq!( + mock_bank.account_shared_data.read().unwrap()[&key].data(), + name.as_bytes() + ); + + let mut loaded_programs_for_tx_batch = ProgramCacheForTxBatch::new_from_cache( + 0, + 0, + &batch_processor.program_cache.read().unwrap(), + ); + batch_processor.program_cache.write().unwrap().extract( + &mut vec![(key, (ProgramCacheMatchCriteria::NoCriteria, 1))], + &mut loaded_programs_for_tx_batch, + true, + ); + let entry = loaded_programs_for_tx_batch.find(&key).unwrap(); + + // Repeating code because ProgramCacheEntry does not implement clone. + let program = ProgramCacheEntry::new_builtin( + 0, + name.len(), + |_invoke_context, _param0, _param1, _param2, _param3, _param4| {}, + ); + assert_eq!(entry, Arc::new(program)); + } + + #[test] + fn fast_concur_test() { + let mut mock_bank = MockBankCallback::default(); + let batch_processor = TransactionBatchProcessor::::new(5, 5, HashSet::new()); + let fork_graph = Arc::new(RwLock::new(TestForkGraph {})); + batch_processor.program_cache.write().unwrap().fork_graph = + Some(Arc::downgrade(&fork_graph)); + + let programs = vec![ + deploy_program("hello-solana".to_string(), &mut mock_bank), + deploy_program("simple-transfer".to_string(), &mut mock_bank), + deploy_program("clock-sysvar".to_string(), &mut mock_bank), + ]; + + let account_maps: HashMap = programs + .iter() + .enumerate() + .map(|(idx, key)| (*key, idx as u64)) + .collect(); + + for _ in 0..10 { + let ths: Vec<_> = (0..4) + .map(|_| { + let local_bank = mock_bank.clone(); + let processor = TransactionBatchProcessor::new_from( + &batch_processor, + batch_processor.slot, + batch_processor.epoch, + ); + let maps = account_maps.clone(); + let programs = programs.clone(); + thread::spawn(move || { + let result = + processor.replenish_program_cache(&local_bank, &maps, false, true); + for key in &programs { + let cache_entry = result.find(key); + assert!(matches!( + cache_entry.unwrap().program, + ProgramCacheEntryType::Loaded(_) + )); + } + }) + }) + .collect(); + + for th in ths { + th.join().unwrap(); + } + } + } + + fn deploy_program(name: String, mock_bank: &mut MockBankCallback) -> Pubkey { + let program_account = Pubkey::new_unique(); + let program_data_account = Pubkey::new_unique(); + let state = UpgradeableLoaderState::Program { + programdata_address: program_data_account, + }; + + // The program account must have funds and hold the executable binary + let mut account_data = AccountSharedData::default(); + account_data.set_data(bincode::serialize(&state).unwrap()); + account_data.set_lamports(25); + account_data.set_owner(bpf_loader_upgradeable::id()); + mock_bank + .account_shared_data + .write() + .unwrap() + .insert(program_account, account_data); + + let mut account_data = AccountSharedData::default(); + let state = UpgradeableLoaderState::ProgramData { + slot: 0, + upgrade_authority_address: None, + }; + let mut header = bincode::serialize(&state).unwrap(); + let mut complement = vec![ + 0; + std::cmp::max( + 0, + UpgradeableLoaderState::size_of_programdata_metadata().saturating_sub(header.len()) + ) + ]; + + let mut dir = env::current_dir().unwrap(); + dir.push("tests"); + dir.push("example-programs"); + dir.push(name.as_str()); + let name = name.replace('-', "_"); + dir.push(name + "_program.so"); + let mut file = File::open(dir.clone()).expect("file not found"); + let metadata = fs::metadata(dir).expect("Unable to read metadata"); + let mut buffer = vec![0; metadata.len() as usize]; + file.read_exact(&mut buffer).expect("Buffer overflow"); + + header.append(&mut complement); + header.append(&mut buffer); + account_data.set_data(header); + mock_bank + .account_shared_data + .write() + .unwrap() + .insert(program_data_account, account_data); + + program_account + } + + #[test] + fn test_validate_transaction_fee_payer_exact_balance() { + let lamports_per_signature = 5000; + let message = new_unchecked_sanitized_message(Message::new_with_blockhash( + &[ + ComputeBudgetInstruction::set_compute_unit_limit(2000u32), + ComputeBudgetInstruction::set_compute_unit_price(1_000_000_000), + ], + Some(&Pubkey::new_unique()), + &Hash::new_unique(), + )); + let compute_budget_limits = + process_compute_budget_instructions(message.program_instructions_iter()).unwrap(); + let fee_payer_address = message.fee_payer(); + let current_epoch = 42; + let rent_collector = RentCollector { + epoch: current_epoch, + ..RentCollector::default() + }; + let min_balance = rent_collector.rent.minimum_balance(nonce::State::size()); + let transaction_fee = lamports_per_signature; + let priority_fee = 2_000_000u64; + let starting_balance = transaction_fee + priority_fee; + assert!( + starting_balance > min_balance, + "we're testing that a rent exempt fee payer can be fully drained, \ + so ensure that the starting balance is more than the min balance" + ); + + let fee_payer_rent_epoch = current_epoch; + let fee_payer_rent_debit = 0; + let fee_payer_account = AccountSharedData::new_rent_epoch( + starting_balance, + 0, + &Pubkey::default(), + fee_payer_rent_epoch, + ); + let mut mock_accounts = HashMap::new(); + mock_accounts.insert(*fee_payer_address, fee_payer_account.clone()); + let mock_bank = MockBankCallback { + account_shared_data: Arc::new(RwLock::new(mock_accounts)), + }; + + let mut error_counters = TransactionErrorMetrics::default(); + let batch_processor = TransactionBatchProcessor::::default(); + let result = batch_processor.validate_transaction_fee_payer( + &mock_bank, + &message, + CheckedTransactionDetails { + nonce: None, + lamports_per_signature, + }, + &FeatureSet::default(), + &FeeStructure::default(), + &rent_collector, + &mut error_counters, + ); + + let post_validation_fee_payer_account = { + let mut account = fee_payer_account.clone(); + account.set_rent_epoch(RENT_EXEMPT_RENT_EPOCH); + account.set_lamports(0); + account + }; + + assert_eq!( + result, + Ok(ValidatedTransactionDetails { + rollback_accounts: RollbackAccounts::new( + None, // nonce + *fee_payer_address, + post_validation_fee_payer_account.clone(), + fee_payer_rent_debit, + fee_payer_rent_epoch + ), + compute_budget_limits, + fee_details: FeeDetails::new_for_tests(transaction_fee, priority_fee, false), + fee_payer_rent_debit, + fee_payer_account: post_validation_fee_payer_account, + }) + ); + } + + #[test] + fn test_validate_transaction_fee_payer_rent_paying() { + let lamports_per_signature = 5000; + let message = new_unchecked_sanitized_message(Message::new_with_blockhash( + &[], + Some(&Pubkey::new_unique()), + &Hash::new_unique(), + )); + let compute_budget_limits = + process_compute_budget_instructions(message.program_instructions_iter()).unwrap(); + let fee_payer_address = message.fee_payer(); + let mut rent_collector = RentCollector::default(); + rent_collector.rent.lamports_per_byte_year = 1_000_000; + let min_balance = rent_collector.rent.minimum_balance(0); + let transaction_fee = lamports_per_signature; + let starting_balance = min_balance - 1; + let fee_payer_account = AccountSharedData::new(starting_balance, 0, &Pubkey::default()); + let fee_payer_rent_debit = rent_collector + .get_rent_due( + fee_payer_account.lamports(), + fee_payer_account.data().len(), + fee_payer_account.rent_epoch(), + ) + .lamports(); + assert!(fee_payer_rent_debit > 0); + + let mut mock_accounts = HashMap::new(); + mock_accounts.insert(*fee_payer_address, fee_payer_account.clone()); + let mock_bank = MockBankCallback { + account_shared_data: Arc::new(RwLock::new(mock_accounts)), + }; + + let mut error_counters = TransactionErrorMetrics::default(); + let batch_processor = TransactionBatchProcessor::::default(); + let result = batch_processor.validate_transaction_fee_payer( + &mock_bank, + &message, + CheckedTransactionDetails { + nonce: None, + lamports_per_signature, + }, + &FeatureSet::default(), + &FeeStructure::default(), + &rent_collector, + &mut error_counters, + ); + + let post_validation_fee_payer_account = { + let mut account = fee_payer_account.clone(); + account.set_rent_epoch(1); + account.set_lamports(starting_balance - transaction_fee - fee_payer_rent_debit); + account + }; + + assert_eq!( + result, + Ok(ValidatedTransactionDetails { + rollback_accounts: RollbackAccounts::new( + None, // nonce + *fee_payer_address, + post_validation_fee_payer_account.clone(), + fee_payer_rent_debit, + 0, // rent epoch + ), + compute_budget_limits, + fee_details: FeeDetails::new_for_tests(transaction_fee, 0, false), + fee_payer_rent_debit, + fee_payer_account: post_validation_fee_payer_account, + }) + ); + } + + #[test] + fn test_validate_transaction_fee_payer_not_found() { + let lamports_per_signature = 5000; + let message = + new_unchecked_sanitized_message(Message::new(&[], Some(&Pubkey::new_unique()))); + + let mock_bank = MockBankCallback::default(); + let mut error_counters = TransactionErrorMetrics::default(); + let batch_processor = TransactionBatchProcessor::::default(); + let result = batch_processor.validate_transaction_fee_payer( + &mock_bank, + &message, + CheckedTransactionDetails { + nonce: None, + lamports_per_signature, + }, + &FeatureSet::default(), + &FeeStructure::default(), + &RentCollector::default(), + &mut error_counters, + ); + + assert_eq!(error_counters.account_not_found, 1); + assert_eq!(result, Err(TransactionError::AccountNotFound)); + } + + #[test] + fn test_validate_transaction_fee_payer_insufficient_funds() { + let lamports_per_signature = 5000; + let message = + new_unchecked_sanitized_message(Message::new(&[], Some(&Pubkey::new_unique()))); + let fee_payer_address = message.fee_payer(); + let fee_payer_account = AccountSharedData::new(1, 0, &Pubkey::default()); + let mut mock_accounts = HashMap::new(); + mock_accounts.insert(*fee_payer_address, fee_payer_account.clone()); + let mock_bank = MockBankCallback { + account_shared_data: Arc::new(RwLock::new(mock_accounts)), + }; + + let mut error_counters = TransactionErrorMetrics::default(); + let batch_processor = TransactionBatchProcessor::::default(); + let result = batch_processor.validate_transaction_fee_payer( + &mock_bank, + &message, + CheckedTransactionDetails { + nonce: None, + lamports_per_signature, + }, + &FeatureSet::default(), + &FeeStructure::default(), + &RentCollector::default(), + &mut error_counters, + ); + + assert_eq!(error_counters.insufficient_funds, 1); + assert_eq!(result, Err(TransactionError::InsufficientFundsForFee)); + } + + #[test] + fn test_validate_transaction_fee_payer_insufficient_rent() { + let lamports_per_signature = 5000; + let message = + new_unchecked_sanitized_message(Message::new(&[], Some(&Pubkey::new_unique()))); + let fee_payer_address = message.fee_payer(); + let transaction_fee = lamports_per_signature; + let rent_collector = RentCollector::default(); + let min_balance = rent_collector.rent.minimum_balance(0); + let starting_balance = min_balance + transaction_fee - 1; + let fee_payer_account = AccountSharedData::new(starting_balance, 0, &Pubkey::default()); + let mut mock_accounts = HashMap::new(); + mock_accounts.insert(*fee_payer_address, fee_payer_account.clone()); + let mock_bank = MockBankCallback { + account_shared_data: Arc::new(RwLock::new(mock_accounts)), + }; + + let mut error_counters = TransactionErrorMetrics::default(); + let batch_processor = TransactionBatchProcessor::::default(); + let result = batch_processor.validate_transaction_fee_payer( + &mock_bank, + &message, + CheckedTransactionDetails { + nonce: None, + lamports_per_signature, + }, + &FeatureSet::default(), + &FeeStructure::default(), + &rent_collector, + &mut error_counters, + ); + + assert_eq!( + result, + Err(TransactionError::InsufficientFundsForRent { account_index: 0 }) + ); + } + + #[test] + fn test_validate_transaction_fee_payer_invalid() { + let lamports_per_signature = 5000; + let message = + new_unchecked_sanitized_message(Message::new(&[], Some(&Pubkey::new_unique()))); + let fee_payer_address = message.fee_payer(); + let fee_payer_account = AccountSharedData::new(1_000_000, 0, &Pubkey::new_unique()); + let mut mock_accounts = HashMap::new(); + mock_accounts.insert(*fee_payer_address, fee_payer_account.clone()); + let mock_bank = MockBankCallback { + account_shared_data: Arc::new(RwLock::new(mock_accounts)), + }; + + let mut error_counters = TransactionErrorMetrics::default(); + let batch_processor = TransactionBatchProcessor::::default(); + let result = batch_processor.validate_transaction_fee_payer( + &mock_bank, + &message, + CheckedTransactionDetails { + nonce: None, + lamports_per_signature, + }, + &FeatureSet::default(), + &FeeStructure::default(), + &RentCollector::default(), + &mut error_counters, + ); + + assert_eq!(error_counters.invalid_account_for_fee, 1); + assert_eq!(result, Err(TransactionError::InvalidAccountForFee)); + } + + #[test] + fn test_validate_transaction_fee_payer_invalid_compute_budget() { + let lamports_per_signature = 5000; + let message = new_unchecked_sanitized_message(Message::new( + &[ + ComputeBudgetInstruction::set_compute_unit_limit(2000u32), + ComputeBudgetInstruction::set_compute_unit_limit(42u32), + ], + Some(&Pubkey::new_unique()), + )); + + let mock_bank = MockBankCallback::default(); + let mut error_counters = TransactionErrorMetrics::default(); + let batch_processor = TransactionBatchProcessor::::default(); + let result = batch_processor.validate_transaction_fee_payer( + &mock_bank, + &message, + CheckedTransactionDetails { + nonce: None, + lamports_per_signature, + }, + &FeatureSet::default(), + &FeeStructure::default(), + &RentCollector::default(), + &mut error_counters, + ); + + assert_eq!(error_counters.invalid_compute_budget, 1); + assert_eq!(result, Err(TransactionError::DuplicateInstruction(1u8))); + } + + #[test] + fn test_validate_transaction_fee_payer_is_nonce() { + let feature_set = FeatureSet::default(); + let lamports_per_signature = 5000; + let rent_collector = RentCollector::default(); + let compute_unit_limit = 2 * solana_compute_budget_program::DEFAULT_COMPUTE_UNITS; + let message = new_unchecked_sanitized_message(Message::new_with_blockhash( + &[ + ComputeBudgetInstruction::set_compute_unit_limit(compute_unit_limit as u32), + ComputeBudgetInstruction::set_compute_unit_price(1_000_000), + ], + Some(&Pubkey::new_unique()), + &Hash::new_unique(), + )); + let compute_budget_limits = + process_compute_budget_instructions(message.program_instructions_iter()).unwrap(); + let fee_payer_address = message.fee_payer(); + let min_balance = Rent::default().minimum_balance(nonce::State::size()); + let transaction_fee = lamports_per_signature; + let priority_fee = compute_unit_limit; + + // Sufficient Fees + { + let fee_payer_account = AccountSharedData::new_data( + min_balance + transaction_fee + priority_fee, + &nonce::state::Versions::new(nonce::State::Initialized( + nonce::state::Data::default(), + )), + &system_program::id(), + ) + .unwrap(); + + let mut mock_accounts = HashMap::new(); + mock_accounts.insert(*fee_payer_address, fee_payer_account.clone()); + let mock_bank = MockBankCallback { + account_shared_data: Arc::new(RwLock::new(mock_accounts)), + }; + + let mut error_counters = TransactionErrorMetrics::default(); + let batch_processor = TransactionBatchProcessor::::default(); + let nonce = Some(NoncePartial::new( + *fee_payer_address, + fee_payer_account.clone(), + )); + let result = batch_processor.validate_transaction_fee_payer( + &mock_bank, + &message, + CheckedTransactionDetails { + nonce: nonce.clone(), + lamports_per_signature, + }, + &feature_set, + &FeeStructure::default(), + &rent_collector, + &mut error_counters, + ); + + let post_validation_fee_payer_account = { + let mut account = fee_payer_account.clone(); + account.set_rent_epoch(RENT_EXEMPT_RENT_EPOCH); + account.set_lamports(min_balance); + account + }; + + assert_eq!( + result, + Ok(ValidatedTransactionDetails { + rollback_accounts: RollbackAccounts::new( + nonce, + *fee_payer_address, + post_validation_fee_payer_account.clone(), + 0, // fee_payer_rent_debit + 0, // fee_payer_rent_epoch + ), + compute_budget_limits, + fee_details: FeeDetails::new_for_tests(transaction_fee, priority_fee, false), + fee_payer_rent_debit: 0, // rent due + fee_payer_account: post_validation_fee_payer_account, + }) + ); + } + + // Insufficient Fees + { + let fee_payer_account = AccountSharedData::new_data( + transaction_fee + priority_fee, // no min_balance this time + &nonce::state::Versions::new(nonce::State::Initialized( + nonce::state::Data::default(), + )), + &system_program::id(), + ) + .unwrap(); + + let mut mock_accounts = HashMap::new(); + mock_accounts.insert(*fee_payer_address, fee_payer_account.clone()); + let mock_bank = MockBankCallback { + account_shared_data: Arc::new(RwLock::new(mock_accounts)), + }; + + let mut error_counters = TransactionErrorMetrics::default(); + let batch_processor = TransactionBatchProcessor::::default(); + let result = batch_processor.validate_transaction_fee_payer( + &mock_bank, + &message, + CheckedTransactionDetails { + nonce: None, + lamports_per_signature, + }, + &feature_set, + &FeeStructure::default(), + &rent_collector, + &mut error_counters, + ); + + assert_eq!(error_counters.insufficient_funds, 1); + assert_eq!(result, Err(TransactionError::InsufficientFundsForFee)); + } + } +} diff --git a/svm/tests/conformance.rs b/svm/tests/conformance.rs new file mode 100644 index 00000000000000..85697440f088fa --- /dev/null +++ b/svm/tests/conformance.rs @@ -0,0 +1,609 @@ +use { + crate::{ + mock_bank::{MockBankCallback, MockForkGraph}, + transaction_builder::SanitizedTransactionBuilder, + }, + lazy_static::lazy_static, + prost::Message, + solana_bpf_loader_program::syscalls::create_program_runtime_environment_v1, + solana_compute_budget::compute_budget::ComputeBudget, + solana_log_collector::LogCollector, + solana_program_runtime::{ + invoke_context::{EnvironmentConfig, InvokeContext}, + loaded_programs::{ProgramCacheEntry, ProgramCacheForTxBatch, ProgramRuntimeEnvironments}, + solana_rbpf::{ + program::{BuiltinProgram, FunctionRegistry}, + vm::Config, + }, + timings::ExecuteTimings, + }, + solana_sdk::{ + account::{AccountSharedData, ReadableAccount, WritableAccount}, + bpf_loader_upgradeable, + feature_set::{FeatureSet, FEATURE_NAMES}, + hash::Hash, + instruction::AccountMeta, + message::SanitizedMessage, + pubkey::Pubkey, + rent::Rent, + signature::Signature, + transaction::TransactionError, + transaction_context::{ + ExecutionRecord, IndexOfAccount, InstructionAccount, TransactionAccount, + TransactionContext, + }, + }, + solana_svm::{ + account_loader::CheckedTransactionDetails, + program_loader, + transaction_processing_callback::TransactionProcessingCallback, + transaction_processor::{ + ExecutionRecordingConfig, TransactionBatchProcessor, TransactionProcessingConfig, + TransactionProcessingEnvironment, + }, + }, + solana_svm_conformance::proto::{InstrEffects, InstrFixture}, + std::{ + collections::{HashMap, HashSet}, + env, + ffi::OsString, + fs::{self, File}, + io::Read, + path::PathBuf, + process::Command, + sync::{Arc, RwLock}, + }, +}; + +mod mock_bank; +mod transaction_builder; + +const fn feature_u64(feature: &Pubkey) -> u64 { + let feature_id = feature.to_bytes(); + feature_id[0] as u64 + | (feature_id[1] as u64) << 8 + | (feature_id[2] as u64) << 16 + | (feature_id[3] as u64) << 24 + | (feature_id[4] as u64) << 32 + | (feature_id[5] as u64) << 40 + | (feature_id[6] as u64) << 48 + | (feature_id[7] as u64) << 56 +} + +lazy_static! { + static ref INDEXED_FEATURES: HashMap = { + FEATURE_NAMES + .iter() + .map(|(pubkey, _)| (feature_u64(pubkey), *pubkey)) + .collect() + }; +} + +fn setup() -> PathBuf { + let mut dir = env::current_dir().unwrap(); + dir.push("test-vectors"); + if !dir.exists() { + std::println!("Cloning test-vectors ..."); + Command::new("git") + .args([ + "clone", + "https://github.com/firedancer-io/test-vectors", + dir.as_os_str().to_str().unwrap(), + ]) + .status() + .expect("Failed to download test-vectors"); + + std::println!("Checking out commit 90a8ad069f8a07d2fdad3cf03b3fb486a00fe988"); + Command::new("git") + .current_dir(&dir) + .args(["checkout", "90a8ad069f8a07d2fdad3cf03b3fb486a00fe988"]) + .status() + .expect("Failed to checkout to proper test-vector commit"); + + std::println!("Setup done!"); + } + + dir +} + +fn cleanup() { + let mut dir = env::current_dir().unwrap(); + dir.push("test-vectors"); + + if dir.exists() { + fs::remove_dir_all(dir).expect("Failed to delete test-vectors repository"); + } +} + +#[test] +fn execute_fixtures() { + let mut base_dir = setup(); + base_dir.push("instr"); + base_dir.push("fixtures"); + + // bpf-loader tests + base_dir.push("bpf-loader"); + run_from_folder(&base_dir, &HashSet::new()); + base_dir.pop(); + + // System program tests + base_dir.push("system"); + // These cases hit a debug_assert here: + // https://github.com/anza-xyz/agave/blob/0142c7fa1c46b05d201552102eb91b6d4b10f077/svm/src/transaction_account_state_info.rs#L34 + let run_as_instr = HashSet::from([ + OsString::from("7fcde5cb94e1dc44.bin.fix"), + OsString::from("9f3c001dcd1803fe.bin.fix"), + OsString::from("34ee00c659dc5aa6.bin.fix"), + OsString::from("8fd951ecde987723.bin.fix"), + ]); + run_from_folder(&base_dir, &run_as_instr); + + cleanup(); +} + +fn run_from_folder(base_dir: &PathBuf, run_as_instr: &HashSet) { + for path in std::fs::read_dir(base_dir).unwrap() { + let filename = path.as_ref().unwrap().file_name(); + let mut file = File::open(path.as_ref().unwrap().path()).expect("file not found"); + let mut buffer = Vec::new(); + file.read_to_end(&mut buffer).expect("Failed to read file"); + + let fixture = InstrFixture::decode(buffer.as_slice()).unwrap(); + let execute_as_instr = run_as_instr.contains(&filename); + run_fixture(fixture, filename, execute_as_instr); + } +} + +fn run_fixture(fixture: InstrFixture, filename: OsString, execute_as_instr: bool) { + let input = fixture.input.unwrap(); + let output = fixture.output.as_ref().unwrap(); + + let mut transaction_builder = SanitizedTransactionBuilder::default(); + let program_id = Pubkey::new_from_array(input.program_id.try_into().unwrap()); + let mut accounts: Vec = Vec::with_capacity(input.instr_accounts.len()); + let mut signatures: HashMap = + HashMap::with_capacity(input.instr_accounts.len()); + + for item in input.instr_accounts { + let pubkey = Pubkey::new_from_array( + input.accounts[item.index as usize] + .address + .clone() + .try_into() + .unwrap(), + ); + accounts.push(AccountMeta { + pubkey, + is_signer: item.is_signer, + is_writable: item.is_writable, + }); + + if item.is_signer { + signatures.insert(pubkey, Signature::new_unique()); + } + } + + transaction_builder.create_instruction(program_id, accounts, signatures, input.data); + + let mut feature_set = FeatureSet::default(); + if let Some(features) = &input.epoch_context.as_ref().unwrap().features { + for id in &features.features { + if let Some(pubkey) = INDEXED_FEATURES.get(id) { + feature_set.activate(pubkey, 0); + } + } + } + + let mut fee_payer = Pubkey::new_unique(); + let mut mock_bank = MockBankCallback::default(); + { + let mut account_data_map = mock_bank.account_shared_data.borrow_mut(); + for item in input.accounts { + let pubkey = Pubkey::new_from_array(item.address.try_into().unwrap()); + let mut account_data = AccountSharedData::default(); + account_data.set_lamports(item.lamports); + account_data.set_data(item.data); + account_data.set_owner(Pubkey::new_from_array(item.owner.try_into().unwrap())); + account_data.set_executable(item.executable); + account_data.set_rent_epoch(item.rent_epoch); + + account_data_map.insert(pubkey, account_data); + } + let mut account_data = AccountSharedData::default(); + account_data.set_lamports(800000); + + while account_data_map.contains_key(&fee_payer) { + // The fee payer must not coincide with any of the previous accounts + fee_payer = Pubkey::new_unique(); + } + account_data_map.insert(fee_payer, account_data); + } + + let Ok(transaction) = + transaction_builder.build(Hash::default(), (fee_payer, Signature::new_unique()), false) + else { + // If we can't build a sanitized transaction, + // the output must be a failed instruction as well + assert_ne!(output.result, 0); + return; + }; + + let transactions = vec![transaction]; + let transaction_check = vec![Ok(CheckedTransactionDetails { + nonce: None, + lamports_per_signature: 30, + })]; + + let compute_budget = ComputeBudget { + compute_unit_limit: input.cu_avail, + ..ComputeBudget::default() + }; + + let v1_environment = + create_program_runtime_environment_v1(&feature_set, &compute_budget, false, false).unwrap(); + + mock_bank.override_feature_set(feature_set); + let batch_processor = TransactionBatchProcessor::::new(42, 2, HashSet::new()); + + let fork_graph = Arc::new(RwLock::new(MockForkGraph {})); + { + let mut program_cache = batch_processor.program_cache.write().unwrap(); + program_cache.environments = ProgramRuntimeEnvironments { + program_runtime_v1: Arc::new(v1_environment), + program_runtime_v2: Arc::new(BuiltinProgram::new_loader( + Config::default(), + FunctionRegistry::default(), + )), + }; + program_cache.fork_graph = Some(Arc::downgrade(&fork_graph.clone())); + } + + batch_processor.fill_missing_sysvar_cache_entries(&mock_bank); + register_builtins(&batch_processor, &mock_bank); + + #[allow(deprecated)] + let (blockhash, lamports_per_signature) = batch_processor + .sysvar_cache() + .get_recent_blockhashes() + .ok() + .and_then(|x| (*x).last().cloned()) + .map(|x| (x.blockhash, x.fee_calculator.lamports_per_signature)) + .unwrap_or_default(); + + let recording_config = ExecutionRecordingConfig { + enable_log_recording: true, + enable_return_data_recording: true, + enable_cpi_recording: false, + }; + let processor_config = TransactionProcessingConfig { + account_overrides: None, + check_program_modification_slot: false, + compute_budget: None, + log_messages_bytes_limit: None, + limit_to_load_programs: true, + recording_config, + transaction_account_lock_limit: None, + }; + + if execute_as_instr { + execute_fixture_as_instr( + &mock_bank, + &batch_processor, + transactions[0].message(), + compute_budget, + output, + filename, + input.cu_avail, + ); + return; + } + + let result = batch_processor.load_and_execute_sanitized_transactions( + &mock_bank, + &transactions, + transaction_check, + &TransactionProcessingEnvironment { + blockhash, + lamports_per_signature, + ..Default::default() + }, + &processor_config, + ); + + // Assert that the transaction has worked without errors. + if !result.execution_results[0].was_executed() + || result.execution_results[0] + .details() + .unwrap() + .status + .is_err() + { + if matches!( + result.execution_results[0].flattened_result(), + Err(TransactionError::InsufficientFundsForRent { .. }) + ) { + // This is a transaction error not an instruction error, so execute the instruction + // instead. + execute_fixture_as_instr( + &mock_bank, + &batch_processor, + transactions[0].message(), + compute_budget, + output, + filename, + input.cu_avail, + ); + return; + } + + assert_ne!( + output.result, 0, + "Transaction was not successful, but should have been: file {:?}", + filename + ); + return; + } + + let execution_details = result.execution_results[0].details().unwrap(); + verify_accounts_and_data( + &result.loaded_transactions[0].as_ref().unwrap().accounts, + output, + execution_details.executed_units, + input.cu_avail, + execution_details + .return_data + .as_ref() + .map(|x| &x.data) + .unwrap_or(&Vec::new()), + filename, + ); +} + +fn register_builtins( + batch_processor: &TransactionBatchProcessor, + mock_bank: &MockBankCallback, +) { + let bpf_loader = "solana_bpf_loader_upgradeable_program"; + batch_processor.add_builtin( + mock_bank, + bpf_loader_upgradeable::id(), + bpf_loader, + ProgramCacheEntry::new_builtin( + 0, + bpf_loader.len(), + solana_bpf_loader_program::Entrypoint::vm, + ), + ); + + let system_program = "system_program"; + batch_processor.add_builtin( + mock_bank, + solana_system_program::id(), + system_program, + ProgramCacheEntry::new_builtin( + 0, + system_program.len(), + solana_system_program::system_processor::Entrypoint::vm, + ), + ); +} + +fn execute_fixture_as_instr( + mock_bank: &MockBankCallback, + batch_processor: &TransactionBatchProcessor, + sanitized_message: &SanitizedMessage, + compute_budget: ComputeBudget, + output: &InstrEffects, + filename: OsString, + cu_avail: u64, +) { + let rent = if let Ok(rent) = batch_processor.sysvar_cache().get_rent() { + (*rent).clone() + } else { + Rent::default() + }; + + let transaction_accounts: Vec = sanitized_message + .account_keys() + .iter() + .map(|key| (*key, mock_bank.get_account_shared_data(key).unwrap())) + .collect(); + + let mut transaction_context = TransactionContext::new( + transaction_accounts, + rent, + compute_budget.max_instruction_stack_depth, + compute_budget.max_instruction_trace_length, + ); + + let mut loaded_programs = ProgramCacheForTxBatch::new( + 42, + batch_processor + .program_cache + .read() + .unwrap() + .environments + .clone(), + None, + 2, + ); + + let program_idx = sanitized_message.instructions()[0].program_id_index as usize; + let program_id = *sanitized_message.account_keys().get(program_idx).unwrap(); + + let loaded_program = program_loader::load_program_with_pubkey( + mock_bank, + &batch_processor.get_environments_for_epoch(2).unwrap(), + &program_id, + 42, + false, + ) + .unwrap(); + + loaded_programs.replenish(program_id, loaded_program); + loaded_programs.replenish( + solana_system_program::id(), + Arc::new(ProgramCacheEntry::new_builtin( + 0u64, + 0usize, + solana_system_program::system_processor::Entrypoint::vm, + )), + ); + + let log_collector = LogCollector::new_ref(); + + let sysvar_cache = &batch_processor.sysvar_cache(); + let env_config = EnvironmentConfig::new( + Hash::default(), + None, + None, + mock_bank.feature_set.clone(), + 0, + sysvar_cache, + ); + + let mut invoke_context = InvokeContext::new( + &mut transaction_context, + &mut loaded_programs, + env_config, + Some(log_collector.clone()), + compute_budget, + ); + + let mut instruction_accounts: Vec = + Vec::with_capacity(sanitized_message.instructions()[0].accounts.len()); + + for (instruction_acct_idx, index_txn) in sanitized_message.instructions()[0] + .accounts + .iter() + .enumerate() + { + let index_in_callee = sanitized_message.instructions()[0] + .accounts + .get(0..instruction_acct_idx) + .unwrap() + .iter() + .position(|idx| *idx == *index_txn) + .unwrap_or(instruction_acct_idx); + + instruction_accounts.push(InstructionAccount { + index_in_transaction: *index_txn as IndexOfAccount, + index_in_caller: *index_txn as IndexOfAccount, + index_in_callee: index_in_callee as IndexOfAccount, + is_signer: sanitized_message.is_signer(*index_txn as usize), + is_writable: sanitized_message.is_writable(*index_txn as usize), + }); + } + + let mut compute_units_consumed = 0u64; + let mut timings = ExecuteTimings::default(); + let result = invoke_context.process_instruction( + &sanitized_message.instructions()[0].data, + &instruction_accounts, + &[program_idx as IndexOfAccount], + &mut compute_units_consumed, + &mut timings, + ); + + if output.result == 0 { + assert!( + result.is_ok(), + "Instruction execution was NOT successful, but should have been: {:?}", + filename + ); + } else { + assert!( + result.is_err(), + "Instruction execution was successful, but should NOT have been: {:?}", + filename + ); + return; + } + + let ExecutionRecord { + accounts, + return_data, + .. + } = transaction_context.into(); + + verify_accounts_and_data( + &accounts, + output, + compute_units_consumed, + cu_avail, + &return_data.data, + filename, + ); +} + +fn verify_accounts_and_data( + accounts: &[TransactionAccount], + output: &InstrEffects, + consumed_units: u64, + cu_avail: u64, + return_data: &Vec, + filename: OsString, +) { + let idx_map: HashMap = accounts + .iter() + .enumerate() + .map(|(idx, item)| (item.0, idx)) + .collect(); + + for item in &output.modified_accounts { + let pubkey = Pubkey::new_from_array(item.address.clone().try_into().unwrap()); + let index = *idx_map + .get(&pubkey) + .expect("Account not in expected results"); + + let received_data = &accounts[index].1; + + assert_eq!( + received_data.lamports(), + item.lamports, + "Lamports differ in case: {:?}", + filename + ); + assert_eq!( + received_data.data(), + item.data.as_slice(), + "Account data differs in case: {:?}", + filename + ); + assert_eq!( + received_data.owner(), + &Pubkey::new_from_array(item.owner.clone().try_into().unwrap()), + "Account owner differs in case: {:?}", + filename + ); + assert_eq!( + received_data.executable(), + item.executable, + "Executable boolean differs in case: {:?}", + filename + ); + + // u64::MAX means we are not considering the epoch + if item.rent_epoch != u64::MAX && received_data.rent_epoch() != u64::MAX { + assert_eq!( + received_data.rent_epoch(), + item.rent_epoch, + "Rent epoch differs in case: {:?}", + filename + ); + } + } + + assert_eq!( + consumed_units, + cu_avail.saturating_sub(output.cu_avail), + "Execution units differs in case: {:?}", + filename + ); + + if return_data.is_empty() { + assert!(output.return_data.is_empty()); + } else { + assert_eq!(&output.return_data, return_data); + } +} diff --git a/svm/tests/integration_test.rs b/svm/tests/integration_test.rs new file mode 100644 index 00000000000000..5c5d74f984c8ec --- /dev/null +++ b/svm/tests/integration_test.rs @@ -0,0 +1,538 @@ +#![cfg(test)] + +use { + crate::{mock_bank::MockBankCallback, transaction_builder::SanitizedTransactionBuilder}, + solana_bpf_loader_program::syscalls::{ + SyscallAbort, SyscallGetClockSysvar, SyscallInvokeSignedRust, SyscallLog, SyscallMemcpy, + SyscallMemset, SyscallSetReturnData, + }, + solana_compute_budget::compute_budget::ComputeBudget, + solana_program_runtime::{ + invoke_context::InvokeContext, + loaded_programs::{ + BlockRelation, ForkGraph, ProgramCache, ProgramCacheEntry, ProgramRuntimeEnvironments, + }, + solana_rbpf::{ + program::{BuiltinFunction, BuiltinProgram, FunctionRegistry}, + vm::Config, + }, + }, + solana_sdk::{ + account::{AccountSharedData, ReadableAccount, WritableAccount}, + bpf_loader_upgradeable::{self, UpgradeableLoaderState}, + clock::{Clock, Epoch, Slot, UnixTimestamp}, + hash::Hash, + instruction::AccountMeta, + pubkey::Pubkey, + signature::Signature, + sysvar::SysvarId, + transaction::{SanitizedTransaction, TransactionError}, + }, + solana_svm::{ + account_loader::{CheckedTransactionDetails, TransactionCheckResult}, + transaction_processing_callback::TransactionProcessingCallback, + transaction_processor::{ + ExecutionRecordingConfig, TransactionBatchProcessor, TransactionProcessingConfig, + TransactionProcessingEnvironment, + }, + transaction_results::TransactionExecutionResult, + }, + std::{ + cmp::Ordering, + collections::{HashMap, HashSet}, + env, + fs::{self, File}, + io::Read, + sync::{Arc, RwLock}, + time::{SystemTime, UNIX_EPOCH}, + }, +}; + +// This module contains the implementation of TransactionProcessingCallback +mod mock_bank; +mod transaction_builder; + +const BPF_LOADER_NAME: &str = "solana_bpf_loader_upgradeable_program"; +const SYSTEM_PROGRAM_NAME: &str = "system_program"; +const DEPLOYMENT_SLOT: u64 = 0; +const EXECUTION_SLOT: u64 = 5; // The execution slot must be greater than the deployment slot +const DEPLOYMENT_EPOCH: u64 = 0; +const EXECUTION_EPOCH: u64 = 2; // The execution epoch must be greater than the deployment epoch + +struct MockForkGraph {} + +impl ForkGraph for MockForkGraph { + fn relationship(&self, a: Slot, b: Slot) -> BlockRelation { + match a.cmp(&b) { + Ordering::Less => BlockRelation::Ancestor, + Ordering::Equal => BlockRelation::Equal, + Ordering::Greater => BlockRelation::Descendant, + } + } + + fn slot_epoch(&self, _slot: Slot) -> Option { + Some(0) + } +} + +fn create_custom_environment<'a>() -> BuiltinProgram> { + let compute_budget = ComputeBudget::default(); + let vm_config = Config { + max_call_depth: compute_budget.max_call_depth, + stack_frame_size: compute_budget.stack_frame_size, + enable_address_translation: true, + enable_stack_frame_gaps: true, + instruction_meter_checkpoint_distance: 10000, + enable_instruction_meter: true, + enable_instruction_tracing: true, + enable_symbol_and_section_labels: true, + reject_broken_elfs: true, + noop_instruction_rate: 256, + sanitize_user_provided_values: true, + external_internal_function_hash_collision: false, + reject_callx_r10: false, + enable_sbpf_v1: true, + enable_sbpf_v2: false, + optimize_rodata: false, + aligned_memory_mapping: true, + }; + + // These functions are system calls the compile contract calls during execution, so they + // need to be registered. + let mut function_registry = FunctionRegistry::>::default(); + function_registry + .register_function_hashed(*b"abort", SyscallAbort::vm) + .expect("Registration failed"); + function_registry + .register_function_hashed(*b"sol_log_", SyscallLog::vm) + .expect("Registration failed"); + function_registry + .register_function_hashed(*b"sol_memcpy_", SyscallMemcpy::vm) + .expect("Registration failed"); + function_registry + .register_function_hashed(*b"sol_memset_", SyscallMemset::vm) + .expect("Registration failed"); + + function_registry + .register_function_hashed(*b"sol_invoke_signed_rust", SyscallInvokeSignedRust::vm) + .expect("Registration failed"); + + function_registry + .register_function_hashed(*b"sol_set_return_data", SyscallSetReturnData::vm) + .expect("Registration failed"); + + function_registry + .register_function_hashed(*b"sol_get_clock_sysvar", SyscallGetClockSysvar::vm) + .expect("Registration failed"); + + BuiltinProgram::new_loader(vm_config, function_registry) +} + +fn create_executable_environment( + fork_graph: Arc>, + mock_bank: &mut MockBankCallback, + program_cache: &mut ProgramCache, +) { + program_cache.environments = ProgramRuntimeEnvironments { + program_runtime_v1: Arc::new(create_custom_environment()), + // We are not using program runtime v2 + program_runtime_v2: Arc::new(BuiltinProgram::new_loader( + Config::default(), + FunctionRegistry::default(), + )), + }; + + program_cache.fork_graph = Some(Arc::downgrade(&fork_graph)); + + // We must fill in the sysvar cache entries + let time_now = SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("Time went backwards") + .as_secs() as i64; + let clock = Clock { + slot: DEPLOYMENT_SLOT, + epoch_start_timestamp: time_now.saturating_sub(10) as UnixTimestamp, + epoch: DEPLOYMENT_EPOCH, + leader_schedule_epoch: DEPLOYMENT_EPOCH, + unix_timestamp: time_now as UnixTimestamp, + }; + + let mut account_data = AccountSharedData::default(); + account_data.set_data(bincode::serialize(&clock).unwrap()); + mock_bank + .account_shared_data + .borrow_mut() + .insert(Clock::id(), account_data); +} + +fn load_program(name: String) -> Vec { + // Loading the program file + let mut dir = env::current_dir().unwrap(); + dir.push("tests"); + dir.push("example-programs"); + dir.push(name.as_str()); + let name = name.replace('-', "_"); + dir.push(name + "_program.so"); + let mut file = File::open(dir.clone()).expect("file not found"); + let metadata = fs::metadata(dir).expect("Unable to read metadata"); + let mut buffer = vec![0; metadata.len() as usize]; + file.read_exact(&mut buffer).expect("Buffer overflow"); + buffer +} + +fn deploy_program(name: String, mock_bank: &mut MockBankCallback) -> Pubkey { + let program_account = Pubkey::new_unique(); + let program_data_account = Pubkey::new_unique(); + let state = UpgradeableLoaderState::Program { + programdata_address: program_data_account, + }; + + // The program account must have funds and hold the executable binary + let mut account_data = AccountSharedData::default(); + account_data.set_data(bincode::serialize(&state).unwrap()); + account_data.set_lamports(25); + account_data.set_owner(bpf_loader_upgradeable::id()); + mock_bank + .account_shared_data + .borrow_mut() + .insert(program_account, account_data); + + let mut account_data = AccountSharedData::default(); + let state = UpgradeableLoaderState::ProgramData { + slot: DEPLOYMENT_SLOT, + upgrade_authority_address: None, + }; + let mut header = bincode::serialize(&state).unwrap(); + let mut complement = vec![ + 0; + std::cmp::max( + 0, + UpgradeableLoaderState::size_of_programdata_metadata().saturating_sub(header.len()) + ) + ]; + let mut buffer = load_program(name); + header.append(&mut complement); + header.append(&mut buffer); + account_data.set_data(header); + mock_bank + .account_shared_data + .borrow_mut() + .insert(program_data_account, account_data); + + program_account +} + +fn register_builtins( + mock_bank: &MockBankCallback, + batch_processor: &TransactionBatchProcessor, +) { + // We must register the bpf loader account as a loadable account, otherwise programs + // won't execute. + batch_processor.add_builtin( + mock_bank, + bpf_loader_upgradeable::id(), + BPF_LOADER_NAME, + ProgramCacheEntry::new_builtin( + DEPLOYMENT_SLOT, + BPF_LOADER_NAME.len(), + solana_bpf_loader_program::Entrypoint::vm, + ), + ); + + // In order to perform a transference of native tokens using the system instruction, + // the system program builtin must be registered. + batch_processor.add_builtin( + mock_bank, + solana_system_program::id(), + SYSTEM_PROGRAM_NAME, + ProgramCacheEntry::new_builtin( + DEPLOYMENT_SLOT, + SYSTEM_PROGRAM_NAME.len(), + solana_system_program::system_processor::Entrypoint::vm, + ), + ); +} + +fn prepare_transactions( + mock_bank: &mut MockBankCallback, +) -> (Vec, Vec) { + let mut transaction_builder = SanitizedTransactionBuilder::default(); + let mut all_transactions = Vec::new(); + let mut transaction_checks = Vec::new(); + + // A transaction that works without any account + let hello_program = deploy_program("hello-solana".to_string(), mock_bank); + let fee_payer = Pubkey::new_unique(); + transaction_builder.create_instruction(hello_program, Vec::new(), HashMap::new(), Vec::new()); + + let sanitized_transaction = + transaction_builder.build(Hash::default(), (fee_payer, Signature::new_unique()), false); + + all_transactions.push(sanitized_transaction.unwrap()); + transaction_checks.push(Ok(CheckedTransactionDetails { + nonce: None, + lamports_per_signature: 20, + })); + + // The transaction fee payer must have enough funds + let mut account_data = AccountSharedData::default(); + account_data.set_lamports(80000); + mock_bank + .account_shared_data + .borrow_mut() + .insert(fee_payer, account_data); + + // A simple funds transfer between accounts + let transfer_program_account = deploy_program("simple-transfer".to_string(), mock_bank); + let sender = Pubkey::new_unique(); + let recipient = Pubkey::new_unique(); + let fee_payer = Pubkey::new_unique(); + let system_account = Pubkey::from([0u8; 32]); + + transaction_builder.create_instruction( + transfer_program_account, + vec![ + AccountMeta { + pubkey: sender, + is_signer: true, + is_writable: true, + }, + AccountMeta { + pubkey: recipient, + is_signer: false, + is_writable: true, + }, + AccountMeta { + pubkey: system_account, + is_signer: false, + is_writable: false, + }, + ], + HashMap::from([(sender, Signature::new_unique())]), + vec![0, 0, 0, 0, 0, 0, 0, 10], + ); + + let sanitized_transaction = + transaction_builder.build(Hash::default(), (fee_payer, Signature::new_unique()), true); + all_transactions.push(sanitized_transaction.unwrap()); + transaction_checks.push(Ok(CheckedTransactionDetails { + nonce: None, + lamports_per_signature: 20, + })); + + // Setting up the accounts for the transfer + + // fee payer + let mut account_data = AccountSharedData::default(); + account_data.set_lamports(80000); + mock_bank + .account_shared_data + .borrow_mut() + .insert(fee_payer, account_data); + + // sender + let mut account_data = AccountSharedData::default(); + account_data.set_lamports(900000); + mock_bank + .account_shared_data + .borrow_mut() + .insert(sender, account_data); + + // recipient + let mut account_data = AccountSharedData::default(); + account_data.set_lamports(900000); + mock_bank + .account_shared_data + .borrow_mut() + .insert(recipient, account_data); + + // The system account is set in `create_executable_environment` + + // A program that utilizes a Sysvar + let program_account = deploy_program("clock-sysvar".to_string(), mock_bank); + let fee_payer = Pubkey::new_unique(); + transaction_builder.create_instruction(program_account, Vec::new(), HashMap::new(), Vec::new()); + + let sanitized_transaction = + transaction_builder.build(Hash::default(), (fee_payer, Signature::new_unique()), false); + + all_transactions.push(sanitized_transaction.unwrap()); + transaction_checks.push(Ok(CheckedTransactionDetails { + nonce: None, + lamports_per_signature: 20, + })); + + let mut account_data = AccountSharedData::default(); + account_data.set_lamports(80000); + mock_bank + .account_shared_data + .borrow_mut() + .insert(fee_payer, account_data); + + // A transaction that fails + let sender = Pubkey::new_unique(); + let recipient = Pubkey::new_unique(); + let fee_payer = Pubkey::new_unique(); + let system_account = Pubkey::new_from_array([0; 32]); + let data = 900050u64.to_be_bytes().to_vec(); + transaction_builder.create_instruction( + transfer_program_account, + vec![ + AccountMeta { + pubkey: sender, + is_signer: true, + is_writable: true, + }, + AccountMeta { + pubkey: recipient, + is_signer: false, + is_writable: true, + }, + AccountMeta { + pubkey: system_account, + is_signer: false, + is_writable: false, + }, + ], + HashMap::from([(sender, Signature::new_unique())]), + data, + ); + + let sanitized_transaction = + transaction_builder.build(Hash::default(), (fee_payer, Signature::new_unique()), true); + all_transactions.push(sanitized_transaction.clone().unwrap()); + transaction_checks.push(Ok(CheckedTransactionDetails { + nonce: None, + lamports_per_signature: 20, + })); + + // fee payer + let mut account_data = AccountSharedData::default(); + account_data.set_lamports(80000); + mock_bank + .account_shared_data + .borrow_mut() + .insert(fee_payer, account_data); + + // Sender without enough funds + let mut account_data = AccountSharedData::default(); + account_data.set_lamports(900000); + mock_bank + .account_shared_data + .borrow_mut() + .insert(sender, account_data); + + // recipient + let mut account_data = AccountSharedData::default(); + account_data.set_lamports(900000); + mock_bank + .account_shared_data + .borrow_mut() + .insert(recipient, account_data); + + // A transaction whose verification has already failed + all_transactions.push(sanitized_transaction.unwrap()); + transaction_checks.push(Err(TransactionError::BlockhashNotFound)); + + (all_transactions, transaction_checks) +} + +#[test] +fn svm_integration() { + let mut mock_bank = MockBankCallback::default(); + let (transactions, check_results) = prepare_transactions(&mut mock_bank); + let batch_processor = TransactionBatchProcessor::::new( + EXECUTION_SLOT, + EXECUTION_EPOCH, + HashSet::new(), + ); + + let fork_graph = Arc::new(RwLock::new(MockForkGraph {})); + + create_executable_environment( + fork_graph.clone(), + &mut mock_bank, + &mut batch_processor.program_cache.write().unwrap(), + ); + + // The sysvars must be put in the cache + batch_processor.fill_missing_sysvar_cache_entries(&mock_bank); + register_builtins(&mock_bank, &batch_processor); + + let processing_config = TransactionProcessingConfig { + recording_config: ExecutionRecordingConfig { + enable_log_recording: true, + enable_return_data_recording: true, + enable_cpi_recording: false, + }, + ..Default::default() + }; + + let result = batch_processor.load_and_execute_sanitized_transactions( + &mock_bank, + &transactions, + check_results, + &TransactionProcessingEnvironment::default(), + &processing_config, + ); + + assert_eq!(result.execution_results.len(), 5); + assert!(result.execution_results[0] + .details() + .unwrap() + .status + .is_ok()); + let logs = result.execution_results[0] + .details() + .unwrap() + .log_messages + .as_ref() + .unwrap(); + assert!(logs.contains(&"Program log: Hello, Solana!".to_string())); + + assert!(result.execution_results[1] + .details() + .unwrap() + .status + .is_ok()); + + // The SVM does not commit the account changes in MockBank + let recipient_key = transactions[1].message().account_keys()[2]; + let recipient_data = result.loaded_transactions[1] + .as_ref() + .unwrap() + .accounts + .iter() + .find(|key| key.0 == recipient_key) + .unwrap(); + assert_eq!(recipient_data.1.lamports(), 900010); + + let return_data = result.execution_results[2] + .details() + .unwrap() + .return_data + .as_ref() + .unwrap(); + let time = i64::from_be_bytes(return_data.data[0..8].try_into().unwrap()); + let clock_data = mock_bank.get_account_shared_data(&Clock::id()).unwrap(); + let clock_info: Clock = bincode::deserialize(clock_data.data()).unwrap(); + assert_eq!(clock_info.unix_timestamp, time); + + assert!(result.execution_results[3] + .details() + .unwrap() + .status + .is_err()); + assert!(result.execution_results[3] + .details() + .unwrap() + .log_messages + .as_ref() + .unwrap() + .contains(&"Transfer: insufficient lamports 900000, need 900050".to_string())); + + assert!(matches!( + result.execution_results[4], + TransactionExecutionResult::NotExecuted(TransactionError::BlockhashNotFound) + )); +} diff --git a/unified-scheduler-pool/src/lib.rs b/unified-scheduler-pool/src/lib.rs index 10cb5309e5e01d..9697d143e41b58 100644 --- a/unified-scheduler-pool/src/lib.rs +++ b/unified-scheduler-pool/src/lib.rs @@ -328,7 +328,10 @@ mod tests { system_transaction, transaction::{SanitizedTransaction, TransactionError}, }, - std::{sync::Arc, thread::JoinHandle}, + std::{ + sync::{Arc, RwLock}, + thread::JoinHandle, + }, }; #[test] @@ -361,6 +364,563 @@ mod tests { assert!(!debug.is_empty()); } +<<<<<<< HEAD +======= + const SHORTENED_POOL_CLEANER_INTERVAL: Duration = Duration::from_millis(1); + const SHORTENED_MAX_POOLING_DURATION: Duration = Duration::from_millis(10); + + #[test] + fn test_scheduler_drop_idle() { + solana_logger::setup(); + + let _progress = sleepless_testing::setup(&[ + &TestCheckPoint::BeforeIdleSchedulerCleaned, + &CheckPoint::IdleSchedulerCleaned(0), + &CheckPoint::IdleSchedulerCleaned(1), + &TestCheckPoint::AfterIdleSchedulerCleaned, + ]); + + let ignored_prioritization_fee_cache = Arc::new(PrioritizationFeeCache::new(0u64)); + let pool_raw = DefaultSchedulerPool::do_new( + None, + None, + None, + None, + ignored_prioritization_fee_cache, + SHORTENED_POOL_CLEANER_INTERVAL, + SHORTENED_MAX_POOLING_DURATION, + DEFAULT_MAX_USAGE_QUEUE_COUNT, + DEFAULT_TIMEOUT_DURATION, + ); + let pool = pool_raw.clone(); + let bank = Arc::new(Bank::default_for_tests()); + let context1 = SchedulingContext::new(bank); + let context2 = context1.clone(); + + let old_scheduler = pool.do_take_scheduler(context1); + let new_scheduler = pool.do_take_scheduler(context2); + let new_scheduler_id = new_scheduler.id(); + Box::new(old_scheduler.into_inner().1).return_to_pool(); + + // sleepless_testing can't be used; wait a bit here to see real progress of wall time... + sleep(SHORTENED_MAX_POOLING_DURATION * 10); + Box::new(new_scheduler.into_inner().1).return_to_pool(); + + // Block solScCleaner until we see returned schedlers... + assert_eq!(pool_raw.scheduler_inners.lock().unwrap().len(), 2); + sleepless_testing::at(TestCheckPoint::BeforeIdleSchedulerCleaned); + + // See the old (= idle) scheduler gone only after solScCleaner did its job... + sleepless_testing::at(&TestCheckPoint::AfterIdleSchedulerCleaned); + assert_eq!(pool_raw.scheduler_inners.lock().unwrap().len(), 1); + assert_eq!( + pool_raw + .scheduler_inners + .lock() + .unwrap() + .first() + .as_ref() + .map(|(inner, _pooled_at)| inner.id()) + .unwrap(), + new_scheduler_id + ); + } + + #[test] + fn test_scheduler_drop_overgrown() { + solana_logger::setup(); + + let _progress = sleepless_testing::setup(&[ + &TestCheckPoint::BeforeTrashedSchedulerCleaned, + &CheckPoint::TrashedSchedulerCleaned(0), + &CheckPoint::TrashedSchedulerCleaned(1), + &TestCheckPoint::AfterTrashedSchedulerCleaned, + ]); + + let ignored_prioritization_fee_cache = Arc::new(PrioritizationFeeCache::new(0u64)); + const REDUCED_MAX_USAGE_QUEUE_COUNT: usize = 1; + let pool_raw = DefaultSchedulerPool::do_new( + None, + None, + None, + None, + ignored_prioritization_fee_cache, + SHORTENED_POOL_CLEANER_INTERVAL, + DEFAULT_MAX_POOLING_DURATION, + REDUCED_MAX_USAGE_QUEUE_COUNT, + DEFAULT_TIMEOUT_DURATION, + ); + let pool = pool_raw.clone(); + let bank = Arc::new(Bank::default_for_tests()); + let context1 = SchedulingContext::new(bank); + let context2 = context1.clone(); + + let small_scheduler = pool.do_take_scheduler(context1); + let small_scheduler_id = small_scheduler.id(); + for _ in 0..REDUCED_MAX_USAGE_QUEUE_COUNT { + small_scheduler + .inner + .usage_queue_loader + .load(Pubkey::new_unique()); + } + let big_scheduler = pool.do_take_scheduler(context2); + for _ in 0..REDUCED_MAX_USAGE_QUEUE_COUNT + 1 { + big_scheduler + .inner + .usage_queue_loader + .load(Pubkey::new_unique()); + } + + assert_eq!(pool_raw.scheduler_inners.lock().unwrap().len(), 0); + assert_eq!(pool_raw.trashed_scheduler_inners.lock().unwrap().len(), 0); + Box::new(small_scheduler.into_inner().1).return_to_pool(); + Box::new(big_scheduler.into_inner().1).return_to_pool(); + + // Block solScCleaner until we see trashed schedler... + assert_eq!(pool_raw.scheduler_inners.lock().unwrap().len(), 1); + assert_eq!(pool_raw.trashed_scheduler_inners.lock().unwrap().len(), 1); + sleepless_testing::at(TestCheckPoint::BeforeTrashedSchedulerCleaned); + + // See the trashed scheduler gone only after solScCleaner did its job... + sleepless_testing::at(&TestCheckPoint::AfterTrashedSchedulerCleaned); + assert_eq!(pool_raw.scheduler_inners.lock().unwrap().len(), 1); + assert_eq!(pool_raw.trashed_scheduler_inners.lock().unwrap().len(), 0); + assert_eq!( + pool_raw + .scheduler_inners + .lock() + .unwrap() + .first() + .as_ref() + .map(|(inner, _pooled_at)| inner.id()) + .unwrap(), + small_scheduler_id + ); + } + + const SHORTENED_TIMEOUT_DURATION: Duration = Duration::from_millis(1); + + #[test] + fn test_scheduler_drop_stale() { + solana_logger::setup(); + + let _progress = sleepless_testing::setup(&[ + &TestCheckPoint::BeforeTimeoutListenerTriggered, + &CheckPoint::TimeoutListenerTriggered(0), + &CheckPoint::TimeoutListenerTriggered(1), + &TestCheckPoint::AfterTimeoutListenerTriggered, + &CheckPoint::IdleSchedulerCleaned(1), + &TestCheckPoint::AfterIdleSchedulerCleaned, + ]); + + let ignored_prioritization_fee_cache = Arc::new(PrioritizationFeeCache::new(0u64)); + let pool_raw = DefaultSchedulerPool::do_new( + None, + None, + None, + None, + ignored_prioritization_fee_cache, + SHORTENED_POOL_CLEANER_INTERVAL, + SHORTENED_MAX_POOLING_DURATION, + DEFAULT_MAX_USAGE_QUEUE_COUNT, + SHORTENED_TIMEOUT_DURATION, + ); + let pool = pool_raw.clone(); + let bank = Arc::new(Bank::default_for_tests()); + let context = SchedulingContext::new(bank.clone()); + let scheduler = pool.take_scheduler(context); + let bank = BankWithScheduler::new(bank, Some(scheduler)); + pool.register_timeout_listener(bank.create_timeout_listener()); + assert_eq!(pool_raw.scheduler_inners.lock().unwrap().len(), 0); + assert_eq!(pool_raw.trashed_scheduler_inners.lock().unwrap().len(), 0); + sleepless_testing::at(TestCheckPoint::BeforeTimeoutListenerTriggered); + + sleepless_testing::at(TestCheckPoint::AfterTimeoutListenerTriggered); + assert_eq!(pool_raw.scheduler_inners.lock().unwrap().len(), 1); + assert_eq!(pool_raw.trashed_scheduler_inners.lock().unwrap().len(), 0); + assert_matches!(bank.wait_for_completed_scheduler(), Some((Ok(()), _))); + + // See the stale scheduler gone only after solScCleaner did its job... + sleepless_testing::at(&TestCheckPoint::AfterIdleSchedulerCleaned); + assert_eq!(pool_raw.scheduler_inners.lock().unwrap().len(), 0); + assert_eq!(pool_raw.trashed_scheduler_inners.lock().unwrap().len(), 0); + } + + #[test] + fn test_scheduler_active_after_stale() { + solana_logger::setup(); + + let _progress = sleepless_testing::setup(&[ + &TestCheckPoint::BeforeTimeoutListenerTriggered, + &CheckPoint::TimeoutListenerTriggered(0), + &CheckPoint::TimeoutListenerTriggered(1), + &TestCheckPoint::AfterTimeoutListenerTriggered, + &TestCheckPoint::BeforeTimeoutListenerTriggered, + &CheckPoint::TimeoutListenerTriggered(0), + &CheckPoint::TimeoutListenerTriggered(1), + &TestCheckPoint::AfterTimeoutListenerTriggered, + ]); + + let ignored_prioritization_fee_cache = Arc::new(PrioritizationFeeCache::new(0u64)); + let pool_raw = SchedulerPool::, _>::do_new( + None, + None, + None, + None, + ignored_prioritization_fee_cache, + SHORTENED_POOL_CLEANER_INTERVAL, + DEFAULT_MAX_POOLING_DURATION, + DEFAULT_MAX_USAGE_QUEUE_COUNT, + SHORTENED_TIMEOUT_DURATION, + ); + + #[derive(Debug)] + struct ExecuteTimingCounter; + impl TaskHandler for ExecuteTimingCounter { + fn handle( + _result: &mut Result<()>, + timings: &mut ExecuteTimings, + _bank: &Arc, + _transaction: &SanitizedTransaction, + _index: usize, + _handler_context: &HandlerContext, + ) { + timings.metrics[ExecuteTimingType::CheckUs] += 123; + } + } + let pool = pool_raw.clone(); + + let GenesisConfigInfo { + genesis_config, + mint_keypair, + .. + } = create_genesis_config(10_000); + let bank = Bank::new_for_tests(&genesis_config); + let (bank, _bank_forks) = setup_dummy_fork_graph(bank); + + let context = SchedulingContext::new(bank.clone()); + + let scheduler = pool.take_scheduler(context); + let bank = BankWithScheduler::new(bank, Some(scheduler)); + pool.register_timeout_listener(bank.create_timeout_listener()); + + let tx_before_stale = + &SanitizedTransaction::from_transaction_for_tests(system_transaction::transfer( + &mint_keypair, + &solana_sdk::pubkey::new_rand(), + 2, + genesis_config.hash(), + )); + bank.schedule_transaction_executions([(tx_before_stale, &0)].into_iter()) + .unwrap(); + sleepless_testing::at(TestCheckPoint::BeforeTimeoutListenerTriggered); + + sleepless_testing::at(TestCheckPoint::AfterTimeoutListenerTriggered); + let tx_after_stale = + &SanitizedTransaction::from_transaction_for_tests(system_transaction::transfer( + &mint_keypair, + &solana_sdk::pubkey::new_rand(), + 2, + genesis_config.hash(), + )); + bank.schedule_transaction_executions([(tx_after_stale, &1)].into_iter()) + .unwrap(); + + // Observe second occurrence of TimeoutListenerTriggered(1), which indicates a new timeout + // lister is registered correctly again for reactivated scheduler. + sleepless_testing::at(TestCheckPoint::BeforeTimeoutListenerTriggered); + sleepless_testing::at(TestCheckPoint::AfterTimeoutListenerTriggered); + + let (result, timings) = bank.wait_for_completed_scheduler().unwrap(); + assert_matches!(result, Ok(())); + // ResultWithTimings should be carried over across active=>stale=>active transitions. + assert_eq!(timings.metrics[ExecuteTimingType::CheckUs], 246); + } + + #[test] + fn test_scheduler_pause_after_stale() { + solana_logger::setup(); + + let _progress = sleepless_testing::setup(&[ + &TestCheckPoint::BeforeTimeoutListenerTriggered, + &CheckPoint::TimeoutListenerTriggered(0), + &CheckPoint::TimeoutListenerTriggered(1), + &TestCheckPoint::AfterTimeoutListenerTriggered, + ]); + + let ignored_prioritization_fee_cache = Arc::new(PrioritizationFeeCache::new(0u64)); + let pool_raw = DefaultSchedulerPool::do_new( + None, + None, + None, + None, + ignored_prioritization_fee_cache, + SHORTENED_POOL_CLEANER_INTERVAL, + DEFAULT_MAX_POOLING_DURATION, + DEFAULT_MAX_USAGE_QUEUE_COUNT, + SHORTENED_TIMEOUT_DURATION, + ); + let pool = pool_raw.clone(); + + let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(10_000); + let bank = Bank::new_for_tests(&genesis_config); + let (bank, _bank_forks) = setup_dummy_fork_graph(bank); + + let context = SchedulingContext::new(bank.clone()); + + let scheduler = pool.take_scheduler(context); + let bank = BankWithScheduler::new(bank, Some(scheduler)); + pool.register_timeout_listener(bank.create_timeout_listener()); + + sleepless_testing::at(TestCheckPoint::BeforeTimeoutListenerTriggered); + sleepless_testing::at(TestCheckPoint::AfterTimeoutListenerTriggered); + + // This calls register_recent_blockhash() internally, which in turn calls + // BankWithScheduler::wait_for_paused_scheduler(). + bank.fill_bank_with_ticks_for_tests(); + let (result, _timings) = bank.wait_for_completed_scheduler().unwrap(); + assert_matches!(result, Ok(())); + } + + #[test] + fn test_scheduler_remain_stale_after_error() { + solana_logger::setup(); + + let _progress = sleepless_testing::setup(&[ + &TestCheckPoint::BeforeTimeoutListenerTriggered, + &CheckPoint::TimeoutListenerTriggered(0), + &CheckPoint::SchedulerThreadAborted, + &TestCheckPoint::AfterSchedulerThreadAborted, + &CheckPoint::TimeoutListenerTriggered(1), + &TestCheckPoint::AfterTimeoutListenerTriggered, + ]); + + let ignored_prioritization_fee_cache = Arc::new(PrioritizationFeeCache::new(0u64)); + let pool_raw = SchedulerPool::, _>::do_new( + None, + None, + None, + None, + ignored_prioritization_fee_cache, + SHORTENED_POOL_CLEANER_INTERVAL, + DEFAULT_MAX_POOLING_DURATION, + DEFAULT_MAX_USAGE_QUEUE_COUNT, + SHORTENED_TIMEOUT_DURATION, + ); + + let pool = pool_raw.clone(); + + let GenesisConfigInfo { + genesis_config, + mint_keypair, + .. + } = create_genesis_config(10_000); + let bank = Bank::new_for_tests(&genesis_config); + let (bank, _bank_forks) = setup_dummy_fork_graph(bank); + + let context = SchedulingContext::new(bank.clone()); + + let scheduler = pool.take_scheduler(context); + let bank = BankWithScheduler::new(bank, Some(scheduler)); + pool.register_timeout_listener(bank.create_timeout_listener()); + + let tx_before_stale = + &SanitizedTransaction::from_transaction_for_tests(system_transaction::transfer( + &mint_keypair, + &solana_sdk::pubkey::new_rand(), + 2, + genesis_config.hash(), + )); + bank.schedule_transaction_executions([(tx_before_stale, &0)].into_iter()) + .unwrap(); + sleepless_testing::at(TestCheckPoint::BeforeTimeoutListenerTriggered); + sleepless_testing::at(TestCheckPoint::AfterSchedulerThreadAborted); + + sleepless_testing::at(TestCheckPoint::AfterTimeoutListenerTriggered); + let tx_after_stale = + &SanitizedTransaction::from_transaction_for_tests(system_transaction::transfer( + &mint_keypair, + &solana_sdk::pubkey::new_rand(), + 2, + genesis_config.hash(), + )); + let result = bank.schedule_transaction_executions([(tx_after_stale, &1)].into_iter()); + assert_matches!(result, Err(TransactionError::AccountNotFound)); + + let (result, _timings) = bank.wait_for_completed_scheduler().unwrap(); + assert_matches!(result, Err(TransactionError::AccountNotFound)); + } + + enum AbortCase { + Unhandled, + UnhandledWhilePanicking, + Handled, + } + + #[derive(Debug)] + struct FaultyHandler; + impl TaskHandler for FaultyHandler { + fn handle( + result: &mut Result<()>, + _timings: &mut ExecuteTimings, + _bank: &Arc, + _transaction: &SanitizedTransaction, + _index: usize, + _handler_context: &HandlerContext, + ) { + *result = Err(TransactionError::AccountNotFound); + } + } + + fn do_test_scheduler_drop_abort(abort_case: AbortCase) { + solana_logger::setup(); + + let _progress = sleepless_testing::setup(match abort_case { + AbortCase::Unhandled => &[ + &CheckPoint::SchedulerThreadAborted, + &TestCheckPoint::AfterSchedulerThreadAborted, + ], + _ => &[], + }); + + let GenesisConfigInfo { + genesis_config, + mint_keypair, + .. + } = create_genesis_config(10_000); + + let tx = &SanitizedTransaction::from_transaction_for_tests(system_transaction::transfer( + &mint_keypair, + &solana_sdk::pubkey::new_rand(), + 2, + genesis_config.hash(), + )); + + let bank = Bank::new_for_tests(&genesis_config); + let (bank, _bank_forks) = setup_dummy_fork_graph(bank); + let ignored_prioritization_fee_cache = Arc::new(PrioritizationFeeCache::new(0u64)); + let pool = SchedulerPool::, _>::new( + None, + None, + None, + None, + ignored_prioritization_fee_cache, + ); + let context = SchedulingContext::new(bank.clone()); + let scheduler = pool.do_take_scheduler(context); + scheduler.schedule_execution(&(tx, 0)).unwrap(); + + match abort_case { + AbortCase::Unhandled => { + sleepless_testing::at(TestCheckPoint::AfterSchedulerThreadAborted); + // Directly dropping PooledScheduler is illegal unless panicking already, especially + // after being aborted. It must be converted to PooledSchedulerInner via + // ::into_inner(); + drop::>(scheduler); + } + AbortCase::UnhandledWhilePanicking => { + // no sleepless_testing::at(); panicking special-casing isn't racy + panic!("ThreadManager::drop() should be skipped..."); + } + AbortCase::Handled => { + // no sleepless_testing::at(); ::into_inner() isn't racy + let ((result, _), mut scheduler_inner) = scheduler.into_inner(); + assert_matches!(result, Err(TransactionError::AccountNotFound)); + + // Calling ensure_join_threads() repeatedly should be safe. + let dummy_flag = true; // doesn't matter because it's skipped anyway + scheduler_inner + .thread_manager + .ensure_join_threads(dummy_flag); + + drop::>(scheduler_inner); + } + } + } + + #[test] + #[should_panic(expected = "does not match `Some((Ok(_), _))")] + fn test_scheduler_drop_abort_unhandled() { + do_test_scheduler_drop_abort(AbortCase::Unhandled); + } + + #[test] + #[should_panic(expected = "ThreadManager::drop() should be skipped...")] + fn test_scheduler_drop_abort_unhandled_while_panicking() { + do_test_scheduler_drop_abort(AbortCase::UnhandledWhilePanicking); + } + + #[test] + fn test_scheduler_drop_abort_handled() { + do_test_scheduler_drop_abort(AbortCase::Handled); + } + + #[test] + fn test_scheduler_drop_short_circuiting() { + solana_logger::setup(); + + let _progress = sleepless_testing::setup(&[ + &TestCheckPoint::BeforeThreadManagerDrop, + &CheckPoint::NewTask(0), + &CheckPoint::SchedulerThreadAborted, + &TestCheckPoint::AfterSchedulerThreadAborted, + ]); + + static TASK_COUNT: Mutex = Mutex::new(0); + + #[derive(Debug)] + struct CountingHandler; + impl TaskHandler for CountingHandler { + fn handle( + _result: &mut Result<()>, + _timings: &mut ExecuteTimings, + _bank: &Arc, + _transaction: &SanitizedTransaction, + _index: usize, + _handler_context: &HandlerContext, + ) { + *TASK_COUNT.lock().unwrap() += 1; + } + } + + let GenesisConfigInfo { + genesis_config, + mint_keypair, + .. + } = create_genesis_config(10_000); + + let bank = Bank::new_for_tests(&genesis_config); + let (bank, _bank_forks) = setup_dummy_fork_graph(bank); + let ignored_prioritization_fee_cache = Arc::new(PrioritizationFeeCache::new(0u64)); + let pool = SchedulerPool::, _>::new( + None, + None, + None, + None, + ignored_prioritization_fee_cache, + ); + let context = SchedulingContext::new(bank.clone()); + let scheduler = pool.do_take_scheduler(context); + + for i in 0..10 { + let tx = + &SanitizedTransaction::from_transaction_for_tests(system_transaction::transfer( + &mint_keypair, + &solana_sdk::pubkey::new_rand(), + 2, + genesis_config.hash(), + )); + scheduler.schedule_execution(&(tx, i)).unwrap(); + } + + // Make sure ThreadManager::drop() is properly short-circuiting for non-aborting scheduler. + sleepless_testing::at(TestCheckPoint::BeforeThreadManagerDrop); + drop::>(scheduler); + sleepless_testing::at(TestCheckPoint::AfterSchedulerThreadAborted); + assert!(*TASK_COUNT.lock().unwrap() < 10); + } + +>>>>>>> d441c0f577 (Fix BankForks::new_rw_arc memory leak (#1893)) #[test] fn test_scheduler_pool_filo() { solana_logger::setup(); @@ -475,15 +1035,20 @@ mod tests { assert!(!child_bank.has_installed_scheduler()); } - fn setup_dummy_fork_graph(bank: Bank) -> Arc { + fn setup_dummy_fork_graph(bank: Bank) -> (Arc, Arc>) { let slot = bank.slot(); let bank_fork = BankForks::new_rw_arc(bank); let bank = bank_fork.read().unwrap().get(slot).unwrap(); +<<<<<<< HEAD bank.loaded_programs_cache .write() .unwrap() .set_fork_graph(bank_fork); bank +======= + bank.set_fork_graph_in_program_cache(Arc::downgrade(&bank_fork)); + (bank, bank_fork) +>>>>>>> d441c0f577 (Fix BankForks::new_rw_arc memory leak (#1893)) } #[test] @@ -502,7 +1067,7 @@ mod tests { genesis_config.hash(), )); let bank = Bank::new_for_tests(&genesis_config); - let bank = setup_dummy_fork_graph(bank); + let (bank, _bank_forks) = setup_dummy_fork_graph(bank); let ignored_prioritization_fee_cache = Arc::new(PrioritizationFeeCache::new(0u64)); let pool = DefaultSchedulerPool::new_dyn(None, None, None, ignored_prioritization_fee_cache); @@ -526,7 +1091,7 @@ mod tests { .. } = create_genesis_config(10_000); let bank = Bank::new_for_tests(&genesis_config); - let bank = setup_dummy_fork_graph(bank); + let (bank, _bank_forks) = setup_dummy_fork_graph(bank); let ignored_prioritization_fee_cache = Arc::new(PrioritizationFeeCache::new(0u64)); let pool = @@ -573,6 +1138,346 @@ mod tests { _timings )) ); +<<<<<<< HEAD +======= + + // Block solScCleaner until we see trashed schedler... + assert_eq!(pool_raw.trashed_scheduler_inners.lock().unwrap().len(), 1); + sleepless_testing::at(TestCheckPoint::BeforeTrashedSchedulerCleaned); + + // See the trashed scheduler gone only after solScCleaner did its job... + sleepless_testing::at(TestCheckPoint::AfterTrashedSchedulerCleaned); + assert_eq!(pool_raw.trashed_scheduler_inners.lock().unwrap().len(), 0); + } + + #[test] + fn test_scheduler_schedule_execution_failure_with_extra_tx() { + do_test_scheduler_schedule_execution_failure(true); + } + + #[test] + fn test_scheduler_schedule_execution_failure_without_extra_tx() { + do_test_scheduler_schedule_execution_failure(false); + } + + #[test] + #[should_panic(expected = "This panic should be propagated. (From: ")] + fn test_scheduler_schedule_execution_panic() { + solana_logger::setup(); + + #[derive(Debug)] + enum PanickingHanlderCheckPoint { + BeforeNotifiedPanic, + BeforeIgnoredPanic, + } + + let progress = sleepless_testing::setup(&[ + &TestCheckPoint::BeforeNewTask, + &CheckPoint::NewTask(0), + &PanickingHanlderCheckPoint::BeforeNotifiedPanic, + &CheckPoint::SchedulerThreadAborted, + &PanickingHanlderCheckPoint::BeforeIgnoredPanic, + &TestCheckPoint::BeforeEndSession, + ]); + + #[derive(Debug)] + struct PanickingHandler; + impl TaskHandler for PanickingHandler { + fn handle( + _result: &mut Result<()>, + _timings: &mut ExecuteTimings, + _bank: &Arc, + _transaction: &SanitizedTransaction, + index: usize, + _handler_context: &HandlerContext, + ) { + if index == 0 { + sleepless_testing::at(PanickingHanlderCheckPoint::BeforeNotifiedPanic); + } else if index == 1 { + sleepless_testing::at(PanickingHanlderCheckPoint::BeforeIgnoredPanic); + } else { + unreachable!(); + } + panic!("This panic should be propagated."); + } + } + + let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(10_000); + + let bank = Bank::new_for_tests(&genesis_config); + let (bank, _bank_forks) = setup_dummy_fork_graph(bank); + + // Use 2 transactions with different timings to deliberately cover the two code paths of + // notifying panics in the handler threads, taken conditionally depending on whether the + // scheduler thread has been aborted already or not. + const TX_COUNT: usize = 2; + + let ignored_prioritization_fee_cache = Arc::new(PrioritizationFeeCache::new(0u64)); + let pool = SchedulerPool::, _>::new_dyn( + Some(TX_COUNT), // fix to use exactly 2 handlers + None, + None, + None, + ignored_prioritization_fee_cache, + ); + let context = SchedulingContext::new(bank.clone()); + + let scheduler = pool.take_scheduler(context); + + for index in 0..TX_COUNT { + // Use 2 non-conflicting txes to exercise the channel disconnected case as well. + let tx = + &SanitizedTransaction::from_transaction_for_tests(system_transaction::transfer( + &Keypair::new(), + &solana_sdk::pubkey::new_rand(), + 1, + genesis_config.hash(), + )); + scheduler.schedule_execution(&(tx, index)).unwrap(); + } + // finally unblock the scheduler thread; otherwise the above schedule_execution could + // return SchedulerAborted... + sleepless_testing::at(TestCheckPoint::BeforeNewTask); + + sleepless_testing::at(TestCheckPoint::BeforeEndSession); + let bank = BankWithScheduler::new(bank, Some(scheduler)); + + // the outer .unwrap() will panic. so, drop progress now. + drop(progress); + bank.wait_for_completed_scheduler().unwrap().0.unwrap(); + } + + #[test] + fn test_scheduler_execution_failure_short_circuiting() { + solana_logger::setup(); + + let _progress = sleepless_testing::setup(&[ + &TestCheckPoint::BeforeNewTask, + &CheckPoint::NewTask(0), + &CheckPoint::TaskHandled(0), + &CheckPoint::SchedulerThreadAborted, + &TestCheckPoint::AfterSchedulerThreadAborted, + ]); + + static TASK_COUNT: Mutex = Mutex::new(0); + + #[derive(Debug)] + struct CountingFaultyHandler; + impl TaskHandler for CountingFaultyHandler { + fn handle( + result: &mut Result<()>, + _timings: &mut ExecuteTimings, + _bank: &Arc, + _transaction: &SanitizedTransaction, + index: usize, + _handler_context: &HandlerContext, + ) { + *TASK_COUNT.lock().unwrap() += 1; + if index == 1 { + *result = Err(TransactionError::AccountNotFound); + } + sleepless_testing::at(CheckPoint::TaskHandled(index)); + } + } + + let GenesisConfigInfo { + genesis_config, + mint_keypair, + .. + } = create_genesis_config(10_000); + + let bank = Bank::new_for_tests(&genesis_config); + let (bank, _bank_forks) = setup_dummy_fork_graph(bank); + let ignored_prioritization_fee_cache = Arc::new(PrioritizationFeeCache::new(0u64)); + let pool = SchedulerPool::, _>::new( + None, + None, + None, + None, + ignored_prioritization_fee_cache, + ); + let context = SchedulingContext::new(bank.clone()); + let scheduler = pool.do_take_scheduler(context); + + for i in 0..10 { + let tx = + &SanitizedTransaction::from_transaction_for_tests(system_transaction::transfer( + &mint_keypair, + &solana_sdk::pubkey::new_rand(), + 2, + genesis_config.hash(), + )); + scheduler.schedule_execution(&(tx, i)).unwrap(); + } + // finally unblock the scheduler thread; otherwise the above schedule_execution could + // return SchedulerAborted... + sleepless_testing::at(TestCheckPoint::BeforeNewTask); + + // Make sure bank.wait_for_completed_scheduler() is properly short-circuiting for aborting scheduler. + let bank = BankWithScheduler::new(bank, Some(Box::new(scheduler))); + assert_matches!( + bank.wait_for_completed_scheduler(), + Some((Err(TransactionError::AccountNotFound), _timings)) + ); + sleepless_testing::at(TestCheckPoint::AfterSchedulerThreadAborted); + assert!(*TASK_COUNT.lock().unwrap() < 10); + } + + #[test] + fn test_scheduler_schedule_execution_blocked() { + solana_logger::setup(); + + const STALLED_TRANSACTION_INDEX: usize = 0; + const BLOCKED_TRANSACTION_INDEX: usize = 1; + static LOCK_TO_STALL: Mutex<()> = Mutex::new(()); + + #[derive(Debug)] + struct StallingHandler; + impl TaskHandler for StallingHandler { + fn handle( + result: &mut Result<()>, + timings: &mut ExecuteTimings, + bank: &Arc, + transaction: &SanitizedTransaction, + index: usize, + handler_context: &HandlerContext, + ) { + match index { + STALLED_TRANSACTION_INDEX => *LOCK_TO_STALL.lock().unwrap(), + BLOCKED_TRANSACTION_INDEX => {} + _ => unreachable!(), + }; + DefaultTaskHandler::handle( + result, + timings, + bank, + transaction, + index, + handler_context, + ); + } + } + + let GenesisConfigInfo { + genesis_config, + mint_keypair, + .. + } = create_genesis_config(10_000); + + // tx0 and tx1 is definitely conflicting to write-lock the mint address + let tx0 = &SanitizedTransaction::from_transaction_for_tests(system_transaction::transfer( + &mint_keypair, + &solana_sdk::pubkey::new_rand(), + 2, + genesis_config.hash(), + )); + let tx1 = &SanitizedTransaction::from_transaction_for_tests(system_transaction::transfer( + &mint_keypair, + &solana_sdk::pubkey::new_rand(), + 2, + genesis_config.hash(), + )); + + let bank = Bank::new_for_tests(&genesis_config); + let (bank, _bank_forks) = setup_dummy_fork_graph(bank); + let ignored_prioritization_fee_cache = Arc::new(PrioritizationFeeCache::new(0u64)); + let pool = SchedulerPool::, _>::new_dyn( + None, + None, + None, + None, + ignored_prioritization_fee_cache, + ); + let context = SchedulingContext::new(bank.clone()); + + assert_eq!(bank.transaction_count(), 0); + let scheduler = pool.take_scheduler(context); + + // Stall handling tx0 and tx1 + let lock_to_stall = LOCK_TO_STALL.lock().unwrap(); + scheduler + .schedule_execution(&(tx0, STALLED_TRANSACTION_INDEX)) + .unwrap(); + scheduler + .schedule_execution(&(tx1, BLOCKED_TRANSACTION_INDEX)) + .unwrap(); + + // Wait a bit for the scheduler thread to decide to block tx1 + std::thread::sleep(std::time::Duration::from_secs(1)); + + // Resume handling by unlocking LOCK_TO_STALL + drop(lock_to_stall); + let bank = BankWithScheduler::new(bank, Some(scheduler)); + assert_matches!(bank.wait_for_completed_scheduler(), Some((Ok(()), _))); + assert_eq!(bank.transaction_count(), 2); + } + + #[test] + fn test_scheduler_mismatched_scheduling_context_race() { + solana_logger::setup(); + + #[derive(Debug)] + struct TaskAndContextChecker; + impl TaskHandler for TaskAndContextChecker { + fn handle( + _result: &mut Result<()>, + _timings: &mut ExecuteTimings, + bank: &Arc, + _transaction: &SanitizedTransaction, + index: usize, + _handler_context: &HandlerContext, + ) { + // The task index must always be matched to the slot. + assert_eq!(index as Slot, bank.slot()); + } + } + + let GenesisConfigInfo { + genesis_config, + mint_keypair, + .. + } = create_genesis_config(10_000); + + // Create two banks for two contexts + let bank0 = Bank::new_for_tests(&genesis_config); + let bank0 = setup_dummy_fork_graph(bank0).0; + let bank1 = Arc::new(Bank::new_from_parent( + bank0.clone(), + &Pubkey::default(), + bank0.slot().checked_add(1).unwrap(), + )); + + let ignored_prioritization_fee_cache = Arc::new(PrioritizationFeeCache::new(0u64)); + let pool = SchedulerPool::, _>::new( + Some(4), // spawn 4 threads + None, + None, + None, + ignored_prioritization_fee_cache, + ); + + // Create a dummy tx and two contexts + let dummy_tx = + &SanitizedTransaction::from_transaction_for_tests(system_transaction::transfer( + &mint_keypair, + &solana_sdk::pubkey::new_rand(), + 2, + genesis_config.hash(), + )); + let context0 = &SchedulingContext::new(bank0.clone()); + let context1 = &SchedulingContext::new(bank1.clone()); + + // Exercise the scheduler by busy-looping to expose the race condition + for (context, index) in [(context0, 0), (context1, 1)] + .into_iter() + .cycle() + .take(10000) + { + let scheduler = pool.take_scheduler(context.clone()); + scheduler.schedule_execution(&(dummy_tx, index)).unwrap(); + scheduler.wait_for_termination(false).1.return_to_pool(); + } +>>>>>>> d441c0f577 (Fix BankForks::new_rw_arc memory leak (#1893)) } #[derive(Debug)] @@ -715,7 +1620,7 @@ mod tests { slot.checked_add(1).unwrap(), ); } - let bank = setup_dummy_fork_graph(bank); + let (bank, _bank_forks) = setup_dummy_fork_graph(bank); let context = SchedulingContext::new(bank.clone()); let ignored_prioritization_fee_cache = Arc::new(PrioritizationFeeCache::new(0u64)); @@ -758,4 +1663,59 @@ mod tests { fn test_scheduler_schedule_execution_recent_blockhash_edge_case_without_race() { do_test_scheduler_schedule_execution_recent_blockhash_edge_case::(); } +<<<<<<< HEAD +======= + + #[test] + fn test_default_handler_count() { + for (detected, expected) in [(32, 8), (4, 1), (2, 1)] { + assert_eq!( + DefaultSchedulerPool::calculate_default_handler_count(Some(detected)), + expected + ); + } + assert_eq!( + DefaultSchedulerPool::calculate_default_handler_count(None), + 4 + ); + } + + // See comment in SchedulingStateMachine::create_task() for the justification of this test + #[test] + fn test_enfoced_get_account_locks_validation() { + solana_logger::setup(); + + let GenesisConfigInfo { + genesis_config, + ref mint_keypair, + .. + } = create_genesis_config(10_000); + let bank = Bank::new_for_tests(&genesis_config); + let (bank, _bank_forks) = &setup_dummy_fork_graph(bank); + + let mut tx = system_transaction::transfer( + mint_keypair, + &solana_sdk::pubkey::new_rand(), + 2, + genesis_config.hash(), + ); + // mangle the transfer tx to try to lock fee_payer (= mint_keypair) address twice! + tx.message.account_keys.push(tx.message.account_keys[0]); + let tx = &SanitizedTransaction::from_transaction_for_tests(tx); + + // this internally should call SanitizedTransaction::get_account_locks(). + let result = &mut Ok(()); + let timings = &mut ExecuteTimings::default(); + let prioritization_fee_cache = Arc::new(PrioritizationFeeCache::new(0u64)); + let handler_context = &HandlerContext { + log_messages_bytes_limit: None, + transaction_status_sender: None, + replay_vote_sender: None, + prioritization_fee_cache, + }; + + DefaultTaskHandler::handle(result, timings, bank, tx, 0, handler_context); + assert_matches!(result, Err(TransactionError::AccountLoadedTwice)); + } +>>>>>>> d441c0f577 (Fix BankForks::new_rw_arc memory leak (#1893)) }