1use {
2 crate::{
3 invoke_context::InvokeContext,
4 loading_task::LoadingTaskWaiter,
5 program_cache_entry::{
6 ProgramCacheEntry, ProgramCacheEntryOwner, ProgramCacheEntryType, retention_score,
7 },
8 program_metrics::{EMA_SCALE, ProgramCacheStats},
9 },
10 log::error,
11 solana_clock::{Epoch, Slot},
12 solana_pubkey::Pubkey,
13 solana_sbpf::program::BuiltinProgram,
14 solana_svm_type_overrides::{
15 rand::{Rng, rng},
16 sync::{Arc, Mutex, RwLock, atomic::Ordering},
17 thread,
18 },
19 std::{
20 collections::{HashMap, hash_map::Entry},
21 sync::Weak,
22 },
23};
24
25#[repr(transparent)]
26#[derive(Clone, Debug)]
27pub struct ProgramRuntimeEnvironment(Arc<BuiltinProgram<InvokeContext<'static, 'static>>>);
28impl std::hash::Hash for ProgramRuntimeEnvironment {
29 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
30 Arc::<BuiltinProgram<InvokeContext<'static, 'static>>>::as_ptr(&self.0).hash(state);
31 }
32}
33impl PartialEq for ProgramRuntimeEnvironment {
34 fn eq(&self, other: &Self) -> bool {
35 Arc::ptr_eq(&self.0, &other.0)
36 }
37}
38impl Eq for ProgramRuntimeEnvironment {}
39impl std::ops::Deref for ProgramRuntimeEnvironment {
40 type Target = Arc<BuiltinProgram<InvokeContext<'static, 'static>>>;
41
42 fn deref(&self) -> &Self::Target {
43 &self.0
44 }
45}
46impl ProgramRuntimeEnvironment {
47 pub fn from(inner: BuiltinProgram<InvokeContext<'static, 'static>>) -> Self {
48 Self(Arc::new(inner))
49 }
50
51 pub const fn from_ref<'a>(
52 inner: &'a Arc<BuiltinProgram<InvokeContext<'static, 'static>>>,
53 ) -> &'a Self {
54 unsafe { std::mem::transmute(inner) }
56 }
57}
58
59pub struct ProgramRuntimeEnvironments {
67 execution: ProgramRuntimeEnvironment,
70 deployment: ProgramRuntimeEnvironment,
73}
74
75impl ProgramRuntimeEnvironments {
76 pub fn new(
79 execution: ProgramRuntimeEnvironment,
80 deployment: ProgramRuntimeEnvironment,
81 ) -> Self {
82 Self {
83 execution,
84 deployment,
85 }
86 }
87
88 pub fn get_env_for_execution(&self) -> &ProgramRuntimeEnvironment {
90 &self.execution
91 }
92
93 pub fn get_env_for_deployment(&self) -> &ProgramRuntimeEnvironment {
95 &self.deployment
96 }
97
98 #[cfg(feature = "dev-context-only-utils")]
99 pub fn mock() -> Self {
100 Self {
101 execution: get_mock_program_runtime_environment(),
102 deployment: get_mock_program_runtime_environment(),
103 }
104 }
105}
106
107#[cfg(feature = "dev-context-only-utils")]
108pub fn get_mock_program_runtime_environment() -> ProgramRuntimeEnvironment {
109 static MOCK_ENVIRONMENT: std::sync::OnceLock<ProgramRuntimeEnvironment> =
110 std::sync::OnceLock::<ProgramRuntimeEnvironment>::new();
111 MOCK_ENVIRONMENT
112 .get_or_init(|| ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock()))
113 .clone()
114}
115
116pub const MAX_LOADED_ENTRY_COUNT: usize = 1024;
117pub const MAX_TOMBSTONE_AGE_IN_SLOTS: u64 = 2250; pub type Percent = u8;
121
122fn percent_of_max_entries(percent: Percent) -> usize {
127 debug_assert!(percent <= 100, "percent must be <= 100");
128 MAX_LOADED_ENTRY_COUNT.saturating_mul(percent as usize) / 100
129}
130
131#[derive(Copy, Clone, Debug, PartialEq)]
133pub enum BlockRelation {
134 Ancestor,
136 Equal,
138 Descendant,
140 Unrelated,
142 Unknown,
144}
145
146pub trait ForkGraph {
148 fn relationship(&self, a: Slot, b: Slot) -> BlockRelation;
150}
151
152#[derive(Debug, Default)]
154pub struct EpochBoundaryPreparation {
155 pub upcoming_epoch: Epoch,
157 pub upcoming_environment: Option<ProgramRuntimeEnvironment>,
163 pub programs_to_recompile: Vec<(Pubkey, Arc<ProgramCacheEntry>)>,
165}
166
167impl EpochBoundaryPreparation {
168 pub fn new(epoch: Epoch) -> Self {
169 Self {
170 upcoming_epoch: epoch,
171 upcoming_environment: None,
172 programs_to_recompile: Vec::default(),
173 }
174 }
175
176 pub fn get_upcoming_environment_for_epoch(
178 &self,
179 epoch: Epoch,
180 ) -> Option<ProgramRuntimeEnvironment> {
181 if epoch == self.upcoming_epoch {
182 return self.upcoming_environment.clone();
183 }
184 None
185 }
186
187 pub fn reroot(&mut self, epoch: Epoch) -> Option<ProgramRuntimeEnvironment> {
189 if epoch == self.upcoming_epoch
190 && let Some(upcoming_environment) = self.upcoming_environment.take()
191 {
192 self.programs_to_recompile.clear();
193 return Some(upcoming_environment);
194 }
195
196 None
197 }
198}
199
200#[derive(Clone, PartialEq, Debug)]
202pub struct ProgramToLoad<'a> {
203 pub program_id: &'a Pubkey,
205 pub loader: ProgramCacheEntryOwner,
207 pub deployment_slot: Slot,
212}
213
214#[derive(Debug)]
215pub(crate) enum IndexImplementation {
216 V1 {
218 entries: HashMap<Pubkey, Vec<Arc<ProgramCacheEntry>>>,
224 loading_entries: Mutex<HashMap<Pubkey, (Slot, thread::ThreadId)>>,
233 },
234}
235
236pub struct ProgramCache<FG: ForkGraph> {
252 pub(crate) index: IndexImplementation,
254 pub latest_root_slot: Slot,
256 pub stats: ProgramCacheStats,
258 pub fork_graph: Option<Weak<RwLock<FG>>>,
260 pub loading_task_waiter: Arc<LoadingTaskWaiter>,
262}
263
264impl<FG: ForkGraph> std::fmt::Debug for ProgramCache<FG> {
265 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
266 f.debug_struct("ProgramCache")
267 .field("root slot", &self.latest_root_slot)
268 .field("stats", &self.stats)
269 .field("index", &self.index)
270 .finish()
271 }
272}
273
274#[derive(Clone, Debug, Default)]
280pub struct ProgramCacheForTxBatch {
281 entries: HashMap<Pubkey, Arc<ProgramCacheEntry>>,
284 modified_entries: HashMap<Pubkey, Arc<ProgramCacheEntry>>,
286 slot: Slot,
287 pub hit_max_limit: bool,
288 pub loaded_missing: bool,
289 pub merged_modified: bool,
290}
291
292impl ProgramCacheForTxBatch {
293 pub fn new(slot: Slot) -> Self {
294 Self {
295 entries: HashMap::new(),
296 modified_entries: HashMap::new(),
297 slot,
298 hit_max_limit: false,
299 loaded_missing: false,
300 merged_modified: false,
301 }
302 }
303
304 pub fn replenish(
310 &mut self,
311 key: Pubkey,
312 entry: Arc<ProgramCacheEntry>,
313 ) -> (bool, Arc<ProgramCacheEntry>) {
314 (self.entries.insert(key, entry.clone()).is_some(), entry)
315 }
316
317 pub fn store_modified_entry(&mut self, key: Pubkey, entry: Arc<ProgramCacheEntry>) {
320 self.modified_entries.insert(key, entry);
321 }
322
323 pub fn drain_modified_entries(&mut self) -> HashMap<Pubkey, Arc<ProgramCacheEntry>> {
326 std::mem::take(&mut self.modified_entries)
327 }
328
329 pub fn find(&self, key: &Pubkey) -> Option<Arc<ProgramCacheEntry>> {
330 self.modified_entries
334 .get(key)
335 .or_else(|| self.entries.get(key))
336 .map(|entry| {
337 if entry.is_implicit_delay_visibility_tombstone(self.slot) {
338 Arc::new(ProgramCacheEntry::new_delay_visibility_tombstone(
342 entry.deployment_slot,
343 entry.account_owner,
344 Arc::clone(&entry.stats),
345 ))
346 } else {
347 entry.clone()
348 }
349 })
350 }
351
352 pub fn slot(&self) -> Slot {
353 self.slot
354 }
355
356 pub fn set_slot_for_tests(&mut self, slot: Slot) {
357 self.slot = slot;
358 }
359
360 pub fn merge(&mut self, modified_entries: &HashMap<Pubkey, Arc<ProgramCacheEntry>>) {
361 modified_entries.iter().for_each(|(key, entry)| {
362 self.merged_modified = true;
363 self.replenish(*key, entry.clone());
364 })
365 }
366
367 pub fn remove_entry(&mut self, key: &Pubkey) {
370 self.entries.remove(key);
371 }
372
373 pub fn is_empty(&self) -> bool {
374 self.entries.is_empty()
375 }
376}
377
378impl<FG: ForkGraph> ProgramCache<FG> {
379 pub fn new(root_slot: Slot) -> Self {
380 Self {
381 index: IndexImplementation::V1 {
382 entries: HashMap::new(),
383 loading_entries: Mutex::new(HashMap::new()),
384 },
385 latest_root_slot: root_slot,
386 stats: ProgramCacheStats::default(),
387 fork_graph: None,
388 loading_task_waiter: Arc::new(LoadingTaskWaiter::default()),
389 }
390 }
391
392 pub fn set_fork_graph(&mut self, fork_graph: Weak<RwLock<FG>>) {
393 self.fork_graph = Some(fork_graph);
394 }
395
396 pub fn assign_program(
399 &mut self,
400 program_runtime_environment: &ProgramRuntimeEnvironment,
401 key: Pubkey,
402 _current_slot: Slot,
403 entry: Arc<ProgramCacheEntry>,
404 ) -> bool {
405 debug_assert!(
406 !matches!(&entry.program, ProgramCacheEntryType::DelayVisibility),
407 "Unexpected assignment of a DelayVisibility tombstone"
408 );
409 fn is_current_env(
413 program_runtime_environment: &ProgramRuntimeEnvironment,
414 env_opt: Option<&ProgramRuntimeEnvironment>,
415 ) -> bool {
416 env_opt
417 .map(|env| env == program_runtime_environment)
418 .unwrap_or(true)
419 }
420 match &mut self.index {
421 IndexImplementation::V1 { entries, .. } => {
422 let slot_versions = &mut entries.entry(key).or_default();
423 let insertion_point = slot_versions.binary_search_by(|at| {
424 at.deployment_slot
425 .cmp(&entry.deployment_slot)
426 .then(at.account_owner.cmp(&entry.account_owner))
427 .then(
428 is_current_env(
432 program_runtime_environment,
433 at.program.get_environment(),
434 )
435 .cmp(&is_current_env(
436 program_runtime_environment,
437 entry.program.get_environment(),
438 )),
439 )
440 });
441 match insertion_point {
442 Ok(index) => {
443 let existing = slot_versions.get_mut(index).unwrap();
444 match (&existing.program, &entry.program) {
445 (
446 ProgramCacheEntryType::Builtin(_),
447 ProgramCacheEntryType::Builtin(_),
448 )
449 | (ProgramCacheEntryType::Closed, ProgramCacheEntryType::Unloaded(_))
450 | (
451 ProgramCacheEntryType::Unloaded(_),
452 ProgramCacheEntryType::Loaded(_),
453 )
454 | (
455 ProgramCacheEntryType::Unloaded(_),
456 ProgramCacheEntryType::FailedVerification(_),
457 ) => {}
458 _ => {
459 error!(
461 "ProgramCache::assign_program() failed key={key:?} \
462 existing={slot_versions:?} entry={entry:?}"
463 );
464 debug_assert!(false, "Unexpected replacement of an entry");
465 self.stats.replacements.fetch_add(1, Ordering::Relaxed);
466 return true;
467 }
468 }
469 entry.stats.merge_from(&existing.stats);
470 *existing = Arc::clone(&entry);
471 self.stats.reloads.fetch_add(1, Ordering::Relaxed);
472 }
473 Err(index) => {
474 self.stats.insertions.fetch_add(1, Ordering::Relaxed);
475 slot_versions.insert(index, Arc::clone(&entry));
476 }
477 }
478 slot_versions.retain(|existing| {
482 existing.deployment_slot != entry.deployment_slot
483 || existing
484 .program
485 .get_environment()
486 .zip(entry.program.get_environment())
487 .map(|(a, b)| a != b)
488 .unwrap_or(false)
489 || Arc::ptr_eq(existing, &entry)
490 });
491 }
492 }
493 false
494 }
495
496 pub fn prune_by_deployment_slot(&mut self, slot: Slot) {
497 match &mut self.index {
498 IndexImplementation::V1 { entries, .. } => {
499 for second_level in entries.values_mut() {
500 second_level.retain(|entry| entry.deployment_slot != slot);
501 }
502 self.remove_programs_with_no_entries();
503 }
504 }
505 }
506
507 pub fn prune(
509 &mut self,
510 new_root_slot: Slot,
511 new_environment: Option<ProgramRuntimeEnvironment>,
512 fork_graph: &FG,
513 ) {
514 match &mut self.index {
515 IndexImplementation::V1 { entries, .. } => {
516 let tombstone_slot_cutoff =
517 new_root_slot.saturating_sub(MAX_TOMBSTONE_AGE_IN_SLOTS);
518 entries.retain(|_id, second_level| {
519 if let [candidate] = &second_level[..]
521 && (matches!(candidate.program, ProgramCacheEntryType::Unloaded(_))
522 || candidate.is_tombstone())
523 && candidate.deployment_slot <= self.latest_root_slot
524 && candidate.latest_access_slot.load(Ordering::Relaxed)
525 < tombstone_slot_cutoff
526 {
527 self.stats.prunes_stale.fetch_add(1, Ordering::Relaxed);
528 return false;
529 }
530 let mut first_ancestor_found = false;
532 let mut first_ancestor_env = None;
533 *second_level = second_level
534 .iter()
535 .rev()
536 .filter(|entry| {
537 let relation =
538 fork_graph.relationship(entry.deployment_slot, new_root_slot);
539 if entry.deployment_slot >= new_root_slot {
540 let keep = matches!(
541 relation,
542 BlockRelation::Equal | BlockRelation::Descendant
543 );
544 if !keep {
545 self.stats.prunes_orphan.fetch_add(1, Ordering::Relaxed);
546 }
547 keep
548 } else if matches!(relation, BlockRelation::Ancestor)
549 || entry.deployment_slot <= self.latest_root_slot
550 {
551 if !first_ancestor_found {
552 first_ancestor_found = true;
553 first_ancestor_env = entry.program.get_environment();
554 return true;
555 }
556 if let Some(entry_env) = entry.program.get_environment()
564 && let Some(env) = first_ancestor_env
565 && entry_env != env
566 {
567 return true;
568 }
569 self.stats.prunes_orphan.fetch_add(1, Ordering::Relaxed);
570 false
571 } else {
572 self.stats.prunes_orphan.fetch_add(1, Ordering::Relaxed);
573 false
574 }
575 })
576 .filter(|entry| {
577 if let Some(new_environment) = new_environment.as_ref()
579 && !Self::matches_environment(entry, new_environment)
580 {
581 self.stats
582 .prunes_environment
583 .fetch_add(1, Ordering::Relaxed);
584 return false;
585 }
586 true
587 })
588 .cloned()
589 .collect();
590 second_level.reverse();
591 true
592 });
593 }
594 }
595 self.remove_programs_with_no_entries();
596 debug_assert!(self.latest_root_slot <= new_root_slot);
597 self.latest_root_slot = new_root_slot;
598 }
599
600 fn matches_environment(
601 entry: &Arc<ProgramCacheEntry>,
602 program_runtime_environment: &ProgramRuntimeEnvironment,
603 ) -> bool {
604 let Some(environment) = entry.program.get_environment() else {
605 return true;
606 };
607 environment == program_runtime_environment
608 }
609
610 pub fn extract(
613 &self,
614 search_for: &mut Vec<ProgramToLoad>,
615 loaded_programs_for_tx_batch: &mut ProgramCacheForTxBatch,
616 program_runtime_environment_for_execution: &ProgramRuntimeEnvironment,
617 increment_usage_counter: bool,
618 count_hits_and_misses: bool,
619 ) -> Option<Pubkey> {
620 debug_assert!(self.fork_graph.is_some());
621 let fork_graph = self.fork_graph.as_ref().unwrap().upgrade().unwrap();
622 let locked_fork_graph = fork_graph.read().unwrap();
623 let entries_in_batch = loaded_programs_for_tx_batch.entries.len();
624 let mut cooperative_loading_task = None;
625 match &self.index {
626 IndexImplementation::V1 {
627 entries,
628 loading_entries,
629 } => {
630 search_for.retain(|program_to_load| {
631 if let Some(second_level) = entries.get(program_to_load.program_id) {
632 for entry in second_level.iter().rev() {
633 if program_to_load.deployment_slot != entry.deployment_slot
636 || program_to_load.loader != entry.account_owner
637 {
638 continue;
639 }
640
641 let entry_in_same_branch = entry.deployment_slot
647 <= self.latest_root_slot
648 || matches!(
649 locked_fork_graph.relationship(
650 entry.deployment_slot,
651 loaded_programs_for_tx_batch.slot
652 ),
653 BlockRelation::Equal | BlockRelation::Ancestor
654 );
655 if entry_in_same_branch {
656 let entry_is_effective =
657 loaded_programs_for_tx_batch.slot >= entry.effective_slot();
658 let entry_to_return = if entry_is_effective {
659 if !Self::matches_environment(
660 entry,
661 program_runtime_environment_for_execution,
662 ) {
663 continue;
667 }
668 if let ProgramCacheEntryType::Unloaded(_environment) =
669 &entry.program
670 {
671 break;
672 }
673 entry.clone()
674 } else if entry.is_implicit_delay_visibility_tombstone(
675 loaded_programs_for_tx_batch.slot,
676 ) {
677 Arc::new(ProgramCacheEntry::new_delay_visibility_tombstone(
681 entry.deployment_slot,
682 entry.account_owner,
683 Arc::clone(&entry.stats),
684 ))
685 } else {
686 continue;
687 };
688 entry.update_access_slot(loaded_programs_for_tx_batch.slot);
689 if increment_usage_counter {
690 entry_to_return.stats.uses.fetch_add(1, Ordering::Relaxed);
691 }
692 loaded_programs_for_tx_batch
693 .entries
694 .insert(*program_to_load.program_id, entry_to_return);
695 return false;
696 }
697 }
698 }
699 if cooperative_loading_task.is_none() {
700 let mut loading_entries = loading_entries.lock().unwrap();
701 let entry = loading_entries.entry(*program_to_load.program_id);
702 if let Entry::Vacant(entry) = entry {
703 entry.insert((
704 loaded_programs_for_tx_batch.slot,
705 thread::current().id(),
706 ));
707 cooperative_loading_task = Some(*program_to_load.program_id);
708 }
709 }
710 true
711 });
712 }
713 }
714 drop(locked_fork_graph);
715 if count_hits_and_misses {
716 let misses = search_for.len() as u64;
717 let hits = loaded_programs_for_tx_batch
718 .entries
719 .len()
720 .saturating_sub(entries_in_batch) as u64;
721 self.stats.misses.fetch_add(misses, Ordering::Relaxed);
722 self.stats.hits.fetch_add(hits, Ordering::Relaxed);
723 }
724 cooperative_loading_task
725 }
726
727 pub fn finish_cooperative_loading_task(
729 &mut self,
730 program_runtime_environment: &ProgramRuntimeEnvironment,
731 current_slot: Slot,
732 key: Pubkey,
733 loaded_program: Arc<ProgramCacheEntry>,
734 ) -> bool {
735 match &mut self.index {
736 IndexImplementation::V1 {
737 loading_entries, ..
738 } => {
739 let loading_thread = loading_entries.get_mut().unwrap().remove(&key);
740 debug_assert_eq!(loading_thread, Some((current_slot, thread::current().id())));
741 if loaded_program.deployment_slot > self.latest_root_slot
743 && !matches!(
744 self.fork_graph
745 .as_ref()
746 .unwrap()
747 .upgrade()
748 .unwrap()
749 .read()
750 .unwrap()
751 .relationship(loaded_program.deployment_slot, current_slot),
752 BlockRelation::Equal | BlockRelation::Ancestor
753 )
754 {
755 self.stats.lost_insertions.fetch_add(1, Ordering::Relaxed);
756 }
757 let was_occupied = self.assign_program(
758 program_runtime_environment,
759 key,
760 current_slot,
761 loaded_program,
762 );
763 self.loading_task_waiter.notify();
764 was_occupied
765 }
766 }
767 }
768
769 pub fn merge(
770 &mut self,
771 program_runtime_environment: &ProgramRuntimeEnvironment,
772 current_slot: Slot,
773 modified_entries: &HashMap<Pubkey, Arc<ProgramCacheEntry>>,
774 ) {
775 modified_entries.iter().for_each(|(key, entry)| {
776 self.assign_program(
777 program_runtime_environment,
778 *key,
779 current_slot,
780 entry.clone(),
781 );
782 })
783 }
784
785 pub fn get_flattened_entries(&self) -> Vec<(Pubkey, Arc<ProgramCacheEntry>)> {
787 match &self.index {
788 IndexImplementation::V1 { entries, .. } => entries
789 .iter()
790 .flat_map(|(id, second_level)| {
791 second_level
792 .iter()
793 .filter_map(move |program| match program.program {
794 ProgramCacheEntryType::Loaded(_) => Some((*id, program.clone())),
795 _ => None,
796 })
797 })
798 .collect(),
799 }
800 }
801
802 #[cfg(feature = "dev-context-only-utils")]
804 pub fn get_flattened_entries_for_tests(&self) -> Vec<(Pubkey, Arc<ProgramCacheEntry>)> {
805 match &self.index {
806 IndexImplementation::V1 { entries, .. } => entries
807 .iter()
808 .flat_map(|(id, second_level)| {
809 second_level.iter().map(|program| (*id, program.clone()))
810 })
811 .collect(),
812 }
813 }
814
815 pub fn get_slot_versions_for_tests(&self, key: &Pubkey) -> &[Arc<ProgramCacheEntry>] {
817 match &self.index {
818 IndexImplementation::V1 { entries, .. } => entries
819 .get(key)
820 .map(|second_level| second_level.as_ref())
821 .unwrap_or(&[]),
822 }
823 }
824
825 pub fn sort_and_unload(&mut self, shrink_to_percent: Percent) {
827 let mut sorted_candidates = self.get_flattened_entries();
828 sorted_candidates
829 .sort_by_cached_key(|(_id, program)| program.stats.uses.load(Ordering::Relaxed));
830 let num_to_unload = sorted_candidates
831 .len()
832 .saturating_sub(percent_of_max_entries(shrink_to_percent));
833 for (program, entry) in sorted_candidates.iter().take(num_to_unload) {
834 self.unload_program_entry(*program, entry);
835 }
836 }
837
838 pub fn evict_using_random_selection(&mut self, shrink_to_percent: Percent, now: Slot) {
844 let mut candidates = self.get_flattened_entries();
845 let mut rng = rng();
846 self.stats
847 .water_level
848 .store(candidates.len() as u64, Ordering::Relaxed);
849 let num_to_unload = candidates
850 .len()
851 .saturating_sub(percent_of_max_entries(shrink_to_percent));
852 let mut sample_entry = |candidates: &Vec<(Pubkey, Arc<ProgramCacheEntry>)>| {
853 #[cfg(feature = "shuttle-test")]
856 let index = rng.gen_range(0..candidates.len());
857 #[cfg(not(feature = "shuttle-test"))]
858 let index = rng.random_range(0..candidates.len());
859 let usage_counter = candidates
860 .get(index)
861 .expect("Failed to get cached entry")
862 .1
863 .retention_score();
864 (index, usage_counter)
865 };
866
867 const MAX_ADDITIONAL_SAMPLES: usize = 3;
875 let avoid_evicting_above_score = retention_score(now, 500 * EMA_SCALE, 500);
876 for _ in 0..num_to_unload {
877 let (mut index, mut score) = sample_entry(&candidates);
878 for _ in 0..MAX_ADDITIONAL_SAMPLES {
879 let (sample_index, sample_score) = sample_entry(&candidates);
880 if score > sample_score {
881 index = sample_index;
882 score = sample_score;
883 }
884 if score < avoid_evicting_above_score {
885 break;
886 }
887 }
888 let (id, entry) = candidates.swap_remove(index);
889 self.unload_program_entry(id, &entry);
890 }
891 }
892
893 pub fn remove_programs(&mut self, keys: impl Iterator<Item = Pubkey>) {
895 match &mut self.index {
896 IndexImplementation::V1 { entries, .. } => {
897 for k in keys {
898 entries.remove(&k);
899 }
900 }
901 }
902 }
903
904 fn unload_program_entry(&mut self, id: Pubkey, remove_entry: &Arc<ProgramCacheEntry>) {
907 match &mut self.index {
908 IndexImplementation::V1 { entries, .. } => {
909 let second_level = entries.get_mut(&id).expect("Cache lookup failed");
910 let candidate = second_level
911 .iter_mut()
912 .find(|entry| Arc::ptr_eq(entry, remove_entry))
913 .expect("Program entry not found");
914
915 if let ProgramCacheEntryType::Loaded(_) = candidate.program
917 && let Some(unloaded) = candidate.to_unloaded()
918 {
919 if candidate.stats.uses.load(Ordering::Relaxed) == 1 {
920 self.stats.one_hit_wonders.fetch_add(1, Ordering::Relaxed);
921 }
922 self.stats
923 .evictions
924 .entry(id)
925 .and_modify(|c| *c = c.saturating_add(1))
926 .or_insert(1);
927 *candidate = Arc::new(unloaded);
928 }
929 }
930 }
931 }
932
933 fn remove_programs_with_no_entries(&mut self) {
934 match &mut self.index {
935 IndexImplementation::V1 { entries, .. } => {
936 let num_programs_before_removal = entries.len();
937 entries.retain(|_key, second_level| !second_level.is_empty());
938 if entries.len() < num_programs_before_removal {
939 self.stats.empty_entries.fetch_add(
940 num_programs_before_removal.saturating_sub(entries.len()) as u64,
941 Ordering::Relaxed,
942 );
943 }
944 }
945 }
946 }
947}
948
949#[cfg(feature = "frozen-abi")]
950impl solana_frozen_abi::abi_example::AbiExample for ProgramCacheEntry {
951 fn example() -> Self {
952 Self::default()
954 }
955}
956
957#[cfg(feature = "frozen-abi")]
958impl<FG: ForkGraph> solana_frozen_abi::abi_example::AbiExample for ProgramCache<FG> {
959 fn example() -> Self {
960 Self::new(Slot::default())
962 }
963}
964
965#[cfg(test)]
966pub(crate) mod tests {
967 use {
968 crate::{
969 loaded_programs::{
970 BlockRelation, ForkGraph, IndexImplementation, MAX_TOMBSTONE_AGE_IN_SLOTS, Percent,
971 ProgramCache, ProgramCacheForTxBatch, ProgramRuntimeEnvironment, ProgramToLoad,
972 get_mock_program_runtime_environment,
973 },
974 program_cache_entry::{
975 ProgramCacheEntry, ProgramCacheEntryOwner, ProgramCacheEntryType,
976 },
977 program_metrics::ProgramStatistics,
978 },
979 assert_matches::assert_matches,
980 solana_clock::Slot,
981 solana_pubkey::Pubkey,
982 solana_sbpf::{elf::Executable, program::BuiltinProgram},
983 solana_svm_type_overrides::{
984 sync::{
985 Arc, RwLock,
986 atomic::{AtomicU64, Ordering},
987 },
988 thread,
989 },
990 std::{fs::File, io::Read, ops::ControlFlow},
991 test_case::{test_case, test_matrix},
992 };
993
994 fn new_test_entry(deployment_slot: Slot) -> Arc<ProgramCacheEntry> {
995 new_test_entry_with_usage(deployment_slot, ProgramStatistics::default())
996 }
997
998 fn new_closed_entry(_env: ProgramRuntimeEnvironment) -> ProgramCacheEntryType {
999 ProgramCacheEntryType::Closed
1000 }
1001
1002 fn new_builtin_entry(_env: ProgramRuntimeEnvironment) -> ProgramCacheEntryType {
1003 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock())
1004 }
1005
1006 fn new_failed_verification_entry(env: ProgramRuntimeEnvironment) -> ProgramCacheEntryType {
1007 ProgramCacheEntryType::FailedVerification(env)
1008 }
1009
1010 fn new_unloaded_entry(env: ProgramRuntimeEnvironment) -> ProgramCacheEntryType {
1011 ProgramCacheEntryType::Unloaded(env)
1012 }
1013
1014 fn new_loaded_entry(env: ProgramRuntimeEnvironment) -> ProgramCacheEntryType {
1015 let mut elf = Vec::new();
1016 File::open("../programs/bpf_loader/test_elfs/out/noop_aligned.so")
1017 .unwrap()
1018 .read_to_end(&mut elf)
1019 .unwrap();
1020 let executable = Executable::load(&elf, Arc::clone(&*env)).unwrap();
1021 ProgramCacheEntryType::Loaded(executable)
1022 }
1023
1024 fn new_test_entry_with_owner(
1025 deployment_slot: Slot,
1026 account_owner: ProgramCacheEntryOwner,
1027 program: ProgramCacheEntryType,
1028 ) -> Arc<ProgramCacheEntry> {
1029 Arc::new(ProgramCacheEntry {
1030 program,
1031 account_owner,
1032 deployment_slot,
1033 stats: Arc::default(),
1034 latest_access_slot: AtomicU64::default(),
1035 })
1036 }
1037
1038 fn new_test_cache_with_fork_graph(
1039 relation: BlockRelation,
1040 ) -> (ProgramCache<TestForkGraph>, Arc<RwLock<TestForkGraph>>) {
1041 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1042 let fork_graph = Arc::new(RwLock::new(TestForkGraph { relation }));
1043 cache.set_fork_graph(Arc::downgrade(&fork_graph));
1044 (cache, fork_graph)
1045 }
1046
1047 pub(crate) fn new_test_entry_with_usage(
1048 deployment_slot: Slot,
1049 stats: ProgramStatistics,
1050 ) -> Arc<ProgramCacheEntry> {
1051 Arc::new(ProgramCacheEntry {
1052 program: new_loaded_entry(get_mock_program_runtime_environment()),
1053 account_owner: ProgramCacheEntryOwner::LoaderV2,
1054 deployment_slot,
1055 stats: Arc::new(stats),
1056 latest_access_slot: AtomicU64::new(deployment_slot),
1057 })
1058 }
1059
1060 fn new_test_builtin_entry(deployment_slot: Slot) -> Arc<ProgramCacheEntry> {
1061 Arc::new(ProgramCacheEntry {
1062 program: ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1063 account_owner: ProgramCacheEntryOwner::NativeLoader,
1064 deployment_slot,
1065 stats: Arc::default(),
1066 latest_access_slot: AtomicU64::default(),
1067 })
1068 }
1069
1070 fn set_failed_verification_tombstone<FG: ForkGraph>(
1071 cache: &mut ProgramCache<FG>,
1072 key: Pubkey,
1073 current_slot: Slot,
1074 env: ProgramRuntimeEnvironment,
1075 ) -> Arc<ProgramCacheEntry> {
1076 let program = Arc::new(ProgramCacheEntry::new_failed_verification_tombstone(
1077 current_slot,
1078 ProgramCacheEntryOwner::LoaderV2,
1079 ProgramRuntimeEnvironment::clone(&env),
1080 ));
1081 cache.assign_program(&env, key, current_slot, program.clone());
1082 program
1083 }
1084
1085 fn insert_unloaded_entry<FG: ForkGraph>(
1086 cache: &mut ProgramCache<FG>,
1087 key: Pubkey,
1088 current_slot: Slot,
1089 ) -> Arc<ProgramCacheEntry> {
1090 let env = get_mock_program_runtime_environment();
1091 let loaded = new_test_entry_with_usage(current_slot, ProgramStatistics::default());
1092 let unloaded = Arc::new(loaded.to_unloaded().expect("Failed to unload the program"));
1093 cache.assign_program(&env, key, current_slot, unloaded.clone());
1094 unloaded
1095 }
1096
1097 fn num_matching_entries<P, FG>(cache: &ProgramCache<FG>, predicate: P) -> usize
1098 where
1099 P: Fn(&ProgramCacheEntryType) -> bool,
1100 FG: ForkGraph,
1101 {
1102 cache
1103 .get_flattened_entries_for_tests()
1104 .iter()
1105 .filter(|(_key, program)| predicate(&program.program))
1106 .count()
1107 }
1108
1109 #[expect(clippy::arithmetic_side_effects)]
1110 fn program_deploy_test_helper(
1111 cache: &mut ProgramCache<TestForkGraph>,
1112 program: Pubkey,
1113 deployment_slots: Vec<Slot>,
1114 usage_counters: Vec<u64>,
1115 programs: &mut Vec<(Pubkey, Slot, u64)>,
1116 ) {
1117 let env = get_mock_program_runtime_environment();
1118 deployment_slots
1120 .iter()
1121 .enumerate()
1122 .for_each(|(i, deployment_slot)| {
1123 let usage_counter = *usage_counters.get(i).unwrap_or(&0);
1124 let stats = ProgramStatistics {
1125 uses: usage_counter.into(),
1126 ..Default::default()
1127 };
1128 cache.assign_program(
1129 &env,
1130 program,
1131 *deployment_slot,
1132 new_test_entry_with_usage(*deployment_slot, stats),
1133 );
1134 programs.push((program, *deployment_slot, usage_counter));
1135 });
1136
1137 let next_slot = deployment_slots.iter().max().map_or(0, |slot| slot + 1);
1138
1139 let env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
1141 for slot in next_slot..next_slot + 10 {
1142 set_failed_verification_tombstone(
1143 cache,
1144 program,
1145 slot,
1146 ProgramRuntimeEnvironment::clone(&env),
1147 );
1148 }
1149
1150 for slot in next_slot + 10..next_slot + 20 {
1152 insert_unloaded_entry(cache, program, slot);
1153 }
1154 }
1155
1156 #[test]
1157 fn test_random_eviction() {
1158 let mut programs = vec![];
1159 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1160
1161 program_deploy_test_helper(
1167 &mut cache,
1168 Pubkey::new_unique(),
1169 vec![0, 10, 20, 30, 40],
1170 vec![4, 5, 25, 35, 12],
1171 &mut programs,
1172 );
1173
1174 program_deploy_test_helper(
1176 &mut cache,
1177 Pubkey::new_unique(),
1178 vec![5, 11, 21, 24],
1179 vec![0, 2, 30, 45],
1180 &mut programs,
1181 );
1182
1183 program_deploy_test_helper(
1185 &mut cache,
1186 Pubkey::new_unique(),
1187 vec![0, 5, 15, 25],
1188 vec![100, 3, 20, 40],
1189 &mut programs,
1190 );
1191
1192 let num_loaded_expected = 13;
1194 let num_unloaded_expected = 30;
1196 let num_tombstones_expected = 30;
1198
1199 programs.sort_by_key(|(_id, _slot, usage_count)| *usage_count);
1201 let num_loaded = num_matching_entries(&cache, |program_type| {
1202 matches!(program_type, ProgramCacheEntryType::Loaded(_))
1203 });
1204 let num_unloaded = num_matching_entries(&cache, |program_type| {
1205 matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1206 });
1207 let num_tombstones = num_matching_entries(&cache, |program_type| {
1208 matches!(
1209 program_type,
1210 ProgramCacheEntryType::DelayVisibility
1211 | ProgramCacheEntryType::FailedVerification(_)
1212 | ProgramCacheEntryType::Closed
1213 )
1214 });
1215
1216 assert_eq!(num_loaded, num_loaded_expected);
1218 assert_eq!(num_unloaded, num_unloaded_expected);
1219 assert_eq!(num_tombstones, num_tombstones_expected);
1220
1221 let eviction_pct: Percent = 1;
1223
1224 let num_loaded_expected = crate::loaded_programs::percent_of_max_entries(eviction_pct);
1225 let num_unloaded_expected = num_unloaded_expected + num_loaded - num_loaded_expected;
1226 cache.evict_using_random_selection(eviction_pct, 21);
1227
1228 let num_loaded = num_matching_entries(&cache, |program_type| {
1230 matches!(program_type, ProgramCacheEntryType::Loaded(_))
1231 });
1232 let num_unloaded = num_matching_entries(&cache, |program_type| {
1233 matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1234 });
1235 let num_tombstones = num_matching_entries(&cache, |program_type| {
1236 matches!(program_type, ProgramCacheEntryType::FailedVerification(_))
1237 });
1238
1239 assert_eq!(num_loaded, num_loaded_expected);
1241 assert_eq!(num_unloaded, num_unloaded_expected);
1243 assert_eq!(num_tombstones, num_tombstones_expected);
1245 }
1246
1247 #[test]
1248 fn test_eviction() {
1249 let mut programs = vec![];
1250 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1251
1252 program_deploy_test_helper(
1254 &mut cache,
1255 Pubkey::new_unique(),
1256 vec![0, 10, 20, 30, 40],
1257 vec![4, 5, 25, 35, 12],
1258 &mut programs,
1259 );
1260
1261 program_deploy_test_helper(
1263 &mut cache,
1264 Pubkey::new_unique(),
1265 vec![5, 11, 21, 24],
1266 vec![0, 2, 30, 45],
1267 &mut programs,
1268 );
1269
1270 program_deploy_test_helper(
1272 &mut cache,
1273 Pubkey::new_unique(),
1274 vec![0, 5, 15, 25],
1275 vec![100, 3, 20, 40],
1276 &mut programs,
1277 );
1278
1279 let num_loaded_expected = 13;
1281 let num_unloaded_expected = 30;
1283 let num_tombstones_expected = 30;
1285
1286 programs.sort_by_key(|(_id, _slot, usage_count)| *usage_count);
1288 let num_loaded = num_matching_entries(&cache, |program_type| {
1289 matches!(program_type, ProgramCacheEntryType::Loaded(_))
1290 });
1291 let num_unloaded = num_matching_entries(&cache, |program_type| {
1292 matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1293 });
1294 let num_tombstones = num_matching_entries(&cache, |program_type| {
1295 matches!(program_type, ProgramCacheEntryType::FailedVerification(_))
1296 });
1297
1298 assert_eq!(num_loaded, num_loaded_expected);
1300 assert_eq!(num_unloaded, num_unloaded_expected);
1301 assert_eq!(num_tombstones, num_tombstones_expected);
1302
1303 let eviction_pct: Percent = 1;
1305
1306 let num_loaded_expected = crate::loaded_programs::percent_of_max_entries(eviction_pct);
1307 let num_unloaded_expected = num_unloaded_expected + num_loaded - num_loaded_expected;
1308
1309 cache.sort_and_unload(eviction_pct);
1310
1311 let entries = cache.get_flattened_entries_for_tests();
1313 programs.iter().for_each(|entry| {
1314 assert!(entries.iter().any(|(key, _entry)| key == &entry.0));
1315 });
1316
1317 let unloaded = entries
1318 .iter()
1319 .filter_map(|(key, program)| {
1320 matches!(program.program, ProgramCacheEntryType::Unloaded(_))
1321 .then_some((*key, program.stats.uses.load(Ordering::Relaxed)))
1322 })
1323 .collect::<Vec<(Pubkey, u64)>>();
1324
1325 for index in 0..3 {
1326 let expected = programs.get(index).expect("Missing program");
1327 assert!(unloaded.contains(&(expected.0, expected.2)));
1328 }
1329
1330 let num_loaded = num_matching_entries(&cache, |program_type| {
1332 matches!(program_type, ProgramCacheEntryType::Loaded(_))
1333 });
1334 let num_unloaded = num_matching_entries(&cache, |program_type| {
1335 matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1336 });
1337 let num_tombstones = num_matching_entries(&cache, |program_type| {
1338 matches!(
1339 program_type,
1340 ProgramCacheEntryType::DelayVisibility
1341 | ProgramCacheEntryType::FailedVerification(_)
1342 | ProgramCacheEntryType::Closed
1343 )
1344 });
1345
1346 assert_eq!(num_loaded, num_loaded_expected);
1348 assert_eq!(num_unloaded, num_unloaded_expected);
1350 assert_eq!(num_tombstones, num_tombstones_expected);
1352 }
1353
1354 #[test]
1355 fn test_usage_count_of_unloaded_program() {
1356 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1357 let env = get_mock_program_runtime_environment();
1358
1359 let program = Pubkey::new_unique();
1360 let evict_to_pct: Percent = 2;
1361 let cache_capacity_after_shrink =
1362 crate::loaded_programs::percent_of_max_entries(evict_to_pct);
1363 let num_total_programs = (cache_capacity_after_shrink + 1) as u64;
1365 (0..num_total_programs).for_each(|i| {
1366 let stats = ProgramStatistics {
1367 uses: (i + 10).into(),
1368 ..Default::default()
1369 };
1370 let entry = new_test_entry_with_usage(i, stats);
1371 cache.assign_program(&env, program, i, entry);
1372 });
1373
1374 cache.sort_and_unload(evict_to_pct);
1375
1376 let num_unloaded = num_matching_entries(&cache, |program_type| {
1377 matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1378 });
1379 assert_eq!(num_unloaded, 1);
1380
1381 cache
1382 .get_flattened_entries_for_tests()
1383 .iter()
1384 .for_each(|(_key, program)| {
1385 if matches!(program.program, ProgramCacheEntryType::Unloaded(_)) {
1386 assert_eq!(program.stats.uses.load(Ordering::Relaxed), 10);
1388 assert_eq!(program.deployment_slot, 0);
1389 assert_eq!(program.effective_slot(), 1);
1390 }
1391 });
1392
1393 cache.assign_program(
1396 &env,
1397 program,
1398 0,
1399 new_test_entry_with_usage(0, ProgramStatistics::default()),
1400 );
1401
1402 cache
1403 .get_flattened_entries_for_tests()
1404 .iter()
1405 .for_each(|(_key, program)| {
1406 if matches!(program.program, ProgramCacheEntryType::Unloaded(_))
1407 && program.deployment_slot == 0
1408 && program.effective_slot() == 1
1409 {
1410 assert_eq!(program.stats.uses.load(Ordering::Relaxed), 10);
1412 }
1413 });
1414 }
1415
1416 #[test_matrix(
1417 (
1418 ProgramCacheEntryType::FailedVerification(get_mock_program_runtime_environment()),
1419 ProgramCacheEntryType::Closed,
1420 ProgramCacheEntryType::Unloaded(get_mock_program_runtime_environment()),
1421 new_loaded_entry(get_mock_program_runtime_environment()),
1422 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1423 ),
1424 (false, true)
1425 )]
1426 fn test_assign_program_no_second_level(
1427 program: ProgramCacheEntryType,
1428 empty_second_level: bool,
1429 ) {
1430 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1434 let env = get_mock_program_runtime_environment();
1435 let program_id = Pubkey::new_unique();
1436
1437 if empty_second_level {
1438 match &mut cache.index {
1440 IndexImplementation::V1 { entries, .. } => {
1441 entries.insert(program_id, Vec::new());
1442 }
1443 }
1444 }
1445
1446 let entry = Arc::new(ProgramCacheEntry {
1447 program,
1448 account_owner: ProgramCacheEntryOwner::LoaderV3,
1449 deployment_slot: 10,
1450 stats: Arc::default(),
1451 latest_access_slot: AtomicU64::default(),
1452 });
1453
1454 cache.assign_program(&env, program_id, 10, Arc::clone(&entry));
1455
1456 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
1458 assert_eq!(slot_versions.len(), 1);
1459 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &entry));
1460
1461 assert_eq!(cache.stats.insertions.load(Ordering::Relaxed), 1);
1463 }
1464
1465 #[test_matrix(
1466 (
1467 new_closed_entry,
1468 new_builtin_entry,
1469 new_failed_verification_entry,
1470 new_unloaded_entry,
1471 new_loaded_entry,
1472 ),
1473 ((50, 0), (150, 1), (250, 2), (350, 3))
1474 )]
1475 fn test_assign_program_new_insertion_deployment_slot(
1476 new_program: fn(ProgramRuntimeEnvironment) -> ProgramCacheEntryType,
1477 case: (Slot, usize),
1478 ) {
1479 let (deployment_slot, expected_index) = case;
1480 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1481 let env = get_mock_program_runtime_environment();
1482 let program_id = Pubkey::new_unique();
1483
1484 for slot in [100, 200, 300] {
1486 cache.assign_program(
1487 &env,
1488 program_id,
1489 slot,
1490 new_test_entry_with_owner(
1491 slot,
1492 ProgramCacheEntryOwner::LoaderV3,
1493 new_program(env.clone()),
1494 ),
1495 );
1496 }
1497
1498 let entry = new_test_entry_with_owner(
1500 deployment_slot,
1501 ProgramCacheEntryOwner::LoaderV3,
1502 new_program(env.clone()),
1503 );
1504 cache.assign_program(&env, program_id, deployment_slot, Arc::clone(&entry));
1505
1506 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
1507 assert_eq!(slot_versions.len(), 4);
1508 assert!(Arc::ptr_eq(
1509 slot_versions.get(expected_index).unwrap(),
1510 &entry
1511 ));
1512 assert_eq!(cache.stats.insertions.load(Ordering::Relaxed), 4);
1513 }
1514
1515 #[test_matrix(
1516 (new_failed_verification_entry, new_unloaded_entry, new_loaded_entry),
1517 (
1518 (ProgramCacheEntryOwner::NativeLoader, 0),
1519 (ProgramCacheEntryOwner::LoaderV2, 1),
1520 (ProgramCacheEntryOwner::LoaderV4, 2),
1521 )
1522 )]
1523 fn test_assign_program_new_insertion_account_owner(
1524 new_program: fn(ProgramRuntimeEnvironment) -> ProgramCacheEntryType,
1525 case: (ProgramCacheEntryOwner, usize),
1526 ) {
1527 let (account_owner, expected_index) = case;
1528 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1529 let env = get_mock_program_runtime_environment();
1530 let program_id = Pubkey::new_unique();
1531
1532 for owner in [
1535 ProgramCacheEntryOwner::LoaderV1,
1536 ProgramCacheEntryOwner::LoaderV3,
1537 ] {
1538 cache.assign_program(
1539 &env,
1540 program_id,
1541 100,
1542 new_test_entry_with_owner(
1543 100,
1544 owner,
1545 new_program(ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock())),
1546 ),
1547 );
1548 }
1549
1550 let entry = new_test_entry_with_owner(
1553 100,
1554 account_owner,
1555 new_program(ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock())),
1556 );
1557 cache.assign_program(&env, program_id, 100, Arc::clone(&entry));
1558
1559 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
1560 assert_eq!(slot_versions.len(), 3);
1561 assert!(Arc::ptr_eq(
1562 slot_versions.get(expected_index).unwrap(),
1563 &entry
1564 ));
1565 assert_eq!(cache.stats.insertions.load(Ordering::Relaxed), 3);
1566 }
1567
1568 #[test_matrix(
1569 (new_failed_verification_entry, new_unloaded_entry, new_loaded_entry),
1570 (false, true)
1571 )]
1572 fn test_assign_program_new_insertion_environment(
1573 new_program: fn(ProgramRuntimeEnvironment) -> ProgramCacheEntryType,
1574 entry_uses_current_env: bool,
1575 ) {
1576 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1577 let env = get_mock_program_runtime_environment();
1578 let other_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
1579 let program_id = Pubkey::new_unique();
1580
1581 let (existing_env, entry_env, expected_index) = if entry_uses_current_env {
1584 (other_env, env.clone(), 1)
1585 } else {
1586 (env.clone(), other_env, 0)
1587 };
1588 cache.assign_program(
1589 &env,
1590 program_id,
1591 100,
1592 new_test_entry_with_owner(
1593 100,
1594 ProgramCacheEntryOwner::LoaderV3,
1595 new_program(existing_env),
1596 ),
1597 );
1598
1599 let entry = new_test_entry_with_owner(
1600 100,
1601 ProgramCacheEntryOwner::LoaderV3,
1602 new_program(entry_env),
1603 );
1604 cache.assign_program(&env, program_id, 100, Arc::clone(&entry));
1605
1606 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
1607 assert_eq!(slot_versions.len(), 2);
1608 assert!(Arc::ptr_eq(
1609 slot_versions.get(expected_index).unwrap(),
1610 &entry
1611 ));
1612 assert_eq!(cache.stats.insertions.load(Ordering::Relaxed), 2);
1613 }
1614
1615 #[test]
1616 #[should_panic(expected = "Unexpected assignment of a DelayVisibility tombstone")]
1617 fn test_assign_program_delay_visibility_tombstone_panics() {
1618 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1621 let env = get_mock_program_runtime_environment();
1622 cache.assign_program(
1623 &env,
1624 Pubkey::new_unique(),
1625 100,
1626 Arc::new(ProgramCacheEntry::new_delay_visibility_tombstone(
1627 100,
1628 ProgramCacheEntryOwner::LoaderV3,
1629 Arc::default(),
1630 )),
1631 );
1632 }
1633
1634 #[test]
1635 fn test_fuzz_assign_program_order() {
1636 use rand::prelude::SliceRandom;
1637 const EXPECTED_ENTRIES: [(u64, bool); 5] =
1638 [(1, true), (3, false), (5, true), (9, true), (10, false)];
1639 let mut rng = rand::rng();
1640 let program_id = Pubkey::new_unique();
1641 let env = get_mock_program_runtime_environment();
1642 for _ in 0..1000 {
1643 let mut entries = EXPECTED_ENTRIES.to_vec();
1644 entries.shuffle(&mut rng);
1645 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1646 for (deployment_slot, delay_visibility) in entries {
1647 let entry = Arc::new(if delay_visibility {
1648 ProgramCacheEntry {
1649 program: new_loaded_entry(ProgramRuntimeEnvironment::from(
1650 BuiltinProgram::new_mock(),
1651 )), account_owner: ProgramCacheEntryOwner::LoaderV2,
1653 deployment_slot,
1654 stats: Arc::default(),
1655 latest_access_slot: AtomicU64::new(deployment_slot),
1656 }
1657 } else {
1658 ProgramCacheEntry::new_failed_verification_tombstone(
1659 deployment_slot,
1660 ProgramCacheEntryOwner::LoaderV2,
1661 ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock()), )
1663 });
1664 assert!(!cache.assign_program(&env, program_id, deployment_slot, entry));
1665 }
1666 for ((deployment_slot, delay_visibility), entry) in EXPECTED_ENTRIES
1667 .iter()
1668 .zip(cache.get_slot_versions_for_tests(&program_id).iter())
1669 {
1670 assert_eq!(entry.deployment_slot, *deployment_slot);
1671 assert_eq!(
1672 entry.effective_slot(),
1673 deployment_slot.saturating_add(*delay_visibility as u64)
1674 );
1675 }
1676 }
1677 }
1678
1679 #[test_matrix(
1680 (
1681 ProgramCacheEntryType::FailedVerification(get_mock_program_runtime_environment()),
1682 new_loaded_entry(get_mock_program_runtime_environment()),
1683 ),
1684 (
1685 ProgramCacheEntryType::FailedVerification(get_mock_program_runtime_environment()),
1686 ProgramCacheEntryType::Closed,
1687 ProgramCacheEntryType::Unloaded(get_mock_program_runtime_environment()),
1688 new_loaded_entry(get_mock_program_runtime_environment()),
1689 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1690 )
1691 )]
1692 #[test_matrix(
1693 ProgramCacheEntryType::Closed,
1694 (
1695 ProgramCacheEntryType::FailedVerification(get_mock_program_runtime_environment()),
1696 ProgramCacheEntryType::Closed,
1697 new_loaded_entry(get_mock_program_runtime_environment()),
1698 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1699 )
1700 )]
1701 #[test_matrix(
1702 ProgramCacheEntryType::Unloaded(get_mock_program_runtime_environment()),
1703 (
1704 ProgramCacheEntryType::Closed,
1705 ProgramCacheEntryType::Unloaded(get_mock_program_runtime_environment()),
1706 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1707 )
1708 )]
1709 #[test_matrix(
1710 (ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),),
1711 (
1712 ProgramCacheEntryType::FailedVerification(get_mock_program_runtime_environment()),
1713 ProgramCacheEntryType::Closed,
1714 ProgramCacheEntryType::Unloaded(get_mock_program_runtime_environment()),
1715 new_loaded_entry(get_mock_program_runtime_environment()),
1716 )
1717 )]
1718 #[should_panic(expected = "Unexpected replacement of an entry")]
1719 fn test_assign_program_failure(old: ProgramCacheEntryType, new: ProgramCacheEntryType) {
1720 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1721 let env = get_mock_program_runtime_environment();
1722 let program_id = Pubkey::new_unique();
1723 assert!(!cache.assign_program(
1724 &env,
1725 program_id,
1726 10,
1727 Arc::new(ProgramCacheEntry {
1728 program: old,
1729 account_owner: ProgramCacheEntryOwner::LoaderV2,
1730 deployment_slot: 10,
1731 stats: Arc::default(),
1732 latest_access_slot: AtomicU64::default(),
1733 }),
1734 ));
1735 cache.assign_program(
1736 &env,
1737 program_id,
1738 10,
1739 Arc::new(ProgramCacheEntry {
1740 program: new,
1741 account_owner: ProgramCacheEntryOwner::LoaderV2,
1742 deployment_slot: 10,
1743 stats: Arc::default(),
1744 latest_access_slot: AtomicU64::default(),
1745 }),
1746 );
1747 }
1748
1749 #[test_matrix(
1750 ProgramCacheEntryType::Unloaded(get_mock_program_runtime_environment()),
1751 (
1752 new_loaded_entry(get_mock_program_runtime_environment()),
1753 ProgramCacheEntryType::FailedVerification(get_mock_program_runtime_environment()),
1754 )
1755 )]
1756 #[test_case(
1757 ProgramCacheEntryType::Closed,
1758 ProgramCacheEntryType::Unloaded(get_mock_program_runtime_environment())
1759 )]
1760 #[test_case(
1761 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1762 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock())
1763 )]
1764 fn test_assign_program_success(old: ProgramCacheEntryType, new: ProgramCacheEntryType) {
1765 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1766 let env = get_mock_program_runtime_environment();
1767 let program_id = Pubkey::new_unique();
1768 assert!(!cache.assign_program(
1769 &env,
1770 program_id,
1771 10,
1772 Arc::new(ProgramCacheEntry {
1773 program: old,
1774 account_owner: ProgramCacheEntryOwner::LoaderV2,
1775 deployment_slot: 10,
1776 stats: Arc::default(),
1777 latest_access_slot: AtomicU64::default(),
1778 }),
1779 ));
1780 assert!(!cache.assign_program(
1781 &env,
1782 program_id,
1783 10,
1784 Arc::new(ProgramCacheEntry {
1785 program: new,
1786 account_owner: ProgramCacheEntryOwner::LoaderV2,
1787 deployment_slot: 10,
1788 stats: Arc::default(),
1789 latest_access_slot: AtomicU64::default(),
1790 }),
1791 ));
1792 }
1793
1794 #[test]
1795 fn test_assign_program_removes_entries_in_same_slot() {
1796 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1797 let env = get_mock_program_runtime_environment();
1798 let program_id = Pubkey::new_unique();
1799 let closed_other_slot = Arc::new(ProgramCacheEntry {
1800 program: ProgramCacheEntryType::Closed,
1801 account_owner: ProgramCacheEntryOwner::LoaderV2,
1802 deployment_slot: 9,
1803 stats: Arc::default(),
1804 latest_access_slot: AtomicU64::default(),
1805 });
1806 let closed_current_slot = Arc::new(ProgramCacheEntry {
1807 program: ProgramCacheEntryType::Closed,
1808 account_owner: ProgramCacheEntryOwner::LoaderV2,
1809 deployment_slot: 10,
1810 stats: Arc::default(),
1811 latest_access_slot: AtomicU64::default(),
1812 });
1813 let loaded_entry_current_env = Arc::new(ProgramCacheEntry {
1814 program: ProgramCacheEntryType::Unloaded(get_mock_program_runtime_environment()),
1815 account_owner: ProgramCacheEntryOwner::LoaderV2,
1816 deployment_slot: 10,
1817 stats: Arc::default(),
1818 latest_access_slot: AtomicU64::default(),
1819 });
1820 let loaded_entry_upcoming_env = Arc::new(ProgramCacheEntry {
1821 program: ProgramCacheEntryType::Unloaded(ProgramRuntimeEnvironment::from(
1822 BuiltinProgram::new_mock(),
1823 )),
1824 account_owner: ProgramCacheEntryOwner::LoaderV2,
1825 deployment_slot: 10,
1826 stats: Arc::default(),
1827 latest_access_slot: AtomicU64::default(),
1828 });
1829 assert!(!cache.assign_program(&env, program_id, 9, closed_other_slot.clone()));
1830 assert!(!cache.assign_program(&env, program_id, 10, closed_current_slot));
1831 assert!(!cache.assign_program(&env, program_id, 10, loaded_entry_upcoming_env.clone()));
1832 assert!(!cache.assign_program(&env, program_id, 10, loaded_entry_current_env.clone()));
1833 assert_eq!(
1835 cache.get_slot_versions_for_tests(&program_id),
1836 &[
1837 closed_other_slot,
1838 loaded_entry_current_env,
1839 loaded_entry_upcoming_env
1840 ]
1841 );
1842 }
1843
1844 #[test]
1845 fn test_assign_program_reload_merges_statistics() {
1846 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1847 let env = get_mock_program_runtime_environment();
1848 let program_id = Pubkey::new_unique();
1849
1850 let stats = Arc::new(ProgramStatistics {
1851 uses: 1.into(),
1852 compilations: 2.into(),
1853 total_compilation_time_us: 3.into(),
1854 compilation_time_ema: 100.into(),
1855 jit_invocations: 4.into(),
1856 total_jit_execution_time_us: 5.into(),
1857 jit_execution_time_ema: 200.into(),
1858 interpreted_invocations: 6.into(),
1859 total_interpretation_time_us: 7.into(),
1860 interpretation_time_ema: 300.into(),
1861 });
1862 let unloaded = Arc::new(ProgramCacheEntry {
1863 program: ProgramCacheEntryType::Unloaded(env.clone()),
1864 account_owner: ProgramCacheEntryOwner::LoaderV3,
1865 deployment_slot: 100,
1866 stats: Arc::clone(&stats),
1867 latest_access_slot: AtomicU64::default(),
1868 });
1869 cache.assign_program(&env, program_id, 100, unloaded);
1870
1871 let loaded = Arc::new(ProgramCacheEntry {
1873 program: new_loaded_entry(env.clone()),
1874 account_owner: ProgramCacheEntryOwner::LoaderV3,
1875 deployment_slot: 100,
1876 stats: Arc::default(), latest_access_slot: AtomicU64::default(),
1878 });
1879 cache.assign_program(&env, program_id, 100, Arc::clone(&loaded));
1880
1881 assert_eq!(cache.stats.insertions.load(Ordering::Relaxed), 1);
1882 assert_eq!(cache.stats.reloads.load(Ordering::Relaxed), 1);
1883
1884 let merged = &loaded.stats;
1885 let ord = Ordering::Relaxed;
1886 assert_eq!(merged.uses.load(ord), stats.uses.load(ord));
1887 assert_eq!(merged.compilations.load(ord), stats.compilations.load(ord));
1888 assert_eq!(
1889 merged.total_compilation_time_us.load(ord),
1890 stats.total_compilation_time_us.load(ord)
1891 );
1892 assert_eq!(
1893 merged.jit_invocations.load(ord),
1894 stats.jit_invocations.load(ord)
1895 );
1896 assert_eq!(
1897 merged.total_jit_execution_time_us.load(ord),
1898 stats.total_jit_execution_time_us.load(ord)
1899 );
1900 assert_eq!(
1901 merged.interpreted_invocations.load(ord),
1902 stats.interpreted_invocations.load(ord)
1903 );
1904 assert_eq!(
1905 merged.total_interpretation_time_us.load(ord),
1906 stats.total_interpretation_time_us.load(ord)
1907 );
1908
1909 const EMA_DIVISOR: u64 = 2;
1912 assert_eq!(
1913 merged.compilation_time_ema.load(ord),
1914 stats
1915 .compilation_time_ema
1916 .load(ord)
1917 .wrapping_div(EMA_DIVISOR)
1918 );
1919 assert_eq!(
1920 merged.jit_execution_time_ema.load(ord),
1921 stats
1922 .jit_execution_time_ema
1923 .load(ord)
1924 .wrapping_div(EMA_DIVISOR)
1925 );
1926 assert_eq!(
1927 merged.interpretation_time_ema.load(ord),
1928 stats
1929 .interpretation_time_ema
1930 .load(ord)
1931 .wrapping_div(EMA_DIVISOR)
1932 );
1933 }
1934
1935 #[test]
1936 fn test_tombstone() {
1937 let env = get_mock_program_runtime_environment();
1938 let tombstone = ProgramCacheEntry::new_failed_verification_tombstone(
1939 0,
1940 ProgramCacheEntryOwner::LoaderV2,
1941 env.clone(),
1942 );
1943 assert_matches!(
1944 tombstone.program,
1945 ProgramCacheEntryType::FailedVerification(_)
1946 );
1947 assert!(tombstone.is_tombstone());
1948 assert_eq!(tombstone.deployment_slot, 0);
1949 assert_eq!(tombstone.effective_slot(), 0);
1950
1951 let tombstone =
1952 ProgramCacheEntry::new_closed_tombstone(100, ProgramCacheEntryOwner::LoaderV2);
1953 assert_matches!(tombstone.program, ProgramCacheEntryType::Closed);
1954 assert!(tombstone.is_tombstone());
1955 assert_eq!(tombstone.deployment_slot, 100);
1956 assert_eq!(tombstone.effective_slot(), 100);
1957
1958 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1959 let program1 = Pubkey::new_unique();
1960 let tombstone = set_failed_verification_tombstone(&mut cache, program1, 10, env.clone());
1961 let slot_versions = cache.get_slot_versions_for_tests(&program1);
1962 assert_eq!(slot_versions.len(), 1);
1963 assert!(slot_versions.first().unwrap().is_tombstone());
1964 assert_eq!(tombstone.deployment_slot, 10);
1965 assert_eq!(tombstone.effective_slot(), 10);
1966
1967 let program2 = Pubkey::new_unique();
1969 cache.assign_program(&env, program2, 50, new_test_builtin_entry(50));
1970 let slot_versions = cache.get_slot_versions_for_tests(&program2);
1971 assert_eq!(slot_versions.len(), 1);
1972 assert!(!slot_versions.first().unwrap().is_tombstone());
1973
1974 let tombstone = set_failed_verification_tombstone(&mut cache, program2, 60, env);
1975 let slot_versions = cache.get_slot_versions_for_tests(&program2);
1976 assert_eq!(slot_versions.len(), 2);
1977 assert!(!slot_versions.first().unwrap().is_tombstone());
1978 assert!(slot_versions.get(1).unwrap().is_tombstone());
1979 assert!(tombstone.is_tombstone());
1980 assert_eq!(tombstone.deployment_slot, 60);
1981 assert_eq!(tombstone.effective_slot(), 60);
1982 }
1983
1984 struct TestForkGraph {
1985 relation: BlockRelation,
1986 }
1987 impl ForkGraph for TestForkGraph {
1988 fn relationship(&self, _a: Slot, _b: Slot) -> BlockRelation {
1989 self.relation
1990 }
1991 }
1992
1993 #[test]
1994 fn test_prune_empty() {
1995 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1996 let fork_graph = Arc::new(RwLock::new(TestForkGraph {
1997 relation: BlockRelation::Unrelated,
1998 }));
1999
2000 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2001
2002 cache.prune(0, None, &fork_graph.read().unwrap());
2003 assert!(cache.get_flattened_entries_for_tests().is_empty());
2004
2005 cache.prune(10, None, &fork_graph.read().unwrap());
2006 assert!(cache.get_flattened_entries_for_tests().is_empty());
2007
2008 let mut cache = ProgramCache::<TestForkGraph>::new(0);
2009 let fork_graph = Arc::new(RwLock::new(TestForkGraph {
2010 relation: BlockRelation::Ancestor,
2011 }));
2012
2013 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2014
2015 cache.prune(0, None, &fork_graph.read().unwrap());
2016 assert!(cache.get_flattened_entries_for_tests().is_empty());
2017
2018 cache.prune(10, None, &fork_graph.read().unwrap());
2019 assert!(cache.get_flattened_entries_for_tests().is_empty());
2020
2021 let mut cache = ProgramCache::<TestForkGraph>::new(0);
2022 let fork_graph = Arc::new(RwLock::new(TestForkGraph {
2023 relation: BlockRelation::Descendant,
2024 }));
2025
2026 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2027
2028 cache.prune(0, None, &fork_graph.read().unwrap());
2029 assert!(cache.get_flattened_entries_for_tests().is_empty());
2030
2031 cache.prune(10, None, &fork_graph.read().unwrap());
2032 assert!(cache.get_flattened_entries_for_tests().is_empty());
2033
2034 let mut cache = ProgramCache::<TestForkGraph>::new(0);
2035 let fork_graph = Arc::new(RwLock::new(TestForkGraph {
2036 relation: BlockRelation::Unknown,
2037 }));
2038 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2039
2040 cache.prune(0, None, &fork_graph.read().unwrap());
2041 assert!(cache.get_flattened_entries_for_tests().is_empty());
2042
2043 cache.prune(10, None, &fork_graph.read().unwrap());
2044 assert!(cache.get_flattened_entries_for_tests().is_empty());
2045 }
2046
2047 #[test]
2048 fn test_prune_removes_programs_with_no_entries() {
2049 let (mut cache, fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Unknown);
2050 let env = get_mock_program_runtime_environment();
2051 let emptied = Pubkey::new_unique();
2052 cache.assign_program(
2053 &env,
2054 emptied,
2055 100,
2056 new_test_entry_with_owner(
2057 100,
2058 ProgramCacheEntryOwner::LoaderV3,
2059 new_loaded_entry(env.clone()),
2060 ),
2061 );
2062
2063 cache.prune(50, None, &fork_graph.read().unwrap());
2065 match &cache.index {
2066 IndexImplementation::V1 { entries, .. } => assert!(!entries.contains_key(&emptied)),
2067 }
2068 assert_eq!(cache.stats.empty_entries.load(Ordering::Relaxed), 1);
2069 assert_eq!(cache.stats.prunes_orphan.load(Ordering::Relaxed), 1);
2070 }
2071
2072 #[test]
2073 fn test_prune_across_the_root() {
2074 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
2080 let mut fork_graph = TestForkGraphSpecific::default();
2081 fork_graph.insert_fork(&[30, 50, 70]);
2082 let fork_graph = Arc::new(RwLock::new(fork_graph));
2083 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2084
2085 let env = get_mock_program_runtime_environment();
2086 let program_id = Pubkey::new_unique();
2087 let below = new_test_entry_with_owner(
2088 30,
2089 ProgramCacheEntryOwner::LoaderV3,
2090 new_loaded_entry(env.clone()),
2091 );
2092 let above = new_test_entry_with_owner(
2093 70,
2094 ProgramCacheEntryOwner::LoaderV3,
2095 new_loaded_entry(env.clone()),
2096 );
2097 cache.assign_program(&env, program_id, 30, Arc::clone(&below));
2098 cache.assign_program(&env, program_id, 70, Arc::clone(&above));
2099
2100 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
2101 assert_eq!(slot_versions.len(), 2);
2102 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &below));
2103 assert!(Arc::ptr_eq(slot_versions.get(1).unwrap(), &above));
2104
2105 cache.prune(50, None, &fork_graph.read().unwrap());
2106
2107 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
2109 assert_eq!(slot_versions.len(), 2);
2110 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &below));
2111 assert!(Arc::ptr_eq(slot_versions.get(1).unwrap(), &above));
2112 }
2113
2114 #[test]
2115 fn test_prune_across_the_root_ancestors() {
2116 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
2122 let mut fork_graph = TestForkGraphSpecific::default();
2123 fork_graph.insert_fork(&[10, 20, 30, 50, 70]);
2124 let fork_graph = Arc::new(RwLock::new(fork_graph));
2125 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2126
2127 let env = get_mock_program_runtime_environment();
2128 let program_id = Pubkey::new_unique();
2129 let oldest = new_test_entry_with_owner(
2130 10,
2131 ProgramCacheEntryOwner::LoaderV3,
2132 new_loaded_entry(env.clone()),
2133 );
2134 let older = new_test_entry_with_owner(
2135 20,
2136 ProgramCacheEntryOwner::LoaderV3,
2137 new_loaded_entry(env.clone()),
2138 );
2139 let below = new_test_entry_with_owner(
2140 30,
2141 ProgramCacheEntryOwner::LoaderV3,
2142 new_loaded_entry(env.clone()),
2143 );
2144 let above = new_test_entry_with_owner(
2145 70,
2146 ProgramCacheEntryOwner::LoaderV3,
2147 new_loaded_entry(env.clone()),
2148 );
2149 cache.assign_program(&env, program_id, 10, Arc::clone(&oldest));
2150 cache.assign_program(&env, program_id, 20, Arc::clone(&older));
2151 cache.assign_program(&env, program_id, 30, Arc::clone(&below));
2152 cache.assign_program(&env, program_id, 70, Arc::clone(&above));
2153
2154 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
2155 assert_eq!(slot_versions.len(), 4);
2156 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &oldest));
2157 assert!(Arc::ptr_eq(slot_versions.get(1).unwrap(), &older));
2158 assert!(Arc::ptr_eq(slot_versions.get(2).unwrap(), &below));
2159 assert!(Arc::ptr_eq(slot_versions.get(3).unwrap(), &above));
2160
2161 cache.prune(50, None, &fork_graph.read().unwrap());
2162
2163 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
2166 assert_eq!(slot_versions.len(), 2);
2167 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &below));
2168 assert!(Arc::ptr_eq(slot_versions.get(1).unwrap(), &above));
2169 assert_eq!(cache.stats.prunes_orphan.load(Ordering::Relaxed), 2);
2170 }
2171
2172 #[test]
2173 fn test_prune_entry_older_than_root() {
2174 let (mut cache, fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Unknown);
2188 let env = get_mock_program_runtime_environment();
2189 let program_id = Pubkey::new_unique();
2190 let entry = new_test_entry_with_owner(
2191 5,
2192 ProgramCacheEntryOwner::LoaderV3,
2193 new_loaded_entry(env.clone()),
2194 );
2195 cache.assign_program(&env, program_id, 5, Arc::clone(&entry));
2196 cache.latest_root_slot = 10;
2197
2198 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
2199 assert_eq!(slot_versions.len(), 1);
2200 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &entry));
2201
2202 cache.prune(20, None, &fork_graph.read().unwrap());
2203
2204 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
2208 assert_eq!(slot_versions.len(), 1);
2209 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &entry));
2210 assert_eq!(cache.stats.prunes_orphan.load(Ordering::Relaxed), 0);
2211 }
2212
2213 #[test]
2214 fn test_prune_orphan_newer_than_root() {
2215 let (mut cache, fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Unknown);
2225 let env = get_mock_program_runtime_environment();
2226 let program_id = Pubkey::new_unique();
2227 let orphan = new_test_entry_with_owner(
2228 100,
2229 ProgramCacheEntryOwner::LoaderV3,
2230 new_loaded_entry(env.clone()),
2231 );
2232 cache.assign_program(&env, program_id, 100, Arc::clone(&orphan));
2233
2234 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
2235 assert_eq!(slot_versions.len(), 1);
2236 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &orphan));
2237
2238 cache.prune(50, None, &fork_graph.read().unwrap());
2239
2240 assert!(cache.get_slot_versions_for_tests(&program_id).is_empty());
2243 assert_eq!(cache.stats.prunes_orphan.load(Ordering::Relaxed), 1);
2244 }
2245
2246 #[test]
2247 fn test_prune_tombstones() {
2248 let env = get_mock_program_runtime_environment();
2249 let fork_graph = Arc::new(RwLock::new(TestForkGraph {
2250 relation: BlockRelation::Ancestor,
2251 }));
2252
2253 let program1 = Pubkey::new_unique();
2254 let entries = [
2255 Arc::new(ProgramCacheEntry::new_unloaded(
2256 20,
2257 ProgramCacheEntryOwner::LoaderV3,
2258 ProgramRuntimeEnvironment::clone(&env),
2259 )),
2260 Arc::new(ProgramCacheEntry::new_closed_tombstone(
2261 20,
2262 ProgramCacheEntryOwner::LoaderV3,
2263 )),
2264 Arc::new(ProgramCacheEntry::new_failed_verification_tombstone(
2265 20,
2266 ProgramCacheEntryOwner::LoaderV3,
2267 ProgramRuntimeEnvironment::clone(&env),
2268 )),
2269 ];
2270 for entry in &entries {
2271 let mut cache = ProgramCache::<TestForkGraph>::new(0);
2272 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2273 cache.assign_program(&env, program1, 10, new_test_entry(10));
2275 cache.assign_program(&env, program1, entry.deployment_slot, Arc::clone(entry));
2276 cache.prune(
2277 MAX_TOMBSTONE_AGE_IN_SLOTS,
2278 None,
2279 &fork_graph.read().unwrap(),
2280 );
2281 let slot_versions = cache.get_slot_versions_for_tests(&program1);
2282 assert_eq!(slot_versions, std::slice::from_ref(entry));
2283 cache.prune(
2285 MAX_TOMBSTONE_AGE_IN_SLOTS
2286 .saturating_add(entry.latest_access_slot.load(Ordering::Relaxed)),
2287 None,
2288 &fork_graph.read().unwrap(),
2289 );
2290 let slot_versions = cache.get_slot_versions_for_tests(&program1);
2291 assert_eq!(slot_versions, std::slice::from_ref(entry));
2292 cache.prune(
2294 MAX_TOMBSTONE_AGE_IN_SLOTS
2295 .saturating_add(entry.latest_access_slot.load(Ordering::Relaxed))
2296 .saturating_add(1),
2297 None,
2298 &fork_graph.read().unwrap(),
2299 );
2300 assert!(cache.get_flattened_entries_for_tests().is_empty());
2301 }
2302 }
2303
2304 #[test]
2305 fn test_prune_tombstone_first_ancestor_takes_the_rest() {
2306 let (mut cache, fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
2315 let env = get_mock_program_runtime_environment();
2316 let other_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
2317 let program_id = Pubkey::new_unique();
2318 let on_other_env = new_test_entry_with_owner(
2319 50,
2320 ProgramCacheEntryOwner::LoaderV3,
2321 new_loaded_entry(other_env.clone()),
2322 );
2323 let on_env = new_test_entry_with_owner(
2324 60,
2325 ProgramCacheEntryOwner::LoaderV3,
2326 new_loaded_entry(env.clone()),
2327 );
2328 let closed = new_test_entry_with_owner(
2329 100,
2330 ProgramCacheEntryOwner::LoaderV3,
2331 new_closed_entry(env.clone()),
2332 );
2333 cache.assign_program(&other_env, program_id, 50, Arc::clone(&on_other_env));
2334 cache.assign_program(&env, program_id, 60, Arc::clone(&on_env));
2335 cache.assign_program(&env, program_id, 100, Arc::clone(&closed));
2336
2337 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
2338 assert_eq!(slot_versions.len(), 3);
2339 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &on_other_env));
2340 assert!(Arc::ptr_eq(slot_versions.get(1).unwrap(), &on_env));
2341 assert!(Arc::ptr_eq(slot_versions.get(2).unwrap(), &closed));
2342
2343 cache.prune(200, None, &fork_graph.read().unwrap());
2344
2345 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
2349 assert_eq!(slot_versions.len(), 1);
2350 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &closed));
2351 assert_eq!(cache.stats.prunes_orphan.load(Ordering::Relaxed), 2);
2352 }
2353
2354 #[test]
2355 fn test_prune_tombstone_newer_than_root_keeps_the_rest() {
2356 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
2366 let mut fork_graph = TestForkGraphSpecific::default();
2367 fork_graph.insert_fork(&[50, 60, 100, 101]);
2368 let fork_graph = Arc::new(RwLock::new(fork_graph));
2369 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2370
2371 let env = get_mock_program_runtime_environment();
2372 let other_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
2373 let program_id = Pubkey::new_unique();
2374 let on_other_env = new_test_entry_with_owner(
2375 50,
2376 ProgramCacheEntryOwner::LoaderV3,
2377 new_loaded_entry(other_env.clone()),
2378 );
2379 let on_env = new_test_entry_with_owner(
2380 60,
2381 ProgramCacheEntryOwner::LoaderV3,
2382 new_loaded_entry(env.clone()),
2383 );
2384 let closed = new_test_entry_with_owner(
2385 101,
2386 ProgramCacheEntryOwner::LoaderV3,
2387 new_closed_entry(env.clone()),
2388 );
2389 cache.assign_program(&other_env, program_id, 50, Arc::clone(&on_other_env));
2390 cache.assign_program(&env, program_id, 60, Arc::clone(&on_env));
2391 cache.assign_program(&env, program_id, 101, Arc::clone(&closed));
2392
2393 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
2394 assert_eq!(slot_versions.len(), 3);
2395 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &on_other_env));
2396 assert!(Arc::ptr_eq(slot_versions.get(1).unwrap(), &on_env));
2397 assert!(Arc::ptr_eq(slot_versions.get(2).unwrap(), &closed));
2398
2399 cache.prune(100, None, &fork_graph.read().unwrap());
2400
2401 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
2406 assert_eq!(slot_versions.len(), 3);
2407 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &on_other_env));
2408 assert!(Arc::ptr_eq(slot_versions.get(1).unwrap(), &on_env));
2409 assert!(Arc::ptr_eq(slot_versions.get(2).unwrap(), &closed));
2410 assert_eq!(cache.stats.prunes_orphan.load(Ordering::Relaxed), 0);
2411 }
2412
2413 #[test]
2414 fn test_prune_with_two_environments_before_epoch_boundary() {
2415 let mut cache = ProgramCache::<TestForkGraph>::new(0);
2416 let env = get_mock_program_runtime_environment();
2417 let new_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
2418 let fork_graph = Arc::new(RwLock::new(TestForkGraph {
2419 relation: BlockRelation::Ancestor,
2420 }));
2421 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2422
2423 let program1 = Pubkey::new_unique();
2424 cache.assign_program(&env, program1, 10, new_test_entry(10));
2425 let updated_program = Arc::new(ProgramCacheEntry {
2426 program: new_loaded_entry(new_env.clone()),
2427 deployment_slot: 20,
2428 ..Default::default()
2429 });
2430 cache.assign_program(&env, program1, 20, updated_program.clone());
2431
2432 assert_eq!(cache.get_slot_versions_for_tests(&program1).len(), 2);
2434
2435 cache.prune(21, None, &fork_graph.read().unwrap());
2436
2437 assert_eq!(cache.get_slot_versions_for_tests(&program1).len(), 2);
2439 }
2440
2441 #[test]
2442 fn test_prune_with_two_environments_after_epoch_boundary() {
2443 let mut cache = ProgramCache::<TestForkGraph>::new(0);
2444 let env = get_mock_program_runtime_environment();
2445 let new_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
2446 let fork_graph = Arc::new(RwLock::new(TestForkGraph {
2447 relation: BlockRelation::Ancestor,
2448 }));
2449 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2450 let program1 = Pubkey::new_unique();
2451
2452 let old_program_old_env = Arc::new(ProgramCacheEntry {
2453 program: new_loaded_entry(env.clone()),
2454 deployment_slot: 10,
2455 ..Default::default()
2456 });
2457 let old_program_new_env = Arc::new(ProgramCacheEntry {
2458 program: new_loaded_entry(new_env.clone()),
2459 deployment_slot: 10,
2460 ..Default::default()
2461 });
2462 let new_program_old_env = Arc::new(ProgramCacheEntry {
2463 program: new_loaded_entry(env.clone()),
2464 deployment_slot: 20,
2465 ..Default::default()
2466 });
2467 cache.assign_program(&env, program1, 10, old_program_old_env.clone());
2468 cache.assign_program(&env, program1, 10, old_program_new_env.clone());
2469 cache.assign_program(&env, program1, 20, new_program_old_env.clone());
2470 let slot_versions = cache.get_slot_versions_for_tests(&program1);
2471 assert_eq!(
2472 &slot_versions,
2473 &[
2474 old_program_new_env.clone(),
2475 old_program_old_env.clone(),
2476 new_program_old_env.clone(),
2477 ]
2478 );
2479
2480 cache.prune(21, Some(new_env.clone()), &fork_graph.read().unwrap());
2481 let slot_versions = cache.get_slot_versions_for_tests(&program1);
2482 assert_eq!(&slot_versions, &[old_program_new_env]);
2483 assert!(matches!(
2484 &slot_versions.first().unwrap().program,
2485 ProgramCacheEntryType::Loaded(_)
2486 ));
2487 }
2488
2489 #[test_matrix(
2490 (
2491 new_closed_entry,
2492 new_builtin_entry,
2493 new_failed_verification_entry,
2494 new_unloaded_entry,
2495 new_loaded_entry,
2496 ),
2497 (false, true)
2498 )]
2499 fn test_prune_environment_sweep_by_entry_type(
2500 new_program: fn(ProgramRuntimeEnvironment) -> ProgramCacheEntryType,
2501 on_new_environment: bool,
2502 ) {
2503 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
2513 let mut fork_graph = TestForkGraphSpecific::default();
2514 fork_graph.insert_fork(&[40, 50]);
2515 let fork_graph = Arc::new(RwLock::new(fork_graph));
2516 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2517
2518 let env = get_mock_program_runtime_environment();
2519 let new_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
2520 let entry_env = if on_new_environment {
2521 new_env.clone()
2522 } else {
2523 env.clone()
2524 };
2525 let program_id = Pubkey::new_unique();
2526 let entry = new_test_entry_with_owner(
2527 50,
2528 ProgramCacheEntryOwner::LoaderV3,
2529 new_program(entry_env.clone()),
2530 );
2531 let carries_an_environment = entry.program.get_environment().is_some();
2532 cache.assign_program(&entry_env, program_id, 50, Arc::clone(&entry));
2533
2534 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
2535 assert_eq!(slot_versions.len(), 1);
2536 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &entry));
2537
2538 cache.prune(40, Some(new_env.clone()), &fork_graph.read().unwrap());
2539
2540 let swept = carries_an_environment && !on_new_environment;
2543 assert_eq!(
2544 cache.stats.prunes_environment.load(Ordering::Relaxed),
2545 u64::from(swept)
2546 );
2547 if swept {
2548 assert!(cache.get_slot_versions_for_tests(&program_id).is_empty());
2549 } else {
2550 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
2551 assert_eq!(slot_versions.len(), 1);
2552 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &entry));
2553 }
2554 }
2555
2556 #[test]
2557 fn test_prune_environment_sweep_keeps_tombstones() {
2558 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
2570 let mut fork_graph = TestForkGraphSpecific::default();
2571 fork_graph.insert_fork(&[0, 40, 50, 60, 70, 100]);
2572 let fork_graph = Arc::new(RwLock::new(fork_graph));
2573 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2574 let env = get_mock_program_runtime_environment();
2575 let new_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
2576 let program_id = Pubkey::new_unique();
2577 let closed = new_test_entry_with_owner(
2578 50,
2579 ProgramCacheEntryOwner::LoaderV3,
2580 new_closed_entry(env.clone()),
2581 );
2582 let failed_verification = new_test_entry_with_owner(
2583 60,
2584 ProgramCacheEntryOwner::LoaderV3,
2585 new_failed_verification_entry(env.clone()),
2587 );
2588 let loaded = new_test_entry_with_owner(
2589 70,
2590 ProgramCacheEntryOwner::LoaderV3,
2591 new_loaded_entry(env.clone()),
2592 );
2593 cache.assign_program(&env, program_id, 50, Arc::clone(&closed));
2594 cache.assign_program(&env, program_id, 60, Arc::clone(&failed_verification));
2595 cache.assign_program(&env, program_id, 70, Arc::clone(&loaded));
2596
2597 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
2598 assert_eq!(slot_versions.len(), 3);
2599 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &closed));
2600 assert!(Arc::ptr_eq(
2601 slot_versions.get(1).unwrap(),
2602 &failed_verification
2603 ));
2604 assert!(Arc::ptr_eq(slot_versions.get(2).unwrap(), &loaded));
2605
2606 cache.prune(40, Some(new_env.clone()), &fork_graph.read().unwrap());
2609 assert_eq!(cache.stats.prunes_environment.load(Ordering::Relaxed), 2);
2610 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
2611 assert_eq!(slot_versions.len(), 1);
2612 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &closed));
2613
2614 let mut search_for = vec![ProgramToLoad {
2616 program_id: &program_id,
2617 loader: ProgramCacheEntryOwner::LoaderV3,
2618 deployment_slot: 50,
2619 }];
2620 let mut extracted = ProgramCacheForTxBatch::new(100);
2621 cache.extract(&mut search_for, &mut extracted, &new_env, true, true);
2622 assert!(search_for.is_empty());
2623 assert!(Arc::ptr_eq(
2624 extracted.entries.get(&program_id).unwrap(),
2625 &closed
2626 ));
2627
2628 let mut search_for = vec![ProgramToLoad {
2632 program_id: &program_id,
2633 loader: ProgramCacheEntryOwner::LoaderV3,
2634 deployment_slot: 50,
2635 }];
2636 let mut extracted = ProgramCacheForTxBatch::new(100);
2637 cache.extract(&mut search_for, &mut extracted, &env, true, true);
2638 assert!(search_for.is_empty());
2639 assert!(Arc::ptr_eq(
2640 extracted.entries.get(&program_id).unwrap(),
2641 &closed
2642 ));
2643 }
2644
2645 #[test]
2646 #[should_panic(expected = "self.latest_root_slot <= new_root_slot")]
2647 fn test_prune_backwards_panics() {
2648 let (mut cache, fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
2649 cache.latest_root_slot = 100;
2650
2651 cache.prune(50, None, &fork_graph.read().unwrap());
2654 }
2655
2656 #[derive(Default)]
2657 struct TestForkGraphSpecific {
2658 forks: Vec<Vec<Slot>>,
2659 }
2660
2661 impl TestForkGraphSpecific {
2662 fn insert_fork(&mut self, fork: &[Slot]) {
2663 let mut fork = fork.to_vec();
2664 fork.sort();
2665 self.forks.push(fork)
2666 }
2667 }
2668
2669 impl ForkGraph for TestForkGraphSpecific {
2670 fn relationship(&self, a: Slot, b: Slot) -> BlockRelation {
2671 match self.forks.iter().try_for_each(|fork| {
2672 let relation = fork
2673 .iter()
2674 .position(|x| *x == a)
2675 .and_then(|a_pos| {
2676 fork.iter().position(|x| *x == b).and_then(|b_pos| {
2677 (a_pos == b_pos)
2678 .then_some(BlockRelation::Equal)
2679 .or_else(|| (a_pos < b_pos).then_some(BlockRelation::Ancestor))
2680 .or(Some(BlockRelation::Descendant))
2681 })
2682 })
2683 .unwrap_or(BlockRelation::Unrelated);
2684
2685 if relation != BlockRelation::Unrelated {
2686 return ControlFlow::Break(relation);
2687 }
2688
2689 ControlFlow::Continue(())
2690 }) {
2691 ControlFlow::Break(relation) => relation,
2692 _ => BlockRelation::Unrelated,
2693 }
2694 }
2695 }
2696
2697 fn get_entries_to_load<'a>(
2698 cache: &ProgramCache<TestForkGraphSpecific>,
2699 loading_slot: Slot,
2700 keys: &'a [Pubkey],
2701 ) -> Vec<ProgramToLoad<'a>> {
2702 let fork_graph = cache.fork_graph.as_ref().unwrap().upgrade().unwrap();
2703 let locked_fork_graph = fork_graph.read().unwrap();
2704 let entries = cache.get_flattened_entries_for_tests();
2705 keys.iter()
2706 .filter_map(|key| {
2707 entries
2708 .iter()
2709 .rev()
2710 .find(|(program_id, entry)| {
2711 program_id == key
2712 && matches!(
2713 locked_fork_graph.relationship(entry.deployment_slot, loading_slot),
2714 BlockRelation::Equal | BlockRelation::Ancestor,
2715 )
2716 })
2717 .map(|(_program_id, entry)| ProgramToLoad {
2718 program_id: key,
2719 loader: entry.account_owner,
2720 deployment_slot: entry.deployment_slot,
2721 })
2722 })
2723 .collect()
2724 }
2725
2726 fn match_slot(
2727 extracted: &ProgramCacheForTxBatch,
2728 program: &Pubkey,
2729 deployment_slot: Slot,
2730 working_slot: Slot,
2731 ) -> bool {
2732 assert_eq!(extracted.slot, working_slot);
2733 extracted
2734 .entries
2735 .get(program)
2736 .map(|entry| entry.deployment_slot == deployment_slot)
2737 .unwrap_or(false)
2738 }
2739
2740 fn match_missing(
2741 missing: &[ProgramToLoad],
2742 program_id: &Pubkey,
2743 expected_result: bool,
2744 ) -> bool {
2745 missing.iter().any(|entry| entry.program_id == program_id) == expected_result
2746 }
2747
2748 #[test]
2749 fn test_fork_extract_and_prune() {
2750 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
2751 let env = get_mock_program_runtime_environment();
2752
2753 let mut fork_graph = TestForkGraphSpecific::default();
2769 fork_graph.insert_fork(&[0, 10, 20, 22]);
2770 fork_graph.insert_fork(&[0, 5, 11, 15, 16, 18, 19, 21, 23]);
2771 fork_graph.insert_fork(&[0, 5, 11, 25, 27]);
2772
2773 let fork_graph = Arc::new(RwLock::new(fork_graph));
2774 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2775
2776 let program1 = Pubkey::new_unique();
2777 cache.assign_program(&env, program1, 0, new_test_entry(0));
2778 cache.assign_program(&env, program1, 10, new_test_entry(10));
2779 cache.assign_program(&env, program1, 20, new_test_entry(20));
2780
2781 let program2 = Pubkey::new_unique();
2782 cache.assign_program(&env, program2, 5, new_test_entry(5));
2783 cache.assign_program(&env, program2, 11, new_test_entry(11));
2784
2785 let program3 = Pubkey::new_unique();
2786 cache.assign_program(&env, program3, 25, new_test_entry(25));
2787
2788 let program4 = Pubkey::new_unique();
2789 cache.assign_program(&env, program4, 0, new_test_entry(0));
2790 cache.assign_program(&env, program4, 5, new_test_entry(5));
2791 cache.assign_program(&env, program4, 15, new_test_entry(15));
2793
2794 let keys = &[program1, program2, program3, program4];
2811 let mut missing = get_entries_to_load(&cache, 22, keys);
2812 assert!(match_missing(&missing, &program2, false));
2813 assert!(match_missing(&missing, &program3, false));
2814 let mut extracted = ProgramCacheForTxBatch::new(22);
2815 cache.extract(&mut missing, &mut extracted, &env, true, true);
2816 assert!(match_slot(&extracted, &program1, 20, 22));
2817 assert!(match_slot(&extracted, &program4, 0, 22));
2818
2819 let mut missing = get_entries_to_load(&cache, 15, keys);
2821 assert!(match_missing(&missing, &program3, false));
2822 let mut extracted = ProgramCacheForTxBatch::new(15);
2823 cache.extract(&mut missing, &mut extracted, &env, true, true);
2824 assert!(match_slot(&extracted, &program1, 0, 15));
2825 assert!(match_slot(&extracted, &program2, 11, 15));
2826 let tombstone = extracted
2829 .find(&program4)
2830 .expect("Failed to find the tombstone");
2831 assert_matches!(tombstone.program, ProgramCacheEntryType::DelayVisibility);
2832 assert_eq!(tombstone.deployment_slot, 15);
2833
2834 let mut missing = get_entries_to_load(&cache, 18, keys);
2836 assert!(match_missing(&missing, &program3, false));
2837 let mut extracted = ProgramCacheForTxBatch::new(18);
2838 cache.extract(&mut missing, &mut extracted, &env, true, true);
2839 assert!(match_slot(&extracted, &program1, 0, 18));
2840 assert!(match_slot(&extracted, &program2, 11, 18));
2841 assert!(match_slot(&extracted, &program4, 15, 18));
2843
2844 let mut missing = get_entries_to_load(&cache, 23, keys);
2846 assert!(match_missing(&missing, &program3, false));
2847 let mut extracted = ProgramCacheForTxBatch::new(23);
2848 cache.extract(&mut missing, &mut extracted, &env, true, true);
2849 assert!(match_slot(&extracted, &program1, 0, 23));
2850 assert!(match_slot(&extracted, &program2, 11, 23));
2851 assert!(match_slot(&extracted, &program4, 15, 23));
2853
2854 let mut missing = get_entries_to_load(&cache, 11, keys);
2856 assert!(match_missing(&missing, &program3, false));
2857 let mut extracted = ProgramCacheForTxBatch::new(11);
2858 cache.extract(&mut missing, &mut extracted, &env, true, true);
2859 assert!(match_slot(&extracted, &program1, 0, 11));
2860 let tombstone = extracted
2862 .find(&program2)
2863 .expect("Failed to find the tombstone");
2864 assert_matches!(tombstone.program, ProgramCacheEntryType::DelayVisibility);
2865 assert_eq!(tombstone.deployment_slot, 11);
2866 assert!(match_slot(&extracted, &program4, 5, 11));
2867
2868 cache.prune(5, None, &fork_graph.read().unwrap());
2869
2870 let mut missing = get_entries_to_load(&cache, 21, keys);
2887 assert!(match_missing(&missing, &program3, false));
2888 let mut extracted = ProgramCacheForTxBatch::new(21);
2889 cache.extract(&mut missing, &mut extracted, &env, true, true);
2890 assert!(match_slot(&extracted, &program1, 0, 21));
2892 assert!(match_slot(&extracted, &program2, 11, 21));
2893 assert!(match_slot(&extracted, &program4, 15, 21));
2894
2895 let mut missing = get_entries_to_load(&cache, 27, keys);
2897 let mut extracted = ProgramCacheForTxBatch::new(27);
2898 cache.extract(&mut missing, &mut extracted, &env, true, true);
2899 assert!(match_slot(&extracted, &program1, 0, 27));
2900 assert!(match_slot(&extracted, &program2, 11, 27));
2901 assert!(match_slot(&extracted, &program3, 25, 27));
2902 assert!(match_slot(&extracted, &program4, 5, 27));
2903
2904 cache.prune(15, None, &fork_graph.read().unwrap());
2905
2906 let mut missing = get_entries_to_load(&cache, 23, keys);
2923 assert!(match_missing(&missing, &program3, false));
2924 let mut extracted = ProgramCacheForTxBatch::new(23);
2925 cache.extract(&mut missing, &mut extracted, &env, true, true);
2926 assert!(match_slot(&extracted, &program1, 0, 23));
2927 assert!(match_slot(&extracted, &program2, 11, 23));
2928 assert!(match_slot(&extracted, &program4, 15, 23));
2929 }
2930
2931 #[test]
2932 fn test_extract_using_deployment_slot() {
2933 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
2934 let env = get_mock_program_runtime_environment();
2935
2936 let mut fork_graph = TestForkGraphSpecific::default();
2952 fork_graph.insert_fork(&[0, 10, 20, 22]);
2953 fork_graph.insert_fork(&[0, 5, 11, 12, 15, 16, 18, 19, 21, 23]);
2954 fork_graph.insert_fork(&[0, 5, 11, 25, 27]);
2955
2956 let fork_graph = Arc::new(RwLock::new(fork_graph));
2957 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2958
2959 let program1 = Pubkey::new_unique();
2960 cache.assign_program(&env, program1, 0, new_test_entry(0));
2961 cache.assign_program(&env, program1, 20, new_test_entry(20));
2962
2963 let program2 = Pubkey::new_unique();
2964 cache.assign_program(&env, program2, 5, new_test_entry(5));
2965 cache.assign_program(&env, program2, 11, new_test_entry(11));
2966
2967 let program3 = Pubkey::new_unique();
2968 cache.assign_program(&env, program3, 25, new_test_entry(25));
2969
2970 let keys = &[program1, program2, program3];
2972 let mut missing = get_entries_to_load(&cache, 12, keys);
2973 assert!(match_missing(&missing, &program3, false));
2974 let mut extracted = ProgramCacheForTxBatch::new(12);
2975 cache.extract(&mut missing, &mut extracted, &env, true, true);
2976 assert!(match_slot(&extracted, &program1, 0, 12));
2977 assert!(match_slot(&extracted, &program2, 11, 12));
2978
2979 let mut missing = get_entries_to_load(&cache, 12, keys);
2982 missing.get_mut(1).unwrap().deployment_slot = 5;
2985 assert!(match_missing(&missing, &program3, false));
2986 let mut extracted = ProgramCacheForTxBatch::new(12);
2987 cache.extract(&mut missing, &mut extracted, &env, true, true);
2988 assert!(match_slot(&extracted, &program1, 0, 12));
2989 assert!(match_slot(&extracted, &program2, 5, 12));
2990 }
2991
2992 #[test]
2993 fn test_extract_rejects_entry_deployed_after_the_requested_slot() {
2994 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
2995 let env = get_mock_program_runtime_environment();
2996
2997 let mut fork_graph = TestForkGraphSpecific::default();
2998 fork_graph.insert_fork(&[0, 5, 11, 12]);
2999 let fork_graph = Arc::new(RwLock::new(fork_graph));
3000 cache.set_fork_graph(Arc::downgrade(&fork_graph));
3001
3002 let program = Pubkey::new_unique();
3004 cache.assign_program(&env, program, 11, new_test_entry(11));
3005
3006 let mut missing = vec![ProgramToLoad {
3008 program_id: &program,
3009 loader: ProgramCacheEntryOwner::LoaderV2,
3010 deployment_slot: 5,
3011 }];
3012 let mut extracted = ProgramCacheForTxBatch::new(12);
3013 cache.extract(&mut missing, &mut extracted, &env, true, true);
3014 assert!(match_missing(&missing, &program, true));
3015 assert!(extracted.find(&program).is_none());
3016
3017 let mut missing = vec![ProgramToLoad {
3019 program_id: &program,
3020 loader: ProgramCacheEntryOwner::LoaderV2,
3021 deployment_slot: 11,
3022 }];
3023 let mut extracted = ProgramCacheForTxBatch::new(12);
3024 cache.extract(&mut missing, &mut extracted, &env, true, true);
3025 assert!(match_missing(&missing, &program, false));
3026 assert!(match_slot(&extracted, &program, 11, 12));
3027 }
3028
3029 #[test]
3030 fn test_extract_unloaded() {
3031 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
3032 let env = get_mock_program_runtime_environment();
3033
3034 let mut fork_graph = TestForkGraphSpecific::default();
3050 fork_graph.insert_fork(&[0, 10, 20, 22]);
3051 fork_graph.insert_fork(&[0, 5, 11, 15, 16, 19, 21, 23]);
3052 fork_graph.insert_fork(&[0, 5, 11, 25, 27]);
3053
3054 let fork_graph = Arc::new(RwLock::new(fork_graph));
3055 cache.set_fork_graph(Arc::downgrade(&fork_graph));
3056
3057 let program1 = Pubkey::new_unique();
3058 cache.assign_program(&env, program1, 0, new_test_entry(0));
3059 cache.assign_program(&env, program1, 20, new_test_entry(20));
3060
3061 let program2 = Pubkey::new_unique();
3062 cache.assign_program(&env, program2, 5, new_test_entry(5));
3063 cache.assign_program(&env, program2, 11, new_test_entry(11));
3064
3065 let program3 = Pubkey::new_unique();
3066 let _ = insert_unloaded_entry(&mut cache, program3, 25);
3068
3069 cache.assign_program(
3073 &env,
3074 program3,
3075 20,
3076 Arc::new(
3077 new_test_entry(20)
3078 .to_unloaded()
3079 .expect("Failed to create unloaded program"),
3080 ),
3081 );
3082
3083 let keys = &[program1, program2, program3];
3085 let mut missing = get_entries_to_load(&cache, 19, keys);
3086 assert!(match_missing(&missing, &program3, false));
3087 let mut extracted = ProgramCacheForTxBatch::new(19);
3088 cache.extract(&mut missing, &mut extracted, &env, true, true);
3089 assert!(match_slot(&extracted, &program1, 0, 19));
3090 assert!(match_slot(&extracted, &program2, 11, 19));
3091
3092 let mut missing = get_entries_to_load(&cache, 27, keys);
3094 let mut extracted = ProgramCacheForTxBatch::new(27);
3095 cache.extract(&mut missing, &mut extracted, &env, true, true);
3096 assert!(match_slot(&extracted, &program1, 0, 27));
3097 assert!(match_slot(&extracted, &program2, 11, 27));
3098 assert!(match_missing(&missing, &program3, true));
3099
3100 let mut missing = get_entries_to_load(&cache, 22, keys);
3102 assert!(match_missing(&missing, &program2, false));
3103 let mut extracted = ProgramCacheForTxBatch::new(22);
3104 cache.extract(&mut missing, &mut extracted, &env, true, true);
3105 assert!(match_slot(&extracted, &program1, 20, 22));
3106 assert!(match_missing(&missing, &program3, true));
3107 }
3108
3109 #[test]
3110 fn test_extract_different_environment() {
3111 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
3112 let env = get_mock_program_runtime_environment();
3113 let other_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
3114
3115 let mut fork_graph = TestForkGraphSpecific::default();
3125 fork_graph.insert_fork(&[0, 10, 20, 22]);
3126
3127 let fork_graph = Arc::new(RwLock::new(fork_graph));
3128 cache.set_fork_graph(Arc::downgrade(&fork_graph));
3129
3130 let program1 = Pubkey::new_unique();
3131 cache.assign_program(
3132 &env,
3133 program1,
3134 10,
3135 Arc::new(ProgramCacheEntry::new_closed_tombstone(
3136 10,
3137 ProgramCacheEntryOwner::LoaderV3,
3138 )),
3139 );
3140 cache.assign_program(&env, program1, 20, new_test_entry(20));
3141
3142 let keys = &[program1];
3144 let mut missing = get_entries_to_load(&cache, 22, keys);
3145 let mut extracted = ProgramCacheForTxBatch::new(22);
3146 cache.extract(&mut missing, &mut extracted, &env, true, true);
3147 assert!(match_slot(&extracted, &program1, 20, 22));
3148
3149 let mut missing = get_entries_to_load(&cache, 22, keys);
3151 let mut extracted = ProgramCacheForTxBatch::new(22);
3152 cache.extract(&mut missing, &mut extracted, &other_env, true, true);
3153 assert!(match_missing(&missing, &program1, true));
3154 }
3155
3156 #[test_matrix((false, true))]
3157 fn test_extract_no_second_level(empty_second_level: bool) {
3158 let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
3159 let env = get_mock_program_runtime_environment();
3160 let program_id = Pubkey::new_unique();
3161 if empty_second_level {
3162 match &mut cache.index {
3164 IndexImplementation::V1 { entries, .. } => {
3165 entries.insert(program_id, Vec::new());
3166 }
3167 }
3168 }
3169
3170 let mut search_for = vec![ProgramToLoad {
3173 program_id: &program_id,
3174 loader: ProgramCacheEntryOwner::LoaderV3,
3175 deployment_slot: 0,
3176 }];
3177 let mut extracted = ProgramCacheForTxBatch::new(100);
3178 let task = cache.extract(&mut search_for, &mut extracted, &env, true, true);
3179 assert_eq!(search_for.len(), 1);
3180 assert!(extracted.entries.is_empty());
3181 assert_eq!(task, Some(program_id));
3182 }
3183
3184 #[test]
3185 fn test_extract_account_owner_mismatch() {
3186 let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
3187 let env = get_mock_program_runtime_environment();
3188 let program_id = Pubkey::new_unique();
3189 let owned_by_v2 = new_test_entry_with_owner(
3190 100,
3191 ProgramCacheEntryOwner::LoaderV2,
3192 new_loaded_entry(env.clone()),
3193 );
3194 cache.assign_program(&env, program_id, 100, Arc::clone(&owned_by_v2));
3195
3196 let mut search_for = vec![ProgramToLoad {
3199 program_id: &program_id,
3200 loader: ProgramCacheEntryOwner::LoaderV3,
3201 deployment_slot: 100,
3202 }];
3203 let mut extracted = ProgramCacheForTxBatch::new(200);
3204 cache.extract(&mut search_for, &mut extracted, &env, true, true);
3205 assert_eq!(search_for.len(), 1);
3206 assert!(extracted.entries.is_empty());
3207
3208 let owned_by_v3 = new_test_entry_with_owner(
3211 150,
3212 ProgramCacheEntryOwner::LoaderV3,
3213 new_loaded_entry(env.clone()),
3214 );
3215 cache.assign_program(&env, program_id, 150, Arc::clone(&owned_by_v3));
3216
3217 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
3219 assert_eq!(slot_versions.len(), 2);
3220 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &owned_by_v2));
3221 assert!(Arc::ptr_eq(slot_versions.get(1).unwrap(), &owned_by_v3));
3222
3223 let mut search_for = vec![ProgramToLoad {
3226 program_id: &program_id,
3227 loader: ProgramCacheEntryOwner::LoaderV2,
3228 deployment_slot: 100,
3229 }];
3230 let mut extracted = ProgramCacheForTxBatch::new(200);
3231 cache.extract(&mut search_for, &mut extracted, &env, true, true);
3232 assert!(search_for.is_empty());
3233 assert!(Arc::ptr_eq(
3234 extracted.entries.get(&program_id).unwrap(),
3235 &owned_by_v2
3236 ));
3237
3238 let mut search_for = vec![ProgramToLoad {
3241 program_id: &program_id,
3242 loader: ProgramCacheEntryOwner::LoaderV3,
3243 deployment_slot: 150,
3244 }];
3245 let mut extracted = ProgramCacheForTxBatch::new(200);
3246 cache.extract(&mut search_for, &mut extracted, &env, true, true);
3247 assert!(search_for.is_empty());
3248 assert!(Arc::ptr_eq(
3249 extracted.entries.get(&program_id).unwrap(),
3250 &owned_by_v3
3251 ));
3252 }
3253
3254 #[test]
3255 fn test_extract_environment_mismatch() {
3256 let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
3257 let env = get_mock_program_runtime_environment();
3258 let other_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
3259 let program_id = Pubkey::new_unique();
3260 let on_other_env = new_test_entry_with_owner(
3261 100,
3262 ProgramCacheEntryOwner::LoaderV3,
3263 new_loaded_entry(other_env.clone()),
3264 );
3265 cache.assign_program(&other_env, program_id, 100, Arc::clone(&on_other_env));
3266
3267 let mut search_for = vec![ProgramToLoad {
3272 program_id: &program_id,
3273 loader: ProgramCacheEntryOwner::LoaderV3,
3274 deployment_slot: 100,
3275 }];
3276 let mut extracted = ProgramCacheForTxBatch::new(200);
3277 cache.extract(&mut search_for, &mut extracted, &env, true, true);
3278 assert_eq!(search_for.len(), 1);
3279 assert!(extracted.entries.is_empty());
3280
3281 let on_execution_env = new_test_entry_with_owner(
3284 100,
3285 ProgramCacheEntryOwner::LoaderV3,
3286 new_loaded_entry(env.clone()),
3287 );
3288 cache.assign_program(&env, program_id, 100, Arc::clone(&on_execution_env));
3289
3290 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
3293 assert_eq!(slot_versions.len(), 2);
3294 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &on_other_env));
3295 assert!(Arc::ptr_eq(
3296 slot_versions.get(1).unwrap(),
3297 &on_execution_env
3298 ));
3299
3300 let mut search_for = vec![ProgramToLoad {
3303 program_id: &program_id,
3304 loader: ProgramCacheEntryOwner::LoaderV3,
3305 deployment_slot: 100,
3306 }];
3307 let mut extracted = ProgramCacheForTxBatch::new(200);
3308 cache.extract(&mut search_for, &mut extracted, &env, true, true);
3309 assert!(search_for.is_empty());
3310 assert!(Arc::ptr_eq(
3311 extracted.entries.get(&program_id).unwrap(),
3312 &on_execution_env
3313 ));
3314
3315 let mut search_for = vec![ProgramToLoad {
3318 program_id: &program_id,
3319 loader: ProgramCacheEntryOwner::LoaderV3,
3320 deployment_slot: 100,
3321 }];
3322 let mut extracted = ProgramCacheForTxBatch::new(200);
3323 cache.extract(&mut search_for, &mut extracted, &other_env, true, true);
3324 assert!(search_for.is_empty());
3325 assert!(Arc::ptr_eq(
3326 extracted.entries.get(&program_id).unwrap(),
3327 &on_other_env
3328 ));
3329 }
3330
3331 #[test]
3332 fn test_extract_unloaded_entry() {
3333 let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
3334 let env = get_mock_program_runtime_environment();
3335 let program_id = Pubkey::new_unique();
3336 let unloaded = new_test_entry_with_owner(
3337 100,
3338 ProgramCacheEntryOwner::LoaderV3,
3339 new_unloaded_entry(env.clone()),
3340 );
3341 cache.assign_program(&env, program_id, 100, Arc::clone(&unloaded));
3342
3343 let mut search_for = vec![ProgramToLoad {
3347 program_id: &program_id,
3348 loader: ProgramCacheEntryOwner::LoaderV3,
3349 deployment_slot: 100,
3350 }];
3351 let mut extracted = ProgramCacheForTxBatch::new(200);
3352 cache.extract(&mut search_for, &mut extracted, &env, true, true);
3353 assert_eq!(search_for.len(), 1);
3354 assert!(extracted.entries.is_empty());
3355
3356 let loaded = new_test_entry_with_owner(
3358 100,
3359 ProgramCacheEntryOwner::LoaderV3,
3360 new_loaded_entry(env.clone()),
3361 );
3362 cache.assign_program(&env, program_id, 100, Arc::clone(&loaded));
3363 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
3364 assert_eq!(slot_versions.len(), 1);
3365 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &loaded));
3366
3367 let mut search_for = vec![ProgramToLoad {
3369 program_id: &program_id,
3370 loader: ProgramCacheEntryOwner::LoaderV3,
3371 deployment_slot: 100,
3372 }];
3373 let mut extracted = ProgramCacheForTxBatch::new(200);
3374 cache.extract(&mut search_for, &mut extracted, &env, true, true);
3375 assert!(search_for.is_empty());
3376 assert!(Arc::ptr_eq(
3377 extracted.entries.get(&program_id).unwrap(),
3378 &loaded
3379 ));
3380 }
3381
3382 #[test_matrix(
3383 (
3384 new_closed_entry,
3385 new_builtin_entry,
3386 new_failed_verification_entry,
3387 new_unloaded_entry,
3388 new_loaded_entry,
3389 ),
3390 (100, 101)
3391 )]
3392 fn test_extract_effective_slot(
3393 new_program: fn(ProgramRuntimeEnvironment) -> ProgramCacheEntryType,
3394 batch_slot: Slot,
3395 ) {
3396 let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
3410 let env = get_mock_program_runtime_environment();
3411 let program_id = Pubkey::new_unique();
3412 let entry = new_test_entry_with_owner(
3413 100,
3414 ProgramCacheEntryOwner::LoaderV3,
3415 new_program(env.clone()),
3416 );
3417 cache.assign_program(&env, program_id, 100, Arc::clone(&entry));
3418
3419 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
3420 assert_eq!(slot_versions.len(), 1);
3421 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &entry));
3422
3423 let mut search_for = vec![ProgramToLoad {
3424 program_id: &program_id,
3425 loader: ProgramCacheEntryOwner::LoaderV3,
3426 deployment_slot: 100,
3427 }];
3428 let mut extracted = ProgramCacheForTxBatch::new(batch_slot);
3429 cache.extract(&mut search_for, &mut extracted, &env, true, true);
3430
3431 if batch_slot < entry.effective_slot() {
3432 assert!(search_for.is_empty());
3436 let tombstone = extracted.entries.get(&program_id).unwrap();
3437 assert_matches!(tombstone.program, ProgramCacheEntryType::DelayVisibility);
3438 assert_eq!(tombstone.account_owner, ProgramCacheEntryOwner::LoaderV3);
3439 assert_eq!(tombstone.deployment_slot, 100);
3440 assert!(Arc::ptr_eq(&tombstone.stats, &entry.stats)); assert_eq!(entry.stats.uses.load(Ordering::Relaxed), 1);
3442
3443 assert_eq!(entry.latest_access_slot.load(Ordering::Relaxed), batch_slot);
3447 assert_eq!(tombstone.latest_access_slot.load(Ordering::Relaxed), 0);
3448 } else if matches!(entry.program, ProgramCacheEntryType::Unloaded(_)) {
3449 assert!(extracted.entries.is_empty());
3453 assert_eq!(search_for.len(), 1);
3454 assert_eq!(entry.stats.uses.load(Ordering::Relaxed), 0);
3455 assert_eq!(entry.latest_access_slot.load(Ordering::Relaxed), 0);
3456 } else {
3457 assert!(search_for.is_empty());
3460 assert!(Arc::ptr_eq(
3461 extracted.entries.get(&program_id).unwrap(),
3462 &entry
3463 ));
3464 assert_eq!(entry.stats.uses.load(Ordering::Relaxed), 1);
3465 assert_eq!(entry.latest_access_slot.load(Ordering::Relaxed), batch_slot);
3466 }
3467 }
3468
3469 #[test]
3470 fn test_extract_closed_entry_matches_any_env() {
3471 let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
3481 let env = get_mock_program_runtime_environment();
3482 let other_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
3483 let program_id = Pubkey::new_unique();
3484 let closed = new_test_entry_with_owner(
3485 100,
3486 ProgramCacheEntryOwner::LoaderV3,
3487 new_closed_entry(env.clone()), );
3489 assert_eq!(closed.effective_slot(), closed.deployment_slot);
3490 cache.assign_program(&env, program_id, 100, Arc::clone(&closed));
3491
3492 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
3493 assert_eq!(slot_versions.len(), 1);
3494 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &closed));
3495
3496 let mut search_for = vec![ProgramToLoad {
3498 program_id: &program_id,
3499 loader: ProgramCacheEntryOwner::LoaderV3,
3500 deployment_slot: 100,
3501 }];
3502 let mut extracted = ProgramCacheForTxBatch::new(100);
3503 cache.extract(&mut search_for, &mut extracted, &env, true, true);
3504 assert!(Arc::ptr_eq(
3505 extracted.entries.get(&program_id).unwrap(),
3506 &closed
3507 ));
3508 assert!(search_for.is_empty());
3509
3510 let mut search_for = vec![ProgramToLoad {
3512 program_id: &program_id,
3513 loader: ProgramCacheEntryOwner::LoaderV3,
3514 deployment_slot: 100,
3515 }];
3516 let mut extracted = ProgramCacheForTxBatch::new(100);
3517 cache.extract(&mut search_for, &mut extracted, &other_env, true, true);
3518 assert!(Arc::ptr_eq(
3519 extracted.entries.get(&program_id).unwrap(),
3520 &closed
3521 ));
3522 assert!(search_for.is_empty());
3523 }
3524
3525 #[test]
3526 fn test_extract_delay_visibility_tombstone_interleaved_environments() {
3527 let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
3550 let env = get_mock_program_runtime_environment();
3551 let upcoming_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
3552 let program_id = Pubkey::new_unique();
3553 let on_execution_env = new_test_entry_with_owner(
3554 100,
3555 ProgramCacheEntryOwner::LoaderV3,
3556 new_loaded_entry(env.clone()),
3557 );
3558 let on_upcoming_env = new_test_entry_with_owner(
3559 100,
3560 ProgramCacheEntryOwner::LoaderV3,
3561 new_loaded_entry(upcoming_env.clone()),
3562 );
3563 cache.assign_program(&env, program_id, 100, Arc::clone(&on_execution_env));
3564 cache.assign_program(&upcoming_env, program_id, 100, Arc::clone(&on_upcoming_env));
3565
3566 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
3567 assert_eq!(slot_versions.len(), 2);
3568 assert!(Arc::ptr_eq(
3569 slot_versions.first().unwrap(),
3570 &on_execution_env
3571 ));
3572 assert!(Arc::ptr_eq(slot_versions.get(1).unwrap(), &on_upcoming_env));
3573
3574 let mut search_for = vec![ProgramToLoad {
3576 program_id: &program_id,
3577 loader: ProgramCacheEntryOwner::LoaderV3,
3578 deployment_slot: 100,
3579 }];
3580 let mut extracted = ProgramCacheForTxBatch::new(100);
3581 cache.extract(&mut search_for, &mut extracted, &env, true, true);
3582 assert!(search_for.is_empty());
3583
3584 let tombstone = extracted.entries.get(&program_id).unwrap();
3586 assert_matches!(tombstone.program, ProgramCacheEntryType::DelayVisibility);
3587
3588 assert!(Arc::ptr_eq(&tombstone.stats, &on_upcoming_env.stats));
3596 assert_eq!(on_upcoming_env.stats.uses.load(Ordering::Relaxed), 1);
3597 assert_eq!(on_execution_env.stats.uses.load(Ordering::Relaxed), 0);
3598
3599 let mut search_for = vec![ProgramToLoad {
3602 program_id: &program_id,
3603 loader: ProgramCacheEntryOwner::LoaderV3,
3604 deployment_slot: 100,
3605 }];
3606 let mut extracted = ProgramCacheForTxBatch::new(101);
3607 cache.extract(&mut search_for, &mut extracted, &env, true, true);
3608 assert!(search_for.is_empty());
3609 assert!(Arc::ptr_eq(
3610 extracted.entries.get(&program_id).unwrap(),
3611 &on_execution_env
3612 ));
3613
3614 assert_eq!(on_upcoming_env.stats.uses.load(Ordering::Relaxed), 1);
3616 assert_eq!(on_execution_env.stats.uses.load(Ordering::Relaxed), 1);
3617 }
3618
3619 #[test_case(false)]
3620 #[test_case(true)]
3621 fn test_extract_environment_filter_same_slot(other_env_first: bool) {
3622 let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
3623 let env = get_mock_program_runtime_environment();
3624 let other_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
3625 let program_id = Pubkey::new_unique();
3626
3627 let on_other_env = new_test_entry_with_owner(
3629 100,
3630 ProgramCacheEntryOwner::LoaderV3,
3631 new_loaded_entry(other_env.clone()),
3632 );
3633 let on_execution_env = new_test_entry_with_owner(
3634 100,
3635 ProgramCacheEntryOwner::LoaderV3,
3636 new_loaded_entry(env.clone()),
3637 );
3638 if other_env_first {
3639 cache.assign_program(&other_env, program_id, 100, Arc::clone(&on_other_env));
3640 cache.assign_program(&env, program_id, 100, Arc::clone(&on_execution_env));
3641 } else {
3642 cache.assign_program(&env, program_id, 100, Arc::clone(&on_execution_env));
3643 cache.assign_program(&other_env, program_id, 100, Arc::clone(&on_other_env));
3644 }
3645
3646 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
3649 assert_eq!(slot_versions.len(), 2);
3650 let (first, second) = if other_env_first {
3651 (&on_other_env, &on_execution_env)
3652 } else {
3653 (&on_execution_env, &on_other_env)
3654 };
3655 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), first));
3656 assert!(Arc::ptr_eq(slot_versions.get(1).unwrap(), second));
3657
3658 let mut search_for = vec![ProgramToLoad {
3661 program_id: &program_id,
3662 loader: ProgramCacheEntryOwner::LoaderV3,
3663 deployment_slot: 100,
3664 }];
3665 let mut extracted = ProgramCacheForTxBatch::new(200);
3666 cache.extract(&mut search_for, &mut extracted, &env, true, true);
3667 assert!(Arc::ptr_eq(
3668 extracted.entries.get(&program_id).unwrap(),
3669 &on_execution_env
3670 ));
3671
3672 let mut search_for = vec![ProgramToLoad {
3674 program_id: &program_id,
3675 loader: ProgramCacheEntryOwner::LoaderV3,
3676 deployment_slot: 100,
3677 }];
3678 let mut extracted = ProgramCacheForTxBatch::new(200);
3679 cache.extract(&mut search_for, &mut extracted, &other_env, true, true);
3680 assert!(Arc::ptr_eq(
3681 extracted.entries.get(&program_id).unwrap(),
3682 &on_other_env
3683 ));
3684 }
3685
3686 #[test_matrix((false, true))]
3687 fn test_extract_usage_counter(increment_usage_counter: bool) {
3688 let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
3689 let env = get_mock_program_runtime_environment();
3690 let program_id = Pubkey::new_unique();
3691 let entry = new_test_entry_with_owner(
3692 100,
3693 ProgramCacheEntryOwner::LoaderV3,
3694 new_loaded_entry(env.clone()),
3695 );
3696 cache.assign_program(&env, program_id, 100, Arc::clone(&entry));
3697
3698 let mut search_for = vec![ProgramToLoad {
3699 program_id: &program_id,
3700 loader: ProgramCacheEntryOwner::LoaderV3,
3701 deployment_slot: 100,
3702 }];
3703 let mut extracted = ProgramCacheForTxBatch::new(200);
3704 cache.extract(
3705 &mut search_for,
3706 &mut extracted,
3707 &env,
3708 increment_usage_counter,
3709 true,
3710 );
3711
3712 assert_eq!(entry.latest_access_slot.load(Ordering::Relaxed), 200);
3714 assert_eq!(
3715 entry.stats.uses.load(Ordering::Relaxed),
3716 u64::from(increment_usage_counter)
3717 );
3718 }
3719
3720 #[test]
3721 fn test_extract_usage_counter_delayed_visibility() {
3722 let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
3723 let env = get_mock_program_runtime_environment();
3724 let program_id = Pubkey::new_unique();
3725 let entry = new_test_entry_with_owner(
3726 100,
3727 ProgramCacheEntryOwner::LoaderV3,
3728 new_loaded_entry(env.clone()),
3729 );
3730 cache.assign_program(&env, program_id, 100, Arc::clone(&entry));
3731
3732 let mut search_for = vec![ProgramToLoad {
3736 program_id: &program_id,
3737 loader: ProgramCacheEntryOwner::LoaderV3,
3738 deployment_slot: 100,
3739 }];
3740 let mut extracted = ProgramCacheForTxBatch::new(100);
3741 cache.extract(&mut search_for, &mut extracted, &env, true, true);
3742
3743 let tombstone = extracted.entries.get(&program_id).unwrap();
3744 assert!(matches!(
3745 tombstone.program,
3746 ProgramCacheEntryType::DelayVisibility
3747 ));
3748 assert!(!Arc::ptr_eq(tombstone, &entry));
3749
3750 assert_eq!(entry.latest_access_slot.load(Ordering::Relaxed), 100);
3753 assert_eq!(tombstone.latest_access_slot.load(Ordering::Relaxed), 0);
3754
3755 assert!(Arc::ptr_eq(&tombstone.stats, &entry.stats));
3758 assert_eq!(entry.stats.uses.load(Ordering::Relaxed), 1);
3759 }
3760
3761 #[test_matrix((false, true))]
3762 fn test_extract_hits_and_misses(count_hits_and_misses: bool) {
3763 let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
3764 let env = get_mock_program_runtime_environment();
3765 let found = Pubkey::new_unique();
3766 let missing = Pubkey::new_unique();
3767 cache.assign_program(
3768 &env,
3769 found,
3770 100,
3771 new_test_entry_with_owner(
3772 100,
3773 ProgramCacheEntryOwner::LoaderV3,
3774 new_loaded_entry(env.clone()),
3775 ),
3776 );
3777
3778 let mut search_for = vec![
3779 ProgramToLoad {
3780 program_id: &found,
3781 loader: ProgramCacheEntryOwner::LoaderV3,
3782 deployment_slot: 100,
3783 },
3784 ProgramToLoad {
3785 program_id: &missing,
3786 loader: ProgramCacheEntryOwner::LoaderV3,
3787 deployment_slot: 0,
3788 },
3789 ];
3790 let mut extracted = ProgramCacheForTxBatch::new(200);
3791 cache.extract(
3792 &mut search_for,
3793 &mut extracted,
3794 &env,
3795 true,
3796 count_hits_and_misses,
3797 );
3798
3799 let expected = u64::from(count_hits_and_misses);
3800 assert_eq!(cache.stats.hits.load(Ordering::Relaxed), expected);
3801 assert_eq!(cache.stats.misses.load(Ordering::Relaxed), expected);
3802 }
3803
3804 #[test]
3805 fn test_extract_hits_count_only_this_call() {
3806 let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
3807 let env = get_mock_program_runtime_environment();
3808 let program_id = Pubkey::new_unique();
3809 cache.assign_program(
3810 &env,
3811 program_id,
3812 100,
3813 new_test_entry_with_owner(
3814 100,
3815 ProgramCacheEntryOwner::LoaderV3,
3816 new_loaded_entry(env.clone()),
3817 ),
3818 );
3819
3820 let mut extracted = ProgramCacheForTxBatch::new(200);
3823 extracted.replenish(Pubkey::new_unique(), new_test_builtin_entry(0));
3824
3825 let mut search_for = vec![ProgramToLoad {
3828 program_id: &program_id,
3829 loader: ProgramCacheEntryOwner::LoaderV3,
3830 deployment_slot: 100,
3831 }];
3832 cache.extract(&mut search_for, &mut extracted, &env, true, true);
3833 assert_eq!(extracted.entries.len(), 2);
3834 assert_eq!(cache.stats.hits.load(Ordering::Relaxed), 1);
3835 }
3836
3837 #[test]
3838 fn test_extract_cooperative_loading_task() {
3839 let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
3840 let env = get_mock_program_runtime_environment();
3841 let program_ids = [Pubkey::new_unique(), Pubkey::new_unique()];
3842
3843 let mut search_for = program_ids
3845 .iter()
3846 .map(|program_id| ProgramToLoad {
3847 program_id,
3848 loader: ProgramCacheEntryOwner::LoaderV3,
3849 deployment_slot: 0,
3850 })
3851 .collect();
3852 let mut extracted = ProgramCacheForTxBatch::new(100);
3853 let task = cache.extract(&mut search_for, &mut extracted, &env, true, true);
3854 assert_eq!(search_for.len(), 2);
3855 assert_eq!(task, program_ids.first().copied());
3856 match &cache.index {
3857 IndexImplementation::V1 {
3858 loading_entries, ..
3859 } => {
3860 let loading_entries = loading_entries.lock().unwrap();
3861 assert_eq!(loading_entries.len(), 1);
3862 assert_eq!(
3863 loading_entries.get(program_ids.first().unwrap()),
3864 Some(&(100, thread::current().id()))
3865 );
3866 }
3867 }
3868
3869 let mut search_for = vec![ProgramToLoad {
3871 program_id: program_ids.first().unwrap(),
3872 loader: ProgramCacheEntryOwner::LoaderV3,
3873 deployment_slot: 0,
3874 }];
3875 let task = cache.extract(&mut search_for, &mut extracted, &env, true, true);
3876 assert_eq!(search_for.len(), 1);
3877 assert_eq!(task, None);
3878
3879 let cookie = cache.loading_task_waiter.cookie();
3881 let loaded = new_test_entry_with_owner(
3882 50,
3883 ProgramCacheEntryOwner::LoaderV3,
3884 new_loaded_entry(env.clone()),
3885 );
3886 cache.finish_cooperative_loading_task(
3887 &env,
3888 100,
3889 *program_ids.first().unwrap(),
3890 Arc::clone(&loaded),
3891 );
3892 assert_ne!(cache.loading_task_waiter.wait(cookie), cookie);
3893
3894 match &cache.index {
3896 IndexImplementation::V1 {
3897 loading_entries, ..
3898 } => assert!(loading_entries.lock().unwrap().is_empty()),
3899 }
3900 let mut search_for = vec![ProgramToLoad {
3901 program_id: program_ids.first().unwrap(),
3902 loader: ProgramCacheEntryOwner::LoaderV3,
3903 deployment_slot: 50,
3904 }];
3905 let mut extracted = ProgramCacheForTxBatch::new(100);
3906 let task = cache.extract(&mut search_for, &mut extracted, &env, true, true);
3907 assert!(search_for.is_empty());
3908 assert_eq!(task, None);
3909 assert!(Arc::ptr_eq(
3910 extracted.entries.get(program_ids.first().unwrap()).unwrap(),
3911 &loaded
3912 ));
3913 }
3914
3915 #[test]
3916 fn test_extract_cooperative_loading_task_ordering() {
3917 let (cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
3918 let env = get_mock_program_runtime_environment();
3919 let program_ids = [Pubkey::new_unique(), Pubkey::new_unique()];
3920 let mut extracted = ProgramCacheForTxBatch::new(100);
3921
3922 let mut search_for = program_ids
3924 .iter()
3925 .map(|program_id| ProgramToLoad {
3926 program_id,
3927 loader: ProgramCacheEntryOwner::LoaderV3,
3928 deployment_slot: 0,
3929 })
3930 .collect();
3931 let task = cache.extract(&mut search_for, &mut extracted, &env, true, true);
3932 assert_eq!(task, program_ids.first().copied());
3933
3934 let mut search_for = program_ids
3937 .iter()
3938 .rev()
3939 .map(|program_id| ProgramToLoad {
3940 program_id,
3941 loader: ProgramCacheEntryOwner::LoaderV3,
3942 deployment_slot: 0,
3943 })
3944 .collect();
3945 let task = cache.extract(&mut search_for, &mut extracted, &env, true, true);
3946 assert_eq!(task, program_ids.get(1).copied());
3947
3948 match &cache.index {
3950 IndexImplementation::V1 {
3951 loading_entries, ..
3952 } => {
3953 let loading_entries = loading_entries.lock().unwrap();
3954 assert_eq!(loading_entries.len(), 2);
3955 for program_id in &program_ids {
3956 assert_eq!(
3957 loading_entries.get(program_id),
3958 Some(&(100, thread::current().id()))
3959 );
3960 }
3961 }
3962 }
3963
3964 let mut search_for = program_ids
3966 .iter()
3967 .map(|program_id| ProgramToLoad {
3968 program_id,
3969 loader: ProgramCacheEntryOwner::LoaderV3,
3970 deployment_slot: 0,
3971 })
3972 .collect();
3973 let task = cache.extract(&mut search_for, &mut extracted, &env, true, true);
3974 assert_eq!(search_for.len(), 2);
3975 assert_eq!(task, None);
3976 }
3977
3978 #[test]
3979 fn test_extract_entry_not_in_same_branch() {
3980 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
3987 let mut fork_graph = TestForkGraphSpecific::default();
3988 fork_graph.insert_fork(&[0, 50, 200]);
3989 fork_graph.insert_fork(&[0, 100]);
3990 let fork_graph = Arc::new(RwLock::new(fork_graph));
3991 cache.set_fork_graph(Arc::downgrade(&fork_graph));
3992
3993 let env = get_mock_program_runtime_environment();
3994 let program_id = Pubkey::new_unique();
3995 let on_other_fork = new_test_entry_with_owner(
3996 100,
3997 ProgramCacheEntryOwner::LoaderV3,
3998 new_loaded_entry(env.clone()),
3999 );
4000 cache.assign_program(&env, program_id, 100, Arc::clone(&on_other_fork));
4001
4002 let mut search_for = vec![ProgramToLoad {
4012 program_id: &program_id,
4013 loader: ProgramCacheEntryOwner::LoaderV3,
4014 deployment_slot: 100,
4015 }];
4016 let mut extracted = ProgramCacheForTxBatch::new(200);
4017 cache.extract(&mut search_for, &mut extracted, &env, true, true);
4018 assert_eq!(search_for.len(), 1);
4019 assert!(extracted.entries.is_empty());
4020
4021 let on_same_fork = new_test_entry_with_owner(
4023 50,
4024 ProgramCacheEntryOwner::LoaderV3,
4025 new_loaded_entry(env.clone()),
4026 );
4027 cache.assign_program(&env, program_id, 50, Arc::clone(&on_same_fork));
4028
4029 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
4031 assert_eq!(slot_versions.len(), 2);
4032 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &on_same_fork));
4033 assert!(Arc::ptr_eq(slot_versions.get(1).unwrap(), &on_other_fork));
4034
4035 let mut search_for = vec![ProgramToLoad {
4038 program_id: &program_id,
4039 loader: ProgramCacheEntryOwner::LoaderV3,
4040 deployment_slot: 50,
4041 }];
4042 let mut extracted = ProgramCacheForTxBatch::new(200);
4043 cache.extract(&mut search_for, &mut extracted, &env, true, true);
4044 assert!(search_for.is_empty());
4045 assert!(Arc::ptr_eq(
4046 extracted.entries.get(&program_id).unwrap(),
4047 &on_same_fork
4048 ));
4049 }
4050
4051 #[test]
4052 fn test_extract_deployment_slot_mismatch() {
4053 let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
4069 assert_eq!(cache.latest_root_slot, 0);
4070 let env = get_mock_program_runtime_environment();
4071 let program_id = Pubkey::new_unique();
4072 let deployed_at_100 = new_test_entry_with_owner(
4073 100,
4074 ProgramCacheEntryOwner::LoaderV3,
4075 new_loaded_entry(env.clone()),
4076 );
4077 cache.assign_program(&env, program_id, 100, Arc::clone(&deployed_at_100));
4078
4079 let mut search_for = vec![ProgramToLoad {
4083 program_id: &program_id,
4084 loader: ProgramCacheEntryOwner::LoaderV3,
4085 deployment_slot: 150,
4086 }];
4087 let mut extracted = ProgramCacheForTxBatch::new(200);
4088 cache.extract(&mut search_for, &mut extracted, &env, true, true);
4089 assert_eq!(search_for.len(), 1);
4090 assert!(extracted.entries.is_empty());
4091
4092 let deployed_at_150 = new_test_entry_with_owner(
4094 150,
4095 ProgramCacheEntryOwner::LoaderV3,
4096 new_loaded_entry(env.clone()),
4097 );
4098 cache.assign_program(&env, program_id, 150, Arc::clone(&deployed_at_150));
4099
4100 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
4103 assert_eq!(slot_versions.len(), 2);
4104 assert!(Arc::ptr_eq(
4105 slot_versions.first().unwrap(),
4106 &deployed_at_100
4107 ));
4108 assert!(Arc::ptr_eq(slot_versions.get(1).unwrap(), &deployed_at_150));
4109
4110 let mut search_for = vec![ProgramToLoad {
4112 program_id: &program_id,
4113 loader: ProgramCacheEntryOwner::LoaderV3,
4114 deployment_slot: 150,
4115 }];
4116 let mut extracted = ProgramCacheForTxBatch::new(200);
4117 cache.extract(&mut search_for, &mut extracted, &env, true, true);
4118 assert!(search_for.is_empty());
4119 assert!(Arc::ptr_eq(
4120 extracted.entries.get(&program_id).unwrap(),
4121 &deployed_at_150
4122 ));
4123 }
4124
4125 #[test]
4126 fn test_extract_older_entry_on_the_callers_fork() {
4127 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
4134 let mut fork_graph = TestForkGraphSpecific::default();
4135 fork_graph.insert_fork(&[0, 50, 200]);
4136 fork_graph.insert_fork(&[0, 150]);
4137 let fork_graph = Arc::new(RwLock::new(fork_graph));
4138 cache.set_fork_graph(Arc::downgrade(&fork_graph));
4139
4140 let env = get_mock_program_runtime_environment();
4141 let program_id = Pubkey::new_unique();
4142 let on_other_fork = new_test_entry_with_owner(
4143 150,
4144 ProgramCacheEntryOwner::LoaderV3,
4145 new_loaded_entry(env.clone()),
4146 );
4147 let on_same_fork = new_test_entry_with_owner(
4148 50,
4149 ProgramCacheEntryOwner::LoaderV3,
4150 new_loaded_entry(env.clone()),
4151 );
4152 cache.assign_program(&env, program_id, 150, Arc::clone(&on_other_fork));
4153 cache.assign_program(&env, program_id, 50, Arc::clone(&on_same_fork));
4154
4155 let mut search_for = vec![ProgramToLoad {
4159 program_id: &program_id,
4160 loader: ProgramCacheEntryOwner::LoaderV3,
4161 deployment_slot: 50,
4162 }];
4163 let mut extracted = ProgramCacheForTxBatch::new(200);
4164 cache.extract(&mut search_for, &mut extracted, &env, true, true);
4165 assert!(Arc::ptr_eq(
4166 extracted.entries.get(&program_id).unwrap(),
4167 &on_same_fork
4168 ));
4169
4170 let mut search_for = vec![ProgramToLoad {
4182 program_id: &program_id,
4183 loader: ProgramCacheEntryOwner::LoaderV3,
4184 deployment_slot: 150,
4185 }];
4186 let mut extracted = ProgramCacheForTxBatch::new(200);
4187 cache.extract(&mut search_for, &mut extracted, &env, true, true);
4188 assert_eq!(search_for.len(), 1);
4189 assert!(extracted.entries.is_empty());
4190 }
4191
4192 #[test]
4193 fn test_extract_below_deployment_slot() {
4194 let (mut cache, fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
4195 let env = get_mock_program_runtime_environment();
4196 let program_id = Pubkey::new_unique();
4197 let entry = new_test_entry_with_owner(
4198 100,
4199 ProgramCacheEntryOwner::LoaderV3,
4200 new_loaded_entry(env.clone()),
4201 );
4202 cache.assign_program(&env, program_id, 100, Arc::clone(&entry));
4203
4204 cache.prune(200, None, &fork_graph.read().unwrap());
4207 assert_eq!(cache.latest_root_slot, 200);
4208
4209 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
4211 assert_eq!(slot_versions.len(), 1);
4212 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &entry));
4213
4214 fork_graph.write().unwrap().relation = BlockRelation::Unknown;
4218
4219 let mut search_for = vec![ProgramToLoad {
4222 program_id: &program_id,
4223 loader: ProgramCacheEntryOwner::LoaderV3,
4224 deployment_slot: 0,
4225 }];
4226 let mut extracted = ProgramCacheForTxBatch::new(50);
4227 cache.extract(&mut search_for, &mut extracted, &env, true, true);
4228 assert_eq!(search_for.len(), 1);
4229 assert!(extracted.entries.is_empty());
4230 }
4231
4232 #[test]
4233 fn test_extract_entry_older_than_root() {
4234 let (mut cache, fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
4237 let env = get_mock_program_runtime_environment();
4238 let program_id = Pubkey::new_unique();
4239 let entry = new_test_entry_with_owner(
4240 100,
4241 ProgramCacheEntryOwner::LoaderV3,
4242 new_loaded_entry(env.clone()),
4243 );
4244 cache.assign_program(&env, program_id, 100, Arc::clone(&entry));
4245
4246 cache.prune(200, None, &fork_graph.read().unwrap());
4249 assert_eq!(cache.latest_root_slot, 200);
4250
4251 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
4253 assert_eq!(slot_versions.len(), 1);
4254 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &entry));
4255
4256 fork_graph.write().unwrap().relation = BlockRelation::Unknown;
4260
4261 let mut search_for = vec![ProgramToLoad {
4266 program_id: &program_id,
4267 loader: ProgramCacheEntryOwner::LoaderV3,
4268 deployment_slot: 100,
4269 }];
4270 let mut extracted = ProgramCacheForTxBatch::new(300);
4271 cache.extract(&mut search_for, &mut extracted, &env, true, true);
4272 assert!(search_for.is_empty());
4273 assert!(Arc::ptr_eq(
4274 extracted.entries.get(&program_id).unwrap(),
4275 &entry
4276 ));
4277 }
4278
4279 #[test]
4280 fn test_unloaded() {
4281 let mut cache = ProgramCache::<TestForkGraph>::new(0);
4282 let env = get_mock_program_runtime_environment();
4283 for program_cache_entry_type in [
4284 ProgramCacheEntryType::Closed,
4285 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
4286 ] {
4287 let entry = Arc::new(ProgramCacheEntry {
4288 program: program_cache_entry_type,
4289 account_owner: ProgramCacheEntryOwner::LoaderV2,
4290 deployment_slot: 0,
4291 stats: Arc::default(),
4292 latest_access_slot: AtomicU64::default(),
4293 });
4294 assert!(entry.to_unloaded().is_none());
4295
4296 let program_id = Pubkey::new_unique();
4298 cache.assign_program(&env, program_id, entry.deployment_slot, entry.clone());
4299 cache.unload_program_entry(program_id, &entry);
4300 assert_eq!(cache.get_slot_versions_for_tests(&program_id).len(), 1);
4301 assert!(cache.stats.evictions.is_empty());
4302 }
4303
4304 let stats = ProgramStatistics {
4305 uses: 3.into(),
4306 ..Default::default()
4307 };
4308 let entry = new_test_entry_with_usage(1, stats);
4309 let unloaded_entry = entry.to_unloaded().unwrap();
4310 assert_eq!(unloaded_entry.deployment_slot, 1);
4311 assert_eq!(unloaded_entry.effective_slot(), 2);
4312 assert_eq!(unloaded_entry.latest_access_slot.load(Ordering::Relaxed), 1);
4313 assert_eq!(unloaded_entry.stats.uses.load(Ordering::Relaxed), 3);
4314
4315 let program_id = Pubkey::new_unique();
4317 cache.assign_program(&env, program_id, entry.deployment_slot, entry.clone());
4318 cache.unload_program_entry(program_id, &entry);
4319 assert!(cache.stats.evictions.contains_key(&program_id));
4320 }
4321
4322 #[test]
4323 fn test_fork_prune_find_first_ancestor() {
4324 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
4325 let env = get_mock_program_runtime_environment();
4326
4327 let mut fork_graph = TestForkGraphSpecific::default();
4338 fork_graph.insert_fork(&[0, 10, 20]);
4339 fork_graph.insert_fork(&[0, 5]);
4340 let fork_graph = Arc::new(RwLock::new(fork_graph));
4341 cache.set_fork_graph(Arc::downgrade(&fork_graph));
4342
4343 let program1 = Pubkey::new_unique();
4344 cache.assign_program(&env, program1, 0, new_test_entry(0));
4345 cache.assign_program(&env, program1, 5, new_test_entry(5));
4346
4347 cache.prune(10, None, &fork_graph.read().unwrap());
4348
4349 let keys = &[program1];
4350 let mut missing = get_entries_to_load(&cache, 20, keys);
4351 let mut extracted = ProgramCacheForTxBatch::new(20);
4352 cache.extract(&mut missing, &mut extracted, &env, true, true);
4353
4354 assert_eq!(
4356 extracted
4357 .find(&program1)
4358 .expect("Did not find the program")
4359 .deployment_slot,
4360 0
4361 );
4362 }
4363
4364 #[test]
4365 fn test_prune_by_deployment_slot() {
4366 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
4367 let env = get_mock_program_runtime_environment();
4368
4369 let mut fork_graph = TestForkGraphSpecific::default();
4380 fork_graph.insert_fork(&[0, 10, 20]);
4381 fork_graph.insert_fork(&[0, 5, 6]);
4382 let fork_graph = Arc::new(RwLock::new(fork_graph));
4383 cache.set_fork_graph(Arc::downgrade(&fork_graph));
4384
4385 let program1 = Pubkey::new_unique();
4386 cache.assign_program(&env, program1, 0, new_test_entry(0));
4387 cache.assign_program(&env, program1, 5, new_test_entry(5));
4388
4389 let program2 = Pubkey::new_unique();
4390 cache.assign_program(&env, program2, 10, new_test_entry(10));
4391
4392 let keys = &[program1, program2];
4393 let mut missing = get_entries_to_load(&cache, 20, keys);
4394 let mut extracted = ProgramCacheForTxBatch::new(20);
4395 cache.extract(&mut missing, &mut extracted, &env, true, true);
4396 assert!(match_slot(&extracted, &program1, 0, 20));
4397 assert!(match_slot(&extracted, &program2, 10, 20));
4398
4399 let mut missing = get_entries_to_load(&cache, 6, keys);
4400 assert!(match_missing(&missing, &program2, false));
4401 let mut extracted = ProgramCacheForTxBatch::new(6);
4402 cache.extract(&mut missing, &mut extracted, &env, true, true);
4403 assert!(match_slot(&extracted, &program1, 5, 6));
4404
4405 cache.prune_by_deployment_slot(5);
4408
4409 let mut missing = get_entries_to_load(&cache, 20, keys);
4410 let mut extracted = ProgramCacheForTxBatch::new(20);
4411 cache.extract(&mut missing, &mut extracted, &env, true, true);
4412 assert!(match_slot(&extracted, &program1, 0, 20));
4413 assert!(match_slot(&extracted, &program2, 10, 20));
4414
4415 let mut missing = get_entries_to_load(&cache, 6, keys);
4416 assert!(match_missing(&missing, &program2, false));
4417 let mut extracted = ProgramCacheForTxBatch::new(6);
4418 cache.extract(&mut missing, &mut extracted, &env, true, true);
4419 assert!(match_slot(&extracted, &program1, 0, 6));
4420
4421 cache.prune_by_deployment_slot(10);
4424
4425 let mut missing = get_entries_to_load(&cache, 20, keys);
4426 assert!(match_missing(&missing, &program2, false));
4427 let mut extracted = ProgramCacheForTxBatch::new(20);
4428 cache.extract(&mut missing, &mut extracted, &env, true, true);
4429 assert!(match_slot(&extracted, &program1, 0, 20));
4430 }
4431}