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(
696 loaded_programs_for_tx_batch.slot.min(self.latest_root_slot),
697 );
698 if increment_usage_counter {
699 entry_to_return.stats.uses.fetch_add(1, Ordering::Relaxed);
700 }
701 loaded_programs_for_tx_batch
702 .entries
703 .insert(*program_to_load.program_id, entry_to_return);
704 return false;
705 }
706 }
707 }
708 if cooperative_loading_task.is_none() {
709 let mut loading_entries = loading_entries.lock().unwrap();
710 let entry = loading_entries.entry(*program_to_load.program_id);
711 if let Entry::Vacant(entry) = entry {
712 entry.insert((
713 loaded_programs_for_tx_batch.slot,
714 thread::current().id(),
715 ));
716 cooperative_loading_task = Some(*program_to_load.program_id);
717 }
718 }
719 true
720 });
721 }
722 }
723 drop(locked_fork_graph);
724 if count_hits_and_misses {
725 let misses = search_for.len() as u64;
726 let hits = loaded_programs_for_tx_batch
727 .entries
728 .len()
729 .saturating_sub(entries_in_batch) as u64;
730 self.stats.misses.fetch_add(misses, Ordering::Relaxed);
731 self.stats.hits.fetch_add(hits, Ordering::Relaxed);
732 }
733 cooperative_loading_task
734 }
735
736 pub fn finish_cooperative_loading_task(
738 &mut self,
739 program_runtime_environment: &ProgramRuntimeEnvironment,
740 current_slot: Slot,
741 key: Pubkey,
742 loaded_program: Arc<ProgramCacheEntry>,
743 ) -> bool {
744 match &mut self.index {
745 IndexImplementation::V1 {
746 loading_entries, ..
747 } => {
748 let loading_thread = loading_entries.get_mut().unwrap().remove(&key);
749 debug_assert_eq!(loading_thread, Some((current_slot, thread::current().id())));
750 if loaded_program.deployment_slot > self.latest_root_slot
752 && !matches!(
753 self.fork_graph
754 .as_ref()
755 .unwrap()
756 .upgrade()
757 .unwrap()
758 .read()
759 .unwrap()
760 .relationship(loaded_program.deployment_slot, current_slot),
761 BlockRelation::Equal | BlockRelation::Ancestor
762 )
763 {
764 self.stats.lost_insertions.fetch_add(1, Ordering::Relaxed);
765 }
766 let was_occupied = self.assign_program(
767 program_runtime_environment,
768 key,
769 current_slot,
770 loaded_program,
771 );
772 self.loading_task_waiter.notify();
773 was_occupied
774 }
775 }
776 }
777
778 pub fn merge(
779 &mut self,
780 program_runtime_environment: &ProgramRuntimeEnvironment,
781 current_slot: Slot,
782 modified_entries: &HashMap<Pubkey, Arc<ProgramCacheEntry>>,
783 ) {
784 modified_entries.iter().for_each(|(key, entry)| {
785 self.assign_program(
786 program_runtime_environment,
787 *key,
788 current_slot,
789 entry.clone(),
790 );
791 })
792 }
793
794 pub fn get_flattened_entries(&self) -> Vec<(Pubkey, Arc<ProgramCacheEntry>)> {
796 match &self.index {
797 IndexImplementation::V1 { entries, .. } => entries
798 .iter()
799 .flat_map(|(id, second_level)| {
800 second_level
801 .iter()
802 .filter_map(move |program| match program.program {
803 ProgramCacheEntryType::Loaded(_) => Some((*id, program.clone())),
804 _ => None,
805 })
806 })
807 .collect(),
808 }
809 }
810
811 #[cfg(feature = "dev-context-only-utils")]
813 pub fn get_flattened_entries_for_tests(&self) -> Vec<(Pubkey, Arc<ProgramCacheEntry>)> {
814 match &self.index {
815 IndexImplementation::V1 { entries, .. } => entries
816 .iter()
817 .flat_map(|(id, second_level)| {
818 second_level.iter().map(|program| (*id, program.clone()))
819 })
820 .collect(),
821 }
822 }
823
824 pub fn get_slot_versions_for_tests(&self, key: &Pubkey) -> &[Arc<ProgramCacheEntry>] {
826 match &self.index {
827 IndexImplementation::V1 { entries, .. } => entries
828 .get(key)
829 .map(|second_level| second_level.as_ref())
830 .unwrap_or(&[]),
831 }
832 }
833
834 pub fn sort_and_unload(&mut self, shrink_to_percent: Percent) {
836 let mut sorted_candidates = self.get_flattened_entries();
837 sorted_candidates
838 .sort_by_cached_key(|(_id, program)| program.stats.uses.load(Ordering::Relaxed));
839 let num_to_unload = sorted_candidates
840 .len()
841 .saturating_sub(percent_of_max_entries(shrink_to_percent));
842 for (program, entry) in sorted_candidates.iter().take(num_to_unload) {
843 self.unload_program_entry(*program, entry);
844 }
845 }
846
847 pub fn evict_using_random_selection(&mut self, shrink_to_percent: Percent, now: Slot) {
853 let mut candidates = self.get_flattened_entries();
854 let mut rng = rng();
855 self.stats
856 .water_level
857 .store(candidates.len() as u64, Ordering::Relaxed);
858 let num_to_unload = candidates
859 .len()
860 .saturating_sub(percent_of_max_entries(shrink_to_percent));
861 let mut sample_entry = |candidates: &Vec<(Pubkey, Arc<ProgramCacheEntry>)>| {
862 #[cfg(feature = "shuttle-test")]
865 let index = rng.gen_range(0..candidates.len());
866 #[cfg(not(feature = "shuttle-test"))]
867 let index = rng.random_range(0..candidates.len());
868 let usage_counter = candidates
869 .get(index)
870 .expect("Failed to get cached entry")
871 .1
872 .retention_score();
873 (index, usage_counter)
874 };
875
876 const MAX_ADDITIONAL_SAMPLES: usize = 3;
884 let avoid_evicting_above_score = retention_score(now, 500 * EMA_SCALE, 500);
885 for _ in 0..num_to_unload {
886 let (mut index, mut score) = sample_entry(&candidates);
887 for _ in 0..MAX_ADDITIONAL_SAMPLES {
888 let (sample_index, sample_score) = sample_entry(&candidates);
889 if score > sample_score {
890 index = sample_index;
891 score = sample_score;
892 }
893 if score < avoid_evicting_above_score {
894 break;
895 }
896 }
897 let (id, entry) = candidates.swap_remove(index);
898 self.unload_program_entry(id, &entry);
899 }
900 }
901
902 pub fn remove_programs(&mut self, keys: impl Iterator<Item = Pubkey>) {
904 match &mut self.index {
905 IndexImplementation::V1 { entries, .. } => {
906 for k in keys {
907 entries.remove(&k);
908 }
909 }
910 }
911 }
912
913 fn unload_program_entry(&mut self, id: Pubkey, remove_entry: &Arc<ProgramCacheEntry>) {
916 match &mut self.index {
917 IndexImplementation::V1 { entries, .. } => {
918 let second_level = entries.get_mut(&id).expect("Cache lookup failed");
919 let candidate = second_level
920 .iter_mut()
921 .find(|entry| Arc::ptr_eq(entry, remove_entry))
922 .expect("Program entry not found");
923
924 if let ProgramCacheEntryType::Loaded(_) = candidate.program
926 && let Some(unloaded) = candidate.to_unloaded()
927 {
928 if candidate.stats.uses.load(Ordering::Relaxed) == 1 {
929 self.stats.one_hit_wonders.fetch_add(1, Ordering::Relaxed);
930 }
931 self.stats
932 .evictions
933 .entry(id)
934 .and_modify(|c| *c = c.saturating_add(1))
935 .or_insert(1);
936 *candidate = Arc::new(unloaded);
937 }
938 }
939 }
940 }
941
942 fn remove_programs_with_no_entries(&mut self) {
943 match &mut self.index {
944 IndexImplementation::V1 { entries, .. } => {
945 let num_programs_before_removal = entries.len();
946 entries.retain(|_key, second_level| !second_level.is_empty());
947 if entries.len() < num_programs_before_removal {
948 self.stats.empty_entries.fetch_add(
949 num_programs_before_removal.saturating_sub(entries.len()) as u64,
950 Ordering::Relaxed,
951 );
952 }
953 }
954 }
955 }
956}
957
958#[cfg(test)]
959pub(crate) mod tests {
960 use {
961 crate::{
962 loaded_programs::{
963 BlockRelation, ForkGraph, IndexImplementation, MAX_TOMBSTONE_AGE_IN_SLOTS, Percent,
964 ProgramCache, ProgramCacheForTxBatch, ProgramRuntimeEnvironment, ProgramToLoad,
965 get_mock_program_runtime_environment,
966 },
967 program_cache_entry::{
968 ProgramCacheEntry, ProgramCacheEntryOwner, ProgramCacheEntryType,
969 },
970 program_metrics::ProgramStatistics,
971 },
972 assert_matches::assert_matches,
973 solana_clock::Slot,
974 solana_pubkey::Pubkey,
975 solana_sbpf::{elf::Executable, program::BuiltinProgram},
976 solana_svm_type_overrides::{
977 sync::{
978 Arc, RwLock,
979 atomic::{AtomicU64, Ordering},
980 },
981 thread,
982 },
983 std::{fs::File, io::Read, ops::ControlFlow},
984 test_case::{test_case, test_matrix},
985 };
986
987 fn new_test_entry(deployment_slot: Slot) -> Arc<ProgramCacheEntry> {
988 new_test_entry_with_usage(deployment_slot, ProgramStatistics::default())
989 }
990
991 fn new_closed_entry(_env: ProgramRuntimeEnvironment) -> ProgramCacheEntryType {
992 ProgramCacheEntryType::Closed
993 }
994
995 fn new_builtin_entry(_env: ProgramRuntimeEnvironment) -> ProgramCacheEntryType {
996 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock())
997 }
998
999 fn new_failed_verification_entry(env: ProgramRuntimeEnvironment) -> ProgramCacheEntryType {
1000 ProgramCacheEntryType::FailedVerification(env)
1001 }
1002
1003 fn new_unloaded_entry(env: ProgramRuntimeEnvironment) -> ProgramCacheEntryType {
1004 ProgramCacheEntryType::Unloaded(env)
1005 }
1006
1007 fn new_loaded_entry(env: ProgramRuntimeEnvironment) -> ProgramCacheEntryType {
1008 let mut elf = Vec::new();
1009 File::open("../programs/bpf_loader/test_elfs/out/noop_aligned.so")
1010 .unwrap()
1011 .read_to_end(&mut elf)
1012 .unwrap();
1013 let executable = Executable::load(&elf, Arc::clone(&*env)).unwrap();
1014 ProgramCacheEntryType::Loaded(executable)
1015 }
1016
1017 fn new_test_entry_with_owner(
1018 deployment_slot: Slot,
1019 account_owner: ProgramCacheEntryOwner,
1020 program: ProgramCacheEntryType,
1021 ) -> Arc<ProgramCacheEntry> {
1022 Arc::new(ProgramCacheEntry {
1023 program,
1024 account_owner,
1025 deployment_slot,
1026 stats: Arc::default(),
1027 latest_access_slot: AtomicU64::default(),
1028 })
1029 }
1030
1031 fn new_test_cache_with_fork_graph(
1032 relation: BlockRelation,
1033 ) -> (ProgramCache<TestForkGraph>, Arc<RwLock<TestForkGraph>>) {
1034 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1035 let fork_graph = Arc::new(RwLock::new(TestForkGraph { relation }));
1036 cache.set_fork_graph(Arc::downgrade(&fork_graph));
1037 (cache, fork_graph)
1038 }
1039
1040 pub(crate) fn new_test_entry_with_usage(
1041 deployment_slot: Slot,
1042 stats: ProgramStatistics,
1043 ) -> Arc<ProgramCacheEntry> {
1044 Arc::new(ProgramCacheEntry {
1045 program: new_loaded_entry(get_mock_program_runtime_environment()),
1046 account_owner: ProgramCacheEntryOwner::LoaderV2,
1047 deployment_slot,
1048 stats: Arc::new(stats),
1049 latest_access_slot: AtomicU64::new(deployment_slot),
1050 })
1051 }
1052
1053 fn new_test_builtin_entry(deployment_slot: Slot) -> Arc<ProgramCacheEntry> {
1054 Arc::new(ProgramCacheEntry {
1055 program: ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1056 account_owner: ProgramCacheEntryOwner::NativeLoader,
1057 deployment_slot,
1058 stats: Arc::default(),
1059 latest_access_slot: AtomicU64::default(),
1060 })
1061 }
1062
1063 fn set_failed_verification_tombstone<FG: ForkGraph>(
1064 cache: &mut ProgramCache<FG>,
1065 key: Pubkey,
1066 current_slot: Slot,
1067 env: ProgramRuntimeEnvironment,
1068 ) -> Arc<ProgramCacheEntry> {
1069 let program = Arc::new(ProgramCacheEntry::new_failed_verification_tombstone(
1070 current_slot,
1071 ProgramCacheEntryOwner::LoaderV2,
1072 ProgramRuntimeEnvironment::clone(&env),
1073 ));
1074 cache.assign_program(&env, key, current_slot, program.clone());
1075 program
1076 }
1077
1078 fn insert_unloaded_entry<FG: ForkGraph>(
1079 cache: &mut ProgramCache<FG>,
1080 key: Pubkey,
1081 current_slot: Slot,
1082 ) -> Arc<ProgramCacheEntry> {
1083 let env = get_mock_program_runtime_environment();
1084 let loaded = new_test_entry_with_usage(current_slot, ProgramStatistics::default());
1085 let unloaded = Arc::new(loaded.to_unloaded().expect("Failed to unload the program"));
1086 cache.assign_program(&env, key, current_slot, unloaded.clone());
1087 unloaded
1088 }
1089
1090 fn num_matching_entries<P, FG>(cache: &ProgramCache<FG>, predicate: P) -> usize
1091 where
1092 P: Fn(&ProgramCacheEntryType) -> bool,
1093 FG: ForkGraph,
1094 {
1095 cache
1096 .get_flattened_entries_for_tests()
1097 .iter()
1098 .filter(|(_key, program)| predicate(&program.program))
1099 .count()
1100 }
1101
1102 #[expect(clippy::arithmetic_side_effects)]
1103 fn program_deploy_test_helper(
1104 cache: &mut ProgramCache<TestForkGraph>,
1105 program: Pubkey,
1106 deployment_slots: Vec<Slot>,
1107 usage_counters: Vec<u64>,
1108 programs: &mut Vec<(Pubkey, Slot, u64)>,
1109 ) {
1110 let env = get_mock_program_runtime_environment();
1111 deployment_slots
1113 .iter()
1114 .enumerate()
1115 .for_each(|(i, deployment_slot)| {
1116 let usage_counter = *usage_counters.get(i).unwrap_or(&0);
1117 let stats = ProgramStatistics {
1118 uses: usage_counter.into(),
1119 ..Default::default()
1120 };
1121 cache.assign_program(
1122 &env,
1123 program,
1124 *deployment_slot,
1125 new_test_entry_with_usage(*deployment_slot, stats),
1126 );
1127 programs.push((program, *deployment_slot, usage_counter));
1128 });
1129
1130 let next_slot = deployment_slots.iter().max().map_or(0, |slot| slot + 1);
1131
1132 let env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
1134 for slot in next_slot..next_slot + 10 {
1135 set_failed_verification_tombstone(
1136 cache,
1137 program,
1138 slot,
1139 ProgramRuntimeEnvironment::clone(&env),
1140 );
1141 }
1142
1143 for slot in next_slot + 10..next_slot + 20 {
1145 insert_unloaded_entry(cache, program, slot);
1146 }
1147 }
1148
1149 #[test]
1150 fn test_random_eviction() {
1151 let mut programs = vec![];
1152 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1153
1154 program_deploy_test_helper(
1160 &mut cache,
1161 Pubkey::new_unique(),
1162 vec![0, 10, 20, 30, 40],
1163 vec![4, 5, 25, 35, 12],
1164 &mut programs,
1165 );
1166
1167 program_deploy_test_helper(
1169 &mut cache,
1170 Pubkey::new_unique(),
1171 vec![5, 11, 21, 24],
1172 vec![0, 2, 30, 45],
1173 &mut programs,
1174 );
1175
1176 program_deploy_test_helper(
1178 &mut cache,
1179 Pubkey::new_unique(),
1180 vec![0, 5, 15, 25],
1181 vec![100, 3, 20, 40],
1182 &mut programs,
1183 );
1184
1185 let num_loaded_expected = 13;
1187 let num_unloaded_expected = 30;
1189 let num_tombstones_expected = 30;
1191
1192 programs.sort_by_key(|(_id, _slot, usage_count)| *usage_count);
1194 let num_loaded = num_matching_entries(&cache, |program_type| {
1195 matches!(program_type, ProgramCacheEntryType::Loaded(_))
1196 });
1197 let num_unloaded = num_matching_entries(&cache, |program_type| {
1198 matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1199 });
1200 let num_tombstones = num_matching_entries(&cache, |program_type| {
1201 matches!(
1202 program_type,
1203 ProgramCacheEntryType::DelayVisibility
1204 | ProgramCacheEntryType::FailedVerification(_)
1205 | ProgramCacheEntryType::Closed
1206 )
1207 });
1208
1209 assert_eq!(num_loaded, num_loaded_expected);
1211 assert_eq!(num_unloaded, num_unloaded_expected);
1212 assert_eq!(num_tombstones, num_tombstones_expected);
1213
1214 let eviction_pct: Percent = 1;
1216
1217 let num_loaded_expected = crate::loaded_programs::percent_of_max_entries(eviction_pct);
1218 let num_unloaded_expected = num_unloaded_expected + num_loaded - num_loaded_expected;
1219 cache.evict_using_random_selection(eviction_pct, 21);
1220
1221 let num_loaded = num_matching_entries(&cache, |program_type| {
1223 matches!(program_type, ProgramCacheEntryType::Loaded(_))
1224 });
1225 let num_unloaded = num_matching_entries(&cache, |program_type| {
1226 matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1227 });
1228 let num_tombstones = num_matching_entries(&cache, |program_type| {
1229 matches!(program_type, ProgramCacheEntryType::FailedVerification(_))
1230 });
1231
1232 assert_eq!(num_loaded, num_loaded_expected);
1234 assert_eq!(num_unloaded, num_unloaded_expected);
1236 assert_eq!(num_tombstones, num_tombstones_expected);
1238 }
1239
1240 #[test]
1241 fn test_eviction() {
1242 let mut programs = vec![];
1243 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1244
1245 program_deploy_test_helper(
1247 &mut cache,
1248 Pubkey::new_unique(),
1249 vec![0, 10, 20, 30, 40],
1250 vec![4, 5, 25, 35, 12],
1251 &mut programs,
1252 );
1253
1254 program_deploy_test_helper(
1256 &mut cache,
1257 Pubkey::new_unique(),
1258 vec![5, 11, 21, 24],
1259 vec![0, 2, 30, 45],
1260 &mut programs,
1261 );
1262
1263 program_deploy_test_helper(
1265 &mut cache,
1266 Pubkey::new_unique(),
1267 vec![0, 5, 15, 25],
1268 vec![100, 3, 20, 40],
1269 &mut programs,
1270 );
1271
1272 let num_loaded_expected = 13;
1274 let num_unloaded_expected = 30;
1276 let num_tombstones_expected = 30;
1278
1279 programs.sort_by_key(|(_id, _slot, usage_count)| *usage_count);
1281 let num_loaded = num_matching_entries(&cache, |program_type| {
1282 matches!(program_type, ProgramCacheEntryType::Loaded(_))
1283 });
1284 let num_unloaded = num_matching_entries(&cache, |program_type| {
1285 matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1286 });
1287 let num_tombstones = num_matching_entries(&cache, |program_type| {
1288 matches!(program_type, ProgramCacheEntryType::FailedVerification(_))
1289 });
1290
1291 assert_eq!(num_loaded, num_loaded_expected);
1293 assert_eq!(num_unloaded, num_unloaded_expected);
1294 assert_eq!(num_tombstones, num_tombstones_expected);
1295
1296 let eviction_pct: Percent = 1;
1298
1299 let num_loaded_expected = crate::loaded_programs::percent_of_max_entries(eviction_pct);
1300 let num_unloaded_expected = num_unloaded_expected + num_loaded - num_loaded_expected;
1301
1302 cache.sort_and_unload(eviction_pct);
1303
1304 let entries = cache.get_flattened_entries_for_tests();
1306 programs.iter().for_each(|entry| {
1307 assert!(entries.iter().any(|(key, _entry)| key == &entry.0));
1308 });
1309
1310 let unloaded = entries
1311 .iter()
1312 .filter_map(|(key, program)| {
1313 matches!(program.program, ProgramCacheEntryType::Unloaded(_))
1314 .then_some((*key, program.stats.uses.load(Ordering::Relaxed)))
1315 })
1316 .collect::<Vec<(Pubkey, u64)>>();
1317
1318 for index in 0..3 {
1319 let expected = programs.get(index).expect("Missing program");
1320 assert!(unloaded.contains(&(expected.0, expected.2)));
1321 }
1322
1323 let num_loaded = num_matching_entries(&cache, |program_type| {
1325 matches!(program_type, ProgramCacheEntryType::Loaded(_))
1326 });
1327 let num_unloaded = num_matching_entries(&cache, |program_type| {
1328 matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1329 });
1330 let num_tombstones = num_matching_entries(&cache, |program_type| {
1331 matches!(
1332 program_type,
1333 ProgramCacheEntryType::DelayVisibility
1334 | ProgramCacheEntryType::FailedVerification(_)
1335 | ProgramCacheEntryType::Closed
1336 )
1337 });
1338
1339 assert_eq!(num_loaded, num_loaded_expected);
1341 assert_eq!(num_unloaded, num_unloaded_expected);
1343 assert_eq!(num_tombstones, num_tombstones_expected);
1345 }
1346
1347 #[test]
1348 fn test_usage_count_of_unloaded_program() {
1349 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1350 let env = get_mock_program_runtime_environment();
1351
1352 let program = Pubkey::new_unique();
1353 let evict_to_pct: Percent = 2;
1354 let cache_capacity_after_shrink =
1355 crate::loaded_programs::percent_of_max_entries(evict_to_pct);
1356 let num_total_programs = (cache_capacity_after_shrink + 1) as u64;
1358 (0..num_total_programs).for_each(|i| {
1359 let stats = ProgramStatistics {
1360 uses: (i + 10).into(),
1361 ..Default::default()
1362 };
1363 let entry = new_test_entry_with_usage(i, stats);
1364 cache.assign_program(&env, program, i, entry);
1365 });
1366
1367 cache.sort_and_unload(evict_to_pct);
1368
1369 let num_unloaded = num_matching_entries(&cache, |program_type| {
1370 matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1371 });
1372 assert_eq!(num_unloaded, 1);
1373
1374 cache
1375 .get_flattened_entries_for_tests()
1376 .iter()
1377 .for_each(|(_key, program)| {
1378 if matches!(program.program, ProgramCacheEntryType::Unloaded(_)) {
1379 assert_eq!(program.stats.uses.load(Ordering::Relaxed), 10);
1381 assert_eq!(program.deployment_slot, 0);
1382 assert_eq!(program.effective_slot(), 1);
1383 }
1384 });
1385
1386 cache.assign_program(
1389 &env,
1390 program,
1391 0,
1392 new_test_entry_with_usage(0, ProgramStatistics::default()),
1393 );
1394
1395 cache
1396 .get_flattened_entries_for_tests()
1397 .iter()
1398 .for_each(|(_key, program)| {
1399 if matches!(program.program, ProgramCacheEntryType::Unloaded(_))
1400 && program.deployment_slot == 0
1401 && program.effective_slot() == 1
1402 {
1403 assert_eq!(program.stats.uses.load(Ordering::Relaxed), 10);
1405 }
1406 });
1407 }
1408
1409 #[test_matrix(
1410 (
1411 ProgramCacheEntryType::FailedVerification(get_mock_program_runtime_environment()),
1412 ProgramCacheEntryType::Closed,
1413 ProgramCacheEntryType::Unloaded(get_mock_program_runtime_environment()),
1414 new_loaded_entry(get_mock_program_runtime_environment()),
1415 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1416 ),
1417 (false, true)
1418 )]
1419 fn test_assign_program_no_second_level(
1420 program: ProgramCacheEntryType,
1421 empty_second_level: bool,
1422 ) {
1423 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1427 let env = get_mock_program_runtime_environment();
1428 let program_id = Pubkey::new_unique();
1429
1430 if empty_second_level {
1431 match &mut cache.index {
1433 IndexImplementation::V1 { entries, .. } => {
1434 entries.insert(program_id, Vec::new());
1435 }
1436 }
1437 }
1438
1439 let entry = Arc::new(ProgramCacheEntry {
1440 program,
1441 account_owner: ProgramCacheEntryOwner::LoaderV3,
1442 deployment_slot: 10,
1443 stats: Arc::default(),
1444 latest_access_slot: AtomicU64::default(),
1445 });
1446
1447 cache.assign_program(&env, program_id, 10, Arc::clone(&entry));
1448
1449 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
1451 assert_eq!(slot_versions.len(), 1);
1452 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &entry));
1453
1454 assert_eq!(cache.stats.insertions.load(Ordering::Relaxed), 1);
1456 }
1457
1458 #[test_matrix(
1459 (
1460 new_closed_entry,
1461 new_builtin_entry,
1462 new_failed_verification_entry,
1463 new_unloaded_entry,
1464 new_loaded_entry,
1465 ),
1466 ((50, 0), (150, 1), (250, 2), (350, 3))
1467 )]
1468 fn test_assign_program_new_insertion_deployment_slot(
1469 new_program: fn(ProgramRuntimeEnvironment) -> ProgramCacheEntryType,
1470 case: (Slot, usize),
1471 ) {
1472 let (deployment_slot, expected_index) = case;
1473 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1474 let env = get_mock_program_runtime_environment();
1475 let program_id = Pubkey::new_unique();
1476
1477 for slot in [100, 200, 300] {
1479 cache.assign_program(
1480 &env,
1481 program_id,
1482 slot,
1483 new_test_entry_with_owner(
1484 slot,
1485 ProgramCacheEntryOwner::LoaderV3,
1486 new_program(env.clone()),
1487 ),
1488 );
1489 }
1490
1491 let entry = new_test_entry_with_owner(
1493 deployment_slot,
1494 ProgramCacheEntryOwner::LoaderV3,
1495 new_program(env.clone()),
1496 );
1497 cache.assign_program(&env, program_id, deployment_slot, Arc::clone(&entry));
1498
1499 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
1500 assert_eq!(slot_versions.len(), 4);
1501 assert!(Arc::ptr_eq(
1502 slot_versions.get(expected_index).unwrap(),
1503 &entry
1504 ));
1505 assert_eq!(cache.stats.insertions.load(Ordering::Relaxed), 4);
1506 }
1507
1508 #[test_matrix(
1509 (new_failed_verification_entry, new_unloaded_entry, new_loaded_entry),
1510 (
1511 (ProgramCacheEntryOwner::NativeLoader, 0),
1512 (ProgramCacheEntryOwner::LoaderV2, 1),
1513 (ProgramCacheEntryOwner::LoaderV4, 2),
1514 )
1515 )]
1516 fn test_assign_program_new_insertion_account_owner(
1517 new_program: fn(ProgramRuntimeEnvironment) -> ProgramCacheEntryType,
1518 case: (ProgramCacheEntryOwner, usize),
1519 ) {
1520 let (account_owner, expected_index) = case;
1521 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1522 let env = get_mock_program_runtime_environment();
1523 let program_id = Pubkey::new_unique();
1524
1525 for owner in [
1528 ProgramCacheEntryOwner::LoaderV1,
1529 ProgramCacheEntryOwner::LoaderV3,
1530 ] {
1531 cache.assign_program(
1532 &env,
1533 program_id,
1534 100,
1535 new_test_entry_with_owner(
1536 100,
1537 owner,
1538 new_program(ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock())),
1539 ),
1540 );
1541 }
1542
1543 let entry = new_test_entry_with_owner(
1546 100,
1547 account_owner,
1548 new_program(ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock())),
1549 );
1550 cache.assign_program(&env, program_id, 100, Arc::clone(&entry));
1551
1552 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
1553 assert_eq!(slot_versions.len(), 3);
1554 assert!(Arc::ptr_eq(
1555 slot_versions.get(expected_index).unwrap(),
1556 &entry
1557 ));
1558 assert_eq!(cache.stats.insertions.load(Ordering::Relaxed), 3);
1559 }
1560
1561 #[test_matrix(
1562 (new_failed_verification_entry, new_unloaded_entry, new_loaded_entry),
1563 (false, true)
1564 )]
1565 fn test_assign_program_new_insertion_environment(
1566 new_program: fn(ProgramRuntimeEnvironment) -> ProgramCacheEntryType,
1567 entry_uses_current_env: bool,
1568 ) {
1569 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1570 let env = get_mock_program_runtime_environment();
1571 let other_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
1572 let program_id = Pubkey::new_unique();
1573
1574 let (existing_env, entry_env, expected_index) = if entry_uses_current_env {
1577 (other_env, env.clone(), 1)
1578 } else {
1579 (env.clone(), other_env, 0)
1580 };
1581 cache.assign_program(
1582 &env,
1583 program_id,
1584 100,
1585 new_test_entry_with_owner(
1586 100,
1587 ProgramCacheEntryOwner::LoaderV3,
1588 new_program(existing_env),
1589 ),
1590 );
1591
1592 let entry = new_test_entry_with_owner(
1593 100,
1594 ProgramCacheEntryOwner::LoaderV3,
1595 new_program(entry_env),
1596 );
1597 cache.assign_program(&env, program_id, 100, Arc::clone(&entry));
1598
1599 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
1600 assert_eq!(slot_versions.len(), 2);
1601 assert!(Arc::ptr_eq(
1602 slot_versions.get(expected_index).unwrap(),
1603 &entry
1604 ));
1605 assert_eq!(cache.stats.insertions.load(Ordering::Relaxed), 2);
1606 }
1607
1608 #[test]
1609 #[should_panic(expected = "Unexpected assignment of a DelayVisibility tombstone")]
1610 fn test_assign_program_delay_visibility_tombstone_panics() {
1611 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1614 let env = get_mock_program_runtime_environment();
1615 cache.assign_program(
1616 &env,
1617 Pubkey::new_unique(),
1618 100,
1619 Arc::new(ProgramCacheEntry::new_delay_visibility_tombstone(
1620 100,
1621 ProgramCacheEntryOwner::LoaderV3,
1622 Arc::default(),
1623 )),
1624 );
1625 }
1626
1627 #[test]
1628 fn test_fuzz_assign_program_order() {
1629 use rand::prelude::SliceRandom;
1630 const EXPECTED_ENTRIES: [(u64, bool); 5] =
1631 [(1, true), (3, false), (5, true), (9, true), (10, false)];
1632 let mut rng = rand::rng();
1633 let program_id = Pubkey::new_unique();
1634 let env = get_mock_program_runtime_environment();
1635 for _ in 0..1000 {
1636 let mut entries = EXPECTED_ENTRIES.to_vec();
1637 entries.shuffle(&mut rng);
1638 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1639 for (deployment_slot, delay_visibility) in entries {
1640 let entry = Arc::new(if delay_visibility {
1641 ProgramCacheEntry {
1642 program: new_loaded_entry(ProgramRuntimeEnvironment::from(
1643 BuiltinProgram::new_mock(),
1644 )), account_owner: ProgramCacheEntryOwner::LoaderV2,
1646 deployment_slot,
1647 stats: Arc::default(),
1648 latest_access_slot: AtomicU64::new(deployment_slot),
1649 }
1650 } else {
1651 ProgramCacheEntry::new_failed_verification_tombstone(
1652 deployment_slot,
1653 ProgramCacheEntryOwner::LoaderV2,
1654 ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock()), )
1656 });
1657 assert!(!cache.assign_program(&env, program_id, deployment_slot, entry));
1658 }
1659 for ((deployment_slot, delay_visibility), entry) in EXPECTED_ENTRIES
1660 .iter()
1661 .zip(cache.get_slot_versions_for_tests(&program_id).iter())
1662 {
1663 assert_eq!(entry.deployment_slot, *deployment_slot);
1664 assert_eq!(
1665 entry.effective_slot(),
1666 deployment_slot.saturating_add(*delay_visibility as u64)
1667 );
1668 }
1669 }
1670 }
1671
1672 #[test_matrix(
1673 (
1674 ProgramCacheEntryType::FailedVerification(get_mock_program_runtime_environment()),
1675 new_loaded_entry(get_mock_program_runtime_environment()),
1676 ),
1677 (
1678 ProgramCacheEntryType::FailedVerification(get_mock_program_runtime_environment()),
1679 ProgramCacheEntryType::Closed,
1680 ProgramCacheEntryType::Unloaded(get_mock_program_runtime_environment()),
1681 new_loaded_entry(get_mock_program_runtime_environment()),
1682 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1683 )
1684 )]
1685 #[test_matrix(
1686 ProgramCacheEntryType::Closed,
1687 (
1688 ProgramCacheEntryType::FailedVerification(get_mock_program_runtime_environment()),
1689 ProgramCacheEntryType::Closed,
1690 new_loaded_entry(get_mock_program_runtime_environment()),
1691 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1692 )
1693 )]
1694 #[test_matrix(
1695 ProgramCacheEntryType::Unloaded(get_mock_program_runtime_environment()),
1696 (
1697 ProgramCacheEntryType::Closed,
1698 ProgramCacheEntryType::Unloaded(get_mock_program_runtime_environment()),
1699 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1700 )
1701 )]
1702 #[test_matrix(
1703 (ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),),
1704 (
1705 ProgramCacheEntryType::FailedVerification(get_mock_program_runtime_environment()),
1706 ProgramCacheEntryType::Closed,
1707 ProgramCacheEntryType::Unloaded(get_mock_program_runtime_environment()),
1708 new_loaded_entry(get_mock_program_runtime_environment()),
1709 )
1710 )]
1711 #[should_panic(expected = "Unexpected replacement of an entry")]
1712 fn test_assign_program_failure(old: ProgramCacheEntryType, new: ProgramCacheEntryType) {
1713 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1714 let env = get_mock_program_runtime_environment();
1715 let program_id = Pubkey::new_unique();
1716 assert!(!cache.assign_program(
1717 &env,
1718 program_id,
1719 10,
1720 Arc::new(ProgramCacheEntry {
1721 program: old,
1722 account_owner: ProgramCacheEntryOwner::LoaderV2,
1723 deployment_slot: 10,
1724 stats: Arc::default(),
1725 latest_access_slot: AtomicU64::default(),
1726 }),
1727 ));
1728 cache.assign_program(
1729 &env,
1730 program_id,
1731 10,
1732 Arc::new(ProgramCacheEntry {
1733 program: new,
1734 account_owner: ProgramCacheEntryOwner::LoaderV2,
1735 deployment_slot: 10,
1736 stats: Arc::default(),
1737 latest_access_slot: AtomicU64::default(),
1738 }),
1739 );
1740 }
1741
1742 #[test_matrix(
1743 ProgramCacheEntryType::Unloaded(get_mock_program_runtime_environment()),
1744 (
1745 new_loaded_entry(get_mock_program_runtime_environment()),
1746 ProgramCacheEntryType::FailedVerification(get_mock_program_runtime_environment()),
1747 )
1748 )]
1749 #[test_case(
1750 ProgramCacheEntryType::Closed,
1751 ProgramCacheEntryType::Unloaded(get_mock_program_runtime_environment())
1752 )]
1753 #[test_case(
1754 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1755 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock())
1756 )]
1757 fn test_assign_program_success(old: ProgramCacheEntryType, new: ProgramCacheEntryType) {
1758 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1759 let env = get_mock_program_runtime_environment();
1760 let program_id = Pubkey::new_unique();
1761 assert!(!cache.assign_program(
1762 &env,
1763 program_id,
1764 10,
1765 Arc::new(ProgramCacheEntry {
1766 program: old,
1767 account_owner: ProgramCacheEntryOwner::LoaderV2,
1768 deployment_slot: 10,
1769 stats: Arc::default(),
1770 latest_access_slot: AtomicU64::default(),
1771 }),
1772 ));
1773 assert!(!cache.assign_program(
1774 &env,
1775 program_id,
1776 10,
1777 Arc::new(ProgramCacheEntry {
1778 program: new,
1779 account_owner: ProgramCacheEntryOwner::LoaderV2,
1780 deployment_slot: 10,
1781 stats: Arc::default(),
1782 latest_access_slot: AtomicU64::default(),
1783 }),
1784 ));
1785 }
1786
1787 #[test]
1788 fn test_assign_program_removes_entries_in_same_slot() {
1789 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1790 let env = get_mock_program_runtime_environment();
1791 let program_id = Pubkey::new_unique();
1792 let closed_other_slot = new_test_entry_with_owner(
1793 9,
1794 ProgramCacheEntryOwner::LoaderV2,
1795 new_closed_entry(env.clone()),
1796 );
1797 let closed_current_slot = new_test_entry_with_owner(
1798 10,
1799 ProgramCacheEntryOwner::LoaderV2,
1800 new_closed_entry(env.clone()),
1801 );
1802 let unloaded_current_env = new_test_entry_with_owner(
1803 10,
1804 ProgramCacheEntryOwner::LoaderV2,
1805 new_unloaded_entry(get_mock_program_runtime_environment()),
1806 );
1807 let unloaded_upcoming_env = new_test_entry_with_owner(
1808 10,
1809 ProgramCacheEntryOwner::LoaderV2,
1810 new_unloaded_entry(ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock())),
1811 );
1812
1813 assert!(!cache.assign_program(&env, program_id, 9, closed_other_slot.clone()));
1817 assert!(!cache.assign_program(&env, program_id, 10, closed_current_slot.clone()));
1818 assert_eq!(
1819 cache.get_slot_versions_for_tests(&program_id),
1820 &[closed_other_slot.clone(), closed_current_slot.clone()]
1821 );
1822
1823 assert!(!cache.assign_program(&env, program_id, 10, unloaded_current_env.clone()));
1832 assert_eq!(
1833 cache.get_slot_versions_for_tests(&program_id),
1834 &[
1835 closed_other_slot.clone(),
1836 unloaded_current_env.clone() ]
1838 );
1839
1840 assert!(!cache.assign_program(&env, program_id, 10, unloaded_upcoming_env.clone()));
1844 assert_eq!(
1845 cache.get_slot_versions_for_tests(&program_id),
1846 &[
1847 closed_other_slot,
1848 unloaded_current_env,
1849 unloaded_upcoming_env
1850 ]
1851 );
1852 }
1853
1854 #[test]
1855 fn test_assign_program_reload_merges_statistics() {
1856 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1857 let env = get_mock_program_runtime_environment();
1858 let program_id = Pubkey::new_unique();
1859
1860 let stats = Arc::new(ProgramStatistics {
1861 uses: 1.into(),
1862 compilations: 2.into(),
1863 total_compilation_time_us: 3.into(),
1864 compilation_time_ema: 100.into(),
1865 jit_invocations: 4.into(),
1866 total_jit_execution_time_us: 5.into(),
1867 jit_execution_time_ema: 200.into(),
1868 interpreted_invocations: 6.into(),
1869 total_interpretation_time_us: 7.into(),
1870 interpretation_time_ema: 300.into(),
1871 });
1872 let unloaded = Arc::new(ProgramCacheEntry {
1873 program: ProgramCacheEntryType::Unloaded(env.clone()),
1874 account_owner: ProgramCacheEntryOwner::LoaderV3,
1875 deployment_slot: 100,
1876 stats: Arc::clone(&stats),
1877 latest_access_slot: AtomicU64::default(),
1878 });
1879 cache.assign_program(&env, program_id, 100, unloaded);
1880
1881 let loaded = Arc::new(ProgramCacheEntry {
1883 program: new_loaded_entry(env.clone()),
1884 account_owner: ProgramCacheEntryOwner::LoaderV3,
1885 deployment_slot: 100,
1886 stats: Arc::default(), latest_access_slot: AtomicU64::default(),
1888 });
1889 cache.assign_program(&env, program_id, 100, Arc::clone(&loaded));
1890
1891 assert_eq!(cache.stats.insertions.load(Ordering::Relaxed), 1);
1892 assert_eq!(cache.stats.reloads.load(Ordering::Relaxed), 1);
1893
1894 let merged = &loaded.stats;
1895 let ord = Ordering::Relaxed;
1896 assert_eq!(merged.uses.load(ord), stats.uses.load(ord));
1897 assert_eq!(merged.compilations.load(ord), stats.compilations.load(ord));
1898 assert_eq!(
1899 merged.total_compilation_time_us.load(ord),
1900 stats.total_compilation_time_us.load(ord)
1901 );
1902 assert_eq!(
1903 merged.jit_invocations.load(ord),
1904 stats.jit_invocations.load(ord)
1905 );
1906 assert_eq!(
1907 merged.total_jit_execution_time_us.load(ord),
1908 stats.total_jit_execution_time_us.load(ord)
1909 );
1910 assert_eq!(
1911 merged.interpreted_invocations.load(ord),
1912 stats.interpreted_invocations.load(ord)
1913 );
1914 assert_eq!(
1915 merged.total_interpretation_time_us.load(ord),
1916 stats.total_interpretation_time_us.load(ord)
1917 );
1918
1919 const EMA_DIVISOR: u64 = 2;
1922 assert_eq!(
1923 merged.compilation_time_ema.load(ord),
1924 stats
1925 .compilation_time_ema
1926 .load(ord)
1927 .wrapping_div(EMA_DIVISOR)
1928 );
1929 assert_eq!(
1930 merged.jit_execution_time_ema.load(ord),
1931 stats
1932 .jit_execution_time_ema
1933 .load(ord)
1934 .wrapping_div(EMA_DIVISOR)
1935 );
1936 assert_eq!(
1937 merged.interpretation_time_ema.load(ord),
1938 stats
1939 .interpretation_time_ema
1940 .load(ord)
1941 .wrapping_div(EMA_DIVISOR)
1942 );
1943 }
1944
1945 #[test]
1946 fn test_tombstone() {
1947 let env = get_mock_program_runtime_environment();
1948 let tombstone = ProgramCacheEntry::new_failed_verification_tombstone(
1949 0,
1950 ProgramCacheEntryOwner::LoaderV2,
1951 env.clone(),
1952 );
1953 assert_matches!(
1954 tombstone.program,
1955 ProgramCacheEntryType::FailedVerification(_)
1956 );
1957 assert!(tombstone.is_tombstone());
1958 assert_eq!(tombstone.deployment_slot, 0);
1959 assert_eq!(tombstone.effective_slot(), 0);
1960
1961 let tombstone =
1962 ProgramCacheEntry::new_closed_tombstone(100, ProgramCacheEntryOwner::LoaderV2);
1963 assert_matches!(tombstone.program, ProgramCacheEntryType::Closed);
1964 assert!(tombstone.is_tombstone());
1965 assert_eq!(tombstone.deployment_slot, 100);
1966 assert_eq!(tombstone.effective_slot(), 100);
1967
1968 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1969 let program1 = Pubkey::new_unique();
1970 let tombstone = set_failed_verification_tombstone(&mut cache, program1, 10, env.clone());
1971 let slot_versions = cache.get_slot_versions_for_tests(&program1);
1972 assert_eq!(slot_versions.len(), 1);
1973 assert!(slot_versions.first().unwrap().is_tombstone());
1974 assert_eq!(tombstone.deployment_slot, 10);
1975 assert_eq!(tombstone.effective_slot(), 10);
1976
1977 let program2 = Pubkey::new_unique();
1979 cache.assign_program(&env, program2, 50, new_test_builtin_entry(50));
1980 let slot_versions = cache.get_slot_versions_for_tests(&program2);
1981 assert_eq!(slot_versions.len(), 1);
1982 assert!(!slot_versions.first().unwrap().is_tombstone());
1983
1984 let tombstone = set_failed_verification_tombstone(&mut cache, program2, 60, env);
1985 let slot_versions = cache.get_slot_versions_for_tests(&program2);
1986 assert_eq!(slot_versions.len(), 2);
1987 assert!(!slot_versions.first().unwrap().is_tombstone());
1988 assert!(slot_versions.get(1).unwrap().is_tombstone());
1989 assert!(tombstone.is_tombstone());
1990 assert_eq!(tombstone.deployment_slot, 60);
1991 assert_eq!(tombstone.effective_slot(), 60);
1992 }
1993
1994 struct TestForkGraph {
1995 relation: BlockRelation,
1996 }
1997 impl ForkGraph for TestForkGraph {
1998 fn relationship(&self, _a: Slot, _b: Slot) -> BlockRelation {
1999 self.relation
2000 }
2001 }
2002
2003 #[test]
2004 fn test_prune_empty() {
2005 let mut cache = ProgramCache::<TestForkGraph>::new(0);
2006 let fork_graph = Arc::new(RwLock::new(TestForkGraph {
2007 relation: BlockRelation::Unrelated,
2008 }));
2009
2010 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2011
2012 cache.prune(0, None, &fork_graph.read().unwrap());
2013 assert!(cache.get_flattened_entries_for_tests().is_empty());
2014
2015 cache.prune(10, None, &fork_graph.read().unwrap());
2016 assert!(cache.get_flattened_entries_for_tests().is_empty());
2017
2018 let mut cache = ProgramCache::<TestForkGraph>::new(0);
2019 let fork_graph = Arc::new(RwLock::new(TestForkGraph {
2020 relation: BlockRelation::Ancestor,
2021 }));
2022
2023 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2024
2025 cache.prune(0, None, &fork_graph.read().unwrap());
2026 assert!(cache.get_flattened_entries_for_tests().is_empty());
2027
2028 cache.prune(10, None, &fork_graph.read().unwrap());
2029 assert!(cache.get_flattened_entries_for_tests().is_empty());
2030
2031 let mut cache = ProgramCache::<TestForkGraph>::new(0);
2032 let fork_graph = Arc::new(RwLock::new(TestForkGraph {
2033 relation: BlockRelation::Descendant,
2034 }));
2035
2036 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2037
2038 cache.prune(0, None, &fork_graph.read().unwrap());
2039 assert!(cache.get_flattened_entries_for_tests().is_empty());
2040
2041 cache.prune(10, None, &fork_graph.read().unwrap());
2042 assert!(cache.get_flattened_entries_for_tests().is_empty());
2043
2044 let mut cache = ProgramCache::<TestForkGraph>::new(0);
2045 let fork_graph = Arc::new(RwLock::new(TestForkGraph {
2046 relation: BlockRelation::Unknown,
2047 }));
2048 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2049
2050 cache.prune(0, None, &fork_graph.read().unwrap());
2051 assert!(cache.get_flattened_entries_for_tests().is_empty());
2052
2053 cache.prune(10, None, &fork_graph.read().unwrap());
2054 assert!(cache.get_flattened_entries_for_tests().is_empty());
2055 }
2056
2057 #[test]
2058 fn test_prune_removes_programs_with_no_entries() {
2059 let (mut cache, fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Unknown);
2060 let env = get_mock_program_runtime_environment();
2061 let emptied = Pubkey::new_unique();
2062 cache.assign_program(
2063 &env,
2064 emptied,
2065 100,
2066 new_test_entry_with_owner(
2067 100,
2068 ProgramCacheEntryOwner::LoaderV3,
2069 new_loaded_entry(env.clone()),
2070 ),
2071 );
2072
2073 cache.prune(50, None, &fork_graph.read().unwrap());
2075 match &cache.index {
2076 IndexImplementation::V1 { entries, .. } => assert!(!entries.contains_key(&emptied)),
2077 }
2078 assert_eq!(cache.stats.empty_entries.load(Ordering::Relaxed), 1);
2079 assert_eq!(cache.stats.prunes_orphan.load(Ordering::Relaxed), 1);
2080 }
2081
2082 #[test]
2083 fn test_prune_across_the_root() {
2084 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
2090 let mut fork_graph = TestForkGraphSpecific::default();
2091 fork_graph.insert_fork(&[30, 50, 70]);
2092 let fork_graph = Arc::new(RwLock::new(fork_graph));
2093 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2094
2095 let env = get_mock_program_runtime_environment();
2096 let program_id = Pubkey::new_unique();
2097 let below = new_test_entry_with_owner(
2098 30,
2099 ProgramCacheEntryOwner::LoaderV3,
2100 new_loaded_entry(env.clone()),
2101 );
2102 let above = new_test_entry_with_owner(
2103 70,
2104 ProgramCacheEntryOwner::LoaderV3,
2105 new_loaded_entry(env.clone()),
2106 );
2107 cache.assign_program(&env, program_id, 30, Arc::clone(&below));
2108 cache.assign_program(&env, program_id, 70, Arc::clone(&above));
2109
2110 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
2111 assert_eq!(slot_versions.len(), 2);
2112 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &below));
2113 assert!(Arc::ptr_eq(slot_versions.get(1).unwrap(), &above));
2114
2115 cache.prune(50, None, &fork_graph.read().unwrap());
2116
2117 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
2119 assert_eq!(slot_versions.len(), 2);
2120 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &below));
2121 assert!(Arc::ptr_eq(slot_versions.get(1).unwrap(), &above));
2122 }
2123
2124 #[test]
2125 fn test_prune_across_the_root_ancestors() {
2126 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
2132 let mut fork_graph = TestForkGraphSpecific::default();
2133 fork_graph.insert_fork(&[10, 20, 30, 50, 70]);
2134 let fork_graph = Arc::new(RwLock::new(fork_graph));
2135 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2136
2137 let env = get_mock_program_runtime_environment();
2138 let program_id = Pubkey::new_unique();
2139 let oldest = new_test_entry_with_owner(
2140 10,
2141 ProgramCacheEntryOwner::LoaderV3,
2142 new_loaded_entry(env.clone()),
2143 );
2144 let older = new_test_entry_with_owner(
2145 20,
2146 ProgramCacheEntryOwner::LoaderV3,
2147 new_loaded_entry(env.clone()),
2148 );
2149 let below = new_test_entry_with_owner(
2150 30,
2151 ProgramCacheEntryOwner::LoaderV3,
2152 new_loaded_entry(env.clone()),
2153 );
2154 let above = new_test_entry_with_owner(
2155 70,
2156 ProgramCacheEntryOwner::LoaderV3,
2157 new_loaded_entry(env.clone()),
2158 );
2159 cache.assign_program(&env, program_id, 10, Arc::clone(&oldest));
2160 cache.assign_program(&env, program_id, 20, Arc::clone(&older));
2161 cache.assign_program(&env, program_id, 30, Arc::clone(&below));
2162 cache.assign_program(&env, program_id, 70, Arc::clone(&above));
2163
2164 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
2165 assert_eq!(slot_versions.len(), 4);
2166 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &oldest));
2167 assert!(Arc::ptr_eq(slot_versions.get(1).unwrap(), &older));
2168 assert!(Arc::ptr_eq(slot_versions.get(2).unwrap(), &below));
2169 assert!(Arc::ptr_eq(slot_versions.get(3).unwrap(), &above));
2170
2171 cache.prune(50, None, &fork_graph.read().unwrap());
2172
2173 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
2176 assert_eq!(slot_versions.len(), 2);
2177 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &below));
2178 assert!(Arc::ptr_eq(slot_versions.get(1).unwrap(), &above));
2179 assert_eq!(cache.stats.prunes_orphan.load(Ordering::Relaxed), 2);
2180 }
2181
2182 #[test]
2183 fn test_prune_entry_older_than_root() {
2184 let (mut cache, fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Unknown);
2198 let env = get_mock_program_runtime_environment();
2199 let program_id = Pubkey::new_unique();
2200 let entry = new_test_entry_with_owner(
2201 5,
2202 ProgramCacheEntryOwner::LoaderV3,
2203 new_loaded_entry(env.clone()),
2204 );
2205 cache.assign_program(&env, program_id, 5, Arc::clone(&entry));
2206 cache.latest_root_slot = 10;
2207
2208 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
2209 assert_eq!(slot_versions.len(), 1);
2210 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &entry));
2211
2212 cache.prune(20, None, &fork_graph.read().unwrap());
2213
2214 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
2218 assert_eq!(slot_versions.len(), 1);
2219 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &entry));
2220 assert_eq!(cache.stats.prunes_orphan.load(Ordering::Relaxed), 0);
2221 }
2222
2223 #[test]
2224 fn test_prune_orphan_newer_than_root() {
2225 let (mut cache, fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Unknown);
2235 let env = get_mock_program_runtime_environment();
2236 let program_id = Pubkey::new_unique();
2237 let orphan = new_test_entry_with_owner(
2238 100,
2239 ProgramCacheEntryOwner::LoaderV3,
2240 new_loaded_entry(env.clone()),
2241 );
2242 cache.assign_program(&env, program_id, 100, Arc::clone(&orphan));
2243
2244 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
2245 assert_eq!(slot_versions.len(), 1);
2246 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &orphan));
2247
2248 cache.prune(50, None, &fork_graph.read().unwrap());
2249
2250 assert!(cache.get_slot_versions_for_tests(&program_id).is_empty());
2253 assert_eq!(cache.stats.prunes_orphan.load(Ordering::Relaxed), 1);
2254 }
2255
2256 #[test]
2257 fn test_prune_tombstones() {
2258 let env = get_mock_program_runtime_environment();
2259 let fork_graph = Arc::new(RwLock::new(TestForkGraph {
2260 relation: BlockRelation::Ancestor,
2261 }));
2262
2263 let program1 = Pubkey::new_unique();
2264 let entries = [
2265 Arc::new(ProgramCacheEntry::new_unloaded(
2266 20,
2267 ProgramCacheEntryOwner::LoaderV3,
2268 ProgramRuntimeEnvironment::clone(&env),
2269 )),
2270 Arc::new(ProgramCacheEntry::new_closed_tombstone(
2271 20,
2272 ProgramCacheEntryOwner::LoaderV3,
2273 )),
2274 Arc::new(ProgramCacheEntry::new_failed_verification_tombstone(
2275 20,
2276 ProgramCacheEntryOwner::LoaderV3,
2277 ProgramRuntimeEnvironment::clone(&env),
2278 )),
2279 ];
2280 for entry in &entries {
2281 let mut cache = ProgramCache::<TestForkGraph>::new(0);
2282 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2283 cache.assign_program(&env, program1, 10, new_test_entry(10));
2285 cache.assign_program(&env, program1, entry.deployment_slot, Arc::clone(entry));
2286 cache.prune(
2287 MAX_TOMBSTONE_AGE_IN_SLOTS,
2288 None,
2289 &fork_graph.read().unwrap(),
2290 );
2291 let slot_versions = cache.get_slot_versions_for_tests(&program1);
2292 assert_eq!(slot_versions, std::slice::from_ref(entry));
2293 cache.prune(
2295 MAX_TOMBSTONE_AGE_IN_SLOTS
2296 .saturating_add(entry.latest_access_slot.load(Ordering::Relaxed)),
2297 None,
2298 &fork_graph.read().unwrap(),
2299 );
2300 let slot_versions = cache.get_slot_versions_for_tests(&program1);
2301 assert_eq!(slot_versions, std::slice::from_ref(entry));
2302 cache.prune(
2304 MAX_TOMBSTONE_AGE_IN_SLOTS
2305 .saturating_add(entry.latest_access_slot.load(Ordering::Relaxed))
2306 .saturating_add(1),
2307 None,
2308 &fork_graph.read().unwrap(),
2309 );
2310 assert!(cache.get_flattened_entries_for_tests().is_empty());
2311 }
2312 }
2313
2314 #[test]
2315 fn test_prune_tombstone_first_ancestor_takes_the_rest() {
2316 let (mut cache, fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
2325 let env = get_mock_program_runtime_environment();
2326 let other_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
2327 let program_id = Pubkey::new_unique();
2328 let on_other_env = new_test_entry_with_owner(
2329 50,
2330 ProgramCacheEntryOwner::LoaderV3,
2331 new_loaded_entry(other_env.clone()),
2332 );
2333 let on_env = new_test_entry_with_owner(
2334 60,
2335 ProgramCacheEntryOwner::LoaderV3,
2336 new_loaded_entry(env.clone()),
2337 );
2338 let closed = new_test_entry_with_owner(
2339 100,
2340 ProgramCacheEntryOwner::LoaderV3,
2341 new_closed_entry(env.clone()),
2342 );
2343 cache.assign_program(&other_env, program_id, 50, Arc::clone(&on_other_env));
2344 cache.assign_program(&env, program_id, 60, Arc::clone(&on_env));
2345 cache.assign_program(&env, program_id, 100, Arc::clone(&closed));
2346
2347 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
2348 assert_eq!(slot_versions.len(), 3);
2349 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &on_other_env));
2350 assert!(Arc::ptr_eq(slot_versions.get(1).unwrap(), &on_env));
2351 assert!(Arc::ptr_eq(slot_versions.get(2).unwrap(), &closed));
2352
2353 cache.prune(200, None, &fork_graph.read().unwrap());
2354
2355 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
2359 assert_eq!(slot_versions.len(), 1);
2360 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &closed));
2361 assert_eq!(cache.stats.prunes_orphan.load(Ordering::Relaxed), 2);
2362 }
2363
2364 #[test]
2365 fn test_prune_tombstone_newer_than_root_keeps_the_rest() {
2366 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
2376 let mut fork_graph = TestForkGraphSpecific::default();
2377 fork_graph.insert_fork(&[50, 60, 100, 101]);
2378 let fork_graph = Arc::new(RwLock::new(fork_graph));
2379 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2380
2381 let env = get_mock_program_runtime_environment();
2382 let other_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
2383 let program_id = Pubkey::new_unique();
2384 let on_other_env = new_test_entry_with_owner(
2385 50,
2386 ProgramCacheEntryOwner::LoaderV3,
2387 new_loaded_entry(other_env.clone()),
2388 );
2389 let on_env = new_test_entry_with_owner(
2390 60,
2391 ProgramCacheEntryOwner::LoaderV3,
2392 new_loaded_entry(env.clone()),
2393 );
2394 let closed = new_test_entry_with_owner(
2395 101,
2396 ProgramCacheEntryOwner::LoaderV3,
2397 new_closed_entry(env.clone()),
2398 );
2399 cache.assign_program(&other_env, program_id, 50, Arc::clone(&on_other_env));
2400 cache.assign_program(&env, program_id, 60, Arc::clone(&on_env));
2401 cache.assign_program(&env, program_id, 101, Arc::clone(&closed));
2402
2403 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
2404 assert_eq!(slot_versions.len(), 3);
2405 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &on_other_env));
2406 assert!(Arc::ptr_eq(slot_versions.get(1).unwrap(), &on_env));
2407 assert!(Arc::ptr_eq(slot_versions.get(2).unwrap(), &closed));
2408
2409 cache.prune(100, None, &fork_graph.read().unwrap());
2410
2411 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
2416 assert_eq!(slot_versions.len(), 3);
2417 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &on_other_env));
2418 assert!(Arc::ptr_eq(slot_versions.get(1).unwrap(), &on_env));
2419 assert!(Arc::ptr_eq(slot_versions.get(2).unwrap(), &closed));
2420 assert_eq!(cache.stats.prunes_orphan.load(Ordering::Relaxed), 0);
2421 }
2422
2423 #[test]
2424 fn test_prune_with_two_environments_before_epoch_boundary() {
2425 let mut cache = ProgramCache::<TestForkGraph>::new(0);
2426 let env = get_mock_program_runtime_environment();
2427 let new_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
2428 let fork_graph = Arc::new(RwLock::new(TestForkGraph {
2429 relation: BlockRelation::Ancestor,
2430 }));
2431 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2432
2433 let program1 = Pubkey::new_unique();
2434 cache.assign_program(&env, program1, 10, new_test_entry(10));
2435 let updated_program = Arc::new(ProgramCacheEntry {
2436 program: new_loaded_entry(new_env.clone()),
2437 deployment_slot: 20,
2438 ..Default::default()
2439 });
2440 cache.assign_program(&env, program1, 20, updated_program.clone());
2441
2442 assert_eq!(cache.get_slot_versions_for_tests(&program1).len(), 2);
2444
2445 cache.prune(21, None, &fork_graph.read().unwrap());
2446
2447 assert_eq!(cache.get_slot_versions_for_tests(&program1).len(), 2);
2449 }
2450
2451 #[test]
2452 fn test_prune_with_two_environments_after_epoch_boundary() {
2453 let mut cache = ProgramCache::<TestForkGraph>::new(0);
2454 let env = get_mock_program_runtime_environment();
2455 let new_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
2456 let fork_graph = Arc::new(RwLock::new(TestForkGraph {
2457 relation: BlockRelation::Ancestor,
2458 }));
2459 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2460 let program1 = Pubkey::new_unique();
2461
2462 let old_program_old_env = Arc::new(ProgramCacheEntry {
2463 program: new_loaded_entry(env.clone()),
2464 deployment_slot: 10,
2465 ..Default::default()
2466 });
2467 let old_program_new_env = Arc::new(ProgramCacheEntry {
2468 program: new_loaded_entry(new_env.clone()),
2469 deployment_slot: 10,
2470 ..Default::default()
2471 });
2472 let new_program_old_env = Arc::new(ProgramCacheEntry {
2473 program: new_loaded_entry(env.clone()),
2474 deployment_slot: 20,
2475 ..Default::default()
2476 });
2477 cache.assign_program(&env, program1, 10, old_program_old_env.clone());
2478 cache.assign_program(&env, program1, 10, old_program_new_env.clone());
2479 cache.assign_program(&env, program1, 20, new_program_old_env.clone());
2480 let slot_versions = cache.get_slot_versions_for_tests(&program1);
2481 assert_eq!(
2482 &slot_versions,
2483 &[
2484 old_program_new_env.clone(),
2485 old_program_old_env.clone(),
2486 new_program_old_env.clone(),
2487 ]
2488 );
2489
2490 cache.prune(21, Some(new_env.clone()), &fork_graph.read().unwrap());
2491 let slot_versions = cache.get_slot_versions_for_tests(&program1);
2492 assert_eq!(&slot_versions, &[old_program_new_env]);
2493 assert!(matches!(
2494 &slot_versions.first().unwrap().program,
2495 ProgramCacheEntryType::Loaded(_)
2496 ));
2497 }
2498
2499 #[test_matrix(
2500 (
2501 new_closed_entry,
2502 new_builtin_entry,
2503 new_failed_verification_entry,
2504 new_unloaded_entry,
2505 new_loaded_entry,
2506 ),
2507 (false, true)
2508 )]
2509 fn test_prune_environment_sweep_by_entry_type(
2510 new_program: fn(ProgramRuntimeEnvironment) -> ProgramCacheEntryType,
2511 on_new_environment: bool,
2512 ) {
2513 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
2523 let mut fork_graph = TestForkGraphSpecific::default();
2524 fork_graph.insert_fork(&[40, 50]);
2525 let fork_graph = Arc::new(RwLock::new(fork_graph));
2526 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2527
2528 let env = get_mock_program_runtime_environment();
2529 let new_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
2530 let entry_env = if on_new_environment {
2531 new_env.clone()
2532 } else {
2533 env.clone()
2534 };
2535 let program_id = Pubkey::new_unique();
2536 let entry = new_test_entry_with_owner(
2537 50,
2538 ProgramCacheEntryOwner::LoaderV3,
2539 new_program(entry_env.clone()),
2540 );
2541 let carries_an_environment = entry.program.get_environment().is_some();
2542 cache.assign_program(&entry_env, program_id, 50, Arc::clone(&entry));
2543
2544 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
2545 assert_eq!(slot_versions.len(), 1);
2546 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &entry));
2547
2548 cache.prune(40, Some(new_env.clone()), &fork_graph.read().unwrap());
2549
2550 let swept = carries_an_environment && !on_new_environment;
2553 assert_eq!(
2554 cache.stats.prunes_environment.load(Ordering::Relaxed),
2555 u64::from(swept)
2556 );
2557 if swept {
2558 assert!(cache.get_slot_versions_for_tests(&program_id).is_empty());
2559 } else {
2560 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
2561 assert_eq!(slot_versions.len(), 1);
2562 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &entry));
2563 }
2564 }
2565
2566 #[test]
2567 fn test_prune_environment_sweep_keeps_tombstones() {
2568 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
2580 let mut fork_graph = TestForkGraphSpecific::default();
2581 fork_graph.insert_fork(&[0, 40, 50, 60, 70, 100]);
2582 let fork_graph = Arc::new(RwLock::new(fork_graph));
2583 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2584 let env = get_mock_program_runtime_environment();
2585 let new_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
2586 let program_id = Pubkey::new_unique();
2587 let closed = new_test_entry_with_owner(
2588 50,
2589 ProgramCacheEntryOwner::LoaderV3,
2590 new_closed_entry(env.clone()),
2591 );
2592 let failed_verification = new_test_entry_with_owner(
2593 60,
2594 ProgramCacheEntryOwner::LoaderV3,
2595 new_failed_verification_entry(env.clone()),
2597 );
2598 let loaded = new_test_entry_with_owner(
2599 70,
2600 ProgramCacheEntryOwner::LoaderV3,
2601 new_loaded_entry(env.clone()),
2602 );
2603 cache.assign_program(&env, program_id, 50, Arc::clone(&closed));
2604 cache.assign_program(&env, program_id, 60, Arc::clone(&failed_verification));
2605 cache.assign_program(&env, program_id, 70, Arc::clone(&loaded));
2606
2607 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
2608 assert_eq!(slot_versions.len(), 3);
2609 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &closed));
2610 assert!(Arc::ptr_eq(
2611 slot_versions.get(1).unwrap(),
2612 &failed_verification
2613 ));
2614 assert!(Arc::ptr_eq(slot_versions.get(2).unwrap(), &loaded));
2615
2616 cache.prune(40, Some(new_env.clone()), &fork_graph.read().unwrap());
2619 assert_eq!(cache.stats.prunes_environment.load(Ordering::Relaxed), 2);
2620 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
2621 assert_eq!(slot_versions.len(), 1);
2622 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &closed));
2623
2624 let mut search_for = vec![ProgramToLoad {
2626 program_id: &program_id,
2627 loader: ProgramCacheEntryOwner::LoaderV3,
2628 deployment_slot: 50,
2629 }];
2630 let mut extracted = ProgramCacheForTxBatch::new(100);
2631 cache.extract(&mut search_for, &mut extracted, &new_env, true, true);
2632 assert!(search_for.is_empty());
2633 assert!(Arc::ptr_eq(
2634 extracted.entries.get(&program_id).unwrap(),
2635 &closed
2636 ));
2637
2638 let mut search_for = vec![ProgramToLoad {
2642 program_id: &program_id,
2643 loader: ProgramCacheEntryOwner::LoaderV3,
2644 deployment_slot: 50,
2645 }];
2646 let mut extracted = ProgramCacheForTxBatch::new(100);
2647 cache.extract(&mut search_for, &mut extracted, &env, true, true);
2648 assert!(search_for.is_empty());
2649 assert!(Arc::ptr_eq(
2650 extracted.entries.get(&program_id).unwrap(),
2651 &closed
2652 ));
2653 }
2654
2655 #[test]
2656 #[should_panic(expected = "self.latest_root_slot <= new_root_slot")]
2657 fn test_prune_backwards_panics() {
2658 let (mut cache, fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
2659 cache.latest_root_slot = 100;
2660
2661 cache.prune(50, None, &fork_graph.read().unwrap());
2664 }
2665
2666 #[derive(Default)]
2667 struct TestForkGraphSpecific {
2668 forks: Vec<Vec<Slot>>,
2669 }
2670
2671 impl TestForkGraphSpecific {
2672 fn insert_fork(&mut self, fork: &[Slot]) {
2673 let mut fork = fork.to_vec();
2674 fork.sort();
2675 self.forks.push(fork)
2676 }
2677 }
2678
2679 impl ForkGraph for TestForkGraphSpecific {
2680 fn relationship(&self, a: Slot, b: Slot) -> BlockRelation {
2681 match self.forks.iter().try_for_each(|fork| {
2682 let relation = fork
2683 .iter()
2684 .position(|x| *x == a)
2685 .and_then(|a_pos| {
2686 fork.iter().position(|x| *x == b).and_then(|b_pos| {
2687 (a_pos == b_pos)
2688 .then_some(BlockRelation::Equal)
2689 .or_else(|| (a_pos < b_pos).then_some(BlockRelation::Ancestor))
2690 .or(Some(BlockRelation::Descendant))
2691 })
2692 })
2693 .unwrap_or(BlockRelation::Unrelated);
2694
2695 if relation != BlockRelation::Unrelated {
2696 return ControlFlow::Break(relation);
2697 }
2698
2699 ControlFlow::Continue(())
2700 }) {
2701 ControlFlow::Break(relation) => relation,
2702 _ => BlockRelation::Unrelated,
2703 }
2704 }
2705 }
2706
2707 fn get_entries_to_load<'a>(
2708 cache: &ProgramCache<TestForkGraphSpecific>,
2709 loading_slot: Slot,
2710 keys: &'a [Pubkey],
2711 ) -> Vec<ProgramToLoad<'a>> {
2712 let fork_graph = cache.fork_graph.as_ref().unwrap().upgrade().unwrap();
2713 let locked_fork_graph = fork_graph.read().unwrap();
2714 let entries = cache.get_flattened_entries_for_tests();
2715 keys.iter()
2716 .filter_map(|key| {
2717 entries
2718 .iter()
2719 .rev()
2720 .find(|(program_id, entry)| {
2721 program_id == key
2722 && matches!(
2723 locked_fork_graph.relationship(entry.deployment_slot, loading_slot),
2724 BlockRelation::Equal | BlockRelation::Ancestor,
2725 )
2726 })
2727 .map(|(_program_id, entry)| ProgramToLoad {
2728 program_id: key,
2729 loader: entry.account_owner,
2730 deployment_slot: entry.deployment_slot,
2731 })
2732 })
2733 .collect()
2734 }
2735
2736 fn match_slot(
2737 extracted: &ProgramCacheForTxBatch,
2738 program: &Pubkey,
2739 deployment_slot: Slot,
2740 working_slot: Slot,
2741 ) -> bool {
2742 assert_eq!(extracted.slot, working_slot);
2743 extracted
2744 .entries
2745 .get(program)
2746 .map(|entry| entry.deployment_slot == deployment_slot)
2747 .unwrap_or(false)
2748 }
2749
2750 fn match_missing(
2751 missing: &[ProgramToLoad],
2752 program_id: &Pubkey,
2753 expected_result: bool,
2754 ) -> bool {
2755 missing.iter().any(|entry| entry.program_id == program_id) == expected_result
2756 }
2757
2758 #[test]
2759 fn test_fork_extract_and_prune() {
2760 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
2761 let env = get_mock_program_runtime_environment();
2762
2763 let mut fork_graph = TestForkGraphSpecific::default();
2779 fork_graph.insert_fork(&[0, 10, 20, 22]);
2780 fork_graph.insert_fork(&[0, 5, 11, 15, 16, 18, 19, 21, 23]);
2781 fork_graph.insert_fork(&[0, 5, 11, 25, 27]);
2782
2783 let fork_graph = Arc::new(RwLock::new(fork_graph));
2784 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2785
2786 let program1 = Pubkey::new_unique();
2787 cache.assign_program(&env, program1, 0, new_test_entry(0));
2788 cache.assign_program(&env, program1, 10, new_test_entry(10));
2789 cache.assign_program(&env, program1, 20, new_test_entry(20));
2790
2791 let program2 = Pubkey::new_unique();
2792 cache.assign_program(&env, program2, 5, new_test_entry(5));
2793 cache.assign_program(&env, program2, 11, new_test_entry(11));
2794
2795 let program3 = Pubkey::new_unique();
2796 cache.assign_program(&env, program3, 25, new_test_entry(25));
2797
2798 let program4 = Pubkey::new_unique();
2799 cache.assign_program(&env, program4, 0, new_test_entry(0));
2800 cache.assign_program(&env, program4, 5, new_test_entry(5));
2801 cache.assign_program(&env, program4, 15, new_test_entry(15));
2803
2804 let keys = &[program1, program2, program3, program4];
2821 let mut missing = get_entries_to_load(&cache, 22, keys);
2822 assert!(match_missing(&missing, &program2, false));
2823 assert!(match_missing(&missing, &program3, false));
2824 let mut extracted = ProgramCacheForTxBatch::new(22);
2825 cache.extract(&mut missing, &mut extracted, &env, true, true);
2826 assert!(match_slot(&extracted, &program1, 20, 22));
2827 assert!(match_slot(&extracted, &program4, 0, 22));
2828
2829 let mut missing = get_entries_to_load(&cache, 15, keys);
2831 assert!(match_missing(&missing, &program3, false));
2832 let mut extracted = ProgramCacheForTxBatch::new(15);
2833 cache.extract(&mut missing, &mut extracted, &env, true, true);
2834 assert!(match_slot(&extracted, &program1, 0, 15));
2835 assert!(match_slot(&extracted, &program2, 11, 15));
2836 let tombstone = extracted
2839 .find(&program4)
2840 .expect("Failed to find the tombstone");
2841 assert_matches!(tombstone.program, ProgramCacheEntryType::DelayVisibility);
2842 assert_eq!(tombstone.deployment_slot, 15);
2843
2844 let mut missing = get_entries_to_load(&cache, 18, keys);
2846 assert!(match_missing(&missing, &program3, false));
2847 let mut extracted = ProgramCacheForTxBatch::new(18);
2848 cache.extract(&mut missing, &mut extracted, &env, true, true);
2849 assert!(match_slot(&extracted, &program1, 0, 18));
2850 assert!(match_slot(&extracted, &program2, 11, 18));
2851 assert!(match_slot(&extracted, &program4, 15, 18));
2853
2854 let mut missing = get_entries_to_load(&cache, 23, keys);
2856 assert!(match_missing(&missing, &program3, false));
2857 let mut extracted = ProgramCacheForTxBatch::new(23);
2858 cache.extract(&mut missing, &mut extracted, &env, true, true);
2859 assert!(match_slot(&extracted, &program1, 0, 23));
2860 assert!(match_slot(&extracted, &program2, 11, 23));
2861 assert!(match_slot(&extracted, &program4, 15, 23));
2863
2864 let mut missing = get_entries_to_load(&cache, 11, keys);
2866 assert!(match_missing(&missing, &program3, false));
2867 let mut extracted = ProgramCacheForTxBatch::new(11);
2868 cache.extract(&mut missing, &mut extracted, &env, true, true);
2869 assert!(match_slot(&extracted, &program1, 0, 11));
2870 let tombstone = extracted
2872 .find(&program2)
2873 .expect("Failed to find the tombstone");
2874 assert_matches!(tombstone.program, ProgramCacheEntryType::DelayVisibility);
2875 assert_eq!(tombstone.deployment_slot, 11);
2876 assert!(match_slot(&extracted, &program4, 5, 11));
2877
2878 cache.prune(5, None, &fork_graph.read().unwrap());
2879
2880 let mut missing = get_entries_to_load(&cache, 21, keys);
2897 assert!(match_missing(&missing, &program3, false));
2898 let mut extracted = ProgramCacheForTxBatch::new(21);
2899 cache.extract(&mut missing, &mut extracted, &env, true, true);
2900 assert!(match_slot(&extracted, &program1, 0, 21));
2902 assert!(match_slot(&extracted, &program2, 11, 21));
2903 assert!(match_slot(&extracted, &program4, 15, 21));
2904
2905 let mut missing = get_entries_to_load(&cache, 27, keys);
2907 let mut extracted = ProgramCacheForTxBatch::new(27);
2908 cache.extract(&mut missing, &mut extracted, &env, true, true);
2909 assert!(match_slot(&extracted, &program1, 0, 27));
2910 assert!(match_slot(&extracted, &program2, 11, 27));
2911 assert!(match_slot(&extracted, &program3, 25, 27));
2912 assert!(match_slot(&extracted, &program4, 5, 27));
2913
2914 cache.prune(15, None, &fork_graph.read().unwrap());
2915
2916 let mut missing = get_entries_to_load(&cache, 23, keys);
2933 assert!(match_missing(&missing, &program3, false));
2934 let mut extracted = ProgramCacheForTxBatch::new(23);
2935 cache.extract(&mut missing, &mut extracted, &env, true, true);
2936 assert!(match_slot(&extracted, &program1, 0, 23));
2937 assert!(match_slot(&extracted, &program2, 11, 23));
2938 assert!(match_slot(&extracted, &program4, 15, 23));
2939 }
2940
2941 #[test]
2942 fn test_extract_using_deployment_slot() {
2943 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
2944 let env = get_mock_program_runtime_environment();
2945
2946 let mut fork_graph = TestForkGraphSpecific::default();
2962 fork_graph.insert_fork(&[0, 10, 20, 22]);
2963 fork_graph.insert_fork(&[0, 5, 11, 12, 15, 16, 18, 19, 21, 23]);
2964 fork_graph.insert_fork(&[0, 5, 11, 25, 27]);
2965
2966 let fork_graph = Arc::new(RwLock::new(fork_graph));
2967 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2968
2969 let program1 = Pubkey::new_unique();
2970 cache.assign_program(&env, program1, 0, new_test_entry(0));
2971 cache.assign_program(&env, program1, 20, new_test_entry(20));
2972
2973 let program2 = Pubkey::new_unique();
2974 cache.assign_program(&env, program2, 5, new_test_entry(5));
2975 cache.assign_program(&env, program2, 11, new_test_entry(11));
2976
2977 let program3 = Pubkey::new_unique();
2978 cache.assign_program(&env, program3, 25, new_test_entry(25));
2979
2980 let keys = &[program1, program2, program3];
2982 let mut missing = get_entries_to_load(&cache, 12, keys);
2983 assert!(match_missing(&missing, &program3, false));
2984 let mut extracted = ProgramCacheForTxBatch::new(12);
2985 cache.extract(&mut missing, &mut extracted, &env, true, true);
2986 assert!(match_slot(&extracted, &program1, 0, 12));
2987 assert!(match_slot(&extracted, &program2, 11, 12));
2988
2989 let mut missing = get_entries_to_load(&cache, 12, keys);
2992 missing.get_mut(1).unwrap().deployment_slot = 5;
2995 assert!(match_missing(&missing, &program3, false));
2996 let mut extracted = ProgramCacheForTxBatch::new(12);
2997 cache.extract(&mut missing, &mut extracted, &env, true, true);
2998 assert!(match_slot(&extracted, &program1, 0, 12));
2999 assert!(match_slot(&extracted, &program2, 5, 12));
3000 }
3001
3002 #[test]
3003 fn test_extract_rejects_entry_deployed_after_the_requested_slot() {
3004 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
3005 let env = get_mock_program_runtime_environment();
3006
3007 let mut fork_graph = TestForkGraphSpecific::default();
3008 fork_graph.insert_fork(&[0, 5, 11, 12]);
3009 let fork_graph = Arc::new(RwLock::new(fork_graph));
3010 cache.set_fork_graph(Arc::downgrade(&fork_graph));
3011
3012 let program = Pubkey::new_unique();
3014 cache.assign_program(&env, program, 11, new_test_entry(11));
3015
3016 let mut missing = vec![ProgramToLoad {
3018 program_id: &program,
3019 loader: ProgramCacheEntryOwner::LoaderV2,
3020 deployment_slot: 5,
3021 }];
3022 let mut extracted = ProgramCacheForTxBatch::new(12);
3023 cache.extract(&mut missing, &mut extracted, &env, true, true);
3024 assert!(match_missing(&missing, &program, true));
3025 assert!(extracted.find(&program).is_none());
3026
3027 let mut missing = vec![ProgramToLoad {
3029 program_id: &program,
3030 loader: ProgramCacheEntryOwner::LoaderV2,
3031 deployment_slot: 11,
3032 }];
3033 let mut extracted = ProgramCacheForTxBatch::new(12);
3034 cache.extract(&mut missing, &mut extracted, &env, true, true);
3035 assert!(match_missing(&missing, &program, false));
3036 assert!(match_slot(&extracted, &program, 11, 12));
3037 }
3038
3039 #[test]
3040 fn test_extract_unloaded() {
3041 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
3042 let env = get_mock_program_runtime_environment();
3043
3044 let mut fork_graph = TestForkGraphSpecific::default();
3060 fork_graph.insert_fork(&[0, 10, 20, 22]);
3061 fork_graph.insert_fork(&[0, 5, 11, 15, 16, 19, 21, 23]);
3062 fork_graph.insert_fork(&[0, 5, 11, 25, 27]);
3063
3064 let fork_graph = Arc::new(RwLock::new(fork_graph));
3065 cache.set_fork_graph(Arc::downgrade(&fork_graph));
3066
3067 let program1 = Pubkey::new_unique();
3068 cache.assign_program(&env, program1, 0, new_test_entry(0));
3069 cache.assign_program(&env, program1, 20, new_test_entry(20));
3070
3071 let program2 = Pubkey::new_unique();
3072 cache.assign_program(&env, program2, 5, new_test_entry(5));
3073 cache.assign_program(&env, program2, 11, new_test_entry(11));
3074
3075 let program3 = Pubkey::new_unique();
3076 let _ = insert_unloaded_entry(&mut cache, program3, 25);
3078
3079 cache.assign_program(
3083 &env,
3084 program3,
3085 20,
3086 Arc::new(
3087 new_test_entry(20)
3088 .to_unloaded()
3089 .expect("Failed to create unloaded program"),
3090 ),
3091 );
3092
3093 let keys = &[program1, program2, program3];
3095 let mut missing = get_entries_to_load(&cache, 19, keys);
3096 assert!(match_missing(&missing, &program3, false));
3097 let mut extracted = ProgramCacheForTxBatch::new(19);
3098 cache.extract(&mut missing, &mut extracted, &env, true, true);
3099 assert!(match_slot(&extracted, &program1, 0, 19));
3100 assert!(match_slot(&extracted, &program2, 11, 19));
3101
3102 let mut missing = get_entries_to_load(&cache, 27, keys);
3104 let mut extracted = ProgramCacheForTxBatch::new(27);
3105 cache.extract(&mut missing, &mut extracted, &env, true, true);
3106 assert!(match_slot(&extracted, &program1, 0, 27));
3107 assert!(match_slot(&extracted, &program2, 11, 27));
3108 assert!(match_missing(&missing, &program3, true));
3109
3110 let mut missing = get_entries_to_load(&cache, 22, keys);
3112 assert!(match_missing(&missing, &program2, false));
3113 let mut extracted = ProgramCacheForTxBatch::new(22);
3114 cache.extract(&mut missing, &mut extracted, &env, true, true);
3115 assert!(match_slot(&extracted, &program1, 20, 22));
3116 assert!(match_missing(&missing, &program3, true));
3117 }
3118
3119 #[test]
3120 fn test_extract_different_environment() {
3121 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
3122 let env = get_mock_program_runtime_environment();
3123 let other_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
3124
3125 let mut fork_graph = TestForkGraphSpecific::default();
3135 fork_graph.insert_fork(&[0, 10, 20, 22]);
3136
3137 let fork_graph = Arc::new(RwLock::new(fork_graph));
3138 cache.set_fork_graph(Arc::downgrade(&fork_graph));
3139
3140 let program1 = Pubkey::new_unique();
3141 cache.assign_program(
3142 &env,
3143 program1,
3144 10,
3145 Arc::new(ProgramCacheEntry::new_closed_tombstone(
3146 10,
3147 ProgramCacheEntryOwner::LoaderV3,
3148 )),
3149 );
3150 cache.assign_program(&env, program1, 20, new_test_entry(20));
3151
3152 let keys = &[program1];
3154 let mut missing = get_entries_to_load(&cache, 22, keys);
3155 let mut extracted = ProgramCacheForTxBatch::new(22);
3156 cache.extract(&mut missing, &mut extracted, &env, true, true);
3157 assert!(match_slot(&extracted, &program1, 20, 22));
3158
3159 let mut missing = get_entries_to_load(&cache, 22, keys);
3161 let mut extracted = ProgramCacheForTxBatch::new(22);
3162 cache.extract(&mut missing, &mut extracted, &other_env, true, true);
3163 assert!(match_missing(&missing, &program1, true));
3164 }
3165
3166 #[test_matrix((false, true))]
3167 fn test_extract_no_second_level(empty_second_level: bool) {
3168 let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
3169 let env = get_mock_program_runtime_environment();
3170 let program_id = Pubkey::new_unique();
3171 if empty_second_level {
3172 match &mut cache.index {
3174 IndexImplementation::V1 { entries, .. } => {
3175 entries.insert(program_id, Vec::new());
3176 }
3177 }
3178 }
3179
3180 let mut search_for = vec![ProgramToLoad {
3183 program_id: &program_id,
3184 loader: ProgramCacheEntryOwner::LoaderV3,
3185 deployment_slot: 0,
3186 }];
3187 let mut extracted = ProgramCacheForTxBatch::new(100);
3188 let task = cache.extract(&mut search_for, &mut extracted, &env, true, true);
3189 assert_eq!(search_for.len(), 1);
3190 assert!(extracted.entries.is_empty());
3191 assert_eq!(task, Some(program_id));
3192 }
3193
3194 #[test]
3195 fn test_extract_account_owner_mismatch() {
3196 let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
3197 let env = get_mock_program_runtime_environment();
3198 let program_id = Pubkey::new_unique();
3199 let owned_by_v2 = new_test_entry_with_owner(
3200 100,
3201 ProgramCacheEntryOwner::LoaderV2,
3202 new_loaded_entry(env.clone()),
3203 );
3204 cache.assign_program(&env, program_id, 100, Arc::clone(&owned_by_v2));
3205
3206 let mut search_for = vec![ProgramToLoad {
3209 program_id: &program_id,
3210 loader: ProgramCacheEntryOwner::LoaderV3,
3211 deployment_slot: 100,
3212 }];
3213 let mut extracted = ProgramCacheForTxBatch::new(200);
3214 cache.extract(&mut search_for, &mut extracted, &env, true, true);
3215 assert_eq!(search_for.len(), 1);
3216 assert!(extracted.entries.is_empty());
3217
3218 let owned_by_v3 = new_test_entry_with_owner(
3221 150,
3222 ProgramCacheEntryOwner::LoaderV3,
3223 new_loaded_entry(env.clone()),
3224 );
3225 cache.assign_program(&env, program_id, 150, Arc::clone(&owned_by_v3));
3226
3227 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
3229 assert_eq!(slot_versions.len(), 2);
3230 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &owned_by_v2));
3231 assert!(Arc::ptr_eq(slot_versions.get(1).unwrap(), &owned_by_v3));
3232
3233 let mut search_for = vec![ProgramToLoad {
3236 program_id: &program_id,
3237 loader: ProgramCacheEntryOwner::LoaderV2,
3238 deployment_slot: 100,
3239 }];
3240 let mut extracted = ProgramCacheForTxBatch::new(200);
3241 cache.extract(&mut search_for, &mut extracted, &env, true, true);
3242 assert!(search_for.is_empty());
3243 assert!(Arc::ptr_eq(
3244 extracted.entries.get(&program_id).unwrap(),
3245 &owned_by_v2
3246 ));
3247
3248 let mut search_for = vec![ProgramToLoad {
3251 program_id: &program_id,
3252 loader: ProgramCacheEntryOwner::LoaderV3,
3253 deployment_slot: 150,
3254 }];
3255 let mut extracted = ProgramCacheForTxBatch::new(200);
3256 cache.extract(&mut search_for, &mut extracted, &env, true, true);
3257 assert!(search_for.is_empty());
3258 assert!(Arc::ptr_eq(
3259 extracted.entries.get(&program_id).unwrap(),
3260 &owned_by_v3
3261 ));
3262 }
3263
3264 #[test]
3265 fn test_extract_environment_mismatch() {
3266 let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
3267 let env = get_mock_program_runtime_environment();
3268 let other_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
3269 let program_id = Pubkey::new_unique();
3270 let on_other_env = new_test_entry_with_owner(
3271 100,
3272 ProgramCacheEntryOwner::LoaderV3,
3273 new_loaded_entry(other_env.clone()),
3274 );
3275 cache.assign_program(&other_env, program_id, 100, Arc::clone(&on_other_env));
3276
3277 let mut search_for = vec![ProgramToLoad {
3282 program_id: &program_id,
3283 loader: ProgramCacheEntryOwner::LoaderV3,
3284 deployment_slot: 100,
3285 }];
3286 let mut extracted = ProgramCacheForTxBatch::new(200);
3287 cache.extract(&mut search_for, &mut extracted, &env, true, true);
3288 assert_eq!(search_for.len(), 1);
3289 assert!(extracted.entries.is_empty());
3290
3291 let on_execution_env = new_test_entry_with_owner(
3294 100,
3295 ProgramCacheEntryOwner::LoaderV3,
3296 new_loaded_entry(env.clone()),
3297 );
3298 cache.assign_program(&env, program_id, 100, Arc::clone(&on_execution_env));
3299
3300 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
3303 assert_eq!(slot_versions.len(), 2);
3304 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &on_other_env));
3305 assert!(Arc::ptr_eq(
3306 slot_versions.get(1).unwrap(),
3307 &on_execution_env
3308 ));
3309
3310 let mut search_for = vec![ProgramToLoad {
3313 program_id: &program_id,
3314 loader: ProgramCacheEntryOwner::LoaderV3,
3315 deployment_slot: 100,
3316 }];
3317 let mut extracted = ProgramCacheForTxBatch::new(200);
3318 cache.extract(&mut search_for, &mut extracted, &env, true, true);
3319 assert!(search_for.is_empty());
3320 assert!(Arc::ptr_eq(
3321 extracted.entries.get(&program_id).unwrap(),
3322 &on_execution_env
3323 ));
3324
3325 let mut search_for = vec![ProgramToLoad {
3328 program_id: &program_id,
3329 loader: ProgramCacheEntryOwner::LoaderV3,
3330 deployment_slot: 100,
3331 }];
3332 let mut extracted = ProgramCacheForTxBatch::new(200);
3333 cache.extract(&mut search_for, &mut extracted, &other_env, true, true);
3334 assert!(search_for.is_empty());
3335 assert!(Arc::ptr_eq(
3336 extracted.entries.get(&program_id).unwrap(),
3337 &on_other_env
3338 ));
3339 }
3340
3341 #[test]
3342 fn test_extract_unloaded_entry() {
3343 let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
3344 let env = get_mock_program_runtime_environment();
3345 let program_id = Pubkey::new_unique();
3346 let unloaded = new_test_entry_with_owner(
3347 100,
3348 ProgramCacheEntryOwner::LoaderV3,
3349 new_unloaded_entry(env.clone()),
3350 );
3351 cache.assign_program(&env, program_id, 100, Arc::clone(&unloaded));
3352
3353 let mut search_for = vec![ProgramToLoad {
3357 program_id: &program_id,
3358 loader: ProgramCacheEntryOwner::LoaderV3,
3359 deployment_slot: 100,
3360 }];
3361 let mut extracted = ProgramCacheForTxBatch::new(200);
3362 cache.extract(&mut search_for, &mut extracted, &env, true, true);
3363 assert_eq!(search_for.len(), 1);
3364 assert!(extracted.entries.is_empty());
3365
3366 let loaded = new_test_entry_with_owner(
3368 100,
3369 ProgramCacheEntryOwner::LoaderV3,
3370 new_loaded_entry(env.clone()),
3371 );
3372 cache.assign_program(&env, program_id, 100, Arc::clone(&loaded));
3373 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
3374 assert_eq!(slot_versions.len(), 1);
3375 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &loaded));
3376
3377 let mut search_for = vec![ProgramToLoad {
3379 program_id: &program_id,
3380 loader: ProgramCacheEntryOwner::LoaderV3,
3381 deployment_slot: 100,
3382 }];
3383 let mut extracted = ProgramCacheForTxBatch::new(200);
3384 cache.extract(&mut search_for, &mut extracted, &env, true, true);
3385 assert!(search_for.is_empty());
3386 assert!(Arc::ptr_eq(
3387 extracted.entries.get(&program_id).unwrap(),
3388 &loaded
3389 ));
3390 }
3391
3392 #[test_matrix(
3393 (
3394 new_closed_entry,
3395 new_builtin_entry,
3396 new_failed_verification_entry,
3397 new_unloaded_entry,
3398 new_loaded_entry,
3399 ),
3400 (100, 101)
3401 )]
3402 fn test_extract_effective_slot(
3403 new_program: fn(ProgramRuntimeEnvironment) -> ProgramCacheEntryType,
3404 batch_slot: Slot,
3405 ) {
3406 let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
3420 let env = get_mock_program_runtime_environment();
3421 let program_id = Pubkey::new_unique();
3422 let entry = new_test_entry_with_owner(
3423 100,
3424 ProgramCacheEntryOwner::LoaderV3,
3425 new_program(env.clone()),
3426 );
3427 cache.assign_program(&env, program_id, 100, Arc::clone(&entry));
3428 cache.latest_root_slot = batch_slot;
3429
3430 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
3431 assert_eq!(slot_versions.len(), 1);
3432 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &entry));
3433
3434 let mut search_for = vec![ProgramToLoad {
3435 program_id: &program_id,
3436 loader: ProgramCacheEntryOwner::LoaderV3,
3437 deployment_slot: 100,
3438 }];
3439 let mut extracted = ProgramCacheForTxBatch::new(batch_slot);
3440 cache.extract(&mut search_for, &mut extracted, &env, true, true);
3441
3442 if batch_slot < entry.effective_slot() {
3443 assert!(search_for.is_empty());
3447 let tombstone = extracted.entries.get(&program_id).unwrap();
3448 assert_matches!(tombstone.program, ProgramCacheEntryType::DelayVisibility);
3449 assert_eq!(tombstone.account_owner, ProgramCacheEntryOwner::LoaderV3);
3450 assert_eq!(tombstone.deployment_slot, 100);
3451 assert!(Arc::ptr_eq(&tombstone.stats, &entry.stats)); assert_eq!(entry.stats.uses.load(Ordering::Relaxed), 1);
3453
3454 assert_eq!(entry.latest_access_slot.load(Ordering::Relaxed), batch_slot);
3458 assert_eq!(tombstone.latest_access_slot.load(Ordering::Relaxed), 0);
3459 } else if matches!(entry.program, ProgramCacheEntryType::Unloaded(_)) {
3460 assert!(extracted.entries.is_empty());
3464 assert_eq!(search_for.len(), 1);
3465 assert_eq!(entry.stats.uses.load(Ordering::Relaxed), 0);
3466 assert_eq!(entry.latest_access_slot.load(Ordering::Relaxed), 0);
3467 } else {
3468 assert!(search_for.is_empty());
3471 assert!(Arc::ptr_eq(
3472 extracted.entries.get(&program_id).unwrap(),
3473 &entry
3474 ));
3475 assert_eq!(entry.stats.uses.load(Ordering::Relaxed), 1);
3476 assert_eq!(entry.latest_access_slot.load(Ordering::Relaxed), batch_slot);
3477 }
3478 }
3479
3480 #[test]
3481 fn test_extract_closed_entry_matches_any_env() {
3482 let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
3492 let env = get_mock_program_runtime_environment();
3493 let other_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
3494 let program_id = Pubkey::new_unique();
3495 let closed = new_test_entry_with_owner(
3496 100,
3497 ProgramCacheEntryOwner::LoaderV3,
3498 new_closed_entry(env.clone()), );
3500 assert_eq!(closed.effective_slot(), closed.deployment_slot);
3501 cache.assign_program(&env, program_id, 100, Arc::clone(&closed));
3502
3503 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
3504 assert_eq!(slot_versions.len(), 1);
3505 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &closed));
3506
3507 let mut search_for = vec![ProgramToLoad {
3509 program_id: &program_id,
3510 loader: ProgramCacheEntryOwner::LoaderV3,
3511 deployment_slot: 100,
3512 }];
3513 let mut extracted = ProgramCacheForTxBatch::new(100);
3514 cache.extract(&mut search_for, &mut extracted, &env, true, true);
3515 assert!(Arc::ptr_eq(
3516 extracted.entries.get(&program_id).unwrap(),
3517 &closed
3518 ));
3519 assert!(search_for.is_empty());
3520
3521 let mut search_for = vec![ProgramToLoad {
3523 program_id: &program_id,
3524 loader: ProgramCacheEntryOwner::LoaderV3,
3525 deployment_slot: 100,
3526 }];
3527 let mut extracted = ProgramCacheForTxBatch::new(100);
3528 cache.extract(&mut search_for, &mut extracted, &other_env, true, true);
3529 assert!(Arc::ptr_eq(
3530 extracted.entries.get(&program_id).unwrap(),
3531 &closed
3532 ));
3533 assert!(search_for.is_empty());
3534 }
3535
3536 #[test]
3537 fn test_extract_delay_visibility_tombstone_interleaved_environments() {
3538 let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
3561 let env = get_mock_program_runtime_environment();
3562 let upcoming_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
3563 let program_id = Pubkey::new_unique();
3564 let on_execution_env = new_test_entry_with_owner(
3565 100,
3566 ProgramCacheEntryOwner::LoaderV3,
3567 new_loaded_entry(env.clone()),
3568 );
3569 let on_upcoming_env = new_test_entry_with_owner(
3570 100,
3571 ProgramCacheEntryOwner::LoaderV3,
3572 new_loaded_entry(upcoming_env.clone()),
3573 );
3574 cache.assign_program(&env, program_id, 100, Arc::clone(&on_execution_env));
3575 cache.assign_program(&upcoming_env, program_id, 100, Arc::clone(&on_upcoming_env));
3576
3577 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
3578 assert_eq!(slot_versions.len(), 2);
3579 assert!(Arc::ptr_eq(
3580 slot_versions.first().unwrap(),
3581 &on_execution_env
3582 ));
3583 assert!(Arc::ptr_eq(slot_versions.get(1).unwrap(), &on_upcoming_env));
3584
3585 let mut search_for = vec![ProgramToLoad {
3587 program_id: &program_id,
3588 loader: ProgramCacheEntryOwner::LoaderV3,
3589 deployment_slot: 100,
3590 }];
3591 let mut extracted = ProgramCacheForTxBatch::new(100);
3592 cache.extract(&mut search_for, &mut extracted, &env, true, true);
3593 assert!(search_for.is_empty());
3594
3595 let tombstone = extracted.entries.get(&program_id).unwrap();
3597 assert_matches!(tombstone.program, ProgramCacheEntryType::DelayVisibility);
3598
3599 assert!(Arc::ptr_eq(&tombstone.stats, &on_upcoming_env.stats));
3607 assert_eq!(on_upcoming_env.stats.uses.load(Ordering::Relaxed), 1);
3608 assert_eq!(on_execution_env.stats.uses.load(Ordering::Relaxed), 0);
3609
3610 let mut search_for = vec![ProgramToLoad {
3613 program_id: &program_id,
3614 loader: ProgramCacheEntryOwner::LoaderV3,
3615 deployment_slot: 100,
3616 }];
3617 let mut extracted = ProgramCacheForTxBatch::new(101);
3618 cache.extract(&mut search_for, &mut extracted, &env, true, true);
3619 assert!(search_for.is_empty());
3620 assert!(Arc::ptr_eq(
3621 extracted.entries.get(&program_id).unwrap(),
3622 &on_execution_env
3623 ));
3624
3625 assert_eq!(on_upcoming_env.stats.uses.load(Ordering::Relaxed), 1);
3627 assert_eq!(on_execution_env.stats.uses.load(Ordering::Relaxed), 1);
3628 }
3629
3630 #[test_case(false)]
3631 #[test_case(true)]
3632 fn test_extract_environment_filter_same_slot(other_env_first: bool) {
3633 let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
3634 let env = get_mock_program_runtime_environment();
3635 let other_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
3636 let program_id = Pubkey::new_unique();
3637
3638 let on_other_env = new_test_entry_with_owner(
3640 100,
3641 ProgramCacheEntryOwner::LoaderV3,
3642 new_loaded_entry(other_env.clone()),
3643 );
3644 let on_execution_env = new_test_entry_with_owner(
3645 100,
3646 ProgramCacheEntryOwner::LoaderV3,
3647 new_loaded_entry(env.clone()),
3648 );
3649 if other_env_first {
3650 cache.assign_program(&other_env, program_id, 100, Arc::clone(&on_other_env));
3651 cache.assign_program(&env, program_id, 100, Arc::clone(&on_execution_env));
3652 } else {
3653 cache.assign_program(&env, program_id, 100, Arc::clone(&on_execution_env));
3654 cache.assign_program(&other_env, program_id, 100, Arc::clone(&on_other_env));
3655 }
3656
3657 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
3660 assert_eq!(slot_versions.len(), 2);
3661 let (first, second) = if other_env_first {
3662 (&on_other_env, &on_execution_env)
3663 } else {
3664 (&on_execution_env, &on_other_env)
3665 };
3666 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), first));
3667 assert!(Arc::ptr_eq(slot_versions.get(1).unwrap(), second));
3668
3669 let mut search_for = vec![ProgramToLoad {
3672 program_id: &program_id,
3673 loader: ProgramCacheEntryOwner::LoaderV3,
3674 deployment_slot: 100,
3675 }];
3676 let mut extracted = ProgramCacheForTxBatch::new(200);
3677 cache.extract(&mut search_for, &mut extracted, &env, true, true);
3678 assert!(Arc::ptr_eq(
3679 extracted.entries.get(&program_id).unwrap(),
3680 &on_execution_env
3681 ));
3682
3683 let mut search_for = vec![ProgramToLoad {
3685 program_id: &program_id,
3686 loader: ProgramCacheEntryOwner::LoaderV3,
3687 deployment_slot: 100,
3688 }];
3689 let mut extracted = ProgramCacheForTxBatch::new(200);
3690 cache.extract(&mut search_for, &mut extracted, &other_env, true, true);
3691 assert!(Arc::ptr_eq(
3692 extracted.entries.get(&program_id).unwrap(),
3693 &on_other_env
3694 ));
3695 }
3696
3697 #[test_matrix((false, true))]
3698 fn test_extract_usage_counter(increment_usage_counter: bool) {
3699 let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
3700 let env = get_mock_program_runtime_environment();
3701 let program_id = Pubkey::new_unique();
3702 let entry = new_test_entry_with_owner(
3703 100,
3704 ProgramCacheEntryOwner::LoaderV3,
3705 new_loaded_entry(env.clone()),
3706 );
3707 cache.assign_program(&env, program_id, 100, Arc::clone(&entry));
3708 cache.latest_root_slot = 200;
3709
3710 let mut search_for = vec![ProgramToLoad {
3711 program_id: &program_id,
3712 loader: ProgramCacheEntryOwner::LoaderV3,
3713 deployment_slot: 100,
3714 }];
3715 let mut extracted = ProgramCacheForTxBatch::new(200);
3716 cache.extract(
3717 &mut search_for,
3718 &mut extracted,
3719 &env,
3720 increment_usage_counter,
3721 true,
3722 );
3723
3724 assert_eq!(entry.latest_access_slot.load(Ordering::Relaxed), 200);
3726 assert_eq!(
3727 entry.stats.uses.load(Ordering::Relaxed),
3728 u64::from(increment_usage_counter)
3729 );
3730 }
3731
3732 #[test]
3733 fn test_extract_usage_counter_delayed_visibility() {
3734 let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
3735 let env = get_mock_program_runtime_environment();
3736 let program_id = Pubkey::new_unique();
3737 let entry = new_test_entry_with_owner(
3738 100,
3739 ProgramCacheEntryOwner::LoaderV3,
3740 new_loaded_entry(env.clone()),
3741 );
3742 cache.assign_program(&env, program_id, 100, Arc::clone(&entry));
3743 cache.latest_root_slot = 100;
3744
3745 let mut search_for = vec![ProgramToLoad {
3749 program_id: &program_id,
3750 loader: ProgramCacheEntryOwner::LoaderV3,
3751 deployment_slot: 100,
3752 }];
3753 let mut extracted = ProgramCacheForTxBatch::new(100);
3754 cache.extract(&mut search_for, &mut extracted, &env, true, true);
3755
3756 let tombstone = extracted.entries.get(&program_id).unwrap();
3757 assert!(matches!(
3758 tombstone.program,
3759 ProgramCacheEntryType::DelayVisibility
3760 ));
3761 assert!(!Arc::ptr_eq(tombstone, &entry));
3762
3763 assert_eq!(entry.latest_access_slot.load(Ordering::Relaxed), 100);
3766 assert_eq!(tombstone.latest_access_slot.load(Ordering::Relaxed), 0);
3767
3768 assert!(Arc::ptr_eq(&tombstone.stats, &entry.stats));
3771 assert_eq!(entry.stats.uses.load(Ordering::Relaxed), 1);
3772 }
3773
3774 #[test_matrix((false, true))]
3775 fn test_extract_hits_and_misses(count_hits_and_misses: bool) {
3776 let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
3777 let env = get_mock_program_runtime_environment();
3778 let found = Pubkey::new_unique();
3779 let missing = Pubkey::new_unique();
3780 cache.assign_program(
3781 &env,
3782 found,
3783 100,
3784 new_test_entry_with_owner(
3785 100,
3786 ProgramCacheEntryOwner::LoaderV3,
3787 new_loaded_entry(env.clone()),
3788 ),
3789 );
3790
3791 let mut search_for = vec![
3792 ProgramToLoad {
3793 program_id: &found,
3794 loader: ProgramCacheEntryOwner::LoaderV3,
3795 deployment_slot: 100,
3796 },
3797 ProgramToLoad {
3798 program_id: &missing,
3799 loader: ProgramCacheEntryOwner::LoaderV3,
3800 deployment_slot: 0,
3801 },
3802 ];
3803 let mut extracted = ProgramCacheForTxBatch::new(200);
3804 cache.extract(
3805 &mut search_for,
3806 &mut extracted,
3807 &env,
3808 true,
3809 count_hits_and_misses,
3810 );
3811
3812 let expected = u64::from(count_hits_and_misses);
3813 assert_eq!(cache.stats.hits.load(Ordering::Relaxed), expected);
3814 assert_eq!(cache.stats.misses.load(Ordering::Relaxed), expected);
3815 }
3816
3817 #[test]
3818 fn test_extract_hits_count_only_this_call() {
3819 let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
3820 let env = get_mock_program_runtime_environment();
3821 let program_id = Pubkey::new_unique();
3822 cache.assign_program(
3823 &env,
3824 program_id,
3825 100,
3826 new_test_entry_with_owner(
3827 100,
3828 ProgramCacheEntryOwner::LoaderV3,
3829 new_loaded_entry(env.clone()),
3830 ),
3831 );
3832
3833 let mut extracted = ProgramCacheForTxBatch::new(200);
3836 extracted.replenish(Pubkey::new_unique(), new_test_builtin_entry(0));
3837
3838 let mut search_for = vec![ProgramToLoad {
3841 program_id: &program_id,
3842 loader: ProgramCacheEntryOwner::LoaderV3,
3843 deployment_slot: 100,
3844 }];
3845 cache.extract(&mut search_for, &mut extracted, &env, true, true);
3846 assert_eq!(extracted.entries.len(), 2);
3847 assert_eq!(cache.stats.hits.load(Ordering::Relaxed), 1);
3848 }
3849
3850 #[test]
3851 fn test_extract_cooperative_loading_task() {
3852 let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
3853 let env = get_mock_program_runtime_environment();
3854 let program_ids = [Pubkey::new_unique(), Pubkey::new_unique()];
3855
3856 let mut search_for = program_ids
3858 .iter()
3859 .map(|program_id| ProgramToLoad {
3860 program_id,
3861 loader: ProgramCacheEntryOwner::LoaderV3,
3862 deployment_slot: 0,
3863 })
3864 .collect();
3865 let mut extracted = ProgramCacheForTxBatch::new(100);
3866 let task = cache.extract(&mut search_for, &mut extracted, &env, true, true);
3867 assert_eq!(search_for.len(), 2);
3868 assert_eq!(task, program_ids.first().copied());
3869 match &cache.index {
3870 IndexImplementation::V1 {
3871 loading_entries, ..
3872 } => {
3873 let loading_entries = loading_entries.lock().unwrap();
3874 assert_eq!(loading_entries.len(), 1);
3875 assert_eq!(
3876 loading_entries.get(program_ids.first().unwrap()),
3877 Some(&(100, thread::current().id()))
3878 );
3879 }
3880 }
3881
3882 let mut search_for = vec![ProgramToLoad {
3884 program_id: program_ids.first().unwrap(),
3885 loader: ProgramCacheEntryOwner::LoaderV3,
3886 deployment_slot: 0,
3887 }];
3888 let task = cache.extract(&mut search_for, &mut extracted, &env, true, true);
3889 assert_eq!(search_for.len(), 1);
3890 assert_eq!(task, None);
3891
3892 let cookie = cache.loading_task_waiter.cookie();
3894 let loaded = new_test_entry_with_owner(
3895 50,
3896 ProgramCacheEntryOwner::LoaderV3,
3897 new_loaded_entry(env.clone()),
3898 );
3899 cache.finish_cooperative_loading_task(
3900 &env,
3901 100,
3902 *program_ids.first().unwrap(),
3903 Arc::clone(&loaded),
3904 );
3905 assert_ne!(cache.loading_task_waiter.wait(cookie), cookie);
3906
3907 match &cache.index {
3909 IndexImplementation::V1 {
3910 loading_entries, ..
3911 } => assert!(loading_entries.lock().unwrap().is_empty()),
3912 }
3913 let mut search_for = vec![ProgramToLoad {
3914 program_id: program_ids.first().unwrap(),
3915 loader: ProgramCacheEntryOwner::LoaderV3,
3916 deployment_slot: 50,
3917 }];
3918 let mut extracted = ProgramCacheForTxBatch::new(100);
3919 let task = cache.extract(&mut search_for, &mut extracted, &env, true, true);
3920 assert!(search_for.is_empty());
3921 assert_eq!(task, None);
3922 assert!(Arc::ptr_eq(
3923 extracted.entries.get(program_ids.first().unwrap()).unwrap(),
3924 &loaded
3925 ));
3926 }
3927
3928 #[test]
3929 fn test_extract_cooperative_loading_task_ordering() {
3930 let (cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
3931 let env = get_mock_program_runtime_environment();
3932 let program_ids = [Pubkey::new_unique(), Pubkey::new_unique()];
3933 let mut extracted = ProgramCacheForTxBatch::new(100);
3934
3935 let mut search_for = program_ids
3937 .iter()
3938 .map(|program_id| ProgramToLoad {
3939 program_id,
3940 loader: ProgramCacheEntryOwner::LoaderV3,
3941 deployment_slot: 0,
3942 })
3943 .collect();
3944 let task = cache.extract(&mut search_for, &mut extracted, &env, true, true);
3945 assert_eq!(task, program_ids.first().copied());
3946
3947 let mut search_for = program_ids
3950 .iter()
3951 .rev()
3952 .map(|program_id| ProgramToLoad {
3953 program_id,
3954 loader: ProgramCacheEntryOwner::LoaderV3,
3955 deployment_slot: 0,
3956 })
3957 .collect();
3958 let task = cache.extract(&mut search_for, &mut extracted, &env, true, true);
3959 assert_eq!(task, program_ids.get(1).copied());
3960
3961 match &cache.index {
3963 IndexImplementation::V1 {
3964 loading_entries, ..
3965 } => {
3966 let loading_entries = loading_entries.lock().unwrap();
3967 assert_eq!(loading_entries.len(), 2);
3968 for program_id in &program_ids {
3969 assert_eq!(
3970 loading_entries.get(program_id),
3971 Some(&(100, thread::current().id()))
3972 );
3973 }
3974 }
3975 }
3976
3977 let mut search_for = program_ids
3979 .iter()
3980 .map(|program_id| ProgramToLoad {
3981 program_id,
3982 loader: ProgramCacheEntryOwner::LoaderV3,
3983 deployment_slot: 0,
3984 })
3985 .collect();
3986 let task = cache.extract(&mut search_for, &mut extracted, &env, true, true);
3987 assert_eq!(search_for.len(), 2);
3988 assert_eq!(task, None);
3989 }
3990
3991 #[test]
3992 fn test_extract_entry_not_in_same_branch() {
3993 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
4000 let mut fork_graph = TestForkGraphSpecific::default();
4001 fork_graph.insert_fork(&[0, 50, 200]);
4002 fork_graph.insert_fork(&[0, 100]);
4003 let fork_graph = Arc::new(RwLock::new(fork_graph));
4004 cache.set_fork_graph(Arc::downgrade(&fork_graph));
4005
4006 let env = get_mock_program_runtime_environment();
4007 let program_id = Pubkey::new_unique();
4008 let on_other_fork = new_test_entry_with_owner(
4009 100,
4010 ProgramCacheEntryOwner::LoaderV3,
4011 new_loaded_entry(env.clone()),
4012 );
4013 cache.assign_program(&env, program_id, 100, Arc::clone(&on_other_fork));
4014
4015 let mut search_for = vec![ProgramToLoad {
4025 program_id: &program_id,
4026 loader: ProgramCacheEntryOwner::LoaderV3,
4027 deployment_slot: 100,
4028 }];
4029 let mut extracted = ProgramCacheForTxBatch::new(200);
4030 cache.extract(&mut search_for, &mut extracted, &env, true, true);
4031 assert_eq!(search_for.len(), 1);
4032 assert!(extracted.entries.is_empty());
4033
4034 let on_same_fork = new_test_entry_with_owner(
4036 50,
4037 ProgramCacheEntryOwner::LoaderV3,
4038 new_loaded_entry(env.clone()),
4039 );
4040 cache.assign_program(&env, program_id, 50, Arc::clone(&on_same_fork));
4041
4042 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
4044 assert_eq!(slot_versions.len(), 2);
4045 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &on_same_fork));
4046 assert!(Arc::ptr_eq(slot_versions.get(1).unwrap(), &on_other_fork));
4047
4048 let mut search_for = vec![ProgramToLoad {
4051 program_id: &program_id,
4052 loader: ProgramCacheEntryOwner::LoaderV3,
4053 deployment_slot: 50,
4054 }];
4055 let mut extracted = ProgramCacheForTxBatch::new(200);
4056 cache.extract(&mut search_for, &mut extracted, &env, true, true);
4057 assert!(search_for.is_empty());
4058 assert!(Arc::ptr_eq(
4059 extracted.entries.get(&program_id).unwrap(),
4060 &on_same_fork
4061 ));
4062 }
4063
4064 #[test]
4065 fn test_extract_deployment_slot_mismatch() {
4066 let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
4082 assert_eq!(cache.latest_root_slot, 0);
4083 let env = get_mock_program_runtime_environment();
4084 let program_id = Pubkey::new_unique();
4085 let deployed_at_100 = new_test_entry_with_owner(
4086 100,
4087 ProgramCacheEntryOwner::LoaderV3,
4088 new_loaded_entry(env.clone()),
4089 );
4090 cache.assign_program(&env, program_id, 100, Arc::clone(&deployed_at_100));
4091
4092 let mut search_for = vec![ProgramToLoad {
4096 program_id: &program_id,
4097 loader: ProgramCacheEntryOwner::LoaderV3,
4098 deployment_slot: 150,
4099 }];
4100 let mut extracted = ProgramCacheForTxBatch::new(200);
4101 cache.extract(&mut search_for, &mut extracted, &env, true, true);
4102 assert_eq!(search_for.len(), 1);
4103 assert!(extracted.entries.is_empty());
4104
4105 let deployed_at_150 = new_test_entry_with_owner(
4107 150,
4108 ProgramCacheEntryOwner::LoaderV3,
4109 new_loaded_entry(env.clone()),
4110 );
4111 cache.assign_program(&env, program_id, 150, Arc::clone(&deployed_at_150));
4112
4113 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
4116 assert_eq!(slot_versions.len(), 2);
4117 assert!(Arc::ptr_eq(
4118 slot_versions.first().unwrap(),
4119 &deployed_at_100
4120 ));
4121 assert!(Arc::ptr_eq(slot_versions.get(1).unwrap(), &deployed_at_150));
4122
4123 let mut search_for = vec![ProgramToLoad {
4125 program_id: &program_id,
4126 loader: ProgramCacheEntryOwner::LoaderV3,
4127 deployment_slot: 150,
4128 }];
4129 let mut extracted = ProgramCacheForTxBatch::new(200);
4130 cache.extract(&mut search_for, &mut extracted, &env, true, true);
4131 assert!(search_for.is_empty());
4132 assert!(Arc::ptr_eq(
4133 extracted.entries.get(&program_id).unwrap(),
4134 &deployed_at_150
4135 ));
4136 }
4137
4138 #[test]
4139 fn test_extract_older_entry_on_the_callers_fork() {
4140 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
4147 let mut fork_graph = TestForkGraphSpecific::default();
4148 fork_graph.insert_fork(&[0, 50, 200]);
4149 fork_graph.insert_fork(&[0, 150]);
4150 let fork_graph = Arc::new(RwLock::new(fork_graph));
4151 cache.set_fork_graph(Arc::downgrade(&fork_graph));
4152
4153 let env = get_mock_program_runtime_environment();
4154 let program_id = Pubkey::new_unique();
4155 let on_other_fork = new_test_entry_with_owner(
4156 150,
4157 ProgramCacheEntryOwner::LoaderV3,
4158 new_loaded_entry(env.clone()),
4159 );
4160 let on_same_fork = new_test_entry_with_owner(
4161 50,
4162 ProgramCacheEntryOwner::LoaderV3,
4163 new_loaded_entry(env.clone()),
4164 );
4165 cache.assign_program(&env, program_id, 150, Arc::clone(&on_other_fork));
4166 cache.assign_program(&env, program_id, 50, Arc::clone(&on_same_fork));
4167
4168 let mut search_for = vec![ProgramToLoad {
4172 program_id: &program_id,
4173 loader: ProgramCacheEntryOwner::LoaderV3,
4174 deployment_slot: 50,
4175 }];
4176 let mut extracted = ProgramCacheForTxBatch::new(200);
4177 cache.extract(&mut search_for, &mut extracted, &env, true, true);
4178 assert!(Arc::ptr_eq(
4179 extracted.entries.get(&program_id).unwrap(),
4180 &on_same_fork
4181 ));
4182
4183 let mut search_for = vec![ProgramToLoad {
4195 program_id: &program_id,
4196 loader: ProgramCacheEntryOwner::LoaderV3,
4197 deployment_slot: 150,
4198 }];
4199 let mut extracted = ProgramCacheForTxBatch::new(200);
4200 cache.extract(&mut search_for, &mut extracted, &env, true, true);
4201 assert_eq!(search_for.len(), 1);
4202 assert!(extracted.entries.is_empty());
4203 }
4204
4205 #[test]
4206 fn test_extract_below_deployment_slot() {
4207 let (mut cache, fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
4208 let env = get_mock_program_runtime_environment();
4209 let program_id = Pubkey::new_unique();
4210 let entry = new_test_entry_with_owner(
4211 100,
4212 ProgramCacheEntryOwner::LoaderV3,
4213 new_loaded_entry(env.clone()),
4214 );
4215 cache.assign_program(&env, program_id, 100, Arc::clone(&entry));
4216
4217 cache.prune(200, None, &fork_graph.read().unwrap());
4220 assert_eq!(cache.latest_root_slot, 200);
4221
4222 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
4224 assert_eq!(slot_versions.len(), 1);
4225 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &entry));
4226
4227 fork_graph.write().unwrap().relation = BlockRelation::Unknown;
4231
4232 let mut search_for = vec![ProgramToLoad {
4235 program_id: &program_id,
4236 loader: ProgramCacheEntryOwner::LoaderV3,
4237 deployment_slot: 0,
4238 }];
4239 let mut extracted = ProgramCacheForTxBatch::new(50);
4240 cache.extract(&mut search_for, &mut extracted, &env, true, true);
4241 assert_eq!(search_for.len(), 1);
4242 assert!(extracted.entries.is_empty());
4243 }
4244
4245 #[test]
4246 fn test_extract_entry_older_than_root() {
4247 let (mut cache, fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
4250 let env = get_mock_program_runtime_environment();
4251 let program_id = Pubkey::new_unique();
4252 let entry = new_test_entry_with_owner(
4253 100,
4254 ProgramCacheEntryOwner::LoaderV3,
4255 new_loaded_entry(env.clone()),
4256 );
4257 cache.assign_program(&env, program_id, 100, Arc::clone(&entry));
4258
4259 cache.prune(200, None, &fork_graph.read().unwrap());
4262 assert_eq!(cache.latest_root_slot, 200);
4263
4264 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
4266 assert_eq!(slot_versions.len(), 1);
4267 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &entry));
4268
4269 fork_graph.write().unwrap().relation = BlockRelation::Unknown;
4273
4274 let mut search_for = vec![ProgramToLoad {
4279 program_id: &program_id,
4280 loader: ProgramCacheEntryOwner::LoaderV3,
4281 deployment_slot: 100,
4282 }];
4283 let mut extracted = ProgramCacheForTxBatch::new(300);
4284 cache.extract(&mut search_for, &mut extracted, &env, true, true);
4285 assert!(search_for.is_empty());
4286 assert!(Arc::ptr_eq(
4287 extracted.entries.get(&program_id).unwrap(),
4288 &entry
4289 ));
4290 }
4291
4292 #[test]
4293 fn test_extract_access_slot_clamped_to_root() {
4294 let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
4295 let env = get_mock_program_runtime_environment();
4296 let program_id = Pubkey::new_unique();
4297 let entry = new_test_entry_with_owner(
4298 100,
4299 ProgramCacheEntryOwner::LoaderV3,
4300 new_loaded_entry(env.clone()),
4301 );
4302 cache.assign_program(&env, program_id, 100, Arc::clone(&entry));
4303
4304 let batch_slot = 200;
4305 for root_slot in [0, 150, batch_slot] {
4306 cache.latest_root_slot = root_slot;
4307
4308 let mut search_for = vec![ProgramToLoad {
4309 program_id: &program_id,
4310 loader: ProgramCacheEntryOwner::LoaderV3,
4311 deployment_slot: 100,
4312 }];
4313 let mut extracted = ProgramCacheForTxBatch::new(batch_slot);
4314 cache.extract(&mut search_for, &mut extracted, &env, true, true);
4315
4316 assert!(search_for.is_empty());
4317 assert_eq!(entry.latest_access_slot.load(Ordering::Relaxed), root_slot);
4318 }
4319 }
4320
4321 #[test]
4322 fn test_unloaded() {
4323 let mut cache = ProgramCache::<TestForkGraph>::new(0);
4324 let env = get_mock_program_runtime_environment();
4325 for program_cache_entry_type in [
4326 ProgramCacheEntryType::Closed,
4327 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
4328 ] {
4329 let entry = Arc::new(ProgramCacheEntry {
4330 program: program_cache_entry_type,
4331 account_owner: ProgramCacheEntryOwner::LoaderV2,
4332 deployment_slot: 0,
4333 stats: Arc::default(),
4334 latest_access_slot: AtomicU64::default(),
4335 });
4336 assert!(entry.to_unloaded().is_none());
4337
4338 let program_id = Pubkey::new_unique();
4340 cache.assign_program(&env, program_id, entry.deployment_slot, entry.clone());
4341 cache.unload_program_entry(program_id, &entry);
4342 assert_eq!(cache.get_slot_versions_for_tests(&program_id).len(), 1);
4343 assert!(cache.stats.evictions.is_empty());
4344 }
4345
4346 let stats = ProgramStatistics {
4347 uses: 3.into(),
4348 ..Default::default()
4349 };
4350 let entry = new_test_entry_with_usage(1, stats);
4351 let unloaded_entry = entry.to_unloaded().unwrap();
4352 assert_eq!(unloaded_entry.deployment_slot, 1);
4353 assert_eq!(unloaded_entry.effective_slot(), 2);
4354 assert_eq!(unloaded_entry.latest_access_slot.load(Ordering::Relaxed), 1);
4355 assert_eq!(unloaded_entry.stats.uses.load(Ordering::Relaxed), 3);
4356
4357 let program_id = Pubkey::new_unique();
4359 cache.assign_program(&env, program_id, entry.deployment_slot, entry.clone());
4360 cache.unload_program_entry(program_id, &entry);
4361 assert!(cache.stats.evictions.contains_key(&program_id));
4362 }
4363
4364 #[test]
4365 fn test_fork_prune_find_first_ancestor() {
4366 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
4367 let env = get_mock_program_runtime_environment();
4368
4369 let mut fork_graph = TestForkGraphSpecific::default();
4380 fork_graph.insert_fork(&[0, 10, 20]);
4381 fork_graph.insert_fork(&[0, 5]);
4382 let fork_graph = Arc::new(RwLock::new(fork_graph));
4383 cache.set_fork_graph(Arc::downgrade(&fork_graph));
4384
4385 let program1 = Pubkey::new_unique();
4386 cache.assign_program(&env, program1, 0, new_test_entry(0));
4387 cache.assign_program(&env, program1, 5, new_test_entry(5));
4388
4389 cache.prune(10, None, &fork_graph.read().unwrap());
4390
4391 let keys = &[program1];
4392 let mut missing = get_entries_to_load(&cache, 20, keys);
4393 let mut extracted = ProgramCacheForTxBatch::new(20);
4394 cache.extract(&mut missing, &mut extracted, &env, true, true);
4395
4396 assert_eq!(
4398 extracted
4399 .find(&program1)
4400 .expect("Did not find the program")
4401 .deployment_slot,
4402 0
4403 );
4404 }
4405
4406 #[test]
4407 fn test_prune_by_deployment_slot() {
4408 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
4409 let env = get_mock_program_runtime_environment();
4410
4411 let mut fork_graph = TestForkGraphSpecific::default();
4422 fork_graph.insert_fork(&[0, 10, 20]);
4423 fork_graph.insert_fork(&[0, 5, 6]);
4424 let fork_graph = Arc::new(RwLock::new(fork_graph));
4425 cache.set_fork_graph(Arc::downgrade(&fork_graph));
4426
4427 let program1 = Pubkey::new_unique();
4428 cache.assign_program(&env, program1, 0, new_test_entry(0));
4429 cache.assign_program(&env, program1, 5, new_test_entry(5));
4430
4431 let program2 = Pubkey::new_unique();
4432 cache.assign_program(&env, program2, 10, new_test_entry(10));
4433
4434 let keys = &[program1, program2];
4435 let mut missing = get_entries_to_load(&cache, 20, keys);
4436 let mut extracted = ProgramCacheForTxBatch::new(20);
4437 cache.extract(&mut missing, &mut extracted, &env, true, true);
4438 assert!(match_slot(&extracted, &program1, 0, 20));
4439 assert!(match_slot(&extracted, &program2, 10, 20));
4440
4441 let mut missing = get_entries_to_load(&cache, 6, keys);
4442 assert!(match_missing(&missing, &program2, false));
4443 let mut extracted = ProgramCacheForTxBatch::new(6);
4444 cache.extract(&mut missing, &mut extracted, &env, true, true);
4445 assert!(match_slot(&extracted, &program1, 5, 6));
4446
4447 cache.prune_by_deployment_slot(5);
4450
4451 let mut missing = get_entries_to_load(&cache, 20, keys);
4452 let mut extracted = ProgramCacheForTxBatch::new(20);
4453 cache.extract(&mut missing, &mut extracted, &env, true, true);
4454 assert!(match_slot(&extracted, &program1, 0, 20));
4455 assert!(match_slot(&extracted, &program2, 10, 20));
4456
4457 let mut missing = get_entries_to_load(&cache, 6, keys);
4458 assert!(match_missing(&missing, &program2, false));
4459 let mut extracted = ProgramCacheForTxBatch::new(6);
4460 cache.extract(&mut missing, &mut extracted, &env, true, true);
4461 assert!(match_slot(&extracted, &program1, 0, 6));
4462
4463 cache.prune_by_deployment_slot(10);
4466
4467 let mut missing = get_entries_to_load(&cache, 20, keys);
4468 assert!(match_missing(&missing, &program2, false));
4469 let mut extracted = ProgramCacheForTxBatch::new(20);
4470 cache.extract(&mut missing, &mut extracted, &env, true, true);
4471 assert!(match_slot(&extracted, &program1, 0, 20));
4472 }
4473}