LoadedPrograms cache implementation and tests (#30139)
This commit is contained in:
parent
1896d4110f
commit
375f9ae41d
|
@ -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;
|
||||
|
|
|
@ -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<RequisiteVerifier, InvokeContext<'static>>),
|
||||
LegacyV1(VerifiedExecutable<RequisiteVerifier, InvokeContext<'static>>),
|
||||
// Typed(TypedProgram<InvokeContext<'static>>),
|
||||
BuiltIn(BuiltInProgram<InvokeContext<'static>>),
|
||||
}
|
||||
|
||||
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<BuiltInProgram<InvokeContext<'static>>>,
|
||||
deployment_slot: Slot,
|
||||
elf_bytes: &[u8],
|
||||
) -> Result<Self, EbpfError> {
|
||||
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<InvokeContext<'static>>,
|
||||
) -> 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<Pubkey, Vec<Arc<LoadedProgram>>>,
|
||||
}
|
||||
|
||||
#[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<dyn Executor> 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<F: ForkGraph>(&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<S: WorkingSlot>(
|
||||
&self,
|
||||
working_slot: &S,
|
||||
keys: impl Iterator<Item = Pubkey>,
|
||||
) -> (HashMap<Pubkey, Arc<LoadedProgram>>, Vec<Pubkey>) {
|
||||
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<Item = Pubkey>) {
|
||||
// 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<Vec<Slot>>,
|
||||
}
|
||||
|
||||
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>,
|
||||
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<LoadedProgram> {
|
||||
Arc::new(LoadedProgram {
|
||||
program: LoadedProgramType::Invalid,
|
||||
deployment_slot,
|
||||
effective_slot,
|
||||
usage_counter: AtomicU64::default(),
|
||||
})
|
||||
}
|
||||
|
||||
fn match_slot(
|
||||
table: &HashMap<Pubkey, Arc<LoadedProgram>>,
|
||||
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));
|
||||
}
|
||||
}
|
|
@ -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<BankIncrementalSnapshotPersistence>,
|
||||
|
||||
pub loaded_programs_cache: Arc<RwLock<LoadedPrograms>>,
|
||||
}
|
||||
|
||||
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::<RwLock<LoadedPrograms>>::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::<RwLock<LoadedPrograms>>::default(),
|
||||
};
|
||||
bank.finish_init(
|
||||
genesis_config,
|
||||
|
|
|
@ -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<Slot>,
|
||||
) -> Vec<Arc<Bank>> {
|
||||
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
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue