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