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:
parent
ec9b30965e
commit
5562f79cc5
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
||||
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,7 +963,17 @@ mod tests {
|
|||
slot: Slot,
|
||||
) -> Arc<LoadedProgram> {
|
||||
let unloaded = Arc::new(
|
||||
new_test_loaded_program(slot, slot.saturating_add(1))
|
||||
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"),
|
||||
);
|
||||
|
@ -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(
|
||||
|
|
|
@ -75,13 +75,17 @@ 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(
|
||||
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,
|
||||
|
@ -91,6 +95,19 @@ pub fn load_program_from_bytes(
|
|||
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
|
||||
|
|
|
@ -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,6 +4724,21 @@ impl Bank {
|
|||
.data()
|
||||
.get(LoaderV4State::program_data_offset()..)
|
||||
.and_then(|elf_bytes| {
|
||||
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(),
|
||||
|
@ -4731,6 +4749,7 @@ impl Bank {
|
|||
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
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
Loading…
Reference in New Issue