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,
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;

View File

@ -228,16 +228,76 @@ impl LoadedProgram {
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,
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>> {
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::<RequisiteVerifier>()?;
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::<RequisiteVerifier>()?;
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<Item = (Pubkey, (LoadedProgramMatchCriteria, u64))>,
) -> (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<LoadedProgram> {
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(

View File

@ -75,22 +75,39 @@ pub fn load_program_from_bytes(
account_size: usize,
deployment_slot: Slot,
program_runtime_environment: Arc<BuiltinProgram<InvokeContext<'static>>>,
reloading: bool,
) -> Result<LoadedProgram, InstructionError> {
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

View File

@ -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<LoadedProgram> {
pub fn load_program(&self, pubkey: &Pubkey, reload: bool) -> Arc<LoadedProgram> {
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<LoadedProgram>)> = missing_programs
let missing_programs: Vec<(Pubkey, Arc<LoadedProgram>)> = 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<LoadedProgram>)> = 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
}

View File

@ -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(