diff --git a/ledger-tool/src/run.rs b/ledger-tool/src/run.rs index 66948268e9b536..125afef03a33fb 100644 --- a/ledger-tool/src/run.rs +++ b/ledger-tool/src/run.rs @@ -15,7 +15,9 @@ use { }, solana_program_runtime::{ invoke_context::InvokeContext, - loaded_programs::{LoadProgramMetrics, LoadedProgram, LoadedProgramType}, + loaded_programs::{ + LoadProgramMetrics, LoadedProgram, LoadedProgramType, DELAY_VISIBILITY_SLOT_OFFSET, + }, with_mock_invoke_context, }, solana_rbpf::{ @@ -416,6 +418,9 @@ pub fn run(ledger_path: &Path, matches: &ArgMatches<'_>) { bank.get_builtin_programs() ); + // Adding `DELAY_VISIBILITY_SLOT_OFFSET` to slots to accommodate for delay visibility of the program + let mut loaded_programs = + LoadedProgramsForTxBatch::new(bank.slot() + DELAY_VISIBILITY_SLOT_OFFSET); for key in cached_account_keys { let program = bank.load_program(&key, true).unwrap_or_else(|err| { // Create a tombstone for the program in the cache @@ -426,11 +431,9 @@ pub fn run(ledger_path: &Path, matches: &ArgMatches<'_>) { )) }); debug!("Loaded program {}", key); - invoke_context - .tx_executor_cache - .borrow_mut() - .set(key, program, false, false, 0); + loaded_programs.replenish(key, program); } + invoke_context.programs_loaded_for_tx_batch = Rc::new(RefCell::new(loaded_programs)); invoke_context .transaction_context diff --git a/program-runtime/src/invoke_context.rs b/program-runtime/src/invoke_context.rs index b7ddd1487942cb..3e968c42c75f53 100644 --- a/program-runtime/src/invoke_context.rs +++ b/program-runtime/src/invoke_context.rs @@ -3,9 +3,8 @@ use { accounts_data_meter::AccountsDataMeter, builtin_program::{BuiltinPrograms, ProcessInstructionWithContext}, compute_budget::ComputeBudget, - executor_cache::TransactionExecutorCache, ic_logger_msg, ic_msg, - loaded_programs::LoadedProgramType, + loaded_programs::{LoadedProgramType, LoadedProgramsForTxBatch}, log_collector::LogCollector, pre_account::PreAccount, stable_log, @@ -160,7 +159,9 @@ pub struct InvokeContext<'a> { current_compute_budget: ComputeBudget, compute_meter: RefCell, accounts_data_meter: AccountsDataMeter, - pub tx_executor_cache: Rc>, + pub programs_loaded_for_tx_batch: Rc>, + pub programs_modified_by_tx: Rc>, + pub programs_updated_only_for_global_cache: Rc>, pub feature_set: Arc, pub timings: ExecuteDetailsTimings, pub blockhash: Hash, @@ -178,7 +179,9 @@ impl<'a> InvokeContext<'a> { sysvar_cache: &'a SysvarCache, log_collector: Option>>, compute_budget: ComputeBudget, - tx_executor_cache: Rc>, + programs_loaded_for_tx_batch: Rc>, + programs_modified_by_tx: Rc>, + programs_updated_only_for_global_cache: Rc>, feature_set: Arc, blockhash: Hash, lamports_per_signature: u64, @@ -195,7 +198,9 @@ impl<'a> InvokeContext<'a> { compute_budget, compute_meter: RefCell::new(compute_budget.compute_unit_limit), accounts_data_meter: AccountsDataMeter::new(prev_accounts_data_len), - tx_executor_cache, + programs_loaded_for_tx_batch, + programs_modified_by_tx, + programs_updated_only_for_global_cache, feature_set, timings: ExecuteDetailsTimings::default(), blockhash, @@ -902,8 +907,8 @@ macro_rules! with_mock_invoke_context_and_builtin_programs { }, std::{cell::RefCell, rc::Rc, sync::Arc}, $crate::{ - compute_budget::ComputeBudget, executor_cache::TransactionExecutorCache, - invoke_context::InvokeContext, log_collector::LogCollector, + compute_budget::ComputeBudget, invoke_context::InvokeContext, + loaded_programs::LoadedProgramsForTxBatch, log_collector::LogCollector, sysvar_cache::SysvarCache, }, }; @@ -940,7 +945,9 @@ macro_rules! with_mock_invoke_context_and_builtin_programs { &sysvar_cache, Some(LogCollector::new_ref()), compute_budget, - Rc::new(RefCell::new(TransactionExecutorCache::default())), + Rc::new(RefCell::new(LoadedProgramsForTxBatch::default())), + Rc::new(RefCell::new(LoadedProgramsForTxBatch::default())), + Rc::new(RefCell::new(LoadedProgramsForTxBatch::default())), Arc::new(FeatureSet::all_enabled()), Hash::default(), 0, diff --git a/program-runtime/src/loaded_programs.rs b/program-runtime/src/loaded_programs.rs index 6fee2b3849005a..1ca8d63216351a 100644 --- a/program-runtime/src/loaded_programs.rs +++ b/program-runtime/src/loaded_programs.rs @@ -281,9 +281,16 @@ pub struct LoadedProgramsForTxBatch { } impl LoadedProgramsForTxBatch { + pub fn new(slot: Slot) -> Self { + Self { + entries: HashMap::new(), + slot, + } + } + /// Refill the cache with a single entry. It's typically called during transaction loading, and /// transaction processing (for program management instructions). - /// The replaces the existing entry (if any) with the provided entry. The return value contains + /// It replaces the existing entry (if any) with the provided entry. The return value contains /// `true` if an entry existed. /// The function also returns the newly inserted value. pub fn replenish( @@ -291,7 +298,6 @@ impl LoadedProgramsForTxBatch { key: Pubkey, entry: Arc, ) -> (bool, Arc) { - debug_assert!(entry.effective_slot <= self.slot); (self.entries.insert(key, entry.clone()).is_some(), entry) } @@ -314,6 +320,16 @@ impl LoadedProgramsForTxBatch { pub fn slot(&self) -> Slot { self.slot } + + pub fn set_slot_for_tests(&mut self, slot: Slot) { + self.slot = slot; + } + + pub fn merge(&mut self, other: &Self) { + other.entries.iter().for_each(|(key, entry)| { + self.replenish(*key, entry.clone()); + }) + } } pub enum LoadedProgramMatchCriteria { @@ -453,6 +469,12 @@ impl LoadedPrograms { return None; } + if matches!(entry.program, LoadedProgramType::Unloaded) { + // The program was unloaded. Consider it as missing, so it can be reloaded. + missing.push(key); + return None; + } + if current_slot >= entry.effective_slot { return Some((key, entry.clone())); } else if entry.is_implicit_delay_visibility_tombstone(current_slot) { @@ -483,6 +505,12 @@ impl LoadedPrograms { ) } + pub fn merge(&mut self, tx_batch_cache: &LoadedProgramsForTxBatch) { + tx_batch_cache.entries.iter().for_each(|(key, entry)| { + self.replenish(*key, entry.clone()); + }) + } + /// Unloads programs which were used infrequently pub fn sort_and_unload(&mut self, shrink_to: PercentageInteger) { let sorted_candidates: Vec<(Pubkey, Arc)> = self diff --git a/programs/bpf_loader/src/lib.rs b/programs/bpf_loader/src/lib.rs index 18e48080d2f141..33de855baa0dbe 100644 --- a/programs/bpf_loader/src/lib.rs +++ b/programs/bpf_loader/src/lib.rs @@ -10,7 +10,9 @@ use { compute_budget::ComputeBudget, ic_logger_msg, ic_msg, invoke_context::{BpfAllocator, InvokeContext, SyscallContext}, - loaded_programs::{LoadProgramMetrics, LoadedProgram, LoadedProgramType}, + loaded_programs::{ + LoadProgramMetrics, LoadedProgram, LoadedProgramType, DELAY_VISIBILITY_SLOT_OFFSET, + }, log_collector::LogCollector, stable_log, sysvar_cache::get_sysvar_with_account_check, @@ -91,7 +93,7 @@ pub fn load_program_from_bytes( register_syscalls_time.stop(); load_program_metrics.register_syscalls_us = register_syscalls_time.as_us(); let effective_slot = if feature_set.is_active(&delay_visibility_of_program_deployment::id()) { - deployment_slot.saturating_add(1) + deployment_slot.saturating_add(DELAY_VISIBILITY_SLOT_OFFSET) } else { deployment_slot }; @@ -190,12 +192,27 @@ pub fn load_program_from_account( Ok((loaded_program, Some(load_program_metrics))) } +fn find_program_in_cache( + invoke_context: &InvokeContext, + pubkey: &Pubkey, +) -> Option> { + // First lookup the cache of the programs modified by the current transaction. If not found, lookup + // the cache of the cache of the programs that are loaded for the transaction batch. + invoke_context + .programs_modified_by_tx + .borrow() + .find(pubkey) + .or_else(|| { + invoke_context + .programs_loaded_for_tx_batch + .borrow() + .find(pubkey) + }) +} + macro_rules! deploy_program { ($invoke_context:expr, $program_id:expr, $loader_key:expr, $account_size:expr, $slot:expr, $drop:expr, $new_programdata:expr $(,)?) => {{ - let delay_visibility_of_program_deployment = $invoke_context - .feature_set - .is_active(&delay_visibility_of_program_deployment::id()); let mut load_program_metrics = LoadProgramMetrics::default(); let executor = load_program_from_bytes( &$invoke_context.feature_set, @@ -209,20 +226,14 @@ macro_rules! deploy_program { true, /* reject_deployment_of_broken_elfs */ false, /* debugging_features */ )?; - if let Some(old_entry) = $invoke_context.tx_executor_cache.borrow().get(&$program_id) { + if let Some(old_entry) = find_program_in_cache($invoke_context, &$program_id) { let usage_counter = old_entry.usage_counter.load(Ordering::Relaxed); executor.usage_counter.store(usage_counter, Ordering::Relaxed); } $drop load_program_metrics.program_id = $program_id.to_string(); load_program_metrics.submit_datapoint(&mut $invoke_context.timings); - $invoke_context.tx_executor_cache.borrow_mut().set( - $program_id, - Arc::new(executor), - true, - delay_visibility_of_program_deployment, - $slot, - ); + $invoke_context.programs_modified_by_tx.borrow_mut().replenish($program_id, Arc::new(executor)); }}; } @@ -562,10 +573,7 @@ fn process_instruction_inner( } let mut get_or_create_executor_time = Measure::start("get_or_create_executor_time"); - let executor = invoke_context - .tx_executor_cache - .borrow() - .get(program_account.get_key()) + let executor = find_program_in_cache(invoke_context, program_account.get_key()) .ok_or(InstructionError::InvalidAccountData)?; if executor.is_tombstone() { @@ -1265,15 +1273,32 @@ fn process_loader_upgradeable_instruction( instruction_context, &log_collector, )?; + let clock = invoke_context.get_sysvar_cache().get_clock()?; if invoke_context .feature_set .is_active(&delay_visibility_of_program_deployment::id()) { - let clock = invoke_context.get_sysvar_cache().get_clock()?; invoke_context - .tx_executor_cache + .programs_modified_by_tx .borrow_mut() - .set_tombstone(program_key, clock.slot); + .replenish( + program_key, + Arc::new(LoadedProgram::new_tombstone( + clock.slot, + LoadedProgramType::Closed, + )), + ); + } else { + invoke_context + .programs_updated_only_for_global_cache + .borrow_mut() + .replenish( + program_key, + Arc::new(LoadedProgram::new_tombstone( + clock.slot, + LoadedProgramType::Closed, + )), + ); } } _ => { @@ -1661,7 +1686,10 @@ fn execute<'a, 'b: 'a>( } pub mod test_utils { - use {super::*, solana_sdk::account::ReadableAccount}; + use { + super::*, solana_program_runtime::loaded_programs::DELAY_VISIBILITY_SLOT_OFFSET, + solana_sdk::account::ReadableAccount, + }; pub fn load_all_invoked_programs(invoke_context: &mut InvokeContext) { let num_accounts = invoke_context.transaction_context.get_number_of_accounts(); @@ -1693,8 +1721,9 @@ pub mod test_utils { true, false, ) { - let mut cache = invoke_context.tx_executor_cache.borrow_mut(); - cache.set(*pubkey, Arc::new(loaded_program), true, false, 0) + let mut cache = invoke_context.programs_modified_by_tx.borrow_mut(); + cache.set_slot_for_tests(DELAY_VISIBILITY_SLOT_OFFSET); + cache.replenish(*pubkey, Arc::new(loaded_program)); } } } @@ -4096,13 +4125,10 @@ mod tests { maybe_expiration_slot: None, usage_counter: AtomicU64::new(100), }; - invoke_context.tx_executor_cache.borrow_mut().set( - program_id, - Arc::new(program), - false, - false, - 0, - ); + invoke_context + .programs_modified_by_tx + .borrow_mut() + .replenish(program_id, Arc::new(program)); assert!(matches!( deploy_test_program(&mut invoke_context, program_id,), @@ -4110,9 +4136,9 @@ mod tests { )); let updated_program = invoke_context - .tx_executor_cache + .programs_modified_by_tx .borrow() - .get(&program_id) + .find(&program_id) .expect("Didn't find upgraded program in the cache"); assert_eq!(updated_program.deployment_slot, 2); @@ -4132,13 +4158,10 @@ mod tests { maybe_expiration_slot: None, usage_counter: AtomicU64::new(100), }; - invoke_context.tx_executor_cache.borrow_mut().set( - program_id, - Arc::new(program), - false, - false, - 0, - ); + invoke_context + .programs_modified_by_tx + .borrow_mut() + .replenish(program_id, Arc::new(program)); let program_id2 = Pubkey::new_unique(); assert!(matches!( @@ -4147,9 +4170,9 @@ mod tests { )); let program2 = invoke_context - .tx_executor_cache + .programs_modified_by_tx .borrow() - .get(&program_id2) + .find(&program_id2) .expect("Didn't find upgraded program in the cache"); assert_eq!(program2.deployment_slot, 2); diff --git a/programs/sbf/tests/programs.rs b/programs/sbf/tests/programs.rs index 07417d3c0fab1c..839e83eb9be9df 100644 --- a/programs/sbf/tests/programs.rs +++ b/programs/sbf/tests/programs.rs @@ -379,7 +379,10 @@ fn test_program_sbf_loader_deprecated() { let bank = Bank::new_for_tests(&genesis_config); let program_id = create_program(&bank, &bpf_loader_deprecated::id(), program); - let bank_client = BankClient::new(bank); + let mut bank_client = BankClient::new(bank); + bank_client + .advance_slot(1, &Pubkey::default()) + .expect("Failed to advance the slot"); let account_metas = vec![AccountMeta::new(mint_keypair.pubkey(), true)]; let instruction = Instruction::new_with_bytes(program_id, &[1], account_metas); let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction); @@ -2065,8 +2068,10 @@ fn test_program_sbf_invoke_in_same_tx_as_redeployment() { ], ); + // load_upgradeable_program sets clock sysvar to 1, which causes the program to be effective + // after 2 slots. So we need to advance the bank client by 2 slots here. let bank = bank_client - .advance_slot(1, &Pubkey::default()) + .advance_slot(2, &Pubkey::default()) .expect("Failed to advance slot"); // Prepare redeployment @@ -2160,8 +2165,10 @@ fn test_program_sbf_invoke_in_same_tx_as_undeployment() { ], ); + // load_upgradeable_program sets clock sysvar to 1, which causes the program to be effective + // after 2 slots. So we need to advance the bank client by 2 slots here. let bank = bank_client - .advance_slot(1, &Pubkey::default()) + .advance_slot(2, &Pubkey::default()) .expect("Failed to advance slot"); // Prepare undeployment @@ -3904,7 +3911,10 @@ fn test_program_sbf_inner_instruction_alignment_checks() { // invoke unaligned program, which will call aligned program twice, // unaligned should be allowed once invoke completes - let bank_client = BankClient::new(bank); + let mut bank_client = BankClient::new(bank); + bank_client + .advance_slot(1, &Pubkey::default()) + .expect("Failed to advance the slot"); let mut instruction = Instruction::new_with_bytes( inner_instruction_alignment_check, &[0], diff --git a/runtime/src/accounts.rs b/runtime/src/accounts.rs index 1d6b88930553ee..d630c899c58708 100644 --- a/runtime/src/accounts.rs +++ b/runtime/src/accounts.rs @@ -26,7 +26,7 @@ use { solana_address_lookup_table_program::{error::AddressLookupError, state::AddressLookupTable}, solana_program_runtime::{ compute_budget::{self, ComputeBudget}, - loaded_programs::{LoadedProgram, LoadedProgramType}, + loaded_programs::{LoadedProgram, LoadedProgramType, LoadedProgramsForTxBatch}, }, solana_sdk::{ account::{Account, AccountSharedData, ReadableAccount, WritableAccount}, @@ -337,7 +337,7 @@ impl Accounts { feature_set: &FeatureSet, account_overrides: Option<&AccountOverrides>, program_accounts: &HashMap, - loaded_programs: &HashMap>, + loaded_programs: &LoadedProgramsForTxBatch, ) -> Result { // NOTE: this check will never fail because `tx` is sanitized if tx.signatures().is_empty() && fee != 0 { @@ -390,7 +390,7 @@ impl Accounts { (account_override.data().len(), account_override.clone(), 0) } else if let Some(program) = (!instruction_account && !message.is_writable(i)) .then_some(()) - .and_then(|_| loaded_programs.get(key)) + .and_then(|_| loaded_programs.find(key)) { // This condition block does special handling for accounts that are passed // as instruction account to any of the instructions in the transaction. @@ -401,7 +401,7 @@ impl Accounts { Self::account_shared_data_from_program( key, feature_set, - program, + program.as_ref(), program_accounts, ) .map(|program_account| (program.account_size, program_account, 0))? @@ -695,7 +695,7 @@ impl Accounts { fee_structure: &FeeStructure, account_overrides: Option<&AccountOverrides>, program_accounts: &HashMap, - loaded_programs: &HashMap>, + loaded_programs: &LoadedProgramsForTxBatch, ) -> Vec { txs.iter() .zip(lock_results) @@ -1470,9 +1470,8 @@ mod tests { }, assert_matches::assert_matches, solana_address_lookup_table_program::state::LookupTableMeta, - solana_program_runtime::{ - executor_cache::TransactionExecutorCache, - prioritization_fee::{PrioritizationFeeDetails, PrioritizationFeeType}, + solana_program_runtime::prioritization_fee::{ + PrioritizationFeeDetails, PrioritizationFeeType, }, solana_sdk::{ account::{AccountSharedData, WritableAccount}, @@ -1525,7 +1524,10 @@ mod tests { executed_units: 0, accounts_data_len_delta: 0, }, - tx_executor_cache: Rc::new(RefCell::new(TransactionExecutorCache::default())), + programs_modified_by_tx: Rc::new(RefCell::new(LoadedProgramsForTxBatch::default())), + programs_updated_only_for_global_cache: Rc::new(RefCell::new( + LoadedProgramsForTxBatch::default(), + )), } } @@ -1563,7 +1565,7 @@ mod tests { fee_structure, None, &HashMap::new(), - &HashMap::new(), + &LoadedProgramsForTxBatch::default(), ) } @@ -3367,7 +3369,7 @@ mod tests { &FeeStructure::default(), account_overrides, &HashMap::new(), - &HashMap::new(), + &LoadedProgramsForTxBatch::default(), ) } diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index 04872d9fcffc90..a58fcb04d9623f 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -33,6 +33,8 @@ //! It offers a high-level API that signs transactions //! on behalf of the caller, and a low-level API for when they have //! already been signed and verified. +#[cfg(test)] +use solana_program_runtime::executor_cache::TransactionExecutorCache; #[allow(deprecated)] use solana_sdk::recent_blockhashes_account; pub use solana_sdk::reward_type::RewardType; @@ -95,7 +97,7 @@ use { accounts_data_meter::MAX_ACCOUNTS_DATA_LEN, builtin_program::BuiltinPrograms, compute_budget::{self, ComputeBudget}, - executor_cache::{BankExecutorCache, TransactionExecutorCache, MAX_CACHED_EXECUTORS}, + executor_cache::{BankExecutorCache, MAX_CACHED_EXECUTORS}, loaded_programs::{ LoadedProgram, LoadedProgramMatchCriteria, LoadedProgramType, LoadedPrograms, LoadedProgramsForTxBatch, WorkingSlot, @@ -321,7 +323,8 @@ pub struct TransactionExecutionDetails { pub enum TransactionExecutionResult { Executed { details: TransactionExecutionDetails, - tx_executor_cache: Rc>, + programs_modified_by_tx: Rc>, + programs_updated_only_for_global_cache: Rc>, }, NotExecuted(TransactionError), } @@ -4108,6 +4111,7 @@ impl Bank { } /// Add executors back to the bank's cache if they were missing and not re-/deployed + #[cfg(test)] fn store_executors_which_added_to_the_cache( &self, tx_executor_cache: &RefCell, @@ -4124,6 +4128,7 @@ impl Bank { } /// Add re-/deployed executors to the bank's cache + #[cfg(test)] fn store_executors_which_were_deployed( &self, tx_executor_cache: &RefCell, @@ -4174,7 +4179,6 @@ impl Bank { } } - #[allow(dead_code)] // Preparation for BankExecutorCache rework pub fn load_program( &self, pubkey: &Pubkey, @@ -4263,7 +4267,7 @@ impl Bank { timings: &mut ExecuteTimings, error_counters: &mut TransactionErrorMetrics, log_messages_bytes_limit: Option, - tx_executor_cache: Rc>, + programs_loaded_for_tx_batch: Rc>, ) -> TransactionExecutionResult { let prev_accounts_data_len = self.load_accounts_data_size(); let transaction_accounts = std::mem::take(&mut loaded_transaction.accounts); @@ -4313,7 +4317,10 @@ impl Bank { let (blockhash, lamports_per_signature) = self.last_blockhash_and_lamports_per_signature(); let mut executed_units = 0u64; - + let programs_modified_by_tx = + Rc::new(RefCell::new(LoadedProgramsForTxBatch::new(self.slot))); + let programs_updated_only_for_global_cache = + Rc::new(RefCell::new(LoadedProgramsForTxBatch::new(self.slot))); let mut process_message_time = Measure::start("process_message_time"); let process_result = MessageProcessor::process_message( &self.builtin_programs, @@ -4322,7 +4329,9 @@ impl Bank { &mut transaction_context, self.rent_collector.rent, log_collector.clone(), - tx_executor_cache.clone(), + programs_loaded_for_tx_batch, + programs_modified_by_tx.clone(), + programs_updated_only_for_global_cache.clone(), self.feature_set.clone(), compute_budget, timings, @@ -4339,15 +4348,6 @@ impl Bank { process_message_time.as_us() ); - let mut store_executors_which_added_to_the_cache_time = - Measure::start("store_executors_which_added_to_the_cache_time"); - self.store_executors_which_added_to_the_cache(&tx_executor_cache); - store_executors_which_added_to_the_cache_time.stop(); - saturating_add_assign!( - timings.execute_accessories.update_executors_us, - store_executors_which_added_to_the_cache_time.as_us() - ); - let status = process_result .and_then(|info| { let post_account_state_info = @@ -4435,7 +4435,8 @@ impl Bank { executed_units, accounts_data_len_delta, }, - tx_executor_cache, + programs_modified_by_tx, + programs_updated_only_for_global_cache, } } @@ -4448,8 +4449,7 @@ impl Bank { || self.cluster_type() != ClusterType::MainnetBeta } - #[allow(dead_code)] // Preparation for BankExecutorCache rework - fn load_and_get_programs_from_cache( + fn replenish_program_cache( &self, program_accounts_map: &HashMap, ) -> LoadedProgramsForTxBatch { @@ -4507,6 +4507,7 @@ impl Bank { loaded_programs_for_txs } + #[allow(dead_code)] // Preparation for BankExecutorCache rework fn replenish_executor_cache( &self, program_accounts_map: &HashMap, @@ -4626,17 +4627,9 @@ impl Bank { &self.blockhash_queue.read().unwrap(), ); - // The following code is currently commented out. This is how the new cache will - // finally be used, once rest of the code blocks are in place. - /* - let loaded_programs_map = - self.load_and_get_programs_from_cache(&program_accounts_map); - */ - let loaded_programs_map = self.replenish_executor_cache(&program_accounts_map); - - let tx_executor_cache = Rc::new(RefCell::new(TransactionExecutorCache::new( - loaded_programs_map.clone().into_iter(), - ))); + let programs_loaded_for_tx_batch = Rc::new(RefCell::new( + self.replenish_program_cache(&program_accounts_map), + )); let mut load_time = Measure::start("accounts_load"); let mut loaded_transactions = self.rc.accounts.load_accounts( @@ -4650,7 +4643,7 @@ impl Bank { &self.fee_structure, account_overrides, &program_accounts_map, - &loaded_programs_map, + &programs_loaded_for_tx_batch.borrow(), ); load_time.stop(); @@ -4696,7 +4689,7 @@ impl Bank { compute_budget }; - self.execute_loaded_transaction( + let result = self.execute_loaded_transaction( tx, loaded_transaction, compute_budget, @@ -4707,8 +4700,25 @@ impl Bank { timings, &mut error_counters, log_messages_bytes_limit, - tx_executor_cache.clone(), - ) + programs_loaded_for_tx_batch.clone(), + ); + + if let TransactionExecutionResult::Executed { + details, + programs_modified_by_tx, + programs_updated_only_for_global_cache: _, + } = &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() { + programs_loaded_for_tx_batch + .borrow_mut() + .merge(&programs_modified_by_tx.borrow()); + } + } + + result } }) .collect(); @@ -5193,11 +5203,14 @@ impl Bank { for execution_result in &execution_results { if let TransactionExecutionResult::Executed { details, - tx_executor_cache, + programs_modified_by_tx, + programs_updated_only_for_global_cache, } = execution_result { if details.status.is_ok() { - self.store_executors_which_were_deployed(tx_executor_cache); + let mut cache = self.loaded_programs_cache.write().unwrap(); + cache.merge(&programs_modified_by_tx.borrow()); + cache.merge(&programs_updated_only_for_global_cache.borrow()); } } } diff --git a/runtime/src/bank/tests.rs b/runtime/src/bank/tests.rs index aa4b917a454363..6dcb7bbf31da29 100644 --- a/runtime/src/bank/tests.rs +++ b/runtime/src/bank/tests.rs @@ -38,7 +38,7 @@ use { declare_process_instruction, executor_cache::TransactionExecutorCache, invoke_context::mock_process_instruction, - loaded_programs::{LoadedProgram, LoadedProgramType}, + loaded_programs::{LoadedProgram, LoadedProgramType, DELAY_VISIBILITY_SLOT_OFFSET}, prioritization_fee::{PrioritizationFeeDetails, PrioritizationFeeType}, timings::ExecuteTimings, }, @@ -176,7 +176,10 @@ fn new_execution_result( executed_units: 0, accounts_data_len_delta: 0, }, - tx_executor_cache: Rc::new(RefCell::new(TransactionExecutorCache::default())), + programs_modified_by_tx: Rc::new(RefCell::new(LoadedProgramsForTxBatch::default())), + programs_updated_only_for_global_cache: Rc::new(RefCell::new( + LoadedProgramsForTxBatch::default(), + )), } } @@ -7771,14 +7774,9 @@ fn test_bpf_loader_upgradeable_deploy_with_max_len() { Ok(()), solana_bpf_loader_program::process_instruction, |invoke_context| { - let mut cache = invoke_context.tx_executor_cache.borrow_mut(); - cache.set( - program_keypair.pubkey(), - loaded_program.clone(), - true, - false, - 0, - ); + let mut cache = invoke_context.programs_modified_by_tx.borrow_mut(); + cache.set_slot_for_tests(bank.slot() + DELAY_VISIBILITY_SLOT_OFFSET); + cache.replenish(program_keypair.pubkey(), loaded_program.clone()); }, |_invoke_context| {}, ); @@ -11506,7 +11504,7 @@ fn test_rent_state_list_len() { &FeeStructure::default(), None, &HashMap::new(), - &HashMap::new(), + &LoadedProgramsForTxBatch::default(), ); let compute_budget = bank.runtime_config.compute_budget.unwrap_or_else(|| { diff --git a/runtime/src/message_processor.rs b/runtime/src/message_processor.rs index 044314fdf78fbc..828e994ebe7cdc 100644 --- a/runtime/src/message_processor.rs +++ b/runtime/src/message_processor.rs @@ -4,8 +4,8 @@ use { solana_program_runtime::{ builtin_program::BuiltinPrograms, compute_budget::ComputeBudget, - executor_cache::TransactionExecutorCache, invoke_context::InvokeContext, + loaded_programs::LoadedProgramsForTxBatch, log_collector::LogCollector, sysvar_cache::SysvarCache, timings::{ExecuteDetailsTimings, ExecuteTimings}, @@ -58,7 +58,9 @@ impl MessageProcessor { transaction_context: &mut TransactionContext, rent: Rent, log_collector: Option>>, - tx_executor_cache: Rc>, + programs_loaded_for_tx_batch: Rc>, + programs_modified_by_tx: Rc>, + programs_updated_only_for_global_cache: Rc>, feature_set: Arc, compute_budget: ComputeBudget, timings: &mut ExecuteTimings, @@ -75,7 +77,9 @@ impl MessageProcessor { sysvar_cache, log_collector, compute_budget, - tx_executor_cache, + programs_loaded_for_tx_batch, + programs_modified_by_tx, + programs_updated_only_for_global_cache, feature_set, blockhash, lamports_per_signature, @@ -272,7 +276,8 @@ mod tests { let mut transaction_context = TransactionContext::new(accounts, Some(Rent::default()), 1, 3); let program_indices = vec![vec![2]]; - let tx_executor_cache = Rc::new(RefCell::new(TransactionExecutorCache::default())); + let programs_loaded_for_tx_batch = + Rc::new(RefCell::new(LoadedProgramsForTxBatch::default())); let account_keys = (0..transaction_context.get_number_of_accounts()) .map(|index| { *transaction_context @@ -308,7 +313,9 @@ mod tests { &mut transaction_context, rent_collector.rent, None, - tx_executor_cache.clone(), + programs_loaded_for_tx_batch.clone(), + Rc::new(RefCell::new(LoadedProgramsForTxBatch::default())), + Rc::new(RefCell::new(LoadedProgramsForTxBatch::default())), Arc::new(FeatureSet::all_enabled()), ComputeBudget::default(), &mut ExecuteTimings::default(), @@ -358,7 +365,9 @@ mod tests { &mut transaction_context, rent_collector.rent, None, - tx_executor_cache.clone(), + programs_loaded_for_tx_batch.clone(), + Rc::new(RefCell::new(LoadedProgramsForTxBatch::default())), + Rc::new(RefCell::new(LoadedProgramsForTxBatch::default())), Arc::new(FeatureSet::all_enabled()), ComputeBudget::default(), &mut ExecuteTimings::default(), @@ -398,7 +407,9 @@ mod tests { &mut transaction_context, rent_collector.rent, None, - tx_executor_cache, + programs_loaded_for_tx_batch, + Rc::new(RefCell::new(LoadedProgramsForTxBatch::default())), + Rc::new(RefCell::new(LoadedProgramsForTxBatch::default())), Arc::new(FeatureSet::all_enabled()), ComputeBudget::default(), &mut ExecuteTimings::default(), @@ -496,7 +507,8 @@ mod tests { let mut transaction_context = TransactionContext::new(accounts, Some(Rent::default()), 1, 3); let program_indices = vec![vec![2]]; - let tx_executor_cache = Rc::new(RefCell::new(TransactionExecutorCache::default())); + let programs_loaded_for_tx_batch = + Rc::new(RefCell::new(LoadedProgramsForTxBatch::default())); let account_metas = vec![ AccountMeta::new( *transaction_context.get_key_of_account_at_index(0).unwrap(), @@ -529,7 +541,9 @@ mod tests { &mut transaction_context, rent_collector.rent, None, - tx_executor_cache.clone(), + programs_loaded_for_tx_batch.clone(), + Rc::new(RefCell::new(LoadedProgramsForTxBatch::default())), + Rc::new(RefCell::new(LoadedProgramsForTxBatch::default())), Arc::new(FeatureSet::all_enabled()), ComputeBudget::default(), &mut ExecuteTimings::default(), @@ -563,7 +577,9 @@ mod tests { &mut transaction_context, rent_collector.rent, None, - tx_executor_cache.clone(), + programs_loaded_for_tx_batch.clone(), + Rc::new(RefCell::new(LoadedProgramsForTxBatch::default())), + Rc::new(RefCell::new(LoadedProgramsForTxBatch::default())), Arc::new(FeatureSet::all_enabled()), ComputeBudget::default(), &mut ExecuteTimings::default(), @@ -594,7 +610,9 @@ mod tests { &mut transaction_context, rent_collector.rent, None, - tx_executor_cache, + programs_loaded_for_tx_batch, + Rc::new(RefCell::new(LoadedProgramsForTxBatch::default())), + Rc::new(RefCell::new(LoadedProgramsForTxBatch::default())), Arc::new(FeatureSet::all_enabled()), ComputeBudget::default(), &mut ExecuteTimings::default(), @@ -668,7 +686,9 @@ mod tests { &mut transaction_context, RentCollector::default().rent, None, - Rc::new(RefCell::new(TransactionExecutorCache::default())), + Rc::new(RefCell::new(LoadedProgramsForTxBatch::default())), + Rc::new(RefCell::new(LoadedProgramsForTxBatch::default())), + Rc::new(RefCell::new(LoadedProgramsForTxBatch::default())), Arc::new(FeatureSet::all_enabled()), ComputeBudget::default(), &mut ExecuteTimings::default(),