From 375f9ae41d80fb7ab93c13ee834388fe34c3786d Mon Sep 17 00:00:00 2001 From: Pankaj Garg Date: Wed, 8 Feb 2023 13:24:44 -0800 Subject: [PATCH] LoadedPrograms cache implementation and tests (#30139) --- program-runtime/src/lib.rs | 1 + program-runtime/src/loaded_programs.rs | 614 +++++++++++++++++++++++++ runtime/src/bank.rs | 17 + runtime/src/bank_forks.rs | 174 +++++++ runtime/src/rent_debit.rs | 0 5 files changed, 806 insertions(+) create mode 100644 program-runtime/src/loaded_programs.rs create mode 100644 runtime/src/rent_debit.rs diff --git a/program-runtime/src/lib.rs b/program-runtime/src/lib.rs index 315f2b6e9d..f7a47e114f 100644 --- a/program-runtime/src/lib.rs +++ b/program-runtime/src/lib.rs @@ -14,6 +14,7 @@ pub mod compute_budget; pub mod executor; pub mod executor_cache; pub mod invoke_context; +pub mod loaded_programs; pub mod log_collector; pub mod pre_account; pub mod prioritization_fee; diff --git a/program-runtime/src/loaded_programs.rs b/program-runtime/src/loaded_programs.rs new file mode 100644 index 0000000000..d6a992145d --- /dev/null +++ b/program-runtime/src/loaded_programs.rs @@ -0,0 +1,614 @@ +use { + crate::invoke_context::InvokeContext, + solana_rbpf::{ + elf::Executable, + error::EbpfError, + verifier::RequisiteVerifier, + vm::{BuiltInProgram, VerifiedExecutable}, + }, + solana_sdk::{ + bpf_loader, bpf_loader_deprecated, bpf_loader_upgradeable, clock::Slot, pubkey::Pubkey, + }, + std::{ + collections::HashMap, + fmt::{Debug, Formatter}, + sync::{atomic::AtomicU64, Arc}, + }, +}; + +/// Relationship between two fork IDs +#[derive(Copy, Clone, PartialEq)] +pub enum BlockRelation { + /// The slot is on the same fork and is an ancestor of the other slot + Ancestor, + /// The two slots are equal and are on the same fork + Equal, + /// The slot is on the same fork and is a descendant of the other slot + Descendant, + /// The slots are on two different forks and may have had a common ancestor at some point + Unrelated, + /// Either one or both of the slots are either older than the latest root, or are in future + Unknown, +} + +/// Maps relationship between two slots. +pub trait ForkGraph { + /// Returns the BlockRelation of A to B + fn relationship(&self, a: Slot, b: Slot) -> BlockRelation; +} + +/// Provides information about current working slot, and its ancestors +pub trait WorkingSlot { + /// Returns the current slot value + fn current_slot(&self) -> Slot; + + /// Returns true if the `other` slot is an ancestor of self, false otherwise + fn is_ancestor(&self, other: Slot) -> bool; +} + +pub enum LoadedProgramType { + /// Tombstone for undeployed, closed or unloadable programs + Invalid, + LegacyV0(VerifiedExecutable>), + LegacyV1(VerifiedExecutable>), + // Typed(TypedProgram>), + BuiltIn(BuiltInProgram>), +} + +impl Debug for LoadedProgramType { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + LoadedProgramType::Invalid => write!(f, "LoadedProgramType::Invalid"), + LoadedProgramType::LegacyV0(_) => write!(f, "LoadedProgramType::LegacyV0"), + LoadedProgramType::LegacyV1(_) => write!(f, "LoadedProgramType::LegacyV1"), + LoadedProgramType::BuiltIn(_) => write!(f, "LoadedProgramType::BuiltIn"), + } + } +} + +#[derive(Debug)] +pub struct LoadedProgram { + /// The program of this entry + pub program: LoadedProgramType, + /// Slot in which the program was (re)deployed + pub deployment_slot: Slot, + /// Slot in which this entry will become active (can be in the future) + pub effective_slot: Slot, + /// How often this entry was used + pub usage_counter: AtomicU64, +} + +impl LoadedProgram { + /// Creates a new user program + pub fn new( + loader_key: &Pubkey, + loader: Arc>>, + deployment_slot: Slot, + elf_bytes: &[u8], + ) -> Result { + let program = if bpf_loader_deprecated::check_id(loader_key) { + let executable = Executable::load(elf_bytes, loader.clone())?; + LoadedProgramType::LegacyV0(VerifiedExecutable::from_executable(executable)?) + } else if bpf_loader::check_id(loader_key) || bpf_loader_upgradeable::check_id(loader_key) { + let executable = Executable::load(elf_bytes, loader.clone())?; + LoadedProgramType::LegacyV1(VerifiedExecutable::from_executable(executable)?) + } else { + panic!(); + }; + Ok(Self { + deployment_slot, + effective_slot: deployment_slot.saturating_add(1), + usage_counter: AtomicU64::new(0), + program, + }) + } + + /// Creates a new built-in program + pub fn new_built_in( + deployment_slot: Slot, + program: BuiltInProgram>, + ) -> Self { + Self { + deployment_slot, + effective_slot: deployment_slot.saturating_add(1), + usage_counter: AtomicU64::new(0), + program: LoadedProgramType::BuiltIn(program), + } + } +} + +#[derive(Debug, Default)] +pub struct LoadedPrograms { + /// A two level index: + /// + /// Pubkey is the address of a program, multiple versions can coexists simultaneously under the same address (in different slots). + entries: HashMap>>, +} + +#[cfg(RUSTC_WITH_SPECIALIZATION)] +impl solana_frozen_abi::abi_example::AbiExample for LoadedPrograms { + fn example() -> Self { + // Delegate AbiExample impl to Default before going deep and stuck with + // not easily impl-able Arc due to rust's coherence issue + // This is safe because LoadedPrograms isn't serializable by definition. + Self::default() + } +} + +impl LoadedPrograms { + /// Inserts a single entry + pub fn insert_entry(&mut self, key: Pubkey, entry: LoadedProgram) -> bool { + let second_level = self.entries.entry(key).or_insert_with(Vec::new); + let index = second_level + .iter() + .position(|at| at.effective_slot >= entry.effective_slot); + if let Some(index) = index { + let existing = second_level + .get(index) + .expect("Missing entry, even though position was found"); + if existing.deployment_slot == entry.deployment_slot + && existing.effective_slot == entry.effective_slot + { + return false; + } + } + second_level.insert(index.unwrap_or(second_level.len()), Arc::new(entry)); + true + } + + /// Before rerooting the blockstore this removes all programs of orphan forks + pub fn prune(&mut self, fork_graph: &F, new_root: Slot) { + self.entries.retain(|_key, second_level| { + let mut first_ancestor = true; + *second_level = second_level + .iter() + .rev() + .filter(|entry| { + let relation = fork_graph.relationship(entry.deployment_slot, new_root); + if entry.deployment_slot >= new_root { + matches!(relation, BlockRelation::Equal | BlockRelation::Descendant) + } else if first_ancestor { + first_ancestor = false; + matches!(relation, BlockRelation::Ancestor) + } else { + false + } + }) + .cloned() + .collect(); + second_level.reverse(); + !second_level.is_empty() + }); + } + + /// 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( + &self, + working_slot: &S, + keys: impl Iterator, + ) -> (HashMap>, Vec) { + let mut missing = Vec::new(); + let found = keys + .filter_map(|key| { + if let Some(second_level) = self.entries.get(&key) { + for entry in second_level.iter().rev() { + if working_slot.current_slot() >= entry.effective_slot + && working_slot.is_ancestor(entry.deployment_slot) + { + return Some((key, entry.clone())); + } + } + } + missing.push(key); + None + }) + .collect(); + (found, missing) + } + + /// Evicts programs which were used infrequently + pub fn sort_and_evict(&mut self) { + // TODO: Sort programs by their usage_counter + // TODO: Truncate the end of the list + } + + /// Removes the entries at the given keys, if they exist + pub fn remove_entries(&mut self, _key: impl Iterator) { + // TODO: Remove at primary index level + } +} + +#[cfg(test)] +mod tests { + use { + crate::loaded_programs::{ + BlockRelation, ForkGraph, LoadedProgram, LoadedProgramType, LoadedPrograms, WorkingSlot, + }, + solana_sdk::{clock::Slot, pubkey::Pubkey}, + std::{ + collections::HashMap, + ops::ControlFlow, + sync::{atomic::AtomicU64, Arc}, + }, + }; + + struct TestForkGraph { + relation: BlockRelation, + } + impl ForkGraph for TestForkGraph { + fn relationship(&self, _a: Slot, _b: Slot) -> BlockRelation { + self.relation + } + } + + #[test] + fn test_prune_empty() { + let mut cache = LoadedPrograms::default(); + let fork_graph = TestForkGraph { + relation: BlockRelation::Unrelated, + }; + + cache.prune(&fork_graph, 0); + assert!(cache.entries.is_empty()); + + cache.prune(&fork_graph, 10); + assert!(cache.entries.is_empty()); + + let fork_graph = TestForkGraph { + relation: BlockRelation::Ancestor, + }; + + cache.prune(&fork_graph, 0); + assert!(cache.entries.is_empty()); + + cache.prune(&fork_graph, 10); + assert!(cache.entries.is_empty()); + + let fork_graph = TestForkGraph { + relation: BlockRelation::Descendant, + }; + + cache.prune(&fork_graph, 0); + assert!(cache.entries.is_empty()); + + cache.prune(&fork_graph, 10); + assert!(cache.entries.is_empty()); + + let fork_graph = TestForkGraph { + relation: BlockRelation::Unknown, + }; + + cache.prune(&fork_graph, 0); + assert!(cache.entries.is_empty()); + + cache.prune(&fork_graph, 10); + assert!(cache.entries.is_empty()); + } + + #[derive(Default)] + struct TestForkGraphSpecific { + forks: Vec>, + } + + impl TestForkGraphSpecific { + fn insert_fork(&mut self, fork: &[Slot]) { + let mut fork = fork.to_vec(); + fork.sort(); + self.forks.push(fork) + } + } + + impl ForkGraph for TestForkGraphSpecific { + fn relationship(&self, a: Slot, b: Slot) -> BlockRelation { + match self.forks.iter().try_for_each(|fork| { + let relation = fork + .iter() + .position(|x| *x == a) + .and_then(|a_pos| { + fork.iter().position(|x| *x == b).and_then(|b_pos| { + (a_pos == b_pos) + .then_some(BlockRelation::Equal) + .or_else(|| (a_pos < b_pos).then_some(BlockRelation::Ancestor)) + .or(Some(BlockRelation::Descendant)) + }) + }) + .unwrap_or(BlockRelation::Unrelated); + + if relation != BlockRelation::Unrelated { + return ControlFlow::Break(relation); + } + + ControlFlow::Continue(()) + }) { + ControlFlow::Break(relation) => relation, + _ => BlockRelation::Unrelated, + } + } + } + + struct TestWorkingSlot { + slot: Slot, + fork: Vec, + slot_pos: usize, + } + + impl TestWorkingSlot { + fn new(slot: Slot, fork: &[Slot]) -> Self { + let mut fork = fork.to_vec(); + fork.sort(); + let slot_pos = fork + .iter() + .position(|current| *current == slot) + .expect("The fork didn't have the slot in it"); + TestWorkingSlot { + slot, + fork, + slot_pos, + } + } + + fn update_slot(&mut self, slot: Slot) { + self.slot = slot; + self.slot_pos = self + .fork + .iter() + .position(|current| *current == slot) + .expect("The fork didn't have the slot in it"); + } + } + + impl WorkingSlot for TestWorkingSlot { + fn current_slot(&self) -> Slot { + self.slot + } + + fn is_ancestor(&self, other: Slot) -> bool { + self.fork + .iter() + .position(|current| *current == other) + .map(|other_pos| other_pos < self.slot_pos) + .unwrap_or(false) + } + } + + fn new_test_loaded_program(deployment_slot: Slot, effective_slot: Slot) -> Arc { + Arc::new(LoadedProgram { + program: LoadedProgramType::Invalid, + deployment_slot, + effective_slot, + usage_counter: AtomicU64::default(), + }) + } + + fn match_slot( + table: &HashMap>, + program: &Pubkey, + deployment_slot: Slot, + ) -> bool { + table + .get(program) + .map(|entry| entry.deployment_slot == deployment_slot) + .unwrap_or(false) + } + + #[test] + fn test_fork_extract_and_prune() { + 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]); + fork_graph.insert_fork(&[0, 5, 11, 25, 27]); + + let program1 = Pubkey::new_unique(); + cache.entries.insert( + program1, + vec![ + new_test_loaded_program(0, 1), + new_test_loaded_program(10, 11), + new_test_loaded_program(20, 21), + ], + ); + + let program2 = Pubkey::new_unique(); + cache.entries.insert( + program2, + vec![ + new_test_loaded_program(5, 6), + new_test_loaded_program(11, 12), + ], + ); + + let program3 = Pubkey::new_unique(); + cache + .entries + .insert(program3, vec![new_test_loaded_program(25, 26)]); + + let program4 = Pubkey::new_unique(); + cache.entries.insert( + program4, + vec![ + new_test_loaded_program(0, 1), + new_test_loaded_program(5, 6), + // The following is a special case, where effective slot is 4 slots in the future + new_test_loaded_program(15, 19), + ], + ); + + // Current fork graph + // 0 + // / \ + // 10 5 + // | | + // 20 11 + // | | \ + // 22 15 25 + // | | + // 16 27 + // | + // 19 + // | + // 23 + + // 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( + &working_slot, + vec![program1, program2, program3, program4].into_iter(), + ); + + assert!(match_slot(&found, &program1, 20)); + assert!(match_slot(&found, &program4, 0)); + + assert!(missing.contains(&program2)); + assert!(missing.contains(&program3)); + + // Testing fork 0 - 5 - 11 - 15 - 16 with current slot at 16 + let mut working_slot = TestWorkingSlot::new(16, &[0, 5, 11, 15, 16, 19, 23]); + let (found, missing) = cache.extract( + &working_slot, + vec![program1, program2, program3, program4].into_iter(), + ); + + assert!(match_slot(&found, &program1, 0)); + assert!(match_slot(&found, &program2, 11)); + + // The effective slot of program4 deployed in slot 15 is 19. So it should not be usable in slot 16. + assert!(match_slot(&found, &program4, 5)); + + assert!(missing.contains(&program3)); + + // Testing the same fork above, but current slot is now 19 (equal to effective slot of program4). + working_slot.update_slot(19); + let (found, missing) = cache.extract( + &working_slot, + vec![program1, program2, program3, program4].into_iter(), + ); + + assert!(match_slot(&found, &program1, 0)); + assert!(match_slot(&found, &program2, 11)); + + // The effective slot of program4 deployed in slot 15 is 19. So it should be usable in slot 19. + assert!(match_slot(&found, &program4, 15)); + + assert!(missing.contains(&program3)); + + // 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( + &working_slot, + vec![program1, program2, program3, program4].into_iter(), + ); + + assert!(match_slot(&found, &program1, 0)); + assert!(match_slot(&found, &program2, 11)); + + // The effective slot of program4 deployed in slot 15 is 19. So it should be usable in slot 23. + assert!(match_slot(&found, &program4, 15)); + + assert!(missing.contains(&program3)); + + // 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( + &working_slot, + vec![program1, program2, program3, program4].into_iter(), + ); + + assert!(match_slot(&found, &program1, 0)); + assert!(match_slot(&found, &program2, 5)); + assert!(match_slot(&found, &program4, 5)); + + assert!(missing.contains(&program3)); + + cache.prune(&fork_graph, 5); + + // Fork graph after pruning + // 0 + // | + // 5 + // | + // 11 + // | \ + // 15 25 + // | | + // 16 27 + // | + // 19 + // | + // 23 + + // Testing fork 0 - 10 - 12 - 22 (which was pruned) with current slot at 22 + let working_slot = TestWorkingSlot::new(22, &[0, 10, 20, 22]); + let (found, missing) = cache.extract( + &working_slot, + vec![program1, program2, program3, program4].into_iter(), + ); + + // Since the fork was pruned, we should not find the entry deployed at slot 20. + assert!(match_slot(&found, &program1, 0)); + assert!(match_slot(&found, &program4, 0)); + + assert!(missing.contains(&program2)); + assert!(missing.contains(&program3)); + + // Testing fork 0 - 5 - 11 - 25 - 27 with current slot at 27 + 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(), + ); + + assert!(match_slot(&found, &program1, 0)); + assert!(match_slot(&found, &program2, 11)); + assert!(match_slot(&found, &program3, 25)); + assert!(match_slot(&found, &program4, 5)); + + cache.prune(&fork_graph, 15); + + // Fork graph after pruning + // 0 + // | + // 5 + // | + // 11 + // | + // 15 + // | + // 16 + // | + // 19 + // | + // 23 + + // Testing fork 0 - 5 - 11 - 25 - 27 (with root at 15, slot 25, 27 are pruned) with current slot at 27 + 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(), + ); + + assert!(match_slot(&found, &program1, 0)); + assert!(match_slot(&found, &program2, 11)); + assert!(match_slot(&found, &program4, 5)); + + // program3 was deployed on slot 25, which has been pruned + assert!(missing.contains(&program3)); + } +} diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index 07fd678514..b58d1d0748 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -98,6 +98,7 @@ use { MAX_CACHED_EXECUTORS, }, invoke_context::{BuiltinProgram, ProcessInstructionWithContext}, + loaded_programs::{LoadedPrograms, WorkingSlot}, log_collector::LogCollector, sysvar_cache::SysvarCache, timings::{ExecuteTimingType, ExecuteTimings}, @@ -862,6 +863,7 @@ impl PartialEq for Bank { accounts_data_size_delta_off_chain: _, fee_structure: _, incremental_snapshot_persistence: _, + loaded_programs_cache: _, // 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. @@ -1119,6 +1121,8 @@ pub struct Bank { pub fee_structure: FeeStructure, pub incremental_snapshot_persistence: Option, + + pub loaded_programs_cache: Arc>, } struct VoteWithStakeDelegations { @@ -1198,6 +1202,16 @@ impl<'a> StorableAccounts<'a, AccountSharedData> for (Slot, &'a [StakeReward], I } } +impl WorkingSlot for Bank { + fn current_slot(&self) -> Slot { + self.slot + } + + fn is_ancestor(&self, other: Slot) -> bool { + self.ancestors.contains_key(&other) + } +} + impl Bank { pub fn default_for_tests() -> Self { Self::default_with_accounts(Accounts::default_for_tests()) @@ -1318,6 +1332,7 @@ impl Bank { accounts_data_size_delta_on_chain: AtomicI64::new(0), accounts_data_size_delta_off_chain: AtomicI64::new(0), fee_structure: FeeStructure::default(), + loaded_programs_cache: Arc::>::default(), }; let accounts_data_size_initial = bank.get_total_accounts_stats().unwrap().data_len as u64; @@ -1612,6 +1627,7 @@ impl Bank { accounts_data_size_delta_on_chain: AtomicI64::new(0), accounts_data_size_delta_off_chain: AtomicI64::new(0), fee_structure: parent.fee_structure.clone(), + loaded_programs_cache: parent.loaded_programs_cache.clone(), }; let (_, ancestors_time_us) = measure_us!({ @@ -1906,6 +1922,7 @@ impl Bank { accounts_data_size_delta_on_chain: AtomicI64::new(0), accounts_data_size_delta_off_chain: AtomicI64::new(0), fee_structure: FeeStructure::default(), + loaded_programs_cache: Arc::>::default(), }; bank.finish_init( genesis_config, diff --git a/runtime/src/bank_forks.rs b/runtime/src/bank_forks.rs index caec9b0071..613309bd60 100644 --- a/runtime/src/bank_forks.rs +++ b/runtime/src/bank_forks.rs @@ -9,6 +9,7 @@ use { }, log::*, solana_measure::measure::Measure, + solana_program_runtime::loaded_programs::{BlockRelation, ForkGraph, WorkingSlot}, solana_sdk::{clock::Slot, feature_set, hash::Hash, timing}, std::{ collections::{hash_map::Entry, HashMap, HashSet}, @@ -422,6 +423,16 @@ impl BankForks { accounts_background_request_sender: &AbsRequestSender, highest_confirmed_root: Option, ) -> Vec> { + let program_cache_prune_start = Instant::now(); + let root_bank = self + .banks + .get(&root) + .expect("root bank didn't exist in bank_forks"); + root_bank + .loaded_programs_cache + .write() + .unwrap() + .prune(self, root); let set_root_start = Instant::now(); let (removed_banks, set_root_metrics) = self.do_set_root_return_metrics( root, @@ -493,6 +504,11 @@ impl BankForks { set_root_metrics.timings.prune_remove_ms, i64 ), + ( + "program_cache_prune_ms", + timing::duration_as_ms(&program_cache_prune_start.elapsed()) as usize, + i64 + ), ("dropped_banks_len", set_root_metrics.dropped_banks_len, i64), ("accounts_data_len", set_root_metrics.accounts_data_len, i64), ); @@ -629,6 +645,29 @@ impl BankForks { } } +impl ForkGraph for BankForks { + fn relationship(&self, a: Slot, b: Slot) -> BlockRelation { + let known_slot_range = self.root()..=self.highest_slot(); + (known_slot_range.contains(&a) && known_slot_range.contains(&b)) + .then(|| { + (a == b) + .then_some(BlockRelation::Equal) + .or_else(|| { + self.banks + .get(&b) + .and_then(|bank| bank.is_ancestor(a).then_some(BlockRelation::Ancestor)) + }) + .or_else(|| { + self.descendants.get(&b).and_then(|slots| { + slots.contains(&a).then_some(BlockRelation::Descendant) + }) + }) + .unwrap_or(BlockRelation::Unrelated) + }) + .unwrap_or(BlockRelation::Unknown) + } +} + #[cfg(test)] mod tests { use { @@ -962,4 +1001,139 @@ mod tests { ]) ); } + + #[test] + fn test_fork_graph() { + let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(10_000); + let bank = Bank::new_for_tests(&genesis_config); + let mut bank_forks = BankForks::new(bank); + + let bank = Bank::new_from_parent(&bank_forks[0], &Pubkey::default(), 1); + bank_forks.insert(bank); + let bank = Bank::new_from_parent(&bank_forks[1], &Pubkey::default(), 3); + bank_forks.insert(bank); + let bank = Bank::new_from_parent(&bank_forks[3], &Pubkey::default(), 8); + bank_forks.insert(bank); + + let bank = Bank::new_from_parent(&bank_forks[0], &Pubkey::default(), 2); + bank_forks.insert(bank); + let bank = Bank::new_from_parent(&bank_forks[2], &Pubkey::default(), 4); + bank_forks.insert(bank); + let bank = Bank::new_from_parent(&bank_forks[4], &Pubkey::default(), 5); + bank_forks.insert(bank); + let bank = Bank::new_from_parent(&bank_forks[5], &Pubkey::default(), 10); + bank_forks.insert(bank); + + let bank = Bank::new_from_parent(&bank_forks[4], &Pubkey::default(), 6); + bank_forks.insert(bank); + let bank = Bank::new_from_parent(&bank_forks[6], &Pubkey::default(), 12); + bank_forks.insert(bank); + + // Fork graph created for the test + // 0 + // / \ + // 1 2 + // | | + // 3 4 + // | | \ + // 8 5 6 + // | | + // 10 12 + + assert!(matches!( + bank_forks.relationship(0, 3), + BlockRelation::Ancestor + )); + assert!(matches!( + bank_forks.relationship(0, 10), + BlockRelation::Ancestor + )); + assert!(matches!( + bank_forks.relationship(0, 12), + BlockRelation::Ancestor + )); + assert!(matches!( + bank_forks.relationship(1, 3), + BlockRelation::Ancestor + )); + assert!(matches!( + bank_forks.relationship(2, 10), + BlockRelation::Ancestor + )); + assert!(matches!( + bank_forks.relationship(2, 12), + BlockRelation::Ancestor + )); + assert!(matches!( + bank_forks.relationship(4, 10), + BlockRelation::Ancestor + )); + assert!(matches!( + bank_forks.relationship(4, 12), + BlockRelation::Ancestor + )); + assert!(matches!( + bank_forks.relationship(6, 10), + BlockRelation::Unrelated + )); + assert!(matches!( + bank_forks.relationship(5, 12), + BlockRelation::Unrelated + )); + assert!(matches!( + bank_forks.relationship(6, 12), + BlockRelation::Ancestor + )); + + assert!(matches!( + bank_forks.relationship(6, 2), + BlockRelation::Descendant + )); + assert!(matches!( + bank_forks.relationship(10, 2), + BlockRelation::Descendant + )); + assert!(matches!( + bank_forks.relationship(8, 3), + BlockRelation::Descendant + )); + assert!(matches!( + bank_forks.relationship(6, 3), + BlockRelation::Unrelated + )); + assert!(matches!( + bank_forks.relationship(12, 2), + BlockRelation::Descendant + )); + assert!(matches!( + bank_forks.relationship(12, 1), + BlockRelation::Unrelated + )); + assert!(matches!( + bank_forks.relationship(1, 2), + BlockRelation::Unrelated + )); + + assert!(matches!( + bank_forks.relationship(1, 13), + BlockRelation::Unknown + )); + assert!(matches!( + bank_forks.relationship(13, 2), + BlockRelation::Unknown + )); + bank_forks.set_root( + 2, + &AbsRequestSender::default(), + Some(1), // highest confirmed root + ); + assert!(matches!( + bank_forks.relationship(1, 2), + BlockRelation::Unknown + )); + assert!(matches!( + bank_forks.relationship(2, 0), + BlockRelation::Unknown + )); + } } diff --git a/runtime/src/rent_debit.rs b/runtime/src/rent_debit.rs new file mode 100644 index 0000000000..e69de29bb2