diff --git a/program-runtime/src/loaded_programs.rs b/program-runtime/src/loaded_programs.rs index 66a28d60db..11e1996506 100644 --- a/program-runtime/src/loaded_programs.rs +++ b/program-runtime/src/loaded_programs.rs @@ -262,6 +262,38 @@ pub struct LoadedPrograms { entries: HashMap>>, } +#[derive(Debug, Default)] +pub struct LoadedProgramsForTxBatch { + /// Pubkey is the address of a program. + /// LoadedProgram is the corresponding program entry valid for the slot in which a transaction is being executed. + entries: HashMap>, + slot: Slot, +} + +impl LoadedProgramsForTxBatch { + /// 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 + /// `true` if an entry existed. + /// The function also returns the newly inserted value. + pub fn replenish( + &mut self, + key: Pubkey, + entry: Arc, + ) -> (bool, Arc) { + debug_assert!(entry.effective_slot <= self.slot); + (self.entries.insert(key, entry.clone()).is_some(), entry) + } + + pub fn find(&self, key: Pubkey) -> Option> { + self.entries.get(&key).cloned() + } + + pub fn slot(&self) -> Slot { + self.slot + } +} + #[cfg(RUSTC_WITH_SPECIALIZATION)] impl solana_frozen_abi::abi_example::AbiExample for LoadedPrograms { fn example() -> Self { @@ -373,7 +405,7 @@ impl LoadedPrograms { &self, working_slot: &S, keys: impl Iterator, - ) -> (HashMap>, Vec) { + ) -> (LoadedProgramsForTxBatch, Vec) { let mut missing = Vec::new(); let found = keys .filter_map(|(key, match_criteria)| { @@ -410,7 +442,13 @@ impl LoadedPrograms { None }) .collect(); - (found, missing) + ( + LoadedProgramsForTxBatch { + entries: found, + slot: working_slot.current_slot(), + }, + missing, + ) } /// Evicts programs which were used infrequently @@ -528,13 +566,12 @@ mod tests { use { crate::loaded_programs::{ BlockRelation, ForkGraph, LoadedProgram, LoadedProgramMatchCriteria, LoadedProgramType, - LoadedPrograms, WorkingSlot, + LoadedPrograms, LoadedProgramsForTxBatch, WorkingSlot, }, percentage::Percentage, solana_rbpf::vm::BuiltInProgram, solana_sdk::{clock::Slot, pubkey::Pubkey}, std::{ - collections::HashMap, ops::ControlFlow, sync::{ atomic::{AtomicU64, Ordering}, @@ -1106,12 +1143,14 @@ mod tests { } fn match_slot( - table: &HashMap>, + table: &LoadedProgramsForTxBatch, program: &Pubkey, deployment_slot: Slot, + working_slot: Slot, ) -> bool { + assert_eq!(table.slot, working_slot); table - .get(program) + .find(*program) .map(|entry| entry.deployment_slot == deployment_slot) .unwrap_or(false) } @@ -1189,8 +1228,8 @@ mod tests { .into_iter(), ); - assert!(match_slot(&found, &program1, 20)); - assert!(match_slot(&found, &program4, 0)); + assert!(match_slot(&found, &program1, 20, 22)); + assert!(match_slot(&found, &program4, 0, 22)); assert!(missing.contains(&program2)); assert!(missing.contains(&program3)); @@ -1208,11 +1247,11 @@ mod tests { .into_iter(), ); - assert!(match_slot(&found, &program1, 0)); - assert!(match_slot(&found, &program2, 11)); + assert!(match_slot(&found, &program1, 0, 16)); + assert!(match_slot(&found, &program2, 11, 16)); // The effective slot of program4 deployed in slot 15 is 19. So it should not be usable in slot 16. - assert!(match_slot(&found, &program4, 5)); + assert!(match_slot(&found, &program4, 5, 16)); assert!(missing.contains(&program3)); @@ -1229,11 +1268,11 @@ mod tests { .into_iter(), ); - assert!(match_slot(&found, &program1, 0)); - assert!(match_slot(&found, &program2, 11)); + assert!(match_slot(&found, &program1, 0, 18)); + assert!(match_slot(&found, &program2, 11, 18)); // The effective slot of program4 deployed in slot 15 is 18. So it should be usable in slot 18. - assert!(match_slot(&found, &program4, 15)); + assert!(match_slot(&found, &program4, 15, 18)); assert!(missing.contains(&program3)); @@ -1250,11 +1289,11 @@ mod tests { .into_iter(), ); - assert!(match_slot(&found, &program1, 0)); - assert!(match_slot(&found, &program2, 11)); + assert!(match_slot(&found, &program1, 0, 23)); + assert!(match_slot(&found, &program2, 11, 23)); // The effective slot of program4 deployed in slot 15 is 19. So it should be usable in slot 23. - assert!(match_slot(&found, &program4, 15)); + assert!(match_slot(&found, &program4, 15, 23)); assert!(missing.contains(&program3)); @@ -1271,9 +1310,9 @@ mod tests { .into_iter(), ); - assert!(match_slot(&found, &program1, 0)); - assert!(match_slot(&found, &program2, 5)); - assert!(match_slot(&found, &program4, 5)); + assert!(match_slot(&found, &program1, 0, 11)); + assert!(match_slot(&found, &program2, 5, 11)); + assert!(match_slot(&found, &program4, 5, 11)); assert!(missing.contains(&program3)); @@ -1301,10 +1340,10 @@ mod tests { .into_iter(), ); - assert!(match_slot(&found, &program1, 0)); - assert!(match_slot(&found, &program2, 11)); + assert!(match_slot(&found, &program1, 0, 19)); + assert!(match_slot(&found, &program2, 11, 19)); // Program4 deployed at slot 19 should not be expired yet - assert!(match_slot(&found, &program4, 19)); + assert!(match_slot(&found, &program4, 19, 19)); assert!(missing.contains(&program3)); @@ -1322,8 +1361,8 @@ mod tests { .into_iter(), ); - assert!(match_slot(&found, &program1, 0)); - assert!(match_slot(&found, &program2, 11)); + assert!(match_slot(&found, &program1, 0, 21)); + assert!(match_slot(&found, &program2, 11, 21)); assert!(missing.contains(&program3)); assert!(missing.contains(&program4)); @@ -1364,8 +1403,8 @@ mod tests { ); // Since the fork was pruned, we should not find the entry deployed at slot 20. - assert!(match_slot(&found, &program1, 0)); - assert!(match_slot(&found, &program4, 0)); + assert!(match_slot(&found, &program1, 0, 22)); + assert!(match_slot(&found, &program4, 0, 22)); assert!(missing.contains(&program2)); assert!(missing.contains(&program3)); @@ -1383,10 +1422,10 @@ mod tests { .into_iter(), ); - assert!(match_slot(&found, &program1, 0)); - assert!(match_slot(&found, &program2, 11)); - assert!(match_slot(&found, &program3, 25)); - assert!(match_slot(&found, &program4, 5)); + assert!(match_slot(&found, &program1, 0, 27)); + assert!(match_slot(&found, &program2, 11, 27)); + assert!(match_slot(&found, &program3, 25, 27)); + assert!(match_slot(&found, &program4, 5, 27)); cache.prune(&fork_graph, 15); @@ -1418,9 +1457,9 @@ mod tests { .into_iter(), ); - assert!(match_slot(&found, &program1, 0)); - assert!(match_slot(&found, &program2, 11)); - assert!(match_slot(&found, &program4, 5)); + assert!(match_slot(&found, &program1, 0, 27)); + assert!(match_slot(&found, &program2, 11, 27)); + assert!(match_slot(&found, &program4, 5, 27)); // program3 was deployed on slot 25, which has been pruned assert!(missing.contains(&program3)); @@ -1473,8 +1512,8 @@ mod tests { .into_iter(), ); - assert!(match_slot(&found, &program1, 0)); - assert!(match_slot(&found, &program2, 11)); + assert!(match_slot(&found, &program1, 0, 12)); + assert!(match_slot(&found, &program2, 11, 12)); assert!(missing.contains(&program3)); @@ -1495,7 +1534,7 @@ mod tests { .into_iter(), ); - assert!(match_slot(&found, &program2, 11)); + assert!(match_slot(&found, &program2, 11, 12)); assert!(missing.contains(&program1)); assert!(missing.contains(&program3)); @@ -1560,8 +1599,8 @@ mod tests { ); // Program1 deployed at slot 11 should not be expired yet - assert!(match_slot(&found, &program1, 11)); - assert!(match_slot(&found, &program2, 11)); + assert!(match_slot(&found, &program1, 11, 12)); + assert!(match_slot(&found, &program2, 11, 12)); assert!(missing.contains(&program3)); @@ -1578,7 +1617,7 @@ mod tests { .into_iter(), ); - assert!(match_slot(&found, &program2, 11)); + assert!(match_slot(&found, &program2, 11, 15)); assert!(missing.contains(&program1)); assert!(missing.contains(&program3)); @@ -1642,6 +1681,7 @@ mod tests { // The cache should have the program deployed at slot 0 assert_eq!( found + .entries .get(&program1) .expect("Did not find the program") .deployment_slot, diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index 721c64eedd..b3e36ba307 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -97,7 +97,10 @@ use { builtin_program::{BuiltinProgram, BuiltinPrograms, ProcessInstructionWithContext}, compute_budget::{self, ComputeBudget}, executor_cache::{BankExecutorCache, TransactionExecutorCache, MAX_CACHED_EXECUTORS}, - loaded_programs::{LoadedProgram, LoadedProgramType, LoadedPrograms, WorkingSlot}, + loaded_programs::{ + LoadedProgram, LoadedProgramMatchCriteria, LoadedProgramType, LoadedPrograms, + LoadedProgramsForTxBatch, WorkingSlot, + }, log_collector::LogCollector, sysvar_cache::SysvarCache, timings::{ExecuteTimingType, ExecuteTimings}, @@ -258,7 +261,6 @@ pub struct BankRc { #[cfg(RUSTC_WITH_SPECIALIZATION)] use solana_frozen_abi::abi_example::AbiExample; -use solana_program_runtime::loaded_programs::LoadedProgramMatchCriteria; #[cfg(RUSTC_WITH_SPECIALIZATION)] impl AbiExample for BankRc { @@ -4427,7 +4429,7 @@ impl Bank { fn load_and_get_programs_from_cache( &self, program_accounts_map: &HashMap, - ) -> HashMap> { + ) -> LoadedProgramsForTxBatch { let programs_and_slots: Vec<(Pubkey, LoadedProgramMatchCriteria)> = if self.check_program_modification_slot { program_accounts_map @@ -4476,7 +4478,7 @@ impl Bank { for (key, program) in missing_programs { let (_was_occupied, entry) = loaded_programs_cache.replenish(key, program); // Use the returned entry as that might have been deduplicated globally - loaded_programs_for_txs.insert(key, entry); + loaded_programs_for_txs.replenish(key, entry); } loaded_programs_for_txs