Check program modification slots during cold start (#31331)

This commit is contained in:
Pankaj Garg 2023-04-28 06:22:14 -07:00 committed by GitHub
parent aafcac27d8
commit 94dc8fed55
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 280 additions and 20 deletions

1
Cargo.lock generated
View File

@ -6699,6 +6699,7 @@ dependencies = [
"solana-config-program",
"solana-frozen-abi 1.16.0",
"solana-frozen-abi-macro 1.16.0",
"solana-loader-v3-program",
"solana-logger 1.16.0",
"solana-measure",
"solana-metrics",

View File

@ -319,6 +319,7 @@ solana-genesis-utils = { path = "genesis-utils", version = "=1.16.0" }
solana-geyser-plugin-interface = { path = "geyser-plugin-interface", version = "=1.16.0" }
solana-geyser-plugin-manager = { path = "geyser-plugin-manager", version = "=1.16.0" }
solana-gossip = { path = "gossip", version = "=1.16.0" }
solana-loader-v3-program = { path = "programs/loader-v3", version = "=1.16.0" }
solana-ledger = { path = "ledger", version = "=1.16.0" }
solana-local-cluster = { path = "local-cluster", version = "=1.16.0" }
solana-logger = { path = "logger", version = "=1.16.0" }

View File

@ -1434,7 +1434,7 @@ fn load_frozen_forks(
let mut progress = ConfirmationProgress::new(last_entry_hash);
let mut m = Measure::start("process_single_slot");
let bank = bank_forks.write().unwrap().insert(bank);
let bank = bank_forks.write().unwrap().insert_from_ledger(bank);
if process_single_slot(
blockstore,
&bank,

View File

@ -272,6 +272,12 @@ impl solana_frozen_abi::abi_example::AbiExample for LoadedPrograms {
}
}
pub enum LoadedProgramMatchCriteria {
DeployedOnOrAfterSlot(Slot),
Closed,
NoCriteria,
}
impl LoadedPrograms {
/// Refill the cache with a single entry. It's typically called during transaction loading,
/// when the cache doesn't contain the entry corresponding to program `key`.
@ -346,16 +352,31 @@ impl LoadedPrograms {
self.remove_programs_with_no_entries();
}
fn matches_loaded_program(
program: &Arc<LoadedProgram>,
criteria: &LoadedProgramMatchCriteria,
) -> bool {
match criteria {
LoadedProgramMatchCriteria::DeployedOnOrAfterSlot(slot) => {
program.deployment_slot >= *slot
}
LoadedProgramMatchCriteria::Closed => {
matches!(program.program, LoadedProgramType::Closed)
}
LoadedProgramMatchCriteria::NoCriteria => true,
}
}
/// Extracts a subset of the programs relevant to a transaction batch
/// and returns which program accounts the accounts DB needs to load.
pub fn extract<S: WorkingSlot>(
&self,
working_slot: &S,
keys: impl Iterator<Item = Pubkey>,
keys: impl Iterator<Item = (Pubkey, LoadedProgramMatchCriteria)>,
) -> (HashMap<Pubkey, Arc<LoadedProgram>>, Vec<Pubkey>) {
let mut missing = Vec::new();
let found = keys
.filter_map(|key| {
.filter_map(|(key, match_criteria)| {
if let Some(second_level) = self.entries.get(&key) {
for entry in second_level.iter().rev() {
let current_slot = working_slot.current_slot();
@ -374,6 +395,11 @@ impl LoadedPrograms {
return None;
}
if !Self::matches_loaded_program(entry, &match_criteria) {
missing.push(key);
return None;
}
if current_slot >= entry.effective_slot {
return Some((key, entry.clone()));
}
@ -501,7 +527,8 @@ impl LoadedPrograms {
mod tests {
use {
crate::loaded_programs::{
BlockRelation, ForkGraph, LoadedProgram, LoadedProgramType, LoadedPrograms, WorkingSlot,
BlockRelation, ForkGraph, LoadedProgram, LoadedProgramMatchCriteria, LoadedProgramType,
LoadedPrograms, WorkingSlot,
},
percentage::Percentage,
solana_rbpf::vm::BuiltInProgram,
@ -1153,7 +1180,13 @@ mod tests {
let working_slot = TestWorkingSlot::new(22, &[0, 10, 20, 22]);
let (found, missing) = cache.extract(
&working_slot,
vec![program1, program2, program3, program4].into_iter(),
vec![
(program1, LoadedProgramMatchCriteria::NoCriteria),
(program2, LoadedProgramMatchCriteria::NoCriteria),
(program3, LoadedProgramMatchCriteria::NoCriteria),
(program4, LoadedProgramMatchCriteria::NoCriteria),
]
.into_iter(),
);
assert!(match_slot(&found, &program1, 20));
@ -1166,7 +1199,13 @@ mod tests {
let mut working_slot = TestWorkingSlot::new(16, &[0, 5, 11, 15, 16, 18, 19, 23]);
let (found, missing) = cache.extract(
&working_slot,
vec![program1, program2, program3, program4].into_iter(),
vec![
(program1, LoadedProgramMatchCriteria::NoCriteria),
(program2, LoadedProgramMatchCriteria::NoCriteria),
(program3, LoadedProgramMatchCriteria::NoCriteria),
(program4, LoadedProgramMatchCriteria::NoCriteria),
]
.into_iter(),
);
assert!(match_slot(&found, &program1, 0));
@ -1181,7 +1220,13 @@ mod tests {
working_slot.update_slot(18);
let (found, missing) = cache.extract(
&working_slot,
vec![program1, program2, program3, program4].into_iter(),
vec![
(program1, LoadedProgramMatchCriteria::NoCriteria),
(program2, LoadedProgramMatchCriteria::NoCriteria),
(program3, LoadedProgramMatchCriteria::NoCriteria),
(program4, LoadedProgramMatchCriteria::NoCriteria),
]
.into_iter(),
);
assert!(match_slot(&found, &program1, 0));
@ -1196,7 +1241,13 @@ mod tests {
working_slot.update_slot(23);
let (found, missing) = cache.extract(
&working_slot,
vec![program1, program2, program3, program4].into_iter(),
vec![
(program1, LoadedProgramMatchCriteria::NoCriteria),
(program2, LoadedProgramMatchCriteria::NoCriteria),
(program3, LoadedProgramMatchCriteria::NoCriteria),
(program4, LoadedProgramMatchCriteria::NoCriteria),
]
.into_iter(),
);
assert!(match_slot(&found, &program1, 0));
@ -1211,7 +1262,13 @@ mod tests {
let working_slot = TestWorkingSlot::new(11, &[0, 5, 11, 15, 16]);
let (found, missing) = cache.extract(
&working_slot,
vec![program1, program2, program3, program4].into_iter(),
vec![
(program1, LoadedProgramMatchCriteria::NoCriteria),
(program2, LoadedProgramMatchCriteria::NoCriteria),
(program3, LoadedProgramMatchCriteria::NoCriteria),
(program4, LoadedProgramMatchCriteria::NoCriteria),
]
.into_iter(),
);
assert!(match_slot(&found, &program1, 0));
@ -1235,7 +1292,13 @@ mod tests {
let working_slot = TestWorkingSlot::new(19, &[0, 5, 11, 15, 16, 18, 19, 21, 23]);
let (found, missing) = cache.extract(
&working_slot,
vec![program1, program2, program3, program4].into_iter(),
vec![
(program1, LoadedProgramMatchCriteria::NoCriteria),
(program2, LoadedProgramMatchCriteria::NoCriteria),
(program3, LoadedProgramMatchCriteria::NoCriteria),
(program4, LoadedProgramMatchCriteria::NoCriteria),
]
.into_iter(),
);
assert!(match_slot(&found, &program1, 0));
@ -1250,7 +1313,13 @@ mod tests {
let working_slot = TestWorkingSlot::new(21, &[0, 5, 11, 15, 16, 18, 19, 21, 23]);
let (found, missing) = cache.extract(
&working_slot,
vec![program1, program2, program3, program4].into_iter(),
vec![
(program1, LoadedProgramMatchCriteria::NoCriteria),
(program2, LoadedProgramMatchCriteria::NoCriteria),
(program3, LoadedProgramMatchCriteria::NoCriteria),
(program4, LoadedProgramMatchCriteria::NoCriteria),
]
.into_iter(),
);
assert!(match_slot(&found, &program1, 0));
@ -1285,7 +1354,13 @@ mod tests {
let working_slot = TestWorkingSlot::new(22, &[0, 10, 20, 22]);
let (found, missing) = cache.extract(
&working_slot,
vec![program1, program2, program3, program4].into_iter(),
vec![
(program1, LoadedProgramMatchCriteria::NoCriteria),
(program2, LoadedProgramMatchCriteria::NoCriteria),
(program3, LoadedProgramMatchCriteria::NoCriteria),
(program4, LoadedProgramMatchCriteria::NoCriteria),
]
.into_iter(),
);
// Since the fork was pruned, we should not find the entry deployed at slot 20.
@ -1299,7 +1374,13 @@ mod tests {
let working_slot = TestWorkingSlot::new(27, &[0, 5, 11, 25, 27]);
let (found, _missing) = cache.extract(
&working_slot,
vec![program1, program2, program3, program4].into_iter(),
vec![
(program1, LoadedProgramMatchCriteria::NoCriteria),
(program2, LoadedProgramMatchCriteria::NoCriteria),
(program3, LoadedProgramMatchCriteria::NoCriteria),
(program4, LoadedProgramMatchCriteria::NoCriteria),
]
.into_iter(),
);
assert!(match_slot(&found, &program1, 0));
@ -1328,7 +1409,13 @@ mod tests {
let working_slot = TestWorkingSlot::new(27, &[0, 5, 11, 25, 27]);
let (found, missing) = cache.extract(
&working_slot,
vec![program1, program2, program3, program4].into_iter(),
vec![
(program1, LoadedProgramMatchCriteria::NoCriteria),
(program2, LoadedProgramMatchCriteria::NoCriteria),
(program3, LoadedProgramMatchCriteria::NoCriteria),
(program4, LoadedProgramMatchCriteria::NoCriteria),
]
.into_iter(),
);
assert!(match_slot(&found, &program1, 0));
@ -1339,6 +1426,81 @@ mod tests {
assert!(missing.contains(&program3));
}
#[test]
fn test_extract_using_deployment_slot() {
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();
assert!(!cache.replenish(program3, new_test_loaded_program(25, 26)).0);
// 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(
&working_slot,
vec![
(program1, LoadedProgramMatchCriteria::NoCriteria),
(program2, LoadedProgramMatchCriteria::NoCriteria),
(program3, LoadedProgramMatchCriteria::NoCriteria),
]
.into_iter(),
);
assert!(match_slot(&found, &program1, 0));
assert!(match_slot(&found, &program2, 11));
assert!(missing.contains(&program3));
// Test the same fork, but request the program modified at a later slot than what's in the cache.
let (found, missing) = cache.extract(
&working_slot,
vec![
(
program1,
LoadedProgramMatchCriteria::DeployedOnOrAfterSlot(5),
),
(
program2,
LoadedProgramMatchCriteria::DeployedOnOrAfterSlot(5),
),
(program3, LoadedProgramMatchCriteria::NoCriteria),
]
.into_iter(),
);
assert!(match_slot(&found, &program2, 11));
assert!(missing.contains(&program1));
assert!(missing.contains(&program3));
}
#[test]
fn test_prune_expired() {
let mut cache = LoadedPrograms::default();
@ -1389,7 +1551,12 @@ mod tests {
let working_slot = TestWorkingSlot::new(12, &[0, 5, 11, 12, 15, 16, 18, 19, 21, 23]);
let (found, missing) = cache.extract(
&working_slot,
vec![program1, program2, program3].into_iter(),
vec![
(program1, LoadedProgramMatchCriteria::NoCriteria),
(program2, LoadedProgramMatchCriteria::NoCriteria),
(program3, LoadedProgramMatchCriteria::NoCriteria),
]
.into_iter(),
);
// Program1 deployed at slot 11 should not be expired yet
@ -1403,7 +1570,12 @@ mod tests {
let working_slot = TestWorkingSlot::new(15, &[0, 5, 11, 15, 16, 18, 19, 21, 23]);
let (found, missing) = cache.extract(
&working_slot,
vec![program1, program2, program3].into_iter(),
vec![
(program1, LoadedProgramMatchCriteria::NoCriteria),
(program2, LoadedProgramMatchCriteria::NoCriteria),
(program3, LoadedProgramMatchCriteria::NoCriteria),
]
.into_iter(),
);
assert!(match_slot(&found, &program2, 11));
@ -1462,7 +1634,10 @@ mod tests {
cache.prune(&fork_graph, 10);
let working_slot = TestWorkingSlot::new(20, &[0, 10, 20]);
let (found, _missing) = cache.extract(&working_slot, vec![program1].into_iter());
let (found, _missing) = cache.extract(
&working_slot,
vec![(program1, LoadedProgramMatchCriteria::NoCriteria)].into_iter(),
);
// The cache should have the program deployed at slot 0
assert_eq!(

View File

@ -37,7 +37,7 @@ use {
},
};
fn get_state(data: &[u8]) -> Result<&LoaderV3State, InstructionError> {
pub fn get_state(data: &[u8]) -> Result<&LoaderV3State, InstructionError> {
unsafe {
let data = data
.get(0..LoaderV3State::program_data_offset())

View File

@ -5113,6 +5113,18 @@ dependencies = [
"trees",
]
[[package]]
name = "solana-loader-v3-program"
version = "1.16.0"
dependencies = [
"log",
"rand 0.7.3",
"solana-measure",
"solana-program-runtime",
"solana-sdk 1.16.0",
"solana_rbpf",
]
[[package]]
name = "solana-logger"
version = "1.15.2"
@ -5608,6 +5620,7 @@ dependencies = [
"solana-config-program",
"solana-frozen-abi 1.16.0",
"solana-frozen-abi-macro 1.16.0",
"solana-loader-v3-program",
"solana-measure",
"solana-metrics",
"solana-perf",

View File

@ -49,6 +49,7 @@ solana-compute-budget-program = { workspace = true }
solana-config-program = { workspace = true }
solana-frozen-abi = { workspace = true }
solana-frozen-abi-macro = { workspace = true }
solana-loader-v3-program = { workspace = true }
solana-measure = { workspace = true }
solana-metrics = { workspace = true }
solana-perf = { workspace = true }

View File

@ -136,6 +136,7 @@ use {
inflation::Inflation,
instruction::{CompiledInstruction, TRANSACTION_LEVEL_STACK_HEIGHT},
lamports::LamportsError,
loader_v3,
message::{AccountKeys, SanitizedMessage},
native_loader,
native_token::{sol_to_lamports, LAMPORTS_PER_SOL},
@ -257,6 +258,7 @@ pub struct BankRc {
#[cfg(RUSTC_WITH_SPECIALIZATION)]
use solana_frozen_abi::abi_example::AbiExample;
use solana_program_runtime::loaded_programs::LoadedProgramMatchCriteria;
#[cfg(RUSTC_WITH_SPECIALIZATION)]
impl AbiExample for BankRc {
@ -794,6 +796,7 @@ impl PartialEq for Bank {
fee_structure: _,
incremental_snapshot_persistence: _,
loaded_programs_cache: _,
check_program_modification_slot: _,
// Ignore new fields explicitly if they do not impact PartialEq.
// Adding ".." will remove compile-time checks that if a new field
// is added to the struct, this PartialEq is accordingly updated.
@ -1049,6 +1052,8 @@ pub struct Bank {
pub loaded_programs_cache: Arc<RwLock<LoadedPrograms>>,
pub check_program_modification_slot: bool,
/// true when the bank's freezing or destruction has completed
bank_freeze_or_destruction_incremented: AtomicBool,
}
@ -1269,6 +1274,7 @@ impl Bank {
accounts_data_size_delta_off_chain: AtomicI64::new(0),
fee_structure: FeeStructure::default(),
loaded_programs_cache: Arc::<RwLock<LoadedPrograms>>::default(),
check_program_modification_slot: false,
};
bank.bank_created();
@ -1567,6 +1573,7 @@ impl Bank {
accounts_data_size_delta_off_chain: AtomicI64::new(0),
fee_structure: parent.fee_structure.clone(),
loaded_programs_cache: parent.loaded_programs_cache.clone(),
check_program_modification_slot: false,
};
let (_, ancestors_time_us) = measure_us!({
@ -1893,6 +1900,7 @@ impl Bank {
accounts_data_size_delta_off_chain: AtomicI64::new(0),
fee_structure: FeeStructure::default(),
loaded_programs_cache: Arc::<RwLock<LoadedPrograms>>::default(),
check_program_modification_slot: false,
};
bank.bank_created();
@ -4110,6 +4118,36 @@ impl Bank {
let _ = self.executor_cache.write().unwrap().remove(pubkey);
}
fn program_modification_slot(&self, pubkey: &Pubkey) -> Result<Slot> {
let program = self
.get_account_with_fixed_root(pubkey)
.ok_or(TransactionError::ProgramAccountNotFound)?;
if bpf_loader_upgradeable::check_id(program.owner()) {
if let Ok(UpgradeableLoaderState::Program {
programdata_address,
}) = program.state()
{
let programdata = self
.get_account_with_fixed_root(&programdata_address)
.ok_or(TransactionError::ProgramAccountNotFound)?;
if let Ok(UpgradeableLoaderState::ProgramData {
slot,
upgrade_authority_address: _,
}) = programdata.state()
{
return Ok(slot);
}
}
Err(TransactionError::ProgramAccountNotFound)
} else if loader_v3::check_id(program.owner()) {
let state = solana_loader_v3_program::get_state(program.data())
.map_err(|_| TransactionError::ProgramAccountNotFound)?;
Ok(state.slot)
} else {
Ok(0)
}
}
#[allow(dead_code)] // Preparation for BankExecutorCache rework
fn load_program(&self, pubkey: &Pubkey) -> Result<Arc<LoadedProgram>> {
let program = if let Some(program) = self.get_account_with_fixed_root(pubkey) {
@ -4386,10 +4424,31 @@ impl Bank {
&self,
program_accounts_map: &HashMap<Pubkey, &Pubkey>,
) -> HashMap<Pubkey, Arc<LoadedProgram>> {
let programs_and_slots: Vec<(Pubkey, LoadedProgramMatchCriteria)> =
if self.check_program_modification_slot {
program_accounts_map
.keys()
.map(|pubkey| {
(
*pubkey,
self.program_modification_slot(pubkey)
.map_or(LoadedProgramMatchCriteria::Closed, |slot| {
LoadedProgramMatchCriteria::DeployedOnOrAfterSlot(slot)
}),
)
})
.collect()
} else {
program_accounts_map
.keys()
.map(|pubkey| (*pubkey, LoadedProgramMatchCriteria::NoCriteria))
.collect()
};
let (mut loaded_programs_for_txs, missing_programs) = {
// 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, program_accounts_map.keys().cloned())
loaded_programs_cache.extract(self, programs_and_slots.into_iter())
};
// Load missing programs while global cache is unlocked

View File

@ -66,6 +66,7 @@ pub struct BankForks {
pub accounts_hash_interval_slots: Slot,
last_accounts_hash_slot: Slot,
in_vote_only_mode: Arc<AtomicBool>,
highest_slot_at_startup: Slot,
}
impl Index<u64> for BankForks {
@ -182,10 +183,14 @@ impl BankForks {
accounts_hash_interval_slots: std::u64::MAX,
last_accounts_hash_slot: root,
in_vote_only_mode: Arc::new(AtomicBool::new(false)),
highest_slot_at_startup: 0,
}
}
pub fn insert(&mut self, bank: Bank) -> Arc<Bank> {
pub fn insert(&mut self, mut bank: Bank) -> Arc<Bank> {
bank.check_program_modification_slot =
self.root.load(Ordering::Relaxed) < self.highest_slot_at_startup;
let bank = Arc::new(bank);
let prev = self.banks.insert(bank.slot(), bank.clone());
assert!(prev.is_none());
@ -197,6 +202,11 @@ impl BankForks {
bank
}
pub fn insert_from_ledger(&mut self, bank: Bank) -> Arc<Bank> {
self.highest_slot_at_startup = std::cmp::max(self.highest_slot_at_startup, bank.slot());
self.insert(bank)
}
pub fn remove(&mut self, slot: Slot) -> Option<Arc<Bank>> {
let bank = self.banks.remove(&slot)?;
for parent in bank.proper_ancestors() {