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(feature = "frozen-abi")]
956impl solana_frozen_abi::abi_example::AbiExample for ProgramCacheEntry {
957 fn example() -> Self {
958 Self::default()
960 }
961}
962
963#[cfg(feature = "frozen-abi")]
964impl<FG: ForkGraph> solana_frozen_abi::abi_example::AbiExample for ProgramCache<FG> {
965 fn example() -> Self {
966 Self::new(Slot::default())
968 }
969}
970
971#[cfg(test)]
972pub(crate) mod tests {
973 use {
974 crate::{
975 loaded_programs::{
976 BlockRelation, ForkGraph, IndexImplementation, MAX_TOMBSTONE_AGE_IN_SLOTS, Percent,
977 ProgramCache, ProgramCacheForTxBatch, ProgramRuntimeEnvironment, ProgramToLoad,
978 get_mock_program_runtime_environment,
979 },
980 program_cache_entry::{
981 ProgramCacheEntry, ProgramCacheEntryOwner, ProgramCacheEntryType,
982 },
983 program_metrics::ProgramStatistics,
984 },
985 assert_matches::assert_matches,
986 solana_clock::Slot,
987 solana_pubkey::Pubkey,
988 solana_sbpf::{elf::Executable, program::BuiltinProgram},
989 solana_svm_type_overrides::{
990 sync::{
991 Arc, RwLock,
992 atomic::{AtomicU64, Ordering},
993 },
994 thread,
995 },
996 std::{fs::File, io::Read, ops::ControlFlow},
997 test_case::{test_case, test_matrix},
998 };
999
1000 fn new_test_entry(deployment_slot: Slot) -> Arc<ProgramCacheEntry> {
1001 new_test_entry_with_usage(deployment_slot, ProgramStatistics::default())
1002 }
1003
1004 fn new_closed_entry(_env: ProgramRuntimeEnvironment) -> ProgramCacheEntryType {
1005 ProgramCacheEntryType::Closed
1006 }
1007
1008 fn new_builtin_entry(_env: ProgramRuntimeEnvironment) -> ProgramCacheEntryType {
1009 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock())
1010 }
1011
1012 fn new_failed_verification_entry(env: ProgramRuntimeEnvironment) -> ProgramCacheEntryType {
1013 ProgramCacheEntryType::FailedVerification(env)
1014 }
1015
1016 fn new_unloaded_entry(env: ProgramRuntimeEnvironment) -> ProgramCacheEntryType {
1017 ProgramCacheEntryType::Unloaded(env)
1018 }
1019
1020 fn new_loaded_entry(env: ProgramRuntimeEnvironment) -> ProgramCacheEntryType {
1021 let mut elf = Vec::new();
1022 File::open("../programs/bpf_loader/test_elfs/out/noop_aligned.so")
1023 .unwrap()
1024 .read_to_end(&mut elf)
1025 .unwrap();
1026 let executable = Executable::load(&elf, Arc::clone(&*env)).unwrap();
1027 ProgramCacheEntryType::Loaded(executable)
1028 }
1029
1030 fn new_test_entry_with_owner(
1031 deployment_slot: Slot,
1032 account_owner: ProgramCacheEntryOwner,
1033 program: ProgramCacheEntryType,
1034 ) -> Arc<ProgramCacheEntry> {
1035 Arc::new(ProgramCacheEntry {
1036 program,
1037 account_owner,
1038 deployment_slot,
1039 stats: Arc::default(),
1040 latest_access_slot: AtomicU64::default(),
1041 })
1042 }
1043
1044 fn new_test_cache_with_fork_graph(
1045 relation: BlockRelation,
1046 ) -> (ProgramCache<TestForkGraph>, Arc<RwLock<TestForkGraph>>) {
1047 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1048 let fork_graph = Arc::new(RwLock::new(TestForkGraph { relation }));
1049 cache.set_fork_graph(Arc::downgrade(&fork_graph));
1050 (cache, fork_graph)
1051 }
1052
1053 pub(crate) fn new_test_entry_with_usage(
1054 deployment_slot: Slot,
1055 stats: ProgramStatistics,
1056 ) -> Arc<ProgramCacheEntry> {
1057 Arc::new(ProgramCacheEntry {
1058 program: new_loaded_entry(get_mock_program_runtime_environment()),
1059 account_owner: ProgramCacheEntryOwner::LoaderV2,
1060 deployment_slot,
1061 stats: Arc::new(stats),
1062 latest_access_slot: AtomicU64::new(deployment_slot),
1063 })
1064 }
1065
1066 fn new_test_builtin_entry(deployment_slot: Slot) -> Arc<ProgramCacheEntry> {
1067 Arc::new(ProgramCacheEntry {
1068 program: ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1069 account_owner: ProgramCacheEntryOwner::NativeLoader,
1070 deployment_slot,
1071 stats: Arc::default(),
1072 latest_access_slot: AtomicU64::default(),
1073 })
1074 }
1075
1076 fn set_failed_verification_tombstone<FG: ForkGraph>(
1077 cache: &mut ProgramCache<FG>,
1078 key: Pubkey,
1079 current_slot: Slot,
1080 env: ProgramRuntimeEnvironment,
1081 ) -> Arc<ProgramCacheEntry> {
1082 let program = Arc::new(ProgramCacheEntry::new_failed_verification_tombstone(
1083 current_slot,
1084 ProgramCacheEntryOwner::LoaderV2,
1085 ProgramRuntimeEnvironment::clone(&env),
1086 ));
1087 cache.assign_program(&env, key, current_slot, program.clone());
1088 program
1089 }
1090
1091 fn insert_unloaded_entry<FG: ForkGraph>(
1092 cache: &mut ProgramCache<FG>,
1093 key: Pubkey,
1094 current_slot: Slot,
1095 ) -> Arc<ProgramCacheEntry> {
1096 let env = get_mock_program_runtime_environment();
1097 let loaded = new_test_entry_with_usage(current_slot, ProgramStatistics::default());
1098 let unloaded = Arc::new(loaded.to_unloaded().expect("Failed to unload the program"));
1099 cache.assign_program(&env, key, current_slot, unloaded.clone());
1100 unloaded
1101 }
1102
1103 fn num_matching_entries<P, FG>(cache: &ProgramCache<FG>, predicate: P) -> usize
1104 where
1105 P: Fn(&ProgramCacheEntryType) -> bool,
1106 FG: ForkGraph,
1107 {
1108 cache
1109 .get_flattened_entries_for_tests()
1110 .iter()
1111 .filter(|(_key, program)| predicate(&program.program))
1112 .count()
1113 }
1114
1115 #[expect(clippy::arithmetic_side_effects)]
1116 fn program_deploy_test_helper(
1117 cache: &mut ProgramCache<TestForkGraph>,
1118 program: Pubkey,
1119 deployment_slots: Vec<Slot>,
1120 usage_counters: Vec<u64>,
1121 programs: &mut Vec<(Pubkey, Slot, u64)>,
1122 ) {
1123 let env = get_mock_program_runtime_environment();
1124 deployment_slots
1126 .iter()
1127 .enumerate()
1128 .for_each(|(i, deployment_slot)| {
1129 let usage_counter = *usage_counters.get(i).unwrap_or(&0);
1130 let stats = ProgramStatistics {
1131 uses: usage_counter.into(),
1132 ..Default::default()
1133 };
1134 cache.assign_program(
1135 &env,
1136 program,
1137 *deployment_slot,
1138 new_test_entry_with_usage(*deployment_slot, stats),
1139 );
1140 programs.push((program, *deployment_slot, usage_counter));
1141 });
1142
1143 let next_slot = deployment_slots.iter().max().map_or(0, |slot| slot + 1);
1144
1145 let env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
1147 for slot in next_slot..next_slot + 10 {
1148 set_failed_verification_tombstone(
1149 cache,
1150 program,
1151 slot,
1152 ProgramRuntimeEnvironment::clone(&env),
1153 );
1154 }
1155
1156 for slot in next_slot + 10..next_slot + 20 {
1158 insert_unloaded_entry(cache, program, slot);
1159 }
1160 }
1161
1162 #[test]
1163 fn test_random_eviction() {
1164 let mut programs = vec![];
1165 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1166
1167 program_deploy_test_helper(
1173 &mut cache,
1174 Pubkey::new_unique(),
1175 vec![0, 10, 20, 30, 40],
1176 vec![4, 5, 25, 35, 12],
1177 &mut programs,
1178 );
1179
1180 program_deploy_test_helper(
1182 &mut cache,
1183 Pubkey::new_unique(),
1184 vec![5, 11, 21, 24],
1185 vec![0, 2, 30, 45],
1186 &mut programs,
1187 );
1188
1189 program_deploy_test_helper(
1191 &mut cache,
1192 Pubkey::new_unique(),
1193 vec![0, 5, 15, 25],
1194 vec![100, 3, 20, 40],
1195 &mut programs,
1196 );
1197
1198 let num_loaded_expected = 13;
1200 let num_unloaded_expected = 30;
1202 let num_tombstones_expected = 30;
1204
1205 programs.sort_by_key(|(_id, _slot, usage_count)| *usage_count);
1207 let num_loaded = num_matching_entries(&cache, |program_type| {
1208 matches!(program_type, ProgramCacheEntryType::Loaded(_))
1209 });
1210 let num_unloaded = num_matching_entries(&cache, |program_type| {
1211 matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1212 });
1213 let num_tombstones = num_matching_entries(&cache, |program_type| {
1214 matches!(
1215 program_type,
1216 ProgramCacheEntryType::DelayVisibility
1217 | ProgramCacheEntryType::FailedVerification(_)
1218 | ProgramCacheEntryType::Closed
1219 )
1220 });
1221
1222 assert_eq!(num_loaded, num_loaded_expected);
1224 assert_eq!(num_unloaded, num_unloaded_expected);
1225 assert_eq!(num_tombstones, num_tombstones_expected);
1226
1227 let eviction_pct: Percent = 1;
1229
1230 let num_loaded_expected = crate::loaded_programs::percent_of_max_entries(eviction_pct);
1231 let num_unloaded_expected = num_unloaded_expected + num_loaded - num_loaded_expected;
1232 cache.evict_using_random_selection(eviction_pct, 21);
1233
1234 let num_loaded = num_matching_entries(&cache, |program_type| {
1236 matches!(program_type, ProgramCacheEntryType::Loaded(_))
1237 });
1238 let num_unloaded = num_matching_entries(&cache, |program_type| {
1239 matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1240 });
1241 let num_tombstones = num_matching_entries(&cache, |program_type| {
1242 matches!(program_type, ProgramCacheEntryType::FailedVerification(_))
1243 });
1244
1245 assert_eq!(num_loaded, num_loaded_expected);
1247 assert_eq!(num_unloaded, num_unloaded_expected);
1249 assert_eq!(num_tombstones, num_tombstones_expected);
1251 }
1252
1253 #[test]
1254 fn test_eviction() {
1255 let mut programs = vec![];
1256 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1257
1258 program_deploy_test_helper(
1260 &mut cache,
1261 Pubkey::new_unique(),
1262 vec![0, 10, 20, 30, 40],
1263 vec![4, 5, 25, 35, 12],
1264 &mut programs,
1265 );
1266
1267 program_deploy_test_helper(
1269 &mut cache,
1270 Pubkey::new_unique(),
1271 vec![5, 11, 21, 24],
1272 vec![0, 2, 30, 45],
1273 &mut programs,
1274 );
1275
1276 program_deploy_test_helper(
1278 &mut cache,
1279 Pubkey::new_unique(),
1280 vec![0, 5, 15, 25],
1281 vec![100, 3, 20, 40],
1282 &mut programs,
1283 );
1284
1285 let num_loaded_expected = 13;
1287 let num_unloaded_expected = 30;
1289 let num_tombstones_expected = 30;
1291
1292 programs.sort_by_key(|(_id, _slot, usage_count)| *usage_count);
1294 let num_loaded = num_matching_entries(&cache, |program_type| {
1295 matches!(program_type, ProgramCacheEntryType::Loaded(_))
1296 });
1297 let num_unloaded = num_matching_entries(&cache, |program_type| {
1298 matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1299 });
1300 let num_tombstones = num_matching_entries(&cache, |program_type| {
1301 matches!(program_type, ProgramCacheEntryType::FailedVerification(_))
1302 });
1303
1304 assert_eq!(num_loaded, num_loaded_expected);
1306 assert_eq!(num_unloaded, num_unloaded_expected);
1307 assert_eq!(num_tombstones, num_tombstones_expected);
1308
1309 let eviction_pct: Percent = 1;
1311
1312 let num_loaded_expected = crate::loaded_programs::percent_of_max_entries(eviction_pct);
1313 let num_unloaded_expected = num_unloaded_expected + num_loaded - num_loaded_expected;
1314
1315 cache.sort_and_unload(eviction_pct);
1316
1317 let entries = cache.get_flattened_entries_for_tests();
1319 programs.iter().for_each(|entry| {
1320 assert!(entries.iter().any(|(key, _entry)| key == &entry.0));
1321 });
1322
1323 let unloaded = entries
1324 .iter()
1325 .filter_map(|(key, program)| {
1326 matches!(program.program, ProgramCacheEntryType::Unloaded(_))
1327 .then_some((*key, program.stats.uses.load(Ordering::Relaxed)))
1328 })
1329 .collect::<Vec<(Pubkey, u64)>>();
1330
1331 for index in 0..3 {
1332 let expected = programs.get(index).expect("Missing program");
1333 assert!(unloaded.contains(&(expected.0, expected.2)));
1334 }
1335
1336 let num_loaded = num_matching_entries(&cache, |program_type| {
1338 matches!(program_type, ProgramCacheEntryType::Loaded(_))
1339 });
1340 let num_unloaded = num_matching_entries(&cache, |program_type| {
1341 matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1342 });
1343 let num_tombstones = num_matching_entries(&cache, |program_type| {
1344 matches!(
1345 program_type,
1346 ProgramCacheEntryType::DelayVisibility
1347 | ProgramCacheEntryType::FailedVerification(_)
1348 | ProgramCacheEntryType::Closed
1349 )
1350 });
1351
1352 assert_eq!(num_loaded, num_loaded_expected);
1354 assert_eq!(num_unloaded, num_unloaded_expected);
1356 assert_eq!(num_tombstones, num_tombstones_expected);
1358 }
1359
1360 #[test]
1361 fn test_usage_count_of_unloaded_program() {
1362 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1363 let env = get_mock_program_runtime_environment();
1364
1365 let program = Pubkey::new_unique();
1366 let evict_to_pct: Percent = 2;
1367 let cache_capacity_after_shrink =
1368 crate::loaded_programs::percent_of_max_entries(evict_to_pct);
1369 let num_total_programs = (cache_capacity_after_shrink + 1) as u64;
1371 (0..num_total_programs).for_each(|i| {
1372 let stats = ProgramStatistics {
1373 uses: (i + 10).into(),
1374 ..Default::default()
1375 };
1376 let entry = new_test_entry_with_usage(i, stats);
1377 cache.assign_program(&env, program, i, entry);
1378 });
1379
1380 cache.sort_and_unload(evict_to_pct);
1381
1382 let num_unloaded = num_matching_entries(&cache, |program_type| {
1383 matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1384 });
1385 assert_eq!(num_unloaded, 1);
1386
1387 cache
1388 .get_flattened_entries_for_tests()
1389 .iter()
1390 .for_each(|(_key, program)| {
1391 if matches!(program.program, ProgramCacheEntryType::Unloaded(_)) {
1392 assert_eq!(program.stats.uses.load(Ordering::Relaxed), 10);
1394 assert_eq!(program.deployment_slot, 0);
1395 assert_eq!(program.effective_slot(), 1);
1396 }
1397 });
1398
1399 cache.assign_program(
1402 &env,
1403 program,
1404 0,
1405 new_test_entry_with_usage(0, ProgramStatistics::default()),
1406 );
1407
1408 cache
1409 .get_flattened_entries_for_tests()
1410 .iter()
1411 .for_each(|(_key, program)| {
1412 if matches!(program.program, ProgramCacheEntryType::Unloaded(_))
1413 && program.deployment_slot == 0
1414 && program.effective_slot() == 1
1415 {
1416 assert_eq!(program.stats.uses.load(Ordering::Relaxed), 10);
1418 }
1419 });
1420 }
1421
1422 #[test_matrix(
1423 (
1424 ProgramCacheEntryType::FailedVerification(get_mock_program_runtime_environment()),
1425 ProgramCacheEntryType::Closed,
1426 ProgramCacheEntryType::Unloaded(get_mock_program_runtime_environment()),
1427 new_loaded_entry(get_mock_program_runtime_environment()),
1428 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1429 ),
1430 (false, true)
1431 )]
1432 fn test_assign_program_no_second_level(
1433 program: ProgramCacheEntryType,
1434 empty_second_level: bool,
1435 ) {
1436 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1440 let env = get_mock_program_runtime_environment();
1441 let program_id = Pubkey::new_unique();
1442
1443 if empty_second_level {
1444 match &mut cache.index {
1446 IndexImplementation::V1 { entries, .. } => {
1447 entries.insert(program_id, Vec::new());
1448 }
1449 }
1450 }
1451
1452 let entry = Arc::new(ProgramCacheEntry {
1453 program,
1454 account_owner: ProgramCacheEntryOwner::LoaderV3,
1455 deployment_slot: 10,
1456 stats: Arc::default(),
1457 latest_access_slot: AtomicU64::default(),
1458 });
1459
1460 cache.assign_program(&env, program_id, 10, Arc::clone(&entry));
1461
1462 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
1464 assert_eq!(slot_versions.len(), 1);
1465 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &entry));
1466
1467 assert_eq!(cache.stats.insertions.load(Ordering::Relaxed), 1);
1469 }
1470
1471 #[test_matrix(
1472 (
1473 new_closed_entry,
1474 new_builtin_entry,
1475 new_failed_verification_entry,
1476 new_unloaded_entry,
1477 new_loaded_entry,
1478 ),
1479 ((50, 0), (150, 1), (250, 2), (350, 3))
1480 )]
1481 fn test_assign_program_new_insertion_deployment_slot(
1482 new_program: fn(ProgramRuntimeEnvironment) -> ProgramCacheEntryType,
1483 case: (Slot, usize),
1484 ) {
1485 let (deployment_slot, expected_index) = case;
1486 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1487 let env = get_mock_program_runtime_environment();
1488 let program_id = Pubkey::new_unique();
1489
1490 for slot in [100, 200, 300] {
1492 cache.assign_program(
1493 &env,
1494 program_id,
1495 slot,
1496 new_test_entry_with_owner(
1497 slot,
1498 ProgramCacheEntryOwner::LoaderV3,
1499 new_program(env.clone()),
1500 ),
1501 );
1502 }
1503
1504 let entry = new_test_entry_with_owner(
1506 deployment_slot,
1507 ProgramCacheEntryOwner::LoaderV3,
1508 new_program(env.clone()),
1509 );
1510 cache.assign_program(&env, program_id, deployment_slot, Arc::clone(&entry));
1511
1512 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
1513 assert_eq!(slot_versions.len(), 4);
1514 assert!(Arc::ptr_eq(
1515 slot_versions.get(expected_index).unwrap(),
1516 &entry
1517 ));
1518 assert_eq!(cache.stats.insertions.load(Ordering::Relaxed), 4);
1519 }
1520
1521 #[test_matrix(
1522 (new_failed_verification_entry, new_unloaded_entry, new_loaded_entry),
1523 (
1524 (ProgramCacheEntryOwner::NativeLoader, 0),
1525 (ProgramCacheEntryOwner::LoaderV2, 1),
1526 (ProgramCacheEntryOwner::LoaderV4, 2),
1527 )
1528 )]
1529 fn test_assign_program_new_insertion_account_owner(
1530 new_program: fn(ProgramRuntimeEnvironment) -> ProgramCacheEntryType,
1531 case: (ProgramCacheEntryOwner, usize),
1532 ) {
1533 let (account_owner, expected_index) = case;
1534 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1535 let env = get_mock_program_runtime_environment();
1536 let program_id = Pubkey::new_unique();
1537
1538 for owner in [
1541 ProgramCacheEntryOwner::LoaderV1,
1542 ProgramCacheEntryOwner::LoaderV3,
1543 ] {
1544 cache.assign_program(
1545 &env,
1546 program_id,
1547 100,
1548 new_test_entry_with_owner(
1549 100,
1550 owner,
1551 new_program(ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock())),
1552 ),
1553 );
1554 }
1555
1556 let entry = new_test_entry_with_owner(
1559 100,
1560 account_owner,
1561 new_program(ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock())),
1562 );
1563 cache.assign_program(&env, program_id, 100, Arc::clone(&entry));
1564
1565 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
1566 assert_eq!(slot_versions.len(), 3);
1567 assert!(Arc::ptr_eq(
1568 slot_versions.get(expected_index).unwrap(),
1569 &entry
1570 ));
1571 assert_eq!(cache.stats.insertions.load(Ordering::Relaxed), 3);
1572 }
1573
1574 #[test_matrix(
1575 (new_failed_verification_entry, new_unloaded_entry, new_loaded_entry),
1576 (false, true)
1577 )]
1578 fn test_assign_program_new_insertion_environment(
1579 new_program: fn(ProgramRuntimeEnvironment) -> ProgramCacheEntryType,
1580 entry_uses_current_env: bool,
1581 ) {
1582 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1583 let env = get_mock_program_runtime_environment();
1584 let other_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
1585 let program_id = Pubkey::new_unique();
1586
1587 let (existing_env, entry_env, expected_index) = if entry_uses_current_env {
1590 (other_env, env.clone(), 1)
1591 } else {
1592 (env.clone(), other_env, 0)
1593 };
1594 cache.assign_program(
1595 &env,
1596 program_id,
1597 100,
1598 new_test_entry_with_owner(
1599 100,
1600 ProgramCacheEntryOwner::LoaderV3,
1601 new_program(existing_env),
1602 ),
1603 );
1604
1605 let entry = new_test_entry_with_owner(
1606 100,
1607 ProgramCacheEntryOwner::LoaderV3,
1608 new_program(entry_env),
1609 );
1610 cache.assign_program(&env, program_id, 100, Arc::clone(&entry));
1611
1612 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
1613 assert_eq!(slot_versions.len(), 2);
1614 assert!(Arc::ptr_eq(
1615 slot_versions.get(expected_index).unwrap(),
1616 &entry
1617 ));
1618 assert_eq!(cache.stats.insertions.load(Ordering::Relaxed), 2);
1619 }
1620
1621 #[test]
1622 #[should_panic(expected = "Unexpected assignment of a DelayVisibility tombstone")]
1623 fn test_assign_program_delay_visibility_tombstone_panics() {
1624 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1627 let env = get_mock_program_runtime_environment();
1628 cache.assign_program(
1629 &env,
1630 Pubkey::new_unique(),
1631 100,
1632 Arc::new(ProgramCacheEntry::new_delay_visibility_tombstone(
1633 100,
1634 ProgramCacheEntryOwner::LoaderV3,
1635 Arc::default(),
1636 )),
1637 );
1638 }
1639
1640 #[test]
1641 fn test_fuzz_assign_program_order() {
1642 use rand::prelude::SliceRandom;
1643 const EXPECTED_ENTRIES: [(u64, bool); 5] =
1644 [(1, true), (3, false), (5, true), (9, true), (10, false)];
1645 let mut rng = rand::rng();
1646 let program_id = Pubkey::new_unique();
1647 let env = get_mock_program_runtime_environment();
1648 for _ in 0..1000 {
1649 let mut entries = EXPECTED_ENTRIES.to_vec();
1650 entries.shuffle(&mut rng);
1651 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1652 for (deployment_slot, delay_visibility) in entries {
1653 let entry = Arc::new(if delay_visibility {
1654 ProgramCacheEntry {
1655 program: new_loaded_entry(ProgramRuntimeEnvironment::from(
1656 BuiltinProgram::new_mock(),
1657 )), account_owner: ProgramCacheEntryOwner::LoaderV2,
1659 deployment_slot,
1660 stats: Arc::default(),
1661 latest_access_slot: AtomicU64::new(deployment_slot),
1662 }
1663 } else {
1664 ProgramCacheEntry::new_failed_verification_tombstone(
1665 deployment_slot,
1666 ProgramCacheEntryOwner::LoaderV2,
1667 ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock()), )
1669 });
1670 assert!(!cache.assign_program(&env, program_id, deployment_slot, entry));
1671 }
1672 for ((deployment_slot, delay_visibility), entry) in EXPECTED_ENTRIES
1673 .iter()
1674 .zip(cache.get_slot_versions_for_tests(&program_id).iter())
1675 {
1676 assert_eq!(entry.deployment_slot, *deployment_slot);
1677 assert_eq!(
1678 entry.effective_slot(),
1679 deployment_slot.saturating_add(*delay_visibility as u64)
1680 );
1681 }
1682 }
1683 }
1684
1685 #[test_matrix(
1686 (
1687 ProgramCacheEntryType::FailedVerification(get_mock_program_runtime_environment()),
1688 new_loaded_entry(get_mock_program_runtime_environment()),
1689 ),
1690 (
1691 ProgramCacheEntryType::FailedVerification(get_mock_program_runtime_environment()),
1692 ProgramCacheEntryType::Closed,
1693 ProgramCacheEntryType::Unloaded(get_mock_program_runtime_environment()),
1694 new_loaded_entry(get_mock_program_runtime_environment()),
1695 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1696 )
1697 )]
1698 #[test_matrix(
1699 ProgramCacheEntryType::Closed,
1700 (
1701 ProgramCacheEntryType::FailedVerification(get_mock_program_runtime_environment()),
1702 ProgramCacheEntryType::Closed,
1703 new_loaded_entry(get_mock_program_runtime_environment()),
1704 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1705 )
1706 )]
1707 #[test_matrix(
1708 ProgramCacheEntryType::Unloaded(get_mock_program_runtime_environment()),
1709 (
1710 ProgramCacheEntryType::Closed,
1711 ProgramCacheEntryType::Unloaded(get_mock_program_runtime_environment()),
1712 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1713 )
1714 )]
1715 #[test_matrix(
1716 (ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),),
1717 (
1718 ProgramCacheEntryType::FailedVerification(get_mock_program_runtime_environment()),
1719 ProgramCacheEntryType::Closed,
1720 ProgramCacheEntryType::Unloaded(get_mock_program_runtime_environment()),
1721 new_loaded_entry(get_mock_program_runtime_environment()),
1722 )
1723 )]
1724 #[should_panic(expected = "Unexpected replacement of an entry")]
1725 fn test_assign_program_failure(old: ProgramCacheEntryType, new: ProgramCacheEntryType) {
1726 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1727 let env = get_mock_program_runtime_environment();
1728 let program_id = Pubkey::new_unique();
1729 assert!(!cache.assign_program(
1730 &env,
1731 program_id,
1732 10,
1733 Arc::new(ProgramCacheEntry {
1734 program: old,
1735 account_owner: ProgramCacheEntryOwner::LoaderV2,
1736 deployment_slot: 10,
1737 stats: Arc::default(),
1738 latest_access_slot: AtomicU64::default(),
1739 }),
1740 ));
1741 cache.assign_program(
1742 &env,
1743 program_id,
1744 10,
1745 Arc::new(ProgramCacheEntry {
1746 program: new,
1747 account_owner: ProgramCacheEntryOwner::LoaderV2,
1748 deployment_slot: 10,
1749 stats: Arc::default(),
1750 latest_access_slot: AtomicU64::default(),
1751 }),
1752 );
1753 }
1754
1755 #[test_matrix(
1756 ProgramCacheEntryType::Unloaded(get_mock_program_runtime_environment()),
1757 (
1758 new_loaded_entry(get_mock_program_runtime_environment()),
1759 ProgramCacheEntryType::FailedVerification(get_mock_program_runtime_environment()),
1760 )
1761 )]
1762 #[test_case(
1763 ProgramCacheEntryType::Closed,
1764 ProgramCacheEntryType::Unloaded(get_mock_program_runtime_environment())
1765 )]
1766 #[test_case(
1767 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1768 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock())
1769 )]
1770 fn test_assign_program_success(old: ProgramCacheEntryType, new: ProgramCacheEntryType) {
1771 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1772 let env = get_mock_program_runtime_environment();
1773 let program_id = Pubkey::new_unique();
1774 assert!(!cache.assign_program(
1775 &env,
1776 program_id,
1777 10,
1778 Arc::new(ProgramCacheEntry {
1779 program: old,
1780 account_owner: ProgramCacheEntryOwner::LoaderV2,
1781 deployment_slot: 10,
1782 stats: Arc::default(),
1783 latest_access_slot: AtomicU64::default(),
1784 }),
1785 ));
1786 assert!(!cache.assign_program(
1787 &env,
1788 program_id,
1789 10,
1790 Arc::new(ProgramCacheEntry {
1791 program: new,
1792 account_owner: ProgramCacheEntryOwner::LoaderV2,
1793 deployment_slot: 10,
1794 stats: Arc::default(),
1795 latest_access_slot: AtomicU64::default(),
1796 }),
1797 ));
1798 }
1799
1800 #[test]
1801 fn test_assign_program_removes_entries_in_same_slot() {
1802 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1803 let env = get_mock_program_runtime_environment();
1804 let program_id = Pubkey::new_unique();
1805 let closed_other_slot = new_test_entry_with_owner(
1806 9,
1807 ProgramCacheEntryOwner::LoaderV2,
1808 new_closed_entry(env.clone()),
1809 );
1810 let closed_current_slot = new_test_entry_with_owner(
1811 10,
1812 ProgramCacheEntryOwner::LoaderV2,
1813 new_closed_entry(env.clone()),
1814 );
1815 let unloaded_current_env = new_test_entry_with_owner(
1816 10,
1817 ProgramCacheEntryOwner::LoaderV2,
1818 new_unloaded_entry(get_mock_program_runtime_environment()),
1819 );
1820 let unloaded_upcoming_env = new_test_entry_with_owner(
1821 10,
1822 ProgramCacheEntryOwner::LoaderV2,
1823 new_unloaded_entry(ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock())),
1824 );
1825
1826 assert!(!cache.assign_program(&env, program_id, 9, closed_other_slot.clone()));
1830 assert!(!cache.assign_program(&env, program_id, 10, closed_current_slot.clone()));
1831 assert_eq!(
1832 cache.get_slot_versions_for_tests(&program_id),
1833 &[closed_other_slot.clone(), closed_current_slot.clone()]
1834 );
1835
1836 assert!(!cache.assign_program(&env, program_id, 10, unloaded_current_env.clone()));
1845 assert_eq!(
1846 cache.get_slot_versions_for_tests(&program_id),
1847 &[
1848 closed_other_slot.clone(),
1849 unloaded_current_env.clone() ]
1851 );
1852
1853 assert!(!cache.assign_program(&env, program_id, 10, unloaded_upcoming_env.clone()));
1857 assert_eq!(
1858 cache.get_slot_versions_for_tests(&program_id),
1859 &[
1860 closed_other_slot,
1861 unloaded_current_env,
1862 unloaded_upcoming_env
1863 ]
1864 );
1865 }
1866
1867 #[test]
1868 fn test_assign_program_reload_merges_statistics() {
1869 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1870 let env = get_mock_program_runtime_environment();
1871 let program_id = Pubkey::new_unique();
1872
1873 let stats = Arc::new(ProgramStatistics {
1874 uses: 1.into(),
1875 compilations: 2.into(),
1876 total_compilation_time_us: 3.into(),
1877 compilation_time_ema: 100.into(),
1878 jit_invocations: 4.into(),
1879 total_jit_execution_time_us: 5.into(),
1880 jit_execution_time_ema: 200.into(),
1881 interpreted_invocations: 6.into(),
1882 total_interpretation_time_us: 7.into(),
1883 interpretation_time_ema: 300.into(),
1884 });
1885 let unloaded = Arc::new(ProgramCacheEntry {
1886 program: ProgramCacheEntryType::Unloaded(env.clone()),
1887 account_owner: ProgramCacheEntryOwner::LoaderV3,
1888 deployment_slot: 100,
1889 stats: Arc::clone(&stats),
1890 latest_access_slot: AtomicU64::default(),
1891 });
1892 cache.assign_program(&env, program_id, 100, unloaded);
1893
1894 let loaded = Arc::new(ProgramCacheEntry {
1896 program: new_loaded_entry(env.clone()),
1897 account_owner: ProgramCacheEntryOwner::LoaderV3,
1898 deployment_slot: 100,
1899 stats: Arc::default(), latest_access_slot: AtomicU64::default(),
1901 });
1902 cache.assign_program(&env, program_id, 100, Arc::clone(&loaded));
1903
1904 assert_eq!(cache.stats.insertions.load(Ordering::Relaxed), 1);
1905 assert_eq!(cache.stats.reloads.load(Ordering::Relaxed), 1);
1906
1907 let merged = &loaded.stats;
1908 let ord = Ordering::Relaxed;
1909 assert_eq!(merged.uses.load(ord), stats.uses.load(ord));
1910 assert_eq!(merged.compilations.load(ord), stats.compilations.load(ord));
1911 assert_eq!(
1912 merged.total_compilation_time_us.load(ord),
1913 stats.total_compilation_time_us.load(ord)
1914 );
1915 assert_eq!(
1916 merged.jit_invocations.load(ord),
1917 stats.jit_invocations.load(ord)
1918 );
1919 assert_eq!(
1920 merged.total_jit_execution_time_us.load(ord),
1921 stats.total_jit_execution_time_us.load(ord)
1922 );
1923 assert_eq!(
1924 merged.interpreted_invocations.load(ord),
1925 stats.interpreted_invocations.load(ord)
1926 );
1927 assert_eq!(
1928 merged.total_interpretation_time_us.load(ord),
1929 stats.total_interpretation_time_us.load(ord)
1930 );
1931
1932 const EMA_DIVISOR: u64 = 2;
1935 assert_eq!(
1936 merged.compilation_time_ema.load(ord),
1937 stats
1938 .compilation_time_ema
1939 .load(ord)
1940 .wrapping_div(EMA_DIVISOR)
1941 );
1942 assert_eq!(
1943 merged.jit_execution_time_ema.load(ord),
1944 stats
1945 .jit_execution_time_ema
1946 .load(ord)
1947 .wrapping_div(EMA_DIVISOR)
1948 );
1949 assert_eq!(
1950 merged.interpretation_time_ema.load(ord),
1951 stats
1952 .interpretation_time_ema
1953 .load(ord)
1954 .wrapping_div(EMA_DIVISOR)
1955 );
1956 }
1957
1958 #[test]
1959 fn test_tombstone() {
1960 let env = get_mock_program_runtime_environment();
1961 let tombstone = ProgramCacheEntry::new_failed_verification_tombstone(
1962 0,
1963 ProgramCacheEntryOwner::LoaderV2,
1964 env.clone(),
1965 );
1966 assert_matches!(
1967 tombstone.program,
1968 ProgramCacheEntryType::FailedVerification(_)
1969 );
1970 assert!(tombstone.is_tombstone());
1971 assert_eq!(tombstone.deployment_slot, 0);
1972 assert_eq!(tombstone.effective_slot(), 0);
1973
1974 let tombstone =
1975 ProgramCacheEntry::new_closed_tombstone(100, ProgramCacheEntryOwner::LoaderV2);
1976 assert_matches!(tombstone.program, ProgramCacheEntryType::Closed);
1977 assert!(tombstone.is_tombstone());
1978 assert_eq!(tombstone.deployment_slot, 100);
1979 assert_eq!(tombstone.effective_slot(), 100);
1980
1981 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1982 let program1 = Pubkey::new_unique();
1983 let tombstone = set_failed_verification_tombstone(&mut cache, program1, 10, env.clone());
1984 let slot_versions = cache.get_slot_versions_for_tests(&program1);
1985 assert_eq!(slot_versions.len(), 1);
1986 assert!(slot_versions.first().unwrap().is_tombstone());
1987 assert_eq!(tombstone.deployment_slot, 10);
1988 assert_eq!(tombstone.effective_slot(), 10);
1989
1990 let program2 = Pubkey::new_unique();
1992 cache.assign_program(&env, program2, 50, new_test_builtin_entry(50));
1993 let slot_versions = cache.get_slot_versions_for_tests(&program2);
1994 assert_eq!(slot_versions.len(), 1);
1995 assert!(!slot_versions.first().unwrap().is_tombstone());
1996
1997 let tombstone = set_failed_verification_tombstone(&mut cache, program2, 60, env);
1998 let slot_versions = cache.get_slot_versions_for_tests(&program2);
1999 assert_eq!(slot_versions.len(), 2);
2000 assert!(!slot_versions.first().unwrap().is_tombstone());
2001 assert!(slot_versions.get(1).unwrap().is_tombstone());
2002 assert!(tombstone.is_tombstone());
2003 assert_eq!(tombstone.deployment_slot, 60);
2004 assert_eq!(tombstone.effective_slot(), 60);
2005 }
2006
2007 struct TestForkGraph {
2008 relation: BlockRelation,
2009 }
2010 impl ForkGraph for TestForkGraph {
2011 fn relationship(&self, _a: Slot, _b: Slot) -> BlockRelation {
2012 self.relation
2013 }
2014 }
2015
2016 #[test]
2017 fn test_prune_empty() {
2018 let mut cache = ProgramCache::<TestForkGraph>::new(0);
2019 let fork_graph = Arc::new(RwLock::new(TestForkGraph {
2020 relation: BlockRelation::Unrelated,
2021 }));
2022
2023 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2024
2025 cache.prune(0, None, &fork_graph.read().unwrap());
2026 assert!(cache.get_flattened_entries_for_tests().is_empty());
2027
2028 cache.prune(10, None, &fork_graph.read().unwrap());
2029 assert!(cache.get_flattened_entries_for_tests().is_empty());
2030
2031 let mut cache = ProgramCache::<TestForkGraph>::new(0);
2032 let fork_graph = Arc::new(RwLock::new(TestForkGraph {
2033 relation: BlockRelation::Ancestor,
2034 }));
2035
2036 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2037
2038 cache.prune(0, None, &fork_graph.read().unwrap());
2039 assert!(cache.get_flattened_entries_for_tests().is_empty());
2040
2041 cache.prune(10, None, &fork_graph.read().unwrap());
2042 assert!(cache.get_flattened_entries_for_tests().is_empty());
2043
2044 let mut cache = ProgramCache::<TestForkGraph>::new(0);
2045 let fork_graph = Arc::new(RwLock::new(TestForkGraph {
2046 relation: BlockRelation::Descendant,
2047 }));
2048
2049 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2050
2051 cache.prune(0, None, &fork_graph.read().unwrap());
2052 assert!(cache.get_flattened_entries_for_tests().is_empty());
2053
2054 cache.prune(10, None, &fork_graph.read().unwrap());
2055 assert!(cache.get_flattened_entries_for_tests().is_empty());
2056
2057 let mut cache = ProgramCache::<TestForkGraph>::new(0);
2058 let fork_graph = Arc::new(RwLock::new(TestForkGraph {
2059 relation: BlockRelation::Unknown,
2060 }));
2061 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2062
2063 cache.prune(0, None, &fork_graph.read().unwrap());
2064 assert!(cache.get_flattened_entries_for_tests().is_empty());
2065
2066 cache.prune(10, None, &fork_graph.read().unwrap());
2067 assert!(cache.get_flattened_entries_for_tests().is_empty());
2068 }
2069
2070 #[test]
2071 fn test_prune_removes_programs_with_no_entries() {
2072 let (mut cache, fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Unknown);
2073 let env = get_mock_program_runtime_environment();
2074 let emptied = Pubkey::new_unique();
2075 cache.assign_program(
2076 &env,
2077 emptied,
2078 100,
2079 new_test_entry_with_owner(
2080 100,
2081 ProgramCacheEntryOwner::LoaderV3,
2082 new_loaded_entry(env.clone()),
2083 ),
2084 );
2085
2086 cache.prune(50, None, &fork_graph.read().unwrap());
2088 match &cache.index {
2089 IndexImplementation::V1 { entries, .. } => assert!(!entries.contains_key(&emptied)),
2090 }
2091 assert_eq!(cache.stats.empty_entries.load(Ordering::Relaxed), 1);
2092 assert_eq!(cache.stats.prunes_orphan.load(Ordering::Relaxed), 1);
2093 }
2094
2095 #[test]
2096 fn test_prune_across_the_root() {
2097 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
2103 let mut fork_graph = TestForkGraphSpecific::default();
2104 fork_graph.insert_fork(&[30, 50, 70]);
2105 let fork_graph = Arc::new(RwLock::new(fork_graph));
2106 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2107
2108 let env = get_mock_program_runtime_environment();
2109 let program_id = Pubkey::new_unique();
2110 let below = new_test_entry_with_owner(
2111 30,
2112 ProgramCacheEntryOwner::LoaderV3,
2113 new_loaded_entry(env.clone()),
2114 );
2115 let above = new_test_entry_with_owner(
2116 70,
2117 ProgramCacheEntryOwner::LoaderV3,
2118 new_loaded_entry(env.clone()),
2119 );
2120 cache.assign_program(&env, program_id, 30, Arc::clone(&below));
2121 cache.assign_program(&env, program_id, 70, Arc::clone(&above));
2122
2123 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
2124 assert_eq!(slot_versions.len(), 2);
2125 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &below));
2126 assert!(Arc::ptr_eq(slot_versions.get(1).unwrap(), &above));
2127
2128 cache.prune(50, None, &fork_graph.read().unwrap());
2129
2130 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
2132 assert_eq!(slot_versions.len(), 2);
2133 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &below));
2134 assert!(Arc::ptr_eq(slot_versions.get(1).unwrap(), &above));
2135 }
2136
2137 #[test]
2138 fn test_prune_across_the_root_ancestors() {
2139 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
2145 let mut fork_graph = TestForkGraphSpecific::default();
2146 fork_graph.insert_fork(&[10, 20, 30, 50, 70]);
2147 let fork_graph = Arc::new(RwLock::new(fork_graph));
2148 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2149
2150 let env = get_mock_program_runtime_environment();
2151 let program_id = Pubkey::new_unique();
2152 let oldest = new_test_entry_with_owner(
2153 10,
2154 ProgramCacheEntryOwner::LoaderV3,
2155 new_loaded_entry(env.clone()),
2156 );
2157 let older = new_test_entry_with_owner(
2158 20,
2159 ProgramCacheEntryOwner::LoaderV3,
2160 new_loaded_entry(env.clone()),
2161 );
2162 let below = new_test_entry_with_owner(
2163 30,
2164 ProgramCacheEntryOwner::LoaderV3,
2165 new_loaded_entry(env.clone()),
2166 );
2167 let above = new_test_entry_with_owner(
2168 70,
2169 ProgramCacheEntryOwner::LoaderV3,
2170 new_loaded_entry(env.clone()),
2171 );
2172 cache.assign_program(&env, program_id, 10, Arc::clone(&oldest));
2173 cache.assign_program(&env, program_id, 20, Arc::clone(&older));
2174 cache.assign_program(&env, program_id, 30, Arc::clone(&below));
2175 cache.assign_program(&env, program_id, 70, Arc::clone(&above));
2176
2177 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
2178 assert_eq!(slot_versions.len(), 4);
2179 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &oldest));
2180 assert!(Arc::ptr_eq(slot_versions.get(1).unwrap(), &older));
2181 assert!(Arc::ptr_eq(slot_versions.get(2).unwrap(), &below));
2182 assert!(Arc::ptr_eq(slot_versions.get(3).unwrap(), &above));
2183
2184 cache.prune(50, None, &fork_graph.read().unwrap());
2185
2186 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
2189 assert_eq!(slot_versions.len(), 2);
2190 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &below));
2191 assert!(Arc::ptr_eq(slot_versions.get(1).unwrap(), &above));
2192 assert_eq!(cache.stats.prunes_orphan.load(Ordering::Relaxed), 2);
2193 }
2194
2195 #[test]
2196 fn test_prune_entry_older_than_root() {
2197 let (mut cache, fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Unknown);
2211 let env = get_mock_program_runtime_environment();
2212 let program_id = Pubkey::new_unique();
2213 let entry = new_test_entry_with_owner(
2214 5,
2215 ProgramCacheEntryOwner::LoaderV3,
2216 new_loaded_entry(env.clone()),
2217 );
2218 cache.assign_program(&env, program_id, 5, Arc::clone(&entry));
2219 cache.latest_root_slot = 10;
2220
2221 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
2222 assert_eq!(slot_versions.len(), 1);
2223 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &entry));
2224
2225 cache.prune(20, None, &fork_graph.read().unwrap());
2226
2227 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
2231 assert_eq!(slot_versions.len(), 1);
2232 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &entry));
2233 assert_eq!(cache.stats.prunes_orphan.load(Ordering::Relaxed), 0);
2234 }
2235
2236 #[test]
2237 fn test_prune_orphan_newer_than_root() {
2238 let (mut cache, fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Unknown);
2248 let env = get_mock_program_runtime_environment();
2249 let program_id = Pubkey::new_unique();
2250 let orphan = new_test_entry_with_owner(
2251 100,
2252 ProgramCacheEntryOwner::LoaderV3,
2253 new_loaded_entry(env.clone()),
2254 );
2255 cache.assign_program(&env, program_id, 100, Arc::clone(&orphan));
2256
2257 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
2258 assert_eq!(slot_versions.len(), 1);
2259 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &orphan));
2260
2261 cache.prune(50, None, &fork_graph.read().unwrap());
2262
2263 assert!(cache.get_slot_versions_for_tests(&program_id).is_empty());
2266 assert_eq!(cache.stats.prunes_orphan.load(Ordering::Relaxed), 1);
2267 }
2268
2269 #[test]
2270 fn test_prune_tombstones() {
2271 let env = get_mock_program_runtime_environment();
2272 let fork_graph = Arc::new(RwLock::new(TestForkGraph {
2273 relation: BlockRelation::Ancestor,
2274 }));
2275
2276 let program1 = Pubkey::new_unique();
2277 let entries = [
2278 Arc::new(ProgramCacheEntry::new_unloaded(
2279 20,
2280 ProgramCacheEntryOwner::LoaderV3,
2281 ProgramRuntimeEnvironment::clone(&env),
2282 )),
2283 Arc::new(ProgramCacheEntry::new_closed_tombstone(
2284 20,
2285 ProgramCacheEntryOwner::LoaderV3,
2286 )),
2287 Arc::new(ProgramCacheEntry::new_failed_verification_tombstone(
2288 20,
2289 ProgramCacheEntryOwner::LoaderV3,
2290 ProgramRuntimeEnvironment::clone(&env),
2291 )),
2292 ];
2293 for entry in &entries {
2294 let mut cache = ProgramCache::<TestForkGraph>::new(0);
2295 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2296 cache.assign_program(&env, program1, 10, new_test_entry(10));
2298 cache.assign_program(&env, program1, entry.deployment_slot, Arc::clone(entry));
2299 cache.prune(
2300 MAX_TOMBSTONE_AGE_IN_SLOTS,
2301 None,
2302 &fork_graph.read().unwrap(),
2303 );
2304 let slot_versions = cache.get_slot_versions_for_tests(&program1);
2305 assert_eq!(slot_versions, std::slice::from_ref(entry));
2306 cache.prune(
2308 MAX_TOMBSTONE_AGE_IN_SLOTS
2309 .saturating_add(entry.latest_access_slot.load(Ordering::Relaxed)),
2310 None,
2311 &fork_graph.read().unwrap(),
2312 );
2313 let slot_versions = cache.get_slot_versions_for_tests(&program1);
2314 assert_eq!(slot_versions, std::slice::from_ref(entry));
2315 cache.prune(
2317 MAX_TOMBSTONE_AGE_IN_SLOTS
2318 .saturating_add(entry.latest_access_slot.load(Ordering::Relaxed))
2319 .saturating_add(1),
2320 None,
2321 &fork_graph.read().unwrap(),
2322 );
2323 assert!(cache.get_flattened_entries_for_tests().is_empty());
2324 }
2325 }
2326
2327 #[test]
2328 fn test_prune_tombstone_first_ancestor_takes_the_rest() {
2329 let (mut cache, fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
2338 let env = get_mock_program_runtime_environment();
2339 let other_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
2340 let program_id = Pubkey::new_unique();
2341 let on_other_env = new_test_entry_with_owner(
2342 50,
2343 ProgramCacheEntryOwner::LoaderV3,
2344 new_loaded_entry(other_env.clone()),
2345 );
2346 let on_env = new_test_entry_with_owner(
2347 60,
2348 ProgramCacheEntryOwner::LoaderV3,
2349 new_loaded_entry(env.clone()),
2350 );
2351 let closed = new_test_entry_with_owner(
2352 100,
2353 ProgramCacheEntryOwner::LoaderV3,
2354 new_closed_entry(env.clone()),
2355 );
2356 cache.assign_program(&other_env, program_id, 50, Arc::clone(&on_other_env));
2357 cache.assign_program(&env, program_id, 60, Arc::clone(&on_env));
2358 cache.assign_program(&env, program_id, 100, Arc::clone(&closed));
2359
2360 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
2361 assert_eq!(slot_versions.len(), 3);
2362 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &on_other_env));
2363 assert!(Arc::ptr_eq(slot_versions.get(1).unwrap(), &on_env));
2364 assert!(Arc::ptr_eq(slot_versions.get(2).unwrap(), &closed));
2365
2366 cache.prune(200, None, &fork_graph.read().unwrap());
2367
2368 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
2372 assert_eq!(slot_versions.len(), 1);
2373 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &closed));
2374 assert_eq!(cache.stats.prunes_orphan.load(Ordering::Relaxed), 2);
2375 }
2376
2377 #[test]
2378 fn test_prune_tombstone_newer_than_root_keeps_the_rest() {
2379 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
2389 let mut fork_graph = TestForkGraphSpecific::default();
2390 fork_graph.insert_fork(&[50, 60, 100, 101]);
2391 let fork_graph = Arc::new(RwLock::new(fork_graph));
2392 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2393
2394 let env = get_mock_program_runtime_environment();
2395 let other_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
2396 let program_id = Pubkey::new_unique();
2397 let on_other_env = new_test_entry_with_owner(
2398 50,
2399 ProgramCacheEntryOwner::LoaderV3,
2400 new_loaded_entry(other_env.clone()),
2401 );
2402 let on_env = new_test_entry_with_owner(
2403 60,
2404 ProgramCacheEntryOwner::LoaderV3,
2405 new_loaded_entry(env.clone()),
2406 );
2407 let closed = new_test_entry_with_owner(
2408 101,
2409 ProgramCacheEntryOwner::LoaderV3,
2410 new_closed_entry(env.clone()),
2411 );
2412 cache.assign_program(&other_env, program_id, 50, Arc::clone(&on_other_env));
2413 cache.assign_program(&env, program_id, 60, Arc::clone(&on_env));
2414 cache.assign_program(&env, program_id, 101, Arc::clone(&closed));
2415
2416 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
2417 assert_eq!(slot_versions.len(), 3);
2418 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &on_other_env));
2419 assert!(Arc::ptr_eq(slot_versions.get(1).unwrap(), &on_env));
2420 assert!(Arc::ptr_eq(slot_versions.get(2).unwrap(), &closed));
2421
2422 cache.prune(100, None, &fork_graph.read().unwrap());
2423
2424 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
2429 assert_eq!(slot_versions.len(), 3);
2430 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &on_other_env));
2431 assert!(Arc::ptr_eq(slot_versions.get(1).unwrap(), &on_env));
2432 assert!(Arc::ptr_eq(slot_versions.get(2).unwrap(), &closed));
2433 assert_eq!(cache.stats.prunes_orphan.load(Ordering::Relaxed), 0);
2434 }
2435
2436 #[test]
2437 fn test_prune_with_two_environments_before_epoch_boundary() {
2438 let mut cache = ProgramCache::<TestForkGraph>::new(0);
2439 let env = get_mock_program_runtime_environment();
2440 let new_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
2441 let fork_graph = Arc::new(RwLock::new(TestForkGraph {
2442 relation: BlockRelation::Ancestor,
2443 }));
2444 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2445
2446 let program1 = Pubkey::new_unique();
2447 cache.assign_program(&env, program1, 10, new_test_entry(10));
2448 let updated_program = Arc::new(ProgramCacheEntry {
2449 program: new_loaded_entry(new_env.clone()),
2450 deployment_slot: 20,
2451 ..Default::default()
2452 });
2453 cache.assign_program(&env, program1, 20, updated_program.clone());
2454
2455 assert_eq!(cache.get_slot_versions_for_tests(&program1).len(), 2);
2457
2458 cache.prune(21, None, &fork_graph.read().unwrap());
2459
2460 assert_eq!(cache.get_slot_versions_for_tests(&program1).len(), 2);
2462 }
2463
2464 #[test]
2465 fn test_prune_with_two_environments_after_epoch_boundary() {
2466 let mut cache = ProgramCache::<TestForkGraph>::new(0);
2467 let env = get_mock_program_runtime_environment();
2468 let new_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
2469 let fork_graph = Arc::new(RwLock::new(TestForkGraph {
2470 relation: BlockRelation::Ancestor,
2471 }));
2472 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2473 let program1 = Pubkey::new_unique();
2474
2475 let old_program_old_env = Arc::new(ProgramCacheEntry {
2476 program: new_loaded_entry(env.clone()),
2477 deployment_slot: 10,
2478 ..Default::default()
2479 });
2480 let old_program_new_env = Arc::new(ProgramCacheEntry {
2481 program: new_loaded_entry(new_env.clone()),
2482 deployment_slot: 10,
2483 ..Default::default()
2484 });
2485 let new_program_old_env = Arc::new(ProgramCacheEntry {
2486 program: new_loaded_entry(env.clone()),
2487 deployment_slot: 20,
2488 ..Default::default()
2489 });
2490 cache.assign_program(&env, program1, 10, old_program_old_env.clone());
2491 cache.assign_program(&env, program1, 10, old_program_new_env.clone());
2492 cache.assign_program(&env, program1, 20, new_program_old_env.clone());
2493 let slot_versions = cache.get_slot_versions_for_tests(&program1);
2494 assert_eq!(
2495 &slot_versions,
2496 &[
2497 old_program_new_env.clone(),
2498 old_program_old_env.clone(),
2499 new_program_old_env.clone(),
2500 ]
2501 );
2502
2503 cache.prune(21, Some(new_env.clone()), &fork_graph.read().unwrap());
2504 let slot_versions = cache.get_slot_versions_for_tests(&program1);
2505 assert_eq!(&slot_versions, &[old_program_new_env]);
2506 assert!(matches!(
2507 &slot_versions.first().unwrap().program,
2508 ProgramCacheEntryType::Loaded(_)
2509 ));
2510 }
2511
2512 #[test_matrix(
2513 (
2514 new_closed_entry,
2515 new_builtin_entry,
2516 new_failed_verification_entry,
2517 new_unloaded_entry,
2518 new_loaded_entry,
2519 ),
2520 (false, true)
2521 )]
2522 fn test_prune_environment_sweep_by_entry_type(
2523 new_program: fn(ProgramRuntimeEnvironment) -> ProgramCacheEntryType,
2524 on_new_environment: bool,
2525 ) {
2526 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
2536 let mut fork_graph = TestForkGraphSpecific::default();
2537 fork_graph.insert_fork(&[40, 50]);
2538 let fork_graph = Arc::new(RwLock::new(fork_graph));
2539 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2540
2541 let env = get_mock_program_runtime_environment();
2542 let new_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
2543 let entry_env = if on_new_environment {
2544 new_env.clone()
2545 } else {
2546 env.clone()
2547 };
2548 let program_id = Pubkey::new_unique();
2549 let entry = new_test_entry_with_owner(
2550 50,
2551 ProgramCacheEntryOwner::LoaderV3,
2552 new_program(entry_env.clone()),
2553 );
2554 let carries_an_environment = entry.program.get_environment().is_some();
2555 cache.assign_program(&entry_env, program_id, 50, Arc::clone(&entry));
2556
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 cache.prune(40, Some(new_env.clone()), &fork_graph.read().unwrap());
2562
2563 let swept = carries_an_environment && !on_new_environment;
2566 assert_eq!(
2567 cache.stats.prunes_environment.load(Ordering::Relaxed),
2568 u64::from(swept)
2569 );
2570 if swept {
2571 assert!(cache.get_slot_versions_for_tests(&program_id).is_empty());
2572 } else {
2573 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
2574 assert_eq!(slot_versions.len(), 1);
2575 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &entry));
2576 }
2577 }
2578
2579 #[test]
2580 fn test_prune_environment_sweep_keeps_tombstones() {
2581 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
2593 let mut fork_graph = TestForkGraphSpecific::default();
2594 fork_graph.insert_fork(&[0, 40, 50, 60, 70, 100]);
2595 let fork_graph = Arc::new(RwLock::new(fork_graph));
2596 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2597 let env = get_mock_program_runtime_environment();
2598 let new_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
2599 let program_id = Pubkey::new_unique();
2600 let closed = new_test_entry_with_owner(
2601 50,
2602 ProgramCacheEntryOwner::LoaderV3,
2603 new_closed_entry(env.clone()),
2604 );
2605 let failed_verification = new_test_entry_with_owner(
2606 60,
2607 ProgramCacheEntryOwner::LoaderV3,
2608 new_failed_verification_entry(env.clone()),
2610 );
2611 let loaded = new_test_entry_with_owner(
2612 70,
2613 ProgramCacheEntryOwner::LoaderV3,
2614 new_loaded_entry(env.clone()),
2615 );
2616 cache.assign_program(&env, program_id, 50, Arc::clone(&closed));
2617 cache.assign_program(&env, program_id, 60, Arc::clone(&failed_verification));
2618 cache.assign_program(&env, program_id, 70, Arc::clone(&loaded));
2619
2620 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
2621 assert_eq!(slot_versions.len(), 3);
2622 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &closed));
2623 assert!(Arc::ptr_eq(
2624 slot_versions.get(1).unwrap(),
2625 &failed_verification
2626 ));
2627 assert!(Arc::ptr_eq(slot_versions.get(2).unwrap(), &loaded));
2628
2629 cache.prune(40, Some(new_env.clone()), &fork_graph.read().unwrap());
2632 assert_eq!(cache.stats.prunes_environment.load(Ordering::Relaxed), 2);
2633 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
2634 assert_eq!(slot_versions.len(), 1);
2635 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &closed));
2636
2637 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, &new_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 let mut search_for = vec![ProgramToLoad {
2655 program_id: &program_id,
2656 loader: ProgramCacheEntryOwner::LoaderV3,
2657 deployment_slot: 50,
2658 }];
2659 let mut extracted = ProgramCacheForTxBatch::new(100);
2660 cache.extract(&mut search_for, &mut extracted, &env, true, true);
2661 assert!(search_for.is_empty());
2662 assert!(Arc::ptr_eq(
2663 extracted.entries.get(&program_id).unwrap(),
2664 &closed
2665 ));
2666 }
2667
2668 #[test]
2669 #[should_panic(expected = "self.latest_root_slot <= new_root_slot")]
2670 fn test_prune_backwards_panics() {
2671 let (mut cache, fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
2672 cache.latest_root_slot = 100;
2673
2674 cache.prune(50, None, &fork_graph.read().unwrap());
2677 }
2678
2679 #[derive(Default)]
2680 struct TestForkGraphSpecific {
2681 forks: Vec<Vec<Slot>>,
2682 }
2683
2684 impl TestForkGraphSpecific {
2685 fn insert_fork(&mut self, fork: &[Slot]) {
2686 let mut fork = fork.to_vec();
2687 fork.sort();
2688 self.forks.push(fork)
2689 }
2690 }
2691
2692 impl ForkGraph for TestForkGraphSpecific {
2693 fn relationship(&self, a: Slot, b: Slot) -> BlockRelation {
2694 match self.forks.iter().try_for_each(|fork| {
2695 let relation = fork
2696 .iter()
2697 .position(|x| *x == a)
2698 .and_then(|a_pos| {
2699 fork.iter().position(|x| *x == b).and_then(|b_pos| {
2700 (a_pos == b_pos)
2701 .then_some(BlockRelation::Equal)
2702 .or_else(|| (a_pos < b_pos).then_some(BlockRelation::Ancestor))
2703 .or(Some(BlockRelation::Descendant))
2704 })
2705 })
2706 .unwrap_or(BlockRelation::Unrelated);
2707
2708 if relation != BlockRelation::Unrelated {
2709 return ControlFlow::Break(relation);
2710 }
2711
2712 ControlFlow::Continue(())
2713 }) {
2714 ControlFlow::Break(relation) => relation,
2715 _ => BlockRelation::Unrelated,
2716 }
2717 }
2718 }
2719
2720 fn get_entries_to_load<'a>(
2721 cache: &ProgramCache<TestForkGraphSpecific>,
2722 loading_slot: Slot,
2723 keys: &'a [Pubkey],
2724 ) -> Vec<ProgramToLoad<'a>> {
2725 let fork_graph = cache.fork_graph.as_ref().unwrap().upgrade().unwrap();
2726 let locked_fork_graph = fork_graph.read().unwrap();
2727 let entries = cache.get_flattened_entries_for_tests();
2728 keys.iter()
2729 .filter_map(|key| {
2730 entries
2731 .iter()
2732 .rev()
2733 .find(|(program_id, entry)| {
2734 program_id == key
2735 && matches!(
2736 locked_fork_graph.relationship(entry.deployment_slot, loading_slot),
2737 BlockRelation::Equal | BlockRelation::Ancestor,
2738 )
2739 })
2740 .map(|(_program_id, entry)| ProgramToLoad {
2741 program_id: key,
2742 loader: entry.account_owner,
2743 deployment_slot: entry.deployment_slot,
2744 })
2745 })
2746 .collect()
2747 }
2748
2749 fn match_slot(
2750 extracted: &ProgramCacheForTxBatch,
2751 program: &Pubkey,
2752 deployment_slot: Slot,
2753 working_slot: Slot,
2754 ) -> bool {
2755 assert_eq!(extracted.slot, working_slot);
2756 extracted
2757 .entries
2758 .get(program)
2759 .map(|entry| entry.deployment_slot == deployment_slot)
2760 .unwrap_or(false)
2761 }
2762
2763 fn match_missing(
2764 missing: &[ProgramToLoad],
2765 program_id: &Pubkey,
2766 expected_result: bool,
2767 ) -> bool {
2768 missing.iter().any(|entry| entry.program_id == program_id) == expected_result
2769 }
2770
2771 #[test]
2772 fn test_fork_extract_and_prune() {
2773 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
2774 let env = get_mock_program_runtime_environment();
2775
2776 let mut fork_graph = TestForkGraphSpecific::default();
2792 fork_graph.insert_fork(&[0, 10, 20, 22]);
2793 fork_graph.insert_fork(&[0, 5, 11, 15, 16, 18, 19, 21, 23]);
2794 fork_graph.insert_fork(&[0, 5, 11, 25, 27]);
2795
2796 let fork_graph = Arc::new(RwLock::new(fork_graph));
2797 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2798
2799 let program1 = Pubkey::new_unique();
2800 cache.assign_program(&env, program1, 0, new_test_entry(0));
2801 cache.assign_program(&env, program1, 10, new_test_entry(10));
2802 cache.assign_program(&env, program1, 20, new_test_entry(20));
2803
2804 let program2 = Pubkey::new_unique();
2805 cache.assign_program(&env, program2, 5, new_test_entry(5));
2806 cache.assign_program(&env, program2, 11, new_test_entry(11));
2807
2808 let program3 = Pubkey::new_unique();
2809 cache.assign_program(&env, program3, 25, new_test_entry(25));
2810
2811 let program4 = Pubkey::new_unique();
2812 cache.assign_program(&env, program4, 0, new_test_entry(0));
2813 cache.assign_program(&env, program4, 5, new_test_entry(5));
2814 cache.assign_program(&env, program4, 15, new_test_entry(15));
2816
2817 let keys = &[program1, program2, program3, program4];
2834 let mut missing = get_entries_to_load(&cache, 22, keys);
2835 assert!(match_missing(&missing, &program2, false));
2836 assert!(match_missing(&missing, &program3, false));
2837 let mut extracted = ProgramCacheForTxBatch::new(22);
2838 cache.extract(&mut missing, &mut extracted, &env, true, true);
2839 assert!(match_slot(&extracted, &program1, 20, 22));
2840 assert!(match_slot(&extracted, &program4, 0, 22));
2841
2842 let mut missing = get_entries_to_load(&cache, 15, keys);
2844 assert!(match_missing(&missing, &program3, false));
2845 let mut extracted = ProgramCacheForTxBatch::new(15);
2846 cache.extract(&mut missing, &mut extracted, &env, true, true);
2847 assert!(match_slot(&extracted, &program1, 0, 15));
2848 assert!(match_slot(&extracted, &program2, 11, 15));
2849 let tombstone = extracted
2852 .find(&program4)
2853 .expect("Failed to find the tombstone");
2854 assert_matches!(tombstone.program, ProgramCacheEntryType::DelayVisibility);
2855 assert_eq!(tombstone.deployment_slot, 15);
2856
2857 let mut missing = get_entries_to_load(&cache, 18, keys);
2859 assert!(match_missing(&missing, &program3, false));
2860 let mut extracted = ProgramCacheForTxBatch::new(18);
2861 cache.extract(&mut missing, &mut extracted, &env, true, true);
2862 assert!(match_slot(&extracted, &program1, 0, 18));
2863 assert!(match_slot(&extracted, &program2, 11, 18));
2864 assert!(match_slot(&extracted, &program4, 15, 18));
2866
2867 let mut missing = get_entries_to_load(&cache, 23, keys);
2869 assert!(match_missing(&missing, &program3, false));
2870 let mut extracted = ProgramCacheForTxBatch::new(23);
2871 cache.extract(&mut missing, &mut extracted, &env, true, true);
2872 assert!(match_slot(&extracted, &program1, 0, 23));
2873 assert!(match_slot(&extracted, &program2, 11, 23));
2874 assert!(match_slot(&extracted, &program4, 15, 23));
2876
2877 let mut missing = get_entries_to_load(&cache, 11, keys);
2879 assert!(match_missing(&missing, &program3, false));
2880 let mut extracted = ProgramCacheForTxBatch::new(11);
2881 cache.extract(&mut missing, &mut extracted, &env, true, true);
2882 assert!(match_slot(&extracted, &program1, 0, 11));
2883 let tombstone = extracted
2885 .find(&program2)
2886 .expect("Failed to find the tombstone");
2887 assert_matches!(tombstone.program, ProgramCacheEntryType::DelayVisibility);
2888 assert_eq!(tombstone.deployment_slot, 11);
2889 assert!(match_slot(&extracted, &program4, 5, 11));
2890
2891 cache.prune(5, None, &fork_graph.read().unwrap());
2892
2893 let mut missing = get_entries_to_load(&cache, 21, keys);
2910 assert!(match_missing(&missing, &program3, false));
2911 let mut extracted = ProgramCacheForTxBatch::new(21);
2912 cache.extract(&mut missing, &mut extracted, &env, true, true);
2913 assert!(match_slot(&extracted, &program1, 0, 21));
2915 assert!(match_slot(&extracted, &program2, 11, 21));
2916 assert!(match_slot(&extracted, &program4, 15, 21));
2917
2918 let mut missing = get_entries_to_load(&cache, 27, keys);
2920 let mut extracted = ProgramCacheForTxBatch::new(27);
2921 cache.extract(&mut missing, &mut extracted, &env, true, true);
2922 assert!(match_slot(&extracted, &program1, 0, 27));
2923 assert!(match_slot(&extracted, &program2, 11, 27));
2924 assert!(match_slot(&extracted, &program3, 25, 27));
2925 assert!(match_slot(&extracted, &program4, 5, 27));
2926
2927 cache.prune(15, None, &fork_graph.read().unwrap());
2928
2929 let mut missing = get_entries_to_load(&cache, 23, keys);
2946 assert!(match_missing(&missing, &program3, false));
2947 let mut extracted = ProgramCacheForTxBatch::new(23);
2948 cache.extract(&mut missing, &mut extracted, &env, true, true);
2949 assert!(match_slot(&extracted, &program1, 0, 23));
2950 assert!(match_slot(&extracted, &program2, 11, 23));
2951 assert!(match_slot(&extracted, &program4, 15, 23));
2952 }
2953
2954 #[test]
2955 fn test_extract_using_deployment_slot() {
2956 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
2957 let env = get_mock_program_runtime_environment();
2958
2959 let mut fork_graph = TestForkGraphSpecific::default();
2975 fork_graph.insert_fork(&[0, 10, 20, 22]);
2976 fork_graph.insert_fork(&[0, 5, 11, 12, 15, 16, 18, 19, 21, 23]);
2977 fork_graph.insert_fork(&[0, 5, 11, 25, 27]);
2978
2979 let fork_graph = Arc::new(RwLock::new(fork_graph));
2980 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2981
2982 let program1 = Pubkey::new_unique();
2983 cache.assign_program(&env, program1, 0, new_test_entry(0));
2984 cache.assign_program(&env, program1, 20, new_test_entry(20));
2985
2986 let program2 = Pubkey::new_unique();
2987 cache.assign_program(&env, program2, 5, new_test_entry(5));
2988 cache.assign_program(&env, program2, 11, new_test_entry(11));
2989
2990 let program3 = Pubkey::new_unique();
2991 cache.assign_program(&env, program3, 25, new_test_entry(25));
2992
2993 let keys = &[program1, program2, program3];
2995 let mut missing = get_entries_to_load(&cache, 12, keys);
2996 assert!(match_missing(&missing, &program3, false));
2997 let mut extracted = ProgramCacheForTxBatch::new(12);
2998 cache.extract(&mut missing, &mut extracted, &env, true, true);
2999 assert!(match_slot(&extracted, &program1, 0, 12));
3000 assert!(match_slot(&extracted, &program2, 11, 12));
3001
3002 let mut missing = get_entries_to_load(&cache, 12, keys);
3005 missing.get_mut(1).unwrap().deployment_slot = 5;
3008 assert!(match_missing(&missing, &program3, false));
3009 let mut extracted = ProgramCacheForTxBatch::new(12);
3010 cache.extract(&mut missing, &mut extracted, &env, true, true);
3011 assert!(match_slot(&extracted, &program1, 0, 12));
3012 assert!(match_slot(&extracted, &program2, 5, 12));
3013 }
3014
3015 #[test]
3016 fn test_extract_rejects_entry_deployed_after_the_requested_slot() {
3017 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
3018 let env = get_mock_program_runtime_environment();
3019
3020 let mut fork_graph = TestForkGraphSpecific::default();
3021 fork_graph.insert_fork(&[0, 5, 11, 12]);
3022 let fork_graph = Arc::new(RwLock::new(fork_graph));
3023 cache.set_fork_graph(Arc::downgrade(&fork_graph));
3024
3025 let program = Pubkey::new_unique();
3027 cache.assign_program(&env, program, 11, new_test_entry(11));
3028
3029 let mut missing = vec![ProgramToLoad {
3031 program_id: &program,
3032 loader: ProgramCacheEntryOwner::LoaderV2,
3033 deployment_slot: 5,
3034 }];
3035 let mut extracted = ProgramCacheForTxBatch::new(12);
3036 cache.extract(&mut missing, &mut extracted, &env, true, true);
3037 assert!(match_missing(&missing, &program, true));
3038 assert!(extracted.find(&program).is_none());
3039
3040 let mut missing = vec![ProgramToLoad {
3042 program_id: &program,
3043 loader: ProgramCacheEntryOwner::LoaderV2,
3044 deployment_slot: 11,
3045 }];
3046 let mut extracted = ProgramCacheForTxBatch::new(12);
3047 cache.extract(&mut missing, &mut extracted, &env, true, true);
3048 assert!(match_missing(&missing, &program, false));
3049 assert!(match_slot(&extracted, &program, 11, 12));
3050 }
3051
3052 #[test]
3053 fn test_extract_unloaded() {
3054 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
3055 let env = get_mock_program_runtime_environment();
3056
3057 let mut fork_graph = TestForkGraphSpecific::default();
3073 fork_graph.insert_fork(&[0, 10, 20, 22]);
3074 fork_graph.insert_fork(&[0, 5, 11, 15, 16, 19, 21, 23]);
3075 fork_graph.insert_fork(&[0, 5, 11, 25, 27]);
3076
3077 let fork_graph = Arc::new(RwLock::new(fork_graph));
3078 cache.set_fork_graph(Arc::downgrade(&fork_graph));
3079
3080 let program1 = Pubkey::new_unique();
3081 cache.assign_program(&env, program1, 0, new_test_entry(0));
3082 cache.assign_program(&env, program1, 20, new_test_entry(20));
3083
3084 let program2 = Pubkey::new_unique();
3085 cache.assign_program(&env, program2, 5, new_test_entry(5));
3086 cache.assign_program(&env, program2, 11, new_test_entry(11));
3087
3088 let program3 = Pubkey::new_unique();
3089 let _ = insert_unloaded_entry(&mut cache, program3, 25);
3091
3092 cache.assign_program(
3096 &env,
3097 program3,
3098 20,
3099 Arc::new(
3100 new_test_entry(20)
3101 .to_unloaded()
3102 .expect("Failed to create unloaded program"),
3103 ),
3104 );
3105
3106 let keys = &[program1, program2, program3];
3108 let mut missing = get_entries_to_load(&cache, 19, keys);
3109 assert!(match_missing(&missing, &program3, false));
3110 let mut extracted = ProgramCacheForTxBatch::new(19);
3111 cache.extract(&mut missing, &mut extracted, &env, true, true);
3112 assert!(match_slot(&extracted, &program1, 0, 19));
3113 assert!(match_slot(&extracted, &program2, 11, 19));
3114
3115 let mut missing = get_entries_to_load(&cache, 27, keys);
3117 let mut extracted = ProgramCacheForTxBatch::new(27);
3118 cache.extract(&mut missing, &mut extracted, &env, true, true);
3119 assert!(match_slot(&extracted, &program1, 0, 27));
3120 assert!(match_slot(&extracted, &program2, 11, 27));
3121 assert!(match_missing(&missing, &program3, true));
3122
3123 let mut missing = get_entries_to_load(&cache, 22, keys);
3125 assert!(match_missing(&missing, &program2, false));
3126 let mut extracted = ProgramCacheForTxBatch::new(22);
3127 cache.extract(&mut missing, &mut extracted, &env, true, true);
3128 assert!(match_slot(&extracted, &program1, 20, 22));
3129 assert!(match_missing(&missing, &program3, true));
3130 }
3131
3132 #[test]
3133 fn test_extract_different_environment() {
3134 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
3135 let env = get_mock_program_runtime_environment();
3136 let other_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
3137
3138 let mut fork_graph = TestForkGraphSpecific::default();
3148 fork_graph.insert_fork(&[0, 10, 20, 22]);
3149
3150 let fork_graph = Arc::new(RwLock::new(fork_graph));
3151 cache.set_fork_graph(Arc::downgrade(&fork_graph));
3152
3153 let program1 = Pubkey::new_unique();
3154 cache.assign_program(
3155 &env,
3156 program1,
3157 10,
3158 Arc::new(ProgramCacheEntry::new_closed_tombstone(
3159 10,
3160 ProgramCacheEntryOwner::LoaderV3,
3161 )),
3162 );
3163 cache.assign_program(&env, program1, 20, new_test_entry(20));
3164
3165 let keys = &[program1];
3167 let mut missing = get_entries_to_load(&cache, 22, keys);
3168 let mut extracted = ProgramCacheForTxBatch::new(22);
3169 cache.extract(&mut missing, &mut extracted, &env, true, true);
3170 assert!(match_slot(&extracted, &program1, 20, 22));
3171
3172 let mut missing = get_entries_to_load(&cache, 22, keys);
3174 let mut extracted = ProgramCacheForTxBatch::new(22);
3175 cache.extract(&mut missing, &mut extracted, &other_env, true, true);
3176 assert!(match_missing(&missing, &program1, true));
3177 }
3178
3179 #[test_matrix((false, true))]
3180 fn test_extract_no_second_level(empty_second_level: bool) {
3181 let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
3182 let env = get_mock_program_runtime_environment();
3183 let program_id = Pubkey::new_unique();
3184 if empty_second_level {
3185 match &mut cache.index {
3187 IndexImplementation::V1 { entries, .. } => {
3188 entries.insert(program_id, Vec::new());
3189 }
3190 }
3191 }
3192
3193 let mut search_for = vec![ProgramToLoad {
3196 program_id: &program_id,
3197 loader: ProgramCacheEntryOwner::LoaderV3,
3198 deployment_slot: 0,
3199 }];
3200 let mut extracted = ProgramCacheForTxBatch::new(100);
3201 let task = cache.extract(&mut search_for, &mut extracted, &env, true, true);
3202 assert_eq!(search_for.len(), 1);
3203 assert!(extracted.entries.is_empty());
3204 assert_eq!(task, Some(program_id));
3205 }
3206
3207 #[test]
3208 fn test_extract_account_owner_mismatch() {
3209 let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
3210 let env = get_mock_program_runtime_environment();
3211 let program_id = Pubkey::new_unique();
3212 let owned_by_v2 = new_test_entry_with_owner(
3213 100,
3214 ProgramCacheEntryOwner::LoaderV2,
3215 new_loaded_entry(env.clone()),
3216 );
3217 cache.assign_program(&env, program_id, 100, Arc::clone(&owned_by_v2));
3218
3219 let mut search_for = vec![ProgramToLoad {
3222 program_id: &program_id,
3223 loader: ProgramCacheEntryOwner::LoaderV3,
3224 deployment_slot: 100,
3225 }];
3226 let mut extracted = ProgramCacheForTxBatch::new(200);
3227 cache.extract(&mut search_for, &mut extracted, &env, true, true);
3228 assert_eq!(search_for.len(), 1);
3229 assert!(extracted.entries.is_empty());
3230
3231 let owned_by_v3 = new_test_entry_with_owner(
3234 150,
3235 ProgramCacheEntryOwner::LoaderV3,
3236 new_loaded_entry(env.clone()),
3237 );
3238 cache.assign_program(&env, program_id, 150, Arc::clone(&owned_by_v3));
3239
3240 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
3242 assert_eq!(slot_versions.len(), 2);
3243 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &owned_by_v2));
3244 assert!(Arc::ptr_eq(slot_versions.get(1).unwrap(), &owned_by_v3));
3245
3246 let mut search_for = vec![ProgramToLoad {
3249 program_id: &program_id,
3250 loader: ProgramCacheEntryOwner::LoaderV2,
3251 deployment_slot: 100,
3252 }];
3253 let mut extracted = ProgramCacheForTxBatch::new(200);
3254 cache.extract(&mut search_for, &mut extracted, &env, true, true);
3255 assert!(search_for.is_empty());
3256 assert!(Arc::ptr_eq(
3257 extracted.entries.get(&program_id).unwrap(),
3258 &owned_by_v2
3259 ));
3260
3261 let mut search_for = vec![ProgramToLoad {
3264 program_id: &program_id,
3265 loader: ProgramCacheEntryOwner::LoaderV3,
3266 deployment_slot: 150,
3267 }];
3268 let mut extracted = ProgramCacheForTxBatch::new(200);
3269 cache.extract(&mut search_for, &mut extracted, &env, true, true);
3270 assert!(search_for.is_empty());
3271 assert!(Arc::ptr_eq(
3272 extracted.entries.get(&program_id).unwrap(),
3273 &owned_by_v3
3274 ));
3275 }
3276
3277 #[test]
3278 fn test_extract_environment_mismatch() {
3279 let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
3280 let env = get_mock_program_runtime_environment();
3281 let other_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
3282 let program_id = Pubkey::new_unique();
3283 let on_other_env = new_test_entry_with_owner(
3284 100,
3285 ProgramCacheEntryOwner::LoaderV3,
3286 new_loaded_entry(other_env.clone()),
3287 );
3288 cache.assign_program(&other_env, program_id, 100, Arc::clone(&on_other_env));
3289
3290 let mut search_for = vec![ProgramToLoad {
3295 program_id: &program_id,
3296 loader: ProgramCacheEntryOwner::LoaderV3,
3297 deployment_slot: 100,
3298 }];
3299 let mut extracted = ProgramCacheForTxBatch::new(200);
3300 cache.extract(&mut search_for, &mut extracted, &env, true, true);
3301 assert_eq!(search_for.len(), 1);
3302 assert!(extracted.entries.is_empty());
3303
3304 let on_execution_env = new_test_entry_with_owner(
3307 100,
3308 ProgramCacheEntryOwner::LoaderV3,
3309 new_loaded_entry(env.clone()),
3310 );
3311 cache.assign_program(&env, program_id, 100, Arc::clone(&on_execution_env));
3312
3313 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
3316 assert_eq!(slot_versions.len(), 2);
3317 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &on_other_env));
3318 assert!(Arc::ptr_eq(
3319 slot_versions.get(1).unwrap(),
3320 &on_execution_env
3321 ));
3322
3323 let mut search_for = vec![ProgramToLoad {
3326 program_id: &program_id,
3327 loader: ProgramCacheEntryOwner::LoaderV3,
3328 deployment_slot: 100,
3329 }];
3330 let mut extracted = ProgramCacheForTxBatch::new(200);
3331 cache.extract(&mut search_for, &mut extracted, &env, true, true);
3332 assert!(search_for.is_empty());
3333 assert!(Arc::ptr_eq(
3334 extracted.entries.get(&program_id).unwrap(),
3335 &on_execution_env
3336 ));
3337
3338 let mut search_for = vec![ProgramToLoad {
3341 program_id: &program_id,
3342 loader: ProgramCacheEntryOwner::LoaderV3,
3343 deployment_slot: 100,
3344 }];
3345 let mut extracted = ProgramCacheForTxBatch::new(200);
3346 cache.extract(&mut search_for, &mut extracted, &other_env, true, true);
3347 assert!(search_for.is_empty());
3348 assert!(Arc::ptr_eq(
3349 extracted.entries.get(&program_id).unwrap(),
3350 &on_other_env
3351 ));
3352 }
3353
3354 #[test]
3355 fn test_extract_unloaded_entry() {
3356 let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
3357 let env = get_mock_program_runtime_environment();
3358 let program_id = Pubkey::new_unique();
3359 let unloaded = new_test_entry_with_owner(
3360 100,
3361 ProgramCacheEntryOwner::LoaderV3,
3362 new_unloaded_entry(env.clone()),
3363 );
3364 cache.assign_program(&env, program_id, 100, Arc::clone(&unloaded));
3365
3366 let mut search_for = vec![ProgramToLoad {
3370 program_id: &program_id,
3371 loader: ProgramCacheEntryOwner::LoaderV3,
3372 deployment_slot: 100,
3373 }];
3374 let mut extracted = ProgramCacheForTxBatch::new(200);
3375 cache.extract(&mut search_for, &mut extracted, &env, true, true);
3376 assert_eq!(search_for.len(), 1);
3377 assert!(extracted.entries.is_empty());
3378
3379 let loaded = new_test_entry_with_owner(
3381 100,
3382 ProgramCacheEntryOwner::LoaderV3,
3383 new_loaded_entry(env.clone()),
3384 );
3385 cache.assign_program(&env, program_id, 100, Arc::clone(&loaded));
3386 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
3387 assert_eq!(slot_versions.len(), 1);
3388 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &loaded));
3389
3390 let mut search_for = vec![ProgramToLoad {
3392 program_id: &program_id,
3393 loader: ProgramCacheEntryOwner::LoaderV3,
3394 deployment_slot: 100,
3395 }];
3396 let mut extracted = ProgramCacheForTxBatch::new(200);
3397 cache.extract(&mut search_for, &mut extracted, &env, true, true);
3398 assert!(search_for.is_empty());
3399 assert!(Arc::ptr_eq(
3400 extracted.entries.get(&program_id).unwrap(),
3401 &loaded
3402 ));
3403 }
3404
3405 #[test_matrix(
3406 (
3407 new_closed_entry,
3408 new_builtin_entry,
3409 new_failed_verification_entry,
3410 new_unloaded_entry,
3411 new_loaded_entry,
3412 ),
3413 (100, 101)
3414 )]
3415 fn test_extract_effective_slot(
3416 new_program: fn(ProgramRuntimeEnvironment) -> ProgramCacheEntryType,
3417 batch_slot: Slot,
3418 ) {
3419 let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
3433 let env = get_mock_program_runtime_environment();
3434 let program_id = Pubkey::new_unique();
3435 let entry = new_test_entry_with_owner(
3436 100,
3437 ProgramCacheEntryOwner::LoaderV3,
3438 new_program(env.clone()),
3439 );
3440 cache.assign_program(&env, program_id, 100, Arc::clone(&entry));
3441
3442 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
3443 assert_eq!(slot_versions.len(), 1);
3444 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &entry));
3445
3446 let mut search_for = vec![ProgramToLoad {
3447 program_id: &program_id,
3448 loader: ProgramCacheEntryOwner::LoaderV3,
3449 deployment_slot: 100,
3450 }];
3451 let mut extracted = ProgramCacheForTxBatch::new(batch_slot);
3452 cache.extract(&mut search_for, &mut extracted, &env, true, true);
3453
3454 if batch_slot < entry.effective_slot() {
3455 assert!(search_for.is_empty());
3459 let tombstone = extracted.entries.get(&program_id).unwrap();
3460 assert_matches!(tombstone.program, ProgramCacheEntryType::DelayVisibility);
3461 assert_eq!(tombstone.account_owner, ProgramCacheEntryOwner::LoaderV3);
3462 assert_eq!(tombstone.deployment_slot, 100);
3463 assert!(Arc::ptr_eq(&tombstone.stats, &entry.stats)); assert_eq!(entry.stats.uses.load(Ordering::Relaxed), 1);
3465
3466 assert_eq!(entry.latest_access_slot.load(Ordering::Relaxed), batch_slot);
3470 assert_eq!(tombstone.latest_access_slot.load(Ordering::Relaxed), 0);
3471 } else if matches!(entry.program, ProgramCacheEntryType::Unloaded(_)) {
3472 assert!(extracted.entries.is_empty());
3476 assert_eq!(search_for.len(), 1);
3477 assert_eq!(entry.stats.uses.load(Ordering::Relaxed), 0);
3478 assert_eq!(entry.latest_access_slot.load(Ordering::Relaxed), 0);
3479 } else {
3480 assert!(search_for.is_empty());
3483 assert!(Arc::ptr_eq(
3484 extracted.entries.get(&program_id).unwrap(),
3485 &entry
3486 ));
3487 assert_eq!(entry.stats.uses.load(Ordering::Relaxed), 1);
3488 assert_eq!(entry.latest_access_slot.load(Ordering::Relaxed), batch_slot);
3489 }
3490 }
3491
3492 #[test]
3493 fn test_extract_closed_entry_matches_any_env() {
3494 let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
3504 let env = get_mock_program_runtime_environment();
3505 let other_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
3506 let program_id = Pubkey::new_unique();
3507 let closed = new_test_entry_with_owner(
3508 100,
3509 ProgramCacheEntryOwner::LoaderV3,
3510 new_closed_entry(env.clone()), );
3512 assert_eq!(closed.effective_slot(), closed.deployment_slot);
3513 cache.assign_program(&env, program_id, 100, Arc::clone(&closed));
3514
3515 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
3516 assert_eq!(slot_versions.len(), 1);
3517 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &closed));
3518
3519 let mut search_for = vec![ProgramToLoad {
3521 program_id: &program_id,
3522 loader: ProgramCacheEntryOwner::LoaderV3,
3523 deployment_slot: 100,
3524 }];
3525 let mut extracted = ProgramCacheForTxBatch::new(100);
3526 cache.extract(&mut search_for, &mut extracted, &env, true, true);
3527 assert!(Arc::ptr_eq(
3528 extracted.entries.get(&program_id).unwrap(),
3529 &closed
3530 ));
3531 assert!(search_for.is_empty());
3532
3533 let mut search_for = vec![ProgramToLoad {
3535 program_id: &program_id,
3536 loader: ProgramCacheEntryOwner::LoaderV3,
3537 deployment_slot: 100,
3538 }];
3539 let mut extracted = ProgramCacheForTxBatch::new(100);
3540 cache.extract(&mut search_for, &mut extracted, &other_env, true, true);
3541 assert!(Arc::ptr_eq(
3542 extracted.entries.get(&program_id).unwrap(),
3543 &closed
3544 ));
3545 assert!(search_for.is_empty());
3546 }
3547
3548 #[test]
3549 fn test_extract_delay_visibility_tombstone_interleaved_environments() {
3550 let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
3573 let env = get_mock_program_runtime_environment();
3574 let upcoming_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
3575 let program_id = Pubkey::new_unique();
3576 let on_execution_env = new_test_entry_with_owner(
3577 100,
3578 ProgramCacheEntryOwner::LoaderV3,
3579 new_loaded_entry(env.clone()),
3580 );
3581 let on_upcoming_env = new_test_entry_with_owner(
3582 100,
3583 ProgramCacheEntryOwner::LoaderV3,
3584 new_loaded_entry(upcoming_env.clone()),
3585 );
3586 cache.assign_program(&env, program_id, 100, Arc::clone(&on_execution_env));
3587 cache.assign_program(&upcoming_env, program_id, 100, Arc::clone(&on_upcoming_env));
3588
3589 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
3590 assert_eq!(slot_versions.len(), 2);
3591 assert!(Arc::ptr_eq(
3592 slot_versions.first().unwrap(),
3593 &on_execution_env
3594 ));
3595 assert!(Arc::ptr_eq(slot_versions.get(1).unwrap(), &on_upcoming_env));
3596
3597 let mut search_for = vec![ProgramToLoad {
3599 program_id: &program_id,
3600 loader: ProgramCacheEntryOwner::LoaderV3,
3601 deployment_slot: 100,
3602 }];
3603 let mut extracted = ProgramCacheForTxBatch::new(100);
3604 cache.extract(&mut search_for, &mut extracted, &env, true, true);
3605 assert!(search_for.is_empty());
3606
3607 let tombstone = extracted.entries.get(&program_id).unwrap();
3609 assert_matches!(tombstone.program, ProgramCacheEntryType::DelayVisibility);
3610
3611 assert!(Arc::ptr_eq(&tombstone.stats, &on_upcoming_env.stats));
3619 assert_eq!(on_upcoming_env.stats.uses.load(Ordering::Relaxed), 1);
3620 assert_eq!(on_execution_env.stats.uses.load(Ordering::Relaxed), 0);
3621
3622 let mut search_for = vec![ProgramToLoad {
3625 program_id: &program_id,
3626 loader: ProgramCacheEntryOwner::LoaderV3,
3627 deployment_slot: 100,
3628 }];
3629 let mut extracted = ProgramCacheForTxBatch::new(101);
3630 cache.extract(&mut search_for, &mut extracted, &env, true, true);
3631 assert!(search_for.is_empty());
3632 assert!(Arc::ptr_eq(
3633 extracted.entries.get(&program_id).unwrap(),
3634 &on_execution_env
3635 ));
3636
3637 assert_eq!(on_upcoming_env.stats.uses.load(Ordering::Relaxed), 1);
3639 assert_eq!(on_execution_env.stats.uses.load(Ordering::Relaxed), 1);
3640 }
3641
3642 #[test_case(false)]
3643 #[test_case(true)]
3644 fn test_extract_environment_filter_same_slot(other_env_first: bool) {
3645 let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
3646 let env = get_mock_program_runtime_environment();
3647 let other_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
3648 let program_id = Pubkey::new_unique();
3649
3650 let on_other_env = new_test_entry_with_owner(
3652 100,
3653 ProgramCacheEntryOwner::LoaderV3,
3654 new_loaded_entry(other_env.clone()),
3655 );
3656 let on_execution_env = new_test_entry_with_owner(
3657 100,
3658 ProgramCacheEntryOwner::LoaderV3,
3659 new_loaded_entry(env.clone()),
3660 );
3661 if other_env_first {
3662 cache.assign_program(&other_env, program_id, 100, Arc::clone(&on_other_env));
3663 cache.assign_program(&env, program_id, 100, Arc::clone(&on_execution_env));
3664 } else {
3665 cache.assign_program(&env, program_id, 100, Arc::clone(&on_execution_env));
3666 cache.assign_program(&other_env, program_id, 100, Arc::clone(&on_other_env));
3667 }
3668
3669 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
3672 assert_eq!(slot_versions.len(), 2);
3673 let (first, second) = if other_env_first {
3674 (&on_other_env, &on_execution_env)
3675 } else {
3676 (&on_execution_env, &on_other_env)
3677 };
3678 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), first));
3679 assert!(Arc::ptr_eq(slot_versions.get(1).unwrap(), second));
3680
3681 let mut search_for = vec![ProgramToLoad {
3684 program_id: &program_id,
3685 loader: ProgramCacheEntryOwner::LoaderV3,
3686 deployment_slot: 100,
3687 }];
3688 let mut extracted = ProgramCacheForTxBatch::new(200);
3689 cache.extract(&mut search_for, &mut extracted, &env, true, true);
3690 assert!(Arc::ptr_eq(
3691 extracted.entries.get(&program_id).unwrap(),
3692 &on_execution_env
3693 ));
3694
3695 let mut search_for = vec![ProgramToLoad {
3697 program_id: &program_id,
3698 loader: ProgramCacheEntryOwner::LoaderV3,
3699 deployment_slot: 100,
3700 }];
3701 let mut extracted = ProgramCacheForTxBatch::new(200);
3702 cache.extract(&mut search_for, &mut extracted, &other_env, true, true);
3703 assert!(Arc::ptr_eq(
3704 extracted.entries.get(&program_id).unwrap(),
3705 &on_other_env
3706 ));
3707 }
3708
3709 #[test_matrix((false, true))]
3710 fn test_extract_usage_counter(increment_usage_counter: bool) {
3711 let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
3712 let env = get_mock_program_runtime_environment();
3713 let program_id = Pubkey::new_unique();
3714 let entry = new_test_entry_with_owner(
3715 100,
3716 ProgramCacheEntryOwner::LoaderV3,
3717 new_loaded_entry(env.clone()),
3718 );
3719 cache.assign_program(&env, program_id, 100, Arc::clone(&entry));
3720
3721 let mut search_for = vec![ProgramToLoad {
3722 program_id: &program_id,
3723 loader: ProgramCacheEntryOwner::LoaderV3,
3724 deployment_slot: 100,
3725 }];
3726 let mut extracted = ProgramCacheForTxBatch::new(200);
3727 cache.extract(
3728 &mut search_for,
3729 &mut extracted,
3730 &env,
3731 increment_usage_counter,
3732 true,
3733 );
3734
3735 assert_eq!(entry.latest_access_slot.load(Ordering::Relaxed), 200);
3737 assert_eq!(
3738 entry.stats.uses.load(Ordering::Relaxed),
3739 u64::from(increment_usage_counter)
3740 );
3741 }
3742
3743 #[test]
3744 fn test_extract_usage_counter_delayed_visibility() {
3745 let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
3746 let env = get_mock_program_runtime_environment();
3747 let program_id = Pubkey::new_unique();
3748 let entry = new_test_entry_with_owner(
3749 100,
3750 ProgramCacheEntryOwner::LoaderV3,
3751 new_loaded_entry(env.clone()),
3752 );
3753 cache.assign_program(&env, program_id, 100, Arc::clone(&entry));
3754
3755 let mut search_for = vec![ProgramToLoad {
3759 program_id: &program_id,
3760 loader: ProgramCacheEntryOwner::LoaderV3,
3761 deployment_slot: 100,
3762 }];
3763 let mut extracted = ProgramCacheForTxBatch::new(100);
3764 cache.extract(&mut search_for, &mut extracted, &env, true, true);
3765
3766 let tombstone = extracted.entries.get(&program_id).unwrap();
3767 assert!(matches!(
3768 tombstone.program,
3769 ProgramCacheEntryType::DelayVisibility
3770 ));
3771 assert!(!Arc::ptr_eq(tombstone, &entry));
3772
3773 assert_eq!(entry.latest_access_slot.load(Ordering::Relaxed), 100);
3776 assert_eq!(tombstone.latest_access_slot.load(Ordering::Relaxed), 0);
3777
3778 assert!(Arc::ptr_eq(&tombstone.stats, &entry.stats));
3781 assert_eq!(entry.stats.uses.load(Ordering::Relaxed), 1);
3782 }
3783
3784 #[test_matrix((false, true))]
3785 fn test_extract_hits_and_misses(count_hits_and_misses: bool) {
3786 let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
3787 let env = get_mock_program_runtime_environment();
3788 let found = Pubkey::new_unique();
3789 let missing = Pubkey::new_unique();
3790 cache.assign_program(
3791 &env,
3792 found,
3793 100,
3794 new_test_entry_with_owner(
3795 100,
3796 ProgramCacheEntryOwner::LoaderV3,
3797 new_loaded_entry(env.clone()),
3798 ),
3799 );
3800
3801 let mut search_for = vec![
3802 ProgramToLoad {
3803 program_id: &found,
3804 loader: ProgramCacheEntryOwner::LoaderV3,
3805 deployment_slot: 100,
3806 },
3807 ProgramToLoad {
3808 program_id: &missing,
3809 loader: ProgramCacheEntryOwner::LoaderV3,
3810 deployment_slot: 0,
3811 },
3812 ];
3813 let mut extracted = ProgramCacheForTxBatch::new(200);
3814 cache.extract(
3815 &mut search_for,
3816 &mut extracted,
3817 &env,
3818 true,
3819 count_hits_and_misses,
3820 );
3821
3822 let expected = u64::from(count_hits_and_misses);
3823 assert_eq!(cache.stats.hits.load(Ordering::Relaxed), expected);
3824 assert_eq!(cache.stats.misses.load(Ordering::Relaxed), expected);
3825 }
3826
3827 #[test]
3828 fn test_extract_hits_count_only_this_call() {
3829 let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
3830 let env = get_mock_program_runtime_environment();
3831 let program_id = Pubkey::new_unique();
3832 cache.assign_program(
3833 &env,
3834 program_id,
3835 100,
3836 new_test_entry_with_owner(
3837 100,
3838 ProgramCacheEntryOwner::LoaderV3,
3839 new_loaded_entry(env.clone()),
3840 ),
3841 );
3842
3843 let mut extracted = ProgramCacheForTxBatch::new(200);
3846 extracted.replenish(Pubkey::new_unique(), new_test_builtin_entry(0));
3847
3848 let mut search_for = vec![ProgramToLoad {
3851 program_id: &program_id,
3852 loader: ProgramCacheEntryOwner::LoaderV3,
3853 deployment_slot: 100,
3854 }];
3855 cache.extract(&mut search_for, &mut extracted, &env, true, true);
3856 assert_eq!(extracted.entries.len(), 2);
3857 assert_eq!(cache.stats.hits.load(Ordering::Relaxed), 1);
3858 }
3859
3860 #[test]
3861 fn test_extract_cooperative_loading_task() {
3862 let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
3863 let env = get_mock_program_runtime_environment();
3864 let program_ids = [Pubkey::new_unique(), Pubkey::new_unique()];
3865
3866 let mut search_for = program_ids
3868 .iter()
3869 .map(|program_id| ProgramToLoad {
3870 program_id,
3871 loader: ProgramCacheEntryOwner::LoaderV3,
3872 deployment_slot: 0,
3873 })
3874 .collect();
3875 let mut extracted = ProgramCacheForTxBatch::new(100);
3876 let task = cache.extract(&mut search_for, &mut extracted, &env, true, true);
3877 assert_eq!(search_for.len(), 2);
3878 assert_eq!(task, program_ids.first().copied());
3879 match &cache.index {
3880 IndexImplementation::V1 {
3881 loading_entries, ..
3882 } => {
3883 let loading_entries = loading_entries.lock().unwrap();
3884 assert_eq!(loading_entries.len(), 1);
3885 assert_eq!(
3886 loading_entries.get(program_ids.first().unwrap()),
3887 Some(&(100, thread::current().id()))
3888 );
3889 }
3890 }
3891
3892 let mut search_for = vec![ProgramToLoad {
3894 program_id: program_ids.first().unwrap(),
3895 loader: ProgramCacheEntryOwner::LoaderV3,
3896 deployment_slot: 0,
3897 }];
3898 let task = cache.extract(&mut search_for, &mut extracted, &env, true, true);
3899 assert_eq!(search_for.len(), 1);
3900 assert_eq!(task, None);
3901
3902 let cookie = cache.loading_task_waiter.cookie();
3904 let loaded = new_test_entry_with_owner(
3905 50,
3906 ProgramCacheEntryOwner::LoaderV3,
3907 new_loaded_entry(env.clone()),
3908 );
3909 cache.finish_cooperative_loading_task(
3910 &env,
3911 100,
3912 *program_ids.first().unwrap(),
3913 Arc::clone(&loaded),
3914 );
3915 assert_ne!(cache.loading_task_waiter.wait(cookie), cookie);
3916
3917 match &cache.index {
3919 IndexImplementation::V1 {
3920 loading_entries, ..
3921 } => assert!(loading_entries.lock().unwrap().is_empty()),
3922 }
3923 let mut search_for = vec![ProgramToLoad {
3924 program_id: program_ids.first().unwrap(),
3925 loader: ProgramCacheEntryOwner::LoaderV3,
3926 deployment_slot: 50,
3927 }];
3928 let mut extracted = ProgramCacheForTxBatch::new(100);
3929 let task = cache.extract(&mut search_for, &mut extracted, &env, true, true);
3930 assert!(search_for.is_empty());
3931 assert_eq!(task, None);
3932 assert!(Arc::ptr_eq(
3933 extracted.entries.get(program_ids.first().unwrap()).unwrap(),
3934 &loaded
3935 ));
3936 }
3937
3938 #[test]
3939 fn test_extract_cooperative_loading_task_ordering() {
3940 let (cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
3941 let env = get_mock_program_runtime_environment();
3942 let program_ids = [Pubkey::new_unique(), Pubkey::new_unique()];
3943 let mut extracted = ProgramCacheForTxBatch::new(100);
3944
3945 let mut search_for = program_ids
3947 .iter()
3948 .map(|program_id| ProgramToLoad {
3949 program_id,
3950 loader: ProgramCacheEntryOwner::LoaderV3,
3951 deployment_slot: 0,
3952 })
3953 .collect();
3954 let task = cache.extract(&mut search_for, &mut extracted, &env, true, true);
3955 assert_eq!(task, program_ids.first().copied());
3956
3957 let mut search_for = program_ids
3960 .iter()
3961 .rev()
3962 .map(|program_id| ProgramToLoad {
3963 program_id,
3964 loader: ProgramCacheEntryOwner::LoaderV3,
3965 deployment_slot: 0,
3966 })
3967 .collect();
3968 let task = cache.extract(&mut search_for, &mut extracted, &env, true, true);
3969 assert_eq!(task, program_ids.get(1).copied());
3970
3971 match &cache.index {
3973 IndexImplementation::V1 {
3974 loading_entries, ..
3975 } => {
3976 let loading_entries = loading_entries.lock().unwrap();
3977 assert_eq!(loading_entries.len(), 2);
3978 for program_id in &program_ids {
3979 assert_eq!(
3980 loading_entries.get(program_id),
3981 Some(&(100, thread::current().id()))
3982 );
3983 }
3984 }
3985 }
3986
3987 let mut search_for = program_ids
3989 .iter()
3990 .map(|program_id| ProgramToLoad {
3991 program_id,
3992 loader: ProgramCacheEntryOwner::LoaderV3,
3993 deployment_slot: 0,
3994 })
3995 .collect();
3996 let task = cache.extract(&mut search_for, &mut extracted, &env, true, true);
3997 assert_eq!(search_for.len(), 2);
3998 assert_eq!(task, None);
3999 }
4000
4001 #[test]
4002 fn test_extract_entry_not_in_same_branch() {
4003 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
4010 let mut fork_graph = TestForkGraphSpecific::default();
4011 fork_graph.insert_fork(&[0, 50, 200]);
4012 fork_graph.insert_fork(&[0, 100]);
4013 let fork_graph = Arc::new(RwLock::new(fork_graph));
4014 cache.set_fork_graph(Arc::downgrade(&fork_graph));
4015
4016 let env = get_mock_program_runtime_environment();
4017 let program_id = Pubkey::new_unique();
4018 let on_other_fork = new_test_entry_with_owner(
4019 100,
4020 ProgramCacheEntryOwner::LoaderV3,
4021 new_loaded_entry(env.clone()),
4022 );
4023 cache.assign_program(&env, program_id, 100, Arc::clone(&on_other_fork));
4024
4025 let mut search_for = vec![ProgramToLoad {
4035 program_id: &program_id,
4036 loader: ProgramCacheEntryOwner::LoaderV3,
4037 deployment_slot: 100,
4038 }];
4039 let mut extracted = ProgramCacheForTxBatch::new(200);
4040 cache.extract(&mut search_for, &mut extracted, &env, true, true);
4041 assert_eq!(search_for.len(), 1);
4042 assert!(extracted.entries.is_empty());
4043
4044 let on_same_fork = new_test_entry_with_owner(
4046 50,
4047 ProgramCacheEntryOwner::LoaderV3,
4048 new_loaded_entry(env.clone()),
4049 );
4050 cache.assign_program(&env, program_id, 50, Arc::clone(&on_same_fork));
4051
4052 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
4054 assert_eq!(slot_versions.len(), 2);
4055 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &on_same_fork));
4056 assert!(Arc::ptr_eq(slot_versions.get(1).unwrap(), &on_other_fork));
4057
4058 let mut search_for = vec![ProgramToLoad {
4061 program_id: &program_id,
4062 loader: ProgramCacheEntryOwner::LoaderV3,
4063 deployment_slot: 50,
4064 }];
4065 let mut extracted = ProgramCacheForTxBatch::new(200);
4066 cache.extract(&mut search_for, &mut extracted, &env, true, true);
4067 assert!(search_for.is_empty());
4068 assert!(Arc::ptr_eq(
4069 extracted.entries.get(&program_id).unwrap(),
4070 &on_same_fork
4071 ));
4072 }
4073
4074 #[test]
4075 fn test_extract_deployment_slot_mismatch() {
4076 let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
4092 assert_eq!(cache.latest_root_slot, 0);
4093 let env = get_mock_program_runtime_environment();
4094 let program_id = Pubkey::new_unique();
4095 let deployed_at_100 = new_test_entry_with_owner(
4096 100,
4097 ProgramCacheEntryOwner::LoaderV3,
4098 new_loaded_entry(env.clone()),
4099 );
4100 cache.assign_program(&env, program_id, 100, Arc::clone(&deployed_at_100));
4101
4102 let mut search_for = vec![ProgramToLoad {
4106 program_id: &program_id,
4107 loader: ProgramCacheEntryOwner::LoaderV3,
4108 deployment_slot: 150,
4109 }];
4110 let mut extracted = ProgramCacheForTxBatch::new(200);
4111 cache.extract(&mut search_for, &mut extracted, &env, true, true);
4112 assert_eq!(search_for.len(), 1);
4113 assert!(extracted.entries.is_empty());
4114
4115 let deployed_at_150 = new_test_entry_with_owner(
4117 150,
4118 ProgramCacheEntryOwner::LoaderV3,
4119 new_loaded_entry(env.clone()),
4120 );
4121 cache.assign_program(&env, program_id, 150, Arc::clone(&deployed_at_150));
4122
4123 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
4126 assert_eq!(slot_versions.len(), 2);
4127 assert!(Arc::ptr_eq(
4128 slot_versions.first().unwrap(),
4129 &deployed_at_100
4130 ));
4131 assert!(Arc::ptr_eq(slot_versions.get(1).unwrap(), &deployed_at_150));
4132
4133 let mut search_for = vec![ProgramToLoad {
4135 program_id: &program_id,
4136 loader: ProgramCacheEntryOwner::LoaderV3,
4137 deployment_slot: 150,
4138 }];
4139 let mut extracted = ProgramCacheForTxBatch::new(200);
4140 cache.extract(&mut search_for, &mut extracted, &env, true, true);
4141 assert!(search_for.is_empty());
4142 assert!(Arc::ptr_eq(
4143 extracted.entries.get(&program_id).unwrap(),
4144 &deployed_at_150
4145 ));
4146 }
4147
4148 #[test]
4149 fn test_extract_older_entry_on_the_callers_fork() {
4150 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
4157 let mut fork_graph = TestForkGraphSpecific::default();
4158 fork_graph.insert_fork(&[0, 50, 200]);
4159 fork_graph.insert_fork(&[0, 150]);
4160 let fork_graph = Arc::new(RwLock::new(fork_graph));
4161 cache.set_fork_graph(Arc::downgrade(&fork_graph));
4162
4163 let env = get_mock_program_runtime_environment();
4164 let program_id = Pubkey::new_unique();
4165 let on_other_fork = new_test_entry_with_owner(
4166 150,
4167 ProgramCacheEntryOwner::LoaderV3,
4168 new_loaded_entry(env.clone()),
4169 );
4170 let on_same_fork = new_test_entry_with_owner(
4171 50,
4172 ProgramCacheEntryOwner::LoaderV3,
4173 new_loaded_entry(env.clone()),
4174 );
4175 cache.assign_program(&env, program_id, 150, Arc::clone(&on_other_fork));
4176 cache.assign_program(&env, program_id, 50, Arc::clone(&on_same_fork));
4177
4178 let mut search_for = vec![ProgramToLoad {
4182 program_id: &program_id,
4183 loader: ProgramCacheEntryOwner::LoaderV3,
4184 deployment_slot: 50,
4185 }];
4186 let mut extracted = ProgramCacheForTxBatch::new(200);
4187 cache.extract(&mut search_for, &mut extracted, &env, true, true);
4188 assert!(Arc::ptr_eq(
4189 extracted.entries.get(&program_id).unwrap(),
4190 &on_same_fork
4191 ));
4192
4193 let mut search_for = vec![ProgramToLoad {
4205 program_id: &program_id,
4206 loader: ProgramCacheEntryOwner::LoaderV3,
4207 deployment_slot: 150,
4208 }];
4209 let mut extracted = ProgramCacheForTxBatch::new(200);
4210 cache.extract(&mut search_for, &mut extracted, &env, true, true);
4211 assert_eq!(search_for.len(), 1);
4212 assert!(extracted.entries.is_empty());
4213 }
4214
4215 #[test]
4216 fn test_extract_below_deployment_slot() {
4217 let (mut cache, fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
4218 let env = get_mock_program_runtime_environment();
4219 let program_id = Pubkey::new_unique();
4220 let entry = new_test_entry_with_owner(
4221 100,
4222 ProgramCacheEntryOwner::LoaderV3,
4223 new_loaded_entry(env.clone()),
4224 );
4225 cache.assign_program(&env, program_id, 100, Arc::clone(&entry));
4226
4227 cache.prune(200, None, &fork_graph.read().unwrap());
4230 assert_eq!(cache.latest_root_slot, 200);
4231
4232 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
4234 assert_eq!(slot_versions.len(), 1);
4235 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &entry));
4236
4237 fork_graph.write().unwrap().relation = BlockRelation::Unknown;
4241
4242 let mut search_for = vec![ProgramToLoad {
4245 program_id: &program_id,
4246 loader: ProgramCacheEntryOwner::LoaderV3,
4247 deployment_slot: 0,
4248 }];
4249 let mut extracted = ProgramCacheForTxBatch::new(50);
4250 cache.extract(&mut search_for, &mut extracted, &env, true, true);
4251 assert_eq!(search_for.len(), 1);
4252 assert!(extracted.entries.is_empty());
4253 }
4254
4255 #[test]
4256 fn test_extract_entry_older_than_root() {
4257 let (mut cache, fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
4260 let env = get_mock_program_runtime_environment();
4261 let program_id = Pubkey::new_unique();
4262 let entry = new_test_entry_with_owner(
4263 100,
4264 ProgramCacheEntryOwner::LoaderV3,
4265 new_loaded_entry(env.clone()),
4266 );
4267 cache.assign_program(&env, program_id, 100, Arc::clone(&entry));
4268
4269 cache.prune(200, None, &fork_graph.read().unwrap());
4272 assert_eq!(cache.latest_root_slot, 200);
4273
4274 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
4276 assert_eq!(slot_versions.len(), 1);
4277 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &entry));
4278
4279 fork_graph.write().unwrap().relation = BlockRelation::Unknown;
4283
4284 let mut search_for = vec![ProgramToLoad {
4289 program_id: &program_id,
4290 loader: ProgramCacheEntryOwner::LoaderV3,
4291 deployment_slot: 100,
4292 }];
4293 let mut extracted = ProgramCacheForTxBatch::new(300);
4294 cache.extract(&mut search_for, &mut extracted, &env, true, true);
4295 assert!(search_for.is_empty());
4296 assert!(Arc::ptr_eq(
4297 extracted.entries.get(&program_id).unwrap(),
4298 &entry
4299 ));
4300 }
4301
4302 #[test]
4303 fn test_unloaded() {
4304 let mut cache = ProgramCache::<TestForkGraph>::new(0);
4305 let env = get_mock_program_runtime_environment();
4306 for program_cache_entry_type in [
4307 ProgramCacheEntryType::Closed,
4308 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
4309 ] {
4310 let entry = Arc::new(ProgramCacheEntry {
4311 program: program_cache_entry_type,
4312 account_owner: ProgramCacheEntryOwner::LoaderV2,
4313 deployment_slot: 0,
4314 stats: Arc::default(),
4315 latest_access_slot: AtomicU64::default(),
4316 });
4317 assert!(entry.to_unloaded().is_none());
4318
4319 let program_id = Pubkey::new_unique();
4321 cache.assign_program(&env, program_id, entry.deployment_slot, entry.clone());
4322 cache.unload_program_entry(program_id, &entry);
4323 assert_eq!(cache.get_slot_versions_for_tests(&program_id).len(), 1);
4324 assert!(cache.stats.evictions.is_empty());
4325 }
4326
4327 let stats = ProgramStatistics {
4328 uses: 3.into(),
4329 ..Default::default()
4330 };
4331 let entry = new_test_entry_with_usage(1, stats);
4332 let unloaded_entry = entry.to_unloaded().unwrap();
4333 assert_eq!(unloaded_entry.deployment_slot, 1);
4334 assert_eq!(unloaded_entry.effective_slot(), 2);
4335 assert_eq!(unloaded_entry.latest_access_slot.load(Ordering::Relaxed), 1);
4336 assert_eq!(unloaded_entry.stats.uses.load(Ordering::Relaxed), 3);
4337
4338 let program_id = Pubkey::new_unique();
4340 cache.assign_program(&env, program_id, entry.deployment_slot, entry.clone());
4341 cache.unload_program_entry(program_id, &entry);
4342 assert!(cache.stats.evictions.contains_key(&program_id));
4343 }
4344
4345 #[test]
4346 fn test_fork_prune_find_first_ancestor() {
4347 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
4348 let env = get_mock_program_runtime_environment();
4349
4350 let mut fork_graph = TestForkGraphSpecific::default();
4361 fork_graph.insert_fork(&[0, 10, 20]);
4362 fork_graph.insert_fork(&[0, 5]);
4363 let fork_graph = Arc::new(RwLock::new(fork_graph));
4364 cache.set_fork_graph(Arc::downgrade(&fork_graph));
4365
4366 let program1 = Pubkey::new_unique();
4367 cache.assign_program(&env, program1, 0, new_test_entry(0));
4368 cache.assign_program(&env, program1, 5, new_test_entry(5));
4369
4370 cache.prune(10, None, &fork_graph.read().unwrap());
4371
4372 let keys = &[program1];
4373 let mut missing = get_entries_to_load(&cache, 20, keys);
4374 let mut extracted = ProgramCacheForTxBatch::new(20);
4375 cache.extract(&mut missing, &mut extracted, &env, true, true);
4376
4377 assert_eq!(
4379 extracted
4380 .find(&program1)
4381 .expect("Did not find the program")
4382 .deployment_slot,
4383 0
4384 );
4385 }
4386
4387 #[test]
4388 fn test_prune_by_deployment_slot() {
4389 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
4390 let env = get_mock_program_runtime_environment();
4391
4392 let mut fork_graph = TestForkGraphSpecific::default();
4403 fork_graph.insert_fork(&[0, 10, 20]);
4404 fork_graph.insert_fork(&[0, 5, 6]);
4405 let fork_graph = Arc::new(RwLock::new(fork_graph));
4406 cache.set_fork_graph(Arc::downgrade(&fork_graph));
4407
4408 let program1 = Pubkey::new_unique();
4409 cache.assign_program(&env, program1, 0, new_test_entry(0));
4410 cache.assign_program(&env, program1, 5, new_test_entry(5));
4411
4412 let program2 = Pubkey::new_unique();
4413 cache.assign_program(&env, program2, 10, new_test_entry(10));
4414
4415 let keys = &[program1, program2];
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, 5, 6));
4427
4428 cache.prune_by_deployment_slot(5);
4431
4432 let mut missing = get_entries_to_load(&cache, 20, keys);
4433 let mut extracted = ProgramCacheForTxBatch::new(20);
4434 cache.extract(&mut missing, &mut extracted, &env, true, true);
4435 assert!(match_slot(&extracted, &program1, 0, 20));
4436 assert!(match_slot(&extracted, &program2, 10, 20));
4437
4438 let mut missing = get_entries_to_load(&cache, 6, keys);
4439 assert!(match_missing(&missing, &program2, false));
4440 let mut extracted = ProgramCacheForTxBatch::new(6);
4441 cache.extract(&mut missing, &mut extracted, &env, true, true);
4442 assert!(match_slot(&extracted, &program1, 0, 6));
4443
4444 cache.prune_by_deployment_slot(10);
4447
4448 let mut missing = get_entries_to_load(&cache, 20, keys);
4449 assert!(match_missing(&missing, &program2, false));
4450 let mut extracted = ProgramCacheForTxBatch::new(20);
4451 cache.extract(&mut missing, &mut extracted, &env, true, true);
4452 assert!(match_slot(&extracted, &program1, 0, 20));
4453 }
4454}