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>,
164 pub programs_to_recompile: Vec<(Pubkey, Arc<ProgramCacheEntry>)>,
166}
167
168impl EpochBoundaryPreparation {
169 pub fn new(epoch: Epoch) -> Self {
170 Self {
171 upcoming_epoch: epoch,
172 upcoming_environment: None,
173 programs_to_recompile: Vec::default(),
174 }
175 }
176
177 pub fn get_upcoming_environment_for_epoch(
179 &self,
180 epoch: Epoch,
181 ) -> Option<ProgramRuntimeEnvironment> {
182 if epoch == self.upcoming_epoch {
183 return self.upcoming_environment.clone();
184 }
185 None
186 }
187
188 pub fn reroot(&mut self, epoch: Epoch) -> Option<ProgramRuntimeEnvironment> {
190 if epoch == self.upcoming_epoch
191 && let Some(upcoming_environment) = self.upcoming_environment.take()
192 {
193 self.programs_to_recompile.clear();
194 return Some(upcoming_environment);
195 }
196
197 None
198 }
199}
200
201#[derive(Clone, PartialEq, Debug)]
203pub struct ProgramToLoad<'a> {
204 pub program_id: &'a Pubkey,
206 pub loader: ProgramCacheEntryOwner,
208 pub deployment_slot: Slot,
213}
214
215#[derive(Debug)]
216pub(crate) enum IndexImplementation {
217 V1 {
219 entries: HashMap<Pubkey, Vec<Arc<ProgramCacheEntry>>>,
225 loading_entries: Mutex<HashMap<Pubkey, (Slot, thread::ThreadId)>>,
234 },
235}
236
237pub struct ProgramCache<FG: ForkGraph> {
253 pub(crate) index: IndexImplementation,
255 pub latest_root_slot: Slot,
257 pub stats: ProgramCacheStats,
259 pub fork_graph: Option<Weak<RwLock<FG>>>,
261 pub loading_task_waiter: Arc<LoadingTaskWaiter>,
263}
264
265impl<FG: ForkGraph> std::fmt::Debug for ProgramCache<FG> {
266 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
267 f.debug_struct("ProgramCache")
268 .field("root slot", &self.latest_root_slot)
269 .field("stats", &self.stats)
270 .field("index", &self.index)
271 .finish()
272 }
273}
274
275#[derive(Clone, Debug, Default)]
281pub struct ProgramCacheForTxBatch {
282 entries: HashMap<Pubkey, Arc<ProgramCacheEntry>>,
285 modified_entries: HashMap<Pubkey, Arc<ProgramCacheEntry>>,
287 slot: Slot,
288 pub hit_max_limit: bool,
289 pub loaded_missing: bool,
290 pub merged_modified: bool,
291}
292
293impl ProgramCacheForTxBatch {
294 pub fn new(slot: Slot) -> Self {
295 Self {
296 entries: HashMap::new(),
297 modified_entries: HashMap::new(),
298 slot,
299 hit_max_limit: false,
300 loaded_missing: false,
301 merged_modified: false,
302 }
303 }
304
305 pub fn replenish(
311 &mut self,
312 key: Pubkey,
313 entry: Arc<ProgramCacheEntry>,
314 ) -> (bool, Arc<ProgramCacheEntry>) {
315 (self.entries.insert(key, entry.clone()).is_some(), entry)
316 }
317
318 pub fn store_modified_entry(&mut self, key: Pubkey, entry: Arc<ProgramCacheEntry>) {
321 self.modified_entries.insert(key, entry);
322 }
323
324 pub fn drain_modified_entries(&mut self) -> HashMap<Pubkey, Arc<ProgramCacheEntry>> {
327 std::mem::take(&mut self.modified_entries)
328 }
329
330 pub fn find(&self, key: &Pubkey) -> Option<Arc<ProgramCacheEntry>> {
331 self.modified_entries
335 .get(key)
336 .or_else(|| self.entries.get(key))
337 .map(|entry| {
338 if entry.is_implicit_delay_visibility_tombstone(self.slot) {
339 Arc::new(ProgramCacheEntry::new_delay_visibility_tombstone(
343 entry.deployment_slot,
344 entry.account_owner,
345 Arc::clone(&entry.stats),
346 ))
347 } else {
348 entry.clone()
349 }
350 })
351 }
352
353 pub fn slot(&self) -> Slot {
354 self.slot
355 }
356
357 pub fn get_entry_for_tests(&self, key: &Pubkey) -> Option<&Arc<ProgramCacheEntry>> {
360 self.entries.get(key)
361 }
362
363 pub fn set_slot_for_tests(&mut self, slot: Slot) {
364 self.slot = slot;
365 }
366
367 pub fn merge(&mut self, modified_entries: &HashMap<Pubkey, Arc<ProgramCacheEntry>>) {
368 modified_entries.iter().for_each(|(key, entry)| {
369 self.merged_modified = true;
370 self.replenish(*key, entry.clone());
371 })
372 }
373
374 pub fn remove_entry(&mut self, key: &Pubkey) {
377 self.entries.remove(key);
378 }
379
380 pub fn is_empty(&self) -> bool {
381 self.entries.is_empty()
382 }
383}
384
385impl<FG: ForkGraph> ProgramCache<FG> {
386 pub fn new(root_slot: Slot) -> Self {
387 Self {
388 index: IndexImplementation::V1 {
389 entries: HashMap::new(),
390 loading_entries: Mutex::new(HashMap::new()),
391 },
392 latest_root_slot: root_slot,
393 stats: ProgramCacheStats::default(),
394 fork_graph: None,
395 loading_task_waiter: Arc::new(LoadingTaskWaiter::default()),
396 }
397 }
398
399 pub fn set_fork_graph(&mut self, fork_graph: Weak<RwLock<FG>>) {
400 self.fork_graph = Some(fork_graph);
401 }
402
403 pub fn assign_program(
406 &mut self,
407 program_runtime_environment: &ProgramRuntimeEnvironment,
408 key: Pubkey,
409 _current_slot: Slot,
410 entry: Arc<ProgramCacheEntry>,
411 ) -> bool {
412 debug_assert!(
413 !matches!(&entry.program, ProgramCacheEntryType::DelayVisibility),
414 "Unexpected assignment of a DelayVisibility tombstone"
415 );
416 fn is_current_env(
420 program_runtime_environment: &ProgramRuntimeEnvironment,
421 env_opt: Option<&ProgramRuntimeEnvironment>,
422 ) -> bool {
423 env_opt
424 .map(|env| env == program_runtime_environment)
425 .unwrap_or(true)
426 }
427 match &mut self.index {
428 IndexImplementation::V1 { entries, .. } => {
429 let slot_versions = &mut entries.entry(key).or_default();
430 let insertion_point = slot_versions.binary_search_by(|at| {
431 at.deployment_slot
432 .cmp(&entry.deployment_slot)
433 .then(at.account_owner.cmp(&entry.account_owner))
434 .then(
435 is_current_env(
439 program_runtime_environment,
440 at.program.get_environment(),
441 )
442 .cmp(&is_current_env(
443 program_runtime_environment,
444 entry.program.get_environment(),
445 )),
446 )
447 });
448 match insertion_point {
449 Ok(index) => {
450 let existing = slot_versions.get_mut(index).unwrap();
451 match (&existing.program, &entry.program) {
452 (
453 ProgramCacheEntryType::Builtin(_),
454 ProgramCacheEntryType::Builtin(_),
455 )
456 | (ProgramCacheEntryType::Closed, ProgramCacheEntryType::Unloaded(_))
457 | (
458 ProgramCacheEntryType::Unloaded(_),
459 ProgramCacheEntryType::Loaded(_),
460 )
461 | (
462 ProgramCacheEntryType::Unloaded(_),
463 ProgramCacheEntryType::FailedVerification(_),
464 ) => {}
465 _ => {
466 error!(
468 "ProgramCache::assign_program() failed key={key:?} \
469 existing={slot_versions:?} entry={entry:?}"
470 );
471 debug_assert!(false, "Unexpected replacement of an entry");
472 self.stats.replacements.fetch_add(1, Ordering::Relaxed);
473 return true;
474 }
475 }
476 entry.stats.merge_from(&existing.stats);
477 *existing = Arc::clone(&entry);
478 self.stats.reloads.fetch_add(1, Ordering::Relaxed);
479 }
480 Err(index) => {
481 self.stats.insertions.fetch_add(1, Ordering::Relaxed);
482 slot_versions.insert(index, Arc::clone(&entry));
483 }
484 }
485 slot_versions.retain(|existing| {
489 existing.deployment_slot != entry.deployment_slot
490 || existing
491 .program
492 .get_environment()
493 .zip(entry.program.get_environment())
494 .map(|(a, b)| a != b)
495 .unwrap_or(false)
496 || Arc::ptr_eq(existing, &entry)
497 });
498 }
499 }
500 false
501 }
502
503 pub fn prune_by_deployment_slot(&mut self, slot: Slot) {
504 match &mut self.index {
505 IndexImplementation::V1 { entries, .. } => {
506 for second_level in entries.values_mut() {
507 second_level.retain(|entry| entry.deployment_slot != slot);
508 }
509 self.remove_programs_with_no_entries();
510 }
511 }
512 }
513
514 pub fn prune(
516 &mut self,
517 new_root_slot: Slot,
518 new_environment: Option<ProgramRuntimeEnvironment>,
519 fork_graph: &FG,
520 ) {
521 match &mut self.index {
522 IndexImplementation::V1 { entries, .. } => {
523 let tombstone_slot_cutoff =
524 new_root_slot.saturating_sub(MAX_TOMBSTONE_AGE_IN_SLOTS);
525 entries.retain(|_id, second_level| {
526 if let [candidate] = &second_level[..]
528 && (matches!(candidate.program, ProgramCacheEntryType::Unloaded(_))
529 || candidate.is_tombstone())
530 && candidate.deployment_slot <= self.latest_root_slot
531 && candidate.latest_access_slot.load(Ordering::Relaxed)
532 < tombstone_slot_cutoff
533 {
534 self.stats.prunes_stale.fetch_add(1, Ordering::Relaxed);
535 return false;
536 }
537 let mut first_ancestor_found = false;
539 let mut first_ancestor_env = None;
540 *second_level = second_level
541 .iter()
542 .rev()
543 .filter(|entry| {
544 let relation =
545 fork_graph.relationship(entry.deployment_slot, new_root_slot);
546 if entry.deployment_slot >= new_root_slot {
547 let keep = matches!(
548 relation,
549 BlockRelation::Equal | BlockRelation::Descendant
550 );
551 if !keep {
552 self.stats.prunes_orphan.fetch_add(1, Ordering::Relaxed);
553 }
554 keep
555 } else if matches!(relation, BlockRelation::Ancestor)
556 || entry.deployment_slot <= self.latest_root_slot
557 {
558 if !first_ancestor_found {
559 first_ancestor_found = true;
560 first_ancestor_env = entry.program.get_environment();
561 return true;
562 }
563 if let Some(entry_env) = entry.program.get_environment()
571 && let Some(env) = first_ancestor_env
572 && entry_env != env
573 {
574 return true;
575 }
576 self.stats.prunes_orphan.fetch_add(1, Ordering::Relaxed);
577 false
578 } else {
579 self.stats.prunes_orphan.fetch_add(1, Ordering::Relaxed);
580 false
581 }
582 })
583 .filter(|entry| {
584 if let Some(new_environment) = new_environment.as_ref()
586 && !Self::matches_environment(entry, new_environment)
587 {
588 self.stats
589 .prunes_environment
590 .fetch_add(1, Ordering::Relaxed);
591 return false;
592 }
593 true
594 })
595 .cloned()
596 .collect();
597 second_level.reverse();
598 true
599 });
600 }
601 }
602 self.remove_programs_with_no_entries();
603 debug_assert!(self.latest_root_slot <= new_root_slot);
604 self.latest_root_slot = new_root_slot;
605 }
606
607 fn matches_environment(
608 entry: &Arc<ProgramCacheEntry>,
609 program_runtime_environment: &ProgramRuntimeEnvironment,
610 ) -> bool {
611 let Some(environment) = entry.program.get_environment() else {
612 return true;
613 };
614 environment == program_runtime_environment
615 }
616
617 pub fn extract(
620 &self,
621 search_for: &mut Vec<ProgramToLoad>,
622 loaded_programs_for_tx_batch: &mut ProgramCacheForTxBatch,
623 program_runtime_environment_for_execution: &ProgramRuntimeEnvironment,
624 increment_usage_counter: bool,
625 count_hits_and_misses: bool,
626 ) -> Option<Pubkey> {
627 debug_assert!(self.fork_graph.is_some());
628 let fork_graph = self.fork_graph.as_ref().unwrap().upgrade().unwrap();
629 let locked_fork_graph = fork_graph.read().unwrap();
630 let entries_in_batch = loaded_programs_for_tx_batch.entries.len();
631 let mut cooperative_loading_task = None;
632 match &self.index {
633 IndexImplementation::V1 {
634 entries,
635 loading_entries,
636 } => {
637 search_for.retain(|program_to_load| {
638 if let Some(second_level) = entries.get(program_to_load.program_id) {
639 for entry in second_level.iter().rev() {
640 if program_to_load.deployment_slot != entry.deployment_slot
643 || program_to_load.loader != entry.account_owner
644 {
645 continue;
646 }
647
648 let entry_in_same_branch = entry.deployment_slot
654 <= self.latest_root_slot
655 || matches!(
656 locked_fork_graph.relationship(
657 entry.deployment_slot,
658 loaded_programs_for_tx_batch.slot
659 ),
660 BlockRelation::Equal | BlockRelation::Ancestor
661 );
662 if entry_in_same_branch {
663 let entry_is_effective =
664 loaded_programs_for_tx_batch.slot >= entry.effective_slot();
665 let entry_to_return = if entry_is_effective {
666 if !Self::matches_environment(
667 entry,
668 program_runtime_environment_for_execution,
669 ) {
670 continue;
674 }
675 if let ProgramCacheEntryType::Unloaded(_environment) =
676 &entry.program
677 {
678 break;
679 }
680 entry.clone()
681 } else if entry.is_implicit_delay_visibility_tombstone(
682 loaded_programs_for_tx_batch.slot,
683 ) {
684 Arc::new(ProgramCacheEntry::new_delay_visibility_tombstone(
688 entry.deployment_slot,
689 entry.account_owner,
690 Arc::clone(&entry.stats),
691 ))
692 } else {
693 continue;
694 };
695 entry.update_access_slot(loaded_programs_for_tx_batch.slot);
696 if increment_usage_counter {
697 entry_to_return.stats.uses.fetch_add(1, Ordering::Relaxed);
698 }
699 loaded_programs_for_tx_batch
700 .entries
701 .insert(*program_to_load.program_id, entry_to_return);
702 return false;
703 }
704 }
705 }
706 if cooperative_loading_task.is_none() {
707 let mut loading_entries = loading_entries.lock().unwrap();
708 let entry = loading_entries.entry(*program_to_load.program_id);
709 if let Entry::Vacant(entry) = entry {
710 entry.insert((
711 loaded_programs_for_tx_batch.slot,
712 thread::current().id(),
713 ));
714 cooperative_loading_task = Some(*program_to_load.program_id);
715 }
716 }
717 true
718 });
719 }
720 }
721 drop(locked_fork_graph);
722 if count_hits_and_misses {
723 let misses = search_for.len() as u64;
724 let hits = loaded_programs_for_tx_batch
725 .entries
726 .len()
727 .saturating_sub(entries_in_batch) as u64;
728 self.stats.misses.fetch_add(misses, Ordering::Relaxed);
729 self.stats.hits.fetch_add(hits, Ordering::Relaxed);
730 }
731 cooperative_loading_task
732 }
733
734 pub fn finish_cooperative_loading_task(
736 &mut self,
737 program_runtime_environment: &ProgramRuntimeEnvironment,
738 current_slot: Slot,
739 key: Pubkey,
740 loaded_program: Arc<ProgramCacheEntry>,
741 ) -> bool {
742 match &mut self.index {
743 IndexImplementation::V1 {
744 loading_entries, ..
745 } => {
746 let loading_thread = loading_entries.get_mut().unwrap().remove(&key);
747 debug_assert_eq!(loading_thread, Some((current_slot, thread::current().id())));
748 if loaded_program.deployment_slot > self.latest_root_slot
750 && !matches!(
751 self.fork_graph
752 .as_ref()
753 .unwrap()
754 .upgrade()
755 .unwrap()
756 .read()
757 .unwrap()
758 .relationship(loaded_program.deployment_slot, current_slot),
759 BlockRelation::Equal | BlockRelation::Ancestor
760 )
761 {
762 self.stats.lost_insertions.fetch_add(1, Ordering::Relaxed);
763 }
764 let was_occupied = self.assign_program(
765 program_runtime_environment,
766 key,
767 current_slot,
768 loaded_program,
769 );
770 self.loading_task_waiter.notify();
771 was_occupied
772 }
773 }
774 }
775
776 pub fn merge(
777 &mut self,
778 program_runtime_environment: &ProgramRuntimeEnvironment,
779 current_slot: Slot,
780 modified_entries: &HashMap<Pubkey, Arc<ProgramCacheEntry>>,
781 ) {
782 modified_entries.iter().for_each(|(key, entry)| {
783 self.assign_program(
784 program_runtime_environment,
785 *key,
786 current_slot,
787 entry.clone(),
788 );
789 })
790 }
791
792 pub fn get_flattened_entries(&self) -> Vec<(Pubkey, Arc<ProgramCacheEntry>)> {
794 match &self.index {
795 IndexImplementation::V1 { entries, .. } => entries
796 .iter()
797 .flat_map(|(id, second_level)| {
798 second_level
799 .iter()
800 .filter_map(move |program| match program.program {
801 ProgramCacheEntryType::Loaded(_) => Some((*id, program.clone())),
802 _ => None,
803 })
804 })
805 .collect(),
806 }
807 }
808
809 #[cfg(feature = "dev-context-only-utils")]
811 pub fn get_flattened_entries_for_tests(&self) -> Vec<(Pubkey, Arc<ProgramCacheEntry>)> {
812 match &self.index {
813 IndexImplementation::V1 { entries, .. } => entries
814 .iter()
815 .flat_map(|(id, second_level)| {
816 second_level.iter().map(|program| (*id, program.clone()))
817 })
818 .collect(),
819 }
820 }
821
822 pub fn get_slot_versions_for_tests(&self, key: &Pubkey) -> &[Arc<ProgramCacheEntry>] {
824 match &self.index {
825 IndexImplementation::V1 { entries, .. } => entries
826 .get(key)
827 .map(|second_level| second_level.as_ref())
828 .unwrap_or(&[]),
829 }
830 }
831
832 pub fn sort_and_unload(&mut self, shrink_to_percent: Percent) {
834 let mut sorted_candidates = self.get_flattened_entries();
835 sorted_candidates
836 .sort_by_cached_key(|(_id, program)| program.stats.uses.load(Ordering::Relaxed));
837 let num_to_unload = sorted_candidates
838 .len()
839 .saturating_sub(percent_of_max_entries(shrink_to_percent));
840 for (program, entry) in sorted_candidates.iter().take(num_to_unload) {
841 self.unload_program_entry(*program, entry);
842 }
843 }
844
845 pub fn evict_using_random_selection(&mut self, shrink_to_percent: Percent, now: Slot) {
851 let mut candidates = self.get_flattened_entries();
852 let mut rng = rng();
853 self.stats
854 .water_level
855 .store(candidates.len() as u64, Ordering::Relaxed);
856 let num_to_unload = candidates
857 .len()
858 .saturating_sub(percent_of_max_entries(shrink_to_percent));
859 let mut sample_entry = |candidates: &Vec<(Pubkey, Arc<ProgramCacheEntry>)>| {
860 #[cfg(feature = "shuttle-test")]
863 let index = rng.gen_range(0..candidates.len());
864 #[cfg(not(feature = "shuttle-test"))]
865 let index = rng.random_range(0..candidates.len());
866 let usage_counter = candidates
867 .get(index)
868 .expect("Failed to get cached entry")
869 .1
870 .retention_score();
871 (index, usage_counter)
872 };
873
874 const MAX_ADDITIONAL_SAMPLES: usize = 3;
882 let avoid_evicting_above_score = retention_score(now, 500 * EMA_SCALE, 500);
883 for _ in 0..num_to_unload {
884 let (mut index, mut score) = sample_entry(&candidates);
885 for _ in 0..MAX_ADDITIONAL_SAMPLES {
886 let (sample_index, sample_score) = sample_entry(&candidates);
887 if score > sample_score {
888 index = sample_index;
889 score = sample_score;
890 }
891 if score < avoid_evicting_above_score {
892 break;
893 }
894 }
895 let (id, entry) = candidates.swap_remove(index);
896 self.unload_program_entry(id, &entry);
897 }
898 }
899
900 pub fn remove_programs(&mut self, keys: impl Iterator<Item = Pubkey>) {
902 match &mut self.index {
903 IndexImplementation::V1 { entries, .. } => {
904 for k in keys {
905 entries.remove(&k);
906 }
907 }
908 }
909 }
910
911 fn unload_program_entry(&mut self, id: Pubkey, remove_entry: &Arc<ProgramCacheEntry>) {
914 match &mut self.index {
915 IndexImplementation::V1 { entries, .. } => {
916 let second_level = entries.get_mut(&id).expect("Cache lookup failed");
917 let candidate = second_level
918 .iter_mut()
919 .find(|entry| Arc::ptr_eq(entry, remove_entry))
920 .expect("Program entry not found");
921
922 if let ProgramCacheEntryType::Loaded(_) = candidate.program
924 && let Some(unloaded) = candidate.to_unloaded()
925 {
926 if candidate.stats.uses.load(Ordering::Relaxed) == 1 {
927 self.stats.one_hit_wonders.fetch_add(1, Ordering::Relaxed);
928 }
929 self.stats
930 .evictions
931 .entry(id)
932 .and_modify(|c| *c = c.saturating_add(1))
933 .or_insert(1);
934 *candidate = Arc::new(unloaded);
935 }
936 }
937 }
938 }
939
940 fn remove_programs_with_no_entries(&mut self) {
941 match &mut self.index {
942 IndexImplementation::V1 { entries, .. } => {
943 let num_programs_before_removal = entries.len();
944 entries.retain(|_key, second_level| !second_level.is_empty());
945 if entries.len() < num_programs_before_removal {
946 self.stats.empty_entries.fetch_add(
947 num_programs_before_removal.saturating_sub(entries.len()) as u64,
948 Ordering::Relaxed,
949 );
950 }
951 }
952 }
953 }
954}
955
956#[cfg(test)]
957pub(crate) mod tests {
958 use {
959 crate::{
960 loaded_programs::{
961 BlockRelation, ForkGraph, IndexImplementation, MAX_TOMBSTONE_AGE_IN_SLOTS, Percent,
962 ProgramCache, ProgramCacheForTxBatch, ProgramRuntimeEnvironment, ProgramToLoad,
963 get_mock_program_runtime_environment,
964 },
965 program_cache_entry::{
966 ProgramCacheEntry, ProgramCacheEntryOwner, ProgramCacheEntryType,
967 },
968 program_metrics::ProgramStatistics,
969 },
970 assert_matches::assert_matches,
971 solana_clock::Slot,
972 solana_pubkey::Pubkey,
973 solana_sbpf::{elf::Executable, program::BuiltinProgram},
974 solana_svm_type_overrides::{
975 sync::{
976 Arc, RwLock,
977 atomic::{AtomicU64, Ordering},
978 },
979 thread,
980 },
981 std::{fs::File, io::Read, ops::ControlFlow},
982 test_case::{test_case, test_matrix},
983 };
984
985 fn new_test_entry(deployment_slot: Slot) -> Arc<ProgramCacheEntry> {
986 new_test_entry_with_usage(deployment_slot, ProgramStatistics::default())
987 }
988
989 fn new_closed_entry(_env: ProgramRuntimeEnvironment) -> ProgramCacheEntryType {
990 ProgramCacheEntryType::Closed
991 }
992
993 fn new_builtin_entry(_env: ProgramRuntimeEnvironment) -> ProgramCacheEntryType {
994 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock())
995 }
996
997 fn new_failed_verification_entry(env: ProgramRuntimeEnvironment) -> ProgramCacheEntryType {
998 ProgramCacheEntryType::FailedVerification(env)
999 }
1000
1001 fn new_unloaded_entry(env: ProgramRuntimeEnvironment) -> ProgramCacheEntryType {
1002 ProgramCacheEntryType::Unloaded(env)
1003 }
1004
1005 fn new_loaded_entry(env: ProgramRuntimeEnvironment) -> ProgramCacheEntryType {
1006 let mut elf = Vec::new();
1007 File::open("../programs/bpf_loader/test_elfs/out/noop_aligned.so")
1008 .unwrap()
1009 .read_to_end(&mut elf)
1010 .unwrap();
1011 let executable = Executable::load(&elf, Arc::clone(&*env)).unwrap();
1012 ProgramCacheEntryType::Loaded(executable)
1013 }
1014
1015 fn new_test_entry_with_owner(
1016 deployment_slot: Slot,
1017 account_owner: ProgramCacheEntryOwner,
1018 program: ProgramCacheEntryType,
1019 ) -> Arc<ProgramCacheEntry> {
1020 Arc::new(ProgramCacheEntry {
1021 program,
1022 account_owner,
1023 deployment_slot,
1024 stats: Arc::default(),
1025 latest_access_slot: AtomicU64::default(),
1026 })
1027 }
1028
1029 fn new_test_cache_with_fork_graph(
1030 relation: BlockRelation,
1031 ) -> (ProgramCache<TestForkGraph>, Arc<RwLock<TestForkGraph>>) {
1032 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1033 let fork_graph = Arc::new(RwLock::new(TestForkGraph { relation }));
1034 cache.set_fork_graph(Arc::downgrade(&fork_graph));
1035 (cache, fork_graph)
1036 }
1037
1038 pub(crate) fn new_test_entry_with_usage(
1039 deployment_slot: Slot,
1040 stats: ProgramStatistics,
1041 ) -> Arc<ProgramCacheEntry> {
1042 Arc::new(ProgramCacheEntry {
1043 program: new_loaded_entry(get_mock_program_runtime_environment()),
1044 account_owner: ProgramCacheEntryOwner::LoaderV2,
1045 deployment_slot,
1046 stats: Arc::new(stats),
1047 latest_access_slot: AtomicU64::new(deployment_slot),
1048 })
1049 }
1050
1051 fn new_test_builtin_entry(deployment_slot: Slot) -> Arc<ProgramCacheEntry> {
1052 Arc::new(ProgramCacheEntry {
1053 program: ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1054 account_owner: ProgramCacheEntryOwner::NativeLoader,
1055 deployment_slot,
1056 stats: Arc::default(),
1057 latest_access_slot: AtomicU64::default(),
1058 })
1059 }
1060
1061 fn set_failed_verification_tombstone<FG: ForkGraph>(
1062 cache: &mut ProgramCache<FG>,
1063 key: Pubkey,
1064 current_slot: Slot,
1065 env: ProgramRuntimeEnvironment,
1066 ) -> Arc<ProgramCacheEntry> {
1067 let program = Arc::new(ProgramCacheEntry::new_failed_verification_tombstone(
1068 current_slot,
1069 ProgramCacheEntryOwner::LoaderV2,
1070 ProgramRuntimeEnvironment::clone(&env),
1071 ));
1072 cache.assign_program(&env, key, current_slot, program.clone());
1073 program
1074 }
1075
1076 fn insert_unloaded_entry<FG: ForkGraph>(
1077 cache: &mut ProgramCache<FG>,
1078 key: Pubkey,
1079 current_slot: Slot,
1080 ) -> Arc<ProgramCacheEntry> {
1081 let env = get_mock_program_runtime_environment();
1082 let loaded = new_test_entry_with_usage(current_slot, ProgramStatistics::default());
1083 let unloaded = Arc::new(loaded.to_unloaded().expect("Failed to unload the program"));
1084 cache.assign_program(&env, key, current_slot, unloaded.clone());
1085 unloaded
1086 }
1087
1088 fn num_matching_entries<P, FG>(cache: &ProgramCache<FG>, predicate: P) -> usize
1089 where
1090 P: Fn(&ProgramCacheEntryType) -> bool,
1091 FG: ForkGraph,
1092 {
1093 cache
1094 .get_flattened_entries_for_tests()
1095 .iter()
1096 .filter(|(_key, program)| predicate(&program.program))
1097 .count()
1098 }
1099
1100 #[expect(clippy::arithmetic_side_effects)]
1101 fn program_deploy_test_helper(
1102 cache: &mut ProgramCache<TestForkGraph>,
1103 program: Pubkey,
1104 deployment_slots: Vec<Slot>,
1105 usage_counters: Vec<u64>,
1106 programs: &mut Vec<(Pubkey, Slot, u64)>,
1107 ) {
1108 let env = get_mock_program_runtime_environment();
1109 deployment_slots
1111 .iter()
1112 .enumerate()
1113 .for_each(|(i, deployment_slot)| {
1114 let usage_counter = *usage_counters.get(i).unwrap_or(&0);
1115 let stats = ProgramStatistics {
1116 uses: usage_counter.into(),
1117 ..Default::default()
1118 };
1119 cache.assign_program(
1120 &env,
1121 program,
1122 *deployment_slot,
1123 new_test_entry_with_usage(*deployment_slot, stats),
1124 );
1125 programs.push((program, *deployment_slot, usage_counter));
1126 });
1127
1128 let next_slot = deployment_slots.iter().max().map_or(0, |slot| slot + 1);
1129
1130 let env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
1132 for slot in next_slot..next_slot + 10 {
1133 set_failed_verification_tombstone(
1134 cache,
1135 program,
1136 slot,
1137 ProgramRuntimeEnvironment::clone(&env),
1138 );
1139 }
1140
1141 for slot in next_slot + 10..next_slot + 20 {
1143 insert_unloaded_entry(cache, program, slot);
1144 }
1145 }
1146
1147 #[test]
1148 fn test_random_eviction() {
1149 let mut programs = vec![];
1150 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1151
1152 program_deploy_test_helper(
1158 &mut cache,
1159 Pubkey::new_unique(),
1160 vec![0, 10, 20, 30, 40],
1161 vec![4, 5, 25, 35, 12],
1162 &mut programs,
1163 );
1164
1165 program_deploy_test_helper(
1167 &mut cache,
1168 Pubkey::new_unique(),
1169 vec![5, 11, 21, 24],
1170 vec![0, 2, 30, 45],
1171 &mut programs,
1172 );
1173
1174 program_deploy_test_helper(
1176 &mut cache,
1177 Pubkey::new_unique(),
1178 vec![0, 5, 15, 25],
1179 vec![100, 3, 20, 40],
1180 &mut programs,
1181 );
1182
1183 let num_loaded_expected = 13;
1185 let num_unloaded_expected = 30;
1187 let num_tombstones_expected = 30;
1189
1190 programs.sort_by_key(|(_id, _slot, usage_count)| *usage_count);
1192 let num_loaded = num_matching_entries(&cache, |program_type| {
1193 matches!(program_type, ProgramCacheEntryType::Loaded(_))
1194 });
1195 let num_unloaded = num_matching_entries(&cache, |program_type| {
1196 matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1197 });
1198 let num_tombstones = num_matching_entries(&cache, |program_type| {
1199 matches!(
1200 program_type,
1201 ProgramCacheEntryType::DelayVisibility
1202 | ProgramCacheEntryType::FailedVerification(_)
1203 | ProgramCacheEntryType::Closed
1204 )
1205 });
1206
1207 assert_eq!(num_loaded, num_loaded_expected);
1209 assert_eq!(num_unloaded, num_unloaded_expected);
1210 assert_eq!(num_tombstones, num_tombstones_expected);
1211
1212 let eviction_pct: Percent = 1;
1214
1215 let num_loaded_expected = crate::loaded_programs::percent_of_max_entries(eviction_pct);
1216 let num_unloaded_expected = num_unloaded_expected + num_loaded - num_loaded_expected;
1217 cache.evict_using_random_selection(eviction_pct, 21);
1218
1219 let num_loaded = num_matching_entries(&cache, |program_type| {
1221 matches!(program_type, ProgramCacheEntryType::Loaded(_))
1222 });
1223 let num_unloaded = num_matching_entries(&cache, |program_type| {
1224 matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1225 });
1226 let num_tombstones = num_matching_entries(&cache, |program_type| {
1227 matches!(program_type, ProgramCacheEntryType::FailedVerification(_))
1228 });
1229
1230 assert_eq!(num_loaded, num_loaded_expected);
1232 assert_eq!(num_unloaded, num_unloaded_expected);
1234 assert_eq!(num_tombstones, num_tombstones_expected);
1236 }
1237
1238 #[test]
1239 fn test_eviction() {
1240 let mut programs = vec![];
1241 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1242
1243 program_deploy_test_helper(
1245 &mut cache,
1246 Pubkey::new_unique(),
1247 vec![0, 10, 20, 30, 40],
1248 vec![4, 5, 25, 35, 12],
1249 &mut programs,
1250 );
1251
1252 program_deploy_test_helper(
1254 &mut cache,
1255 Pubkey::new_unique(),
1256 vec![5, 11, 21, 24],
1257 vec![0, 2, 30, 45],
1258 &mut programs,
1259 );
1260
1261 program_deploy_test_helper(
1263 &mut cache,
1264 Pubkey::new_unique(),
1265 vec![0, 5, 15, 25],
1266 vec![100, 3, 20, 40],
1267 &mut programs,
1268 );
1269
1270 let num_loaded_expected = 13;
1272 let num_unloaded_expected = 30;
1274 let num_tombstones_expected = 30;
1276
1277 programs.sort_by_key(|(_id, _slot, usage_count)| *usage_count);
1279 let num_loaded = num_matching_entries(&cache, |program_type| {
1280 matches!(program_type, ProgramCacheEntryType::Loaded(_))
1281 });
1282 let num_unloaded = num_matching_entries(&cache, |program_type| {
1283 matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1284 });
1285 let num_tombstones = num_matching_entries(&cache, |program_type| {
1286 matches!(program_type, ProgramCacheEntryType::FailedVerification(_))
1287 });
1288
1289 assert_eq!(num_loaded, num_loaded_expected);
1291 assert_eq!(num_unloaded, num_unloaded_expected);
1292 assert_eq!(num_tombstones, num_tombstones_expected);
1293
1294 let eviction_pct: Percent = 1;
1296
1297 let num_loaded_expected = crate::loaded_programs::percent_of_max_entries(eviction_pct);
1298 let num_unloaded_expected = num_unloaded_expected + num_loaded - num_loaded_expected;
1299
1300 cache.sort_and_unload(eviction_pct);
1301
1302 let entries = cache.get_flattened_entries_for_tests();
1304 programs.iter().for_each(|entry| {
1305 assert!(entries.iter().any(|(key, _entry)| key == &entry.0));
1306 });
1307
1308 let unloaded = entries
1309 .iter()
1310 .filter_map(|(key, program)| {
1311 matches!(program.program, ProgramCacheEntryType::Unloaded(_))
1312 .then_some((*key, program.stats.uses.load(Ordering::Relaxed)))
1313 })
1314 .collect::<Vec<(Pubkey, u64)>>();
1315
1316 for index in 0..3 {
1317 let expected = programs.get(index).expect("Missing program");
1318 assert!(unloaded.contains(&(expected.0, expected.2)));
1319 }
1320
1321 let num_loaded = num_matching_entries(&cache, |program_type| {
1323 matches!(program_type, ProgramCacheEntryType::Loaded(_))
1324 });
1325 let num_unloaded = num_matching_entries(&cache, |program_type| {
1326 matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1327 });
1328 let num_tombstones = num_matching_entries(&cache, |program_type| {
1329 matches!(
1330 program_type,
1331 ProgramCacheEntryType::DelayVisibility
1332 | ProgramCacheEntryType::FailedVerification(_)
1333 | ProgramCacheEntryType::Closed
1334 )
1335 });
1336
1337 assert_eq!(num_loaded, num_loaded_expected);
1339 assert_eq!(num_unloaded, num_unloaded_expected);
1341 assert_eq!(num_tombstones, num_tombstones_expected);
1343 }
1344
1345 #[test]
1346 fn test_usage_count_of_unloaded_program() {
1347 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1348 let env = get_mock_program_runtime_environment();
1349
1350 let program = Pubkey::new_unique();
1351 let evict_to_pct: Percent = 2;
1352 let cache_capacity_after_shrink =
1353 crate::loaded_programs::percent_of_max_entries(evict_to_pct);
1354 let num_total_programs = (cache_capacity_after_shrink + 1) as u64;
1356 (0..num_total_programs).for_each(|i| {
1357 let stats = ProgramStatistics {
1358 uses: (i + 10).into(),
1359 ..Default::default()
1360 };
1361 let entry = new_test_entry_with_usage(i, stats);
1362 cache.assign_program(&env, program, i, entry);
1363 });
1364
1365 cache.sort_and_unload(evict_to_pct);
1366
1367 let num_unloaded = num_matching_entries(&cache, |program_type| {
1368 matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1369 });
1370 assert_eq!(num_unloaded, 1);
1371
1372 cache
1373 .get_flattened_entries_for_tests()
1374 .iter()
1375 .for_each(|(_key, program)| {
1376 if matches!(program.program, ProgramCacheEntryType::Unloaded(_)) {
1377 assert_eq!(program.stats.uses.load(Ordering::Relaxed), 10);
1379 assert_eq!(program.deployment_slot, 0);
1380 assert_eq!(program.effective_slot(), 1);
1381 }
1382 });
1383
1384 cache.assign_program(
1387 &env,
1388 program,
1389 0,
1390 new_test_entry_with_usage(0, ProgramStatistics::default()),
1391 );
1392
1393 cache
1394 .get_flattened_entries_for_tests()
1395 .iter()
1396 .for_each(|(_key, program)| {
1397 if matches!(program.program, ProgramCacheEntryType::Unloaded(_))
1398 && program.deployment_slot == 0
1399 && program.effective_slot() == 1
1400 {
1401 assert_eq!(program.stats.uses.load(Ordering::Relaxed), 10);
1403 }
1404 });
1405 }
1406
1407 #[test_matrix(
1408 (
1409 ProgramCacheEntryType::FailedVerification(get_mock_program_runtime_environment()),
1410 ProgramCacheEntryType::Closed,
1411 ProgramCacheEntryType::Unloaded(get_mock_program_runtime_environment()),
1412 new_loaded_entry(get_mock_program_runtime_environment()),
1413 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1414 ),
1415 (false, true)
1416 )]
1417 fn test_assign_program_no_second_level(
1418 program: ProgramCacheEntryType,
1419 empty_second_level: bool,
1420 ) {
1421 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1425 let env = get_mock_program_runtime_environment();
1426 let program_id = Pubkey::new_unique();
1427
1428 if empty_second_level {
1429 match &mut cache.index {
1431 IndexImplementation::V1 { entries, .. } => {
1432 entries.insert(program_id, Vec::new());
1433 }
1434 }
1435 }
1436
1437 let entry = Arc::new(ProgramCacheEntry {
1438 program,
1439 account_owner: ProgramCacheEntryOwner::LoaderV3,
1440 deployment_slot: 10,
1441 stats: Arc::default(),
1442 latest_access_slot: AtomicU64::default(),
1443 });
1444
1445 cache.assign_program(&env, program_id, 10, Arc::clone(&entry));
1446
1447 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
1449 assert_eq!(slot_versions.len(), 1);
1450 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &entry));
1451
1452 assert_eq!(cache.stats.insertions.load(Ordering::Relaxed), 1);
1454 }
1455
1456 #[test_matrix(
1457 (
1458 new_closed_entry,
1459 new_builtin_entry,
1460 new_failed_verification_entry,
1461 new_unloaded_entry,
1462 new_loaded_entry,
1463 ),
1464 ((50, 0), (150, 1), (250, 2), (350, 3))
1465 )]
1466 fn test_assign_program_new_insertion_deployment_slot(
1467 new_program: fn(ProgramRuntimeEnvironment) -> ProgramCacheEntryType,
1468 case: (Slot, usize),
1469 ) {
1470 let (deployment_slot, expected_index) = case;
1471 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1472 let env = get_mock_program_runtime_environment();
1473 let program_id = Pubkey::new_unique();
1474
1475 for slot in [100, 200, 300] {
1477 cache.assign_program(
1478 &env,
1479 program_id,
1480 slot,
1481 new_test_entry_with_owner(
1482 slot,
1483 ProgramCacheEntryOwner::LoaderV3,
1484 new_program(env.clone()),
1485 ),
1486 );
1487 }
1488
1489 let entry = new_test_entry_with_owner(
1491 deployment_slot,
1492 ProgramCacheEntryOwner::LoaderV3,
1493 new_program(env.clone()),
1494 );
1495 cache.assign_program(&env, program_id, deployment_slot, Arc::clone(&entry));
1496
1497 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
1498 assert_eq!(slot_versions.len(), 4);
1499 assert!(Arc::ptr_eq(
1500 slot_versions.get(expected_index).unwrap(),
1501 &entry
1502 ));
1503 assert_eq!(cache.stats.insertions.load(Ordering::Relaxed), 4);
1504 }
1505
1506 #[test_matrix(
1507 (new_failed_verification_entry, new_unloaded_entry, new_loaded_entry),
1508 (
1509 (ProgramCacheEntryOwner::NativeLoader, 0),
1510 (ProgramCacheEntryOwner::LoaderV2, 1),
1511 (ProgramCacheEntryOwner::LoaderV4, 2),
1512 )
1513 )]
1514 fn test_assign_program_new_insertion_account_owner(
1515 new_program: fn(ProgramRuntimeEnvironment) -> ProgramCacheEntryType,
1516 case: (ProgramCacheEntryOwner, usize),
1517 ) {
1518 let (account_owner, expected_index) = case;
1519 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1520 let env = get_mock_program_runtime_environment();
1521 let program_id = Pubkey::new_unique();
1522
1523 for owner in [
1526 ProgramCacheEntryOwner::LoaderV1,
1527 ProgramCacheEntryOwner::LoaderV3,
1528 ] {
1529 cache.assign_program(
1530 &env,
1531 program_id,
1532 100,
1533 new_test_entry_with_owner(
1534 100,
1535 owner,
1536 new_program(ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock())),
1537 ),
1538 );
1539 }
1540
1541 let entry = new_test_entry_with_owner(
1544 100,
1545 account_owner,
1546 new_program(ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock())),
1547 );
1548 cache.assign_program(&env, program_id, 100, Arc::clone(&entry));
1549
1550 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
1551 assert_eq!(slot_versions.len(), 3);
1552 assert!(Arc::ptr_eq(
1553 slot_versions.get(expected_index).unwrap(),
1554 &entry
1555 ));
1556 assert_eq!(cache.stats.insertions.load(Ordering::Relaxed), 3);
1557 }
1558
1559 #[test_matrix(
1560 (new_failed_verification_entry, new_unloaded_entry, new_loaded_entry),
1561 (false, true)
1562 )]
1563 fn test_assign_program_new_insertion_environment(
1564 new_program: fn(ProgramRuntimeEnvironment) -> ProgramCacheEntryType,
1565 entry_uses_current_env: bool,
1566 ) {
1567 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1568 let env = get_mock_program_runtime_environment();
1569 let other_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
1570 let program_id = Pubkey::new_unique();
1571
1572 let (existing_env, entry_env, expected_index) = if entry_uses_current_env {
1575 (other_env, env.clone(), 1)
1576 } else {
1577 (env.clone(), other_env, 0)
1578 };
1579 cache.assign_program(
1580 &env,
1581 program_id,
1582 100,
1583 new_test_entry_with_owner(
1584 100,
1585 ProgramCacheEntryOwner::LoaderV3,
1586 new_program(existing_env),
1587 ),
1588 );
1589
1590 let entry = new_test_entry_with_owner(
1591 100,
1592 ProgramCacheEntryOwner::LoaderV3,
1593 new_program(entry_env),
1594 );
1595 cache.assign_program(&env, program_id, 100, Arc::clone(&entry));
1596
1597 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
1598 assert_eq!(slot_versions.len(), 2);
1599 assert!(Arc::ptr_eq(
1600 slot_versions.get(expected_index).unwrap(),
1601 &entry
1602 ));
1603 assert_eq!(cache.stats.insertions.load(Ordering::Relaxed), 2);
1604 }
1605
1606 #[test]
1607 #[should_panic(expected = "Unexpected assignment of a DelayVisibility tombstone")]
1608 fn test_assign_program_delay_visibility_tombstone_panics() {
1609 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1612 let env = get_mock_program_runtime_environment();
1613 cache.assign_program(
1614 &env,
1615 Pubkey::new_unique(),
1616 100,
1617 Arc::new(ProgramCacheEntry::new_delay_visibility_tombstone(
1618 100,
1619 ProgramCacheEntryOwner::LoaderV3,
1620 Arc::default(),
1621 )),
1622 );
1623 }
1624
1625 #[test]
1626 fn test_fuzz_assign_program_order() {
1627 use rand::prelude::SliceRandom;
1628 const EXPECTED_ENTRIES: [(u64, bool); 5] =
1629 [(1, true), (3, false), (5, true), (9, true), (10, false)];
1630 let mut rng = rand::rng();
1631 let program_id = Pubkey::new_unique();
1632 let env = get_mock_program_runtime_environment();
1633 for _ in 0..1000 {
1634 let mut entries = EXPECTED_ENTRIES.to_vec();
1635 entries.shuffle(&mut rng);
1636 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1637 for (deployment_slot, delay_visibility) in entries {
1638 let entry = Arc::new(if delay_visibility {
1639 ProgramCacheEntry {
1640 program: new_loaded_entry(ProgramRuntimeEnvironment::from(
1641 BuiltinProgram::new_mock(),
1642 )), account_owner: ProgramCacheEntryOwner::LoaderV2,
1644 deployment_slot,
1645 stats: Arc::default(),
1646 latest_access_slot: AtomicU64::new(deployment_slot),
1647 }
1648 } else {
1649 ProgramCacheEntry::new_failed_verification_tombstone(
1650 deployment_slot,
1651 ProgramCacheEntryOwner::LoaderV2,
1652 ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock()), )
1654 });
1655 assert!(!cache.assign_program(&env, program_id, deployment_slot, entry));
1656 }
1657 for ((deployment_slot, delay_visibility), entry) in EXPECTED_ENTRIES
1658 .iter()
1659 .zip(cache.get_slot_versions_for_tests(&program_id).iter())
1660 {
1661 assert_eq!(entry.deployment_slot, *deployment_slot);
1662 assert_eq!(
1663 entry.effective_slot(),
1664 deployment_slot.saturating_add(*delay_visibility as u64)
1665 );
1666 }
1667 }
1668 }
1669
1670 #[test_matrix(
1671 (
1672 ProgramCacheEntryType::FailedVerification(get_mock_program_runtime_environment()),
1673 new_loaded_entry(get_mock_program_runtime_environment()),
1674 ),
1675 (
1676 ProgramCacheEntryType::FailedVerification(get_mock_program_runtime_environment()),
1677 ProgramCacheEntryType::Closed,
1678 ProgramCacheEntryType::Unloaded(get_mock_program_runtime_environment()),
1679 new_loaded_entry(get_mock_program_runtime_environment()),
1680 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1681 )
1682 )]
1683 #[test_matrix(
1684 ProgramCacheEntryType::Closed,
1685 (
1686 ProgramCacheEntryType::FailedVerification(get_mock_program_runtime_environment()),
1687 ProgramCacheEntryType::Closed,
1688 new_loaded_entry(get_mock_program_runtime_environment()),
1689 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1690 )
1691 )]
1692 #[test_matrix(
1693 ProgramCacheEntryType::Unloaded(get_mock_program_runtime_environment()),
1694 (
1695 ProgramCacheEntryType::Closed,
1696 ProgramCacheEntryType::Unloaded(get_mock_program_runtime_environment()),
1697 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1698 )
1699 )]
1700 #[test_matrix(
1701 (ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),),
1702 (
1703 ProgramCacheEntryType::FailedVerification(get_mock_program_runtime_environment()),
1704 ProgramCacheEntryType::Closed,
1705 ProgramCacheEntryType::Unloaded(get_mock_program_runtime_environment()),
1706 new_loaded_entry(get_mock_program_runtime_environment()),
1707 )
1708 )]
1709 #[should_panic(expected = "Unexpected replacement of an entry")]
1710 fn test_assign_program_failure(old: ProgramCacheEntryType, new: ProgramCacheEntryType) {
1711 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1712 let env = get_mock_program_runtime_environment();
1713 let program_id = Pubkey::new_unique();
1714 assert!(!cache.assign_program(
1715 &env,
1716 program_id,
1717 10,
1718 Arc::new(ProgramCacheEntry {
1719 program: old,
1720 account_owner: ProgramCacheEntryOwner::LoaderV2,
1721 deployment_slot: 10,
1722 stats: Arc::default(),
1723 latest_access_slot: AtomicU64::default(),
1724 }),
1725 ));
1726 cache.assign_program(
1727 &env,
1728 program_id,
1729 10,
1730 Arc::new(ProgramCacheEntry {
1731 program: new,
1732 account_owner: ProgramCacheEntryOwner::LoaderV2,
1733 deployment_slot: 10,
1734 stats: Arc::default(),
1735 latest_access_slot: AtomicU64::default(),
1736 }),
1737 );
1738 }
1739
1740 #[test_matrix(
1741 ProgramCacheEntryType::Unloaded(get_mock_program_runtime_environment()),
1742 (
1743 new_loaded_entry(get_mock_program_runtime_environment()),
1744 ProgramCacheEntryType::FailedVerification(get_mock_program_runtime_environment()),
1745 )
1746 )]
1747 #[test_case(
1748 ProgramCacheEntryType::Closed,
1749 ProgramCacheEntryType::Unloaded(get_mock_program_runtime_environment())
1750 )]
1751 #[test_case(
1752 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1753 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock())
1754 )]
1755 fn test_assign_program_success(old: ProgramCacheEntryType, new: ProgramCacheEntryType) {
1756 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1757 let env = get_mock_program_runtime_environment();
1758 let program_id = Pubkey::new_unique();
1759 assert!(!cache.assign_program(
1760 &env,
1761 program_id,
1762 10,
1763 Arc::new(ProgramCacheEntry {
1764 program: old,
1765 account_owner: ProgramCacheEntryOwner::LoaderV2,
1766 deployment_slot: 10,
1767 stats: Arc::default(),
1768 latest_access_slot: AtomicU64::default(),
1769 }),
1770 ));
1771 assert!(!cache.assign_program(
1772 &env,
1773 program_id,
1774 10,
1775 Arc::new(ProgramCacheEntry {
1776 program: new,
1777 account_owner: ProgramCacheEntryOwner::LoaderV2,
1778 deployment_slot: 10,
1779 stats: Arc::default(),
1780 latest_access_slot: AtomicU64::default(),
1781 }),
1782 ));
1783 }
1784
1785 #[test]
1786 fn test_assign_program_removes_entries_in_same_slot() {
1787 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1788 let env = get_mock_program_runtime_environment();
1789 let program_id = Pubkey::new_unique();
1790 let closed_other_slot = new_test_entry_with_owner(
1791 9,
1792 ProgramCacheEntryOwner::LoaderV2,
1793 new_closed_entry(env.clone()),
1794 );
1795 let closed_current_slot = new_test_entry_with_owner(
1796 10,
1797 ProgramCacheEntryOwner::LoaderV2,
1798 new_closed_entry(env.clone()),
1799 );
1800 let unloaded_current_env = new_test_entry_with_owner(
1801 10,
1802 ProgramCacheEntryOwner::LoaderV2,
1803 new_unloaded_entry(get_mock_program_runtime_environment()),
1804 );
1805 let unloaded_upcoming_env = new_test_entry_with_owner(
1806 10,
1807 ProgramCacheEntryOwner::LoaderV2,
1808 new_unloaded_entry(ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock())),
1809 );
1810
1811 assert!(!cache.assign_program(&env, program_id, 9, closed_other_slot.clone()));
1815 assert!(!cache.assign_program(&env, program_id, 10, closed_current_slot.clone()));
1816 assert_eq!(
1817 cache.get_slot_versions_for_tests(&program_id),
1818 &[closed_other_slot.clone(), closed_current_slot.clone()]
1819 );
1820
1821 assert!(!cache.assign_program(&env, program_id, 10, unloaded_current_env.clone()));
1830 assert_eq!(
1831 cache.get_slot_versions_for_tests(&program_id),
1832 &[
1833 closed_other_slot.clone(),
1834 unloaded_current_env.clone() ]
1836 );
1837
1838 assert!(!cache.assign_program(&env, program_id, 10, unloaded_upcoming_env.clone()));
1842 assert_eq!(
1843 cache.get_slot_versions_for_tests(&program_id),
1844 &[
1845 closed_other_slot,
1846 unloaded_current_env,
1847 unloaded_upcoming_env
1848 ]
1849 );
1850 }
1851
1852 #[test]
1853 fn test_assign_program_reload_merges_statistics() {
1854 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1855 let env = get_mock_program_runtime_environment();
1856 let program_id = Pubkey::new_unique();
1857
1858 let stats = Arc::new(ProgramStatistics {
1859 uses: 1.into(),
1860 compilations: 2.into(),
1861 total_compilation_time_us: 3.into(),
1862 compilation_time_ema: 100.into(),
1863 jit_invocations: 4.into(),
1864 total_jit_execution_time_us: 5.into(),
1865 jit_execution_time_ema: 200.into(),
1866 interpreted_invocations: 6.into(),
1867 total_interpretation_time_us: 7.into(),
1868 interpretation_time_ema: 300.into(),
1869 });
1870 let unloaded = Arc::new(ProgramCacheEntry {
1871 program: ProgramCacheEntryType::Unloaded(env.clone()),
1872 account_owner: ProgramCacheEntryOwner::LoaderV3,
1873 deployment_slot: 100,
1874 stats: Arc::clone(&stats),
1875 latest_access_slot: AtomicU64::default(),
1876 });
1877 cache.assign_program(&env, program_id, 100, unloaded);
1878
1879 let loaded = Arc::new(ProgramCacheEntry {
1881 program: new_loaded_entry(env.clone()),
1882 account_owner: ProgramCacheEntryOwner::LoaderV3,
1883 deployment_slot: 100,
1884 stats: Arc::default(), latest_access_slot: AtomicU64::default(),
1886 });
1887 cache.assign_program(&env, program_id, 100, Arc::clone(&loaded));
1888
1889 assert_eq!(cache.stats.insertions.load(Ordering::Relaxed), 1);
1890 assert_eq!(cache.stats.reloads.load(Ordering::Relaxed), 1);
1891
1892 let merged = &loaded.stats;
1893 let ord = Ordering::Relaxed;
1894 assert_eq!(merged.uses.load(ord), stats.uses.load(ord));
1895 assert_eq!(merged.compilations.load(ord), stats.compilations.load(ord));
1896 assert_eq!(
1897 merged.total_compilation_time_us.load(ord),
1898 stats.total_compilation_time_us.load(ord)
1899 );
1900 assert_eq!(
1901 merged.jit_invocations.load(ord),
1902 stats.jit_invocations.load(ord)
1903 );
1904 assert_eq!(
1905 merged.total_jit_execution_time_us.load(ord),
1906 stats.total_jit_execution_time_us.load(ord)
1907 );
1908 assert_eq!(
1909 merged.interpreted_invocations.load(ord),
1910 stats.interpreted_invocations.load(ord)
1911 );
1912 assert_eq!(
1913 merged.total_interpretation_time_us.load(ord),
1914 stats.total_interpretation_time_us.load(ord)
1915 );
1916
1917 const EMA_DIVISOR: u64 = 2;
1920 assert_eq!(
1921 merged.compilation_time_ema.load(ord),
1922 stats
1923 .compilation_time_ema
1924 .load(ord)
1925 .wrapping_div(EMA_DIVISOR)
1926 );
1927 assert_eq!(
1928 merged.jit_execution_time_ema.load(ord),
1929 stats
1930 .jit_execution_time_ema
1931 .load(ord)
1932 .wrapping_div(EMA_DIVISOR)
1933 );
1934 assert_eq!(
1935 merged.interpretation_time_ema.load(ord),
1936 stats
1937 .interpretation_time_ema
1938 .load(ord)
1939 .wrapping_div(EMA_DIVISOR)
1940 );
1941 }
1942
1943 #[test]
1944 fn test_tombstone() {
1945 let env = get_mock_program_runtime_environment();
1946 let tombstone = ProgramCacheEntry::new_failed_verification_tombstone(
1947 0,
1948 ProgramCacheEntryOwner::LoaderV2,
1949 env.clone(),
1950 );
1951 assert_matches!(
1952 tombstone.program,
1953 ProgramCacheEntryType::FailedVerification(_)
1954 );
1955 assert!(tombstone.is_tombstone());
1956 assert_eq!(tombstone.deployment_slot, 0);
1957 assert_eq!(tombstone.effective_slot(), 0);
1958
1959 let tombstone =
1960 ProgramCacheEntry::new_closed_tombstone(100, ProgramCacheEntryOwner::LoaderV2);
1961 assert_matches!(tombstone.program, ProgramCacheEntryType::Closed);
1962 assert!(tombstone.is_tombstone());
1963 assert_eq!(tombstone.deployment_slot, 100);
1964 assert_eq!(tombstone.effective_slot(), 100);
1965
1966 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1967 let program1 = Pubkey::new_unique();
1968 let tombstone = set_failed_verification_tombstone(&mut cache, program1, 10, env.clone());
1969 let slot_versions = cache.get_slot_versions_for_tests(&program1);
1970 assert_eq!(slot_versions.len(), 1);
1971 assert!(slot_versions.first().unwrap().is_tombstone());
1972 assert_eq!(tombstone.deployment_slot, 10);
1973 assert_eq!(tombstone.effective_slot(), 10);
1974
1975 let program2 = Pubkey::new_unique();
1977 cache.assign_program(&env, program2, 50, new_test_builtin_entry(50));
1978 let slot_versions = cache.get_slot_versions_for_tests(&program2);
1979 assert_eq!(slot_versions.len(), 1);
1980 assert!(!slot_versions.first().unwrap().is_tombstone());
1981
1982 let tombstone = set_failed_verification_tombstone(&mut cache, program2, 60, env);
1983 let slot_versions = cache.get_slot_versions_for_tests(&program2);
1984 assert_eq!(slot_versions.len(), 2);
1985 assert!(!slot_versions.first().unwrap().is_tombstone());
1986 assert!(slot_versions.get(1).unwrap().is_tombstone());
1987 assert!(tombstone.is_tombstone());
1988 assert_eq!(tombstone.deployment_slot, 60);
1989 assert_eq!(tombstone.effective_slot(), 60);
1990 }
1991
1992 struct TestForkGraph {
1993 relation: BlockRelation,
1994 }
1995 impl ForkGraph for TestForkGraph {
1996 fn relationship(&self, _a: Slot, _b: Slot) -> BlockRelation {
1997 self.relation
1998 }
1999 }
2000
2001 #[test]
2002 fn test_prune_empty() {
2003 let mut cache = ProgramCache::<TestForkGraph>::new(0);
2004 let fork_graph = Arc::new(RwLock::new(TestForkGraph {
2005 relation: BlockRelation::Unrelated,
2006 }));
2007
2008 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2009
2010 cache.prune(0, None, &fork_graph.read().unwrap());
2011 assert!(cache.get_flattened_entries_for_tests().is_empty());
2012
2013 cache.prune(10, None, &fork_graph.read().unwrap());
2014 assert!(cache.get_flattened_entries_for_tests().is_empty());
2015
2016 let mut cache = ProgramCache::<TestForkGraph>::new(0);
2017 let fork_graph = Arc::new(RwLock::new(TestForkGraph {
2018 relation: BlockRelation::Ancestor,
2019 }));
2020
2021 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2022
2023 cache.prune(0, None, &fork_graph.read().unwrap());
2024 assert!(cache.get_flattened_entries_for_tests().is_empty());
2025
2026 cache.prune(10, None, &fork_graph.read().unwrap());
2027 assert!(cache.get_flattened_entries_for_tests().is_empty());
2028
2029 let mut cache = ProgramCache::<TestForkGraph>::new(0);
2030 let fork_graph = Arc::new(RwLock::new(TestForkGraph {
2031 relation: BlockRelation::Descendant,
2032 }));
2033
2034 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2035
2036 cache.prune(0, None, &fork_graph.read().unwrap());
2037 assert!(cache.get_flattened_entries_for_tests().is_empty());
2038
2039 cache.prune(10, None, &fork_graph.read().unwrap());
2040 assert!(cache.get_flattened_entries_for_tests().is_empty());
2041
2042 let mut cache = ProgramCache::<TestForkGraph>::new(0);
2043 let fork_graph = Arc::new(RwLock::new(TestForkGraph {
2044 relation: BlockRelation::Unknown,
2045 }));
2046 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2047
2048 cache.prune(0, None, &fork_graph.read().unwrap());
2049 assert!(cache.get_flattened_entries_for_tests().is_empty());
2050
2051 cache.prune(10, None, &fork_graph.read().unwrap());
2052 assert!(cache.get_flattened_entries_for_tests().is_empty());
2053 }
2054
2055 #[test]
2056 fn test_prune_removes_programs_with_no_entries() {
2057 let (mut cache, fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Unknown);
2058 let env = get_mock_program_runtime_environment();
2059 let emptied = Pubkey::new_unique();
2060 cache.assign_program(
2061 &env,
2062 emptied,
2063 100,
2064 new_test_entry_with_owner(
2065 100,
2066 ProgramCacheEntryOwner::LoaderV3,
2067 new_loaded_entry(env.clone()),
2068 ),
2069 );
2070
2071 cache.prune(50, None, &fork_graph.read().unwrap());
2073 match &cache.index {
2074 IndexImplementation::V1 { entries, .. } => assert!(!entries.contains_key(&emptied)),
2075 }
2076 assert_eq!(cache.stats.empty_entries.load(Ordering::Relaxed), 1);
2077 assert_eq!(cache.stats.prunes_orphan.load(Ordering::Relaxed), 1);
2078 }
2079
2080 #[test]
2081 fn test_prune_across_the_root() {
2082 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
2088 let mut fork_graph = TestForkGraphSpecific::default();
2089 fork_graph.insert_fork(&[30, 50, 70]);
2090 let fork_graph = Arc::new(RwLock::new(fork_graph));
2091 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2092
2093 let env = get_mock_program_runtime_environment();
2094 let program_id = Pubkey::new_unique();
2095 let below = new_test_entry_with_owner(
2096 30,
2097 ProgramCacheEntryOwner::LoaderV3,
2098 new_loaded_entry(env.clone()),
2099 );
2100 let above = new_test_entry_with_owner(
2101 70,
2102 ProgramCacheEntryOwner::LoaderV3,
2103 new_loaded_entry(env.clone()),
2104 );
2105 cache.assign_program(&env, program_id, 30, Arc::clone(&below));
2106 cache.assign_program(&env, program_id, 70, Arc::clone(&above));
2107
2108 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 cache.prune(50, None, &fork_graph.read().unwrap());
2114
2115 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
2117 assert_eq!(slot_versions.len(), 2);
2118 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &below));
2119 assert!(Arc::ptr_eq(slot_versions.get(1).unwrap(), &above));
2120 }
2121
2122 #[test]
2123 fn test_prune_across_the_root_ancestors() {
2124 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
2130 let mut fork_graph = TestForkGraphSpecific::default();
2131 fork_graph.insert_fork(&[10, 20, 30, 50, 70]);
2132 let fork_graph = Arc::new(RwLock::new(fork_graph));
2133 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2134
2135 let env = get_mock_program_runtime_environment();
2136 let program_id = Pubkey::new_unique();
2137 let oldest = new_test_entry_with_owner(
2138 10,
2139 ProgramCacheEntryOwner::LoaderV3,
2140 new_loaded_entry(env.clone()),
2141 );
2142 let older = new_test_entry_with_owner(
2143 20,
2144 ProgramCacheEntryOwner::LoaderV3,
2145 new_loaded_entry(env.clone()),
2146 );
2147 let below = new_test_entry_with_owner(
2148 30,
2149 ProgramCacheEntryOwner::LoaderV3,
2150 new_loaded_entry(env.clone()),
2151 );
2152 let above = new_test_entry_with_owner(
2153 70,
2154 ProgramCacheEntryOwner::LoaderV3,
2155 new_loaded_entry(env.clone()),
2156 );
2157 cache.assign_program(&env, program_id, 10, Arc::clone(&oldest));
2158 cache.assign_program(&env, program_id, 20, Arc::clone(&older));
2159 cache.assign_program(&env, program_id, 30, Arc::clone(&below));
2160 cache.assign_program(&env, program_id, 70, Arc::clone(&above));
2161
2162 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
2163 assert_eq!(slot_versions.len(), 4);
2164 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &oldest));
2165 assert!(Arc::ptr_eq(slot_versions.get(1).unwrap(), &older));
2166 assert!(Arc::ptr_eq(slot_versions.get(2).unwrap(), &below));
2167 assert!(Arc::ptr_eq(slot_versions.get(3).unwrap(), &above));
2168
2169 cache.prune(50, None, &fork_graph.read().unwrap());
2170
2171 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
2174 assert_eq!(slot_versions.len(), 2);
2175 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &below));
2176 assert!(Arc::ptr_eq(slot_versions.get(1).unwrap(), &above));
2177 assert_eq!(cache.stats.prunes_orphan.load(Ordering::Relaxed), 2);
2178 }
2179
2180 #[test]
2181 fn test_prune_entry_older_than_root() {
2182 let (mut cache, fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Unknown);
2196 let env = get_mock_program_runtime_environment();
2197 let program_id = Pubkey::new_unique();
2198 let entry = new_test_entry_with_owner(
2199 5,
2200 ProgramCacheEntryOwner::LoaderV3,
2201 new_loaded_entry(env.clone()),
2202 );
2203 cache.assign_program(&env, program_id, 5, Arc::clone(&entry));
2204 cache.latest_root_slot = 10;
2205
2206 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
2207 assert_eq!(slot_versions.len(), 1);
2208 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &entry));
2209
2210 cache.prune(20, None, &fork_graph.read().unwrap());
2211
2212 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
2216 assert_eq!(slot_versions.len(), 1);
2217 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &entry));
2218 assert_eq!(cache.stats.prunes_orphan.load(Ordering::Relaxed), 0);
2219 }
2220
2221 #[test]
2222 fn test_prune_orphan_newer_than_root() {
2223 let (mut cache, fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Unknown);
2233 let env = get_mock_program_runtime_environment();
2234 let program_id = Pubkey::new_unique();
2235 let orphan = new_test_entry_with_owner(
2236 100,
2237 ProgramCacheEntryOwner::LoaderV3,
2238 new_loaded_entry(env.clone()),
2239 );
2240 cache.assign_program(&env, program_id, 100, Arc::clone(&orphan));
2241
2242 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
2243 assert_eq!(slot_versions.len(), 1);
2244 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &orphan));
2245
2246 cache.prune(50, None, &fork_graph.read().unwrap());
2247
2248 assert!(cache.get_slot_versions_for_tests(&program_id).is_empty());
2251 assert_eq!(cache.stats.prunes_orphan.load(Ordering::Relaxed), 1);
2252 }
2253
2254 #[test]
2255 fn test_prune_tombstones() {
2256 let env = get_mock_program_runtime_environment();
2257 let fork_graph = Arc::new(RwLock::new(TestForkGraph {
2258 relation: BlockRelation::Ancestor,
2259 }));
2260
2261 let program1 = Pubkey::new_unique();
2262 let entries = [
2263 Arc::new(ProgramCacheEntry::new_unloaded(
2264 20,
2265 ProgramCacheEntryOwner::LoaderV3,
2266 ProgramRuntimeEnvironment::clone(&env),
2267 )),
2268 Arc::new(ProgramCacheEntry::new_closed_tombstone(
2269 20,
2270 ProgramCacheEntryOwner::LoaderV3,
2271 )),
2272 Arc::new(ProgramCacheEntry::new_failed_verification_tombstone(
2273 20,
2274 ProgramCacheEntryOwner::LoaderV3,
2275 ProgramRuntimeEnvironment::clone(&env),
2276 )),
2277 ];
2278 for entry in &entries {
2279 let mut cache = ProgramCache::<TestForkGraph>::new(0);
2280 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2281 cache.assign_program(&env, program1, 10, new_test_entry(10));
2283 cache.assign_program(&env, program1, entry.deployment_slot, Arc::clone(entry));
2284 cache.prune(
2285 MAX_TOMBSTONE_AGE_IN_SLOTS,
2286 None,
2287 &fork_graph.read().unwrap(),
2288 );
2289 let slot_versions = cache.get_slot_versions_for_tests(&program1);
2290 assert_eq!(slot_versions, std::slice::from_ref(entry));
2291 cache.prune(
2293 MAX_TOMBSTONE_AGE_IN_SLOTS
2294 .saturating_add(entry.latest_access_slot.load(Ordering::Relaxed)),
2295 None,
2296 &fork_graph.read().unwrap(),
2297 );
2298 let slot_versions = cache.get_slot_versions_for_tests(&program1);
2299 assert_eq!(slot_versions, std::slice::from_ref(entry));
2300 cache.prune(
2302 MAX_TOMBSTONE_AGE_IN_SLOTS
2303 .saturating_add(entry.latest_access_slot.load(Ordering::Relaxed))
2304 .saturating_add(1),
2305 None,
2306 &fork_graph.read().unwrap(),
2307 );
2308 assert!(cache.get_flattened_entries_for_tests().is_empty());
2309 }
2310 }
2311
2312 #[test]
2313 fn test_prune_tombstone_first_ancestor_takes_the_rest() {
2314 let (mut cache, fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
2323 let env = get_mock_program_runtime_environment();
2324 let other_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
2325 let program_id = Pubkey::new_unique();
2326 let on_other_env = new_test_entry_with_owner(
2327 50,
2328 ProgramCacheEntryOwner::LoaderV3,
2329 new_loaded_entry(other_env.clone()),
2330 );
2331 let on_env = new_test_entry_with_owner(
2332 60,
2333 ProgramCacheEntryOwner::LoaderV3,
2334 new_loaded_entry(env.clone()),
2335 );
2336 let closed = new_test_entry_with_owner(
2337 100,
2338 ProgramCacheEntryOwner::LoaderV3,
2339 new_closed_entry(env.clone()),
2340 );
2341 cache.assign_program(&other_env, program_id, 50, Arc::clone(&on_other_env));
2342 cache.assign_program(&env, program_id, 60, Arc::clone(&on_env));
2343 cache.assign_program(&env, program_id, 100, Arc::clone(&closed));
2344
2345 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
2346 assert_eq!(slot_versions.len(), 3);
2347 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &on_other_env));
2348 assert!(Arc::ptr_eq(slot_versions.get(1).unwrap(), &on_env));
2349 assert!(Arc::ptr_eq(slot_versions.get(2).unwrap(), &closed));
2350
2351 cache.prune(200, None, &fork_graph.read().unwrap());
2352
2353 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
2357 assert_eq!(slot_versions.len(), 1);
2358 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &closed));
2359 assert_eq!(cache.stats.prunes_orphan.load(Ordering::Relaxed), 2);
2360 }
2361
2362 #[test]
2363 fn test_prune_tombstone_newer_than_root_keeps_the_rest() {
2364 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
2374 let mut fork_graph = TestForkGraphSpecific::default();
2375 fork_graph.insert_fork(&[50, 60, 100, 101]);
2376 let fork_graph = Arc::new(RwLock::new(fork_graph));
2377 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2378
2379 let env = get_mock_program_runtime_environment();
2380 let other_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
2381 let program_id = Pubkey::new_unique();
2382 let on_other_env = new_test_entry_with_owner(
2383 50,
2384 ProgramCacheEntryOwner::LoaderV3,
2385 new_loaded_entry(other_env.clone()),
2386 );
2387 let on_env = new_test_entry_with_owner(
2388 60,
2389 ProgramCacheEntryOwner::LoaderV3,
2390 new_loaded_entry(env.clone()),
2391 );
2392 let closed = new_test_entry_with_owner(
2393 101,
2394 ProgramCacheEntryOwner::LoaderV3,
2395 new_closed_entry(env.clone()),
2396 );
2397 cache.assign_program(&other_env, program_id, 50, Arc::clone(&on_other_env));
2398 cache.assign_program(&env, program_id, 60, Arc::clone(&on_env));
2399 cache.assign_program(&env, program_id, 101, Arc::clone(&closed));
2400
2401 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
2402 assert_eq!(slot_versions.len(), 3);
2403 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &on_other_env));
2404 assert!(Arc::ptr_eq(slot_versions.get(1).unwrap(), &on_env));
2405 assert!(Arc::ptr_eq(slot_versions.get(2).unwrap(), &closed));
2406
2407 cache.prune(100, None, &fork_graph.read().unwrap());
2408
2409 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
2414 assert_eq!(slot_versions.len(), 3);
2415 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &on_other_env));
2416 assert!(Arc::ptr_eq(slot_versions.get(1).unwrap(), &on_env));
2417 assert!(Arc::ptr_eq(slot_versions.get(2).unwrap(), &closed));
2418 assert_eq!(cache.stats.prunes_orphan.load(Ordering::Relaxed), 0);
2419 }
2420
2421 #[test]
2422 fn test_prune_with_two_environments_before_epoch_boundary() {
2423 let mut cache = ProgramCache::<TestForkGraph>::new(0);
2424 let env = get_mock_program_runtime_environment();
2425 let new_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
2426 let fork_graph = Arc::new(RwLock::new(TestForkGraph {
2427 relation: BlockRelation::Ancestor,
2428 }));
2429 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2430
2431 let program1 = Pubkey::new_unique();
2432 cache.assign_program(&env, program1, 10, new_test_entry(10));
2433 let updated_program = Arc::new(ProgramCacheEntry {
2434 program: new_loaded_entry(new_env.clone()),
2435 deployment_slot: 20,
2436 ..Default::default()
2437 });
2438 cache.assign_program(&env, program1, 20, updated_program.clone());
2439
2440 assert_eq!(cache.get_slot_versions_for_tests(&program1).len(), 2);
2442
2443 cache.prune(21, None, &fork_graph.read().unwrap());
2444
2445 assert_eq!(cache.get_slot_versions_for_tests(&program1).len(), 2);
2447 }
2448
2449 #[test]
2450 fn test_prune_with_two_environments_after_epoch_boundary() {
2451 let mut cache = ProgramCache::<TestForkGraph>::new(0);
2452 let env = get_mock_program_runtime_environment();
2453 let new_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
2454 let fork_graph = Arc::new(RwLock::new(TestForkGraph {
2455 relation: BlockRelation::Ancestor,
2456 }));
2457 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2458 let program1 = Pubkey::new_unique();
2459
2460 let old_program_old_env = Arc::new(ProgramCacheEntry {
2461 program: new_loaded_entry(env.clone()),
2462 deployment_slot: 10,
2463 ..Default::default()
2464 });
2465 let old_program_new_env = Arc::new(ProgramCacheEntry {
2466 program: new_loaded_entry(new_env.clone()),
2467 deployment_slot: 10,
2468 ..Default::default()
2469 });
2470 let new_program_old_env = Arc::new(ProgramCacheEntry {
2471 program: new_loaded_entry(env.clone()),
2472 deployment_slot: 20,
2473 ..Default::default()
2474 });
2475 cache.assign_program(&env, program1, 10, old_program_old_env.clone());
2476 cache.assign_program(&env, program1, 10, old_program_new_env.clone());
2477 cache.assign_program(&env, program1, 20, new_program_old_env.clone());
2478 let slot_versions = cache.get_slot_versions_for_tests(&program1);
2479 assert_eq!(
2480 &slot_versions,
2481 &[
2482 old_program_new_env.clone(),
2483 old_program_old_env.clone(),
2484 new_program_old_env.clone(),
2485 ]
2486 );
2487
2488 cache.prune(21, Some(new_env.clone()), &fork_graph.read().unwrap());
2489 let slot_versions = cache.get_slot_versions_for_tests(&program1);
2490 assert_eq!(&slot_versions, &[old_program_new_env]);
2491 assert!(matches!(
2492 &slot_versions.first().unwrap().program,
2493 ProgramCacheEntryType::Loaded(_)
2494 ));
2495 }
2496
2497 #[test_matrix(
2498 (
2499 new_closed_entry,
2500 new_builtin_entry,
2501 new_failed_verification_entry,
2502 new_unloaded_entry,
2503 new_loaded_entry,
2504 ),
2505 (false, true)
2506 )]
2507 fn test_prune_environment_sweep_by_entry_type(
2508 new_program: fn(ProgramRuntimeEnvironment) -> ProgramCacheEntryType,
2509 on_new_environment: bool,
2510 ) {
2511 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
2521 let mut fork_graph = TestForkGraphSpecific::default();
2522 fork_graph.insert_fork(&[40, 50]);
2523 let fork_graph = Arc::new(RwLock::new(fork_graph));
2524 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2525
2526 let env = get_mock_program_runtime_environment();
2527 let new_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
2528 let entry_env = if on_new_environment {
2529 new_env.clone()
2530 } else {
2531 env.clone()
2532 };
2533 let program_id = Pubkey::new_unique();
2534 let entry = new_test_entry_with_owner(
2535 50,
2536 ProgramCacheEntryOwner::LoaderV3,
2537 new_program(entry_env.clone()),
2538 );
2539 let carries_an_environment = entry.program.get_environment().is_some();
2540 cache.assign_program(&entry_env, program_id, 50, Arc::clone(&entry));
2541
2542 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
2543 assert_eq!(slot_versions.len(), 1);
2544 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &entry));
2545
2546 cache.prune(40, Some(new_env.clone()), &fork_graph.read().unwrap());
2547
2548 let swept = carries_an_environment && !on_new_environment;
2551 assert_eq!(
2552 cache.stats.prunes_environment.load(Ordering::Relaxed),
2553 u64::from(swept)
2554 );
2555 if swept {
2556 assert!(cache.get_slot_versions_for_tests(&program_id).is_empty());
2557 } else {
2558 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
2559 assert_eq!(slot_versions.len(), 1);
2560 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &entry));
2561 }
2562 }
2563
2564 #[test]
2565 fn test_prune_environment_sweep_keeps_tombstones() {
2566 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
2578 let mut fork_graph = TestForkGraphSpecific::default();
2579 fork_graph.insert_fork(&[0, 40, 50, 60, 70, 100]);
2580 let fork_graph = Arc::new(RwLock::new(fork_graph));
2581 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2582 let env = get_mock_program_runtime_environment();
2583 let new_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
2584 let program_id = Pubkey::new_unique();
2585 let closed = new_test_entry_with_owner(
2586 50,
2587 ProgramCacheEntryOwner::LoaderV3,
2588 new_closed_entry(env.clone()),
2589 );
2590 let failed_verification = new_test_entry_with_owner(
2591 60,
2592 ProgramCacheEntryOwner::LoaderV3,
2593 new_failed_verification_entry(env.clone()),
2595 );
2596 let loaded = new_test_entry_with_owner(
2597 70,
2598 ProgramCacheEntryOwner::LoaderV3,
2599 new_loaded_entry(env.clone()),
2600 );
2601 cache.assign_program(&env, program_id, 50, Arc::clone(&closed));
2602 cache.assign_program(&env, program_id, 60, Arc::clone(&failed_verification));
2603 cache.assign_program(&env, program_id, 70, Arc::clone(&loaded));
2604
2605 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
2606 assert_eq!(slot_versions.len(), 3);
2607 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &closed));
2608 assert!(Arc::ptr_eq(
2609 slot_versions.get(1).unwrap(),
2610 &failed_verification
2611 ));
2612 assert!(Arc::ptr_eq(slot_versions.get(2).unwrap(), &loaded));
2613
2614 cache.prune(40, Some(new_env.clone()), &fork_graph.read().unwrap());
2617 assert_eq!(cache.stats.prunes_environment.load(Ordering::Relaxed), 2);
2618 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
2619 assert_eq!(slot_versions.len(), 1);
2620 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &closed));
2621
2622 let mut search_for = vec![ProgramToLoad {
2624 program_id: &program_id,
2625 loader: ProgramCacheEntryOwner::LoaderV3,
2626 deployment_slot: 50,
2627 }];
2628 let mut extracted = ProgramCacheForTxBatch::new(100);
2629 cache.extract(&mut search_for, &mut extracted, &new_env, true, true);
2630 assert!(search_for.is_empty());
2631 assert!(Arc::ptr_eq(
2632 extracted.entries.get(&program_id).unwrap(),
2633 &closed
2634 ));
2635
2636 let mut search_for = vec![ProgramToLoad {
2640 program_id: &program_id,
2641 loader: ProgramCacheEntryOwner::LoaderV3,
2642 deployment_slot: 50,
2643 }];
2644 let mut extracted = ProgramCacheForTxBatch::new(100);
2645 cache.extract(&mut search_for, &mut extracted, &env, true, true);
2646 assert!(search_for.is_empty());
2647 assert!(Arc::ptr_eq(
2648 extracted.entries.get(&program_id).unwrap(),
2649 &closed
2650 ));
2651 }
2652
2653 #[test]
2654 #[should_panic(expected = "self.latest_root_slot <= new_root_slot")]
2655 fn test_prune_backwards_panics() {
2656 let (mut cache, fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
2657 cache.latest_root_slot = 100;
2658
2659 cache.prune(50, None, &fork_graph.read().unwrap());
2662 }
2663
2664 #[derive(Default)]
2665 struct TestForkGraphSpecific {
2666 forks: Vec<Vec<Slot>>,
2667 }
2668
2669 impl TestForkGraphSpecific {
2670 fn insert_fork(&mut self, fork: &[Slot]) {
2671 let mut fork = fork.to_vec();
2672 fork.sort();
2673 self.forks.push(fork)
2674 }
2675 }
2676
2677 impl ForkGraph for TestForkGraphSpecific {
2678 fn relationship(&self, a: Slot, b: Slot) -> BlockRelation {
2679 match self.forks.iter().try_for_each(|fork| {
2680 let relation = fork
2681 .iter()
2682 .position(|x| *x == a)
2683 .and_then(|a_pos| {
2684 fork.iter().position(|x| *x == b).and_then(|b_pos| {
2685 (a_pos == b_pos)
2686 .then_some(BlockRelation::Equal)
2687 .or_else(|| (a_pos < b_pos).then_some(BlockRelation::Ancestor))
2688 .or(Some(BlockRelation::Descendant))
2689 })
2690 })
2691 .unwrap_or(BlockRelation::Unrelated);
2692
2693 if relation != BlockRelation::Unrelated {
2694 return ControlFlow::Break(relation);
2695 }
2696
2697 ControlFlow::Continue(())
2698 }) {
2699 ControlFlow::Break(relation) => relation,
2700 _ => BlockRelation::Unrelated,
2701 }
2702 }
2703 }
2704
2705 fn get_entries_to_load<'a>(
2706 cache: &ProgramCache<TestForkGraphSpecific>,
2707 loading_slot: Slot,
2708 keys: &'a [Pubkey],
2709 ) -> Vec<ProgramToLoad<'a>> {
2710 let fork_graph = cache.fork_graph.as_ref().unwrap().upgrade().unwrap();
2711 let locked_fork_graph = fork_graph.read().unwrap();
2712 let entries = cache.get_flattened_entries_for_tests();
2713 keys.iter()
2714 .filter_map(|key| {
2715 entries
2716 .iter()
2717 .rev()
2718 .find(|(program_id, entry)| {
2719 program_id == key
2720 && matches!(
2721 locked_fork_graph.relationship(entry.deployment_slot, loading_slot),
2722 BlockRelation::Equal | BlockRelation::Ancestor,
2723 )
2724 })
2725 .map(|(_program_id, entry)| ProgramToLoad {
2726 program_id: key,
2727 loader: entry.account_owner,
2728 deployment_slot: entry.deployment_slot,
2729 })
2730 })
2731 .collect()
2732 }
2733
2734 fn match_slot(
2735 extracted: &ProgramCacheForTxBatch,
2736 program: &Pubkey,
2737 deployment_slot: Slot,
2738 working_slot: Slot,
2739 ) -> bool {
2740 assert_eq!(extracted.slot, working_slot);
2741 extracted
2742 .entries
2743 .get(program)
2744 .map(|entry| entry.deployment_slot == deployment_slot)
2745 .unwrap_or(false)
2746 }
2747
2748 fn match_missing(
2749 missing: &[ProgramToLoad],
2750 program_id: &Pubkey,
2751 expected_result: bool,
2752 ) -> bool {
2753 missing.iter().any(|entry| entry.program_id == program_id) == expected_result
2754 }
2755
2756 #[test]
2757 fn test_fork_extract_and_prune() {
2758 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
2759 let env = get_mock_program_runtime_environment();
2760
2761 let mut fork_graph = TestForkGraphSpecific::default();
2777 fork_graph.insert_fork(&[0, 10, 20, 22]);
2778 fork_graph.insert_fork(&[0, 5, 11, 15, 16, 18, 19, 21, 23]);
2779 fork_graph.insert_fork(&[0, 5, 11, 25, 27]);
2780
2781 let fork_graph = Arc::new(RwLock::new(fork_graph));
2782 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2783
2784 let program1 = Pubkey::new_unique();
2785 cache.assign_program(&env, program1, 0, new_test_entry(0));
2786 cache.assign_program(&env, program1, 10, new_test_entry(10));
2787 cache.assign_program(&env, program1, 20, new_test_entry(20));
2788
2789 let program2 = Pubkey::new_unique();
2790 cache.assign_program(&env, program2, 5, new_test_entry(5));
2791 cache.assign_program(&env, program2, 11, new_test_entry(11));
2792
2793 let program3 = Pubkey::new_unique();
2794 cache.assign_program(&env, program3, 25, new_test_entry(25));
2795
2796 let program4 = Pubkey::new_unique();
2797 cache.assign_program(&env, program4, 0, new_test_entry(0));
2798 cache.assign_program(&env, program4, 5, new_test_entry(5));
2799 cache.assign_program(&env, program4, 15, new_test_entry(15));
2801
2802 let keys = &[program1, program2, program3, program4];
2819 let mut missing = get_entries_to_load(&cache, 22, keys);
2820 assert!(match_missing(&missing, &program2, false));
2821 assert!(match_missing(&missing, &program3, false));
2822 let mut extracted = ProgramCacheForTxBatch::new(22);
2823 cache.extract(&mut missing, &mut extracted, &env, true, true);
2824 assert!(match_slot(&extracted, &program1, 20, 22));
2825 assert!(match_slot(&extracted, &program4, 0, 22));
2826
2827 let mut missing = get_entries_to_load(&cache, 15, keys);
2829 assert!(match_missing(&missing, &program3, false));
2830 let mut extracted = ProgramCacheForTxBatch::new(15);
2831 cache.extract(&mut missing, &mut extracted, &env, true, true);
2832 assert!(match_slot(&extracted, &program1, 0, 15));
2833 assert!(match_slot(&extracted, &program2, 11, 15));
2834 let tombstone = extracted
2837 .find(&program4)
2838 .expect("Failed to find the tombstone");
2839 assert_matches!(tombstone.program, ProgramCacheEntryType::DelayVisibility);
2840 assert_eq!(tombstone.deployment_slot, 15);
2841
2842 let mut missing = get_entries_to_load(&cache, 18, keys);
2844 assert!(match_missing(&missing, &program3, false));
2845 let mut extracted = ProgramCacheForTxBatch::new(18);
2846 cache.extract(&mut missing, &mut extracted, &env, true, true);
2847 assert!(match_slot(&extracted, &program1, 0, 18));
2848 assert!(match_slot(&extracted, &program2, 11, 18));
2849 assert!(match_slot(&extracted, &program4, 15, 18));
2851
2852 let mut missing = get_entries_to_load(&cache, 23, keys);
2854 assert!(match_missing(&missing, &program3, false));
2855 let mut extracted = ProgramCacheForTxBatch::new(23);
2856 cache.extract(&mut missing, &mut extracted, &env, true, true);
2857 assert!(match_slot(&extracted, &program1, 0, 23));
2858 assert!(match_slot(&extracted, &program2, 11, 23));
2859 assert!(match_slot(&extracted, &program4, 15, 23));
2861
2862 let mut missing = get_entries_to_load(&cache, 11, keys);
2864 assert!(match_missing(&missing, &program3, false));
2865 let mut extracted = ProgramCacheForTxBatch::new(11);
2866 cache.extract(&mut missing, &mut extracted, &env, true, true);
2867 assert!(match_slot(&extracted, &program1, 0, 11));
2868 let tombstone = extracted
2870 .find(&program2)
2871 .expect("Failed to find the tombstone");
2872 assert_matches!(tombstone.program, ProgramCacheEntryType::DelayVisibility);
2873 assert_eq!(tombstone.deployment_slot, 11);
2874 assert!(match_slot(&extracted, &program4, 5, 11));
2875
2876 cache.prune(5, None, &fork_graph.read().unwrap());
2877
2878 let mut missing = get_entries_to_load(&cache, 21, keys);
2895 assert!(match_missing(&missing, &program3, false));
2896 let mut extracted = ProgramCacheForTxBatch::new(21);
2897 cache.extract(&mut missing, &mut extracted, &env, true, true);
2898 assert!(match_slot(&extracted, &program1, 0, 21));
2900 assert!(match_slot(&extracted, &program2, 11, 21));
2901 assert!(match_slot(&extracted, &program4, 15, 21));
2902
2903 let mut missing = get_entries_to_load(&cache, 27, keys);
2905 let mut extracted = ProgramCacheForTxBatch::new(27);
2906 cache.extract(&mut missing, &mut extracted, &env, true, true);
2907 assert!(match_slot(&extracted, &program1, 0, 27));
2908 assert!(match_slot(&extracted, &program2, 11, 27));
2909 assert!(match_slot(&extracted, &program3, 25, 27));
2910 assert!(match_slot(&extracted, &program4, 5, 27));
2911
2912 cache.prune(15, None, &fork_graph.read().unwrap());
2913
2914 let mut missing = get_entries_to_load(&cache, 23, keys);
2931 assert!(match_missing(&missing, &program3, false));
2932 let mut extracted = ProgramCacheForTxBatch::new(23);
2933 cache.extract(&mut missing, &mut extracted, &env, true, true);
2934 assert!(match_slot(&extracted, &program1, 0, 23));
2935 assert!(match_slot(&extracted, &program2, 11, 23));
2936 assert!(match_slot(&extracted, &program4, 15, 23));
2937 }
2938
2939 #[test]
2940 fn test_extract_using_deployment_slot() {
2941 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
2942 let env = get_mock_program_runtime_environment();
2943
2944 let mut fork_graph = TestForkGraphSpecific::default();
2960 fork_graph.insert_fork(&[0, 10, 20, 22]);
2961 fork_graph.insert_fork(&[0, 5, 11, 12, 15, 16, 18, 19, 21, 23]);
2962 fork_graph.insert_fork(&[0, 5, 11, 25, 27]);
2963
2964 let fork_graph = Arc::new(RwLock::new(fork_graph));
2965 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2966
2967 let program1 = Pubkey::new_unique();
2968 cache.assign_program(&env, program1, 0, new_test_entry(0));
2969 cache.assign_program(&env, program1, 20, new_test_entry(20));
2970
2971 let program2 = Pubkey::new_unique();
2972 cache.assign_program(&env, program2, 5, new_test_entry(5));
2973 cache.assign_program(&env, program2, 11, new_test_entry(11));
2974
2975 let program3 = Pubkey::new_unique();
2976 cache.assign_program(&env, program3, 25, new_test_entry(25));
2977
2978 let keys = &[program1, program2, program3];
2980 let mut missing = get_entries_to_load(&cache, 12, keys);
2981 assert!(match_missing(&missing, &program3, false));
2982 let mut extracted = ProgramCacheForTxBatch::new(12);
2983 cache.extract(&mut missing, &mut extracted, &env, true, true);
2984 assert!(match_slot(&extracted, &program1, 0, 12));
2985 assert!(match_slot(&extracted, &program2, 11, 12));
2986
2987 let mut missing = get_entries_to_load(&cache, 12, keys);
2990 missing.get_mut(1).unwrap().deployment_slot = 5;
2993 assert!(match_missing(&missing, &program3, false));
2994 let mut extracted = ProgramCacheForTxBatch::new(12);
2995 cache.extract(&mut missing, &mut extracted, &env, true, true);
2996 assert!(match_slot(&extracted, &program1, 0, 12));
2997 assert!(match_slot(&extracted, &program2, 5, 12));
2998 }
2999
3000 #[test]
3001 fn test_extract_rejects_entry_deployed_after_the_requested_slot() {
3002 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
3003 let env = get_mock_program_runtime_environment();
3004
3005 let mut fork_graph = TestForkGraphSpecific::default();
3006 fork_graph.insert_fork(&[0, 5, 11, 12]);
3007 let fork_graph = Arc::new(RwLock::new(fork_graph));
3008 cache.set_fork_graph(Arc::downgrade(&fork_graph));
3009
3010 let program = Pubkey::new_unique();
3012 cache.assign_program(&env, program, 11, new_test_entry(11));
3013
3014 let mut missing = vec![ProgramToLoad {
3016 program_id: &program,
3017 loader: ProgramCacheEntryOwner::LoaderV2,
3018 deployment_slot: 5,
3019 }];
3020 let mut extracted = ProgramCacheForTxBatch::new(12);
3021 cache.extract(&mut missing, &mut extracted, &env, true, true);
3022 assert!(match_missing(&missing, &program, true));
3023 assert!(extracted.find(&program).is_none());
3024
3025 let mut missing = vec![ProgramToLoad {
3027 program_id: &program,
3028 loader: ProgramCacheEntryOwner::LoaderV2,
3029 deployment_slot: 11,
3030 }];
3031 let mut extracted = ProgramCacheForTxBatch::new(12);
3032 cache.extract(&mut missing, &mut extracted, &env, true, true);
3033 assert!(match_missing(&missing, &program, false));
3034 assert!(match_slot(&extracted, &program, 11, 12));
3035 }
3036
3037 #[test]
3038 fn test_extract_unloaded() {
3039 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
3040 let env = get_mock_program_runtime_environment();
3041
3042 let mut fork_graph = TestForkGraphSpecific::default();
3058 fork_graph.insert_fork(&[0, 10, 20, 22]);
3059 fork_graph.insert_fork(&[0, 5, 11, 15, 16, 19, 21, 23]);
3060 fork_graph.insert_fork(&[0, 5, 11, 25, 27]);
3061
3062 let fork_graph = Arc::new(RwLock::new(fork_graph));
3063 cache.set_fork_graph(Arc::downgrade(&fork_graph));
3064
3065 let program1 = Pubkey::new_unique();
3066 cache.assign_program(&env, program1, 0, new_test_entry(0));
3067 cache.assign_program(&env, program1, 20, new_test_entry(20));
3068
3069 let program2 = Pubkey::new_unique();
3070 cache.assign_program(&env, program2, 5, new_test_entry(5));
3071 cache.assign_program(&env, program2, 11, new_test_entry(11));
3072
3073 let program3 = Pubkey::new_unique();
3074 let _ = insert_unloaded_entry(&mut cache, program3, 25);
3076
3077 cache.assign_program(
3081 &env,
3082 program3,
3083 20,
3084 Arc::new(
3085 new_test_entry(20)
3086 .to_unloaded()
3087 .expect("Failed to create unloaded program"),
3088 ),
3089 );
3090
3091 let keys = &[program1, program2, program3];
3093 let mut missing = get_entries_to_load(&cache, 19, keys);
3094 assert!(match_missing(&missing, &program3, false));
3095 let mut extracted = ProgramCacheForTxBatch::new(19);
3096 cache.extract(&mut missing, &mut extracted, &env, true, true);
3097 assert!(match_slot(&extracted, &program1, 0, 19));
3098 assert!(match_slot(&extracted, &program2, 11, 19));
3099
3100 let mut missing = get_entries_to_load(&cache, 27, keys);
3102 let mut extracted = ProgramCacheForTxBatch::new(27);
3103 cache.extract(&mut missing, &mut extracted, &env, true, true);
3104 assert!(match_slot(&extracted, &program1, 0, 27));
3105 assert!(match_slot(&extracted, &program2, 11, 27));
3106 assert!(match_missing(&missing, &program3, true));
3107
3108 let mut missing = get_entries_to_load(&cache, 22, keys);
3110 assert!(match_missing(&missing, &program2, false));
3111 let mut extracted = ProgramCacheForTxBatch::new(22);
3112 cache.extract(&mut missing, &mut extracted, &env, true, true);
3113 assert!(match_slot(&extracted, &program1, 20, 22));
3114 assert!(match_missing(&missing, &program3, true));
3115 }
3116
3117 #[test]
3118 fn test_extract_different_environment() {
3119 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
3120 let env = get_mock_program_runtime_environment();
3121 let other_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
3122
3123 let mut fork_graph = TestForkGraphSpecific::default();
3133 fork_graph.insert_fork(&[0, 10, 20, 22]);
3134
3135 let fork_graph = Arc::new(RwLock::new(fork_graph));
3136 cache.set_fork_graph(Arc::downgrade(&fork_graph));
3137
3138 let program1 = Pubkey::new_unique();
3139 cache.assign_program(
3140 &env,
3141 program1,
3142 10,
3143 Arc::new(ProgramCacheEntry::new_closed_tombstone(
3144 10,
3145 ProgramCacheEntryOwner::LoaderV3,
3146 )),
3147 );
3148 cache.assign_program(&env, program1, 20, new_test_entry(20));
3149
3150 let keys = &[program1];
3152 let mut missing = get_entries_to_load(&cache, 22, keys);
3153 let mut extracted = ProgramCacheForTxBatch::new(22);
3154 cache.extract(&mut missing, &mut extracted, &env, true, true);
3155 assert!(match_slot(&extracted, &program1, 20, 22));
3156
3157 let mut missing = get_entries_to_load(&cache, 22, keys);
3159 let mut extracted = ProgramCacheForTxBatch::new(22);
3160 cache.extract(&mut missing, &mut extracted, &other_env, true, true);
3161 assert!(match_missing(&missing, &program1, true));
3162 }
3163
3164 #[test_matrix((false, true))]
3165 fn test_extract_no_second_level(empty_second_level: bool) {
3166 let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
3167 let env = get_mock_program_runtime_environment();
3168 let program_id = Pubkey::new_unique();
3169 if empty_second_level {
3170 match &mut cache.index {
3172 IndexImplementation::V1 { entries, .. } => {
3173 entries.insert(program_id, Vec::new());
3174 }
3175 }
3176 }
3177
3178 let mut search_for = vec![ProgramToLoad {
3181 program_id: &program_id,
3182 loader: ProgramCacheEntryOwner::LoaderV3,
3183 deployment_slot: 0,
3184 }];
3185 let mut extracted = ProgramCacheForTxBatch::new(100);
3186 let task = cache.extract(&mut search_for, &mut extracted, &env, true, true);
3187 assert_eq!(search_for.len(), 1);
3188 assert!(extracted.entries.is_empty());
3189 assert_eq!(task, Some(program_id));
3190 }
3191
3192 #[test]
3193 fn test_extract_account_owner_mismatch() {
3194 let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
3195 let env = get_mock_program_runtime_environment();
3196 let program_id = Pubkey::new_unique();
3197 let owned_by_v2 = new_test_entry_with_owner(
3198 100,
3199 ProgramCacheEntryOwner::LoaderV2,
3200 new_loaded_entry(env.clone()),
3201 );
3202 cache.assign_program(&env, program_id, 100, Arc::clone(&owned_by_v2));
3203
3204 let mut search_for = vec![ProgramToLoad {
3207 program_id: &program_id,
3208 loader: ProgramCacheEntryOwner::LoaderV3,
3209 deployment_slot: 100,
3210 }];
3211 let mut extracted = ProgramCacheForTxBatch::new(200);
3212 cache.extract(&mut search_for, &mut extracted, &env, true, true);
3213 assert_eq!(search_for.len(), 1);
3214 assert!(extracted.entries.is_empty());
3215
3216 let owned_by_v3 = new_test_entry_with_owner(
3219 150,
3220 ProgramCacheEntryOwner::LoaderV3,
3221 new_loaded_entry(env.clone()),
3222 );
3223 cache.assign_program(&env, program_id, 150, Arc::clone(&owned_by_v3));
3224
3225 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
3227 assert_eq!(slot_versions.len(), 2);
3228 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &owned_by_v2));
3229 assert!(Arc::ptr_eq(slot_versions.get(1).unwrap(), &owned_by_v3));
3230
3231 let mut search_for = vec![ProgramToLoad {
3234 program_id: &program_id,
3235 loader: ProgramCacheEntryOwner::LoaderV2,
3236 deployment_slot: 100,
3237 }];
3238 let mut extracted = ProgramCacheForTxBatch::new(200);
3239 cache.extract(&mut search_for, &mut extracted, &env, true, true);
3240 assert!(search_for.is_empty());
3241 assert!(Arc::ptr_eq(
3242 extracted.entries.get(&program_id).unwrap(),
3243 &owned_by_v2
3244 ));
3245
3246 let mut search_for = vec![ProgramToLoad {
3249 program_id: &program_id,
3250 loader: ProgramCacheEntryOwner::LoaderV3,
3251 deployment_slot: 150,
3252 }];
3253 let mut extracted = ProgramCacheForTxBatch::new(200);
3254 cache.extract(&mut search_for, &mut extracted, &env, true, true);
3255 assert!(search_for.is_empty());
3256 assert!(Arc::ptr_eq(
3257 extracted.entries.get(&program_id).unwrap(),
3258 &owned_by_v3
3259 ));
3260 }
3261
3262 #[test]
3263 fn test_extract_environment_mismatch() {
3264 let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
3265 let env = get_mock_program_runtime_environment();
3266 let other_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
3267 let program_id = Pubkey::new_unique();
3268 let on_other_env = new_test_entry_with_owner(
3269 100,
3270 ProgramCacheEntryOwner::LoaderV3,
3271 new_loaded_entry(other_env.clone()),
3272 );
3273 cache.assign_program(&other_env, program_id, 100, Arc::clone(&on_other_env));
3274
3275 let mut search_for = vec![ProgramToLoad {
3280 program_id: &program_id,
3281 loader: ProgramCacheEntryOwner::LoaderV3,
3282 deployment_slot: 100,
3283 }];
3284 let mut extracted = ProgramCacheForTxBatch::new(200);
3285 cache.extract(&mut search_for, &mut extracted, &env, true, true);
3286 assert_eq!(search_for.len(), 1);
3287 assert!(extracted.entries.is_empty());
3288
3289 let on_execution_env = new_test_entry_with_owner(
3292 100,
3293 ProgramCacheEntryOwner::LoaderV3,
3294 new_loaded_entry(env.clone()),
3295 );
3296 cache.assign_program(&env, program_id, 100, Arc::clone(&on_execution_env));
3297
3298 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
3301 assert_eq!(slot_versions.len(), 2);
3302 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &on_other_env));
3303 assert!(Arc::ptr_eq(
3304 slot_versions.get(1).unwrap(),
3305 &on_execution_env
3306 ));
3307
3308 let mut search_for = vec![ProgramToLoad {
3311 program_id: &program_id,
3312 loader: ProgramCacheEntryOwner::LoaderV3,
3313 deployment_slot: 100,
3314 }];
3315 let mut extracted = ProgramCacheForTxBatch::new(200);
3316 cache.extract(&mut search_for, &mut extracted, &env, true, true);
3317 assert!(search_for.is_empty());
3318 assert!(Arc::ptr_eq(
3319 extracted.entries.get(&program_id).unwrap(),
3320 &on_execution_env
3321 ));
3322
3323 let mut search_for = vec![ProgramToLoad {
3326 program_id: &program_id,
3327 loader: ProgramCacheEntryOwner::LoaderV3,
3328 deployment_slot: 100,
3329 }];
3330 let mut extracted = ProgramCacheForTxBatch::new(200);
3331 cache.extract(&mut search_for, &mut extracted, &other_env, true, true);
3332 assert!(search_for.is_empty());
3333 assert!(Arc::ptr_eq(
3334 extracted.entries.get(&program_id).unwrap(),
3335 &on_other_env
3336 ));
3337 }
3338
3339 #[test]
3340 fn test_extract_unloaded_entry() {
3341 let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
3342 let env = get_mock_program_runtime_environment();
3343 let program_id = Pubkey::new_unique();
3344 let unloaded = new_test_entry_with_owner(
3345 100,
3346 ProgramCacheEntryOwner::LoaderV3,
3347 new_unloaded_entry(env.clone()),
3348 );
3349 cache.assign_program(&env, program_id, 100, Arc::clone(&unloaded));
3350
3351 let mut search_for = vec![ProgramToLoad {
3355 program_id: &program_id,
3356 loader: ProgramCacheEntryOwner::LoaderV3,
3357 deployment_slot: 100,
3358 }];
3359 let mut extracted = ProgramCacheForTxBatch::new(200);
3360 cache.extract(&mut search_for, &mut extracted, &env, true, true);
3361 assert_eq!(search_for.len(), 1);
3362 assert!(extracted.entries.is_empty());
3363
3364 let loaded = new_test_entry_with_owner(
3366 100,
3367 ProgramCacheEntryOwner::LoaderV3,
3368 new_loaded_entry(env.clone()),
3369 );
3370 cache.assign_program(&env, program_id, 100, Arc::clone(&loaded));
3371 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
3372 assert_eq!(slot_versions.len(), 1);
3373 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &loaded));
3374
3375 let mut search_for = vec![ProgramToLoad {
3377 program_id: &program_id,
3378 loader: ProgramCacheEntryOwner::LoaderV3,
3379 deployment_slot: 100,
3380 }];
3381 let mut extracted = ProgramCacheForTxBatch::new(200);
3382 cache.extract(&mut search_for, &mut extracted, &env, true, true);
3383 assert!(search_for.is_empty());
3384 assert!(Arc::ptr_eq(
3385 extracted.entries.get(&program_id).unwrap(),
3386 &loaded
3387 ));
3388 }
3389
3390 #[test_matrix(
3391 (
3392 new_closed_entry,
3393 new_builtin_entry,
3394 new_failed_verification_entry,
3395 new_unloaded_entry,
3396 new_loaded_entry,
3397 ),
3398 (100, 101)
3399 )]
3400 fn test_extract_effective_slot(
3401 new_program: fn(ProgramRuntimeEnvironment) -> ProgramCacheEntryType,
3402 batch_slot: Slot,
3403 ) {
3404 let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
3418 let env = get_mock_program_runtime_environment();
3419 let program_id = Pubkey::new_unique();
3420 let entry = new_test_entry_with_owner(
3421 100,
3422 ProgramCacheEntryOwner::LoaderV3,
3423 new_program(env.clone()),
3424 );
3425 cache.assign_program(&env, program_id, 100, Arc::clone(&entry));
3426
3427 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
3428 assert_eq!(slot_versions.len(), 1);
3429 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &entry));
3430
3431 let mut search_for = vec![ProgramToLoad {
3432 program_id: &program_id,
3433 loader: ProgramCacheEntryOwner::LoaderV3,
3434 deployment_slot: 100,
3435 }];
3436 let mut extracted = ProgramCacheForTxBatch::new(batch_slot);
3437 cache.extract(&mut search_for, &mut extracted, &env, true, true);
3438
3439 if batch_slot < entry.effective_slot() {
3440 assert!(search_for.is_empty());
3444 let tombstone = extracted.entries.get(&program_id).unwrap();
3445 assert_matches!(tombstone.program, ProgramCacheEntryType::DelayVisibility);
3446 assert_eq!(tombstone.account_owner, ProgramCacheEntryOwner::LoaderV3);
3447 assert_eq!(tombstone.deployment_slot, 100);
3448 assert!(Arc::ptr_eq(&tombstone.stats, &entry.stats)); assert_eq!(entry.stats.uses.load(Ordering::Relaxed), 1);
3450
3451 assert_eq!(entry.latest_access_slot.load(Ordering::Relaxed), batch_slot);
3455 assert_eq!(tombstone.latest_access_slot.load(Ordering::Relaxed), 0);
3456 } else if matches!(entry.program, ProgramCacheEntryType::Unloaded(_)) {
3457 assert!(extracted.entries.is_empty());
3461 assert_eq!(search_for.len(), 1);
3462 assert_eq!(entry.stats.uses.load(Ordering::Relaxed), 0);
3463 assert_eq!(entry.latest_access_slot.load(Ordering::Relaxed), 0);
3464 } else {
3465 assert!(search_for.is_empty());
3468 assert!(Arc::ptr_eq(
3469 extracted.entries.get(&program_id).unwrap(),
3470 &entry
3471 ));
3472 assert_eq!(entry.stats.uses.load(Ordering::Relaxed), 1);
3473 assert_eq!(entry.latest_access_slot.load(Ordering::Relaxed), batch_slot);
3474 }
3475 }
3476
3477 #[test]
3478 fn test_extract_closed_entry_matches_any_env() {
3479 let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
3489 let env = get_mock_program_runtime_environment();
3490 let other_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
3491 let program_id = Pubkey::new_unique();
3492 let closed = new_test_entry_with_owner(
3493 100,
3494 ProgramCacheEntryOwner::LoaderV3,
3495 new_closed_entry(env.clone()), );
3497 assert_eq!(closed.effective_slot(), closed.deployment_slot);
3498 cache.assign_program(&env, program_id, 100, Arc::clone(&closed));
3499
3500 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
3501 assert_eq!(slot_versions.len(), 1);
3502 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &closed));
3503
3504 let mut search_for = vec![ProgramToLoad {
3506 program_id: &program_id,
3507 loader: ProgramCacheEntryOwner::LoaderV3,
3508 deployment_slot: 100,
3509 }];
3510 let mut extracted = ProgramCacheForTxBatch::new(100);
3511 cache.extract(&mut search_for, &mut extracted, &env, true, true);
3512 assert!(Arc::ptr_eq(
3513 extracted.entries.get(&program_id).unwrap(),
3514 &closed
3515 ));
3516 assert!(search_for.is_empty());
3517
3518 let mut search_for = vec![ProgramToLoad {
3520 program_id: &program_id,
3521 loader: ProgramCacheEntryOwner::LoaderV3,
3522 deployment_slot: 100,
3523 }];
3524 let mut extracted = ProgramCacheForTxBatch::new(100);
3525 cache.extract(&mut search_for, &mut extracted, &other_env, true, true);
3526 assert!(Arc::ptr_eq(
3527 extracted.entries.get(&program_id).unwrap(),
3528 &closed
3529 ));
3530 assert!(search_for.is_empty());
3531 }
3532
3533 #[test]
3534 fn test_extract_delay_visibility_tombstone_interleaved_environments() {
3535 let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
3558 let env = get_mock_program_runtime_environment();
3559 let upcoming_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
3560 let program_id = Pubkey::new_unique();
3561 let on_execution_env = new_test_entry_with_owner(
3562 100,
3563 ProgramCacheEntryOwner::LoaderV3,
3564 new_loaded_entry(env.clone()),
3565 );
3566 let on_upcoming_env = new_test_entry_with_owner(
3567 100,
3568 ProgramCacheEntryOwner::LoaderV3,
3569 new_loaded_entry(upcoming_env.clone()),
3570 );
3571 cache.assign_program(&env, program_id, 100, Arc::clone(&on_execution_env));
3572 cache.assign_program(&upcoming_env, program_id, 100, Arc::clone(&on_upcoming_env));
3573
3574 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
3575 assert_eq!(slot_versions.len(), 2);
3576 assert!(Arc::ptr_eq(
3577 slot_versions.first().unwrap(),
3578 &on_execution_env
3579 ));
3580 assert!(Arc::ptr_eq(slot_versions.get(1).unwrap(), &on_upcoming_env));
3581
3582 let mut search_for = vec![ProgramToLoad {
3584 program_id: &program_id,
3585 loader: ProgramCacheEntryOwner::LoaderV3,
3586 deployment_slot: 100,
3587 }];
3588 let mut extracted = ProgramCacheForTxBatch::new(100);
3589 cache.extract(&mut search_for, &mut extracted, &env, true, true);
3590 assert!(search_for.is_empty());
3591
3592 let tombstone = extracted.entries.get(&program_id).unwrap();
3594 assert_matches!(tombstone.program, ProgramCacheEntryType::DelayVisibility);
3595
3596 assert!(Arc::ptr_eq(&tombstone.stats, &on_upcoming_env.stats));
3604 assert_eq!(on_upcoming_env.stats.uses.load(Ordering::Relaxed), 1);
3605 assert_eq!(on_execution_env.stats.uses.load(Ordering::Relaxed), 0);
3606
3607 let mut search_for = vec![ProgramToLoad {
3610 program_id: &program_id,
3611 loader: ProgramCacheEntryOwner::LoaderV3,
3612 deployment_slot: 100,
3613 }];
3614 let mut extracted = ProgramCacheForTxBatch::new(101);
3615 cache.extract(&mut search_for, &mut extracted, &env, true, true);
3616 assert!(search_for.is_empty());
3617 assert!(Arc::ptr_eq(
3618 extracted.entries.get(&program_id).unwrap(),
3619 &on_execution_env
3620 ));
3621
3622 assert_eq!(on_upcoming_env.stats.uses.load(Ordering::Relaxed), 1);
3624 assert_eq!(on_execution_env.stats.uses.load(Ordering::Relaxed), 1);
3625 }
3626
3627 #[test_case(false)]
3628 #[test_case(true)]
3629 fn test_extract_environment_filter_same_slot(other_env_first: bool) {
3630 let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
3631 let env = get_mock_program_runtime_environment();
3632 let other_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
3633 let program_id = Pubkey::new_unique();
3634
3635 let on_other_env = new_test_entry_with_owner(
3637 100,
3638 ProgramCacheEntryOwner::LoaderV3,
3639 new_loaded_entry(other_env.clone()),
3640 );
3641 let on_execution_env = new_test_entry_with_owner(
3642 100,
3643 ProgramCacheEntryOwner::LoaderV3,
3644 new_loaded_entry(env.clone()),
3645 );
3646 if other_env_first {
3647 cache.assign_program(&other_env, program_id, 100, Arc::clone(&on_other_env));
3648 cache.assign_program(&env, program_id, 100, Arc::clone(&on_execution_env));
3649 } else {
3650 cache.assign_program(&env, program_id, 100, Arc::clone(&on_execution_env));
3651 cache.assign_program(&other_env, program_id, 100, Arc::clone(&on_other_env));
3652 }
3653
3654 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
3657 assert_eq!(slot_versions.len(), 2);
3658 let (first, second) = if other_env_first {
3659 (&on_other_env, &on_execution_env)
3660 } else {
3661 (&on_execution_env, &on_other_env)
3662 };
3663 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), first));
3664 assert!(Arc::ptr_eq(slot_versions.get(1).unwrap(), second));
3665
3666 let mut search_for = vec![ProgramToLoad {
3669 program_id: &program_id,
3670 loader: ProgramCacheEntryOwner::LoaderV3,
3671 deployment_slot: 100,
3672 }];
3673 let mut extracted = ProgramCacheForTxBatch::new(200);
3674 cache.extract(&mut search_for, &mut extracted, &env, true, true);
3675 assert!(Arc::ptr_eq(
3676 extracted.entries.get(&program_id).unwrap(),
3677 &on_execution_env
3678 ));
3679
3680 let mut search_for = vec![ProgramToLoad {
3682 program_id: &program_id,
3683 loader: ProgramCacheEntryOwner::LoaderV3,
3684 deployment_slot: 100,
3685 }];
3686 let mut extracted = ProgramCacheForTxBatch::new(200);
3687 cache.extract(&mut search_for, &mut extracted, &other_env, true, true);
3688 assert!(Arc::ptr_eq(
3689 extracted.entries.get(&program_id).unwrap(),
3690 &on_other_env
3691 ));
3692 }
3693
3694 #[test_matrix((false, true))]
3695 fn test_extract_usage_counter(increment_usage_counter: bool) {
3696 let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
3697 let env = get_mock_program_runtime_environment();
3698 let program_id = Pubkey::new_unique();
3699 let entry = new_test_entry_with_owner(
3700 100,
3701 ProgramCacheEntryOwner::LoaderV3,
3702 new_loaded_entry(env.clone()),
3703 );
3704 cache.assign_program(&env, program_id, 100, Arc::clone(&entry));
3705
3706 let mut search_for = vec![ProgramToLoad {
3707 program_id: &program_id,
3708 loader: ProgramCacheEntryOwner::LoaderV3,
3709 deployment_slot: 100,
3710 }];
3711 let mut extracted = ProgramCacheForTxBatch::new(200);
3712 cache.extract(
3713 &mut search_for,
3714 &mut extracted,
3715 &env,
3716 increment_usage_counter,
3717 true,
3718 );
3719
3720 assert_eq!(entry.latest_access_slot.load(Ordering::Relaxed), 200);
3722 assert_eq!(
3723 entry.stats.uses.load(Ordering::Relaxed),
3724 u64::from(increment_usage_counter)
3725 );
3726 }
3727
3728 #[test]
3729 fn test_extract_usage_counter_delayed_visibility() {
3730 let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
3731 let env = get_mock_program_runtime_environment();
3732 let program_id = Pubkey::new_unique();
3733 let entry = new_test_entry_with_owner(
3734 100,
3735 ProgramCacheEntryOwner::LoaderV3,
3736 new_loaded_entry(env.clone()),
3737 );
3738 cache.assign_program(&env, program_id, 100, Arc::clone(&entry));
3739
3740 let mut search_for = vec![ProgramToLoad {
3744 program_id: &program_id,
3745 loader: ProgramCacheEntryOwner::LoaderV3,
3746 deployment_slot: 100,
3747 }];
3748 let mut extracted = ProgramCacheForTxBatch::new(100);
3749 cache.extract(&mut search_for, &mut extracted, &env, true, true);
3750
3751 let tombstone = extracted.entries.get(&program_id).unwrap();
3752 assert!(matches!(
3753 tombstone.program,
3754 ProgramCacheEntryType::DelayVisibility
3755 ));
3756 assert!(!Arc::ptr_eq(tombstone, &entry));
3757
3758 assert_eq!(entry.latest_access_slot.load(Ordering::Relaxed), 100);
3761 assert_eq!(tombstone.latest_access_slot.load(Ordering::Relaxed), 0);
3762
3763 assert!(Arc::ptr_eq(&tombstone.stats, &entry.stats));
3766 assert_eq!(entry.stats.uses.load(Ordering::Relaxed), 1);
3767 }
3768
3769 #[test_matrix((false, true))]
3770 fn test_extract_hits_and_misses(count_hits_and_misses: bool) {
3771 let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
3772 let env = get_mock_program_runtime_environment();
3773 let found = Pubkey::new_unique();
3774 let missing = Pubkey::new_unique();
3775 cache.assign_program(
3776 &env,
3777 found,
3778 100,
3779 new_test_entry_with_owner(
3780 100,
3781 ProgramCacheEntryOwner::LoaderV3,
3782 new_loaded_entry(env.clone()),
3783 ),
3784 );
3785
3786 let mut search_for = vec![
3787 ProgramToLoad {
3788 program_id: &found,
3789 loader: ProgramCacheEntryOwner::LoaderV3,
3790 deployment_slot: 100,
3791 },
3792 ProgramToLoad {
3793 program_id: &missing,
3794 loader: ProgramCacheEntryOwner::LoaderV3,
3795 deployment_slot: 0,
3796 },
3797 ];
3798 let mut extracted = ProgramCacheForTxBatch::new(200);
3799 cache.extract(
3800 &mut search_for,
3801 &mut extracted,
3802 &env,
3803 true,
3804 count_hits_and_misses,
3805 );
3806
3807 let expected = u64::from(count_hits_and_misses);
3808 assert_eq!(cache.stats.hits.load(Ordering::Relaxed), expected);
3809 assert_eq!(cache.stats.misses.load(Ordering::Relaxed), expected);
3810 }
3811
3812 #[test]
3813 fn test_extract_hits_count_only_this_call() {
3814 let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
3815 let env = get_mock_program_runtime_environment();
3816 let program_id = Pubkey::new_unique();
3817 cache.assign_program(
3818 &env,
3819 program_id,
3820 100,
3821 new_test_entry_with_owner(
3822 100,
3823 ProgramCacheEntryOwner::LoaderV3,
3824 new_loaded_entry(env.clone()),
3825 ),
3826 );
3827
3828 let mut extracted = ProgramCacheForTxBatch::new(200);
3831 extracted.replenish(Pubkey::new_unique(), new_test_builtin_entry(0));
3832
3833 let mut search_for = vec![ProgramToLoad {
3836 program_id: &program_id,
3837 loader: ProgramCacheEntryOwner::LoaderV3,
3838 deployment_slot: 100,
3839 }];
3840 cache.extract(&mut search_for, &mut extracted, &env, true, true);
3841 assert_eq!(extracted.entries.len(), 2);
3842 assert_eq!(cache.stats.hits.load(Ordering::Relaxed), 1);
3843 }
3844
3845 #[test]
3846 fn test_extract_cooperative_loading_task() {
3847 let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
3848 let env = get_mock_program_runtime_environment();
3849 let program_ids = [Pubkey::new_unique(), Pubkey::new_unique()];
3850
3851 let mut search_for = program_ids
3853 .iter()
3854 .map(|program_id| ProgramToLoad {
3855 program_id,
3856 loader: ProgramCacheEntryOwner::LoaderV3,
3857 deployment_slot: 0,
3858 })
3859 .collect();
3860 let mut extracted = ProgramCacheForTxBatch::new(100);
3861 let task = cache.extract(&mut search_for, &mut extracted, &env, true, true);
3862 assert_eq!(search_for.len(), 2);
3863 assert_eq!(task, program_ids.first().copied());
3864 match &cache.index {
3865 IndexImplementation::V1 {
3866 loading_entries, ..
3867 } => {
3868 let loading_entries = loading_entries.lock().unwrap();
3869 assert_eq!(loading_entries.len(), 1);
3870 assert_eq!(
3871 loading_entries.get(program_ids.first().unwrap()),
3872 Some(&(100, thread::current().id()))
3873 );
3874 }
3875 }
3876
3877 let mut search_for = vec![ProgramToLoad {
3879 program_id: program_ids.first().unwrap(),
3880 loader: ProgramCacheEntryOwner::LoaderV3,
3881 deployment_slot: 0,
3882 }];
3883 let task = cache.extract(&mut search_for, &mut extracted, &env, true, true);
3884 assert_eq!(search_for.len(), 1);
3885 assert_eq!(task, None);
3886
3887 let cookie = cache.loading_task_waiter.cookie();
3889 let loaded = new_test_entry_with_owner(
3890 50,
3891 ProgramCacheEntryOwner::LoaderV3,
3892 new_loaded_entry(env.clone()),
3893 );
3894 cache.finish_cooperative_loading_task(
3895 &env,
3896 100,
3897 *program_ids.first().unwrap(),
3898 Arc::clone(&loaded),
3899 );
3900 assert_ne!(cache.loading_task_waiter.wait(cookie), cookie);
3901
3902 match &cache.index {
3904 IndexImplementation::V1 {
3905 loading_entries, ..
3906 } => assert!(loading_entries.lock().unwrap().is_empty()),
3907 }
3908 let mut search_for = vec![ProgramToLoad {
3909 program_id: program_ids.first().unwrap(),
3910 loader: ProgramCacheEntryOwner::LoaderV3,
3911 deployment_slot: 50,
3912 }];
3913 let mut extracted = ProgramCacheForTxBatch::new(100);
3914 let task = cache.extract(&mut search_for, &mut extracted, &env, true, true);
3915 assert!(search_for.is_empty());
3916 assert_eq!(task, None);
3917 assert!(Arc::ptr_eq(
3918 extracted.entries.get(program_ids.first().unwrap()).unwrap(),
3919 &loaded
3920 ));
3921 }
3922
3923 #[test]
3924 fn test_extract_cooperative_loading_task_ordering() {
3925 let (cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
3926 let env = get_mock_program_runtime_environment();
3927 let program_ids = [Pubkey::new_unique(), Pubkey::new_unique()];
3928 let mut extracted = ProgramCacheForTxBatch::new(100);
3929
3930 let mut search_for = program_ids
3932 .iter()
3933 .map(|program_id| ProgramToLoad {
3934 program_id,
3935 loader: ProgramCacheEntryOwner::LoaderV3,
3936 deployment_slot: 0,
3937 })
3938 .collect();
3939 let task = cache.extract(&mut search_for, &mut extracted, &env, true, true);
3940 assert_eq!(task, program_ids.first().copied());
3941
3942 let mut search_for = program_ids
3945 .iter()
3946 .rev()
3947 .map(|program_id| ProgramToLoad {
3948 program_id,
3949 loader: ProgramCacheEntryOwner::LoaderV3,
3950 deployment_slot: 0,
3951 })
3952 .collect();
3953 let task = cache.extract(&mut search_for, &mut extracted, &env, true, true);
3954 assert_eq!(task, program_ids.get(1).copied());
3955
3956 match &cache.index {
3958 IndexImplementation::V1 {
3959 loading_entries, ..
3960 } => {
3961 let loading_entries = loading_entries.lock().unwrap();
3962 assert_eq!(loading_entries.len(), 2);
3963 for program_id in &program_ids {
3964 assert_eq!(
3965 loading_entries.get(program_id),
3966 Some(&(100, thread::current().id()))
3967 );
3968 }
3969 }
3970 }
3971
3972 let mut search_for = program_ids
3974 .iter()
3975 .map(|program_id| ProgramToLoad {
3976 program_id,
3977 loader: ProgramCacheEntryOwner::LoaderV3,
3978 deployment_slot: 0,
3979 })
3980 .collect();
3981 let task = cache.extract(&mut search_for, &mut extracted, &env, true, true);
3982 assert_eq!(search_for.len(), 2);
3983 assert_eq!(task, None);
3984 }
3985
3986 #[test]
3987 fn test_extract_entry_not_in_same_branch() {
3988 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
3995 let mut fork_graph = TestForkGraphSpecific::default();
3996 fork_graph.insert_fork(&[0, 50, 200]);
3997 fork_graph.insert_fork(&[0, 100]);
3998 let fork_graph = Arc::new(RwLock::new(fork_graph));
3999 cache.set_fork_graph(Arc::downgrade(&fork_graph));
4000
4001 let env = get_mock_program_runtime_environment();
4002 let program_id = Pubkey::new_unique();
4003 let on_other_fork = new_test_entry_with_owner(
4004 100,
4005 ProgramCacheEntryOwner::LoaderV3,
4006 new_loaded_entry(env.clone()),
4007 );
4008 cache.assign_program(&env, program_id, 100, Arc::clone(&on_other_fork));
4009
4010 let mut search_for = vec![ProgramToLoad {
4020 program_id: &program_id,
4021 loader: ProgramCacheEntryOwner::LoaderV3,
4022 deployment_slot: 100,
4023 }];
4024 let mut extracted = ProgramCacheForTxBatch::new(200);
4025 cache.extract(&mut search_for, &mut extracted, &env, true, true);
4026 assert_eq!(search_for.len(), 1);
4027 assert!(extracted.entries.is_empty());
4028
4029 let on_same_fork = new_test_entry_with_owner(
4031 50,
4032 ProgramCacheEntryOwner::LoaderV3,
4033 new_loaded_entry(env.clone()),
4034 );
4035 cache.assign_program(&env, program_id, 50, Arc::clone(&on_same_fork));
4036
4037 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
4039 assert_eq!(slot_versions.len(), 2);
4040 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &on_same_fork));
4041 assert!(Arc::ptr_eq(slot_versions.get(1).unwrap(), &on_other_fork));
4042
4043 let mut search_for = vec![ProgramToLoad {
4046 program_id: &program_id,
4047 loader: ProgramCacheEntryOwner::LoaderV3,
4048 deployment_slot: 50,
4049 }];
4050 let mut extracted = ProgramCacheForTxBatch::new(200);
4051 cache.extract(&mut search_for, &mut extracted, &env, true, true);
4052 assert!(search_for.is_empty());
4053 assert!(Arc::ptr_eq(
4054 extracted.entries.get(&program_id).unwrap(),
4055 &on_same_fork
4056 ));
4057 }
4058
4059 #[test]
4060 fn test_extract_deployment_slot_mismatch() {
4061 let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
4077 assert_eq!(cache.latest_root_slot, 0);
4078 let env = get_mock_program_runtime_environment();
4079 let program_id = Pubkey::new_unique();
4080 let deployed_at_100 = new_test_entry_with_owner(
4081 100,
4082 ProgramCacheEntryOwner::LoaderV3,
4083 new_loaded_entry(env.clone()),
4084 );
4085 cache.assign_program(&env, program_id, 100, Arc::clone(&deployed_at_100));
4086
4087 let mut search_for = vec![ProgramToLoad {
4091 program_id: &program_id,
4092 loader: ProgramCacheEntryOwner::LoaderV3,
4093 deployment_slot: 150,
4094 }];
4095 let mut extracted = ProgramCacheForTxBatch::new(200);
4096 cache.extract(&mut search_for, &mut extracted, &env, true, true);
4097 assert_eq!(search_for.len(), 1);
4098 assert!(extracted.entries.is_empty());
4099
4100 let deployed_at_150 = new_test_entry_with_owner(
4102 150,
4103 ProgramCacheEntryOwner::LoaderV3,
4104 new_loaded_entry(env.clone()),
4105 );
4106 cache.assign_program(&env, program_id, 150, Arc::clone(&deployed_at_150));
4107
4108 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
4111 assert_eq!(slot_versions.len(), 2);
4112 assert!(Arc::ptr_eq(
4113 slot_versions.first().unwrap(),
4114 &deployed_at_100
4115 ));
4116 assert!(Arc::ptr_eq(slot_versions.get(1).unwrap(), &deployed_at_150));
4117
4118 let mut search_for = vec![ProgramToLoad {
4120 program_id: &program_id,
4121 loader: ProgramCacheEntryOwner::LoaderV3,
4122 deployment_slot: 150,
4123 }];
4124 let mut extracted = ProgramCacheForTxBatch::new(200);
4125 cache.extract(&mut search_for, &mut extracted, &env, true, true);
4126 assert!(search_for.is_empty());
4127 assert!(Arc::ptr_eq(
4128 extracted.entries.get(&program_id).unwrap(),
4129 &deployed_at_150
4130 ));
4131 }
4132
4133 #[test]
4134 fn test_extract_older_entry_on_the_callers_fork() {
4135 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
4142 let mut fork_graph = TestForkGraphSpecific::default();
4143 fork_graph.insert_fork(&[0, 50, 200]);
4144 fork_graph.insert_fork(&[0, 150]);
4145 let fork_graph = Arc::new(RwLock::new(fork_graph));
4146 cache.set_fork_graph(Arc::downgrade(&fork_graph));
4147
4148 let env = get_mock_program_runtime_environment();
4149 let program_id = Pubkey::new_unique();
4150 let on_other_fork = new_test_entry_with_owner(
4151 150,
4152 ProgramCacheEntryOwner::LoaderV3,
4153 new_loaded_entry(env.clone()),
4154 );
4155 let on_same_fork = new_test_entry_with_owner(
4156 50,
4157 ProgramCacheEntryOwner::LoaderV3,
4158 new_loaded_entry(env.clone()),
4159 );
4160 cache.assign_program(&env, program_id, 150, Arc::clone(&on_other_fork));
4161 cache.assign_program(&env, program_id, 50, Arc::clone(&on_same_fork));
4162
4163 let mut search_for = vec![ProgramToLoad {
4167 program_id: &program_id,
4168 loader: ProgramCacheEntryOwner::LoaderV3,
4169 deployment_slot: 50,
4170 }];
4171 let mut extracted = ProgramCacheForTxBatch::new(200);
4172 cache.extract(&mut search_for, &mut extracted, &env, true, true);
4173 assert!(Arc::ptr_eq(
4174 extracted.entries.get(&program_id).unwrap(),
4175 &on_same_fork
4176 ));
4177
4178 let mut search_for = vec![ProgramToLoad {
4190 program_id: &program_id,
4191 loader: ProgramCacheEntryOwner::LoaderV3,
4192 deployment_slot: 150,
4193 }];
4194 let mut extracted = ProgramCacheForTxBatch::new(200);
4195 cache.extract(&mut search_for, &mut extracted, &env, true, true);
4196 assert_eq!(search_for.len(), 1);
4197 assert!(extracted.entries.is_empty());
4198 }
4199
4200 #[test]
4201 fn test_extract_below_deployment_slot() {
4202 let (mut cache, fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
4203 let env = get_mock_program_runtime_environment();
4204 let program_id = Pubkey::new_unique();
4205 let entry = new_test_entry_with_owner(
4206 100,
4207 ProgramCacheEntryOwner::LoaderV3,
4208 new_loaded_entry(env.clone()),
4209 );
4210 cache.assign_program(&env, program_id, 100, Arc::clone(&entry));
4211
4212 cache.prune(200, None, &fork_graph.read().unwrap());
4215 assert_eq!(cache.latest_root_slot, 200);
4216
4217 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
4219 assert_eq!(slot_versions.len(), 1);
4220 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &entry));
4221
4222 fork_graph.write().unwrap().relation = BlockRelation::Unknown;
4226
4227 let mut search_for = vec![ProgramToLoad {
4230 program_id: &program_id,
4231 loader: ProgramCacheEntryOwner::LoaderV3,
4232 deployment_slot: 0,
4233 }];
4234 let mut extracted = ProgramCacheForTxBatch::new(50);
4235 cache.extract(&mut search_for, &mut extracted, &env, true, true);
4236 assert_eq!(search_for.len(), 1);
4237 assert!(extracted.entries.is_empty());
4238 }
4239
4240 #[test]
4241 fn test_extract_entry_older_than_root() {
4242 let (mut cache, fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
4245 let env = get_mock_program_runtime_environment();
4246 let program_id = Pubkey::new_unique();
4247 let entry = new_test_entry_with_owner(
4248 100,
4249 ProgramCacheEntryOwner::LoaderV3,
4250 new_loaded_entry(env.clone()),
4251 );
4252 cache.assign_program(&env, program_id, 100, Arc::clone(&entry));
4253
4254 cache.prune(200, None, &fork_graph.read().unwrap());
4257 assert_eq!(cache.latest_root_slot, 200);
4258
4259 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
4261 assert_eq!(slot_versions.len(), 1);
4262 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &entry));
4263
4264 fork_graph.write().unwrap().relation = BlockRelation::Unknown;
4268
4269 let mut search_for = vec![ProgramToLoad {
4274 program_id: &program_id,
4275 loader: ProgramCacheEntryOwner::LoaderV3,
4276 deployment_slot: 100,
4277 }];
4278 let mut extracted = ProgramCacheForTxBatch::new(300);
4279 cache.extract(&mut search_for, &mut extracted, &env, true, true);
4280 assert!(search_for.is_empty());
4281 assert!(Arc::ptr_eq(
4282 extracted.entries.get(&program_id).unwrap(),
4283 &entry
4284 ));
4285 }
4286
4287 #[test]
4288 fn test_unloaded() {
4289 let mut cache = ProgramCache::<TestForkGraph>::new(0);
4290 let env = get_mock_program_runtime_environment();
4291 for program_cache_entry_type in [
4292 ProgramCacheEntryType::Closed,
4293 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
4294 ] {
4295 let entry = Arc::new(ProgramCacheEntry {
4296 program: program_cache_entry_type,
4297 account_owner: ProgramCacheEntryOwner::LoaderV2,
4298 deployment_slot: 0,
4299 stats: Arc::default(),
4300 latest_access_slot: AtomicU64::default(),
4301 });
4302 assert!(entry.to_unloaded().is_none());
4303
4304 let program_id = Pubkey::new_unique();
4306 cache.assign_program(&env, program_id, entry.deployment_slot, entry.clone());
4307 cache.unload_program_entry(program_id, &entry);
4308 assert_eq!(cache.get_slot_versions_for_tests(&program_id).len(), 1);
4309 assert!(cache.stats.evictions.is_empty());
4310 }
4311
4312 let stats = ProgramStatistics {
4313 uses: 3.into(),
4314 ..Default::default()
4315 };
4316 let entry = new_test_entry_with_usage(1, stats);
4317 let unloaded_entry = entry.to_unloaded().unwrap();
4318 assert_eq!(unloaded_entry.deployment_slot, 1);
4319 assert_eq!(unloaded_entry.effective_slot(), 2);
4320 assert_eq!(unloaded_entry.latest_access_slot.load(Ordering::Relaxed), 1);
4321 assert_eq!(unloaded_entry.stats.uses.load(Ordering::Relaxed), 3);
4322
4323 let program_id = Pubkey::new_unique();
4325 cache.assign_program(&env, program_id, entry.deployment_slot, entry.clone());
4326 cache.unload_program_entry(program_id, &entry);
4327 assert!(cache.stats.evictions.contains_key(&program_id));
4328 }
4329
4330 #[test]
4331 fn test_fork_prune_find_first_ancestor() {
4332 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
4333 let env = get_mock_program_runtime_environment();
4334
4335 let mut fork_graph = TestForkGraphSpecific::default();
4346 fork_graph.insert_fork(&[0, 10, 20]);
4347 fork_graph.insert_fork(&[0, 5]);
4348 let fork_graph = Arc::new(RwLock::new(fork_graph));
4349 cache.set_fork_graph(Arc::downgrade(&fork_graph));
4350
4351 let program1 = Pubkey::new_unique();
4352 cache.assign_program(&env, program1, 0, new_test_entry(0));
4353 cache.assign_program(&env, program1, 5, new_test_entry(5));
4354
4355 cache.prune(10, None, &fork_graph.read().unwrap());
4356
4357 let keys = &[program1];
4358 let mut missing = get_entries_to_load(&cache, 20, keys);
4359 let mut extracted = ProgramCacheForTxBatch::new(20);
4360 cache.extract(&mut missing, &mut extracted, &env, true, true);
4361
4362 assert_eq!(
4364 extracted
4365 .find(&program1)
4366 .expect("Did not find the program")
4367 .deployment_slot,
4368 0
4369 );
4370 }
4371
4372 #[test]
4373 fn test_prune_by_deployment_slot() {
4374 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
4375 let env = get_mock_program_runtime_environment();
4376
4377 let mut fork_graph = TestForkGraphSpecific::default();
4388 fork_graph.insert_fork(&[0, 10, 20]);
4389 fork_graph.insert_fork(&[0, 5, 6]);
4390 let fork_graph = Arc::new(RwLock::new(fork_graph));
4391 cache.set_fork_graph(Arc::downgrade(&fork_graph));
4392
4393 let program1 = Pubkey::new_unique();
4394 cache.assign_program(&env, program1, 0, new_test_entry(0));
4395 cache.assign_program(&env, program1, 5, new_test_entry(5));
4396
4397 let program2 = Pubkey::new_unique();
4398 cache.assign_program(&env, program2, 10, new_test_entry(10));
4399
4400 let keys = &[program1, program2];
4401 let mut missing = get_entries_to_load(&cache, 20, keys);
4402 let mut extracted = ProgramCacheForTxBatch::new(20);
4403 cache.extract(&mut missing, &mut extracted, &env, true, true);
4404 assert!(match_slot(&extracted, &program1, 0, 20));
4405 assert!(match_slot(&extracted, &program2, 10, 20));
4406
4407 let mut missing = get_entries_to_load(&cache, 6, keys);
4408 assert!(match_missing(&missing, &program2, false));
4409 let mut extracted = ProgramCacheForTxBatch::new(6);
4410 cache.extract(&mut missing, &mut extracted, &env, true, true);
4411 assert!(match_slot(&extracted, &program1, 5, 6));
4412
4413 cache.prune_by_deployment_slot(5);
4416
4417 let mut missing = get_entries_to_load(&cache, 20, keys);
4418 let mut extracted = ProgramCacheForTxBatch::new(20);
4419 cache.extract(&mut missing, &mut extracted, &env, true, true);
4420 assert!(match_slot(&extracted, &program1, 0, 20));
4421 assert!(match_slot(&extracted, &program2, 10, 20));
4422
4423 let mut missing = get_entries_to_load(&cache, 6, keys);
4424 assert!(match_missing(&missing, &program2, false));
4425 let mut extracted = ProgramCacheForTxBatch::new(6);
4426 cache.extract(&mut missing, &mut extracted, &env, true, true);
4427 assert!(match_slot(&extracted, &program1, 0, 6));
4428
4429 cache.prune_by_deployment_slot(10);
4432
4433 let mut missing = get_entries_to_load(&cache, 20, keys);
4434 assert!(match_missing(&missing, &program2, false));
4435 let mut extracted = ProgramCacheForTxBatch::new(20);
4436 cache.extract(&mut missing, &mut extracted, &env, true, true);
4437 assert!(match_slot(&extracted, &program1, 0, 20));
4438 }
4439}