Do not unnecessarily re-verify unloaded program (#32722)

* Do not unnecessarily re-verify unloaded program

* clippy fixes

* new unit test for extract

* fixes after rebase

* fixes after rebase

* address review comments

* fix clippy
This commit is contained in:
Pankaj Garg 2023-09-13 06:25:56 -07:00 committed by GitHub
parent ec9b30965e
commit 5562f79cc5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 426 additions and 91 deletions

View File

@ -365,6 +365,7 @@ fn load_program<'a>(
account_size, account_size,
slot, slot,
Arc::new(program_runtime_environment), Arc::new(program_runtime_environment),
false,
); );
match result { match result {
Ok(loaded_program) => match loaded_program.program { Ok(loaded_program) => match loaded_program.program {
@ -548,7 +549,7 @@ pub fn program(ledger_path: &Path, matches: &ArgMatches<'_>) {
.clone(), .clone(),
); );
for key in cached_account_keys { 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); debug!("Loaded program {}", key);
} }
invoke_context.programs_loaded_for_tx_batch = &loaded_programs; invoke_context.programs_loaded_for_tx_batch = &loaded_programs;

View File

@ -228,16 +228,76 @@ impl LoadedProgram {
elf_bytes: &[u8], elf_bytes: &[u8],
account_size: usize, account_size: usize,
metrics: &mut LoadProgramMetrics, metrics: &mut LoadProgramMetrics,
) -> Result<Self, Box<dyn std::error::Error>> {
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<BuiltinProgram<InvokeContext<'static>>>,
deployment_slot: Slot,
effective_slot: Slot,
maybe_expiration_slot: Option<Slot>,
elf_bytes: &[u8],
account_size: usize,
metrics: &mut LoadProgramMetrics,
) -> Result<Self, Box<dyn std::error::Error>> {
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<BuiltinProgram<InvokeContext<'static>>>,
deployment_slot: Slot,
effective_slot: Slot,
maybe_expiration_slot: Option<Slot>,
elf_bytes: &[u8],
account_size: usize,
metrics: &mut LoadProgramMetrics,
reloading: bool,
) -> Result<Self, Box<dyn std::error::Error>> { ) -> Result<Self, Box<dyn std::error::Error>> {
let mut load_elf_time = Measure::start("load_elf_time"); 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())?; let mut executable = Executable::load(elf_bytes, program_runtime_environment.clone())?;
load_elf_time.stop(); load_elf_time.stop();
metrics.load_elf_us = load_elf_time.as_us(); metrics.load_elf_us = load_elf_time.as_us();
let mut verify_code_time = Measure::start("verify_code_time"); if !reloading {
executable.verify::<RequisiteVerifier>()?; let mut verify_code_time = Measure::start("verify_code_time");
verify_code_time.stop(); executable.verify::<RequisiteVerifier>()?;
metrics.verify_code_us = verify_code_time.as_us(); verify_code_time.stop();
metrics.verify_code_us = verify_code_time.as_us();
}
#[cfg(all(not(target_os = "windows"), target_arch = "x86_64"))] #[cfg(all(not(target_os = "windows"), target_arch = "x86_64"))]
{ {
@ -386,6 +446,12 @@ pub struct LoadedProgramsForTxBatch {
pub environments: ProgramRuntimeEnvironments, pub environments: ProgramRuntimeEnvironments,
} }
pub struct ExtractedPrograms {
pub loaded: LoadedProgramsForTxBatch,
pub missing: Vec<(Pubkey, u64)>,
pub unloaded: Vec<(Pubkey, u64)>,
}
impl LoadedProgramsForTxBatch { impl LoadedProgramsForTxBatch {
pub fn new(slot: Slot, environments: ProgramRuntimeEnvironments) -> Self { pub fn new(slot: Slot, environments: ProgramRuntimeEnvironments) -> Self {
Self { Self {
@ -624,8 +690,6 @@ impl LoadedPrograms {
} }
Self::matches_loaded_program_criteria(entry, match_criteria) 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 /// Extracts a subset of the programs relevant to a transaction batch
@ -634,8 +698,9 @@ impl LoadedPrograms {
&self, &self,
working_slot: &S, working_slot: &S,
keys: impl Iterator<Item = (Pubkey, (LoadedProgramMatchCriteria, u64))>, keys: impl Iterator<Item = (Pubkey, (LoadedProgramMatchCriteria, u64))>,
) -> (LoadedProgramsForTxBatch, Vec<(Pubkey, u64)>) { ) -> ExtractedPrograms {
let mut missing = Vec::new(); let mut missing = Vec::new();
let mut unloaded = Vec::new();
let found = keys let found = keys
.filter_map(|(key, (match_criteria, count))| { .filter_map(|(key, (match_criteria, count))| {
if let Some(second_level) = self.entries.get(&key) { if let Some(second_level) = self.entries.get(&key) {
@ -650,6 +715,21 @@ impl LoadedPrograms {
return None; 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 { if current_slot >= entry.effective_slot {
let mut usage_count = let mut usage_count =
entry.tx_usage_counter.load(Ordering::Relaxed); entry.tx_usage_counter.load(Ordering::Relaxed);
@ -682,14 +762,15 @@ impl LoadedPrograms {
self.stats self.stats
.hits .hits
.fetch_add(found.len() as u64, Ordering::Relaxed); .fetch_add(found.len() as u64, Ordering::Relaxed);
( ExtractedPrograms {
LoadedProgramsForTxBatch { loaded: LoadedProgramsForTxBatch {
entries: found, entries: found,
slot: working_slot.current_slot(), slot: working_slot.current_slot(),
environments: self.environments.clone(), environments: self.environments.clone(),
}, },
missing, missing,
) unloaded,
}
} }
pub fn merge(&mut self, tx_batch_cache: &LoadedProgramsForTxBatch) { pub fn merge(&mut self, tx_batch_cache: &LoadedProgramsForTxBatch) {
@ -838,8 +919,9 @@ impl solana_frozen_abi::abi_example::AbiExample for LoadedPrograms {
mod tests { mod tests {
use { use {
crate::loaded_programs::{ crate::loaded_programs::{
BlockRelation, ForkGraph, LoadedProgram, LoadedProgramMatchCriteria, LoadedProgramType, BlockRelation, ExtractedPrograms, ForkGraph, LoadedProgram, LoadedProgramMatchCriteria,
LoadedPrograms, LoadedProgramsForTxBatch, WorkingSlot, DELAY_VISIBILITY_SLOT_OFFSET, LoadedProgramType, LoadedPrograms, LoadedProgramsForTxBatch, WorkingSlot,
DELAY_VISIBILITY_SLOT_OFFSET,
}, },
assert_matches::assert_matches, assert_matches::assert_matches,
percentage::Percentage, percentage::Percentage,
@ -881,9 +963,19 @@ mod tests {
slot: Slot, slot: Slot,
) -> Arc<LoadedProgram> { ) -> Arc<LoadedProgram> {
let unloaded = Arc::new( let unloaded = Arc::new(
new_test_loaded_program(slot, slot.saturating_add(1)) LoadedProgram {
.to_unloaded() program: LoadedProgramType::TestLoaded(
.expect("Failed to unload the program"), 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 cache.replenish(key, unloaded).1
} }
@ -1471,7 +1563,11 @@ mod tests {
// Testing fork 0 - 10 - 12 - 22 with current slot at 22 // Testing fork 0 - 10 - 12 - 22 with current slot at 22
let working_slot = TestWorkingSlot::new(22, &[0, 10, 20, 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, &working_slot,
vec![ vec![
(program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)),
@ -1487,10 +1583,15 @@ mod tests {
assert!(missing.contains(&(program2, 2))); assert!(missing.contains(&(program2, 2)));
assert!(missing.contains(&(program3, 3))); assert!(missing.contains(&(program3, 3)));
assert!(unloaded.is_empty());
// Testing fork 0 - 5 - 11 - 15 - 16 with current slot at 16 // 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 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, &working_slot,
vec![ vec![
(program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)),
@ -1511,10 +1612,15 @@ mod tests {
assert_eq!(tombstone.deployment_slot, 15); assert_eq!(tombstone.deployment_slot, 15);
assert!(missing.contains(&(program3, 1))); 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). // Testing the same fork above, but current slot is now 18 (equal to effective slot of program4).
working_slot.update_slot(18); working_slot.update_slot(18);
let (found, missing) = cache.extract( let ExtractedPrograms {
loaded: found,
missing,
unloaded,
} = cache.extract(
&working_slot, &working_slot,
vec![ vec![
(program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)),
@ -1532,10 +1638,15 @@ mod tests {
assert!(match_slot(&found, &program4, 15, 18)); assert!(match_slot(&found, &program4, 15, 18));
assert!(missing.contains(&(program3, 1))); 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). // Testing the same fork above, but current slot is now 23 (future slot than effective slot of program4).
working_slot.update_slot(23); working_slot.update_slot(23);
let (found, missing) = cache.extract( let ExtractedPrograms {
loaded: found,
missing,
unloaded,
} = cache.extract(
&working_slot, &working_slot,
vec![ vec![
(program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)),
@ -1553,10 +1664,15 @@ mod tests {
assert!(match_slot(&found, &program4, 15, 23)); assert!(match_slot(&found, &program4, 15, 23));
assert!(missing.contains(&(program3, 1))); assert!(missing.contains(&(program3, 1)));
assert!(unloaded.is_empty());
// Testing fork 0 - 5 - 11 - 15 - 16 with current slot at 11 // Testing fork 0 - 5 - 11 - 15 - 16 with current slot at 11
let working_slot = TestWorkingSlot::new(11, &[0, 5, 11, 15, 16]); 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, &working_slot,
vec![ vec![
(program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)),
@ -1575,6 +1691,7 @@ mod tests {
assert!(match_slot(&found, &program4, 5, 11)); assert!(match_slot(&found, &program4, 5, 11));
assert!(missing.contains(&(program3, 1))); assert!(missing.contains(&(program3, 1)));
assert!(unloaded.is_empty());
// The following is a special case, where there's an expiration slot // The following is a special case, where there's an expiration slot
let test_program = Arc::new(LoadedProgram { 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 // 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 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, &working_slot,
vec![ vec![
(program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)),
@ -1607,11 +1728,16 @@ mod tests {
assert!(match_slot(&found, &program4, 19, 19)); assert!(match_slot(&found, &program4, 19, 19));
assert!(missing.contains(&(program3, 1))); assert!(missing.contains(&(program3, 1)));
assert!(unloaded.is_empty());
// Testing fork 0 - 5 - 11 - 15 - 16 - 19 - 21 - 23 with current slot at 21 // 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. // 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 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, &working_slot,
vec![ vec![
(program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)),
@ -1627,6 +1753,7 @@ mod tests {
assert!(missing.contains(&(program3, 1))); assert!(missing.contains(&(program3, 1)));
assert!(missing.contains(&(program4, 1))); assert!(missing.contains(&(program4, 1)));
assert!(unloaded.is_empty());
// Remove the expired entry to let the rest of the test continue // Remove the expired entry to let the rest of the test continue
if let Some(programs) = cache.entries.get_mut(&program4) { 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 // 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 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, &working_slot,
vec![ vec![
(program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)),
@ -1669,10 +1800,15 @@ mod tests {
assert!(match_slot(&found, &program4, 15, 22)); assert!(match_slot(&found, &program4, 15, 22));
assert!(missing.contains(&(program3, 1))); assert!(missing.contains(&(program3, 1)));
assert!(unloaded.is_empty());
// Testing fork 0 - 5 - 11 - 25 - 27 with current slot at 27 // Testing fork 0 - 5 - 11 - 25 - 27 with current slot at 27
let working_slot = TestWorkingSlot::new(27, &[11, 25, 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, &working_slot,
vec![ vec![
(program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)),
@ -1683,6 +1819,7 @@ mod tests {
.into_iter(), .into_iter(),
); );
assert!(unloaded.is_empty());
assert!(match_slot(&found, &program1, 0, 27)); assert!(match_slot(&found, &program1, 0, 27));
assert!(match_slot(&found, &program2, 11, 27)); assert!(match_slot(&found, &program2, 11, 27));
assert!(match_slot(&found, &program3, 25, 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 // Testing fork 16, 19, 23, with root at 15, current slot at 23
let working_slot = TestWorkingSlot::new(23, &[16, 19, 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, &working_slot,
vec![ vec![
(program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)),
@ -1724,6 +1865,7 @@ mod tests {
// program3 was deployed on slot 25, which has been pruned // program3 was deployed on slot 25, which has been pruned
assert!(missing.contains(&(program3, 1))); assert!(missing.contains(&(program3, 1)));
assert!(unloaded.is_empty());
} }
#[test] #[test]
@ -1763,7 +1905,11 @@ mod tests {
// Testing fork 0 - 5 - 11 - 15 - 16 - 19 - 21 - 23 with current slot at 19 // 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 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, &working_slot,
vec![ vec![
(program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)),
@ -1777,9 +1923,14 @@ mod tests {
assert!(match_slot(&found, &program2, 11, 12)); assert!(match_slot(&found, &program2, 11, 12));
assert!(missing.contains(&(program3, 1))); 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. // 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, &working_slot,
vec![ vec![
( (
@ -1799,6 +1950,126 @@ mod tests {
assert!(missing.contains(&(program1, 1))); assert!(missing.contains(&(program1, 1)));
assert!(missing.contains(&(program3, 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] #[test]
@ -1850,7 +2121,11 @@ mod tests {
// Testing fork 0 - 5 - 11 - 15 - 16 - 19 - 21 - 23 with current slot at 19 // 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 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, &working_slot,
vec![ vec![
(program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)),
@ -1865,11 +2140,16 @@ mod tests {
assert!(match_slot(&found, &program2, 11, 12)); assert!(match_slot(&found, &program2, 11, 12));
assert!(missing.contains(&(program3, 1))); 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 // 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. // 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 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, &working_slot,
vec![ vec![
(program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)),
@ -1878,6 +2158,7 @@ mod tests {
] ]
.into_iter(), .into_iter(),
); );
assert!(unloaded.is_empty());
assert!(match_slot(&found, &program2, 11, 15)); assert!(match_slot(&found, &program2, 11, 15));
@ -1935,10 +2216,15 @@ mod tests {
cache.prune(&fork_graph, 10); cache.prune(&fork_graph, 10);
let working_slot = TestWorkingSlot::new(20, &[0, 10, 20]); let working_slot = TestWorkingSlot::new(20, &[0, 10, 20]);
let (found, _missing) = cache.extract( let ExtractedPrograms {
loaded: found,
missing: _,
unloaded,
} = cache.extract(
&working_slot, &working_slot,
vec![(program1, (LoadedProgramMatchCriteria::NoCriteria, 1))].into_iter(), vec![(program1, (LoadedProgramMatchCriteria::NoCriteria, 1))].into_iter(),
); );
assert!(unloaded.is_empty());
// The cache should have the program deployed at slot 0 // The cache should have the program deployed at slot 0
assert_eq!( assert_eq!(
@ -1977,7 +2263,11 @@ mod tests {
assert!(!cache.replenish(program2, new_test_loaded_program(10, 11)).0); assert!(!cache.replenish(program2, new_test_loaded_program(10, 11)).0);
let working_slot = TestWorkingSlot::new(20, &[0, 10, 20]); let working_slot = TestWorkingSlot::new(20, &[0, 10, 20]);
let (found, _missing) = cache.extract( let ExtractedPrograms {
loaded: found,
missing: _,
unloaded: _,
} = cache.extract(
&working_slot, &working_slot,
vec![ vec![
(program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)),
@ -1990,7 +2280,11 @@ mod tests {
assert!(match_slot(&found, &program2, 10, 20)); assert!(match_slot(&found, &program2, 10, 20));
let working_slot = TestWorkingSlot::new(6, &[0, 5, 6]); let working_slot = TestWorkingSlot::new(6, &[0, 5, 6]);
let (found, missing) = cache.extract( let ExtractedPrograms {
loaded: found,
missing,
unloaded: _,
} = cache.extract(
&working_slot, &working_slot,
vec![ vec![
(program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)),
@ -2007,7 +2301,11 @@ mod tests {
cache.prune_by_deployment_slot(5); cache.prune_by_deployment_slot(5);
let working_slot = TestWorkingSlot::new(20, &[0, 10, 20]); let working_slot = TestWorkingSlot::new(20, &[0, 10, 20]);
let (found, _missing) = cache.extract( let ExtractedPrograms {
loaded: found,
missing: _,
unloaded: _,
} = cache.extract(
&working_slot, &working_slot,
vec![ vec![
(program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)),
@ -2020,7 +2318,11 @@ mod tests {
assert!(match_slot(&found, &program2, 10, 20)); assert!(match_slot(&found, &program2, 10, 20));
let working_slot = TestWorkingSlot::new(6, &[0, 5, 6]); let working_slot = TestWorkingSlot::new(6, &[0, 5, 6]);
let (found, missing) = cache.extract( let ExtractedPrograms {
loaded: found,
missing,
unloaded: _,
} = cache.extract(
&working_slot, &working_slot,
vec![ vec![
(program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)),
@ -2037,7 +2339,11 @@ mod tests {
cache.prune_by_deployment_slot(10); cache.prune_by_deployment_slot(10);
let working_slot = TestWorkingSlot::new(20, &[0, 10, 20]); let working_slot = TestWorkingSlot::new(20, &[0, 10, 20]);
let (found, _missing) = cache.extract( let ExtractedPrograms {
loaded: found,
missing: _,
unloaded: _,
} = cache.extract(
&working_slot, &working_slot,
vec![ vec![
(program1, (LoadedProgramMatchCriteria::NoCriteria, 1)), (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)),
@ -2052,35 +2358,6 @@ mod tests {
#[test] #[test]
fn test_usable_entries_for_slot() { 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)); let tombstone = Arc::new(LoadedProgram::new_tombstone(0, LoadedProgramType::Closed));
assert!(LoadedPrograms::is_entry_usable( assert!(LoadedPrograms::is_entry_usable(

View File

@ -75,22 +75,39 @@ pub fn load_program_from_bytes(
account_size: usize, account_size: usize,
deployment_slot: Slot, deployment_slot: Slot,
program_runtime_environment: Arc<BuiltinProgram<InvokeContext<'static>>>, program_runtime_environment: Arc<BuiltinProgram<InvokeContext<'static>>>,
reloading: bool,
) -> Result<LoadedProgram, InstructionError> { ) -> Result<LoadedProgram, InstructionError> {
let effective_slot = if feature_set.is_active(&delay_visibility_of_program_deployment::id()) { let effective_slot = if feature_set.is_active(&delay_visibility_of_program_deployment::id()) {
deployment_slot.saturating_add(DELAY_VISIBILITY_SLOT_OFFSET) deployment_slot.saturating_add(DELAY_VISIBILITY_SLOT_OFFSET)
} else { } else {
deployment_slot deployment_slot
}; };
let loaded_program = LoadedProgram::new( let loaded_program = if reloading {
loader_key, // Safety: this is safe because the program is being reloaded in the cache.
program_runtime_environment, unsafe {
deployment_slot, LoadedProgram::reload(
effective_slot, loader_key,
None, program_runtime_environment,
programdata, deployment_slot,
account_size, effective_slot,
load_program_metrics, 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| { .map_err(|err| {
ic_logger_msg!(log_collector, "{}", err); ic_logger_msg!(log_collector, "{}", err);
InstructionError::InvalidAccountData InstructionError::InvalidAccountData
@ -123,6 +140,7 @@ macro_rules! deploy_program {
$account_size, $account_size,
$slot, $slot,
Arc::new(program_runtime_environment), Arc::new(program_runtime_environment),
false,
)?; )?;
if let Some(old_entry) = $invoke_context.find_program_in_cache(&$program_id) { if let Some(old_entry) = $invoke_context.find_program_in_cache(&$program_id) {
executor.tx_usage_counter.store( executor.tx_usage_counter.store(
@ -1700,6 +1718,7 @@ pub mod test_utils {
account.data().len(), account.data().len(),
0, 0,
program_runtime_environment.clone(), program_runtime_environment.clone(),
false,
) { ) {
invoke_context invoke_context
.programs_modified_by_tx .programs_modified_by_tx

View File

@ -276,6 +276,7 @@ pub struct BankRc {
#[cfg(RUSTC_WITH_SPECIALIZATION)] #[cfg(RUSTC_WITH_SPECIALIZATION)]
use solana_frozen_abi::abi_example::AbiExample; use solana_frozen_abi::abi_example::AbiExample;
use solana_program_runtime::loaded_programs::ExtractedPrograms;
#[cfg(RUSTC_WITH_SPECIALIZATION)] #[cfg(RUSTC_WITH_SPECIALIZATION)]
impl AbiExample for BankRc { impl AbiExample for BankRc {
@ -4656,7 +4657,7 @@ impl Bank {
ProgramAccountLoadResult::InvalidAccountData ProgramAccountLoadResult::InvalidAccountData
} }
pub fn load_program(&self, pubkey: &Pubkey) -> Arc<LoadedProgram> { pub fn load_program(&self, pubkey: &Pubkey, reload: bool) -> Arc<LoadedProgram> {
let environments = self let environments = self
.loaded_programs_cache .loaded_programs_cache
.read() .read()
@ -4689,6 +4690,7 @@ impl Bank {
program_account.data().len(), program_account.data().len(),
0, 0,
environments.program_runtime_v1.clone(), environments.program_runtime_v1.clone(),
reload,
) )
} }
@ -4713,6 +4715,7 @@ impl Bank {
.saturating_add(programdata_account.data().len()), .saturating_add(programdata_account.data().len()),
slot, slot,
environments.program_runtime_v1.clone(), environments.program_runtime_v1.clone(),
reload,
) )
}), }),
@ -4721,16 +4724,32 @@ impl Bank {
.data() .data()
.get(LoaderV4State::program_data_offset()..) .get(LoaderV4State::program_data_offset()..)
.and_then(|elf_bytes| { .and_then(|elf_bytes| {
LoadedProgram::new( if reload {
&loader_v4::id(), // Safety: this is safe because the program is being reloaded in the cache.
environments.program_runtime_v2.clone(), unsafe {
slot, LoadedProgram::reload(
slot.saturating_add(DELAY_VISIBILITY_SLOT_OFFSET), &loader_v4::id(),
None, environments.program_runtime_v2.clone(),
elf_bytes, slot,
program_account.data().len(), slot.saturating_add(DELAY_VISIBILITY_SLOT_OFFSET),
&mut load_program_metrics, 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() .ok()
}) })
.unwrap_or(LoadedProgram::new_tombstone( .unwrap_or(LoadedProgram::new_tombstone(
@ -4987,17 +5006,31 @@ impl Bank {
.collect() .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 // Lock the global cache to figure out which programs need to be loaded
let loaded_programs_cache = self.loaded_programs_cache.read().unwrap(); let loaded_programs_cache = self.loaded_programs_cache.read().unwrap();
loaded_programs_cache.extract(self, programs_and_slots.into_iter()) loaded_programs_cache.extract(self, programs_and_slots.into_iter())
}; };
// Load missing programs while global cache is unlocked // Load missing programs while global cache is unlocked
let missing_programs: Vec<(Pubkey, Arc<LoadedProgram>)> = missing_programs let missing_programs: Vec<(Pubkey, Arc<LoadedProgram>)> = missing
.iter() .iter()
.map(|(key, count)| { .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<LoadedProgram>)> = unloaded
.iter()
.map(|(key, count)| {
let program = self.load_program(key, true);
program.tx_usage_counter.store(*count, Ordering::Relaxed); program.tx_usage_counter.store(*count, Ordering::Relaxed);
(*key, program) (*key, program)
}) })
@ -5010,6 +5043,11 @@ impl Bank {
// Use the returned entry as that might have been deduplicated globally // Use the returned entry as that might have been deduplicated globally
loaded_programs_for_txs.replenish(key, entry); 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 loaded_programs_for_txs
} }

View File

@ -7230,7 +7230,7 @@ fn test_bank_load_program() {
programdata_account.set_rent_epoch(1); programdata_account.set_rent_epoch(1);
bank.store_account_and_update_capitalization(&key1, &program_account); bank.store_account_and_update_capitalization(&key1, &program_account);
bank.store_account_and_update_capitalization(&programdata_key, &programdata_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_matches!(program.program, LoadedProgramType::LegacyV1(_));
assert_eq!( assert_eq!(
program.account_size, program.account_size,
@ -7385,7 +7385,7 @@ fn test_bpf_loader_upgradeable_deploy_with_max_len() {
assert_eq!(*elf.get(i).unwrap(), *byte); 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 // Invoke deployed program
mock_process_instruction( mock_process_instruction(