1use {
2 crate::{
3 invoke_context::InvokeContext,
4 loading_task::LoadingTaskWaiter,
5 program_cache_entry::{
6 ProgramCacheEntry, ProgramCacheEntryOwner, ProgramCacheEntryType, retention_score,
7 },
8 program_metrics::{EMA_SCALE, ProgramCacheStats},
9 },
10 log::error,
11 solana_clock::{Epoch, Slot},
12 solana_pubkey::Pubkey,
13 solana_sbpf::program::BuiltinProgram,
14 solana_svm_type_overrides::{
15 rand::{Rng, rng},
16 sync::{Arc, Mutex, RwLock, atomic::Ordering},
17 thread,
18 },
19 std::{
20 collections::{HashMap, hash_map::Entry},
21 sync::Weak,
22 },
23};
24
25#[repr(transparent)]
26#[derive(Clone, Debug)]
27pub struct ProgramRuntimeEnvironment(Arc<BuiltinProgram<InvokeContext<'static, 'static>>>);
28impl std::hash::Hash for ProgramRuntimeEnvironment {
29 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
30 Arc::<BuiltinProgram<InvokeContext<'static, 'static>>>::as_ptr(&self.0).hash(state);
31 }
32}
33impl PartialEq for ProgramRuntimeEnvironment {
34 fn eq(&self, other: &Self) -> bool {
35 Arc::ptr_eq(&self.0, &other.0)
36 }
37}
38impl Eq for ProgramRuntimeEnvironment {}
39impl std::ops::Deref for ProgramRuntimeEnvironment {
40 type Target = Arc<BuiltinProgram<InvokeContext<'static, 'static>>>;
41
42 fn deref(&self) -> &Self::Target {
43 &self.0
44 }
45}
46impl ProgramRuntimeEnvironment {
47 pub fn from(inner: BuiltinProgram<InvokeContext<'static, 'static>>) -> Self {
48 Self(Arc::new(inner))
49 }
50
51 pub const fn from_ref<'a>(
52 inner: &'a Arc<BuiltinProgram<InvokeContext<'static, 'static>>>,
53 ) -> &'a Self {
54 unsafe { std::mem::transmute(inner) }
56 }
57}
58
59pub struct ProgramRuntimeEnvironments {
67 execution: ProgramRuntimeEnvironment,
70 deployment: ProgramRuntimeEnvironment,
73}
74
75impl ProgramRuntimeEnvironments {
76 pub fn new(
79 execution: ProgramRuntimeEnvironment,
80 deployment: ProgramRuntimeEnvironment,
81 ) -> Self {
82 Self {
83 execution,
84 deployment,
85 }
86 }
87
88 pub fn get_env_for_execution(&self) -> &ProgramRuntimeEnvironment {
90 &self.execution
91 }
92
93 pub fn get_env_for_deployment(&self) -> &ProgramRuntimeEnvironment {
95 &self.deployment
96 }
97
98 #[cfg(feature = "dev-context-only-utils")]
99 pub fn mock() -> Self {
100 Self {
101 execution: get_mock_program_runtime_environment(),
102 deployment: get_mock_program_runtime_environment(),
103 }
104 }
105}
106
107#[cfg(feature = "dev-context-only-utils")]
108pub fn get_mock_program_runtime_environment() -> ProgramRuntimeEnvironment {
109 static MOCK_ENVIRONMENT: std::sync::OnceLock<ProgramRuntimeEnvironment> =
110 std::sync::OnceLock::<ProgramRuntimeEnvironment>::new();
111 MOCK_ENVIRONMENT
112 .get_or_init(|| ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock()))
113 .clone()
114}
115
116pub const MAX_LOADED_ENTRY_COUNT: usize = 1024;
117pub const MAX_TOMBSTONE_AGE_IN_SLOTS: u64 = 2250; pub type Percent = u8;
121
122fn percent_of_max_entries(percent: Percent) -> usize {
127 debug_assert!(percent <= 100, "percent must be <= 100");
128 MAX_LOADED_ENTRY_COUNT.saturating_mul(percent as usize) / 100
129}
130
131#[derive(Copy, Clone, Debug, PartialEq)]
133pub enum BlockRelation {
134 Ancestor,
136 Equal,
138 Descendant,
140 Unrelated,
142 Unknown,
144}
145
146pub trait ForkGraph {
148 fn relationship(&self, a: Slot, b: Slot) -> BlockRelation;
150}
151
152#[derive(Debug, Default)]
154pub struct EpochBoundaryPreparation {
155 pub upcoming_epoch: Epoch,
157 pub upcoming_environment: Option<ProgramRuntimeEnvironment>,
164 pub programs_to_recompile: Vec<(Pubkey, Arc<ProgramCacheEntry>)>,
166}
167
168impl EpochBoundaryPreparation {
169 pub fn new(epoch: Epoch) -> Self {
170 Self {
171 upcoming_epoch: epoch,
172 upcoming_environment: None,
173 programs_to_recompile: Vec::default(),
174 }
175 }
176
177 pub fn get_upcoming_environment_for_epoch(
179 &self,
180 epoch: Epoch,
181 ) -> Option<ProgramRuntimeEnvironment> {
182 if epoch == self.upcoming_epoch {
183 return self.upcoming_environment.clone();
184 }
185 None
186 }
187
188 pub fn reroot(&mut self, epoch: Epoch) -> Option<ProgramRuntimeEnvironment> {
190 if epoch == self.upcoming_epoch
191 && let Some(upcoming_environment) = self.upcoming_environment.take()
192 {
193 self.programs_to_recompile.clear();
194 return Some(upcoming_environment);
195 }
196
197 None
198 }
199}
200
201#[derive(Clone, PartialEq, Debug)]
203pub struct ProgramToLoad<'a> {
204 pub program_id: &'a Pubkey,
206 pub loader: ProgramCacheEntryOwner,
208 pub deployment_slot: Slot,
213}
214
215#[derive(Debug)]
216pub(crate) enum IndexImplementation {
217 V1 {
219 entries: HashMap<Pubkey, Vec<Arc<ProgramCacheEntry>>>,
225 loading_entries: Mutex<HashMap<Pubkey, (Slot, thread::ThreadId)>>,
234 },
235}
236
237pub struct ProgramCache<FG: ForkGraph> {
253 pub(crate) index: IndexImplementation,
255 pub latest_root_slot: Slot,
257 pub stats: ProgramCacheStats,
259 pub fork_graph: Option<Weak<RwLock<FG>>>,
261 pub loading_task_waiter: Arc<LoadingTaskWaiter>,
263}
264
265impl<FG: ForkGraph> std::fmt::Debug for ProgramCache<FG> {
266 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
267 f.debug_struct("ProgramCache")
268 .field("root slot", &self.latest_root_slot)
269 .field("stats", &self.stats)
270 .field("index", &self.index)
271 .finish()
272 }
273}
274
275#[derive(Clone, Debug, Default)]
281pub struct ProgramCacheForTxBatch {
282 entries: HashMap<Pubkey, Arc<ProgramCacheEntry>>,
285 modified_entries: HashMap<Pubkey, Arc<ProgramCacheEntry>>,
287 slot: Slot,
288 pub hit_max_limit: bool,
289 pub loaded_missing: bool,
290 pub merged_modified: bool,
291}
292
293impl ProgramCacheForTxBatch {
294 pub fn new(slot: Slot) -> Self {
295 Self {
296 entries: HashMap::new(),
297 modified_entries: HashMap::new(),
298 slot,
299 hit_max_limit: false,
300 loaded_missing: false,
301 merged_modified: false,
302 }
303 }
304
305 pub fn replenish(
311 &mut self,
312 key: Pubkey,
313 entry: Arc<ProgramCacheEntry>,
314 ) -> (bool, Arc<ProgramCacheEntry>) {
315 (self.entries.insert(key, entry.clone()).is_some(), entry)
316 }
317
318 pub fn store_modified_entry(&mut self, key: Pubkey, entry: Arc<ProgramCacheEntry>) {
321 self.modified_entries.insert(key, entry);
322 }
323
324 pub fn drain_modified_entries(&mut self) -> HashMap<Pubkey, Arc<ProgramCacheEntry>> {
327 std::mem::take(&mut self.modified_entries)
328 }
329
330 pub fn find(&self, key: &Pubkey) -> Option<Arc<ProgramCacheEntry>> {
331 self.modified_entries
335 .get(key)
336 .or_else(|| self.entries.get(key))
337 .map(|entry| {
338 if entry.is_implicit_delay_visibility_tombstone(self.slot) {
339 Arc::new(ProgramCacheEntry::new_delay_visibility_tombstone(
343 entry.deployment_slot,
344 entry.account_owner,
345 Arc::clone(&entry.stats),
346 ))
347 } else {
348 entry.clone()
349 }
350 })
351 }
352
353 pub fn slot(&self) -> Slot {
354 self.slot
355 }
356
357 pub fn get_entry_for_tests(&self, key: &Pubkey) -> Option<&Arc<ProgramCacheEntry>> {
360 self.entries.get(key)
361 }
362
363 pub fn set_slot_for_tests(&mut self, slot: Slot) {
364 self.slot = slot;
365 }
366
367 pub fn merge(&mut self, modified_entries: &HashMap<Pubkey, Arc<ProgramCacheEntry>>) {
368 modified_entries.iter().for_each(|(key, entry)| {
369 self.merged_modified = true;
370 self.replenish(*key, entry.clone());
371 })
372 }
373
374 pub fn remove_entry(&mut self, key: &Pubkey) {
377 self.entries.remove(key);
378 }
379
380 pub fn is_empty(&self) -> bool {
381 self.entries.is_empty()
382 }
383}
384
385impl<FG: ForkGraph> ProgramCache<FG> {
386 pub fn new(root_slot: Slot) -> Self {
387 Self {
388 index: IndexImplementation::V1 {
389 entries: HashMap::new(),
390 loading_entries: Mutex::new(HashMap::new()),
391 },
392 latest_root_slot: root_slot,
393 stats: ProgramCacheStats::default(),
394 fork_graph: None,
395 loading_task_waiter: Arc::new(LoadingTaskWaiter::default()),
396 }
397 }
398
399 pub fn set_fork_graph(&mut self, fork_graph: Weak<RwLock<FG>>) {
400 self.fork_graph = Some(fork_graph);
401 }
402
403 pub fn assign_program(
406 &mut self,
407 program_runtime_environment: &ProgramRuntimeEnvironment,
408 key: Pubkey,
409 _current_slot: Slot,
410 entry: Arc<ProgramCacheEntry>,
411 ) -> bool {
412 debug_assert!(
413 !matches!(&entry.program, ProgramCacheEntryType::DelayVisibility),
414 "Unexpected assignment of a DelayVisibility tombstone"
415 );
416 fn is_current_env(
420 program_runtime_environment: &ProgramRuntimeEnvironment,
421 env_opt: Option<&ProgramRuntimeEnvironment>,
422 ) -> bool {
423 env_opt
424 .map(|env| env == program_runtime_environment)
425 .unwrap_or(true)
426 }
427 match &mut self.index {
428 IndexImplementation::V1 { entries, .. } => {
429 let slot_versions = &mut entries.entry(key).or_default();
430 let insertion_point = slot_versions.binary_search_by(|at| {
431 at.deployment_slot
432 .cmp(&entry.deployment_slot)
433 .then(at.account_owner.cmp(&entry.account_owner))
434 .then(
435 is_current_env(
439 program_runtime_environment,
440 at.program.get_environment(),
441 )
442 .cmp(&is_current_env(
443 program_runtime_environment,
444 entry.program.get_environment(),
445 )),
446 )
447 });
448 match insertion_point {
449 Ok(index) => {
450 let existing = slot_versions.get_mut(index).unwrap();
451 match (&existing.program, &entry.program) {
452 (
453 ProgramCacheEntryType::Builtin(_),
454 ProgramCacheEntryType::Builtin(_),
455 )
456 | (ProgramCacheEntryType::Closed, ProgramCacheEntryType::Unloaded(_))
457 | (
458 ProgramCacheEntryType::Unloaded(_),
459 ProgramCacheEntryType::Loaded(_),
460 )
461 | (
462 ProgramCacheEntryType::Unloaded(_),
463 ProgramCacheEntryType::FailedVerification(_),
464 ) => {}
465 _ => {
466 error!(
468 "ProgramCache::assign_program() failed key={key:?} \
469 existing={slot_versions:?} entry={entry:?}"
470 );
471 debug_assert!(false, "Unexpected replacement of an entry");
472 self.stats.replacements.fetch_add(1, Ordering::Relaxed);
473 return true;
474 }
475 }
476 entry.stats.merge_from(&existing.stats);
477 *existing = Arc::clone(&entry);
478 self.stats.reloads.fetch_add(1, Ordering::Relaxed);
479 }
480 Err(index) => {
481 self.stats.insertions.fetch_add(1, Ordering::Relaxed);
482 slot_versions.insert(index, Arc::clone(&entry));
483 }
484 }
485 slot_versions.retain(|existing| {
489 existing.deployment_slot != entry.deployment_slot
490 || existing
491 .program
492 .get_environment()
493 .zip(entry.program.get_environment())
494 .map(|(a, b)| a != b)
495 .unwrap_or(false)
496 || Arc::ptr_eq(existing, &entry)
497 });
498 }
499 }
500 false
501 }
502
503 pub fn prune_by_deployment_slot(&mut self, slot: Slot) {
504 match &mut self.index {
505 IndexImplementation::V1 { entries, .. } => {
506 for second_level in entries.values_mut() {
507 second_level.retain(|entry| entry.deployment_slot != slot);
508 }
509 self.remove_programs_with_no_entries();
510 }
511 }
512 }
513
514 pub fn prune(
516 &mut self,
517 new_root_slot: Slot,
518 new_environment: Option<ProgramRuntimeEnvironment>,
519 fork_graph: &FG,
520 ) {
521 match &mut self.index {
522 IndexImplementation::V1 { entries, .. } => {
523 let tombstone_slot_cutoff =
524 new_root_slot.saturating_sub(MAX_TOMBSTONE_AGE_IN_SLOTS);
525 entries.retain(|_id, second_level| {
526 if let [candidate] = &second_level[..]
528 && (candidate.is_unloaded() || 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 batch_slot = loaded_programs_for_tx_batch.slot;
631 let mut cooperative_loading_task = None;
632 match &self.index {
633 IndexImplementation::V1 {
634 entries,
635 loading_entries,
636 } => {
637 search_for.retain(|program_to_load| {
638 if let Some(second_level) = entries.get(program_to_load.program_id) {
639 for entry in second_level.iter().rev() {
640 if program_to_load.deployment_slot != entry.deployment_slot
643 || program_to_load.loader != entry.account_owner
644 {
645 continue;
646 }
647
648 let entry_in_same_branch = entry.deployment_slot
654 <= self.latest_root_slot
655 || matches!(
656 locked_fork_graph
657 .relationship(entry.deployment_slot, batch_slot),
658 BlockRelation::Equal | BlockRelation::Ancestor
659 );
660 if entry_in_same_branch {
661 let entry_is_effective = batch_slot >= entry.effective_slot();
662 let entry_to_return = if entry_is_effective {
663 if !Self::matches_environment(
664 entry,
665 program_runtime_environment_for_execution,
666 ) {
667 continue;
671 }
672 if entry.is_unloaded() {
673 break;
674 }
675 entry.clone()
676 } else if entry.is_implicit_delay_visibility_tombstone(batch_slot) {
677 Arc::new(ProgramCacheEntry::new_delay_visibility_tombstone(
681 entry.deployment_slot,
682 entry.account_owner,
683 Arc::clone(&entry.stats),
684 ))
685 } else {
686 continue;
687 };
688 entry.update_access_slot(batch_slot.min(self.latest_root_slot));
689 if increment_usage_counter {
690 entry_to_return.stats.uses.fetch_add(1, Ordering::Relaxed);
691 }
692 loaded_programs_for_tx_batch
693 .entries
694 .insert(*program_to_load.program_id, entry_to_return);
695 return false;
696 }
697 }
698 }
699 if cooperative_loading_task.is_none() {
700 let mut loading_entries = loading_entries.lock().unwrap();
701 let entry = loading_entries.entry(*program_to_load.program_id);
702 if let Entry::Vacant(entry) = entry {
703 entry.insert((batch_slot, thread::current().id()));
704 cooperative_loading_task = Some(*program_to_load.program_id);
705 }
706 }
707 true
708 });
709 }
710 }
711 drop(locked_fork_graph);
712 if count_hits_and_misses {
713 let misses = search_for.len() as u64;
714 let hits = loaded_programs_for_tx_batch
715 .entries
716 .len()
717 .saturating_sub(entries_in_batch) as u64;
718 self.stats.misses.fetch_add(misses, Ordering::Relaxed);
719 self.stats.hits.fetch_add(hits, Ordering::Relaxed);
720 }
721 cooperative_loading_task
722 }
723
724 pub fn finish_cooperative_loading_task(
726 &mut self,
727 program_runtime_environment: &ProgramRuntimeEnvironment,
728 current_slot: Slot,
729 key: Pubkey,
730 loaded_program: Arc<ProgramCacheEntry>,
731 ) -> bool {
732 match &mut self.index {
733 IndexImplementation::V1 {
734 loading_entries, ..
735 } => {
736 let loading_thread = loading_entries.get_mut().unwrap().remove(&key);
737 debug_assert_eq!(loading_thread, Some((current_slot, thread::current().id())));
738 if loaded_program.deployment_slot > self.latest_root_slot
740 && !matches!(
741 self.fork_graph
742 .as_ref()
743 .unwrap()
744 .upgrade()
745 .unwrap()
746 .read()
747 .unwrap()
748 .relationship(loaded_program.deployment_slot, current_slot),
749 BlockRelation::Equal | BlockRelation::Ancestor
750 )
751 {
752 self.stats.lost_insertions.fetch_add(1, Ordering::Relaxed);
753 }
754 let was_occupied = self.assign_program(
755 program_runtime_environment,
756 key,
757 current_slot,
758 loaded_program,
759 );
760 self.loading_task_waiter.notify();
761 was_occupied
762 }
763 }
764 }
765
766 pub fn get_flattened_entries(&self) -> Vec<(Pubkey, Arc<ProgramCacheEntry>)> {
768 match &self.index {
769 IndexImplementation::V1 { entries, .. } => entries
770 .iter()
771 .flat_map(|(id, second_level)| {
772 second_level
773 .iter()
774 .filter_map(move |program| match program.program {
775 ProgramCacheEntryType::Loaded(_) => Some((*id, program.clone())),
776 _ => None,
777 })
778 })
779 .collect(),
780 }
781 }
782
783 #[cfg(feature = "dev-context-only-utils")]
785 pub fn get_flattened_entries_for_tests(&self) -> Vec<(Pubkey, Arc<ProgramCacheEntry>)> {
786 match &self.index {
787 IndexImplementation::V1 { entries, .. } => entries
788 .iter()
789 .flat_map(|(id, second_level)| {
790 second_level.iter().map(|program| (*id, program.clone()))
791 })
792 .collect(),
793 }
794 }
795
796 pub fn get_slot_versions_for_tests(&self, key: &Pubkey) -> &[Arc<ProgramCacheEntry>] {
798 match &self.index {
799 IndexImplementation::V1 { entries, .. } => entries
800 .get(key)
801 .map(|second_level| second_level.as_ref())
802 .unwrap_or(&[]),
803 }
804 }
805
806 pub fn sort_and_unload(&mut self, shrink_to_percent: Percent) {
808 let mut sorted_candidates = self.get_flattened_entries();
809 sorted_candidates
810 .sort_by_cached_key(|(_id, program)| program.stats.uses.load(Ordering::Relaxed));
811 let num_to_unload = sorted_candidates
812 .len()
813 .saturating_sub(percent_of_max_entries(shrink_to_percent));
814 for (program, entry) in sorted_candidates.iter().take(num_to_unload) {
815 self.unload_program_entry(*program, entry);
816 }
817 }
818
819 pub fn evict_using_random_selection(&mut self, shrink_to_percent: Percent, now: Slot) {
825 let mut candidates = self.get_flattened_entries();
826 let mut rng = rng();
827 self.stats
828 .water_level
829 .store(candidates.len() as u64, Ordering::Relaxed);
830 let num_to_unload = candidates
831 .len()
832 .saturating_sub(percent_of_max_entries(shrink_to_percent));
833 let mut sample_entry = |candidates: &Vec<(Pubkey, Arc<ProgramCacheEntry>)>| {
834 #[cfg(feature = "shuttle-test")]
837 let index = rng.gen_range(0..candidates.len());
838 #[cfg(not(feature = "shuttle-test"))]
839 let index = rng.random_range(0..candidates.len());
840 let usage_counter = candidates
841 .get(index)
842 .expect("Failed to get cached entry")
843 .1
844 .retention_score();
845 (index, usage_counter)
846 };
847
848 const MAX_ADDITIONAL_SAMPLES: usize = 3;
856 let avoid_evicting_above_score = retention_score(now, 500 * EMA_SCALE, 500);
857 for _ in 0..num_to_unload {
858 let (mut index, mut score) = sample_entry(&candidates);
859 for _ in 0..MAX_ADDITIONAL_SAMPLES {
860 let (sample_index, sample_score) = sample_entry(&candidates);
861 if score > sample_score {
862 index = sample_index;
863 score = sample_score;
864 }
865 if score < avoid_evicting_above_score {
866 break;
867 }
868 }
869 let (id, entry) = candidates.swap_remove(index);
870 self.unload_program_entry(id, &entry);
871 }
872 }
873
874 pub fn remove_programs(&mut self, keys: impl Iterator<Item = Pubkey>) {
876 match &mut self.index {
877 IndexImplementation::V1 { entries, .. } => {
878 for k in keys {
879 entries.remove(&k);
880 }
881 }
882 }
883 }
884
885 fn unload_program_entry(&mut self, id: Pubkey, remove_entry: &Arc<ProgramCacheEntry>) {
888 match &mut self.index {
889 IndexImplementation::V1 { entries, .. } => {
890 let second_level = entries.get_mut(&id).expect("Cache lookup failed");
891 let candidate = second_level
892 .iter_mut()
893 .find(|entry| Arc::ptr_eq(entry, remove_entry))
894 .expect("Program entry not found");
895
896 if let ProgramCacheEntryType::Loaded(_) = candidate.program
898 && let Some(unloaded) = candidate.to_unloaded()
899 {
900 if candidate.stats.uses.load(Ordering::Relaxed) == 1 {
901 self.stats.one_hit_wonders.fetch_add(1, Ordering::Relaxed);
902 }
903 self.stats
904 .evictions
905 .entry(id)
906 .and_modify(|c| *c = c.saturating_add(1))
907 .or_insert(1);
908 *candidate = Arc::new(unloaded);
909 }
910 }
911 }
912 }
913
914 fn remove_programs_with_no_entries(&mut self) {
915 match &mut self.index {
916 IndexImplementation::V1 { entries, .. } => {
917 let num_programs_before_removal = entries.len();
918 entries.retain(|_key, second_level| !second_level.is_empty());
919 if entries.len() < num_programs_before_removal {
920 self.stats.empty_entries.fetch_add(
921 num_programs_before_removal.saturating_sub(entries.len()) as u64,
922 Ordering::Relaxed,
923 );
924 }
925 }
926 }
927 }
928}
929
930#[cfg(test)]
931pub(crate) mod tests {
932 use {
933 crate::{
934 loaded_programs::{
935 BlockRelation, ForkGraph, IndexImplementation, MAX_TOMBSTONE_AGE_IN_SLOTS, Percent,
936 ProgramCache, ProgramCacheForTxBatch, ProgramRuntimeEnvironment, ProgramToLoad,
937 get_mock_program_runtime_environment,
938 },
939 program_cache_entry::{
940 ProgramCacheEntry, ProgramCacheEntryOwner, ProgramCacheEntryType,
941 },
942 program_metrics::ProgramStatistics,
943 },
944 assert_matches::assert_matches,
945 solana_clock::Slot,
946 solana_pubkey::Pubkey,
947 solana_sbpf::{elf::Executable, program::BuiltinProgram},
948 solana_svm_type_overrides::{
949 sync::{
950 Arc, RwLock,
951 atomic::{AtomicU64, Ordering},
952 },
953 thread,
954 },
955 std::{fs::File, io::Read, ops::ControlFlow},
956 test_case::{test_case, test_matrix},
957 };
958
959 fn new_test_entry(deployment_slot: Slot) -> Arc<ProgramCacheEntry> {
960 new_test_entry_with_usage(deployment_slot, ProgramStatistics::default())
961 }
962
963 fn new_closed_entry(_env: ProgramRuntimeEnvironment) -> ProgramCacheEntryType {
964 ProgramCacheEntryType::Closed
965 }
966
967 fn new_builtin_entry(_env: ProgramRuntimeEnvironment) -> ProgramCacheEntryType {
968 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock())
969 }
970
971 fn new_failed_verification_entry(env: ProgramRuntimeEnvironment) -> ProgramCacheEntryType {
972 ProgramCacheEntryType::FailedVerification(env)
973 }
974
975 fn new_unloaded_entry(env: ProgramRuntimeEnvironment) -> ProgramCacheEntryType {
976 ProgramCacheEntryType::Unloaded(env)
977 }
978
979 fn new_loaded_entry(env: ProgramRuntimeEnvironment) -> ProgramCacheEntryType {
980 let mut elf = Vec::new();
981 File::open("../programs/bpf_loader/test_elfs/out/noop_aligned.so")
982 .unwrap()
983 .read_to_end(&mut elf)
984 .unwrap();
985 let executable = Executable::load(&elf, Arc::clone(&*env)).unwrap();
986 ProgramCacheEntryType::Loaded(executable)
987 }
988
989 fn new_test_entry_with_owner(
990 deployment_slot: Slot,
991 account_owner: ProgramCacheEntryOwner,
992 program: ProgramCacheEntryType,
993 ) -> Arc<ProgramCacheEntry> {
994 Arc::new(ProgramCacheEntry {
995 program,
996 account_owner,
997 deployment_slot,
998 stats: Arc::default(),
999 latest_access_slot: AtomicU64::default(),
1000 })
1001 }
1002
1003 fn new_test_cache_with_fork_graph(
1004 relation: BlockRelation,
1005 ) -> (ProgramCache<TestForkGraph>, Arc<RwLock<TestForkGraph>>) {
1006 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1007 let fork_graph = Arc::new(RwLock::new(TestForkGraph { relation }));
1008 cache.set_fork_graph(Arc::downgrade(&fork_graph));
1009 (cache, fork_graph)
1010 }
1011
1012 pub(crate) fn new_test_entry_with_usage(
1013 deployment_slot: Slot,
1014 stats: ProgramStatistics,
1015 ) -> Arc<ProgramCacheEntry> {
1016 Arc::new(ProgramCacheEntry {
1017 program: new_loaded_entry(get_mock_program_runtime_environment()),
1018 account_owner: ProgramCacheEntryOwner::LoaderV2,
1019 deployment_slot,
1020 stats: Arc::new(stats),
1021 latest_access_slot: AtomicU64::new(deployment_slot),
1022 })
1023 }
1024
1025 fn new_test_builtin_entry(deployment_slot: Slot) -> Arc<ProgramCacheEntry> {
1026 Arc::new(ProgramCacheEntry {
1027 program: ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1028 account_owner: ProgramCacheEntryOwner::NativeLoader,
1029 deployment_slot,
1030 stats: Arc::default(),
1031 latest_access_slot: AtomicU64::default(),
1032 })
1033 }
1034
1035 fn set_failed_verification_tombstone<FG: ForkGraph>(
1036 cache: &mut ProgramCache<FG>,
1037 key: Pubkey,
1038 current_slot: Slot,
1039 env: ProgramRuntimeEnvironment,
1040 ) -> Arc<ProgramCacheEntry> {
1041 let program = Arc::new(ProgramCacheEntry::new_failed_verification_tombstone(
1042 current_slot,
1043 ProgramCacheEntryOwner::LoaderV2,
1044 ProgramRuntimeEnvironment::clone(&env),
1045 ));
1046 cache.assign_program(&env, key, current_slot, program.clone());
1047 program
1048 }
1049
1050 fn insert_unloaded_entry<FG: ForkGraph>(
1051 cache: &mut ProgramCache<FG>,
1052 key: Pubkey,
1053 current_slot: Slot,
1054 ) -> Arc<ProgramCacheEntry> {
1055 let env = get_mock_program_runtime_environment();
1056 let loaded = new_test_entry_with_usage(current_slot, ProgramStatistics::default());
1057 let unloaded = Arc::new(loaded.to_unloaded().expect("Failed to unload the program"));
1058 cache.assign_program(&env, key, current_slot, unloaded.clone());
1059 unloaded
1060 }
1061
1062 fn num_matching_entries<P, FG>(cache: &ProgramCache<FG>, predicate: P) -> usize
1063 where
1064 P: Fn(&ProgramCacheEntryType) -> bool,
1065 FG: ForkGraph,
1066 {
1067 cache
1068 .get_flattened_entries_for_tests()
1069 .iter()
1070 .filter(|(_key, program)| predicate(&program.program))
1071 .count()
1072 }
1073
1074 #[expect(clippy::arithmetic_side_effects)]
1075 fn program_deploy_test_helper(
1076 cache: &mut ProgramCache<TestForkGraph>,
1077 program: Pubkey,
1078 deployment_slots: Vec<Slot>,
1079 usage_counters: Vec<u64>,
1080 programs: &mut Vec<(Pubkey, Slot, u64)>,
1081 ) {
1082 let env = get_mock_program_runtime_environment();
1083 deployment_slots
1085 .iter()
1086 .enumerate()
1087 .for_each(|(i, deployment_slot)| {
1088 let usage_counter = *usage_counters.get(i).unwrap_or(&0);
1089 let stats = ProgramStatistics {
1090 uses: usage_counter.into(),
1091 ..Default::default()
1092 };
1093 cache.assign_program(
1094 &env,
1095 program,
1096 *deployment_slot,
1097 new_test_entry_with_usage(*deployment_slot, stats),
1098 );
1099 programs.push((program, *deployment_slot, usage_counter));
1100 });
1101
1102 let next_slot = deployment_slots.iter().max().map_or(0, |slot| slot + 1);
1103
1104 let env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
1106 for slot in next_slot..next_slot + 10 {
1107 set_failed_verification_tombstone(
1108 cache,
1109 program,
1110 slot,
1111 ProgramRuntimeEnvironment::clone(&env),
1112 );
1113 }
1114
1115 for slot in next_slot + 10..next_slot + 20 {
1117 insert_unloaded_entry(cache, program, slot);
1118 }
1119 }
1120
1121 #[test]
1122 fn test_random_eviction() {
1123 let mut programs = vec![];
1124 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1125
1126 program_deploy_test_helper(
1132 &mut cache,
1133 Pubkey::new_unique(),
1134 vec![0, 10, 20, 30, 40],
1135 vec![4, 5, 25, 35, 12],
1136 &mut programs,
1137 );
1138
1139 program_deploy_test_helper(
1141 &mut cache,
1142 Pubkey::new_unique(),
1143 vec![5, 11, 21, 24],
1144 vec![0, 2, 30, 45],
1145 &mut programs,
1146 );
1147
1148 program_deploy_test_helper(
1150 &mut cache,
1151 Pubkey::new_unique(),
1152 vec![0, 5, 15, 25],
1153 vec![100, 3, 20, 40],
1154 &mut programs,
1155 );
1156
1157 let num_loaded_expected = 13;
1159 let num_unloaded_expected = 30;
1161 let num_tombstones_expected = 30;
1163
1164 programs.sort_by_key(|(_id, _slot, usage_count)| *usage_count);
1166 let num_loaded = num_matching_entries(&cache, |program_type| {
1167 matches!(program_type, ProgramCacheEntryType::Loaded(_))
1168 });
1169 let num_unloaded = num_matching_entries(&cache, |program_type| {
1170 matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1171 });
1172 let num_tombstones = num_matching_entries(&cache, |program_type| {
1173 matches!(
1174 program_type,
1175 ProgramCacheEntryType::DelayVisibility
1176 | ProgramCacheEntryType::FailedVerification(_)
1177 | ProgramCacheEntryType::Closed
1178 )
1179 });
1180
1181 assert_eq!(num_loaded, num_loaded_expected);
1183 assert_eq!(num_unloaded, num_unloaded_expected);
1184 assert_eq!(num_tombstones, num_tombstones_expected);
1185
1186 let eviction_pct: Percent = 1;
1188
1189 let num_loaded_expected = crate::loaded_programs::percent_of_max_entries(eviction_pct);
1190 let num_unloaded_expected = num_unloaded_expected + num_loaded - num_loaded_expected;
1191 cache.evict_using_random_selection(eviction_pct, 21);
1192
1193 let num_loaded = num_matching_entries(&cache, |program_type| {
1195 matches!(program_type, ProgramCacheEntryType::Loaded(_))
1196 });
1197 let num_unloaded = num_matching_entries(&cache, |program_type| {
1198 matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1199 });
1200 let num_tombstones = num_matching_entries(&cache, |program_type| {
1201 matches!(program_type, ProgramCacheEntryType::FailedVerification(_))
1202 });
1203
1204 assert_eq!(num_loaded, num_loaded_expected);
1206 assert_eq!(num_unloaded, num_unloaded_expected);
1208 assert_eq!(num_tombstones, num_tombstones_expected);
1210 }
1211
1212 #[test]
1213 fn test_eviction() {
1214 let mut programs = vec![];
1215 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1216
1217 program_deploy_test_helper(
1219 &mut cache,
1220 Pubkey::new_unique(),
1221 vec![0, 10, 20, 30, 40],
1222 vec![4, 5, 25, 35, 12],
1223 &mut programs,
1224 );
1225
1226 program_deploy_test_helper(
1228 &mut cache,
1229 Pubkey::new_unique(),
1230 vec![5, 11, 21, 24],
1231 vec![0, 2, 30, 45],
1232 &mut programs,
1233 );
1234
1235 program_deploy_test_helper(
1237 &mut cache,
1238 Pubkey::new_unique(),
1239 vec![0, 5, 15, 25],
1240 vec![100, 3, 20, 40],
1241 &mut programs,
1242 );
1243
1244 let num_loaded_expected = 13;
1246 let num_unloaded_expected = 30;
1248 let num_tombstones_expected = 30;
1250
1251 programs.sort_by_key(|(_id, _slot, usage_count)| *usage_count);
1253 let num_loaded = num_matching_entries(&cache, |program_type| {
1254 matches!(program_type, ProgramCacheEntryType::Loaded(_))
1255 });
1256 let num_unloaded = num_matching_entries(&cache, |program_type| {
1257 matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1258 });
1259 let num_tombstones = num_matching_entries(&cache, |program_type| {
1260 matches!(program_type, ProgramCacheEntryType::FailedVerification(_))
1261 });
1262
1263 assert_eq!(num_loaded, num_loaded_expected);
1265 assert_eq!(num_unloaded, num_unloaded_expected);
1266 assert_eq!(num_tombstones, num_tombstones_expected);
1267
1268 let eviction_pct: Percent = 1;
1270
1271 let num_loaded_expected = crate::loaded_programs::percent_of_max_entries(eviction_pct);
1272 let num_unloaded_expected = num_unloaded_expected + num_loaded - num_loaded_expected;
1273
1274 cache.sort_and_unload(eviction_pct);
1275
1276 let entries = cache.get_flattened_entries_for_tests();
1278 programs.iter().for_each(|entry| {
1279 assert!(entries.iter().any(|(key, _entry)| key == &entry.0));
1280 });
1281
1282 let unloaded = entries
1283 .iter()
1284 .filter_map(|(key, program)| {
1285 program
1286 .is_unloaded()
1287 .then_some((*key, program.stats.uses.load(Ordering::Relaxed)))
1288 })
1289 .collect::<Vec<(Pubkey, u64)>>();
1290
1291 for index in 0..3 {
1292 let expected = programs.get(index).expect("Missing program");
1293 assert!(unloaded.contains(&(expected.0, expected.2)));
1294 }
1295
1296 let num_loaded = num_matching_entries(&cache, |program_type| {
1298 matches!(program_type, ProgramCacheEntryType::Loaded(_))
1299 });
1300 let num_unloaded = num_matching_entries(&cache, |program_type| {
1301 matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1302 });
1303 let num_tombstones = num_matching_entries(&cache, |program_type| {
1304 matches!(
1305 program_type,
1306 ProgramCacheEntryType::DelayVisibility
1307 | ProgramCacheEntryType::FailedVerification(_)
1308 | ProgramCacheEntryType::Closed
1309 )
1310 });
1311
1312 assert_eq!(num_loaded, num_loaded_expected);
1314 assert_eq!(num_unloaded, num_unloaded_expected);
1316 assert_eq!(num_tombstones, num_tombstones_expected);
1318 }
1319
1320 #[test]
1321 fn test_usage_count_of_unloaded_program() {
1322 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1323 let env = get_mock_program_runtime_environment();
1324
1325 let program = Pubkey::new_unique();
1326 let evict_to_pct: Percent = 2;
1327 let cache_capacity_after_shrink =
1328 crate::loaded_programs::percent_of_max_entries(evict_to_pct);
1329 let num_total_programs = (cache_capacity_after_shrink + 1) as u64;
1331 (0..num_total_programs).for_each(|i| {
1332 let stats = ProgramStatistics {
1333 uses: (i + 10).into(),
1334 ..Default::default()
1335 };
1336 let entry = new_test_entry_with_usage(i, stats);
1337 cache.assign_program(&env, program, i, entry);
1338 });
1339
1340 cache.sort_and_unload(evict_to_pct);
1341
1342 let num_unloaded = num_matching_entries(&cache, |program_type| {
1343 matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1344 });
1345 assert_eq!(num_unloaded, 1);
1346
1347 cache
1348 .get_flattened_entries_for_tests()
1349 .iter()
1350 .for_each(|(_key, program)| {
1351 if program.is_unloaded() {
1352 assert_eq!(program.stats.uses.load(Ordering::Relaxed), 10);
1354 assert_eq!(program.deployment_slot, 0);
1355 assert_eq!(program.effective_slot(), 1);
1356 }
1357 });
1358
1359 cache.assign_program(
1362 &env,
1363 program,
1364 0,
1365 new_test_entry_with_usage(0, ProgramStatistics::default()),
1366 );
1367
1368 cache
1369 .get_flattened_entries_for_tests()
1370 .iter()
1371 .for_each(|(_key, program)| {
1372 if program.is_unloaded()
1373 && program.deployment_slot == 0
1374 && program.effective_slot() == 1
1375 {
1376 assert_eq!(program.stats.uses.load(Ordering::Relaxed), 10);
1378 }
1379 });
1380 }
1381
1382 #[test_matrix(
1383 (
1384 ProgramCacheEntryType::FailedVerification(get_mock_program_runtime_environment()),
1385 ProgramCacheEntryType::Closed,
1386 ProgramCacheEntryType::Unloaded(get_mock_program_runtime_environment()),
1387 new_loaded_entry(get_mock_program_runtime_environment()),
1388 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1389 ),
1390 (false, true)
1391 )]
1392 fn test_assign_program_no_second_level(
1393 program: ProgramCacheEntryType,
1394 empty_second_level: bool,
1395 ) {
1396 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1400 let env = get_mock_program_runtime_environment();
1401 let program_id = Pubkey::new_unique();
1402
1403 if empty_second_level {
1404 match &mut cache.index {
1406 IndexImplementation::V1 { entries, .. } => {
1407 entries.insert(program_id, Vec::new());
1408 }
1409 }
1410 }
1411
1412 let entry = Arc::new(ProgramCacheEntry {
1413 program,
1414 account_owner: ProgramCacheEntryOwner::LoaderV3,
1415 deployment_slot: 10,
1416 stats: Arc::default(),
1417 latest_access_slot: AtomicU64::default(),
1418 });
1419
1420 cache.assign_program(&env, program_id, 10, Arc::clone(&entry));
1421
1422 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
1424 assert_eq!(slot_versions.len(), 1);
1425 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &entry));
1426
1427 assert_eq!(cache.stats.insertions.load(Ordering::Relaxed), 1);
1429 }
1430
1431 #[test_matrix(
1432 (
1433 new_closed_entry,
1434 new_builtin_entry,
1435 new_failed_verification_entry,
1436 new_unloaded_entry,
1437 new_loaded_entry,
1438 ),
1439 ((50, 0), (150, 1), (250, 2), (350, 3))
1440 )]
1441 fn test_assign_program_new_insertion_deployment_slot(
1442 new_program: fn(ProgramRuntimeEnvironment) -> ProgramCacheEntryType,
1443 case: (Slot, usize),
1444 ) {
1445 let (deployment_slot, expected_index) = case;
1446 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1447 let env = get_mock_program_runtime_environment();
1448 let program_id = Pubkey::new_unique();
1449
1450 for slot in [100, 200, 300] {
1452 cache.assign_program(
1453 &env,
1454 program_id,
1455 slot,
1456 new_test_entry_with_owner(
1457 slot,
1458 ProgramCacheEntryOwner::LoaderV3,
1459 new_program(env.clone()),
1460 ),
1461 );
1462 }
1463
1464 let entry = new_test_entry_with_owner(
1466 deployment_slot,
1467 ProgramCacheEntryOwner::LoaderV3,
1468 new_program(env.clone()),
1469 );
1470 cache.assign_program(&env, program_id, deployment_slot, Arc::clone(&entry));
1471
1472 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
1473 assert_eq!(slot_versions.len(), 4);
1474 assert!(Arc::ptr_eq(
1475 slot_versions.get(expected_index).unwrap(),
1476 &entry
1477 ));
1478 assert_eq!(cache.stats.insertions.load(Ordering::Relaxed), 4);
1479 }
1480
1481 #[test_matrix(
1482 (new_failed_verification_entry, new_unloaded_entry, new_loaded_entry),
1483 (
1484 (ProgramCacheEntryOwner::NativeLoader, 0),
1485 (ProgramCacheEntryOwner::LoaderV2, 1),
1486 (ProgramCacheEntryOwner::LoaderV4, 2),
1487 )
1488 )]
1489 fn test_assign_program_new_insertion_account_owner(
1490 new_program: fn(ProgramRuntimeEnvironment) -> ProgramCacheEntryType,
1491 case: (ProgramCacheEntryOwner, usize),
1492 ) {
1493 let (account_owner, expected_index) = case;
1494 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1495 let env = get_mock_program_runtime_environment();
1496 let program_id = Pubkey::new_unique();
1497
1498 for owner in [
1501 ProgramCacheEntryOwner::LoaderV1,
1502 ProgramCacheEntryOwner::LoaderV3,
1503 ] {
1504 cache.assign_program(
1505 &env,
1506 program_id,
1507 100,
1508 new_test_entry_with_owner(
1509 100,
1510 owner,
1511 new_program(ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock())),
1512 ),
1513 );
1514 }
1515
1516 let entry = new_test_entry_with_owner(
1519 100,
1520 account_owner,
1521 new_program(ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock())),
1522 );
1523 cache.assign_program(&env, program_id, 100, Arc::clone(&entry));
1524
1525 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
1526 assert_eq!(slot_versions.len(), 3);
1527 assert!(Arc::ptr_eq(
1528 slot_versions.get(expected_index).unwrap(),
1529 &entry
1530 ));
1531 assert_eq!(cache.stats.insertions.load(Ordering::Relaxed), 3);
1532 }
1533
1534 #[test_matrix(
1535 (new_failed_verification_entry, new_unloaded_entry, new_loaded_entry),
1536 (false, true)
1537 )]
1538 fn test_assign_program_new_insertion_environment(
1539 new_program: fn(ProgramRuntimeEnvironment) -> ProgramCacheEntryType,
1540 entry_uses_current_env: bool,
1541 ) {
1542 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1543 let env = get_mock_program_runtime_environment();
1544 let other_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
1545 let program_id = Pubkey::new_unique();
1546
1547 let (existing_env, entry_env, expected_index) = if entry_uses_current_env {
1550 (other_env, env.clone(), 1)
1551 } else {
1552 (env.clone(), other_env, 0)
1553 };
1554 cache.assign_program(
1555 &env,
1556 program_id,
1557 100,
1558 new_test_entry_with_owner(
1559 100,
1560 ProgramCacheEntryOwner::LoaderV3,
1561 new_program(existing_env),
1562 ),
1563 );
1564
1565 let entry = new_test_entry_with_owner(
1566 100,
1567 ProgramCacheEntryOwner::LoaderV3,
1568 new_program(entry_env),
1569 );
1570 cache.assign_program(&env, program_id, 100, Arc::clone(&entry));
1571
1572 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
1573 assert_eq!(slot_versions.len(), 2);
1574 assert!(Arc::ptr_eq(
1575 slot_versions.get(expected_index).unwrap(),
1576 &entry
1577 ));
1578 assert_eq!(cache.stats.insertions.load(Ordering::Relaxed), 2);
1579 }
1580
1581 #[test]
1582 #[should_panic(expected = "Unexpected assignment of a DelayVisibility tombstone")]
1583 fn test_assign_program_delay_visibility_tombstone_panics() {
1584 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1587 let env = get_mock_program_runtime_environment();
1588 cache.assign_program(
1589 &env,
1590 Pubkey::new_unique(),
1591 100,
1592 Arc::new(ProgramCacheEntry::new_delay_visibility_tombstone(
1593 100,
1594 ProgramCacheEntryOwner::LoaderV3,
1595 Arc::default(),
1596 )),
1597 );
1598 }
1599
1600 #[test]
1601 fn test_fuzz_assign_program_order() {
1602 use rand::prelude::SliceRandom;
1603 const EXPECTED_ENTRIES: [(u64, bool); 5] =
1604 [(1, true), (3, false), (5, true), (9, true), (10, false)];
1605 let mut rng = rand::rng();
1606 let program_id = Pubkey::new_unique();
1607 let env = get_mock_program_runtime_environment();
1608 for _ in 0..1000 {
1609 let mut entries = EXPECTED_ENTRIES.to_vec();
1610 entries.shuffle(&mut rng);
1611 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1612 for (deployment_slot, delay_visibility) in entries {
1613 let entry = Arc::new(if delay_visibility {
1614 ProgramCacheEntry {
1615 program: new_loaded_entry(ProgramRuntimeEnvironment::from(
1616 BuiltinProgram::new_mock(),
1617 )), account_owner: ProgramCacheEntryOwner::LoaderV2,
1619 deployment_slot,
1620 stats: Arc::default(),
1621 latest_access_slot: AtomicU64::new(deployment_slot),
1622 }
1623 } else {
1624 ProgramCacheEntry::new_failed_verification_tombstone(
1625 deployment_slot,
1626 ProgramCacheEntryOwner::LoaderV2,
1627 ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock()), )
1629 });
1630 assert!(!cache.assign_program(&env, program_id, deployment_slot, entry));
1631 }
1632 for ((deployment_slot, delay_visibility), entry) in EXPECTED_ENTRIES
1633 .iter()
1634 .zip(cache.get_slot_versions_for_tests(&program_id).iter())
1635 {
1636 assert_eq!(entry.deployment_slot, *deployment_slot);
1637 assert_eq!(
1638 entry.effective_slot(),
1639 deployment_slot.saturating_add(*delay_visibility as u64)
1640 );
1641 }
1642 }
1643 }
1644
1645 #[test_matrix(
1646 (
1647 ProgramCacheEntryType::FailedVerification(get_mock_program_runtime_environment()),
1648 new_loaded_entry(get_mock_program_runtime_environment()),
1649 ),
1650 (
1651 ProgramCacheEntryType::FailedVerification(get_mock_program_runtime_environment()),
1652 ProgramCacheEntryType::Closed,
1653 ProgramCacheEntryType::Unloaded(get_mock_program_runtime_environment()),
1654 new_loaded_entry(get_mock_program_runtime_environment()),
1655 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1656 )
1657 )]
1658 #[test_matrix(
1659 ProgramCacheEntryType::Closed,
1660 (
1661 ProgramCacheEntryType::FailedVerification(get_mock_program_runtime_environment()),
1662 ProgramCacheEntryType::Closed,
1663 new_loaded_entry(get_mock_program_runtime_environment()),
1664 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1665 )
1666 )]
1667 #[test_matrix(
1668 ProgramCacheEntryType::Unloaded(get_mock_program_runtime_environment()),
1669 (
1670 ProgramCacheEntryType::Closed,
1671 ProgramCacheEntryType::Unloaded(get_mock_program_runtime_environment()),
1672 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1673 )
1674 )]
1675 #[test_matrix(
1676 (ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),),
1677 (
1678 ProgramCacheEntryType::FailedVerification(get_mock_program_runtime_environment()),
1679 ProgramCacheEntryType::Closed,
1680 ProgramCacheEntryType::Unloaded(get_mock_program_runtime_environment()),
1681 new_loaded_entry(get_mock_program_runtime_environment()),
1682 )
1683 )]
1684 #[should_panic(expected = "Unexpected replacement of an entry")]
1685 fn test_assign_program_failure(old: ProgramCacheEntryType, new: ProgramCacheEntryType) {
1686 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1687 let env = get_mock_program_runtime_environment();
1688 let program_id = Pubkey::new_unique();
1689 assert!(!cache.assign_program(
1690 &env,
1691 program_id,
1692 10,
1693 Arc::new(ProgramCacheEntry {
1694 program: old,
1695 account_owner: ProgramCacheEntryOwner::LoaderV2,
1696 deployment_slot: 10,
1697 stats: Arc::default(),
1698 latest_access_slot: AtomicU64::default(),
1699 }),
1700 ));
1701 cache.assign_program(
1702 &env,
1703 program_id,
1704 10,
1705 Arc::new(ProgramCacheEntry {
1706 program: new,
1707 account_owner: ProgramCacheEntryOwner::LoaderV2,
1708 deployment_slot: 10,
1709 stats: Arc::default(),
1710 latest_access_slot: AtomicU64::default(),
1711 }),
1712 );
1713 }
1714
1715 #[test_matrix(
1716 ProgramCacheEntryType::Unloaded(get_mock_program_runtime_environment()),
1717 (
1718 new_loaded_entry(get_mock_program_runtime_environment()),
1719 ProgramCacheEntryType::FailedVerification(get_mock_program_runtime_environment()),
1720 )
1721 )]
1722 #[test_case(
1723 ProgramCacheEntryType::Closed,
1724 ProgramCacheEntryType::Unloaded(get_mock_program_runtime_environment())
1725 )]
1726 #[test_case(
1727 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1728 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock())
1729 )]
1730 fn test_assign_program_success(old: ProgramCacheEntryType, new: ProgramCacheEntryType) {
1731 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1732 let env = get_mock_program_runtime_environment();
1733 let program_id = Pubkey::new_unique();
1734 assert!(!cache.assign_program(
1735 &env,
1736 program_id,
1737 10,
1738 Arc::new(ProgramCacheEntry {
1739 program: old,
1740 account_owner: ProgramCacheEntryOwner::LoaderV2,
1741 deployment_slot: 10,
1742 stats: Arc::default(),
1743 latest_access_slot: AtomicU64::default(),
1744 }),
1745 ));
1746 assert!(!cache.assign_program(
1747 &env,
1748 program_id,
1749 10,
1750 Arc::new(ProgramCacheEntry {
1751 program: new,
1752 account_owner: ProgramCacheEntryOwner::LoaderV2,
1753 deployment_slot: 10,
1754 stats: Arc::default(),
1755 latest_access_slot: AtomicU64::default(),
1756 }),
1757 ));
1758 }
1759
1760 #[test]
1761 fn test_assign_program_removes_entries_in_same_slot() {
1762 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1763 let env = get_mock_program_runtime_environment();
1764 let program_id = Pubkey::new_unique();
1765 let closed_other_slot = new_test_entry_with_owner(
1766 9,
1767 ProgramCacheEntryOwner::LoaderV2,
1768 new_closed_entry(env.clone()),
1769 );
1770 let closed_current_slot = new_test_entry_with_owner(
1771 10,
1772 ProgramCacheEntryOwner::LoaderV2,
1773 new_closed_entry(env.clone()),
1774 );
1775 let unloaded_current_env = new_test_entry_with_owner(
1776 10,
1777 ProgramCacheEntryOwner::LoaderV2,
1778 new_unloaded_entry(get_mock_program_runtime_environment()),
1779 );
1780 let unloaded_upcoming_env = new_test_entry_with_owner(
1781 10,
1782 ProgramCacheEntryOwner::LoaderV2,
1783 new_unloaded_entry(ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock())),
1784 );
1785
1786 assert!(!cache.assign_program(&env, program_id, 9, closed_other_slot.clone()));
1790 assert!(!cache.assign_program(&env, program_id, 10, closed_current_slot.clone()));
1791 assert_eq!(
1792 cache.get_slot_versions_for_tests(&program_id),
1793 &[closed_other_slot.clone(), closed_current_slot.clone()]
1794 );
1795
1796 assert!(!cache.assign_program(&env, program_id, 10, unloaded_current_env.clone()));
1805 assert_eq!(
1806 cache.get_slot_versions_for_tests(&program_id),
1807 &[
1808 closed_other_slot.clone(),
1809 unloaded_current_env.clone() ]
1811 );
1812
1813 assert!(!cache.assign_program(&env, program_id, 10, unloaded_upcoming_env.clone()));
1817 assert_eq!(
1818 cache.get_slot_versions_for_tests(&program_id),
1819 &[
1820 closed_other_slot,
1821 unloaded_current_env,
1822 unloaded_upcoming_env
1823 ]
1824 );
1825 }
1826
1827 #[test]
1828 fn test_assign_program_reload_merges_statistics() {
1829 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1830 let env = get_mock_program_runtime_environment();
1831 let program_id = Pubkey::new_unique();
1832
1833 let stats = Arc::new(ProgramStatistics {
1834 uses: 1.into(),
1835 compilations: 2.into(),
1836 total_compilation_time_us: 3.into(),
1837 compilation_time_ema: 100.into(),
1838 jit_invocations: 4.into(),
1839 total_jit_execution_time_us: 5.into(),
1840 jit_execution_time_ema: 200.into(),
1841 interpreted_invocations: 6.into(),
1842 total_interpretation_time_us: 7.into(),
1843 interpretation_time_ema: 300.into(),
1844 });
1845 let unloaded = Arc::new(ProgramCacheEntry {
1846 program: ProgramCacheEntryType::Unloaded(env.clone()),
1847 account_owner: ProgramCacheEntryOwner::LoaderV3,
1848 deployment_slot: 100,
1849 stats: Arc::clone(&stats),
1850 latest_access_slot: AtomicU64::default(),
1851 });
1852 cache.assign_program(&env, program_id, 100, unloaded);
1853
1854 let loaded = Arc::new(ProgramCacheEntry {
1856 program: new_loaded_entry(env.clone()),
1857 account_owner: ProgramCacheEntryOwner::LoaderV3,
1858 deployment_slot: 100,
1859 stats: Arc::default(), latest_access_slot: AtomicU64::default(),
1861 });
1862 cache.assign_program(&env, program_id, 100, Arc::clone(&loaded));
1863
1864 assert_eq!(cache.stats.insertions.load(Ordering::Relaxed), 1);
1865 assert_eq!(cache.stats.reloads.load(Ordering::Relaxed), 1);
1866
1867 let merged = &loaded.stats;
1868 let ord = Ordering::Relaxed;
1869 assert_eq!(merged.uses.load(ord), stats.uses.load(ord));
1870 assert_eq!(merged.compilations.load(ord), stats.compilations.load(ord));
1871 assert_eq!(
1872 merged.total_compilation_time_us.load(ord),
1873 stats.total_compilation_time_us.load(ord)
1874 );
1875 assert_eq!(
1876 merged.jit_invocations.load(ord),
1877 stats.jit_invocations.load(ord)
1878 );
1879 assert_eq!(
1880 merged.total_jit_execution_time_us.load(ord),
1881 stats.total_jit_execution_time_us.load(ord)
1882 );
1883 assert_eq!(
1884 merged.interpreted_invocations.load(ord),
1885 stats.interpreted_invocations.load(ord)
1886 );
1887 assert_eq!(
1888 merged.total_interpretation_time_us.load(ord),
1889 stats.total_interpretation_time_us.load(ord)
1890 );
1891
1892 const EMA_DIVISOR: u64 = 2;
1895 assert_eq!(
1896 merged.compilation_time_ema.load(ord),
1897 stats
1898 .compilation_time_ema
1899 .load(ord)
1900 .wrapping_div(EMA_DIVISOR)
1901 );
1902 assert_eq!(
1903 merged.jit_execution_time_ema.load(ord),
1904 stats
1905 .jit_execution_time_ema
1906 .load(ord)
1907 .wrapping_div(EMA_DIVISOR)
1908 );
1909 assert_eq!(
1910 merged.interpretation_time_ema.load(ord),
1911 stats
1912 .interpretation_time_ema
1913 .load(ord)
1914 .wrapping_div(EMA_DIVISOR)
1915 );
1916 }
1917
1918 #[test]
1919 fn test_tombstone() {
1920 let env = get_mock_program_runtime_environment();
1921 let tombstone = ProgramCacheEntry::new_failed_verification_tombstone(
1922 0,
1923 ProgramCacheEntryOwner::LoaderV2,
1924 env.clone(),
1925 );
1926 assert_matches!(
1927 tombstone.program,
1928 ProgramCacheEntryType::FailedVerification(_)
1929 );
1930 assert!(tombstone.is_tombstone());
1931 assert_eq!(tombstone.deployment_slot, 0);
1932 assert_eq!(tombstone.effective_slot(), 0);
1933
1934 let tombstone =
1935 ProgramCacheEntry::new_closed_tombstone(100, ProgramCacheEntryOwner::LoaderV2);
1936 assert_matches!(tombstone.program, ProgramCacheEntryType::Closed);
1937 assert!(tombstone.is_tombstone());
1938 assert_eq!(tombstone.deployment_slot, 100);
1939 assert_eq!(tombstone.effective_slot(), 100);
1940
1941 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1942 let program1 = Pubkey::new_unique();
1943 let tombstone = set_failed_verification_tombstone(&mut cache, program1, 10, env.clone());
1944 let slot_versions = cache.get_slot_versions_for_tests(&program1);
1945 assert_eq!(slot_versions.len(), 1);
1946 assert!(slot_versions.first().unwrap().is_tombstone());
1947 assert_eq!(tombstone.deployment_slot, 10);
1948 assert_eq!(tombstone.effective_slot(), 10);
1949
1950 let program2 = Pubkey::new_unique();
1952 cache.assign_program(&env, program2, 50, new_test_builtin_entry(50));
1953 let slot_versions = cache.get_slot_versions_for_tests(&program2);
1954 assert_eq!(slot_versions.len(), 1);
1955 assert!(!slot_versions.first().unwrap().is_tombstone());
1956
1957 let tombstone = set_failed_verification_tombstone(&mut cache, program2, 60, env);
1958 let slot_versions = cache.get_slot_versions_for_tests(&program2);
1959 assert_eq!(slot_versions.len(), 2);
1960 assert!(!slot_versions.first().unwrap().is_tombstone());
1961 assert!(slot_versions.get(1).unwrap().is_tombstone());
1962 assert!(tombstone.is_tombstone());
1963 assert_eq!(tombstone.deployment_slot, 60);
1964 assert_eq!(tombstone.effective_slot(), 60);
1965 }
1966
1967 struct TestForkGraph {
1968 relation: BlockRelation,
1969 }
1970 impl ForkGraph for TestForkGraph {
1971 fn relationship(&self, _a: Slot, _b: Slot) -> BlockRelation {
1972 self.relation
1973 }
1974 }
1975
1976 #[test]
1977 fn test_prune_empty() {
1978 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1979 let fork_graph = Arc::new(RwLock::new(TestForkGraph {
1980 relation: BlockRelation::Unrelated,
1981 }));
1982
1983 cache.set_fork_graph(Arc::downgrade(&fork_graph));
1984
1985 cache.prune(0, None, &fork_graph.read().unwrap());
1986 assert!(cache.get_flattened_entries_for_tests().is_empty());
1987
1988 cache.prune(10, None, &fork_graph.read().unwrap());
1989 assert!(cache.get_flattened_entries_for_tests().is_empty());
1990
1991 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1992 let fork_graph = Arc::new(RwLock::new(TestForkGraph {
1993 relation: BlockRelation::Ancestor,
1994 }));
1995
1996 cache.set_fork_graph(Arc::downgrade(&fork_graph));
1997
1998 cache.prune(0, None, &fork_graph.read().unwrap());
1999 assert!(cache.get_flattened_entries_for_tests().is_empty());
2000
2001 cache.prune(10, None, &fork_graph.read().unwrap());
2002 assert!(cache.get_flattened_entries_for_tests().is_empty());
2003
2004 let mut cache = ProgramCache::<TestForkGraph>::new(0);
2005 let fork_graph = Arc::new(RwLock::new(TestForkGraph {
2006 relation: BlockRelation::Descendant,
2007 }));
2008
2009 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2010
2011 cache.prune(0, None, &fork_graph.read().unwrap());
2012 assert!(cache.get_flattened_entries_for_tests().is_empty());
2013
2014 cache.prune(10, None, &fork_graph.read().unwrap());
2015 assert!(cache.get_flattened_entries_for_tests().is_empty());
2016
2017 let mut cache = ProgramCache::<TestForkGraph>::new(0);
2018 let fork_graph = Arc::new(RwLock::new(TestForkGraph {
2019 relation: BlockRelation::Unknown,
2020 }));
2021 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2022
2023 cache.prune(0, None, &fork_graph.read().unwrap());
2024 assert!(cache.get_flattened_entries_for_tests().is_empty());
2025
2026 cache.prune(10, None, &fork_graph.read().unwrap());
2027 assert!(cache.get_flattened_entries_for_tests().is_empty());
2028 }
2029
2030 #[test]
2031 fn test_prune_removes_programs_with_no_entries() {
2032 let (mut cache, fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Unknown);
2033 let env = get_mock_program_runtime_environment();
2034 let emptied = Pubkey::new_unique();
2035 cache.assign_program(
2036 &env,
2037 emptied,
2038 100,
2039 new_test_entry_with_owner(
2040 100,
2041 ProgramCacheEntryOwner::LoaderV3,
2042 new_loaded_entry(env.clone()),
2043 ),
2044 );
2045
2046 cache.prune(50, None, &fork_graph.read().unwrap());
2048 match &cache.index {
2049 IndexImplementation::V1 { entries, .. } => assert!(!entries.contains_key(&emptied)),
2050 }
2051 assert_eq!(cache.stats.empty_entries.load(Ordering::Relaxed), 1);
2052 assert_eq!(cache.stats.prunes_orphan.load(Ordering::Relaxed), 1);
2053 }
2054
2055 #[test]
2056 fn test_prune_across_the_root() {
2057 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
2063 let mut fork_graph = TestForkGraphSpecific::default();
2064 fork_graph.insert_fork(&[30, 50, 70]);
2065 let fork_graph = Arc::new(RwLock::new(fork_graph));
2066 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2067
2068 let env = get_mock_program_runtime_environment();
2069 let program_id = Pubkey::new_unique();
2070 let below = new_test_entry_with_owner(
2071 30,
2072 ProgramCacheEntryOwner::LoaderV3,
2073 new_loaded_entry(env.clone()),
2074 );
2075 let above = new_test_entry_with_owner(
2076 70,
2077 ProgramCacheEntryOwner::LoaderV3,
2078 new_loaded_entry(env.clone()),
2079 );
2080 cache.assign_program(&env, program_id, 30, Arc::clone(&below));
2081 cache.assign_program(&env, program_id, 70, Arc::clone(&above));
2082
2083 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
2084 assert_eq!(slot_versions.len(), 2);
2085 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &below));
2086 assert!(Arc::ptr_eq(slot_versions.get(1).unwrap(), &above));
2087
2088 cache.prune(50, None, &fork_graph.read().unwrap());
2089
2090 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
2092 assert_eq!(slot_versions.len(), 2);
2093 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &below));
2094 assert!(Arc::ptr_eq(slot_versions.get(1).unwrap(), &above));
2095 }
2096
2097 #[test]
2098 fn test_prune_across_the_root_ancestors() {
2099 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
2105 let mut fork_graph = TestForkGraphSpecific::default();
2106 fork_graph.insert_fork(&[10, 20, 30, 50, 70]);
2107 let fork_graph = Arc::new(RwLock::new(fork_graph));
2108 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2109
2110 let env = get_mock_program_runtime_environment();
2111 let program_id = Pubkey::new_unique();
2112 let oldest = new_test_entry_with_owner(
2113 10,
2114 ProgramCacheEntryOwner::LoaderV3,
2115 new_loaded_entry(env.clone()),
2116 );
2117 let older = new_test_entry_with_owner(
2118 20,
2119 ProgramCacheEntryOwner::LoaderV3,
2120 new_loaded_entry(env.clone()),
2121 );
2122 let below = new_test_entry_with_owner(
2123 30,
2124 ProgramCacheEntryOwner::LoaderV3,
2125 new_loaded_entry(env.clone()),
2126 );
2127 let above = new_test_entry_with_owner(
2128 70,
2129 ProgramCacheEntryOwner::LoaderV3,
2130 new_loaded_entry(env.clone()),
2131 );
2132 cache.assign_program(&env, program_id, 10, Arc::clone(&oldest));
2133 cache.assign_program(&env, program_id, 20, Arc::clone(&older));
2134 cache.assign_program(&env, program_id, 30, Arc::clone(&below));
2135 cache.assign_program(&env, program_id, 70, Arc::clone(&above));
2136
2137 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
2138 assert_eq!(slot_versions.len(), 4);
2139 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &oldest));
2140 assert!(Arc::ptr_eq(slot_versions.get(1).unwrap(), &older));
2141 assert!(Arc::ptr_eq(slot_versions.get(2).unwrap(), &below));
2142 assert!(Arc::ptr_eq(slot_versions.get(3).unwrap(), &above));
2143
2144 cache.prune(50, None, &fork_graph.read().unwrap());
2145
2146 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
2149 assert_eq!(slot_versions.len(), 2);
2150 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &below));
2151 assert!(Arc::ptr_eq(slot_versions.get(1).unwrap(), &above));
2152 assert_eq!(cache.stats.prunes_orphan.load(Ordering::Relaxed), 2);
2153 }
2154
2155 #[test]
2156 fn test_prune_entry_older_than_root() {
2157 let (mut cache, fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Unknown);
2171 let env = get_mock_program_runtime_environment();
2172 let program_id = Pubkey::new_unique();
2173 let entry = new_test_entry_with_owner(
2174 5,
2175 ProgramCacheEntryOwner::LoaderV3,
2176 new_loaded_entry(env.clone()),
2177 );
2178 cache.assign_program(&env, program_id, 5, Arc::clone(&entry));
2179 cache.latest_root_slot = 10;
2180
2181 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
2182 assert_eq!(slot_versions.len(), 1);
2183 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &entry));
2184
2185 cache.prune(20, None, &fork_graph.read().unwrap());
2186
2187 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
2191 assert_eq!(slot_versions.len(), 1);
2192 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &entry));
2193 assert_eq!(cache.stats.prunes_orphan.load(Ordering::Relaxed), 0);
2194 }
2195
2196 #[test]
2197 fn test_prune_orphan_newer_than_root() {
2198 let (mut cache, fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Unknown);
2208 let env = get_mock_program_runtime_environment();
2209 let program_id = Pubkey::new_unique();
2210 let orphan = new_test_entry_with_owner(
2211 100,
2212 ProgramCacheEntryOwner::LoaderV3,
2213 new_loaded_entry(env.clone()),
2214 );
2215 cache.assign_program(&env, program_id, 100, Arc::clone(&orphan));
2216
2217 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
2218 assert_eq!(slot_versions.len(), 1);
2219 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &orphan));
2220
2221 cache.prune(50, None, &fork_graph.read().unwrap());
2222
2223 assert!(cache.get_slot_versions_for_tests(&program_id).is_empty());
2226 assert_eq!(cache.stats.prunes_orphan.load(Ordering::Relaxed), 1);
2227 }
2228
2229 #[test]
2230 fn test_prune_tombstones() {
2231 let env = get_mock_program_runtime_environment();
2232 let fork_graph = Arc::new(RwLock::new(TestForkGraph {
2233 relation: BlockRelation::Ancestor,
2234 }));
2235
2236 let program1 = Pubkey::new_unique();
2237 let entries = [
2238 Arc::new(ProgramCacheEntry::new_unloaded(
2239 20,
2240 ProgramCacheEntryOwner::LoaderV3,
2241 ProgramRuntimeEnvironment::clone(&env),
2242 )),
2243 Arc::new(ProgramCacheEntry::new_closed_tombstone(
2244 20,
2245 ProgramCacheEntryOwner::LoaderV3,
2246 )),
2247 Arc::new(ProgramCacheEntry::new_failed_verification_tombstone(
2248 20,
2249 ProgramCacheEntryOwner::LoaderV3,
2250 ProgramRuntimeEnvironment::clone(&env),
2251 )),
2252 ];
2253 for entry in &entries {
2254 let mut cache = ProgramCache::<TestForkGraph>::new(0);
2255 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2256 cache.assign_program(&env, program1, 10, new_test_entry(10));
2258 cache.assign_program(&env, program1, entry.deployment_slot, Arc::clone(entry));
2259 cache.prune(
2260 MAX_TOMBSTONE_AGE_IN_SLOTS,
2261 None,
2262 &fork_graph.read().unwrap(),
2263 );
2264 let slot_versions = cache.get_slot_versions_for_tests(&program1);
2265 assert_eq!(slot_versions, std::slice::from_ref(entry));
2266 cache.prune(
2268 MAX_TOMBSTONE_AGE_IN_SLOTS
2269 .saturating_add(entry.latest_access_slot.load(Ordering::Relaxed)),
2270 None,
2271 &fork_graph.read().unwrap(),
2272 );
2273 let slot_versions = cache.get_slot_versions_for_tests(&program1);
2274 assert_eq!(slot_versions, std::slice::from_ref(entry));
2275 cache.prune(
2277 MAX_TOMBSTONE_AGE_IN_SLOTS
2278 .saturating_add(entry.latest_access_slot.load(Ordering::Relaxed))
2279 .saturating_add(1),
2280 None,
2281 &fork_graph.read().unwrap(),
2282 );
2283 assert!(cache.get_flattened_entries_for_tests().is_empty());
2284 }
2285 }
2286
2287 #[test]
2288 fn test_prune_tombstone_first_ancestor_takes_the_rest() {
2289 let (mut cache, fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
2298 let env = get_mock_program_runtime_environment();
2299 let other_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
2300 let program_id = Pubkey::new_unique();
2301 let on_other_env = new_test_entry_with_owner(
2302 50,
2303 ProgramCacheEntryOwner::LoaderV3,
2304 new_loaded_entry(other_env.clone()),
2305 );
2306 let on_env = new_test_entry_with_owner(
2307 60,
2308 ProgramCacheEntryOwner::LoaderV3,
2309 new_loaded_entry(env.clone()),
2310 );
2311 let closed = new_test_entry_with_owner(
2312 100,
2313 ProgramCacheEntryOwner::LoaderV3,
2314 new_closed_entry(env.clone()),
2315 );
2316 cache.assign_program(&other_env, program_id, 50, Arc::clone(&on_other_env));
2317 cache.assign_program(&env, program_id, 60, Arc::clone(&on_env));
2318 cache.assign_program(&env, program_id, 100, Arc::clone(&closed));
2319
2320 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
2321 assert_eq!(slot_versions.len(), 3);
2322 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &on_other_env));
2323 assert!(Arc::ptr_eq(slot_versions.get(1).unwrap(), &on_env));
2324 assert!(Arc::ptr_eq(slot_versions.get(2).unwrap(), &closed));
2325
2326 cache.prune(200, None, &fork_graph.read().unwrap());
2327
2328 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
2332 assert_eq!(slot_versions.len(), 1);
2333 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &closed));
2334 assert_eq!(cache.stats.prunes_orphan.load(Ordering::Relaxed), 2);
2335 }
2336
2337 #[test]
2338 fn test_prune_tombstone_newer_than_root_keeps_the_rest() {
2339 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
2349 let mut fork_graph = TestForkGraphSpecific::default();
2350 fork_graph.insert_fork(&[50, 60, 100, 101]);
2351 let fork_graph = Arc::new(RwLock::new(fork_graph));
2352 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2353
2354 let env = get_mock_program_runtime_environment();
2355 let other_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
2356 let program_id = Pubkey::new_unique();
2357 let on_other_env = new_test_entry_with_owner(
2358 50,
2359 ProgramCacheEntryOwner::LoaderV3,
2360 new_loaded_entry(other_env.clone()),
2361 );
2362 let on_env = new_test_entry_with_owner(
2363 60,
2364 ProgramCacheEntryOwner::LoaderV3,
2365 new_loaded_entry(env.clone()),
2366 );
2367 let closed = new_test_entry_with_owner(
2368 101,
2369 ProgramCacheEntryOwner::LoaderV3,
2370 new_closed_entry(env.clone()),
2371 );
2372 cache.assign_program(&other_env, program_id, 50, Arc::clone(&on_other_env));
2373 cache.assign_program(&env, program_id, 60, Arc::clone(&on_env));
2374 cache.assign_program(&env, program_id, 101, Arc::clone(&closed));
2375
2376 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
2377 assert_eq!(slot_versions.len(), 3);
2378 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &on_other_env));
2379 assert!(Arc::ptr_eq(slot_versions.get(1).unwrap(), &on_env));
2380 assert!(Arc::ptr_eq(slot_versions.get(2).unwrap(), &closed));
2381
2382 cache.prune(100, None, &fork_graph.read().unwrap());
2383
2384 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
2389 assert_eq!(slot_versions.len(), 3);
2390 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &on_other_env));
2391 assert!(Arc::ptr_eq(slot_versions.get(1).unwrap(), &on_env));
2392 assert!(Arc::ptr_eq(slot_versions.get(2).unwrap(), &closed));
2393 assert_eq!(cache.stats.prunes_orphan.load(Ordering::Relaxed), 0);
2394 }
2395
2396 #[test]
2397 fn test_prune_with_two_environments_before_epoch_boundary() {
2398 let mut cache = ProgramCache::<TestForkGraph>::new(0);
2399 let env = get_mock_program_runtime_environment();
2400 let new_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
2401 let fork_graph = Arc::new(RwLock::new(TestForkGraph {
2402 relation: BlockRelation::Ancestor,
2403 }));
2404 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2405
2406 let program1 = Pubkey::new_unique();
2407 cache.assign_program(&env, program1, 10, new_test_entry(10));
2408 let updated_program = Arc::new(ProgramCacheEntry {
2409 program: new_loaded_entry(new_env.clone()),
2410 deployment_slot: 20,
2411 ..Default::default()
2412 });
2413 cache.assign_program(&env, program1, 20, updated_program.clone());
2414
2415 assert_eq!(cache.get_slot_versions_for_tests(&program1).len(), 2);
2417
2418 cache.prune(21, None, &fork_graph.read().unwrap());
2419
2420 assert_eq!(cache.get_slot_versions_for_tests(&program1).len(), 2);
2422 }
2423
2424 #[test]
2425 fn test_prune_with_two_environments_after_epoch_boundary() {
2426 let mut cache = ProgramCache::<TestForkGraph>::new(0);
2427 let env = get_mock_program_runtime_environment();
2428 let new_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
2429 let fork_graph = Arc::new(RwLock::new(TestForkGraph {
2430 relation: BlockRelation::Ancestor,
2431 }));
2432 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2433 let program1 = Pubkey::new_unique();
2434
2435 let old_program_old_env = Arc::new(ProgramCacheEntry {
2436 program: new_loaded_entry(env.clone()),
2437 deployment_slot: 10,
2438 ..Default::default()
2439 });
2440 let old_program_new_env = Arc::new(ProgramCacheEntry {
2441 program: new_loaded_entry(new_env.clone()),
2442 deployment_slot: 10,
2443 ..Default::default()
2444 });
2445 let new_program_old_env = Arc::new(ProgramCacheEntry {
2446 program: new_loaded_entry(env.clone()),
2447 deployment_slot: 20,
2448 ..Default::default()
2449 });
2450 cache.assign_program(&env, program1, 10, old_program_old_env.clone());
2451 cache.assign_program(&env, program1, 10, old_program_new_env.clone());
2452 cache.assign_program(&env, program1, 20, new_program_old_env.clone());
2453 let slot_versions = cache.get_slot_versions_for_tests(&program1);
2454 assert_eq!(
2455 &slot_versions,
2456 &[
2457 old_program_new_env.clone(),
2458 old_program_old_env.clone(),
2459 new_program_old_env.clone(),
2460 ]
2461 );
2462
2463 cache.prune(21, Some(new_env.clone()), &fork_graph.read().unwrap());
2464 let slot_versions = cache.get_slot_versions_for_tests(&program1);
2465 assert_eq!(&slot_versions, &[old_program_new_env]);
2466 assert!(matches!(
2467 &slot_versions.first().unwrap().program,
2468 ProgramCacheEntryType::Loaded(_)
2469 ));
2470 }
2471
2472 #[test_matrix(
2473 (
2474 new_closed_entry,
2475 new_builtin_entry,
2476 new_failed_verification_entry,
2477 new_unloaded_entry,
2478 new_loaded_entry,
2479 ),
2480 (false, true)
2481 )]
2482 fn test_prune_environment_sweep_by_entry_type(
2483 new_program: fn(ProgramRuntimeEnvironment) -> ProgramCacheEntryType,
2484 on_new_environment: bool,
2485 ) {
2486 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
2496 let mut fork_graph = TestForkGraphSpecific::default();
2497 fork_graph.insert_fork(&[40, 50]);
2498 let fork_graph = Arc::new(RwLock::new(fork_graph));
2499 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2500
2501 let env = get_mock_program_runtime_environment();
2502 let new_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
2503 let entry_env = if on_new_environment {
2504 new_env.clone()
2505 } else {
2506 env.clone()
2507 };
2508 let program_id = Pubkey::new_unique();
2509 let entry = new_test_entry_with_owner(
2510 50,
2511 ProgramCacheEntryOwner::LoaderV3,
2512 new_program(entry_env.clone()),
2513 );
2514 let carries_an_environment = entry.program.get_environment().is_some();
2515 cache.assign_program(&entry_env, program_id, 50, Arc::clone(&entry));
2516
2517 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
2518 assert_eq!(slot_versions.len(), 1);
2519 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &entry));
2520
2521 cache.prune(40, Some(new_env.clone()), &fork_graph.read().unwrap());
2522
2523 let swept = carries_an_environment && !on_new_environment;
2526 assert_eq!(
2527 cache.stats.prunes_environment.load(Ordering::Relaxed),
2528 u64::from(swept)
2529 );
2530 if swept {
2531 assert!(cache.get_slot_versions_for_tests(&program_id).is_empty());
2532 } else {
2533 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
2534 assert_eq!(slot_versions.len(), 1);
2535 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &entry));
2536 }
2537 }
2538
2539 #[test]
2540 fn test_prune_environment_sweep_keeps_tombstones() {
2541 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
2553 let mut fork_graph = TestForkGraphSpecific::default();
2554 fork_graph.insert_fork(&[0, 40, 50, 60, 70, 100]);
2555 let fork_graph = Arc::new(RwLock::new(fork_graph));
2556 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2557 let env = get_mock_program_runtime_environment();
2558 let new_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
2559 let program_id = Pubkey::new_unique();
2560 let closed = new_test_entry_with_owner(
2561 50,
2562 ProgramCacheEntryOwner::LoaderV3,
2563 new_closed_entry(env.clone()),
2564 );
2565 let failed_verification = new_test_entry_with_owner(
2566 60,
2567 ProgramCacheEntryOwner::LoaderV3,
2568 new_failed_verification_entry(env.clone()),
2570 );
2571 let loaded = new_test_entry_with_owner(
2572 70,
2573 ProgramCacheEntryOwner::LoaderV3,
2574 new_loaded_entry(env.clone()),
2575 );
2576 cache.assign_program(&env, program_id, 50, Arc::clone(&closed));
2577 cache.assign_program(&env, program_id, 60, Arc::clone(&failed_verification));
2578 cache.assign_program(&env, program_id, 70, Arc::clone(&loaded));
2579
2580 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
2581 assert_eq!(slot_versions.len(), 3);
2582 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &closed));
2583 assert!(Arc::ptr_eq(
2584 slot_versions.get(1).unwrap(),
2585 &failed_verification
2586 ));
2587 assert!(Arc::ptr_eq(slot_versions.get(2).unwrap(), &loaded));
2588
2589 cache.prune(40, Some(new_env.clone()), &fork_graph.read().unwrap());
2592 assert_eq!(cache.stats.prunes_environment.load(Ordering::Relaxed), 2);
2593 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
2594 assert_eq!(slot_versions.len(), 1);
2595 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &closed));
2596
2597 let mut search_for = vec![ProgramToLoad {
2599 program_id: &program_id,
2600 loader: ProgramCacheEntryOwner::LoaderV3,
2601 deployment_slot: 50,
2602 }];
2603 let mut extracted = ProgramCacheForTxBatch::new(100);
2604 cache.extract(&mut search_for, &mut extracted, &new_env, true, true);
2605 assert!(search_for.is_empty());
2606 assert!(Arc::ptr_eq(
2607 extracted.entries.get(&program_id).unwrap(),
2608 &closed
2609 ));
2610
2611 let mut search_for = vec![ProgramToLoad {
2615 program_id: &program_id,
2616 loader: ProgramCacheEntryOwner::LoaderV3,
2617 deployment_slot: 50,
2618 }];
2619 let mut extracted = ProgramCacheForTxBatch::new(100);
2620 cache.extract(&mut search_for, &mut extracted, &env, true, true);
2621 assert!(search_for.is_empty());
2622 assert!(Arc::ptr_eq(
2623 extracted.entries.get(&program_id).unwrap(),
2624 &closed
2625 ));
2626 }
2627
2628 #[test]
2629 #[should_panic(expected = "self.latest_root_slot <= new_root_slot")]
2630 fn test_prune_backwards_panics() {
2631 let (mut cache, fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
2632 cache.latest_root_slot = 100;
2633
2634 cache.prune(50, None, &fork_graph.read().unwrap());
2637 }
2638
2639 #[derive(Default)]
2640 struct TestForkGraphSpecific {
2641 forks: Vec<Vec<Slot>>,
2642 }
2643
2644 impl TestForkGraphSpecific {
2645 fn insert_fork(&mut self, fork: &[Slot]) {
2646 let mut fork = fork.to_vec();
2647 fork.sort();
2648 self.forks.push(fork)
2649 }
2650 }
2651
2652 impl ForkGraph for TestForkGraphSpecific {
2653 fn relationship(&self, a: Slot, b: Slot) -> BlockRelation {
2654 match self.forks.iter().try_for_each(|fork| {
2655 let relation = fork
2656 .iter()
2657 .position(|x| *x == a)
2658 .and_then(|a_pos| {
2659 fork.iter().position(|x| *x == b).and_then(|b_pos| {
2660 (a_pos == b_pos)
2661 .then_some(BlockRelation::Equal)
2662 .or_else(|| (a_pos < b_pos).then_some(BlockRelation::Ancestor))
2663 .or(Some(BlockRelation::Descendant))
2664 })
2665 })
2666 .unwrap_or(BlockRelation::Unrelated);
2667
2668 if relation != BlockRelation::Unrelated {
2669 return ControlFlow::Break(relation);
2670 }
2671
2672 ControlFlow::Continue(())
2673 }) {
2674 ControlFlow::Break(relation) => relation,
2675 _ => BlockRelation::Unrelated,
2676 }
2677 }
2678 }
2679
2680 fn get_entries_to_load<'a>(
2681 cache: &ProgramCache<TestForkGraphSpecific>,
2682 loading_slot: Slot,
2683 keys: &'a [Pubkey],
2684 ) -> Vec<ProgramToLoad<'a>> {
2685 let fork_graph = cache.fork_graph.as_ref().unwrap().upgrade().unwrap();
2686 let locked_fork_graph = fork_graph.read().unwrap();
2687 let entries = cache.get_flattened_entries_for_tests();
2688 keys.iter()
2689 .filter_map(|key| {
2690 entries
2691 .iter()
2692 .rev()
2693 .find(|(program_id, entry)| {
2694 program_id == key
2695 && matches!(
2696 locked_fork_graph.relationship(entry.deployment_slot, loading_slot),
2697 BlockRelation::Equal | BlockRelation::Ancestor,
2698 )
2699 })
2700 .map(|(_program_id, entry)| ProgramToLoad {
2701 program_id: key,
2702 loader: entry.account_owner,
2703 deployment_slot: entry.deployment_slot,
2704 })
2705 })
2706 .collect()
2707 }
2708
2709 fn match_slot(
2710 extracted: &ProgramCacheForTxBatch,
2711 program: &Pubkey,
2712 deployment_slot: Slot,
2713 working_slot: Slot,
2714 ) -> bool {
2715 assert_eq!(extracted.slot, working_slot);
2716 extracted
2717 .entries
2718 .get(program)
2719 .map(|entry| entry.deployment_slot == deployment_slot)
2720 .unwrap_or(false)
2721 }
2722
2723 fn match_missing(
2724 missing: &[ProgramToLoad],
2725 program_id: &Pubkey,
2726 expected_result: bool,
2727 ) -> bool {
2728 missing.iter().any(|entry| entry.program_id == program_id) == expected_result
2729 }
2730
2731 #[test]
2732 fn test_fork_extract_and_prune() {
2733 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
2734 let env = get_mock_program_runtime_environment();
2735
2736 let mut fork_graph = TestForkGraphSpecific::default();
2752 fork_graph.insert_fork(&[0, 10, 20, 22]);
2753 fork_graph.insert_fork(&[0, 5, 11, 15, 16, 18, 19, 21, 23]);
2754 fork_graph.insert_fork(&[0, 5, 11, 25, 27]);
2755
2756 let fork_graph = Arc::new(RwLock::new(fork_graph));
2757 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2758
2759 let program1 = Pubkey::new_unique();
2760 cache.assign_program(&env, program1, 0, new_test_entry(0));
2761 cache.assign_program(&env, program1, 10, new_test_entry(10));
2762 cache.assign_program(&env, program1, 20, new_test_entry(20));
2763
2764 let program2 = Pubkey::new_unique();
2765 cache.assign_program(&env, program2, 5, new_test_entry(5));
2766 cache.assign_program(&env, program2, 11, new_test_entry(11));
2767
2768 let program3 = Pubkey::new_unique();
2769 cache.assign_program(&env, program3, 25, new_test_entry(25));
2770
2771 let program4 = Pubkey::new_unique();
2772 cache.assign_program(&env, program4, 0, new_test_entry(0));
2773 cache.assign_program(&env, program4, 5, new_test_entry(5));
2774 cache.assign_program(&env, program4, 15, new_test_entry(15));
2776
2777 let keys = &[program1, program2, program3, program4];
2794 let mut missing = get_entries_to_load(&cache, 22, keys);
2795 assert!(match_missing(&missing, &program2, false));
2796 assert!(match_missing(&missing, &program3, false));
2797 let mut extracted = ProgramCacheForTxBatch::new(22);
2798 cache.extract(&mut missing, &mut extracted, &env, true, true);
2799 assert!(match_slot(&extracted, &program1, 20, 22));
2800 assert!(match_slot(&extracted, &program4, 0, 22));
2801
2802 let mut missing = get_entries_to_load(&cache, 15, keys);
2804 assert!(match_missing(&missing, &program3, false));
2805 let mut extracted = ProgramCacheForTxBatch::new(15);
2806 cache.extract(&mut missing, &mut extracted, &env, true, true);
2807 assert!(match_slot(&extracted, &program1, 0, 15));
2808 assert!(match_slot(&extracted, &program2, 11, 15));
2809 let tombstone = extracted
2812 .find(&program4)
2813 .expect("Failed to find the tombstone");
2814 assert_matches!(tombstone.program, ProgramCacheEntryType::DelayVisibility);
2815 assert_eq!(tombstone.deployment_slot, 15);
2816
2817 let mut missing = get_entries_to_load(&cache, 18, keys);
2819 assert!(match_missing(&missing, &program3, false));
2820 let mut extracted = ProgramCacheForTxBatch::new(18);
2821 cache.extract(&mut missing, &mut extracted, &env, true, true);
2822 assert!(match_slot(&extracted, &program1, 0, 18));
2823 assert!(match_slot(&extracted, &program2, 11, 18));
2824 assert!(match_slot(&extracted, &program4, 15, 18));
2826
2827 let mut missing = get_entries_to_load(&cache, 23, keys);
2829 assert!(match_missing(&missing, &program3, false));
2830 let mut extracted = ProgramCacheForTxBatch::new(23);
2831 cache.extract(&mut missing, &mut extracted, &env, true, true);
2832 assert!(match_slot(&extracted, &program1, 0, 23));
2833 assert!(match_slot(&extracted, &program2, 11, 23));
2834 assert!(match_slot(&extracted, &program4, 15, 23));
2836
2837 let mut missing = get_entries_to_load(&cache, 11, keys);
2839 assert!(match_missing(&missing, &program3, false));
2840 let mut extracted = ProgramCacheForTxBatch::new(11);
2841 cache.extract(&mut missing, &mut extracted, &env, true, true);
2842 assert!(match_slot(&extracted, &program1, 0, 11));
2843 let tombstone = extracted
2845 .find(&program2)
2846 .expect("Failed to find the tombstone");
2847 assert_matches!(tombstone.program, ProgramCacheEntryType::DelayVisibility);
2848 assert_eq!(tombstone.deployment_slot, 11);
2849 assert!(match_slot(&extracted, &program4, 5, 11));
2850
2851 cache.prune(5, None, &fork_graph.read().unwrap());
2852
2853 let mut missing = get_entries_to_load(&cache, 21, keys);
2870 assert!(match_missing(&missing, &program3, false));
2871 let mut extracted = ProgramCacheForTxBatch::new(21);
2872 cache.extract(&mut missing, &mut extracted, &env, true, true);
2873 assert!(match_slot(&extracted, &program1, 0, 21));
2875 assert!(match_slot(&extracted, &program2, 11, 21));
2876 assert!(match_slot(&extracted, &program4, 15, 21));
2877
2878 let mut missing = get_entries_to_load(&cache, 27, keys);
2880 let mut extracted = ProgramCacheForTxBatch::new(27);
2881 cache.extract(&mut missing, &mut extracted, &env, true, true);
2882 assert!(match_slot(&extracted, &program1, 0, 27));
2883 assert!(match_slot(&extracted, &program2, 11, 27));
2884 assert!(match_slot(&extracted, &program3, 25, 27));
2885 assert!(match_slot(&extracted, &program4, 5, 27));
2886
2887 cache.prune(15, None, &fork_graph.read().unwrap());
2888
2889 let mut missing = get_entries_to_load(&cache, 23, keys);
2906 assert!(match_missing(&missing, &program3, false));
2907 let mut extracted = ProgramCacheForTxBatch::new(23);
2908 cache.extract(&mut missing, &mut extracted, &env, true, true);
2909 assert!(match_slot(&extracted, &program1, 0, 23));
2910 assert!(match_slot(&extracted, &program2, 11, 23));
2911 assert!(match_slot(&extracted, &program4, 15, 23));
2912 }
2913
2914 #[test]
2915 fn test_extract_using_deployment_slot() {
2916 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
2917 let env = get_mock_program_runtime_environment();
2918
2919 let mut fork_graph = TestForkGraphSpecific::default();
2935 fork_graph.insert_fork(&[0, 10, 20, 22]);
2936 fork_graph.insert_fork(&[0, 5, 11, 12, 15, 16, 18, 19, 21, 23]);
2937 fork_graph.insert_fork(&[0, 5, 11, 25, 27]);
2938
2939 let fork_graph = Arc::new(RwLock::new(fork_graph));
2940 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2941
2942 let program1 = Pubkey::new_unique();
2943 cache.assign_program(&env, program1, 0, new_test_entry(0));
2944 cache.assign_program(&env, program1, 20, new_test_entry(20));
2945
2946 let program2 = Pubkey::new_unique();
2947 cache.assign_program(&env, program2, 5, new_test_entry(5));
2948 cache.assign_program(&env, program2, 11, new_test_entry(11));
2949
2950 let program3 = Pubkey::new_unique();
2951 cache.assign_program(&env, program3, 25, new_test_entry(25));
2952
2953 let keys = &[program1, program2, program3];
2955 let mut missing = get_entries_to_load(&cache, 12, keys);
2956 assert!(match_missing(&missing, &program3, false));
2957 let mut extracted = ProgramCacheForTxBatch::new(12);
2958 cache.extract(&mut missing, &mut extracted, &env, true, true);
2959 assert!(match_slot(&extracted, &program1, 0, 12));
2960 assert!(match_slot(&extracted, &program2, 11, 12));
2961
2962 let mut missing = get_entries_to_load(&cache, 12, keys);
2965 missing.get_mut(1).unwrap().deployment_slot = 5;
2968 assert!(match_missing(&missing, &program3, false));
2969 let mut extracted = ProgramCacheForTxBatch::new(12);
2970 cache.extract(&mut missing, &mut extracted, &env, true, true);
2971 assert!(match_slot(&extracted, &program1, 0, 12));
2972 assert!(match_slot(&extracted, &program2, 5, 12));
2973 }
2974
2975 #[test]
2976 fn test_extract_rejects_entry_deployed_after_the_requested_slot() {
2977 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
2978 let env = get_mock_program_runtime_environment();
2979
2980 let mut fork_graph = TestForkGraphSpecific::default();
2981 fork_graph.insert_fork(&[0, 5, 11, 12]);
2982 let fork_graph = Arc::new(RwLock::new(fork_graph));
2983 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2984
2985 let program = Pubkey::new_unique();
2987 cache.assign_program(&env, program, 11, new_test_entry(11));
2988
2989 let mut missing = vec![ProgramToLoad {
2991 program_id: &program,
2992 loader: ProgramCacheEntryOwner::LoaderV2,
2993 deployment_slot: 5,
2994 }];
2995 let mut extracted = ProgramCacheForTxBatch::new(12);
2996 cache.extract(&mut missing, &mut extracted, &env, true, true);
2997 assert!(match_missing(&missing, &program, true));
2998 assert!(extracted.find(&program).is_none());
2999
3000 let mut missing = vec![ProgramToLoad {
3002 program_id: &program,
3003 loader: ProgramCacheEntryOwner::LoaderV2,
3004 deployment_slot: 11,
3005 }];
3006 let mut extracted = ProgramCacheForTxBatch::new(12);
3007 cache.extract(&mut missing, &mut extracted, &env, true, true);
3008 assert!(match_missing(&missing, &program, false));
3009 assert!(match_slot(&extracted, &program, 11, 12));
3010 }
3011
3012 #[test]
3013 fn test_extract_unloaded() {
3014 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
3015 let env = get_mock_program_runtime_environment();
3016
3017 let mut fork_graph = TestForkGraphSpecific::default();
3033 fork_graph.insert_fork(&[0, 10, 20, 22]);
3034 fork_graph.insert_fork(&[0, 5, 11, 15, 16, 19, 21, 23]);
3035 fork_graph.insert_fork(&[0, 5, 11, 25, 27]);
3036
3037 let fork_graph = Arc::new(RwLock::new(fork_graph));
3038 cache.set_fork_graph(Arc::downgrade(&fork_graph));
3039
3040 let program1 = Pubkey::new_unique();
3041 cache.assign_program(&env, program1, 0, new_test_entry(0));
3042 cache.assign_program(&env, program1, 20, new_test_entry(20));
3043
3044 let program2 = Pubkey::new_unique();
3045 cache.assign_program(&env, program2, 5, new_test_entry(5));
3046 cache.assign_program(&env, program2, 11, new_test_entry(11));
3047
3048 let program3 = Pubkey::new_unique();
3049 let _ = insert_unloaded_entry(&mut cache, program3, 25);
3051
3052 cache.assign_program(
3056 &env,
3057 program3,
3058 20,
3059 Arc::new(
3060 new_test_entry(20)
3061 .to_unloaded()
3062 .expect("Failed to create unloaded program"),
3063 ),
3064 );
3065
3066 let keys = &[program1, program2, program3];
3068 let mut missing = get_entries_to_load(&cache, 19, keys);
3069 assert!(match_missing(&missing, &program3, false));
3070 let mut extracted = ProgramCacheForTxBatch::new(19);
3071 cache.extract(&mut missing, &mut extracted, &env, true, true);
3072 assert!(match_slot(&extracted, &program1, 0, 19));
3073 assert!(match_slot(&extracted, &program2, 11, 19));
3074
3075 let mut missing = get_entries_to_load(&cache, 27, keys);
3077 let mut extracted = ProgramCacheForTxBatch::new(27);
3078 cache.extract(&mut missing, &mut extracted, &env, true, true);
3079 assert!(match_slot(&extracted, &program1, 0, 27));
3080 assert!(match_slot(&extracted, &program2, 11, 27));
3081 assert!(match_missing(&missing, &program3, true));
3082
3083 let mut missing = get_entries_to_load(&cache, 22, keys);
3085 assert!(match_missing(&missing, &program2, false));
3086 let mut extracted = ProgramCacheForTxBatch::new(22);
3087 cache.extract(&mut missing, &mut extracted, &env, true, true);
3088 assert!(match_slot(&extracted, &program1, 20, 22));
3089 assert!(match_missing(&missing, &program3, true));
3090 }
3091
3092 #[test]
3093 fn test_extract_different_environment() {
3094 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
3095 let env = get_mock_program_runtime_environment();
3096 let other_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
3097
3098 let mut fork_graph = TestForkGraphSpecific::default();
3108 fork_graph.insert_fork(&[0, 10, 20, 22]);
3109
3110 let fork_graph = Arc::new(RwLock::new(fork_graph));
3111 cache.set_fork_graph(Arc::downgrade(&fork_graph));
3112
3113 let program1 = Pubkey::new_unique();
3114 cache.assign_program(
3115 &env,
3116 program1,
3117 10,
3118 Arc::new(ProgramCacheEntry::new_closed_tombstone(
3119 10,
3120 ProgramCacheEntryOwner::LoaderV3,
3121 )),
3122 );
3123 cache.assign_program(&env, program1, 20, new_test_entry(20));
3124
3125 let keys = &[program1];
3127 let mut missing = get_entries_to_load(&cache, 22, keys);
3128 let mut extracted = ProgramCacheForTxBatch::new(22);
3129 cache.extract(&mut missing, &mut extracted, &env, true, true);
3130 assert!(match_slot(&extracted, &program1, 20, 22));
3131
3132 let mut missing = get_entries_to_load(&cache, 22, keys);
3134 let mut extracted = ProgramCacheForTxBatch::new(22);
3135 cache.extract(&mut missing, &mut extracted, &other_env, true, true);
3136 assert!(match_missing(&missing, &program1, true));
3137 }
3138
3139 #[test_matrix((false, true))]
3140 fn test_extract_no_second_level(empty_second_level: bool) {
3141 let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
3142 let env = get_mock_program_runtime_environment();
3143 let program_id = Pubkey::new_unique();
3144 if empty_second_level {
3145 match &mut cache.index {
3147 IndexImplementation::V1 { entries, .. } => {
3148 entries.insert(program_id, Vec::new());
3149 }
3150 }
3151 }
3152
3153 let mut search_for = vec![ProgramToLoad {
3156 program_id: &program_id,
3157 loader: ProgramCacheEntryOwner::LoaderV3,
3158 deployment_slot: 0,
3159 }];
3160 let mut extracted = ProgramCacheForTxBatch::new(100);
3161 let task = cache.extract(&mut search_for, &mut extracted, &env, true, true);
3162 assert_eq!(search_for.len(), 1);
3163 assert!(extracted.entries.is_empty());
3164 assert_eq!(task, Some(program_id));
3165 }
3166
3167 #[test]
3168 fn test_extract_account_owner_mismatch() {
3169 let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
3170 let env = get_mock_program_runtime_environment();
3171 let program_id = Pubkey::new_unique();
3172 let owned_by_v2 = new_test_entry_with_owner(
3173 100,
3174 ProgramCacheEntryOwner::LoaderV2,
3175 new_loaded_entry(env.clone()),
3176 );
3177 cache.assign_program(&env, program_id, 100, Arc::clone(&owned_by_v2));
3178
3179 let mut search_for = vec![ProgramToLoad {
3182 program_id: &program_id,
3183 loader: ProgramCacheEntryOwner::LoaderV3,
3184 deployment_slot: 100,
3185 }];
3186 let mut extracted = ProgramCacheForTxBatch::new(200);
3187 cache.extract(&mut search_for, &mut extracted, &env, true, true);
3188 assert_eq!(search_for.len(), 1);
3189 assert!(extracted.entries.is_empty());
3190
3191 let owned_by_v3 = new_test_entry_with_owner(
3194 150,
3195 ProgramCacheEntryOwner::LoaderV3,
3196 new_loaded_entry(env.clone()),
3197 );
3198 cache.assign_program(&env, program_id, 150, Arc::clone(&owned_by_v3));
3199
3200 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
3202 assert_eq!(slot_versions.len(), 2);
3203 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &owned_by_v2));
3204 assert!(Arc::ptr_eq(slot_versions.get(1).unwrap(), &owned_by_v3));
3205
3206 let mut search_for = vec![ProgramToLoad {
3209 program_id: &program_id,
3210 loader: ProgramCacheEntryOwner::LoaderV2,
3211 deployment_slot: 100,
3212 }];
3213 let mut extracted = ProgramCacheForTxBatch::new(200);
3214 cache.extract(&mut search_for, &mut extracted, &env, true, true);
3215 assert!(search_for.is_empty());
3216 assert!(Arc::ptr_eq(
3217 extracted.entries.get(&program_id).unwrap(),
3218 &owned_by_v2
3219 ));
3220
3221 let mut search_for = vec![ProgramToLoad {
3224 program_id: &program_id,
3225 loader: ProgramCacheEntryOwner::LoaderV3,
3226 deployment_slot: 150,
3227 }];
3228 let mut extracted = ProgramCacheForTxBatch::new(200);
3229 cache.extract(&mut search_for, &mut extracted, &env, true, true);
3230 assert!(search_for.is_empty());
3231 assert!(Arc::ptr_eq(
3232 extracted.entries.get(&program_id).unwrap(),
3233 &owned_by_v3
3234 ));
3235 }
3236
3237 #[test]
3238 fn test_extract_environment_mismatch() {
3239 let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
3240 let env = get_mock_program_runtime_environment();
3241 let other_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
3242 let program_id = Pubkey::new_unique();
3243 let on_other_env = new_test_entry_with_owner(
3244 100,
3245 ProgramCacheEntryOwner::LoaderV3,
3246 new_loaded_entry(other_env.clone()),
3247 );
3248 cache.assign_program(&other_env, program_id, 100, Arc::clone(&on_other_env));
3249
3250 let mut search_for = vec![ProgramToLoad {
3255 program_id: &program_id,
3256 loader: ProgramCacheEntryOwner::LoaderV3,
3257 deployment_slot: 100,
3258 }];
3259 let mut extracted = ProgramCacheForTxBatch::new(200);
3260 cache.extract(&mut search_for, &mut extracted, &env, true, true);
3261 assert_eq!(search_for.len(), 1);
3262 assert!(extracted.entries.is_empty());
3263
3264 let on_execution_env = new_test_entry_with_owner(
3267 100,
3268 ProgramCacheEntryOwner::LoaderV3,
3269 new_loaded_entry(env.clone()),
3270 );
3271 cache.assign_program(&env, program_id, 100, Arc::clone(&on_execution_env));
3272
3273 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
3276 assert_eq!(slot_versions.len(), 2);
3277 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &on_other_env));
3278 assert!(Arc::ptr_eq(
3279 slot_versions.get(1).unwrap(),
3280 &on_execution_env
3281 ));
3282
3283 let mut search_for = vec![ProgramToLoad {
3286 program_id: &program_id,
3287 loader: ProgramCacheEntryOwner::LoaderV3,
3288 deployment_slot: 100,
3289 }];
3290 let mut extracted = ProgramCacheForTxBatch::new(200);
3291 cache.extract(&mut search_for, &mut extracted, &env, true, true);
3292 assert!(search_for.is_empty());
3293 assert!(Arc::ptr_eq(
3294 extracted.entries.get(&program_id).unwrap(),
3295 &on_execution_env
3296 ));
3297
3298 let mut search_for = vec![ProgramToLoad {
3301 program_id: &program_id,
3302 loader: ProgramCacheEntryOwner::LoaderV3,
3303 deployment_slot: 100,
3304 }];
3305 let mut extracted = ProgramCacheForTxBatch::new(200);
3306 cache.extract(&mut search_for, &mut extracted, &other_env, true, true);
3307 assert!(search_for.is_empty());
3308 assert!(Arc::ptr_eq(
3309 extracted.entries.get(&program_id).unwrap(),
3310 &on_other_env
3311 ));
3312 }
3313
3314 #[test]
3315 fn test_extract_unloaded_entry() {
3316 let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
3317 let env = get_mock_program_runtime_environment();
3318 let program_id = Pubkey::new_unique();
3319 let unloaded = new_test_entry_with_owner(
3320 100,
3321 ProgramCacheEntryOwner::LoaderV3,
3322 new_unloaded_entry(env.clone()),
3323 );
3324 cache.assign_program(&env, program_id, 100, Arc::clone(&unloaded));
3325
3326 let mut search_for = vec![ProgramToLoad {
3330 program_id: &program_id,
3331 loader: ProgramCacheEntryOwner::LoaderV3,
3332 deployment_slot: 100,
3333 }];
3334 let mut extracted = ProgramCacheForTxBatch::new(200);
3335 cache.extract(&mut search_for, &mut extracted, &env, true, true);
3336 assert_eq!(search_for.len(), 1);
3337 assert!(extracted.entries.is_empty());
3338
3339 let loaded = new_test_entry_with_owner(
3341 100,
3342 ProgramCacheEntryOwner::LoaderV3,
3343 new_loaded_entry(env.clone()),
3344 );
3345 cache.assign_program(&env, program_id, 100, Arc::clone(&loaded));
3346 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
3347 assert_eq!(slot_versions.len(), 1);
3348 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &loaded));
3349
3350 let mut search_for = vec![ProgramToLoad {
3352 program_id: &program_id,
3353 loader: ProgramCacheEntryOwner::LoaderV3,
3354 deployment_slot: 100,
3355 }];
3356 let mut extracted = ProgramCacheForTxBatch::new(200);
3357 cache.extract(&mut search_for, &mut extracted, &env, true, true);
3358 assert!(search_for.is_empty());
3359 assert!(Arc::ptr_eq(
3360 extracted.entries.get(&program_id).unwrap(),
3361 &loaded
3362 ));
3363 }
3364
3365 #[test_matrix(
3366 (
3367 new_closed_entry,
3368 new_builtin_entry,
3369 new_failed_verification_entry,
3370 new_unloaded_entry,
3371 new_loaded_entry,
3372 ),
3373 (100, 101)
3374 )]
3375 fn test_extract_effective_slot(
3376 new_program: fn(ProgramRuntimeEnvironment) -> ProgramCacheEntryType,
3377 batch_slot: Slot,
3378 ) {
3379 let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
3393 let env = get_mock_program_runtime_environment();
3394 let program_id = Pubkey::new_unique();
3395 let entry = new_test_entry_with_owner(
3396 100,
3397 ProgramCacheEntryOwner::LoaderV3,
3398 new_program(env.clone()),
3399 );
3400 cache.assign_program(&env, program_id, 100, Arc::clone(&entry));
3401 cache.latest_root_slot = batch_slot;
3402
3403 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
3404 assert_eq!(slot_versions.len(), 1);
3405 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &entry));
3406
3407 let mut search_for = vec![ProgramToLoad {
3408 program_id: &program_id,
3409 loader: ProgramCacheEntryOwner::LoaderV3,
3410 deployment_slot: 100,
3411 }];
3412 let mut extracted = ProgramCacheForTxBatch::new(batch_slot);
3413 cache.extract(&mut search_for, &mut extracted, &env, true, true);
3414
3415 if batch_slot < entry.effective_slot() {
3416 assert!(search_for.is_empty());
3420 let tombstone = extracted.entries.get(&program_id).unwrap();
3421 assert_matches!(tombstone.program, ProgramCacheEntryType::DelayVisibility);
3422 assert_eq!(tombstone.account_owner, ProgramCacheEntryOwner::LoaderV3);
3423 assert_eq!(tombstone.deployment_slot, 100);
3424 assert!(Arc::ptr_eq(&tombstone.stats, &entry.stats)); assert_eq!(entry.stats.uses.load(Ordering::Relaxed), 1);
3426
3427 assert_eq!(entry.latest_access_slot.load(Ordering::Relaxed), batch_slot);
3431 assert_eq!(tombstone.latest_access_slot.load(Ordering::Relaxed), 0);
3432 } else if entry.is_unloaded() {
3433 assert!(extracted.entries.is_empty());
3437 assert_eq!(search_for.len(), 1);
3438 assert_eq!(entry.stats.uses.load(Ordering::Relaxed), 0);
3439 assert_eq!(entry.latest_access_slot.load(Ordering::Relaxed), 0);
3440 } else {
3441 assert!(search_for.is_empty());
3444 assert!(Arc::ptr_eq(
3445 extracted.entries.get(&program_id).unwrap(),
3446 &entry
3447 ));
3448 assert_eq!(entry.stats.uses.load(Ordering::Relaxed), 1);
3449 assert_eq!(entry.latest_access_slot.load(Ordering::Relaxed), batch_slot);
3450 }
3451 }
3452
3453 #[test]
3454 fn test_extract_closed_entry_matches_any_env() {
3455 let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
3465 let env = get_mock_program_runtime_environment();
3466 let other_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
3467 let program_id = Pubkey::new_unique();
3468 let closed = new_test_entry_with_owner(
3469 100,
3470 ProgramCacheEntryOwner::LoaderV3,
3471 new_closed_entry(env.clone()), );
3473 assert_eq!(closed.effective_slot(), closed.deployment_slot);
3474 cache.assign_program(&env, program_id, 100, Arc::clone(&closed));
3475
3476 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
3477 assert_eq!(slot_versions.len(), 1);
3478 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &closed));
3479
3480 let mut search_for = vec![ProgramToLoad {
3482 program_id: &program_id,
3483 loader: ProgramCacheEntryOwner::LoaderV3,
3484 deployment_slot: 100,
3485 }];
3486 let mut extracted = ProgramCacheForTxBatch::new(100);
3487 cache.extract(&mut search_for, &mut extracted, &env, true, true);
3488 assert!(Arc::ptr_eq(
3489 extracted.entries.get(&program_id).unwrap(),
3490 &closed
3491 ));
3492 assert!(search_for.is_empty());
3493
3494 let mut search_for = vec![ProgramToLoad {
3496 program_id: &program_id,
3497 loader: ProgramCacheEntryOwner::LoaderV3,
3498 deployment_slot: 100,
3499 }];
3500 let mut extracted = ProgramCacheForTxBatch::new(100);
3501 cache.extract(&mut search_for, &mut extracted, &other_env, true, true);
3502 assert!(Arc::ptr_eq(
3503 extracted.entries.get(&program_id).unwrap(),
3504 &closed
3505 ));
3506 assert!(search_for.is_empty());
3507 }
3508
3509 #[test]
3510 fn test_extract_delay_visibility_tombstone_interleaved_environments() {
3511 let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
3534 let env = get_mock_program_runtime_environment();
3535 let upcoming_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
3536 let program_id = Pubkey::new_unique();
3537 let on_execution_env = new_test_entry_with_owner(
3538 100,
3539 ProgramCacheEntryOwner::LoaderV3,
3540 new_loaded_entry(env.clone()),
3541 );
3542 let on_upcoming_env = new_test_entry_with_owner(
3543 100,
3544 ProgramCacheEntryOwner::LoaderV3,
3545 new_loaded_entry(upcoming_env.clone()),
3546 );
3547 cache.assign_program(&env, program_id, 100, Arc::clone(&on_execution_env));
3548 cache.assign_program(&upcoming_env, program_id, 100, Arc::clone(&on_upcoming_env));
3549
3550 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
3551 assert_eq!(slot_versions.len(), 2);
3552 assert!(Arc::ptr_eq(
3553 slot_versions.first().unwrap(),
3554 &on_execution_env
3555 ));
3556 assert!(Arc::ptr_eq(slot_versions.get(1).unwrap(), &on_upcoming_env));
3557
3558 let mut search_for = vec![ProgramToLoad {
3560 program_id: &program_id,
3561 loader: ProgramCacheEntryOwner::LoaderV3,
3562 deployment_slot: 100,
3563 }];
3564 let mut extracted = ProgramCacheForTxBatch::new(100);
3565 cache.extract(&mut search_for, &mut extracted, &env, true, true);
3566 assert!(search_for.is_empty());
3567
3568 let tombstone = extracted.entries.get(&program_id).unwrap();
3570 assert_matches!(tombstone.program, ProgramCacheEntryType::DelayVisibility);
3571
3572 assert!(Arc::ptr_eq(&tombstone.stats, &on_upcoming_env.stats));
3580 assert_eq!(on_upcoming_env.stats.uses.load(Ordering::Relaxed), 1);
3581 assert_eq!(on_execution_env.stats.uses.load(Ordering::Relaxed), 0);
3582
3583 let mut search_for = vec![ProgramToLoad {
3586 program_id: &program_id,
3587 loader: ProgramCacheEntryOwner::LoaderV3,
3588 deployment_slot: 100,
3589 }];
3590 let mut extracted = ProgramCacheForTxBatch::new(101);
3591 cache.extract(&mut search_for, &mut extracted, &env, true, true);
3592 assert!(search_for.is_empty());
3593 assert!(Arc::ptr_eq(
3594 extracted.entries.get(&program_id).unwrap(),
3595 &on_execution_env
3596 ));
3597
3598 assert_eq!(on_upcoming_env.stats.uses.load(Ordering::Relaxed), 1);
3600 assert_eq!(on_execution_env.stats.uses.load(Ordering::Relaxed), 1);
3601 }
3602
3603 #[test_case(false)]
3604 #[test_case(true)]
3605 fn test_extract_environment_filter_same_slot(other_env_first: bool) {
3606 let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
3607 let env = get_mock_program_runtime_environment();
3608 let other_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
3609 let program_id = Pubkey::new_unique();
3610
3611 let on_other_env = new_test_entry_with_owner(
3613 100,
3614 ProgramCacheEntryOwner::LoaderV3,
3615 new_loaded_entry(other_env.clone()),
3616 );
3617 let on_execution_env = new_test_entry_with_owner(
3618 100,
3619 ProgramCacheEntryOwner::LoaderV3,
3620 new_loaded_entry(env.clone()),
3621 );
3622 if other_env_first {
3623 cache.assign_program(&other_env, program_id, 100, Arc::clone(&on_other_env));
3624 cache.assign_program(&env, program_id, 100, Arc::clone(&on_execution_env));
3625 } else {
3626 cache.assign_program(&env, program_id, 100, Arc::clone(&on_execution_env));
3627 cache.assign_program(&other_env, program_id, 100, Arc::clone(&on_other_env));
3628 }
3629
3630 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
3633 assert_eq!(slot_versions.len(), 2);
3634 let (first, second) = if other_env_first {
3635 (&on_other_env, &on_execution_env)
3636 } else {
3637 (&on_execution_env, &on_other_env)
3638 };
3639 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), first));
3640 assert!(Arc::ptr_eq(slot_versions.get(1).unwrap(), second));
3641
3642 let mut search_for = vec![ProgramToLoad {
3645 program_id: &program_id,
3646 loader: ProgramCacheEntryOwner::LoaderV3,
3647 deployment_slot: 100,
3648 }];
3649 let mut extracted = ProgramCacheForTxBatch::new(200);
3650 cache.extract(&mut search_for, &mut extracted, &env, true, true);
3651 assert!(Arc::ptr_eq(
3652 extracted.entries.get(&program_id).unwrap(),
3653 &on_execution_env
3654 ));
3655
3656 let mut search_for = vec![ProgramToLoad {
3658 program_id: &program_id,
3659 loader: ProgramCacheEntryOwner::LoaderV3,
3660 deployment_slot: 100,
3661 }];
3662 let mut extracted = ProgramCacheForTxBatch::new(200);
3663 cache.extract(&mut search_for, &mut extracted, &other_env, true, true);
3664 assert!(Arc::ptr_eq(
3665 extracted.entries.get(&program_id).unwrap(),
3666 &on_other_env
3667 ));
3668 }
3669
3670 #[test_matrix((false, true))]
3671 fn test_extract_usage_counter(increment_usage_counter: bool) {
3672 let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
3673 let env = get_mock_program_runtime_environment();
3674 let program_id = Pubkey::new_unique();
3675 let entry = new_test_entry_with_owner(
3676 100,
3677 ProgramCacheEntryOwner::LoaderV3,
3678 new_loaded_entry(env.clone()),
3679 );
3680 cache.assign_program(&env, program_id, 100, Arc::clone(&entry));
3681 cache.latest_root_slot = 200;
3682
3683 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(
3690 &mut search_for,
3691 &mut extracted,
3692 &env,
3693 increment_usage_counter,
3694 true,
3695 );
3696
3697 assert_eq!(entry.latest_access_slot.load(Ordering::Relaxed), 200);
3699 assert_eq!(
3700 entry.stats.uses.load(Ordering::Relaxed),
3701 u64::from(increment_usage_counter)
3702 );
3703 }
3704
3705 #[test]
3706 fn test_extract_usage_counter_delayed_visibility() {
3707 let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
3708 let env = get_mock_program_runtime_environment();
3709 let program_id = Pubkey::new_unique();
3710 let entry = new_test_entry_with_owner(
3711 100,
3712 ProgramCacheEntryOwner::LoaderV3,
3713 new_loaded_entry(env.clone()),
3714 );
3715 cache.assign_program(&env, program_id, 100, Arc::clone(&entry));
3716 cache.latest_root_slot = 100;
3717
3718 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(100);
3727 cache.extract(&mut search_for, &mut extracted, &env, true, true);
3728
3729 let tombstone = extracted.entries.get(&program_id).unwrap();
3730 assert!(matches!(
3731 tombstone.program,
3732 ProgramCacheEntryType::DelayVisibility
3733 ));
3734 assert!(!Arc::ptr_eq(tombstone, &entry));
3735
3736 assert_eq!(entry.latest_access_slot.load(Ordering::Relaxed), 100);
3739 assert_eq!(tombstone.latest_access_slot.load(Ordering::Relaxed), 0);
3740
3741 assert!(Arc::ptr_eq(&tombstone.stats, &entry.stats));
3744 assert_eq!(entry.stats.uses.load(Ordering::Relaxed), 1);
3745 }
3746
3747 #[test_matrix((false, true))]
3748 fn test_extract_hits_and_misses(count_hits_and_misses: bool) {
3749 let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
3750 let env = get_mock_program_runtime_environment();
3751 let found = Pubkey::new_unique();
3752 let missing = Pubkey::new_unique();
3753 cache.assign_program(
3754 &env,
3755 found,
3756 100,
3757 new_test_entry_with_owner(
3758 100,
3759 ProgramCacheEntryOwner::LoaderV3,
3760 new_loaded_entry(env.clone()),
3761 ),
3762 );
3763
3764 let mut search_for = vec![
3765 ProgramToLoad {
3766 program_id: &found,
3767 loader: ProgramCacheEntryOwner::LoaderV3,
3768 deployment_slot: 100,
3769 },
3770 ProgramToLoad {
3771 program_id: &missing,
3772 loader: ProgramCacheEntryOwner::LoaderV3,
3773 deployment_slot: 0,
3774 },
3775 ];
3776 let mut extracted = ProgramCacheForTxBatch::new(200);
3777 cache.extract(
3778 &mut search_for,
3779 &mut extracted,
3780 &env,
3781 true,
3782 count_hits_and_misses,
3783 );
3784
3785 let expected = u64::from(count_hits_and_misses);
3786 assert_eq!(cache.stats.hits.load(Ordering::Relaxed), expected);
3787 assert_eq!(cache.stats.misses.load(Ordering::Relaxed), expected);
3788 }
3789
3790 #[test]
3791 fn test_extract_hits_count_only_this_call() {
3792 let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
3793 let env = get_mock_program_runtime_environment();
3794 let program_id = Pubkey::new_unique();
3795 cache.assign_program(
3796 &env,
3797 program_id,
3798 100,
3799 new_test_entry_with_owner(
3800 100,
3801 ProgramCacheEntryOwner::LoaderV3,
3802 new_loaded_entry(env.clone()),
3803 ),
3804 );
3805
3806 let mut extracted = ProgramCacheForTxBatch::new(200);
3809 extracted.replenish(Pubkey::new_unique(), new_test_builtin_entry(0));
3810
3811 let mut search_for = vec![ProgramToLoad {
3814 program_id: &program_id,
3815 loader: ProgramCacheEntryOwner::LoaderV3,
3816 deployment_slot: 100,
3817 }];
3818 cache.extract(&mut search_for, &mut extracted, &env, true, true);
3819 assert_eq!(extracted.entries.len(), 2);
3820 assert_eq!(cache.stats.hits.load(Ordering::Relaxed), 1);
3821 }
3822
3823 #[test]
3824 fn test_extract_cooperative_loading_task() {
3825 let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
3826 let env = get_mock_program_runtime_environment();
3827 let program_ids = [Pubkey::new_unique(), Pubkey::new_unique()];
3828
3829 let mut search_for = program_ids
3831 .iter()
3832 .map(|program_id| ProgramToLoad {
3833 program_id,
3834 loader: ProgramCacheEntryOwner::LoaderV3,
3835 deployment_slot: 0,
3836 })
3837 .collect();
3838 let mut extracted = ProgramCacheForTxBatch::new(100);
3839 let task = cache.extract(&mut search_for, &mut extracted, &env, true, true);
3840 assert_eq!(search_for.len(), 2);
3841 assert_eq!(task, program_ids.first().copied());
3842 match &cache.index {
3843 IndexImplementation::V1 {
3844 loading_entries, ..
3845 } => {
3846 let loading_entries = loading_entries.lock().unwrap();
3847 assert_eq!(loading_entries.len(), 1);
3848 assert_eq!(
3849 loading_entries.get(program_ids.first().unwrap()),
3850 Some(&(100, thread::current().id()))
3851 );
3852 }
3853 }
3854
3855 let mut search_for = vec![ProgramToLoad {
3857 program_id: program_ids.first().unwrap(),
3858 loader: ProgramCacheEntryOwner::LoaderV3,
3859 deployment_slot: 0,
3860 }];
3861 let task = cache.extract(&mut search_for, &mut extracted, &env, true, true);
3862 assert_eq!(search_for.len(), 1);
3863 assert_eq!(task, None);
3864
3865 let cookie = cache.loading_task_waiter.cookie();
3867 let loaded = new_test_entry_with_owner(
3868 50,
3869 ProgramCacheEntryOwner::LoaderV3,
3870 new_loaded_entry(env.clone()),
3871 );
3872 cache.finish_cooperative_loading_task(
3873 &env,
3874 100,
3875 *program_ids.first().unwrap(),
3876 Arc::clone(&loaded),
3877 );
3878 assert_ne!(cache.loading_task_waiter.wait(cookie), cookie);
3879
3880 match &cache.index {
3882 IndexImplementation::V1 {
3883 loading_entries, ..
3884 } => assert!(loading_entries.lock().unwrap().is_empty()),
3885 }
3886 let mut search_for = vec![ProgramToLoad {
3887 program_id: program_ids.first().unwrap(),
3888 loader: ProgramCacheEntryOwner::LoaderV3,
3889 deployment_slot: 50,
3890 }];
3891 let mut extracted = ProgramCacheForTxBatch::new(100);
3892 let task = cache.extract(&mut search_for, &mut extracted, &env, true, true);
3893 assert!(search_for.is_empty());
3894 assert_eq!(task, None);
3895 assert!(Arc::ptr_eq(
3896 extracted.entries.get(program_ids.first().unwrap()).unwrap(),
3897 &loaded
3898 ));
3899 }
3900
3901 #[test]
3902 fn test_extract_cooperative_loading_task_ordering() {
3903 let (cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
3904 let env = get_mock_program_runtime_environment();
3905 let program_ids = [Pubkey::new_unique(), Pubkey::new_unique()];
3906 let mut extracted = ProgramCacheForTxBatch::new(100);
3907
3908 let mut search_for = program_ids
3910 .iter()
3911 .map(|program_id| ProgramToLoad {
3912 program_id,
3913 loader: ProgramCacheEntryOwner::LoaderV3,
3914 deployment_slot: 0,
3915 })
3916 .collect();
3917 let task = cache.extract(&mut search_for, &mut extracted, &env, true, true);
3918 assert_eq!(task, program_ids.first().copied());
3919
3920 let mut search_for = program_ids
3923 .iter()
3924 .rev()
3925 .map(|program_id| ProgramToLoad {
3926 program_id,
3927 loader: ProgramCacheEntryOwner::LoaderV3,
3928 deployment_slot: 0,
3929 })
3930 .collect();
3931 let task = cache.extract(&mut search_for, &mut extracted, &env, true, true);
3932 assert_eq!(task, program_ids.get(1).copied());
3933
3934 match &cache.index {
3936 IndexImplementation::V1 {
3937 loading_entries, ..
3938 } => {
3939 let loading_entries = loading_entries.lock().unwrap();
3940 assert_eq!(loading_entries.len(), 2);
3941 for program_id in &program_ids {
3942 assert_eq!(
3943 loading_entries.get(program_id),
3944 Some(&(100, thread::current().id()))
3945 );
3946 }
3947 }
3948 }
3949
3950 let mut search_for = program_ids
3952 .iter()
3953 .map(|program_id| ProgramToLoad {
3954 program_id,
3955 loader: ProgramCacheEntryOwner::LoaderV3,
3956 deployment_slot: 0,
3957 })
3958 .collect();
3959 let task = cache.extract(&mut search_for, &mut extracted, &env, true, true);
3960 assert_eq!(search_for.len(), 2);
3961 assert_eq!(task, None);
3962 }
3963
3964 #[test]
3965 fn test_extract_entry_not_in_same_branch() {
3966 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
3973 let mut fork_graph = TestForkGraphSpecific::default();
3974 fork_graph.insert_fork(&[0, 50, 200]);
3975 fork_graph.insert_fork(&[0, 100]);
3976 let fork_graph = Arc::new(RwLock::new(fork_graph));
3977 cache.set_fork_graph(Arc::downgrade(&fork_graph));
3978
3979 let env = get_mock_program_runtime_environment();
3980 let program_id = Pubkey::new_unique();
3981 let on_other_fork = new_test_entry_with_owner(
3982 100,
3983 ProgramCacheEntryOwner::LoaderV3,
3984 new_loaded_entry(env.clone()),
3985 );
3986 cache.assign_program(&env, program_id, 100, Arc::clone(&on_other_fork));
3987
3988 let mut search_for = vec![ProgramToLoad {
3998 program_id: &program_id,
3999 loader: ProgramCacheEntryOwner::LoaderV3,
4000 deployment_slot: 100,
4001 }];
4002 let mut extracted = ProgramCacheForTxBatch::new(200);
4003 cache.extract(&mut search_for, &mut extracted, &env, true, true);
4004 assert_eq!(search_for.len(), 1);
4005 assert!(extracted.entries.is_empty());
4006
4007 let on_same_fork = new_test_entry_with_owner(
4009 50,
4010 ProgramCacheEntryOwner::LoaderV3,
4011 new_loaded_entry(env.clone()),
4012 );
4013 cache.assign_program(&env, program_id, 50, Arc::clone(&on_same_fork));
4014
4015 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
4017 assert_eq!(slot_versions.len(), 2);
4018 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &on_same_fork));
4019 assert!(Arc::ptr_eq(slot_versions.get(1).unwrap(), &on_other_fork));
4020
4021 let mut search_for = vec![ProgramToLoad {
4024 program_id: &program_id,
4025 loader: ProgramCacheEntryOwner::LoaderV3,
4026 deployment_slot: 50,
4027 }];
4028 let mut extracted = ProgramCacheForTxBatch::new(200);
4029 cache.extract(&mut search_for, &mut extracted, &env, true, true);
4030 assert!(search_for.is_empty());
4031 assert!(Arc::ptr_eq(
4032 extracted.entries.get(&program_id).unwrap(),
4033 &on_same_fork
4034 ));
4035 }
4036
4037 #[test]
4038 fn test_extract_deployment_slot_mismatch() {
4039 let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
4055 assert_eq!(cache.latest_root_slot, 0);
4056 let env = get_mock_program_runtime_environment();
4057 let program_id = Pubkey::new_unique();
4058 let deployed_at_100 = new_test_entry_with_owner(
4059 100,
4060 ProgramCacheEntryOwner::LoaderV3,
4061 new_loaded_entry(env.clone()),
4062 );
4063 cache.assign_program(&env, program_id, 100, Arc::clone(&deployed_at_100));
4064
4065 let mut search_for = vec![ProgramToLoad {
4069 program_id: &program_id,
4070 loader: ProgramCacheEntryOwner::LoaderV3,
4071 deployment_slot: 150,
4072 }];
4073 let mut extracted = ProgramCacheForTxBatch::new(200);
4074 cache.extract(&mut search_for, &mut extracted, &env, true, true);
4075 assert_eq!(search_for.len(), 1);
4076 assert!(extracted.entries.is_empty());
4077
4078 let deployed_at_150 = new_test_entry_with_owner(
4080 150,
4081 ProgramCacheEntryOwner::LoaderV3,
4082 new_loaded_entry(env.clone()),
4083 );
4084 cache.assign_program(&env, program_id, 150, Arc::clone(&deployed_at_150));
4085
4086 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
4089 assert_eq!(slot_versions.len(), 2);
4090 assert!(Arc::ptr_eq(
4091 slot_versions.first().unwrap(),
4092 &deployed_at_100
4093 ));
4094 assert!(Arc::ptr_eq(slot_versions.get(1).unwrap(), &deployed_at_150));
4095
4096 let mut search_for = vec![ProgramToLoad {
4098 program_id: &program_id,
4099 loader: ProgramCacheEntryOwner::LoaderV3,
4100 deployment_slot: 150,
4101 }];
4102 let mut extracted = ProgramCacheForTxBatch::new(200);
4103 cache.extract(&mut search_for, &mut extracted, &env, true, true);
4104 assert!(search_for.is_empty());
4105 assert!(Arc::ptr_eq(
4106 extracted.entries.get(&program_id).unwrap(),
4107 &deployed_at_150
4108 ));
4109 }
4110
4111 #[test]
4112 fn test_extract_older_entry_on_the_callers_fork() {
4113 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
4120 let mut fork_graph = TestForkGraphSpecific::default();
4121 fork_graph.insert_fork(&[0, 50, 200]);
4122 fork_graph.insert_fork(&[0, 150]);
4123 let fork_graph = Arc::new(RwLock::new(fork_graph));
4124 cache.set_fork_graph(Arc::downgrade(&fork_graph));
4125
4126 let env = get_mock_program_runtime_environment();
4127 let program_id = Pubkey::new_unique();
4128 let on_other_fork = new_test_entry_with_owner(
4129 150,
4130 ProgramCacheEntryOwner::LoaderV3,
4131 new_loaded_entry(env.clone()),
4132 );
4133 let on_same_fork = new_test_entry_with_owner(
4134 50,
4135 ProgramCacheEntryOwner::LoaderV3,
4136 new_loaded_entry(env.clone()),
4137 );
4138 cache.assign_program(&env, program_id, 150, Arc::clone(&on_other_fork));
4139 cache.assign_program(&env, program_id, 50, Arc::clone(&on_same_fork));
4140
4141 let mut search_for = vec![ProgramToLoad {
4145 program_id: &program_id,
4146 loader: ProgramCacheEntryOwner::LoaderV3,
4147 deployment_slot: 50,
4148 }];
4149 let mut extracted = ProgramCacheForTxBatch::new(200);
4150 cache.extract(&mut search_for, &mut extracted, &env, true, true);
4151 assert!(Arc::ptr_eq(
4152 extracted.entries.get(&program_id).unwrap(),
4153 &on_same_fork
4154 ));
4155
4156 let mut search_for = vec![ProgramToLoad {
4168 program_id: &program_id,
4169 loader: ProgramCacheEntryOwner::LoaderV3,
4170 deployment_slot: 150,
4171 }];
4172 let mut extracted = ProgramCacheForTxBatch::new(200);
4173 cache.extract(&mut search_for, &mut extracted, &env, true, true);
4174 assert_eq!(search_for.len(), 1);
4175 assert!(extracted.entries.is_empty());
4176 }
4177
4178 #[test]
4179 fn test_extract_below_deployment_slot() {
4180 let (mut cache, fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
4181 let env = get_mock_program_runtime_environment();
4182 let program_id = Pubkey::new_unique();
4183 let entry = new_test_entry_with_owner(
4184 100,
4185 ProgramCacheEntryOwner::LoaderV3,
4186 new_loaded_entry(env.clone()),
4187 );
4188 cache.assign_program(&env, program_id, 100, Arc::clone(&entry));
4189
4190 cache.prune(200, None, &fork_graph.read().unwrap());
4193 assert_eq!(cache.latest_root_slot, 200);
4194
4195 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
4197 assert_eq!(slot_versions.len(), 1);
4198 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &entry));
4199
4200 fork_graph.write().unwrap().relation = BlockRelation::Unknown;
4204
4205 let mut search_for = vec![ProgramToLoad {
4208 program_id: &program_id,
4209 loader: ProgramCacheEntryOwner::LoaderV3,
4210 deployment_slot: 0,
4211 }];
4212 let mut extracted = ProgramCacheForTxBatch::new(50);
4213 cache.extract(&mut search_for, &mut extracted, &env, true, true);
4214 assert_eq!(search_for.len(), 1);
4215 assert!(extracted.entries.is_empty());
4216 }
4217
4218 #[test]
4219 fn test_extract_entry_older_than_root() {
4220 let (mut cache, fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
4223 let env = get_mock_program_runtime_environment();
4224 let program_id = Pubkey::new_unique();
4225 let entry = new_test_entry_with_owner(
4226 100,
4227 ProgramCacheEntryOwner::LoaderV3,
4228 new_loaded_entry(env.clone()),
4229 );
4230 cache.assign_program(&env, program_id, 100, Arc::clone(&entry));
4231
4232 cache.prune(200, None, &fork_graph.read().unwrap());
4235 assert_eq!(cache.latest_root_slot, 200);
4236
4237 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
4239 assert_eq!(slot_versions.len(), 1);
4240 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &entry));
4241
4242 fork_graph.write().unwrap().relation = BlockRelation::Unknown;
4246
4247 let mut search_for = vec![ProgramToLoad {
4252 program_id: &program_id,
4253 loader: ProgramCacheEntryOwner::LoaderV3,
4254 deployment_slot: 100,
4255 }];
4256 let mut extracted = ProgramCacheForTxBatch::new(300);
4257 cache.extract(&mut search_for, &mut extracted, &env, true, true);
4258 assert!(search_for.is_empty());
4259 assert!(Arc::ptr_eq(
4260 extracted.entries.get(&program_id).unwrap(),
4261 &entry
4262 ));
4263 }
4264
4265 #[test]
4266 fn test_extract_access_slot_clamped_to_root() {
4267 let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
4268 let env = get_mock_program_runtime_environment();
4269 let program_id = Pubkey::new_unique();
4270 let entry = new_test_entry_with_owner(
4271 100,
4272 ProgramCacheEntryOwner::LoaderV3,
4273 new_loaded_entry(env.clone()),
4274 );
4275 cache.assign_program(&env, program_id, 100, Arc::clone(&entry));
4276
4277 let batch_slot = 200;
4278 for root_slot in [0, 150, batch_slot] {
4279 cache.latest_root_slot = root_slot;
4280
4281 let mut search_for = vec![ProgramToLoad {
4282 program_id: &program_id,
4283 loader: ProgramCacheEntryOwner::LoaderV3,
4284 deployment_slot: 100,
4285 }];
4286 let mut extracted = ProgramCacheForTxBatch::new(batch_slot);
4287 cache.extract(&mut search_for, &mut extracted, &env, true, true);
4288
4289 assert!(search_for.is_empty());
4290 assert_eq!(entry.latest_access_slot.load(Ordering::Relaxed), root_slot);
4291 }
4292 }
4293
4294 #[test]
4295 fn test_unloaded() {
4296 let mut cache = ProgramCache::<TestForkGraph>::new(0);
4297 let env = get_mock_program_runtime_environment();
4298 for program_cache_entry_type in [
4299 ProgramCacheEntryType::Closed,
4300 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
4301 ] {
4302 let entry = Arc::new(ProgramCacheEntry {
4303 program: program_cache_entry_type,
4304 account_owner: ProgramCacheEntryOwner::LoaderV2,
4305 deployment_slot: 0,
4306 stats: Arc::default(),
4307 latest_access_slot: AtomicU64::default(),
4308 });
4309 assert!(entry.to_unloaded().is_none());
4310
4311 let program_id = Pubkey::new_unique();
4313 cache.assign_program(&env, program_id, entry.deployment_slot, entry.clone());
4314 cache.unload_program_entry(program_id, &entry);
4315 assert_eq!(cache.get_slot_versions_for_tests(&program_id).len(), 1);
4316 assert!(cache.stats.evictions.is_empty());
4317 }
4318
4319 let stats = ProgramStatistics {
4320 uses: 3.into(),
4321 ..Default::default()
4322 };
4323 let entry = new_test_entry_with_usage(1, stats);
4324 let unloaded_entry = entry.to_unloaded().unwrap();
4325 assert_eq!(unloaded_entry.deployment_slot, 1);
4326 assert_eq!(unloaded_entry.effective_slot(), 2);
4327 assert_eq!(unloaded_entry.latest_access_slot.load(Ordering::Relaxed), 1);
4328 assert_eq!(unloaded_entry.stats.uses.load(Ordering::Relaxed), 3);
4329
4330 let program_id = Pubkey::new_unique();
4332 cache.assign_program(&env, program_id, entry.deployment_slot, entry.clone());
4333 cache.unload_program_entry(program_id, &entry);
4334 assert!(cache.stats.evictions.contains_key(&program_id));
4335 }
4336
4337 #[test]
4338 fn test_fork_prune_find_first_ancestor() {
4339 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
4340 let env = get_mock_program_runtime_environment();
4341
4342 let mut fork_graph = TestForkGraphSpecific::default();
4353 fork_graph.insert_fork(&[0, 10, 20]);
4354 fork_graph.insert_fork(&[0, 5]);
4355 let fork_graph = Arc::new(RwLock::new(fork_graph));
4356 cache.set_fork_graph(Arc::downgrade(&fork_graph));
4357
4358 let program1 = Pubkey::new_unique();
4359 cache.assign_program(&env, program1, 0, new_test_entry(0));
4360 cache.assign_program(&env, program1, 5, new_test_entry(5));
4361
4362 cache.prune(10, None, &fork_graph.read().unwrap());
4363
4364 let keys = &[program1];
4365 let mut missing = get_entries_to_load(&cache, 20, keys);
4366 let mut extracted = ProgramCacheForTxBatch::new(20);
4367 cache.extract(&mut missing, &mut extracted, &env, true, true);
4368
4369 assert_eq!(
4371 extracted
4372 .find(&program1)
4373 .expect("Did not find the program")
4374 .deployment_slot,
4375 0
4376 );
4377 }
4378
4379 #[test]
4380 fn test_prune_by_deployment_slot() {
4381 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
4382 let env = get_mock_program_runtime_environment();
4383
4384 let mut fork_graph = TestForkGraphSpecific::default();
4395 fork_graph.insert_fork(&[0, 10, 20]);
4396 fork_graph.insert_fork(&[0, 5, 6]);
4397 let fork_graph = Arc::new(RwLock::new(fork_graph));
4398 cache.set_fork_graph(Arc::downgrade(&fork_graph));
4399
4400 let program1 = Pubkey::new_unique();
4401 cache.assign_program(&env, program1, 0, new_test_entry(0));
4402 cache.assign_program(&env, program1, 5, new_test_entry(5));
4403
4404 let program2 = Pubkey::new_unique();
4405 cache.assign_program(&env, program2, 10, new_test_entry(10));
4406
4407 let keys = &[program1, program2];
4408 let mut missing = get_entries_to_load(&cache, 20, keys);
4409 let mut extracted = ProgramCacheForTxBatch::new(20);
4410 cache.extract(&mut missing, &mut extracted, &env, true, true);
4411 assert!(match_slot(&extracted, &program1, 0, 20));
4412 assert!(match_slot(&extracted, &program2, 10, 20));
4413
4414 let mut missing = get_entries_to_load(&cache, 6, keys);
4415 assert!(match_missing(&missing, &program2, false));
4416 let mut extracted = ProgramCacheForTxBatch::new(6);
4417 cache.extract(&mut missing, &mut extracted, &env, true, true);
4418 assert!(match_slot(&extracted, &program1, 5, 6));
4419
4420 cache.prune_by_deployment_slot(5);
4423
4424 let mut missing = get_entries_to_load(&cache, 20, keys);
4425 let mut extracted = ProgramCacheForTxBatch::new(20);
4426 cache.extract(&mut missing, &mut extracted, &env, true, true);
4427 assert!(match_slot(&extracted, &program1, 0, 20));
4428 assert!(match_slot(&extracted, &program2, 10, 20));
4429
4430 let mut missing = get_entries_to_load(&cache, 6, keys);
4431 assert!(match_missing(&missing, &program2, false));
4432 let mut extracted = ProgramCacheForTxBatch::new(6);
4433 cache.extract(&mut missing, &mut extracted, &env, true, true);
4434 assert!(match_slot(&extracted, &program1, 0, 6));
4435
4436 cache.prune_by_deployment_slot(10);
4439
4440 let mut missing = get_entries_to_load(&cache, 20, keys);
4441 assert!(match_missing(&missing, &program2, false));
4442 let mut extracted = ProgramCacheForTxBatch::new(20);
4443 cache.extract(&mut missing, &mut extracted, &env, true, true);
4444 assert!(match_slot(&extracted, &program1, 0, 20));
4445 }
4446}