diff --git a/ledger-tool/src/program.rs b/ledger-tool/src/program.rs index 434919095..ef72c98a4 100644 --- a/ledger-tool/src/program.rs +++ b/ledger-tool/src/program.rs @@ -365,6 +365,7 @@ fn load_program<'a>( account_size, slot, Arc::new(program_runtime_environment), + false, ); match result { Ok(loaded_program) => match loaded_program.program { @@ -548,7 +549,7 @@ pub fn program(ledger_path: &Path, matches: &ArgMatches<'_>) { .clone(), ); for key in cached_account_keys { - loaded_programs.replenish(key, bank.load_program(&key)); + loaded_programs.replenish(key, bank.load_program(&key, false)); debug!("Loaded program {}", key); } invoke_context.programs_loaded_for_tx_batch = &loaded_programs; diff --git a/program-runtime/src/loaded_programs.rs b/program-runtime/src/loaded_programs.rs index 77246479e..ed34ca523 100644 --- a/program-runtime/src/loaded_programs.rs +++ b/program-runtime/src/loaded_programs.rs @@ -228,16 +228,76 @@ impl LoadedProgram { elf_bytes: &[u8], account_size: usize, metrics: &mut LoadProgramMetrics, + ) -> Result> { + Self::new_internal( + loader_key, + program_runtime_environment, + deployment_slot, + effective_slot, + maybe_expiration_slot, + elf_bytes, + account_size, + metrics, + false, /* reloading */ + ) + } + + /// Reloads a user program, *without* running the verifier. + /// + /// # Safety + /// + /// This method is unsafe since it assumes that the program has already been verified. Should + /// only be called when the program was previously verified and loaded in the cache, but was + /// unloaded due to inactivity. It should also be checked that the `program_runtime_environment` + /// hasn't changed since it was unloaded. + pub unsafe fn reload( + loader_key: &Pubkey, + program_runtime_environment: Arc>>, + deployment_slot: Slot, + effective_slot: Slot, + maybe_expiration_slot: Option, + elf_bytes: &[u8], + account_size: usize, + metrics: &mut LoadProgramMetrics, + ) -> Result> { + Self::new_internal( + loader_key, + program_runtime_environment, + deployment_slot, + effective_slot, + maybe_expiration_slot, + elf_bytes, + account_size, + metrics, + true, /* reloading */ + ) + } + + fn new_internal( + loader_key: &Pubkey, + program_runtime_environment: Arc>>, + deployment_slot: Slot, + effective_slot: Slot, + maybe_expiration_slot: Option, + elf_bytes: &[u8], + account_size: usize, + metrics: &mut LoadProgramMetrics, + reloading: bool, ) -> Result> { let mut load_elf_time = Measure::start("load_elf_time"); + // The following unused_mut exception is needed for architectures that do not + // support JIT compilation. + #[allow(unused_mut)] let mut executable = Executable::load(elf_bytes, program_runtime_environment.clone())?; load_elf_time.stop(); metrics.load_elf_us = load_elf_time.as_us(); - let mut verify_code_time = Measure::start("verify_code_time"); - executable.verify::()?; - verify_code_time.stop(); - metrics.verify_code_us = verify_code_time.as_us(); + if !reloading { + let mut verify_code_time = Measure::start("verify_code_time"); + executable.verify::()?; + verify_code_time.stop(); + metrics.verify_code_us = verify_code_time.as_us(); + } #[cfg(all(not(target_os = "windows"), target_arch = "x86_64"))] { @@ -386,6 +446,12 @@ pub struct LoadedProgramsForTxBatch { pub environments: ProgramRuntimeEnvironments, } +pub struct ExtractedPrograms { + pub loaded: LoadedProgramsForTxBatch, + pub missing: Vec<(Pubkey, u64)>, + pub unloaded: Vec<(Pubkey, u64)>, +} + impl LoadedProgramsForTxBatch { pub fn new(slot: Slot, environments: ProgramRuntimeEnvironments) -> Self { Self { @@ -624,8 +690,6 @@ impl LoadedPrograms { } Self::matches_loaded_program_criteria(entry, match_criteria) - // If the program was unloaded. Consider it as unusable, so it can be reloaded. - && !matches!(entry.program, LoadedProgramType::Unloaded(_)) } /// Extracts a subset of the programs relevant to a transaction batch @@ -634,8 +698,9 @@ impl LoadedPrograms { &self, working_slot: &S, keys: impl Iterator, - ) -> (LoadedProgramsForTxBatch, Vec<(Pubkey, u64)>) { + ) -> ExtractedPrograms { let mut missing = Vec::new(); + let mut unloaded = Vec::new(); let found = keys .filter_map(|(key, (match_criteria, count))| { if let Some(second_level) = self.entries.get(&key) { @@ -650,6 +715,21 @@ impl LoadedPrograms { return None; } + if let LoadedProgramType::Unloaded(environment) = &entry.program { + if Arc::ptr_eq(environment, &self.environments.program_runtime_v1) + || Arc::ptr_eq( + environment, + &self.environments.program_runtime_v2, + ) + { + // if the environment hasn't changed since the entry was unloaded. + unloaded.push((key, count)); + } else { + missing.push((key, count)); + } + return None; + } + if current_slot >= entry.effective_slot { let mut usage_count = entry.tx_usage_counter.load(Ordering::Relaxed); @@ -682,14 +762,15 @@ impl LoadedPrograms { self.stats .hits .fetch_add(found.len() as u64, Ordering::Relaxed); - ( - LoadedProgramsForTxBatch { + ExtractedPrograms { + loaded: LoadedProgramsForTxBatch { entries: found, slot: working_slot.current_slot(), environments: self.environments.clone(), }, missing, - ) + unloaded, + } } pub fn merge(&mut self, tx_batch_cache: &LoadedProgramsForTxBatch) { @@ -838,8 +919,9 @@ impl solana_frozen_abi::abi_example::AbiExample for LoadedPrograms { mod tests { use { crate::loaded_programs::{ - BlockRelation, ForkGraph, LoadedProgram, LoadedProgramMatchCriteria, LoadedProgramType, - LoadedPrograms, LoadedProgramsForTxBatch, WorkingSlot, DELAY_VISIBILITY_SLOT_OFFSET, + BlockRelation, ExtractedPrograms, ForkGraph, LoadedProgram, LoadedProgramMatchCriteria, + LoadedProgramType, LoadedPrograms, LoadedProgramsForTxBatch, WorkingSlot, + DELAY_VISIBILITY_SLOT_OFFSET, }, assert_matches::assert_matches, percentage::Percentage, @@ -881,9 +963,19 @@ mod tests { slot: Slot, ) -> Arc { let unloaded = Arc::new( - new_test_loaded_program(slot, slot.saturating_add(1)) - .to_unloaded() - .expect("Failed to unload the program"), + LoadedProgram { + program: LoadedProgramType::TestLoaded( + cache.environments.program_runtime_v1.clone(), + ), + account_size: 0, + deployment_slot: slot, + effective_slot: slot.saturating_add(1), + maybe_expiration_slot: None, + tx_usage_counter: AtomicU64::default(), + ix_usage_counter: AtomicU64::default(), + } + .to_unloaded() + .expect("Failed to unload the program"), ); cache.replenish(key, unloaded).1 } @@ -1471,7 +1563,11 @@ mod tests { // Testing fork 0 - 10 - 12 - 22 with current slot at 22 let working_slot = TestWorkingSlot::new(22, &[0, 10, 20, 22]); - let (found, missing) = cache.extract( + let ExtractedPrograms { + loaded: found, + missing, + unloaded, + } = cache.extract( &working_slot, vec![ (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), @@ -1487,10 +1583,15 @@ mod tests { assert!(missing.contains(&(program2, 2))); assert!(missing.contains(&(program3, 3))); + assert!(unloaded.is_empty()); // Testing fork 0 - 5 - 11 - 15 - 16 with current slot at 16 let mut working_slot = TestWorkingSlot::new(15, &[0, 5, 11, 15, 16, 18, 19, 23]); - let (found, missing) = cache.extract( + let ExtractedPrograms { + loaded: found, + missing, + unloaded, + } = cache.extract( &working_slot, vec![ (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), @@ -1511,10 +1612,15 @@ mod tests { assert_eq!(tombstone.deployment_slot, 15); assert!(missing.contains(&(program3, 1))); + assert!(unloaded.is_empty()); // Testing the same fork above, but current slot is now 18 (equal to effective slot of program4). working_slot.update_slot(18); - let (found, missing) = cache.extract( + let ExtractedPrograms { + loaded: found, + missing, + unloaded, + } = cache.extract( &working_slot, vec![ (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), @@ -1532,10 +1638,15 @@ mod tests { assert!(match_slot(&found, &program4, 15, 18)); assert!(missing.contains(&(program3, 1))); + assert!(unloaded.is_empty()); // Testing the same fork above, but current slot is now 23 (future slot than effective slot of program4). working_slot.update_slot(23); - let (found, missing) = cache.extract( + let ExtractedPrograms { + loaded: found, + missing, + unloaded, + } = cache.extract( &working_slot, vec![ (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), @@ -1553,10 +1664,15 @@ mod tests { assert!(match_slot(&found, &program4, 15, 23)); assert!(missing.contains(&(program3, 1))); + assert!(unloaded.is_empty()); // Testing fork 0 - 5 - 11 - 15 - 16 with current slot at 11 let working_slot = TestWorkingSlot::new(11, &[0, 5, 11, 15, 16]); - let (found, missing) = cache.extract( + let ExtractedPrograms { + loaded: found, + missing, + unloaded, + } = cache.extract( &working_slot, vec![ (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), @@ -1575,6 +1691,7 @@ mod tests { assert!(match_slot(&found, &program4, 5, 11)); assert!(missing.contains(&(program3, 1))); + assert!(unloaded.is_empty()); // The following is a special case, where there's an expiration slot let test_program = Arc::new(LoadedProgram { @@ -1590,7 +1707,11 @@ mod tests { // Testing fork 0 - 5 - 11 - 15 - 16 - 19 - 21 - 23 with current slot at 19 let working_slot = TestWorkingSlot::new(19, &[0, 5, 11, 15, 16, 18, 19, 21, 23]); - let (found, missing) = cache.extract( + let ExtractedPrograms { + loaded: found, + missing, + unloaded, + } = cache.extract( &working_slot, vec![ (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), @@ -1607,11 +1728,16 @@ mod tests { assert!(match_slot(&found, &program4, 19, 19)); assert!(missing.contains(&(program3, 1))); + assert!(unloaded.is_empty()); // Testing fork 0 - 5 - 11 - 15 - 16 - 19 - 21 - 23 with current slot at 21 // This would cause program4 deployed at slot 19 to be expired. let working_slot = TestWorkingSlot::new(21, &[0, 5, 11, 15, 16, 18, 19, 21, 23]); - let (found, missing) = cache.extract( + let ExtractedPrograms { + loaded: found, + missing, + unloaded, + } = cache.extract( &working_slot, vec![ (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), @@ -1627,6 +1753,7 @@ mod tests { assert!(missing.contains(&(program3, 1))); assert!(missing.contains(&(program4, 1))); + assert!(unloaded.is_empty()); // Remove the expired entry to let the rest of the test continue if let Some(programs) = cache.entries.get_mut(&program4) { @@ -1652,7 +1779,11 @@ mod tests { // Testing fork 11 - 15 - 16- 19 - 22 with root at 5 and current slot at 22 let working_slot = TestWorkingSlot::new(22, &[5, 11, 15, 16, 19, 22, 23]); - let (found, missing) = cache.extract( + let ExtractedPrograms { + loaded: found, + missing, + unloaded, + } = cache.extract( &working_slot, vec![ (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), @@ -1669,10 +1800,15 @@ mod tests { assert!(match_slot(&found, &program4, 15, 22)); assert!(missing.contains(&(program3, 1))); + assert!(unloaded.is_empty()); // Testing fork 0 - 5 - 11 - 25 - 27 with current slot at 27 let working_slot = TestWorkingSlot::new(27, &[11, 25, 27]); - let (found, _missing) = cache.extract( + let ExtractedPrograms { + loaded: found, + missing: _, + unloaded, + } = cache.extract( &working_slot, vec![ (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), @@ -1683,6 +1819,7 @@ mod tests { .into_iter(), ); + assert!(unloaded.is_empty()); assert!(match_slot(&found, &program1, 0, 27)); assert!(match_slot(&found, &program2, 11, 27)); assert!(match_slot(&found, &program3, 25, 27)); @@ -1707,7 +1844,11 @@ mod tests { // Testing fork 16, 19, 23, with root at 15, current slot at 23 let working_slot = TestWorkingSlot::new(23, &[16, 19, 23]); - let (found, missing) = cache.extract( + let ExtractedPrograms { + loaded: found, + missing, + unloaded, + } = cache.extract( &working_slot, vec![ (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), @@ -1724,6 +1865,7 @@ mod tests { // program3 was deployed on slot 25, which has been pruned assert!(missing.contains(&(program3, 1))); + assert!(unloaded.is_empty()); } #[test] @@ -1763,7 +1905,11 @@ mod tests { // Testing fork 0 - 5 - 11 - 15 - 16 - 19 - 21 - 23 with current slot at 19 let working_slot = TestWorkingSlot::new(12, &[0, 5, 11, 12, 15, 16, 18, 19, 21, 23]); - let (found, missing) = cache.extract( + let ExtractedPrograms { + loaded: found, + missing, + unloaded, + } = cache.extract( &working_slot, vec![ (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), @@ -1777,9 +1923,14 @@ mod tests { assert!(match_slot(&found, &program2, 11, 12)); assert!(missing.contains(&(program3, 1))); + assert!(unloaded.is_empty()); // Test the same fork, but request the program modified at a later slot than what's in the cache. - let (found, missing) = cache.extract( + let ExtractedPrograms { + loaded: found, + missing, + unloaded, + } = cache.extract( &working_slot, vec![ ( @@ -1799,6 +1950,126 @@ mod tests { assert!(missing.contains(&(program1, 1))); assert!(missing.contains(&(program3, 1))); + assert!(unloaded.is_empty()); + } + + #[test] + fn test_extract_unloaded() { + let mut cache = LoadedPrograms::default(); + + // Fork graph created for the test + // 0 + // / \ + // 10 5 + // | | + // 20 11 + // | | \ + // 22 15 25 + // | | + // 16 27 + // | + // 19 + // | + // 23 + + let mut fork_graph = TestForkGraphSpecific::default(); + fork_graph.insert_fork(&[0, 10, 20, 22]); + fork_graph.insert_fork(&[0, 5, 11, 15, 16, 19, 21, 23]); + fork_graph.insert_fork(&[0, 5, 11, 25, 27]); + + let program1 = Pubkey::new_unique(); + assert!(!cache.replenish(program1, new_test_loaded_program(0, 1)).0); + assert!(!cache.replenish(program1, new_test_loaded_program(20, 21)).0); + + let program2 = Pubkey::new_unique(); + assert!(!cache.replenish(program2, new_test_loaded_program(5, 6)).0); + assert!(!cache.replenish(program2, new_test_loaded_program(11, 12)).0); + + let program3 = Pubkey::new_unique(); + // Insert an unloaded program with correct/cache's environment at slot 25 + let _ = insert_unloaded_program(&mut cache, program3, 25); + + // Insert another unloaded program with a different environment at slot 20 + // Since this entry's environment won't match cache's environment, looking up this + // entry should return missing instead of unloaded entry. + assert!( + !cache + .replenish( + program3, + Arc::new( + new_test_loaded_program(20, 21) + .to_unloaded() + .expect("Failed to create unloaded program") + ) + ) + .0 + ); + + // Testing fork 0 - 5 - 11 - 15 - 16 - 19 - 21 - 23 with current slot at 19 + let working_slot = TestWorkingSlot::new(19, &[0, 5, 11, 12, 15, 16, 18, 19, 21, 23]); + let ExtractedPrograms { + loaded: found, + missing, + unloaded, + } = cache.extract( + &working_slot, + vec![ + (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), + (program2, (LoadedProgramMatchCriteria::NoCriteria, 1)), + (program3, (LoadedProgramMatchCriteria::NoCriteria, 1)), + ] + .into_iter(), + ); + + assert!(match_slot(&found, &program1, 0, 19)); + assert!(match_slot(&found, &program2, 11, 19)); + + assert!(missing.contains(&(program3, 1))); + assert!(unloaded.is_empty()); + + // Testing fork 0 - 5 - 11 - 25 - 27 with current slot at 27 + let working_slot = TestWorkingSlot::new(27, &[0, 5, 11, 25, 27]); + let ExtractedPrograms { + loaded: found, + missing, + unloaded, + } = cache.extract( + &working_slot, + vec![ + (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), + (program2, (LoadedProgramMatchCriteria::NoCriteria, 1)), + (program3, (LoadedProgramMatchCriteria::NoCriteria, 1)), + ] + .into_iter(), + ); + + assert!(match_slot(&found, &program1, 0, 27)); + assert!(match_slot(&found, &program2, 11, 27)); + + assert!(unloaded.contains(&(program3, 1))); + assert!(missing.is_empty()); + + // Testing fork 0 - 10 - 20 - 22 with current slot at 22 + let working_slot = TestWorkingSlot::new(22, &[0, 10, 20, 22]); + let ExtractedPrograms { + loaded: found, + missing, + unloaded, + } = cache.extract( + &working_slot, + vec![ + (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), + (program2, (LoadedProgramMatchCriteria::NoCriteria, 1)), + (program3, (LoadedProgramMatchCriteria::NoCriteria, 1)), + ] + .into_iter(), + ); + + assert!(match_slot(&found, &program1, 20, 22)); + + assert!(missing.contains(&(program2, 1))); + assert!(missing.contains(&(program3, 1))); + assert!(unloaded.is_empty()); } #[test] @@ -1850,7 +2121,11 @@ mod tests { // Testing fork 0 - 5 - 11 - 15 - 16 - 19 - 21 - 23 with current slot at 19 let working_slot = TestWorkingSlot::new(12, &[0, 5, 11, 12, 15, 16, 18, 19, 21, 23]); - let (found, missing) = cache.extract( + let ExtractedPrograms { + loaded: found, + missing, + unloaded, + } = cache.extract( &working_slot, vec![ (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), @@ -1865,11 +2140,16 @@ mod tests { assert!(match_slot(&found, &program2, 11, 12)); assert!(missing.contains(&(program3, 1))); + assert!(unloaded.is_empty()); // Testing fork 0 - 5 - 11 - 12 - 15 - 16 - 19 - 21 - 23 with current slot at 15 // This would cause program4 deployed at slot 15 to be expired. let working_slot = TestWorkingSlot::new(15, &[0, 5, 11, 15, 16, 18, 19, 21, 23]); - let (found, missing) = cache.extract( + let ExtractedPrograms { + loaded: found, + missing, + unloaded, + } = cache.extract( &working_slot, vec![ (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), @@ -1878,6 +2158,7 @@ mod tests { ] .into_iter(), ); + assert!(unloaded.is_empty()); assert!(match_slot(&found, &program2, 11, 15)); @@ -1935,10 +2216,15 @@ mod tests { cache.prune(&fork_graph, 10); let working_slot = TestWorkingSlot::new(20, &[0, 10, 20]); - let (found, _missing) = cache.extract( + let ExtractedPrograms { + loaded: found, + missing: _, + unloaded, + } = cache.extract( &working_slot, vec![(program1, (LoadedProgramMatchCriteria::NoCriteria, 1))].into_iter(), ); + assert!(unloaded.is_empty()); // The cache should have the program deployed at slot 0 assert_eq!( @@ -1977,7 +2263,11 @@ mod tests { assert!(!cache.replenish(program2, new_test_loaded_program(10, 11)).0); let working_slot = TestWorkingSlot::new(20, &[0, 10, 20]); - let (found, _missing) = cache.extract( + let ExtractedPrograms { + loaded: found, + missing: _, + unloaded: _, + } = cache.extract( &working_slot, vec![ (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), @@ -1990,7 +2280,11 @@ mod tests { assert!(match_slot(&found, &program2, 10, 20)); let working_slot = TestWorkingSlot::new(6, &[0, 5, 6]); - let (found, missing) = cache.extract( + let ExtractedPrograms { + loaded: found, + missing, + unloaded: _, + } = cache.extract( &working_slot, vec![ (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), @@ -2007,7 +2301,11 @@ mod tests { cache.prune_by_deployment_slot(5); let working_slot = TestWorkingSlot::new(20, &[0, 10, 20]); - let (found, _missing) = cache.extract( + let ExtractedPrograms { + loaded: found, + missing: _, + unloaded: _, + } = cache.extract( &working_slot, vec![ (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), @@ -2020,7 +2318,11 @@ mod tests { assert!(match_slot(&found, &program2, 10, 20)); let working_slot = TestWorkingSlot::new(6, &[0, 5, 6]); - let (found, missing) = cache.extract( + let ExtractedPrograms { + loaded: found, + missing, + unloaded: _, + } = cache.extract( &working_slot, vec![ (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), @@ -2037,7 +2339,11 @@ mod tests { cache.prune_by_deployment_slot(10); let working_slot = TestWorkingSlot::new(20, &[0, 10, 20]); - let (found, _missing) = cache.extract( + let ExtractedPrograms { + loaded: found, + missing: _, + unloaded: _, + } = cache.extract( &working_slot, vec![ (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), @@ -2052,35 +2358,6 @@ mod tests { #[test] fn test_usable_entries_for_slot() { - let unloaded_entry = Arc::new( - new_test_loaded_program(0, 0) - .to_unloaded() - .expect("Failed to unload the program"), - ); - assert!(!LoadedPrograms::is_entry_usable( - &unloaded_entry, - 0, - &LoadedProgramMatchCriteria::NoCriteria - )); - - assert!(!LoadedPrograms::is_entry_usable( - &unloaded_entry, - 1, - &LoadedProgramMatchCriteria::NoCriteria - )); - - assert!(!LoadedPrograms::is_entry_usable( - &unloaded_entry, - 1, - &LoadedProgramMatchCriteria::Tombstone - )); - - assert!(!LoadedPrograms::is_entry_usable( - &unloaded_entry, - 1, - &LoadedProgramMatchCriteria::DeployedOnOrAfterSlot(0) - )); - let tombstone = Arc::new(LoadedProgram::new_tombstone(0, LoadedProgramType::Closed)); assert!(LoadedPrograms::is_entry_usable( diff --git a/programs/bpf_loader/src/lib.rs b/programs/bpf_loader/src/lib.rs index 342d38363..9a9128632 100644 --- a/programs/bpf_loader/src/lib.rs +++ b/programs/bpf_loader/src/lib.rs @@ -75,22 +75,39 @@ pub fn load_program_from_bytes( account_size: usize, deployment_slot: Slot, program_runtime_environment: Arc>>, + reloading: bool, ) -> Result { let effective_slot = if feature_set.is_active(&delay_visibility_of_program_deployment::id()) { deployment_slot.saturating_add(DELAY_VISIBILITY_SLOT_OFFSET) } else { deployment_slot }; - let loaded_program = LoadedProgram::new( - loader_key, - program_runtime_environment, - deployment_slot, - effective_slot, - None, - programdata, - account_size, - load_program_metrics, - ) + let loaded_program = if reloading { + // Safety: this is safe because the program is being reloaded in the cache. + unsafe { + LoadedProgram::reload( + loader_key, + program_runtime_environment, + deployment_slot, + effective_slot, + None, + programdata, + account_size, + load_program_metrics, + ) + } + } else { + LoadedProgram::new( + loader_key, + program_runtime_environment, + deployment_slot, + effective_slot, + None, + programdata, + account_size, + load_program_metrics, + ) + } .map_err(|err| { ic_logger_msg!(log_collector, "{}", err); InstructionError::InvalidAccountData @@ -123,6 +140,7 @@ macro_rules! deploy_program { $account_size, $slot, Arc::new(program_runtime_environment), + false, )?; if let Some(old_entry) = $invoke_context.find_program_in_cache(&$program_id) { executor.tx_usage_counter.store( @@ -1700,6 +1718,7 @@ pub mod test_utils { account.data().len(), 0, program_runtime_environment.clone(), + false, ) { invoke_context .programs_modified_by_tx diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index daa6bfff1..65546bb20 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -276,6 +276,7 @@ pub struct BankRc { #[cfg(RUSTC_WITH_SPECIALIZATION)] use solana_frozen_abi::abi_example::AbiExample; +use solana_program_runtime::loaded_programs::ExtractedPrograms; #[cfg(RUSTC_WITH_SPECIALIZATION)] impl AbiExample for BankRc { @@ -4656,7 +4657,7 @@ impl Bank { ProgramAccountLoadResult::InvalidAccountData } - pub fn load_program(&self, pubkey: &Pubkey) -> Arc { + pub fn load_program(&self, pubkey: &Pubkey, reload: bool) -> Arc { let environments = self .loaded_programs_cache .read() @@ -4689,6 +4690,7 @@ impl Bank { program_account.data().len(), 0, environments.program_runtime_v1.clone(), + reload, ) } @@ -4713,6 +4715,7 @@ impl Bank { .saturating_add(programdata_account.data().len()), slot, environments.program_runtime_v1.clone(), + reload, ) }), @@ -4721,16 +4724,32 @@ impl Bank { .data() .get(LoaderV4State::program_data_offset()..) .and_then(|elf_bytes| { - LoadedProgram::new( - &loader_v4::id(), - environments.program_runtime_v2.clone(), - slot, - slot.saturating_add(DELAY_VISIBILITY_SLOT_OFFSET), - None, - elf_bytes, - program_account.data().len(), - &mut load_program_metrics, - ) + if reload { + // Safety: this is safe because the program is being reloaded in the cache. + unsafe { + LoadedProgram::reload( + &loader_v4::id(), + environments.program_runtime_v2.clone(), + slot, + slot.saturating_add(DELAY_VISIBILITY_SLOT_OFFSET), + None, + elf_bytes, + program_account.data().len(), + &mut load_program_metrics, + ) + } + } else { + LoadedProgram::new( + &loader_v4::id(), + environments.program_runtime_v2.clone(), + slot, + slot.saturating_add(DELAY_VISIBILITY_SLOT_OFFSET), + None, + elf_bytes, + program_account.data().len(), + &mut load_program_metrics, + ) + } .ok() }) .unwrap_or(LoadedProgram::new_tombstone( @@ -4987,17 +5006,31 @@ impl Bank { .collect() }; - let (mut loaded_programs_for_txs, missing_programs) = { + let ExtractedPrograms { + loaded: mut loaded_programs_for_txs, + missing, + unloaded, + } = { // Lock the global cache to figure out which programs need to be loaded let loaded_programs_cache = self.loaded_programs_cache.read().unwrap(); loaded_programs_cache.extract(self, programs_and_slots.into_iter()) }; // Load missing programs while global cache is unlocked - let missing_programs: Vec<(Pubkey, Arc)> = missing_programs + let missing_programs: Vec<(Pubkey, Arc)> = missing .iter() .map(|(key, count)| { - let program = self.load_program(key); + let program = self.load_program(key, false); + program.tx_usage_counter.store(*count, Ordering::Relaxed); + (*key, program) + }) + .collect(); + + // Reload unloaded programs while global cache is unlocked + let unloaded_programs: Vec<(Pubkey, Arc)> = unloaded + .iter() + .map(|(key, count)| { + let program = self.load_program(key, true); program.tx_usage_counter.store(*count, Ordering::Relaxed); (*key, program) }) @@ -5010,6 +5043,11 @@ impl Bank { // Use the returned entry as that might have been deduplicated globally loaded_programs_for_txs.replenish(key, entry); } + for (key, program) in unloaded_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.replenish(key, entry); + } loaded_programs_for_txs } diff --git a/runtime/src/bank/tests.rs b/runtime/src/bank/tests.rs index b0f758a25..001494e55 100644 --- a/runtime/src/bank/tests.rs +++ b/runtime/src/bank/tests.rs @@ -7230,7 +7230,7 @@ fn test_bank_load_program() { programdata_account.set_rent_epoch(1); bank.store_account_and_update_capitalization(&key1, &program_account); bank.store_account_and_update_capitalization(&programdata_key, &programdata_account); - let program = bank.load_program(&key1); + let program = bank.load_program(&key1, false); assert_matches!(program.program, LoadedProgramType::LegacyV1(_)); assert_eq!( program.account_size, @@ -7385,7 +7385,7 @@ fn test_bpf_loader_upgradeable_deploy_with_max_len() { assert_eq!(*elf.get(i).unwrap(), *byte); } - let loaded_program = bank.load_program(&program_keypair.pubkey()); + let loaded_program = bank.load_program(&program_keypair.pubkey(), false); // Invoke deployed program mock_process_instruction(