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;
117
118pub type Percent = u8;
120
121fn percent_of_max_entries(percent: Percent) -> usize {
126 debug_assert!(percent <= 100, "percent must be <= 100");
127 MAX_LOADED_ENTRY_COUNT.saturating_mul(percent as usize) / 100
128}
129
130#[derive(Copy, Clone, Debug, PartialEq)]
132pub enum BlockRelation {
133 Ancestor,
135 Equal,
137 Descendant,
139 Unrelated,
141 Unknown,
143}
144
145pub trait ForkGraph {
147 fn relationship(&self, a: Slot, b: Slot) -> BlockRelation;
149}
150
151#[derive(Debug, Default)]
153pub struct EpochBoundaryPreparation {
154 pub upcoming_epoch: Epoch,
156 pub upcoming_environment: Option<ProgramRuntimeEnvironment>,
162 pub programs_to_recompile: Vec<(Pubkey, Arc<ProgramCacheEntry>)>,
164}
165
166impl EpochBoundaryPreparation {
167 pub fn new(epoch: Epoch) -> Self {
168 Self {
169 upcoming_epoch: epoch,
170 upcoming_environment: None,
171 programs_to_recompile: Vec::default(),
172 }
173 }
174
175 pub fn get_upcoming_environment_for_epoch(
177 &self,
178 epoch: Epoch,
179 ) -> Option<ProgramRuntimeEnvironment> {
180 if epoch == self.upcoming_epoch {
181 return self.upcoming_environment.clone();
182 }
183 None
184 }
185
186 pub fn reroot(&mut self, epoch: Epoch) -> Option<ProgramRuntimeEnvironment> {
188 if epoch == self.upcoming_epoch
189 && let Some(upcoming_environment) = self.upcoming_environment.take()
190 {
191 self.programs_to_recompile.clear();
192 return Some(upcoming_environment);
193 }
194
195 None
196 }
197}
198
199#[derive(Clone, PartialEq, Debug)]
201pub struct ProgramToLoad<'a> {
202 pub program_id: &'a Pubkey,
204 pub loader: ProgramCacheEntryOwner,
206 pub match_criteria: ProgramCacheMatchCriteria,
208 pub last_modification_slot: Slot,
210}
211
212#[derive(Debug)]
213pub(crate) enum IndexImplementation {
214 V1 {
216 entries: HashMap<Pubkey, Vec<Arc<ProgramCacheEntry>>>,
221 loading_entries: Mutex<HashMap<Pubkey, (Slot, thread::ThreadId)>>,
230 },
231}
232
233pub struct ProgramCache<FG: ForkGraph> {
249 pub(crate) index: IndexImplementation,
251 pub latest_root_slot: Slot,
253 pub stats: ProgramCacheStats,
255 pub fork_graph: Option<Weak<RwLock<FG>>>,
257 pub loading_task_waiter: Arc<LoadingTaskWaiter>,
259}
260
261impl<FG: ForkGraph> std::fmt::Debug for ProgramCache<FG> {
262 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
263 f.debug_struct("ProgramCache")
264 .field("root slot", &self.latest_root_slot)
265 .field("stats", &self.stats)
266 .field("index", &self.index)
267 .finish()
268 }
269}
270
271#[derive(Clone, Debug, Default)]
277pub struct ProgramCacheForTxBatch {
278 entries: HashMap<Pubkey, Arc<ProgramCacheEntry>>,
281 modified_entries: HashMap<Pubkey, Arc<ProgramCacheEntry>>,
283 slot: Slot,
284 pub hit_max_limit: bool,
285 pub loaded_missing: bool,
286 pub merged_modified: bool,
287}
288
289impl ProgramCacheForTxBatch {
290 pub fn new(slot: Slot) -> Self {
291 Self {
292 entries: HashMap::new(),
293 modified_entries: HashMap::new(),
294 slot,
295 hit_max_limit: false,
296 loaded_missing: false,
297 merged_modified: false,
298 }
299 }
300
301 pub fn replenish(
307 &mut self,
308 key: Pubkey,
309 entry: Arc<ProgramCacheEntry>,
310 ) -> (bool, Arc<ProgramCacheEntry>) {
311 (self.entries.insert(key, entry.clone()).is_some(), entry)
312 }
313
314 pub fn store_modified_entry(&mut self, key: Pubkey, entry: Arc<ProgramCacheEntry>) {
317 self.modified_entries.insert(key, entry);
318 }
319
320 pub fn drain_modified_entries(&mut self) -> HashMap<Pubkey, Arc<ProgramCacheEntry>> {
323 std::mem::take(&mut self.modified_entries)
324 }
325
326 pub fn find(&self, key: &Pubkey) -> Option<Arc<ProgramCacheEntry>> {
327 self.modified_entries
331 .get(key)
332 .or_else(|| self.entries.get(key))
333 .map(|entry| {
334 if entry.is_implicit_delay_visibility_tombstone(self.slot) {
335 Arc::new(ProgramCacheEntry::new_delay_visibility_tombstone(
339 entry.deployment_slot,
340 entry.account_owner,
341 Arc::clone(&entry.stats),
342 ))
343 } else {
344 entry.clone()
345 }
346 })
347 }
348
349 pub fn slot(&self) -> Slot {
350 self.slot
351 }
352
353 pub fn set_slot_for_tests(&mut self, slot: Slot) {
354 self.slot = slot;
355 }
356
357 pub fn merge(&mut self, modified_entries: &HashMap<Pubkey, Arc<ProgramCacheEntry>>) {
358 modified_entries.iter().for_each(|(key, entry)| {
359 self.merged_modified = true;
360 self.replenish(*key, entry.clone());
361 })
362 }
363
364 pub fn is_empty(&self) -> bool {
365 self.entries.is_empty()
366 }
367}
368
369#[derive(Clone, PartialEq, Debug)]
370pub enum ProgramCacheMatchCriteria {
371 DeployedOnOrAfterSlot(Slot),
372 NoCriteria,
373}
374
375impl<FG: ForkGraph> ProgramCache<FG> {
376 pub fn new(root_slot: Slot) -> Self {
377 Self {
378 index: IndexImplementation::V1 {
379 entries: HashMap::new(),
380 loading_entries: Mutex::new(HashMap::new()),
381 },
382 latest_root_slot: root_slot,
383 stats: ProgramCacheStats::default(),
384 fork_graph: None,
385 loading_task_waiter: Arc::new(LoadingTaskWaiter::default()),
386 }
387 }
388
389 pub fn set_fork_graph(&mut self, fork_graph: Weak<RwLock<FG>>) {
390 self.fork_graph = Some(fork_graph);
391 }
392
393 pub fn assign_program(
396 &mut self,
397 program_runtime_environment: &ProgramRuntimeEnvironment,
398 key: Pubkey,
399 _last_modification_slot: Slot,
400 entry: Arc<ProgramCacheEntry>,
401 ) -> bool {
402 debug_assert!(!matches!(
403 &entry.program,
404 ProgramCacheEntryType::DelayVisibility
405 ));
406 fn is_current_env(
410 program_runtime_environment: &ProgramRuntimeEnvironment,
411 env_opt: Option<&ProgramRuntimeEnvironment>,
412 ) -> bool {
413 env_opt
414 .map(|env| env == program_runtime_environment)
415 .unwrap_or(true)
416 }
417 match &mut self.index {
418 IndexImplementation::V1 { entries, .. } => {
419 let slot_versions = &mut entries.entry(key).or_default();
420 let insertion_point = slot_versions.binary_search_by(|at| {
421 at.deployment_slot
422 .cmp(&entry.deployment_slot)
423 .then(at.account_owner.cmp(&entry.account_owner))
424 .then(
425 is_current_env(
429 program_runtime_environment,
430 at.program.get_environment(),
431 )
432 .cmp(&is_current_env(
433 program_runtime_environment,
434 entry.program.get_environment(),
435 )),
436 )
437 });
438 match insertion_point {
439 Ok(index) => {
440 let existing = slot_versions.get_mut(index).unwrap();
441 match (&existing.program, &entry.program) {
442 (
443 ProgramCacheEntryType::Builtin(_),
444 ProgramCacheEntryType::Builtin(_),
445 )
446 | (ProgramCacheEntryType::Closed, ProgramCacheEntryType::Unloaded(_))
447 | (
448 ProgramCacheEntryType::Unloaded(_),
449 ProgramCacheEntryType::Loaded(_),
450 )
451 | (
452 ProgramCacheEntryType::Unloaded(_),
453 ProgramCacheEntryType::FailedVerification(_),
454 ) => {}
455 _ => {
456 error!(
458 "ProgramCache::assign_program() failed key={key:?} \
459 existing={slot_versions:?} entry={entry:?}"
460 );
461 debug_assert!(false, "Unexpected replacement of an entry");
462 self.stats.replacements.fetch_add(1, Ordering::Relaxed);
463 return true;
464 }
465 }
466 entry.stats.merge_from(&existing.stats);
467 *existing = Arc::clone(&entry);
468 self.stats.reloads.fetch_add(1, Ordering::Relaxed);
469 }
470 Err(index) => {
471 self.stats.insertions.fetch_add(1, Ordering::Relaxed);
472 slot_versions.insert(index, Arc::clone(&entry));
473 }
474 }
475 slot_versions.retain(|existing| {
479 existing.deployment_slot != entry.deployment_slot
480 || existing
481 .program
482 .get_environment()
483 .zip(entry.program.get_environment())
484 .map(|(a, b)| a != b)
485 .unwrap_or(false)
486 || Arc::ptr_eq(existing, &entry)
487 });
488 }
489 }
490 false
491 }
492
493 pub fn prune_by_deployment_slot(&mut self, slot: Slot) {
494 match &mut self.index {
495 IndexImplementation::V1 { entries, .. } => {
496 for second_level in entries.values_mut() {
497 second_level.retain(|entry| entry.deployment_slot != slot);
498 }
499 self.remove_programs_with_no_entries();
500 }
501 }
502 }
503
504 pub fn prune(
506 &mut self,
507 new_root_slot: Slot,
508 new_environment: Option<ProgramRuntimeEnvironment>,
509 fork_graph: &FG,
510 ) {
511 match &mut self.index {
512 IndexImplementation::V1 { entries, .. } => {
513 for second_level in entries.values_mut() {
514 let mut first_ancestor_found = false;
516 let mut first_ancestor_env = None;
517 *second_level = second_level
518 .iter()
519 .rev()
520 .filter(|entry| {
521 let relation =
522 fork_graph.relationship(entry.deployment_slot, new_root_slot);
523 if entry.deployment_slot >= new_root_slot {
524 matches!(relation, BlockRelation::Equal | BlockRelation::Descendant)
525 } else if matches!(relation, BlockRelation::Ancestor)
526 || entry.deployment_slot <= self.latest_root_slot
527 {
528 if !first_ancestor_found {
529 first_ancestor_found = true;
530 first_ancestor_env = entry.program.get_environment();
531 return true;
532 }
533 if let Some(entry_env) = entry.program.get_environment()
541 && let Some(env) = first_ancestor_env
542 && entry_env != env
543 {
544 return true;
545 }
546 self.stats.prunes_orphan.fetch_add(1, Ordering::Relaxed);
547 false
548 } else {
549 self.stats.prunes_orphan.fetch_add(1, Ordering::Relaxed);
550 false
551 }
552 })
553 .filter(|entry| {
554 if let Some(new_environment) = new_environment.as_ref()
556 && !Self::matches_environment(entry, new_environment)
557 {
558 self.stats
559 .prunes_environment
560 .fetch_add(1, Ordering::Relaxed);
561 return false;
562 }
563 true
564 })
565 .cloned()
566 .collect();
567 second_level.reverse();
568 }
569 }
570 }
571 self.remove_programs_with_no_entries();
572 debug_assert!(self.latest_root_slot <= new_root_slot);
573 self.latest_root_slot = new_root_slot;
574 }
575
576 fn matches_environment(
577 entry: &Arc<ProgramCacheEntry>,
578 program_runtime_environment: &ProgramRuntimeEnvironment,
579 ) -> bool {
580 let Some(environment) = entry.program.get_environment() else {
581 return true;
582 };
583 environment == program_runtime_environment
584 }
585
586 fn matches_criteria(
587 program: &Arc<ProgramCacheEntry>,
588 criteria: &ProgramCacheMatchCriteria,
589 ) -> bool {
590 match criteria {
591 ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(slot) => {
592 program.deployment_slot >= *slot
593 }
594 ProgramCacheMatchCriteria::NoCriteria => true,
595 }
596 }
597
598 pub fn extract(
601 &self,
602 search_for: &mut Vec<ProgramToLoad>,
603 loaded_programs_for_tx_batch: &mut ProgramCacheForTxBatch,
604 program_runtime_environment_for_execution: &ProgramRuntimeEnvironment,
605 increment_usage_counter: bool,
606 count_hits_and_misses: bool,
607 ) -> Option<Pubkey> {
608 debug_assert!(self.fork_graph.is_some());
609 let fork_graph = self.fork_graph.as_ref().unwrap().upgrade().unwrap();
610 let locked_fork_graph = fork_graph.read().unwrap();
611 let mut cooperative_loading_task = None;
612 match &self.index {
613 IndexImplementation::V1 {
614 entries,
615 loading_entries,
616 } => {
617 search_for.retain(|program_to_load| {
618 if let Some(second_level) = entries.get(program_to_load.program_id) {
619 let mut filter_by_deployment_slot = None;
620 for entry in second_level.iter().rev() {
621 let required_deployment_slot =
622 filter_by_deployment_slot.unwrap_or(entry.deployment_slot);
623 if required_deployment_slot != entry.deployment_slot
624 || program_to_load.loader != entry.account_owner
625 {
626 continue;
627 }
628 let entry_in_same_branch = entry.deployment_slot
629 <= self.latest_root_slot
630 || matches!(
631 locked_fork_graph.relationship(
632 entry.deployment_slot,
633 loaded_programs_for_tx_batch.slot
634 ),
635 BlockRelation::Equal | BlockRelation::Ancestor
636 );
637 if entry_in_same_branch {
638 let entry_is_effective =
639 loaded_programs_for_tx_batch.slot >= entry.effective_slot();
640 let entry_to_return = if entry_is_effective {
641 if !Self::matches_environment(
642 entry,
643 program_runtime_environment_for_execution,
644 ) {
645 filter_by_deployment_slot = filter_by_deployment_slot
654 .or(Some(entry.deployment_slot));
655 continue;
656 }
657 if !Self::matches_criteria(
658 entry,
659 &program_to_load.match_criteria,
660 ) {
661 break;
662 }
663 if let ProgramCacheEntryType::Unloaded(_environment) =
664 &entry.program
665 {
666 break;
667 }
668 entry.clone()
669 } else if entry.is_implicit_delay_visibility_tombstone(
670 loaded_programs_for_tx_batch.slot,
671 ) {
672 Arc::new(ProgramCacheEntry::new_delay_visibility_tombstone(
676 entry.deployment_slot,
677 entry.account_owner,
678 Arc::clone(&entry.stats),
679 ))
680 } else {
681 continue;
682 };
683 entry_to_return
684 .update_access_slot(loaded_programs_for_tx_batch.slot);
685 if increment_usage_counter {
686 entry_to_return.stats.uses.fetch_add(1, Ordering::Relaxed);
687 }
688 loaded_programs_for_tx_batch
689 .entries
690 .insert(*program_to_load.program_id, entry_to_return);
691 return false;
692 }
693 }
694 }
695 if cooperative_loading_task.is_none() {
696 let mut loading_entries = loading_entries.lock().unwrap();
697 let entry = loading_entries.entry(*program_to_load.program_id);
698 if let Entry::Vacant(entry) = entry {
699 entry.insert((
700 loaded_programs_for_tx_batch.slot,
701 thread::current().id(),
702 ));
703 cooperative_loading_task = Some(*program_to_load.program_id);
704 }
705 }
706 true
707 });
708 }
709 }
710 drop(locked_fork_graph);
711 if count_hits_and_misses {
712 self.stats
713 .misses
714 .fetch_add(search_for.len() as u64, Ordering::Relaxed);
715 self.stats.hits.fetch_add(
716 loaded_programs_for_tx_batch.entries.len() as u64,
717 Ordering::Relaxed,
718 );
719 }
720 cooperative_loading_task
721 }
722
723 pub fn finish_cooperative_loading_task(
725 &mut self,
726 program_runtime_environment: &ProgramRuntimeEnvironment,
727 current_slot: Slot,
728 key: Pubkey,
729 last_modification_slot: Slot,
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 last_modification_slot,
758 loaded_program,
759 );
760 self.loading_task_waiter.notify();
761 was_occupied
762 }
763 }
764 }
765
766 pub fn merge(
767 &mut self,
768 program_runtime_environment: &ProgramRuntimeEnvironment,
769 current_slot: Slot,
770 modified_entries: &HashMap<Pubkey, Arc<ProgramCacheEntry>>,
771 ) {
772 modified_entries.iter().for_each(|(key, entry)| {
773 self.assign_program(
774 program_runtime_environment,
775 *key,
776 current_slot,
777 entry.clone(),
778 );
779 })
780 }
781
782 pub fn get_flattened_entries(&self) -> Vec<(Pubkey, Slot, Arc<ProgramCacheEntry>)> {
784 match &self.index {
785 IndexImplementation::V1 { entries, .. } => entries
786 .iter()
787 .flat_map(|(id, second_level)| {
788 second_level
789 .iter()
790 .filter_map(move |program| match program.program {
791 ProgramCacheEntryType::Loaded(_) => Some((*id, 0, program.clone())),
792 _ => None,
793 })
794 })
795 .collect(),
796 }
797 }
798
799 #[cfg(feature = "dev-context-only-utils")]
801 pub fn get_flattened_entries_for_tests(&self) -> Vec<(Pubkey, Arc<ProgramCacheEntry>)> {
802 match &self.index {
803 IndexImplementation::V1 { entries, .. } => entries
804 .iter()
805 .flat_map(|(id, second_level)| {
806 second_level.iter().map(|program| (*id, program.clone()))
807 })
808 .collect(),
809 }
810 }
811
812 pub fn get_slot_versions_for_tests(&self, key: &Pubkey) -> &[Arc<ProgramCacheEntry>] {
814 match &self.index {
815 IndexImplementation::V1 { entries, .. } => entries
816 .get(key)
817 .map(|second_level| second_level.as_ref())
818 .unwrap_or(&[]),
819 }
820 }
821
822 pub fn sort_and_unload(&mut self, shrink_to_percent: Percent) {
824 let mut sorted_candidates = self.get_flattened_entries();
825 sorted_candidates.sort_by_cached_key(|(_id, _last_modification_slot, program)| {
826 program.stats.uses.load(Ordering::Relaxed)
827 });
828 let num_to_unload = sorted_candidates
829 .len()
830 .saturating_sub(percent_of_max_entries(shrink_to_percent));
831 for (program, last_modification_slot, entry) in sorted_candidates.iter().take(num_to_unload)
832 {
833 self.unload_program_entry(*program, *last_modification_slot, entry);
834 }
835 }
836
837 pub fn evict_using_random_selection(&mut self, shrink_to_percent: Percent, now: Slot) {
843 let mut candidates = self.get_flattened_entries();
844 let mut rng = rng();
845 self.stats
846 .water_level
847 .store(candidates.len() as u64, Ordering::Relaxed);
848 let num_to_unload = candidates
849 .len()
850 .saturating_sub(percent_of_max_entries(shrink_to_percent));
851 let mut sample_entry = |candidates: &Vec<(Pubkey, u64, Arc<ProgramCacheEntry>)>| {
852 #[cfg(feature = "shuttle-test")]
855 let index = rng.gen_range(0..candidates.len());
856 #[cfg(not(feature = "shuttle-test"))]
857 let index = rng.random_range(0..candidates.len());
858 let usage_counter = candidates
859 .get(index)
860 .expect("Failed to get cached entry")
861 .2
862 .retention_score();
863 (index, usage_counter)
864 };
865
866 const MAX_ADDITIONAL_SAMPLES: usize = 3;
874 let avoid_evicting_above_score = retention_score(now, 500 * EMA_SCALE, 500);
875 for _ in 0..num_to_unload {
876 let (mut index, mut score) = sample_entry(&candidates);
877 for _ in 0..MAX_ADDITIONAL_SAMPLES {
878 let (sample_index, sample_score) = sample_entry(&candidates);
879 if score > sample_score {
880 index = sample_index;
881 score = sample_score;
882 }
883 if score < avoid_evicting_above_score {
884 break;
885 }
886 }
887 let (id, last_modification_slot, entry) = candidates.swap_remove(index);
888 self.unload_program_entry(id, last_modification_slot, &entry);
889 }
890 }
891
892 pub fn remove_programs(&mut self, keys: impl Iterator<Item = Pubkey>) {
894 match &mut self.index {
895 IndexImplementation::V1 { entries, .. } => {
896 for k in keys {
897 entries.remove(&k);
898 }
899 }
900 }
901 }
902
903 fn unload_program_entry(
906 &mut self,
907 id: Pubkey,
908 _last_modification_slot: Slot,
909 remove_entry: &Arc<ProgramCacheEntry>,
910 ) {
911 match &mut self.index {
912 IndexImplementation::V1 { entries, .. } => {
913 let second_level = entries.get_mut(&id).expect("Cache lookup failed");
914 let candidate = second_level
915 .iter_mut()
916 .find(|entry| Arc::ptr_eq(entry, remove_entry))
917 .expect("Program entry not found");
918
919 if let ProgramCacheEntryType::Loaded(_) = candidate.program
921 && let Some(unloaded) = candidate.to_unloaded()
922 {
923 if candidate.stats.uses.load(Ordering::Relaxed) == 1 {
924 self.stats.one_hit_wonders.fetch_add(1, Ordering::Relaxed);
925 }
926 self.stats
927 .evictions
928 .entry(id)
929 .and_modify(|c| *c = c.saturating_add(1))
930 .or_insert(1);
931 *candidate = Arc::new(unloaded);
932 }
933 }
934 }
935 }
936
937 fn remove_programs_with_no_entries(&mut self) {
938 match &mut self.index {
939 IndexImplementation::V1 { entries, .. } => {
940 let num_programs_before_removal = entries.len();
941 entries.retain(|_key, second_level| !second_level.is_empty());
942 if entries.len() < num_programs_before_removal {
943 self.stats.empty_entries.fetch_add(
944 num_programs_before_removal.saturating_sub(entries.len()) as u64,
945 Ordering::Relaxed,
946 );
947 }
948 }
949 }
950 }
951}
952
953#[cfg(feature = "frozen-abi")]
954impl solana_frozen_abi::abi_example::AbiExample for ProgramCacheEntry {
955 fn example() -> Self {
956 Self::default()
958 }
959}
960
961#[cfg(feature = "frozen-abi")]
962impl<FG: ForkGraph> solana_frozen_abi::abi_example::AbiExample for ProgramCache<FG> {
963 fn example() -> Self {
964 Self::new(Slot::default())
966 }
967}
968
969#[cfg(test)]
970pub(crate) mod tests {
971 use {
972 crate::{
973 loaded_programs::{
974 BlockRelation, ForkGraph, Percent, ProgramCache, ProgramCacheForTxBatch,
975 ProgramCacheMatchCriteria, ProgramRuntimeEnvironment, ProgramToLoad,
976 get_mock_program_runtime_environment,
977 },
978 program_cache_entry::{
979 ProgramCacheEntry, ProgramCacheEntryOwner, ProgramCacheEntryType,
980 },
981 program_metrics::ProgramStatistics,
982 },
983 assert_matches::assert_matches,
984 solana_clock::Slot,
985 solana_pubkey::Pubkey,
986 solana_sbpf::{elf::Executable, program::BuiltinProgram},
987 solana_svm_type_overrides::sync::{
988 Arc, RwLock,
989 atomic::{AtomicU64, Ordering},
990 },
991 std::{fs::File, io::Read, ops::ControlFlow},
992 test_case::test_matrix,
993 };
994
995 fn new_test_entry(deployment_slot: Slot) -> Arc<ProgramCacheEntry> {
996 new_test_entry_with_usage(deployment_slot, ProgramStatistics::default())
997 }
998
999 fn new_loaded_entry(env: ProgramRuntimeEnvironment) -> ProgramCacheEntryType {
1000 let mut elf = Vec::new();
1001 File::open("../programs/bpf_loader/test_elfs/out/noop_aligned.so")
1002 .unwrap()
1003 .read_to_end(&mut elf)
1004 .unwrap();
1005 let executable = Executable::load(&elf, Arc::clone(&*env)).unwrap();
1006 ProgramCacheEntryType::Loaded(executable)
1007 }
1008
1009 pub(crate) fn new_test_entry_with_usage(
1010 deployment_slot: Slot,
1011 stats: ProgramStatistics,
1012 ) -> Arc<ProgramCacheEntry> {
1013 Arc::new(ProgramCacheEntry {
1014 program: new_loaded_entry(get_mock_program_runtime_environment()),
1015 account_owner: ProgramCacheEntryOwner::LoaderV2,
1016 deployment_slot,
1017 stats: Arc::new(stats),
1018 latest_access_slot: AtomicU64::new(deployment_slot),
1019 })
1020 }
1021
1022 fn new_test_builtin_entry(deployment_slot: Slot) -> Arc<ProgramCacheEntry> {
1023 Arc::new(ProgramCacheEntry {
1024 program: ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1025 account_owner: ProgramCacheEntryOwner::NativeLoader,
1026 deployment_slot,
1027 stats: Arc::default(),
1028 latest_access_slot: AtomicU64::default(),
1029 })
1030 }
1031
1032 fn set_failed_verification_tombstone<FG: ForkGraph>(
1033 cache: &mut ProgramCache<FG>,
1034 key: Pubkey,
1035 current_slot: Slot,
1036 env: ProgramRuntimeEnvironment,
1037 ) -> Arc<ProgramCacheEntry> {
1038 let program = Arc::new(ProgramCacheEntry::new_failed_verification_tombstone(
1039 current_slot,
1040 ProgramCacheEntryOwner::LoaderV2,
1041 ProgramRuntimeEnvironment::clone(&env),
1042 ));
1043 cache.assign_program(&env, key, current_slot, program.clone());
1044 program
1045 }
1046
1047 fn insert_unloaded_entry<FG: ForkGraph>(
1048 cache: &mut ProgramCache<FG>,
1049 key: Pubkey,
1050 current_slot: Slot,
1051 ) -> Arc<ProgramCacheEntry> {
1052 let env = get_mock_program_runtime_environment();
1053 let loaded = new_test_entry_with_usage(current_slot, ProgramStatistics::default());
1054 let unloaded = Arc::new(loaded.to_unloaded().expect("Failed to unload the program"));
1055 cache.assign_program(&env, key, current_slot, unloaded.clone());
1056 unloaded
1057 }
1058
1059 fn num_matching_entries<P, FG>(cache: &ProgramCache<FG>, predicate: P) -> usize
1060 where
1061 P: Fn(&ProgramCacheEntryType) -> bool,
1062 FG: ForkGraph,
1063 {
1064 cache
1065 .get_flattened_entries_for_tests()
1066 .iter()
1067 .filter(|(_key, program)| predicate(&program.program))
1068 .count()
1069 }
1070
1071 #[expect(clippy::arithmetic_side_effects)]
1072 fn program_deploy_test_helper(
1073 cache: &mut ProgramCache<TestForkGraph>,
1074 program: Pubkey,
1075 deployment_slots: Vec<Slot>,
1076 usage_counters: Vec<u64>,
1077 programs: &mut Vec<(Pubkey, Slot, u64)>,
1078 ) {
1079 let env = get_mock_program_runtime_environment();
1080 deployment_slots
1082 .iter()
1083 .enumerate()
1084 .for_each(|(i, deployment_slot)| {
1085 let usage_counter = *usage_counters.get(i).unwrap_or(&0);
1086 let stats = ProgramStatistics {
1087 uses: usage_counter.into(),
1088 ..Default::default()
1089 };
1090 cache.assign_program(
1091 &env,
1092 program,
1093 *deployment_slot,
1094 new_test_entry_with_usage(*deployment_slot, stats),
1095 );
1096 programs.push((program, *deployment_slot, usage_counter));
1097 });
1098
1099 let next_slot = deployment_slots.iter().max().map_or(0, |slot| slot + 1);
1100
1101 let env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
1103 for slot in next_slot..next_slot + 10 {
1104 set_failed_verification_tombstone(
1105 cache,
1106 program,
1107 slot,
1108 ProgramRuntimeEnvironment::clone(&env),
1109 );
1110 }
1111
1112 for slot in next_slot + 10..next_slot + 20 {
1114 insert_unloaded_entry(cache, program, slot);
1115 }
1116 }
1117
1118 #[test]
1119 fn test_random_eviction() {
1120 let mut programs = vec![];
1121 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1122
1123 program_deploy_test_helper(
1129 &mut cache,
1130 Pubkey::new_unique(),
1131 vec![0, 10, 20, 30, 40],
1132 vec![4, 5, 25, 35, 12],
1133 &mut programs,
1134 );
1135
1136 program_deploy_test_helper(
1138 &mut cache,
1139 Pubkey::new_unique(),
1140 vec![5, 11, 21, 24],
1141 vec![0, 2, 30, 45],
1142 &mut programs,
1143 );
1144
1145 program_deploy_test_helper(
1147 &mut cache,
1148 Pubkey::new_unique(),
1149 vec![0, 5, 15, 25],
1150 vec![100, 3, 20, 40],
1151 &mut programs,
1152 );
1153
1154 let num_loaded_expected = 13;
1156 let num_unloaded_expected = 30;
1158 let num_tombstones_expected = 30;
1160
1161 programs.sort_by_key(|(_id, _slot, usage_count)| *usage_count);
1163 let num_loaded = num_matching_entries(&cache, |program_type| {
1164 matches!(program_type, ProgramCacheEntryType::Loaded(_))
1165 });
1166 let num_unloaded = num_matching_entries(&cache, |program_type| {
1167 matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1168 });
1169 let num_tombstones = num_matching_entries(&cache, |program_type| {
1170 matches!(
1171 program_type,
1172 ProgramCacheEntryType::DelayVisibility
1173 | ProgramCacheEntryType::FailedVerification(_)
1174 | ProgramCacheEntryType::Closed
1175 )
1176 });
1177
1178 assert_eq!(num_loaded, num_loaded_expected);
1180 assert_eq!(num_unloaded, num_unloaded_expected);
1181 assert_eq!(num_tombstones, num_tombstones_expected);
1182
1183 let eviction_pct: Percent = 1;
1185
1186 let num_loaded_expected = crate::loaded_programs::percent_of_max_entries(eviction_pct);
1187 let num_unloaded_expected = num_unloaded_expected + num_loaded - num_loaded_expected;
1188 cache.evict_using_random_selection(eviction_pct, 21);
1189
1190 let num_loaded = num_matching_entries(&cache, |program_type| {
1192 matches!(program_type, ProgramCacheEntryType::Loaded(_))
1193 });
1194 let num_unloaded = num_matching_entries(&cache, |program_type| {
1195 matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1196 });
1197 let num_tombstones = num_matching_entries(&cache, |program_type| {
1198 matches!(program_type, ProgramCacheEntryType::FailedVerification(_))
1199 });
1200
1201 assert_eq!(num_loaded, num_loaded_expected);
1203 assert_eq!(num_unloaded, num_unloaded_expected);
1205 assert_eq!(num_tombstones, num_tombstones_expected);
1207 }
1208
1209 #[test]
1210 fn test_eviction() {
1211 let mut programs = vec![];
1212 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1213
1214 program_deploy_test_helper(
1216 &mut cache,
1217 Pubkey::new_unique(),
1218 vec![0, 10, 20, 30, 40],
1219 vec![4, 5, 25, 35, 12],
1220 &mut programs,
1221 );
1222
1223 program_deploy_test_helper(
1225 &mut cache,
1226 Pubkey::new_unique(),
1227 vec![5, 11, 21, 24],
1228 vec![0, 2, 30, 45],
1229 &mut programs,
1230 );
1231
1232 program_deploy_test_helper(
1234 &mut cache,
1235 Pubkey::new_unique(),
1236 vec![0, 5, 15, 25],
1237 vec![100, 3, 20, 40],
1238 &mut programs,
1239 );
1240
1241 let num_loaded_expected = 13;
1243 let num_unloaded_expected = 30;
1245 let num_tombstones_expected = 30;
1247
1248 programs.sort_by_key(|(_id, _slot, usage_count)| *usage_count);
1250 let num_loaded = num_matching_entries(&cache, |program_type| {
1251 matches!(program_type, ProgramCacheEntryType::Loaded(_))
1252 });
1253 let num_unloaded = num_matching_entries(&cache, |program_type| {
1254 matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1255 });
1256 let num_tombstones = num_matching_entries(&cache, |program_type| {
1257 matches!(program_type, ProgramCacheEntryType::FailedVerification(_))
1258 });
1259
1260 assert_eq!(num_loaded, num_loaded_expected);
1262 assert_eq!(num_unloaded, num_unloaded_expected);
1263 assert_eq!(num_tombstones, num_tombstones_expected);
1264
1265 let eviction_pct: Percent = 1;
1267
1268 let num_loaded_expected = crate::loaded_programs::percent_of_max_entries(eviction_pct);
1269 let num_unloaded_expected = num_unloaded_expected + num_loaded - num_loaded_expected;
1270
1271 cache.sort_and_unload(eviction_pct);
1272
1273 let entries = cache.get_flattened_entries_for_tests();
1275 programs.iter().for_each(|entry| {
1276 assert!(entries.iter().any(|(key, _entry)| key == &entry.0));
1277 });
1278
1279 let unloaded = entries
1280 .iter()
1281 .filter_map(|(key, program)| {
1282 matches!(program.program, ProgramCacheEntryType::Unloaded(_))
1283 .then_some((*key, program.stats.uses.load(Ordering::Relaxed)))
1284 })
1285 .collect::<Vec<(Pubkey, u64)>>();
1286
1287 for index in 0..3 {
1288 let expected = programs.get(index).expect("Missing program");
1289 assert!(unloaded.contains(&(expected.0, expected.2)));
1290 }
1291
1292 let num_loaded = num_matching_entries(&cache, |program_type| {
1294 matches!(program_type, ProgramCacheEntryType::Loaded(_))
1295 });
1296 let num_unloaded = num_matching_entries(&cache, |program_type| {
1297 matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1298 });
1299 let num_tombstones = num_matching_entries(&cache, |program_type| {
1300 matches!(
1301 program_type,
1302 ProgramCacheEntryType::DelayVisibility
1303 | ProgramCacheEntryType::FailedVerification(_)
1304 | ProgramCacheEntryType::Closed
1305 )
1306 });
1307
1308 assert_eq!(num_loaded, num_loaded_expected);
1310 assert_eq!(num_unloaded, num_unloaded_expected);
1312 assert_eq!(num_tombstones, num_tombstones_expected);
1314 }
1315
1316 #[test]
1317 fn test_usage_count_of_unloaded_program() {
1318 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1319 let env = get_mock_program_runtime_environment();
1320
1321 let program = Pubkey::new_unique();
1322 let evict_to_pct: Percent = 2;
1323 let cache_capacity_after_shrink =
1324 crate::loaded_programs::percent_of_max_entries(evict_to_pct);
1325 let num_total_programs = (cache_capacity_after_shrink + 1) as u64;
1327 (0..num_total_programs).for_each(|i| {
1328 let stats = ProgramStatistics {
1329 uses: (i + 10).into(),
1330 ..Default::default()
1331 };
1332 let entry = new_test_entry_with_usage(i, stats);
1333 cache.assign_program(&env, program, i, entry);
1334 });
1335
1336 cache.sort_and_unload(evict_to_pct);
1337
1338 let num_unloaded = num_matching_entries(&cache, |program_type| {
1339 matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1340 });
1341 assert_eq!(num_unloaded, 1);
1342
1343 cache
1344 .get_flattened_entries_for_tests()
1345 .iter()
1346 .for_each(|(_key, program)| {
1347 if matches!(program.program, ProgramCacheEntryType::Unloaded(_)) {
1348 assert_eq!(program.stats.uses.load(Ordering::Relaxed), 10);
1350 assert_eq!(program.deployment_slot, 0);
1351 assert_eq!(program.effective_slot(), 1);
1352 }
1353 });
1354
1355 cache.assign_program(
1358 &env,
1359 program,
1360 0,
1361 new_test_entry_with_usage(0, ProgramStatistics::default()),
1362 );
1363
1364 cache
1365 .get_flattened_entries_for_tests()
1366 .iter()
1367 .for_each(|(_key, program)| {
1368 if matches!(program.program, ProgramCacheEntryType::Unloaded(_))
1369 && program.deployment_slot == 0
1370 && program.effective_slot() == 1
1371 {
1372 assert_eq!(program.stats.uses.load(Ordering::Relaxed), 10);
1374 }
1375 });
1376 }
1377
1378 #[test]
1379 fn test_fuzz_assign_program_order() {
1380 use rand::prelude::SliceRandom;
1381 const EXPECTED_ENTRIES: [(u64, bool); 5] =
1382 [(1, true), (3, false), (5, true), (9, true), (10, false)];
1383 let mut rng = rand::rng();
1384 let program_id = Pubkey::new_unique();
1385 let env = get_mock_program_runtime_environment();
1386 for _ in 0..1000 {
1387 let mut entries = EXPECTED_ENTRIES.to_vec();
1388 entries.shuffle(&mut rng);
1389 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1390 for (deployment_slot, delay_visibility) in entries {
1391 let entry = Arc::new(if delay_visibility {
1392 ProgramCacheEntry {
1393 program: new_loaded_entry(ProgramRuntimeEnvironment::from(
1394 BuiltinProgram::new_mock(),
1395 )), account_owner: ProgramCacheEntryOwner::LoaderV2,
1397 deployment_slot,
1398 stats: Arc::default(),
1399 latest_access_slot: AtomicU64::new(deployment_slot),
1400 }
1401 } else {
1402 ProgramCacheEntry::new_failed_verification_tombstone(
1403 deployment_slot,
1404 ProgramCacheEntryOwner::LoaderV2,
1405 ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock()), )
1407 });
1408 assert!(!cache.assign_program(&env, program_id, deployment_slot, entry));
1409 }
1410 for ((deployment_slot, delay_visibility), entry) in EXPECTED_ENTRIES
1411 .iter()
1412 .zip(cache.get_slot_versions_for_tests(&program_id).iter())
1413 {
1414 assert_eq!(entry.deployment_slot, *deployment_slot);
1415 assert_eq!(
1416 entry.effective_slot(),
1417 deployment_slot.saturating_add(*delay_visibility as u64)
1418 );
1419 }
1420 }
1421 }
1422
1423 #[test_matrix(
1424 (
1425 ProgramCacheEntryType::FailedVerification(get_mock_program_runtime_environment()),
1426 new_loaded_entry(get_mock_program_runtime_environment()),
1427 ),
1428 (
1429 ProgramCacheEntryType::FailedVerification(get_mock_program_runtime_environment()),
1430 ProgramCacheEntryType::Closed,
1431 ProgramCacheEntryType::Unloaded(get_mock_program_runtime_environment()),
1432 new_loaded_entry(get_mock_program_runtime_environment()),
1433 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1434 )
1435 )]
1436 #[test_matrix(
1437 ProgramCacheEntryType::Closed,
1438 (
1439 ProgramCacheEntryType::FailedVerification(get_mock_program_runtime_environment()),
1440 ProgramCacheEntryType::Closed,
1441 new_loaded_entry(get_mock_program_runtime_environment()),
1442 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1443 )
1444 )]
1445 #[test_matrix(
1446 ProgramCacheEntryType::Unloaded(get_mock_program_runtime_environment()),
1447 (
1448 ProgramCacheEntryType::Closed,
1449 ProgramCacheEntryType::Unloaded(get_mock_program_runtime_environment()),
1450 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1451 )
1452 )]
1453 #[test_matrix(
1454 (ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),),
1455 (
1456 ProgramCacheEntryType::FailedVerification(get_mock_program_runtime_environment()),
1457 ProgramCacheEntryType::Closed,
1458 ProgramCacheEntryType::Unloaded(get_mock_program_runtime_environment()),
1459 new_loaded_entry(get_mock_program_runtime_environment()),
1460 )
1461 )]
1462 #[should_panic(expected = "Unexpected replacement of an entry")]
1463 fn test_assign_program_failure(old: ProgramCacheEntryType, new: ProgramCacheEntryType) {
1464 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1465 let env = get_mock_program_runtime_environment();
1466 let program_id = Pubkey::new_unique();
1467 assert!(!cache.assign_program(
1468 &env,
1469 program_id,
1470 10,
1471 Arc::new(ProgramCacheEntry {
1472 program: old,
1473 account_owner: ProgramCacheEntryOwner::LoaderV2,
1474 deployment_slot: 10,
1475 stats: Arc::default(),
1476 latest_access_slot: AtomicU64::default(),
1477 }),
1478 ));
1479 cache.assign_program(
1480 &env,
1481 program_id,
1482 10,
1483 Arc::new(ProgramCacheEntry {
1484 program: new,
1485 account_owner: ProgramCacheEntryOwner::LoaderV2,
1486 deployment_slot: 10,
1487 stats: Arc::default(),
1488 latest_access_slot: AtomicU64::default(),
1489 }),
1490 );
1491 }
1492
1493 #[test_matrix(
1494 ProgramCacheEntryType::Unloaded(get_mock_program_runtime_environment()),
1495 (
1496 new_loaded_entry(get_mock_program_runtime_environment()),
1497 ProgramCacheEntryType::FailedVerification(get_mock_program_runtime_environment()),
1498 )
1499 )]
1500 #[test_case(
1501 ProgramCacheEntryType::Closed,
1502 ProgramCacheEntryType::Unloaded(get_mock_program_runtime_environment())
1503 )]
1504 #[test_case(
1505 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1506 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock())
1507 )]
1508 fn test_assign_program_success(old: ProgramCacheEntryType, new: ProgramCacheEntryType) {
1509 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1510 let env = get_mock_program_runtime_environment();
1511 let program_id = Pubkey::new_unique();
1512 assert!(!cache.assign_program(
1513 &env,
1514 program_id,
1515 10,
1516 Arc::new(ProgramCacheEntry {
1517 program: old,
1518 account_owner: ProgramCacheEntryOwner::LoaderV2,
1519 deployment_slot: 10,
1520 stats: Arc::default(),
1521 latest_access_slot: AtomicU64::default(),
1522 }),
1523 ));
1524 assert!(!cache.assign_program(
1525 &env,
1526 program_id,
1527 10,
1528 Arc::new(ProgramCacheEntry {
1529 program: new,
1530 account_owner: ProgramCacheEntryOwner::LoaderV2,
1531 deployment_slot: 10,
1532 stats: Arc::default(),
1533 latest_access_slot: AtomicU64::default(),
1534 }),
1535 ));
1536 }
1537
1538 #[test]
1539 fn test_assign_program_removes_entries_in_same_slot() {
1540 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1541 let env = get_mock_program_runtime_environment();
1542 let program_id = Pubkey::new_unique();
1543 let closed_other_slot = Arc::new(ProgramCacheEntry {
1544 program: ProgramCacheEntryType::Closed,
1545 account_owner: ProgramCacheEntryOwner::LoaderV2,
1546 deployment_slot: 9,
1547 stats: Arc::default(),
1548 latest_access_slot: AtomicU64::default(),
1549 });
1550 let closed_current_slot = Arc::new(ProgramCacheEntry {
1551 program: ProgramCacheEntryType::Closed,
1552 account_owner: ProgramCacheEntryOwner::LoaderV2,
1553 deployment_slot: 10,
1554 stats: Arc::default(),
1555 latest_access_slot: AtomicU64::default(),
1556 });
1557 let loaded_entry_current_env = Arc::new(ProgramCacheEntry {
1558 program: ProgramCacheEntryType::Unloaded(get_mock_program_runtime_environment()),
1559 account_owner: ProgramCacheEntryOwner::LoaderV2,
1560 deployment_slot: 10,
1561 stats: Arc::default(),
1562 latest_access_slot: AtomicU64::default(),
1563 });
1564 let loaded_entry_upcoming_env = Arc::new(ProgramCacheEntry {
1565 program: ProgramCacheEntryType::Unloaded(ProgramRuntimeEnvironment::from(
1566 BuiltinProgram::new_mock(),
1567 )),
1568 account_owner: ProgramCacheEntryOwner::LoaderV2,
1569 deployment_slot: 10,
1570 stats: Arc::default(),
1571 latest_access_slot: AtomicU64::default(),
1572 });
1573 assert!(!cache.assign_program(&env, program_id, 9, closed_other_slot.clone()));
1574 assert!(!cache.assign_program(&env, program_id, 10, closed_current_slot));
1575 assert!(!cache.assign_program(&env, program_id, 10, loaded_entry_upcoming_env.clone()));
1576 assert!(!cache.assign_program(&env, program_id, 10, loaded_entry_current_env.clone()));
1577 assert_eq!(
1579 cache.get_slot_versions_for_tests(&program_id),
1580 &[
1581 closed_other_slot,
1582 loaded_entry_current_env,
1583 loaded_entry_upcoming_env
1584 ]
1585 );
1586 }
1587
1588 #[test]
1589 fn test_tombstone() {
1590 let env = get_mock_program_runtime_environment();
1591 let tombstone = ProgramCacheEntry::new_failed_verification_tombstone(
1592 0,
1593 ProgramCacheEntryOwner::LoaderV2,
1594 env.clone(),
1595 );
1596 assert_matches!(
1597 tombstone.program,
1598 ProgramCacheEntryType::FailedVerification(_)
1599 );
1600 assert!(tombstone.is_tombstone());
1601 assert_eq!(tombstone.deployment_slot, 0);
1602 assert_eq!(tombstone.effective_slot(), 0);
1603
1604 let tombstone =
1605 ProgramCacheEntry::new_closed_tombstone(100, ProgramCacheEntryOwner::LoaderV2);
1606 assert_matches!(tombstone.program, ProgramCacheEntryType::Closed);
1607 assert!(tombstone.is_tombstone());
1608 assert_eq!(tombstone.deployment_slot, 100);
1609 assert_eq!(tombstone.effective_slot(), 100);
1610
1611 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1612 let program1 = Pubkey::new_unique();
1613 let tombstone = set_failed_verification_tombstone(&mut cache, program1, 10, env.clone());
1614 let slot_versions = cache.get_slot_versions_for_tests(&program1);
1615 assert_eq!(slot_versions.len(), 1);
1616 assert!(slot_versions.first().unwrap().is_tombstone());
1617 assert_eq!(tombstone.deployment_slot, 10);
1618 assert_eq!(tombstone.effective_slot(), 10);
1619
1620 let program2 = Pubkey::new_unique();
1622 cache.assign_program(&env, program2, 50, new_test_builtin_entry(50));
1623 let slot_versions = cache.get_slot_versions_for_tests(&program2);
1624 assert_eq!(slot_versions.len(), 1);
1625 assert!(!slot_versions.first().unwrap().is_tombstone());
1626
1627 let tombstone = set_failed_verification_tombstone(&mut cache, program2, 60, env);
1628 let slot_versions = cache.get_slot_versions_for_tests(&program2);
1629 assert_eq!(slot_versions.len(), 2);
1630 assert!(!slot_versions.first().unwrap().is_tombstone());
1631 assert!(slot_versions.get(1).unwrap().is_tombstone());
1632 assert!(tombstone.is_tombstone());
1633 assert_eq!(tombstone.deployment_slot, 60);
1634 assert_eq!(tombstone.effective_slot(), 60);
1635 }
1636
1637 struct TestForkGraph {
1638 relation: BlockRelation,
1639 }
1640 impl ForkGraph for TestForkGraph {
1641 fn relationship(&self, _a: Slot, _b: Slot) -> BlockRelation {
1642 self.relation
1643 }
1644 }
1645
1646 #[test]
1647 fn test_prune_empty() {
1648 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1649 let fork_graph = Arc::new(RwLock::new(TestForkGraph {
1650 relation: BlockRelation::Unrelated,
1651 }));
1652
1653 cache.set_fork_graph(Arc::downgrade(&fork_graph));
1654
1655 cache.prune(0, None, &fork_graph.read().unwrap());
1656 assert!(cache.get_flattened_entries_for_tests().is_empty());
1657
1658 cache.prune(10, None, &fork_graph.read().unwrap());
1659 assert!(cache.get_flattened_entries_for_tests().is_empty());
1660
1661 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1662 let fork_graph = Arc::new(RwLock::new(TestForkGraph {
1663 relation: BlockRelation::Ancestor,
1664 }));
1665
1666 cache.set_fork_graph(Arc::downgrade(&fork_graph));
1667
1668 cache.prune(0, None, &fork_graph.read().unwrap());
1669 assert!(cache.get_flattened_entries_for_tests().is_empty());
1670
1671 cache.prune(10, None, &fork_graph.read().unwrap());
1672 assert!(cache.get_flattened_entries_for_tests().is_empty());
1673
1674 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1675 let fork_graph = Arc::new(RwLock::new(TestForkGraph {
1676 relation: BlockRelation::Descendant,
1677 }));
1678
1679 cache.set_fork_graph(Arc::downgrade(&fork_graph));
1680
1681 cache.prune(0, None, &fork_graph.read().unwrap());
1682 assert!(cache.get_flattened_entries_for_tests().is_empty());
1683
1684 cache.prune(10, None, &fork_graph.read().unwrap());
1685 assert!(cache.get_flattened_entries_for_tests().is_empty());
1686
1687 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1688 let fork_graph = Arc::new(RwLock::new(TestForkGraph {
1689 relation: BlockRelation::Unknown,
1690 }));
1691 cache.set_fork_graph(Arc::downgrade(&fork_graph));
1692
1693 cache.prune(0, None, &fork_graph.read().unwrap());
1694 assert!(cache.get_flattened_entries_for_tests().is_empty());
1695
1696 cache.prune(10, None, &fork_graph.read().unwrap());
1697 assert!(cache.get_flattened_entries_for_tests().is_empty());
1698 }
1699
1700 #[test]
1701 fn test_prune_with_two_environments_before_epoch_boundary() {
1702 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1703 let env = get_mock_program_runtime_environment();
1704 let new_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
1705 let fork_graph = Arc::new(RwLock::new(TestForkGraph {
1706 relation: BlockRelation::Ancestor,
1707 }));
1708 cache.set_fork_graph(Arc::downgrade(&fork_graph));
1709
1710 let program1 = Pubkey::new_unique();
1711 cache.assign_program(&env, program1, 10, new_test_entry(10));
1712 let updated_program = Arc::new(ProgramCacheEntry {
1713 program: new_loaded_entry(new_env.clone()),
1714 deployment_slot: 20,
1715 ..Default::default()
1716 });
1717 cache.assign_program(&env, program1, 20, updated_program.clone());
1718
1719 assert_eq!(cache.get_slot_versions_for_tests(&program1).len(), 2);
1721
1722 cache.prune(21, None, &fork_graph.read().unwrap());
1723
1724 assert_eq!(cache.get_slot_versions_for_tests(&program1).len(), 2);
1726 }
1727
1728 #[test]
1729 fn test_prune_with_two_environments_after_epoch_boundary() {
1730 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1731 let env = get_mock_program_runtime_environment();
1732 let new_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
1733 let fork_graph = Arc::new(RwLock::new(TestForkGraph {
1734 relation: BlockRelation::Ancestor,
1735 }));
1736 cache.set_fork_graph(Arc::downgrade(&fork_graph));
1737 let program1 = Pubkey::new_unique();
1738
1739 let old_program_old_env = Arc::new(ProgramCacheEntry {
1740 program: new_loaded_entry(env.clone()),
1741 deployment_slot: 10,
1742 ..Default::default()
1743 });
1744 let old_program_new_env = Arc::new(ProgramCacheEntry {
1745 program: new_loaded_entry(new_env.clone()),
1746 deployment_slot: 10,
1747 ..Default::default()
1748 });
1749 let new_program_old_env = Arc::new(ProgramCacheEntry {
1750 program: new_loaded_entry(env.clone()),
1751 deployment_slot: 20,
1752 ..Default::default()
1753 });
1754 cache.assign_program(&env, program1, 10, old_program_old_env.clone());
1755 cache.assign_program(&env, program1, 10, old_program_new_env.clone());
1756 cache.assign_program(&env, program1, 20, new_program_old_env.clone());
1757 let slot_versions = cache.get_slot_versions_for_tests(&program1);
1758 assert_eq!(
1759 &slot_versions,
1760 &[
1761 old_program_new_env.clone(),
1762 old_program_old_env.clone(),
1763 new_program_old_env.clone(),
1764 ]
1765 );
1766
1767 cache.prune(21, Some(new_env.clone()), &fork_graph.read().unwrap());
1768 let slot_versions = cache.get_slot_versions_for_tests(&program1);
1769 assert_eq!(&slot_versions, &[old_program_new_env]);
1770 assert!(matches!(
1771 &slot_versions.first().unwrap().program,
1772 ProgramCacheEntryType::Loaded(_)
1773 ));
1774 }
1775
1776 #[derive(Default)]
1777 struct TestForkGraphSpecific {
1778 forks: Vec<Vec<Slot>>,
1779 }
1780
1781 impl TestForkGraphSpecific {
1782 fn insert_fork(&mut self, fork: &[Slot]) {
1783 let mut fork = fork.to_vec();
1784 fork.sort();
1785 self.forks.push(fork)
1786 }
1787 }
1788
1789 impl ForkGraph for TestForkGraphSpecific {
1790 fn relationship(&self, a: Slot, b: Slot) -> BlockRelation {
1791 match self.forks.iter().try_for_each(|fork| {
1792 let relation = fork
1793 .iter()
1794 .position(|x| *x == a)
1795 .and_then(|a_pos| {
1796 fork.iter().position(|x| *x == b).and_then(|b_pos| {
1797 (a_pos == b_pos)
1798 .then_some(BlockRelation::Equal)
1799 .or_else(|| (a_pos < b_pos).then_some(BlockRelation::Ancestor))
1800 .or(Some(BlockRelation::Descendant))
1801 })
1802 })
1803 .unwrap_or(BlockRelation::Unrelated);
1804
1805 if relation != BlockRelation::Unrelated {
1806 return ControlFlow::Break(relation);
1807 }
1808
1809 ControlFlow::Continue(())
1810 }) {
1811 ControlFlow::Break(relation) => relation,
1812 _ => BlockRelation::Unrelated,
1813 }
1814 }
1815 }
1816
1817 fn get_entries_to_load<'a>(
1818 cache: &ProgramCache<TestForkGraphSpecific>,
1819 loading_slot: Slot,
1820 keys: &'a [Pubkey],
1821 ) -> Vec<ProgramToLoad<'a>> {
1822 let fork_graph = cache.fork_graph.as_ref().unwrap().upgrade().unwrap();
1823 let locked_fork_graph = fork_graph.read().unwrap();
1824 let entries = cache.get_flattened_entries_for_tests();
1825 keys.iter()
1826 .filter_map(|key| {
1827 entries
1828 .iter()
1829 .rev()
1830 .find(|(program_id, entry)| {
1831 program_id == key
1832 && matches!(
1833 locked_fork_graph.relationship(entry.deployment_slot, loading_slot),
1834 BlockRelation::Equal | BlockRelation::Ancestor,
1835 )
1836 })
1837 .map(|(_program_id, entry)| ProgramToLoad {
1838 program_id: key,
1839 loader: entry.account_owner,
1840 match_criteria: ProgramCacheMatchCriteria::NoCriteria,
1841 last_modification_slot: entry.deployment_slot,
1842 })
1843 })
1844 .collect()
1845 }
1846
1847 fn match_slot(
1848 extracted: &ProgramCacheForTxBatch,
1849 program: &Pubkey,
1850 deployment_slot: Slot,
1851 working_slot: Slot,
1852 ) -> bool {
1853 assert_eq!(extracted.slot, working_slot);
1854 extracted
1855 .entries
1856 .get(program)
1857 .map(|entry| entry.deployment_slot == deployment_slot)
1858 .unwrap_or(false)
1859 }
1860
1861 fn match_missing(
1862 missing: &[ProgramToLoad],
1863 program_id: &Pubkey,
1864 expected_result: bool,
1865 ) -> bool {
1866 missing.iter().any(|entry| entry.program_id == program_id) == expected_result
1867 }
1868
1869 #[test]
1870 fn test_fork_extract_and_prune() {
1871 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
1872 let env = get_mock_program_runtime_environment();
1873
1874 let mut fork_graph = TestForkGraphSpecific::default();
1890 fork_graph.insert_fork(&[0, 10, 20, 22]);
1891 fork_graph.insert_fork(&[0, 5, 11, 15, 16, 18, 19, 21, 23]);
1892 fork_graph.insert_fork(&[0, 5, 11, 25, 27]);
1893
1894 let fork_graph = Arc::new(RwLock::new(fork_graph));
1895 cache.set_fork_graph(Arc::downgrade(&fork_graph));
1896
1897 let program1 = Pubkey::new_unique();
1898 cache.assign_program(&env, program1, 0, new_test_entry(0));
1899 cache.assign_program(&env, program1, 10, new_test_entry(10));
1900 cache.assign_program(&env, program1, 20, new_test_entry(20));
1901
1902 let program2 = Pubkey::new_unique();
1903 cache.assign_program(&env, program2, 5, new_test_entry(5));
1904 cache.assign_program(&env, program2, 11, new_test_entry(11));
1905
1906 let program3 = Pubkey::new_unique();
1907 cache.assign_program(&env, program3, 25, new_test_entry(25));
1908
1909 let program4 = Pubkey::new_unique();
1910 cache.assign_program(&env, program4, 0, new_test_entry(0));
1911 cache.assign_program(&env, program4, 5, new_test_entry(5));
1912 cache.assign_program(&env, program4, 15, new_test_entry(15));
1914
1915 let keys = &[program1, program2, program3, program4];
1932 let mut missing = get_entries_to_load(&cache, 22, keys);
1933 assert!(match_missing(&missing, &program2, false));
1934 assert!(match_missing(&missing, &program3, false));
1935 let mut extracted = ProgramCacheForTxBatch::new(22);
1936 cache.extract(&mut missing, &mut extracted, &env, true, true);
1937 assert!(match_slot(&extracted, &program1, 20, 22));
1938 assert!(match_slot(&extracted, &program4, 0, 22));
1939
1940 let mut missing = get_entries_to_load(&cache, 15, keys);
1942 assert!(match_missing(&missing, &program3, false));
1943 let mut extracted = ProgramCacheForTxBatch::new(15);
1944 cache.extract(&mut missing, &mut extracted, &env, true, true);
1945 assert!(match_slot(&extracted, &program1, 0, 15));
1946 assert!(match_slot(&extracted, &program2, 11, 15));
1947 let tombstone = extracted
1950 .find(&program4)
1951 .expect("Failed to find the tombstone");
1952 assert_matches!(tombstone.program, ProgramCacheEntryType::DelayVisibility);
1953 assert_eq!(tombstone.deployment_slot, 15);
1954
1955 let mut missing = get_entries_to_load(&cache, 18, keys);
1957 assert!(match_missing(&missing, &program3, false));
1958 let mut extracted = ProgramCacheForTxBatch::new(18);
1959 cache.extract(&mut missing, &mut extracted, &env, true, true);
1960 assert!(match_slot(&extracted, &program1, 0, 18));
1961 assert!(match_slot(&extracted, &program2, 11, 18));
1962 assert!(match_slot(&extracted, &program4, 15, 18));
1964
1965 let mut missing = get_entries_to_load(&cache, 23, keys);
1967 assert!(match_missing(&missing, &program3, false));
1968 let mut extracted = ProgramCacheForTxBatch::new(23);
1969 cache.extract(&mut missing, &mut extracted, &env, true, true);
1970 assert!(match_slot(&extracted, &program1, 0, 23));
1971 assert!(match_slot(&extracted, &program2, 11, 23));
1972 assert!(match_slot(&extracted, &program4, 15, 23));
1974
1975 let mut missing = get_entries_to_load(&cache, 11, keys);
1977 assert!(match_missing(&missing, &program3, false));
1978 let mut extracted = ProgramCacheForTxBatch::new(11);
1979 cache.extract(&mut missing, &mut extracted, &env, true, true);
1980 assert!(match_slot(&extracted, &program1, 0, 11));
1981 let tombstone = extracted
1983 .find(&program2)
1984 .expect("Failed to find the tombstone");
1985 assert_matches!(tombstone.program, ProgramCacheEntryType::DelayVisibility);
1986 assert_eq!(tombstone.deployment_slot, 11);
1987 assert!(match_slot(&extracted, &program4, 5, 11));
1988
1989 cache.prune(5, None, &fork_graph.read().unwrap());
1990
1991 let mut missing = get_entries_to_load(&cache, 21, keys);
2008 assert!(match_missing(&missing, &program3, false));
2009 let mut extracted = ProgramCacheForTxBatch::new(21);
2010 cache.extract(&mut missing, &mut extracted, &env, true, true);
2011 assert!(match_slot(&extracted, &program1, 0, 21));
2013 assert!(match_slot(&extracted, &program2, 11, 21));
2014 assert!(match_slot(&extracted, &program4, 15, 21));
2015
2016 let mut missing = get_entries_to_load(&cache, 27, keys);
2018 let mut extracted = ProgramCacheForTxBatch::new(27);
2019 cache.extract(&mut missing, &mut extracted, &env, true, true);
2020 assert!(match_slot(&extracted, &program1, 0, 27));
2021 assert!(match_slot(&extracted, &program2, 11, 27));
2022 assert!(match_slot(&extracted, &program3, 25, 27));
2023 assert!(match_slot(&extracted, &program4, 5, 27));
2024
2025 cache.prune(15, None, &fork_graph.read().unwrap());
2026
2027 let mut missing = get_entries_to_load(&cache, 23, keys);
2044 assert!(match_missing(&missing, &program3, false));
2045 let mut extracted = ProgramCacheForTxBatch::new(23);
2046 cache.extract(&mut missing, &mut extracted, &env, true, true);
2047 assert!(match_slot(&extracted, &program1, 0, 23));
2048 assert!(match_slot(&extracted, &program2, 11, 23));
2049 assert!(match_slot(&extracted, &program4, 15, 23));
2050 }
2051
2052 #[test]
2053 fn test_extract_using_deployment_slot() {
2054 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
2055 let env = get_mock_program_runtime_environment();
2056
2057 let mut fork_graph = TestForkGraphSpecific::default();
2073 fork_graph.insert_fork(&[0, 10, 20, 22]);
2074 fork_graph.insert_fork(&[0, 5, 11, 12, 15, 16, 18, 19, 21, 23]);
2075 fork_graph.insert_fork(&[0, 5, 11, 25, 27]);
2076
2077 let fork_graph = Arc::new(RwLock::new(fork_graph));
2078 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2079
2080 let program1 = Pubkey::new_unique();
2081 cache.assign_program(&env, program1, 0, new_test_entry(0));
2082 cache.assign_program(&env, program1, 20, new_test_entry(20));
2083
2084 let program2 = Pubkey::new_unique();
2085 cache.assign_program(&env, program2, 5, new_test_entry(5));
2086 cache.assign_program(&env, program2, 11, new_test_entry(11));
2087
2088 let program3 = Pubkey::new_unique();
2089 cache.assign_program(&env, program3, 25, new_test_entry(25));
2090
2091 let keys = &[program1, program2, program3];
2093 let mut missing = get_entries_to_load(&cache, 12, keys);
2094 assert!(match_missing(&missing, &program3, false));
2095 let mut extracted = ProgramCacheForTxBatch::new(12);
2096 cache.extract(&mut missing, &mut extracted, &env, true, true);
2097 assert!(match_slot(&extracted, &program1, 0, 12));
2098 assert!(match_slot(&extracted, &program2, 11, 12));
2099
2100 let mut missing = get_entries_to_load(&cache, 12, keys);
2102 missing.get_mut(0).unwrap().match_criteria =
2103 ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(5);
2104 missing.get_mut(1).unwrap().match_criteria =
2105 ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(5);
2106 assert!(match_missing(&missing, &program3, false));
2107 let mut extracted = ProgramCacheForTxBatch::new(12);
2108 cache.extract(&mut missing, &mut extracted, &env, true, true);
2109 assert!(match_missing(&missing, &program1, true));
2110 assert!(match_slot(&extracted, &program2, 11, 12));
2111 }
2112
2113 #[test]
2114 fn test_extract_unloaded() {
2115 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
2116 let env = get_mock_program_runtime_environment();
2117
2118 let mut fork_graph = TestForkGraphSpecific::default();
2134 fork_graph.insert_fork(&[0, 10, 20, 22]);
2135 fork_graph.insert_fork(&[0, 5, 11, 15, 16, 19, 21, 23]);
2136 fork_graph.insert_fork(&[0, 5, 11, 25, 27]);
2137
2138 let fork_graph = Arc::new(RwLock::new(fork_graph));
2139 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2140
2141 let program1 = Pubkey::new_unique();
2142 cache.assign_program(&env, program1, 0, new_test_entry(0));
2143 cache.assign_program(&env, program1, 20, new_test_entry(20));
2144
2145 let program2 = Pubkey::new_unique();
2146 cache.assign_program(&env, program2, 5, new_test_entry(5));
2147 cache.assign_program(&env, program2, 11, new_test_entry(11));
2148
2149 let program3 = Pubkey::new_unique();
2150 let _ = insert_unloaded_entry(&mut cache, program3, 25);
2152
2153 cache.assign_program(
2157 &env,
2158 program3,
2159 20,
2160 Arc::new(
2161 new_test_entry(20)
2162 .to_unloaded()
2163 .expect("Failed to create unloaded program"),
2164 ),
2165 );
2166
2167 let keys = &[program1, program2, program3];
2169 let mut missing = get_entries_to_load(&cache, 19, keys);
2170 assert!(match_missing(&missing, &program3, false));
2171 let mut extracted = ProgramCacheForTxBatch::new(19);
2172 cache.extract(&mut missing, &mut extracted, &env, true, true);
2173 assert!(match_slot(&extracted, &program1, 0, 19));
2174 assert!(match_slot(&extracted, &program2, 11, 19));
2175
2176 let mut missing = get_entries_to_load(&cache, 27, keys);
2178 let mut extracted = ProgramCacheForTxBatch::new(27);
2179 cache.extract(&mut missing, &mut extracted, &env, true, true);
2180 assert!(match_slot(&extracted, &program1, 0, 27));
2181 assert!(match_slot(&extracted, &program2, 11, 27));
2182 assert!(match_missing(&missing, &program3, true));
2183
2184 let mut missing = get_entries_to_load(&cache, 22, keys);
2186 assert!(match_missing(&missing, &program2, false));
2187 let mut extracted = ProgramCacheForTxBatch::new(22);
2188 cache.extract(&mut missing, &mut extracted, &env, true, true);
2189 assert!(match_slot(&extracted, &program1, 20, 22));
2190 assert!(match_missing(&missing, &program3, true));
2191 }
2192
2193 #[test]
2194 fn test_extract_different_environment() {
2195 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
2196 let env = get_mock_program_runtime_environment();
2197 let other_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
2198
2199 let mut fork_graph = TestForkGraphSpecific::default();
2209 fork_graph.insert_fork(&[0, 10, 20, 22]);
2210
2211 let fork_graph = Arc::new(RwLock::new(fork_graph));
2212 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2213
2214 let program1 = Pubkey::new_unique();
2215 cache.assign_program(
2216 &env,
2217 program1,
2218 10,
2219 Arc::new(ProgramCacheEntry::new_closed_tombstone(
2220 10,
2221 ProgramCacheEntryOwner::LoaderV3,
2222 )),
2223 );
2224 cache.assign_program(&env, program1, 20, new_test_entry(20));
2225
2226 let keys = &[program1];
2228 let mut missing = get_entries_to_load(&cache, 22, keys);
2229 let mut extracted = ProgramCacheForTxBatch::new(22);
2230 cache.extract(&mut missing, &mut extracted, &env, true, true);
2231 assert!(match_slot(&extracted, &program1, 20, 22));
2232
2233 let mut missing = get_entries_to_load(&cache, 22, keys);
2235 let mut extracted = ProgramCacheForTxBatch::new(22);
2236 cache.extract(&mut missing, &mut extracted, &other_env, true, true);
2237 assert!(match_missing(&missing, &program1, true));
2238 }
2239
2240 #[test]
2241 fn test_extract_nonexistent() {
2242 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
2243 let env = get_mock_program_runtime_environment();
2244 let fork_graph = TestForkGraphSpecific::default();
2245 let fork_graph = Arc::new(RwLock::new(fork_graph));
2246 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2247
2248 let program1 = Pubkey::new_unique();
2249 let mut missing = vec![ProgramToLoad {
2250 program_id: &program1,
2251 loader: ProgramCacheEntryOwner::LoaderV3,
2252 match_criteria: ProgramCacheMatchCriteria::NoCriteria,
2253 last_modification_slot: 0,
2254 }];
2255 let mut extracted = ProgramCacheForTxBatch::new(0);
2256 cache.extract(&mut missing, &mut extracted, &env, true, true);
2257 assert!(match_missing(&missing, &program1, true));
2258 }
2259
2260 #[test]
2261 fn test_unloaded() {
2262 let mut cache = ProgramCache::<TestForkGraph>::new(0);
2263 let env = get_mock_program_runtime_environment();
2264 for program_cache_entry_type in [
2265 ProgramCacheEntryType::Closed,
2266 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
2267 ] {
2268 let entry = Arc::new(ProgramCacheEntry {
2269 program: program_cache_entry_type,
2270 account_owner: ProgramCacheEntryOwner::LoaderV2,
2271 deployment_slot: 0,
2272 stats: Arc::default(),
2273 latest_access_slot: AtomicU64::default(),
2274 });
2275 assert!(entry.to_unloaded().is_none());
2276
2277 let program_id = Pubkey::new_unique();
2279 cache.assign_program(&env, program_id, entry.deployment_slot, entry.clone());
2280 cache.unload_program_entry(program_id, entry.deployment_slot, &entry);
2281 assert_eq!(cache.get_slot_versions_for_tests(&program_id).len(), 1);
2282 assert!(cache.stats.evictions.is_empty());
2283 }
2284
2285 let stats = ProgramStatistics {
2286 uses: 3.into(),
2287 ..Default::default()
2288 };
2289 let entry = new_test_entry_with_usage(1, stats);
2290 let unloaded_entry = entry.to_unloaded().unwrap();
2291 assert_eq!(unloaded_entry.deployment_slot, 1);
2292 assert_eq!(unloaded_entry.effective_slot(), 2);
2293 assert_eq!(unloaded_entry.latest_access_slot.load(Ordering::Relaxed), 1);
2294 assert_eq!(unloaded_entry.stats.uses.load(Ordering::Relaxed), 3);
2295
2296 let program_id = Pubkey::new_unique();
2298 cache.assign_program(&env, program_id, entry.deployment_slot, entry.clone());
2299 cache.unload_program_entry(program_id, entry.deployment_slot, &entry);
2300 assert!(cache.stats.evictions.contains_key(&program_id));
2301 }
2302
2303 #[test]
2304 fn test_fork_prune_find_first_ancestor() {
2305 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
2306 let env = get_mock_program_runtime_environment();
2307
2308 let mut fork_graph = TestForkGraphSpecific::default();
2319 fork_graph.insert_fork(&[0, 10, 20]);
2320 fork_graph.insert_fork(&[0, 5]);
2321 let fork_graph = Arc::new(RwLock::new(fork_graph));
2322 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2323
2324 let program1 = Pubkey::new_unique();
2325 cache.assign_program(&env, program1, 0, new_test_entry(0));
2326 cache.assign_program(&env, program1, 5, new_test_entry(5));
2327
2328 cache.prune(10, None, &fork_graph.read().unwrap());
2329
2330 let keys = &[program1];
2331 let mut missing = get_entries_to_load(&cache, 20, keys);
2332 let mut extracted = ProgramCacheForTxBatch::new(20);
2333 cache.extract(&mut missing, &mut extracted, &env, true, true);
2334
2335 assert_eq!(
2337 extracted
2338 .find(&program1)
2339 .expect("Did not find the program")
2340 .deployment_slot,
2341 0
2342 );
2343 }
2344
2345 #[test]
2346 fn test_prune_by_deployment_slot() {
2347 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
2348 let env = get_mock_program_runtime_environment();
2349
2350 let mut fork_graph = TestForkGraphSpecific::default();
2361 fork_graph.insert_fork(&[0, 10, 20]);
2362 fork_graph.insert_fork(&[0, 5, 6]);
2363 let fork_graph = Arc::new(RwLock::new(fork_graph));
2364 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2365
2366 let program1 = Pubkey::new_unique();
2367 cache.assign_program(&env, program1, 0, new_test_entry(0));
2368 cache.assign_program(&env, program1, 5, new_test_entry(5));
2369
2370 let program2 = Pubkey::new_unique();
2371 cache.assign_program(&env, program2, 10, new_test_entry(10));
2372
2373 let keys = &[program1, program2];
2374 let mut missing = get_entries_to_load(&cache, 20, keys);
2375 let mut extracted = ProgramCacheForTxBatch::new(20);
2376 cache.extract(&mut missing, &mut extracted, &env, true, true);
2377 assert!(match_slot(&extracted, &program1, 0, 20));
2378 assert!(match_slot(&extracted, &program2, 10, 20));
2379
2380 let mut missing = get_entries_to_load(&cache, 6, keys);
2381 assert!(match_missing(&missing, &program2, false));
2382 let mut extracted = ProgramCacheForTxBatch::new(6);
2383 cache.extract(&mut missing, &mut extracted, &env, true, true);
2384 assert!(match_slot(&extracted, &program1, 5, 6));
2385
2386 cache.prune_by_deployment_slot(5);
2389
2390 let mut missing = get_entries_to_load(&cache, 20, keys);
2391 let mut extracted = ProgramCacheForTxBatch::new(20);
2392 cache.extract(&mut missing, &mut extracted, &env, true, true);
2393 assert!(match_slot(&extracted, &program1, 0, 20));
2394 assert!(match_slot(&extracted, &program2, 10, 20));
2395
2396 let mut missing = get_entries_to_load(&cache, 6, keys);
2397 assert!(match_missing(&missing, &program2, false));
2398 let mut extracted = ProgramCacheForTxBatch::new(6);
2399 cache.extract(&mut missing, &mut extracted, &env, true, true);
2400 assert!(match_slot(&extracted, &program1, 0, 6));
2401
2402 cache.prune_by_deployment_slot(10);
2405
2406 let mut missing = get_entries_to_load(&cache, 20, keys);
2407 assert!(match_missing(&missing, &program2, false));
2408 let mut extracted = ProgramCacheForTxBatch::new(20);
2409 cache.extract(&mut missing, &mut extracted, &env, true, true);
2410 assert!(match_slot(&extracted, &program1, 0, 20));
2411 }
2412
2413 #[test]
2414 fn test_usable_entries_for_slot() {
2415 ProgramCache::<TestForkGraph>::new(0);
2416 let tombstone = Arc::new(ProgramCacheEntry::new_closed_tombstone(
2417 0,
2418 ProgramCacheEntryOwner::LoaderV2,
2419 ));
2420
2421 assert!(ProgramCache::<TestForkGraph>::matches_criteria(
2422 &tombstone,
2423 &ProgramCacheMatchCriteria::NoCriteria
2424 ));
2425
2426 assert!(ProgramCache::<TestForkGraph>::matches_criteria(
2427 &tombstone,
2428 &ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(0)
2429 ));
2430
2431 assert!(!ProgramCache::<TestForkGraph>::matches_criteria(
2432 &tombstone,
2433 &ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(1)
2434 ));
2435
2436 let program = new_test_entry(0);
2437
2438 assert!(ProgramCache::<TestForkGraph>::matches_criteria(
2439 &program,
2440 &ProgramCacheMatchCriteria::NoCriteria
2441 ));
2442
2443 assert!(ProgramCache::<TestForkGraph>::matches_criteria(
2444 &program,
2445 &ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(0)
2446 ));
2447
2448 assert!(!ProgramCache::<TestForkGraph>::matches_criteria(
2449 &program,
2450 &ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(1)
2451 ));
2452
2453 let program = Arc::new(new_test_entry_with_usage(0, ProgramStatistics::default()));
2454
2455 assert!(ProgramCache::<TestForkGraph>::matches_criteria(
2456 &program,
2457 &ProgramCacheMatchCriteria::NoCriteria
2458 ));
2459
2460 assert!(ProgramCache::<TestForkGraph>::matches_criteria(
2461 &program,
2462 &ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(0)
2463 ));
2464
2465 assert!(!ProgramCache::<TestForkGraph>::matches_criteria(
2466 &program,
2467 &ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(1)
2468 ));
2469 }
2470}