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 deployed_on_or_after_slot: Slot,
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
369impl<FG: ForkGraph> ProgramCache<FG> {
370 pub fn new(root_slot: Slot) -> Self {
371 Self {
372 index: IndexImplementation::V1 {
373 entries: HashMap::new(),
374 loading_entries: Mutex::new(HashMap::new()),
375 },
376 latest_root_slot: root_slot,
377 stats: ProgramCacheStats::default(),
378 fork_graph: None,
379 loading_task_waiter: Arc::new(LoadingTaskWaiter::default()),
380 }
381 }
382
383 pub fn set_fork_graph(&mut self, fork_graph: Weak<RwLock<FG>>) {
384 self.fork_graph = Some(fork_graph);
385 }
386
387 pub fn assign_program(
390 &mut self,
391 program_runtime_environment: &ProgramRuntimeEnvironment,
392 key: Pubkey,
393 _last_modification_slot: Slot,
394 entry: Arc<ProgramCacheEntry>,
395 ) -> bool {
396 debug_assert!(!matches!(
397 &entry.program,
398 ProgramCacheEntryType::DelayVisibility
399 ));
400 fn is_current_env(
404 program_runtime_environment: &ProgramRuntimeEnvironment,
405 env_opt: Option<&ProgramRuntimeEnvironment>,
406 ) -> bool {
407 env_opt
408 .map(|env| env == program_runtime_environment)
409 .unwrap_or(true)
410 }
411 match &mut self.index {
412 IndexImplementation::V1 { entries, .. } => {
413 let slot_versions = &mut entries.entry(key).or_default();
414 let insertion_point = slot_versions.binary_search_by(|at| {
415 at.deployment_slot
416 .cmp(&entry.deployment_slot)
417 .then(at.account_owner.cmp(&entry.account_owner))
418 .then(
419 is_current_env(
423 program_runtime_environment,
424 at.program.get_environment(),
425 )
426 .cmp(&is_current_env(
427 program_runtime_environment,
428 entry.program.get_environment(),
429 )),
430 )
431 });
432 match insertion_point {
433 Ok(index) => {
434 let existing = slot_versions.get_mut(index).unwrap();
435 match (&existing.program, &entry.program) {
436 (
437 ProgramCacheEntryType::Builtin(_),
438 ProgramCacheEntryType::Builtin(_),
439 )
440 | (ProgramCacheEntryType::Closed, ProgramCacheEntryType::Unloaded(_))
441 | (
442 ProgramCacheEntryType::Unloaded(_),
443 ProgramCacheEntryType::Loaded(_),
444 )
445 | (
446 ProgramCacheEntryType::Unloaded(_),
447 ProgramCacheEntryType::FailedVerification(_),
448 ) => {}
449 _ => {
450 error!(
452 "ProgramCache::assign_program() failed key={key:?} \
453 existing={slot_versions:?} entry={entry:?}"
454 );
455 debug_assert!(false, "Unexpected replacement of an entry");
456 self.stats.replacements.fetch_add(1, Ordering::Relaxed);
457 return true;
458 }
459 }
460 entry.stats.merge_from(&existing.stats);
461 *existing = Arc::clone(&entry);
462 self.stats.reloads.fetch_add(1, Ordering::Relaxed);
463 }
464 Err(index) => {
465 self.stats.insertions.fetch_add(1, Ordering::Relaxed);
466 slot_versions.insert(index, Arc::clone(&entry));
467 }
468 }
469 slot_versions.retain(|existing| {
473 existing.deployment_slot != entry.deployment_slot
474 || existing
475 .program
476 .get_environment()
477 .zip(entry.program.get_environment())
478 .map(|(a, b)| a != b)
479 .unwrap_or(false)
480 || Arc::ptr_eq(existing, &entry)
481 });
482 }
483 }
484 false
485 }
486
487 pub fn prune_by_deployment_slot(&mut self, slot: Slot) {
488 match &mut self.index {
489 IndexImplementation::V1 { entries, .. } => {
490 for second_level in entries.values_mut() {
491 second_level.retain(|entry| entry.deployment_slot != slot);
492 }
493 self.remove_programs_with_no_entries();
494 }
495 }
496 }
497
498 pub fn prune(
500 &mut self,
501 new_root_slot: Slot,
502 new_environment: Option<ProgramRuntimeEnvironment>,
503 fork_graph: &FG,
504 ) {
505 match &mut self.index {
506 IndexImplementation::V1 { entries, .. } => {
507 for second_level in entries.values_mut() {
508 let mut first_ancestor_found = false;
510 let mut first_ancestor_env = None;
511 *second_level = second_level
512 .iter()
513 .rev()
514 .filter(|entry| {
515 let relation =
516 fork_graph.relationship(entry.deployment_slot, new_root_slot);
517 if entry.deployment_slot >= new_root_slot {
518 matches!(relation, BlockRelation::Equal | BlockRelation::Descendant)
519 } else if matches!(relation, BlockRelation::Ancestor)
520 || entry.deployment_slot <= self.latest_root_slot
521 {
522 if !first_ancestor_found {
523 first_ancestor_found = true;
524 first_ancestor_env = entry.program.get_environment();
525 return true;
526 }
527 if let Some(entry_env) = entry.program.get_environment()
535 && let Some(env) = first_ancestor_env
536 && entry_env != env
537 {
538 return true;
539 }
540 self.stats.prunes_orphan.fetch_add(1, Ordering::Relaxed);
541 false
542 } else {
543 self.stats.prunes_orphan.fetch_add(1, Ordering::Relaxed);
544 false
545 }
546 })
547 .filter(|entry| {
548 if let Some(new_environment) = new_environment.as_ref()
550 && !Self::matches_environment(entry, new_environment)
551 {
552 self.stats
553 .prunes_environment
554 .fetch_add(1, Ordering::Relaxed);
555 return false;
556 }
557 true
558 })
559 .cloned()
560 .collect();
561 second_level.reverse();
562 }
563 }
564 }
565 self.remove_programs_with_no_entries();
566 debug_assert!(self.latest_root_slot <= new_root_slot);
567 self.latest_root_slot = new_root_slot;
568 }
569
570 fn matches_environment(
571 entry: &Arc<ProgramCacheEntry>,
572 program_runtime_environment: &ProgramRuntimeEnvironment,
573 ) -> bool {
574 let Some(environment) = entry.program.get_environment() else {
575 return true;
576 };
577 environment == program_runtime_environment
578 }
579
580 pub fn extract(
583 &self,
584 search_for: &mut Vec<ProgramToLoad>,
585 loaded_programs_for_tx_batch: &mut ProgramCacheForTxBatch,
586 program_runtime_environment_for_execution: &ProgramRuntimeEnvironment,
587 increment_usage_counter: bool,
588 count_hits_and_misses: bool,
589 ) -> Option<Pubkey> {
590 debug_assert!(self.fork_graph.is_some());
591 let fork_graph = self.fork_graph.as_ref().unwrap().upgrade().unwrap();
592 let locked_fork_graph = fork_graph.read().unwrap();
593 let mut cooperative_loading_task = None;
594 match &self.index {
595 IndexImplementation::V1 {
596 entries,
597 loading_entries,
598 } => {
599 search_for.retain(|program_to_load| {
600 if let Some(second_level) = entries.get(program_to_load.program_id) {
601 let mut filter_by_deployment_slot = None;
602 for entry in second_level.iter().rev() {
603 let required_deployment_slot =
604 filter_by_deployment_slot.unwrap_or(entry.deployment_slot);
605 if required_deployment_slot != entry.deployment_slot
606 || program_to_load.loader != entry.account_owner
607 {
608 continue;
609 }
610 let entry_in_same_branch = entry.deployment_slot
611 <= self.latest_root_slot
612 || matches!(
613 locked_fork_graph.relationship(
614 entry.deployment_slot,
615 loaded_programs_for_tx_batch.slot
616 ),
617 BlockRelation::Equal | BlockRelation::Ancestor
618 );
619 if entry_in_same_branch {
620 let entry_is_effective =
621 loaded_programs_for_tx_batch.slot >= entry.effective_slot();
622 let entry_to_return = if entry_is_effective {
623 if !Self::matches_environment(
624 entry,
625 program_runtime_environment_for_execution,
626 ) {
627 filter_by_deployment_slot = filter_by_deployment_slot
636 .or(Some(entry.deployment_slot));
637 continue;
638 }
639 if entry.deployment_slot
640 < program_to_load.deployed_on_or_after_slot
641 {
642 break;
643 }
644 if let ProgramCacheEntryType::Unloaded(_environment) =
645 &entry.program
646 {
647 break;
648 }
649 entry.clone()
650 } else if entry.is_implicit_delay_visibility_tombstone(
651 loaded_programs_for_tx_batch.slot,
652 ) {
653 Arc::new(ProgramCacheEntry::new_delay_visibility_tombstone(
657 entry.deployment_slot,
658 entry.account_owner,
659 Arc::clone(&entry.stats),
660 ))
661 } else {
662 continue;
663 };
664 entry_to_return
665 .update_access_slot(loaded_programs_for_tx_batch.slot);
666 if increment_usage_counter {
667 entry_to_return.stats.uses.fetch_add(1, Ordering::Relaxed);
668 }
669 loaded_programs_for_tx_batch
670 .entries
671 .insert(*program_to_load.program_id, entry_to_return);
672 return false;
673 }
674 }
675 }
676 if cooperative_loading_task.is_none() {
677 let mut loading_entries = loading_entries.lock().unwrap();
678 let entry = loading_entries.entry(*program_to_load.program_id);
679 if let Entry::Vacant(entry) = entry {
680 entry.insert((
681 loaded_programs_for_tx_batch.slot,
682 thread::current().id(),
683 ));
684 cooperative_loading_task = Some(*program_to_load.program_id);
685 }
686 }
687 true
688 });
689 }
690 }
691 drop(locked_fork_graph);
692 if count_hits_and_misses {
693 self.stats
694 .misses
695 .fetch_add(search_for.len() as u64, Ordering::Relaxed);
696 self.stats.hits.fetch_add(
697 loaded_programs_for_tx_batch.entries.len() as u64,
698 Ordering::Relaxed,
699 );
700 }
701 cooperative_loading_task
702 }
703
704 pub fn finish_cooperative_loading_task(
706 &mut self,
707 program_runtime_environment: &ProgramRuntimeEnvironment,
708 current_slot: Slot,
709 key: Pubkey,
710 last_modification_slot: Slot,
711 loaded_program: Arc<ProgramCacheEntry>,
712 ) -> bool {
713 match &mut self.index {
714 IndexImplementation::V1 {
715 loading_entries, ..
716 } => {
717 let loading_thread = loading_entries.get_mut().unwrap().remove(&key);
718 debug_assert_eq!(loading_thread, Some((current_slot, thread::current().id())));
719 if loaded_program.deployment_slot > self.latest_root_slot
721 && !matches!(
722 self.fork_graph
723 .as_ref()
724 .unwrap()
725 .upgrade()
726 .unwrap()
727 .read()
728 .unwrap()
729 .relationship(loaded_program.deployment_slot, current_slot),
730 BlockRelation::Equal | BlockRelation::Ancestor
731 )
732 {
733 self.stats.lost_insertions.fetch_add(1, Ordering::Relaxed);
734 }
735 let was_occupied = self.assign_program(
736 program_runtime_environment,
737 key,
738 last_modification_slot,
739 loaded_program,
740 );
741 self.loading_task_waiter.notify();
742 was_occupied
743 }
744 }
745 }
746
747 pub fn merge(
748 &mut self,
749 program_runtime_environment: &ProgramRuntimeEnvironment,
750 current_slot: Slot,
751 modified_entries: &HashMap<Pubkey, Arc<ProgramCacheEntry>>,
752 ) {
753 modified_entries.iter().for_each(|(key, entry)| {
754 self.assign_program(
755 program_runtime_environment,
756 *key,
757 current_slot,
758 entry.clone(),
759 );
760 })
761 }
762
763 pub fn get_flattened_entries(&self) -> Vec<(Pubkey, Slot, Arc<ProgramCacheEntry>)> {
765 match &self.index {
766 IndexImplementation::V1 { entries, .. } => entries
767 .iter()
768 .flat_map(|(id, second_level)| {
769 second_level
770 .iter()
771 .filter_map(move |program| match program.program {
772 ProgramCacheEntryType::Loaded(_) => Some((*id, 0, program.clone())),
773 _ => None,
774 })
775 })
776 .collect(),
777 }
778 }
779
780 #[cfg(feature = "dev-context-only-utils")]
782 pub fn get_flattened_entries_for_tests(&self) -> Vec<(Pubkey, Arc<ProgramCacheEntry>)> {
783 match &self.index {
784 IndexImplementation::V1 { entries, .. } => entries
785 .iter()
786 .flat_map(|(id, second_level)| {
787 second_level.iter().map(|program| (*id, program.clone()))
788 })
789 .collect(),
790 }
791 }
792
793 pub fn get_slot_versions_for_tests(&self, key: &Pubkey) -> &[Arc<ProgramCacheEntry>] {
795 match &self.index {
796 IndexImplementation::V1 { entries, .. } => entries
797 .get(key)
798 .map(|second_level| second_level.as_ref())
799 .unwrap_or(&[]),
800 }
801 }
802
803 pub fn sort_and_unload(&mut self, shrink_to_percent: Percent) {
805 let mut sorted_candidates = self.get_flattened_entries();
806 sorted_candidates.sort_by_cached_key(|(_id, _last_modification_slot, program)| {
807 program.stats.uses.load(Ordering::Relaxed)
808 });
809 let num_to_unload = sorted_candidates
810 .len()
811 .saturating_sub(percent_of_max_entries(shrink_to_percent));
812 for (program, last_modification_slot, entry) in sorted_candidates.iter().take(num_to_unload)
813 {
814 self.unload_program_entry(*program, *last_modification_slot, entry);
815 }
816 }
817
818 pub fn evict_using_random_selection(&mut self, shrink_to_percent: Percent, now: Slot) {
824 let mut candidates = self.get_flattened_entries();
825 let mut rng = rng();
826 self.stats
827 .water_level
828 .store(candidates.len() as u64, Ordering::Relaxed);
829 let num_to_unload = candidates
830 .len()
831 .saturating_sub(percent_of_max_entries(shrink_to_percent));
832 let mut sample_entry = |candidates: &Vec<(Pubkey, u64, Arc<ProgramCacheEntry>)>| {
833 #[cfg(feature = "shuttle-test")]
836 let index = rng.gen_range(0..candidates.len());
837 #[cfg(not(feature = "shuttle-test"))]
838 let index = rng.random_range(0..candidates.len());
839 let usage_counter = candidates
840 .get(index)
841 .expect("Failed to get cached entry")
842 .2
843 .retention_score();
844 (index, usage_counter)
845 };
846
847 const MAX_ADDITIONAL_SAMPLES: usize = 3;
855 let avoid_evicting_above_score = retention_score(now, 500 * EMA_SCALE, 500);
856 for _ in 0..num_to_unload {
857 let (mut index, mut score) = sample_entry(&candidates);
858 for _ in 0..MAX_ADDITIONAL_SAMPLES {
859 let (sample_index, sample_score) = sample_entry(&candidates);
860 if score > sample_score {
861 index = sample_index;
862 score = sample_score;
863 }
864 if score < avoid_evicting_above_score {
865 break;
866 }
867 }
868 let (id, last_modification_slot, entry) = candidates.swap_remove(index);
869 self.unload_program_entry(id, last_modification_slot, &entry);
870 }
871 }
872
873 pub fn remove_programs(&mut self, keys: impl Iterator<Item = Pubkey>) {
875 match &mut self.index {
876 IndexImplementation::V1 { entries, .. } => {
877 for k in keys {
878 entries.remove(&k);
879 }
880 }
881 }
882 }
883
884 fn unload_program_entry(
887 &mut self,
888 id: Pubkey,
889 _last_modification_slot: Slot,
890 remove_entry: &Arc<ProgramCacheEntry>,
891 ) {
892 match &mut self.index {
893 IndexImplementation::V1 { entries, .. } => {
894 let second_level = entries.get_mut(&id).expect("Cache lookup failed");
895 let candidate = second_level
896 .iter_mut()
897 .find(|entry| Arc::ptr_eq(entry, remove_entry))
898 .expect("Program entry not found");
899
900 if let ProgramCacheEntryType::Loaded(_) = candidate.program
902 && let Some(unloaded) = candidate.to_unloaded()
903 {
904 if candidate.stats.uses.load(Ordering::Relaxed) == 1 {
905 self.stats.one_hit_wonders.fetch_add(1, Ordering::Relaxed);
906 }
907 self.stats
908 .evictions
909 .entry(id)
910 .and_modify(|c| *c = c.saturating_add(1))
911 .or_insert(1);
912 *candidate = Arc::new(unloaded);
913 }
914 }
915 }
916 }
917
918 fn remove_programs_with_no_entries(&mut self) {
919 match &mut self.index {
920 IndexImplementation::V1 { entries, .. } => {
921 let num_programs_before_removal = entries.len();
922 entries.retain(|_key, second_level| !second_level.is_empty());
923 if entries.len() < num_programs_before_removal {
924 self.stats.empty_entries.fetch_add(
925 num_programs_before_removal.saturating_sub(entries.len()) as u64,
926 Ordering::Relaxed,
927 );
928 }
929 }
930 }
931 }
932}
933
934#[cfg(feature = "frozen-abi")]
935impl solana_frozen_abi::abi_example::AbiExample for ProgramCacheEntry {
936 fn example() -> Self {
937 Self::default()
939 }
940}
941
942#[cfg(feature = "frozen-abi")]
943impl<FG: ForkGraph> solana_frozen_abi::abi_example::AbiExample for ProgramCache<FG> {
944 fn example() -> Self {
945 Self::new(Slot::default())
947 }
948}
949
950#[cfg(test)]
951pub(crate) mod tests {
952 use {
953 crate::{
954 loaded_programs::{
955 BlockRelation, ForkGraph, Percent, ProgramCache, ProgramCacheForTxBatch,
956 ProgramRuntimeEnvironment, ProgramToLoad, get_mock_program_runtime_environment,
957 },
958 program_cache_entry::{
959 ProgramCacheEntry, ProgramCacheEntryOwner, ProgramCacheEntryType,
960 },
961 program_metrics::ProgramStatistics,
962 },
963 assert_matches::assert_matches,
964 solana_clock::Slot,
965 solana_pubkey::Pubkey,
966 solana_sbpf::{elf::Executable, program::BuiltinProgram},
967 solana_svm_type_overrides::sync::{
968 Arc, RwLock,
969 atomic::{AtomicU64, Ordering},
970 },
971 std::{fs::File, io::Read, ops::ControlFlow},
972 test_case::test_matrix,
973 };
974
975 fn new_test_entry(deployment_slot: Slot) -> Arc<ProgramCacheEntry> {
976 new_test_entry_with_usage(deployment_slot, ProgramStatistics::default())
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 pub(crate) fn new_test_entry_with_usage(
990 deployment_slot: Slot,
991 stats: ProgramStatistics,
992 ) -> Arc<ProgramCacheEntry> {
993 Arc::new(ProgramCacheEntry {
994 program: new_loaded_entry(get_mock_program_runtime_environment()),
995 account_owner: ProgramCacheEntryOwner::LoaderV2,
996 deployment_slot,
997 stats: Arc::new(stats),
998 latest_access_slot: AtomicU64::new(deployment_slot),
999 })
1000 }
1001
1002 fn new_test_builtin_entry(deployment_slot: Slot) -> Arc<ProgramCacheEntry> {
1003 Arc::new(ProgramCacheEntry {
1004 program: ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1005 account_owner: ProgramCacheEntryOwner::NativeLoader,
1006 deployment_slot,
1007 stats: Arc::default(),
1008 latest_access_slot: AtomicU64::default(),
1009 })
1010 }
1011
1012 fn set_failed_verification_tombstone<FG: ForkGraph>(
1013 cache: &mut ProgramCache<FG>,
1014 key: Pubkey,
1015 current_slot: Slot,
1016 env: ProgramRuntimeEnvironment,
1017 ) -> Arc<ProgramCacheEntry> {
1018 let program = Arc::new(ProgramCacheEntry::new_failed_verification_tombstone(
1019 current_slot,
1020 ProgramCacheEntryOwner::LoaderV2,
1021 ProgramRuntimeEnvironment::clone(&env),
1022 ));
1023 cache.assign_program(&env, key, current_slot, program.clone());
1024 program
1025 }
1026
1027 fn insert_unloaded_entry<FG: ForkGraph>(
1028 cache: &mut ProgramCache<FG>,
1029 key: Pubkey,
1030 current_slot: Slot,
1031 ) -> Arc<ProgramCacheEntry> {
1032 let env = get_mock_program_runtime_environment();
1033 let loaded = new_test_entry_with_usage(current_slot, ProgramStatistics::default());
1034 let unloaded = Arc::new(loaded.to_unloaded().expect("Failed to unload the program"));
1035 cache.assign_program(&env, key, current_slot, unloaded.clone());
1036 unloaded
1037 }
1038
1039 fn num_matching_entries<P, FG>(cache: &ProgramCache<FG>, predicate: P) -> usize
1040 where
1041 P: Fn(&ProgramCacheEntryType) -> bool,
1042 FG: ForkGraph,
1043 {
1044 cache
1045 .get_flattened_entries_for_tests()
1046 .iter()
1047 .filter(|(_key, program)| predicate(&program.program))
1048 .count()
1049 }
1050
1051 #[expect(clippy::arithmetic_side_effects)]
1052 fn program_deploy_test_helper(
1053 cache: &mut ProgramCache<TestForkGraph>,
1054 program: Pubkey,
1055 deployment_slots: Vec<Slot>,
1056 usage_counters: Vec<u64>,
1057 programs: &mut Vec<(Pubkey, Slot, u64)>,
1058 ) {
1059 let env = get_mock_program_runtime_environment();
1060 deployment_slots
1062 .iter()
1063 .enumerate()
1064 .for_each(|(i, deployment_slot)| {
1065 let usage_counter = *usage_counters.get(i).unwrap_or(&0);
1066 let stats = ProgramStatistics {
1067 uses: usage_counter.into(),
1068 ..Default::default()
1069 };
1070 cache.assign_program(
1071 &env,
1072 program,
1073 *deployment_slot,
1074 new_test_entry_with_usage(*deployment_slot, stats),
1075 );
1076 programs.push((program, *deployment_slot, usage_counter));
1077 });
1078
1079 let next_slot = deployment_slots.iter().max().map_or(0, |slot| slot + 1);
1080
1081 let env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
1083 for slot in next_slot..next_slot + 10 {
1084 set_failed_verification_tombstone(
1085 cache,
1086 program,
1087 slot,
1088 ProgramRuntimeEnvironment::clone(&env),
1089 );
1090 }
1091
1092 for slot in next_slot + 10..next_slot + 20 {
1094 insert_unloaded_entry(cache, program, slot);
1095 }
1096 }
1097
1098 #[test]
1099 fn test_random_eviction() {
1100 let mut programs = vec![];
1101 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1102
1103 program_deploy_test_helper(
1109 &mut cache,
1110 Pubkey::new_unique(),
1111 vec![0, 10, 20, 30, 40],
1112 vec![4, 5, 25, 35, 12],
1113 &mut programs,
1114 );
1115
1116 program_deploy_test_helper(
1118 &mut cache,
1119 Pubkey::new_unique(),
1120 vec![5, 11, 21, 24],
1121 vec![0, 2, 30, 45],
1122 &mut programs,
1123 );
1124
1125 program_deploy_test_helper(
1127 &mut cache,
1128 Pubkey::new_unique(),
1129 vec![0, 5, 15, 25],
1130 vec![100, 3, 20, 40],
1131 &mut programs,
1132 );
1133
1134 let num_loaded_expected = 13;
1136 let num_unloaded_expected = 30;
1138 let num_tombstones_expected = 30;
1140
1141 programs.sort_by_key(|(_id, _slot, usage_count)| *usage_count);
1143 let num_loaded = num_matching_entries(&cache, |program_type| {
1144 matches!(program_type, ProgramCacheEntryType::Loaded(_))
1145 });
1146 let num_unloaded = num_matching_entries(&cache, |program_type| {
1147 matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1148 });
1149 let num_tombstones = num_matching_entries(&cache, |program_type| {
1150 matches!(
1151 program_type,
1152 ProgramCacheEntryType::DelayVisibility
1153 | ProgramCacheEntryType::FailedVerification(_)
1154 | ProgramCacheEntryType::Closed
1155 )
1156 });
1157
1158 assert_eq!(num_loaded, num_loaded_expected);
1160 assert_eq!(num_unloaded, num_unloaded_expected);
1161 assert_eq!(num_tombstones, num_tombstones_expected);
1162
1163 let eviction_pct: Percent = 1;
1165
1166 let num_loaded_expected = crate::loaded_programs::percent_of_max_entries(eviction_pct);
1167 let num_unloaded_expected = num_unloaded_expected + num_loaded - num_loaded_expected;
1168 cache.evict_using_random_selection(eviction_pct, 21);
1169
1170 let num_loaded = num_matching_entries(&cache, |program_type| {
1172 matches!(program_type, ProgramCacheEntryType::Loaded(_))
1173 });
1174 let num_unloaded = num_matching_entries(&cache, |program_type| {
1175 matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1176 });
1177 let num_tombstones = num_matching_entries(&cache, |program_type| {
1178 matches!(program_type, ProgramCacheEntryType::FailedVerification(_))
1179 });
1180
1181 assert_eq!(num_loaded, num_loaded_expected);
1183 assert_eq!(num_unloaded, num_unloaded_expected);
1185 assert_eq!(num_tombstones, num_tombstones_expected);
1187 }
1188
1189 #[test]
1190 fn test_eviction() {
1191 let mut programs = vec![];
1192 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1193
1194 program_deploy_test_helper(
1196 &mut cache,
1197 Pubkey::new_unique(),
1198 vec![0, 10, 20, 30, 40],
1199 vec![4, 5, 25, 35, 12],
1200 &mut programs,
1201 );
1202
1203 program_deploy_test_helper(
1205 &mut cache,
1206 Pubkey::new_unique(),
1207 vec![5, 11, 21, 24],
1208 vec![0, 2, 30, 45],
1209 &mut programs,
1210 );
1211
1212 program_deploy_test_helper(
1214 &mut cache,
1215 Pubkey::new_unique(),
1216 vec![0, 5, 15, 25],
1217 vec![100, 3, 20, 40],
1218 &mut programs,
1219 );
1220
1221 let num_loaded_expected = 13;
1223 let num_unloaded_expected = 30;
1225 let num_tombstones_expected = 30;
1227
1228 programs.sort_by_key(|(_id, _slot, usage_count)| *usage_count);
1230 let num_loaded = num_matching_entries(&cache, |program_type| {
1231 matches!(program_type, ProgramCacheEntryType::Loaded(_))
1232 });
1233 let num_unloaded = num_matching_entries(&cache, |program_type| {
1234 matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1235 });
1236 let num_tombstones = num_matching_entries(&cache, |program_type| {
1237 matches!(program_type, ProgramCacheEntryType::FailedVerification(_))
1238 });
1239
1240 assert_eq!(num_loaded, num_loaded_expected);
1242 assert_eq!(num_unloaded, num_unloaded_expected);
1243 assert_eq!(num_tombstones, num_tombstones_expected);
1244
1245 let eviction_pct: Percent = 1;
1247
1248 let num_loaded_expected = crate::loaded_programs::percent_of_max_entries(eviction_pct);
1249 let num_unloaded_expected = num_unloaded_expected + num_loaded - num_loaded_expected;
1250
1251 cache.sort_and_unload(eviction_pct);
1252
1253 let entries = cache.get_flattened_entries_for_tests();
1255 programs.iter().for_each(|entry| {
1256 assert!(entries.iter().any(|(key, _entry)| key == &entry.0));
1257 });
1258
1259 let unloaded = entries
1260 .iter()
1261 .filter_map(|(key, program)| {
1262 matches!(program.program, ProgramCacheEntryType::Unloaded(_))
1263 .then_some((*key, program.stats.uses.load(Ordering::Relaxed)))
1264 })
1265 .collect::<Vec<(Pubkey, u64)>>();
1266
1267 for index in 0..3 {
1268 let expected = programs.get(index).expect("Missing program");
1269 assert!(unloaded.contains(&(expected.0, expected.2)));
1270 }
1271
1272 let num_loaded = num_matching_entries(&cache, |program_type| {
1274 matches!(program_type, ProgramCacheEntryType::Loaded(_))
1275 });
1276 let num_unloaded = num_matching_entries(&cache, |program_type| {
1277 matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1278 });
1279 let num_tombstones = num_matching_entries(&cache, |program_type| {
1280 matches!(
1281 program_type,
1282 ProgramCacheEntryType::DelayVisibility
1283 | ProgramCacheEntryType::FailedVerification(_)
1284 | ProgramCacheEntryType::Closed
1285 )
1286 });
1287
1288 assert_eq!(num_loaded, num_loaded_expected);
1290 assert_eq!(num_unloaded, num_unloaded_expected);
1292 assert_eq!(num_tombstones, num_tombstones_expected);
1294 }
1295
1296 #[test]
1297 fn test_usage_count_of_unloaded_program() {
1298 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1299 let env = get_mock_program_runtime_environment();
1300
1301 let program = Pubkey::new_unique();
1302 let evict_to_pct: Percent = 2;
1303 let cache_capacity_after_shrink =
1304 crate::loaded_programs::percent_of_max_entries(evict_to_pct);
1305 let num_total_programs = (cache_capacity_after_shrink + 1) as u64;
1307 (0..num_total_programs).for_each(|i| {
1308 let stats = ProgramStatistics {
1309 uses: (i + 10).into(),
1310 ..Default::default()
1311 };
1312 let entry = new_test_entry_with_usage(i, stats);
1313 cache.assign_program(&env, program, i, entry);
1314 });
1315
1316 cache.sort_and_unload(evict_to_pct);
1317
1318 let num_unloaded = num_matching_entries(&cache, |program_type| {
1319 matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1320 });
1321 assert_eq!(num_unloaded, 1);
1322
1323 cache
1324 .get_flattened_entries_for_tests()
1325 .iter()
1326 .for_each(|(_key, program)| {
1327 if matches!(program.program, ProgramCacheEntryType::Unloaded(_)) {
1328 assert_eq!(program.stats.uses.load(Ordering::Relaxed), 10);
1330 assert_eq!(program.deployment_slot, 0);
1331 assert_eq!(program.effective_slot(), 1);
1332 }
1333 });
1334
1335 cache.assign_program(
1338 &env,
1339 program,
1340 0,
1341 new_test_entry_with_usage(0, ProgramStatistics::default()),
1342 );
1343
1344 cache
1345 .get_flattened_entries_for_tests()
1346 .iter()
1347 .for_each(|(_key, program)| {
1348 if matches!(program.program, ProgramCacheEntryType::Unloaded(_))
1349 && program.deployment_slot == 0
1350 && program.effective_slot() == 1
1351 {
1352 assert_eq!(program.stats.uses.load(Ordering::Relaxed), 10);
1354 }
1355 });
1356 }
1357
1358 #[test]
1359 fn test_fuzz_assign_program_order() {
1360 use rand::prelude::SliceRandom;
1361 const EXPECTED_ENTRIES: [(u64, bool); 5] =
1362 [(1, true), (3, false), (5, true), (9, true), (10, false)];
1363 let mut rng = rand::rng();
1364 let program_id = Pubkey::new_unique();
1365 let env = get_mock_program_runtime_environment();
1366 for _ in 0..1000 {
1367 let mut entries = EXPECTED_ENTRIES.to_vec();
1368 entries.shuffle(&mut rng);
1369 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1370 for (deployment_slot, delay_visibility) in entries {
1371 let entry = Arc::new(if delay_visibility {
1372 ProgramCacheEntry {
1373 program: new_loaded_entry(ProgramRuntimeEnvironment::from(
1374 BuiltinProgram::new_mock(),
1375 )), account_owner: ProgramCacheEntryOwner::LoaderV2,
1377 deployment_slot,
1378 stats: Arc::default(),
1379 latest_access_slot: AtomicU64::new(deployment_slot),
1380 }
1381 } else {
1382 ProgramCacheEntry::new_failed_verification_tombstone(
1383 deployment_slot,
1384 ProgramCacheEntryOwner::LoaderV2,
1385 ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock()), )
1387 });
1388 assert!(!cache.assign_program(&env, program_id, deployment_slot, entry));
1389 }
1390 for ((deployment_slot, delay_visibility), entry) in EXPECTED_ENTRIES
1391 .iter()
1392 .zip(cache.get_slot_versions_for_tests(&program_id).iter())
1393 {
1394 assert_eq!(entry.deployment_slot, *deployment_slot);
1395 assert_eq!(
1396 entry.effective_slot(),
1397 deployment_slot.saturating_add(*delay_visibility as u64)
1398 );
1399 }
1400 }
1401 }
1402
1403 #[test_matrix(
1404 (
1405 ProgramCacheEntryType::FailedVerification(get_mock_program_runtime_environment()),
1406 new_loaded_entry(get_mock_program_runtime_environment()),
1407 ),
1408 (
1409 ProgramCacheEntryType::FailedVerification(get_mock_program_runtime_environment()),
1410 ProgramCacheEntryType::Closed,
1411 ProgramCacheEntryType::Unloaded(get_mock_program_runtime_environment()),
1412 new_loaded_entry(get_mock_program_runtime_environment()),
1413 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1414 )
1415 )]
1416 #[test_matrix(
1417 ProgramCacheEntryType::Closed,
1418 (
1419 ProgramCacheEntryType::FailedVerification(get_mock_program_runtime_environment()),
1420 ProgramCacheEntryType::Closed,
1421 new_loaded_entry(get_mock_program_runtime_environment()),
1422 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1423 )
1424 )]
1425 #[test_matrix(
1426 ProgramCacheEntryType::Unloaded(get_mock_program_runtime_environment()),
1427 (
1428 ProgramCacheEntryType::Closed,
1429 ProgramCacheEntryType::Unloaded(get_mock_program_runtime_environment()),
1430 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1431 )
1432 )]
1433 #[test_matrix(
1434 (ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),),
1435 (
1436 ProgramCacheEntryType::FailedVerification(get_mock_program_runtime_environment()),
1437 ProgramCacheEntryType::Closed,
1438 ProgramCacheEntryType::Unloaded(get_mock_program_runtime_environment()),
1439 new_loaded_entry(get_mock_program_runtime_environment()),
1440 )
1441 )]
1442 #[should_panic(expected = "Unexpected replacement of an entry")]
1443 fn test_assign_program_failure(old: ProgramCacheEntryType, new: ProgramCacheEntryType) {
1444 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1445 let env = get_mock_program_runtime_environment();
1446 let program_id = Pubkey::new_unique();
1447 assert!(!cache.assign_program(
1448 &env,
1449 program_id,
1450 10,
1451 Arc::new(ProgramCacheEntry {
1452 program: old,
1453 account_owner: ProgramCacheEntryOwner::LoaderV2,
1454 deployment_slot: 10,
1455 stats: Arc::default(),
1456 latest_access_slot: AtomicU64::default(),
1457 }),
1458 ));
1459 cache.assign_program(
1460 &env,
1461 program_id,
1462 10,
1463 Arc::new(ProgramCacheEntry {
1464 program: new,
1465 account_owner: ProgramCacheEntryOwner::LoaderV2,
1466 deployment_slot: 10,
1467 stats: Arc::default(),
1468 latest_access_slot: AtomicU64::default(),
1469 }),
1470 );
1471 }
1472
1473 #[test_matrix(
1474 ProgramCacheEntryType::Unloaded(get_mock_program_runtime_environment()),
1475 (
1476 new_loaded_entry(get_mock_program_runtime_environment()),
1477 ProgramCacheEntryType::FailedVerification(get_mock_program_runtime_environment()),
1478 )
1479 )]
1480 #[test_case(
1481 ProgramCacheEntryType::Closed,
1482 ProgramCacheEntryType::Unloaded(get_mock_program_runtime_environment())
1483 )]
1484 #[test_case(
1485 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1486 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock())
1487 )]
1488 fn test_assign_program_success(old: ProgramCacheEntryType, new: ProgramCacheEntryType) {
1489 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1490 let env = get_mock_program_runtime_environment();
1491 let program_id = Pubkey::new_unique();
1492 assert!(!cache.assign_program(
1493 &env,
1494 program_id,
1495 10,
1496 Arc::new(ProgramCacheEntry {
1497 program: old,
1498 account_owner: ProgramCacheEntryOwner::LoaderV2,
1499 deployment_slot: 10,
1500 stats: Arc::default(),
1501 latest_access_slot: AtomicU64::default(),
1502 }),
1503 ));
1504 assert!(!cache.assign_program(
1505 &env,
1506 program_id,
1507 10,
1508 Arc::new(ProgramCacheEntry {
1509 program: new,
1510 account_owner: ProgramCacheEntryOwner::LoaderV2,
1511 deployment_slot: 10,
1512 stats: Arc::default(),
1513 latest_access_slot: AtomicU64::default(),
1514 }),
1515 ));
1516 }
1517
1518 #[test]
1519 fn test_assign_program_removes_entries_in_same_slot() {
1520 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1521 let env = get_mock_program_runtime_environment();
1522 let program_id = Pubkey::new_unique();
1523 let closed_other_slot = Arc::new(ProgramCacheEntry {
1524 program: ProgramCacheEntryType::Closed,
1525 account_owner: ProgramCacheEntryOwner::LoaderV2,
1526 deployment_slot: 9,
1527 stats: Arc::default(),
1528 latest_access_slot: AtomicU64::default(),
1529 });
1530 let closed_current_slot = Arc::new(ProgramCacheEntry {
1531 program: ProgramCacheEntryType::Closed,
1532 account_owner: ProgramCacheEntryOwner::LoaderV2,
1533 deployment_slot: 10,
1534 stats: Arc::default(),
1535 latest_access_slot: AtomicU64::default(),
1536 });
1537 let loaded_entry_current_env = Arc::new(ProgramCacheEntry {
1538 program: ProgramCacheEntryType::Unloaded(get_mock_program_runtime_environment()),
1539 account_owner: ProgramCacheEntryOwner::LoaderV2,
1540 deployment_slot: 10,
1541 stats: Arc::default(),
1542 latest_access_slot: AtomicU64::default(),
1543 });
1544 let loaded_entry_upcoming_env = Arc::new(ProgramCacheEntry {
1545 program: ProgramCacheEntryType::Unloaded(ProgramRuntimeEnvironment::from(
1546 BuiltinProgram::new_mock(),
1547 )),
1548 account_owner: ProgramCacheEntryOwner::LoaderV2,
1549 deployment_slot: 10,
1550 stats: Arc::default(),
1551 latest_access_slot: AtomicU64::default(),
1552 });
1553 assert!(!cache.assign_program(&env, program_id, 9, closed_other_slot.clone()));
1554 assert!(!cache.assign_program(&env, program_id, 10, closed_current_slot));
1555 assert!(!cache.assign_program(&env, program_id, 10, loaded_entry_upcoming_env.clone()));
1556 assert!(!cache.assign_program(&env, program_id, 10, loaded_entry_current_env.clone()));
1557 assert_eq!(
1559 cache.get_slot_versions_for_tests(&program_id),
1560 &[
1561 closed_other_slot,
1562 loaded_entry_current_env,
1563 loaded_entry_upcoming_env
1564 ]
1565 );
1566 }
1567
1568 #[test]
1569 fn test_tombstone() {
1570 let env = get_mock_program_runtime_environment();
1571 let tombstone = ProgramCacheEntry::new_failed_verification_tombstone(
1572 0,
1573 ProgramCacheEntryOwner::LoaderV2,
1574 env.clone(),
1575 );
1576 assert_matches!(
1577 tombstone.program,
1578 ProgramCacheEntryType::FailedVerification(_)
1579 );
1580 assert!(tombstone.is_tombstone());
1581 assert_eq!(tombstone.deployment_slot, 0);
1582 assert_eq!(tombstone.effective_slot(), 0);
1583
1584 let tombstone =
1585 ProgramCacheEntry::new_closed_tombstone(100, ProgramCacheEntryOwner::LoaderV2);
1586 assert_matches!(tombstone.program, ProgramCacheEntryType::Closed);
1587 assert!(tombstone.is_tombstone());
1588 assert_eq!(tombstone.deployment_slot, 100);
1589 assert_eq!(tombstone.effective_slot(), 100);
1590
1591 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1592 let program1 = Pubkey::new_unique();
1593 let tombstone = set_failed_verification_tombstone(&mut cache, program1, 10, env.clone());
1594 let slot_versions = cache.get_slot_versions_for_tests(&program1);
1595 assert_eq!(slot_versions.len(), 1);
1596 assert!(slot_versions.first().unwrap().is_tombstone());
1597 assert_eq!(tombstone.deployment_slot, 10);
1598 assert_eq!(tombstone.effective_slot(), 10);
1599
1600 let program2 = Pubkey::new_unique();
1602 cache.assign_program(&env, program2, 50, new_test_builtin_entry(50));
1603 let slot_versions = cache.get_slot_versions_for_tests(&program2);
1604 assert_eq!(slot_versions.len(), 1);
1605 assert!(!slot_versions.first().unwrap().is_tombstone());
1606
1607 let tombstone = set_failed_verification_tombstone(&mut cache, program2, 60, env);
1608 let slot_versions = cache.get_slot_versions_for_tests(&program2);
1609 assert_eq!(slot_versions.len(), 2);
1610 assert!(!slot_versions.first().unwrap().is_tombstone());
1611 assert!(slot_versions.get(1).unwrap().is_tombstone());
1612 assert!(tombstone.is_tombstone());
1613 assert_eq!(tombstone.deployment_slot, 60);
1614 assert_eq!(tombstone.effective_slot(), 60);
1615 }
1616
1617 struct TestForkGraph {
1618 relation: BlockRelation,
1619 }
1620 impl ForkGraph for TestForkGraph {
1621 fn relationship(&self, _a: Slot, _b: Slot) -> BlockRelation {
1622 self.relation
1623 }
1624 }
1625
1626 #[test]
1627 fn test_prune_empty() {
1628 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1629 let fork_graph = Arc::new(RwLock::new(TestForkGraph {
1630 relation: BlockRelation::Unrelated,
1631 }));
1632
1633 cache.set_fork_graph(Arc::downgrade(&fork_graph));
1634
1635 cache.prune(0, None, &fork_graph.read().unwrap());
1636 assert!(cache.get_flattened_entries_for_tests().is_empty());
1637
1638 cache.prune(10, None, &fork_graph.read().unwrap());
1639 assert!(cache.get_flattened_entries_for_tests().is_empty());
1640
1641 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1642 let fork_graph = Arc::new(RwLock::new(TestForkGraph {
1643 relation: BlockRelation::Ancestor,
1644 }));
1645
1646 cache.set_fork_graph(Arc::downgrade(&fork_graph));
1647
1648 cache.prune(0, None, &fork_graph.read().unwrap());
1649 assert!(cache.get_flattened_entries_for_tests().is_empty());
1650
1651 cache.prune(10, None, &fork_graph.read().unwrap());
1652 assert!(cache.get_flattened_entries_for_tests().is_empty());
1653
1654 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1655 let fork_graph = Arc::new(RwLock::new(TestForkGraph {
1656 relation: BlockRelation::Descendant,
1657 }));
1658
1659 cache.set_fork_graph(Arc::downgrade(&fork_graph));
1660
1661 cache.prune(0, None, &fork_graph.read().unwrap());
1662 assert!(cache.get_flattened_entries_for_tests().is_empty());
1663
1664 cache.prune(10, None, &fork_graph.read().unwrap());
1665 assert!(cache.get_flattened_entries_for_tests().is_empty());
1666
1667 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1668 let fork_graph = Arc::new(RwLock::new(TestForkGraph {
1669 relation: BlockRelation::Unknown,
1670 }));
1671 cache.set_fork_graph(Arc::downgrade(&fork_graph));
1672
1673 cache.prune(0, None, &fork_graph.read().unwrap());
1674 assert!(cache.get_flattened_entries_for_tests().is_empty());
1675
1676 cache.prune(10, None, &fork_graph.read().unwrap());
1677 assert!(cache.get_flattened_entries_for_tests().is_empty());
1678 }
1679
1680 #[test]
1681 fn test_prune_with_two_environments_before_epoch_boundary() {
1682 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1683 let env = get_mock_program_runtime_environment();
1684 let new_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
1685 let fork_graph = Arc::new(RwLock::new(TestForkGraph {
1686 relation: BlockRelation::Ancestor,
1687 }));
1688 cache.set_fork_graph(Arc::downgrade(&fork_graph));
1689
1690 let program1 = Pubkey::new_unique();
1691 cache.assign_program(&env, program1, 10, new_test_entry(10));
1692 let updated_program = Arc::new(ProgramCacheEntry {
1693 program: new_loaded_entry(new_env.clone()),
1694 deployment_slot: 20,
1695 ..Default::default()
1696 });
1697 cache.assign_program(&env, program1, 20, updated_program.clone());
1698
1699 assert_eq!(cache.get_slot_versions_for_tests(&program1).len(), 2);
1701
1702 cache.prune(21, None, &fork_graph.read().unwrap());
1703
1704 assert_eq!(cache.get_slot_versions_for_tests(&program1).len(), 2);
1706 }
1707
1708 #[test]
1709 fn test_prune_with_two_environments_after_epoch_boundary() {
1710 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1711 let env = get_mock_program_runtime_environment();
1712 let new_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
1713 let fork_graph = Arc::new(RwLock::new(TestForkGraph {
1714 relation: BlockRelation::Ancestor,
1715 }));
1716 cache.set_fork_graph(Arc::downgrade(&fork_graph));
1717 let program1 = Pubkey::new_unique();
1718
1719 let old_program_old_env = Arc::new(ProgramCacheEntry {
1720 program: new_loaded_entry(env.clone()),
1721 deployment_slot: 10,
1722 ..Default::default()
1723 });
1724 let old_program_new_env = Arc::new(ProgramCacheEntry {
1725 program: new_loaded_entry(new_env.clone()),
1726 deployment_slot: 10,
1727 ..Default::default()
1728 });
1729 let new_program_old_env = Arc::new(ProgramCacheEntry {
1730 program: new_loaded_entry(env.clone()),
1731 deployment_slot: 20,
1732 ..Default::default()
1733 });
1734 cache.assign_program(&env, program1, 10, old_program_old_env.clone());
1735 cache.assign_program(&env, program1, 10, old_program_new_env.clone());
1736 cache.assign_program(&env, program1, 20, new_program_old_env.clone());
1737 let slot_versions = cache.get_slot_versions_for_tests(&program1);
1738 assert_eq!(
1739 &slot_versions,
1740 &[
1741 old_program_new_env.clone(),
1742 old_program_old_env.clone(),
1743 new_program_old_env.clone(),
1744 ]
1745 );
1746
1747 cache.prune(21, Some(new_env.clone()), &fork_graph.read().unwrap());
1748 let slot_versions = cache.get_slot_versions_for_tests(&program1);
1749 assert_eq!(&slot_versions, &[old_program_new_env]);
1750 assert!(matches!(
1751 &slot_versions.first().unwrap().program,
1752 ProgramCacheEntryType::Loaded(_)
1753 ));
1754 }
1755
1756 #[derive(Default)]
1757 struct TestForkGraphSpecific {
1758 forks: Vec<Vec<Slot>>,
1759 }
1760
1761 impl TestForkGraphSpecific {
1762 fn insert_fork(&mut self, fork: &[Slot]) {
1763 let mut fork = fork.to_vec();
1764 fork.sort();
1765 self.forks.push(fork)
1766 }
1767 }
1768
1769 impl ForkGraph for TestForkGraphSpecific {
1770 fn relationship(&self, a: Slot, b: Slot) -> BlockRelation {
1771 match self.forks.iter().try_for_each(|fork| {
1772 let relation = fork
1773 .iter()
1774 .position(|x| *x == a)
1775 .and_then(|a_pos| {
1776 fork.iter().position(|x| *x == b).and_then(|b_pos| {
1777 (a_pos == b_pos)
1778 .then_some(BlockRelation::Equal)
1779 .or_else(|| (a_pos < b_pos).then_some(BlockRelation::Ancestor))
1780 .or(Some(BlockRelation::Descendant))
1781 })
1782 })
1783 .unwrap_or(BlockRelation::Unrelated);
1784
1785 if relation != BlockRelation::Unrelated {
1786 return ControlFlow::Break(relation);
1787 }
1788
1789 ControlFlow::Continue(())
1790 }) {
1791 ControlFlow::Break(relation) => relation,
1792 _ => BlockRelation::Unrelated,
1793 }
1794 }
1795 }
1796
1797 fn get_entries_to_load<'a>(
1798 cache: &ProgramCache<TestForkGraphSpecific>,
1799 loading_slot: Slot,
1800 keys: &'a [Pubkey],
1801 ) -> Vec<ProgramToLoad<'a>> {
1802 let fork_graph = cache.fork_graph.as_ref().unwrap().upgrade().unwrap();
1803 let locked_fork_graph = fork_graph.read().unwrap();
1804 let entries = cache.get_flattened_entries_for_tests();
1805 keys.iter()
1806 .filter_map(|key| {
1807 entries
1808 .iter()
1809 .rev()
1810 .find(|(program_id, entry)| {
1811 program_id == key
1812 && matches!(
1813 locked_fork_graph.relationship(entry.deployment_slot, loading_slot),
1814 BlockRelation::Equal | BlockRelation::Ancestor,
1815 )
1816 })
1817 .map(|(_program_id, entry)| ProgramToLoad {
1818 program_id: key,
1819 loader: entry.account_owner,
1820 deployed_on_or_after_slot: 0,
1821 last_modification_slot: entry.deployment_slot,
1822 })
1823 })
1824 .collect()
1825 }
1826
1827 fn match_slot(
1828 extracted: &ProgramCacheForTxBatch,
1829 program: &Pubkey,
1830 deployment_slot: Slot,
1831 working_slot: Slot,
1832 ) -> bool {
1833 assert_eq!(extracted.slot, working_slot);
1834 extracted
1835 .entries
1836 .get(program)
1837 .map(|entry| entry.deployment_slot == deployment_slot)
1838 .unwrap_or(false)
1839 }
1840
1841 fn match_missing(
1842 missing: &[ProgramToLoad],
1843 program_id: &Pubkey,
1844 expected_result: bool,
1845 ) -> bool {
1846 missing.iter().any(|entry| entry.program_id == program_id) == expected_result
1847 }
1848
1849 #[test]
1850 fn test_fork_extract_and_prune() {
1851 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
1852 let env = get_mock_program_runtime_environment();
1853
1854 let mut fork_graph = TestForkGraphSpecific::default();
1870 fork_graph.insert_fork(&[0, 10, 20, 22]);
1871 fork_graph.insert_fork(&[0, 5, 11, 15, 16, 18, 19, 21, 23]);
1872 fork_graph.insert_fork(&[0, 5, 11, 25, 27]);
1873
1874 let fork_graph = Arc::new(RwLock::new(fork_graph));
1875 cache.set_fork_graph(Arc::downgrade(&fork_graph));
1876
1877 let program1 = Pubkey::new_unique();
1878 cache.assign_program(&env, program1, 0, new_test_entry(0));
1879 cache.assign_program(&env, program1, 10, new_test_entry(10));
1880 cache.assign_program(&env, program1, 20, new_test_entry(20));
1881
1882 let program2 = Pubkey::new_unique();
1883 cache.assign_program(&env, program2, 5, new_test_entry(5));
1884 cache.assign_program(&env, program2, 11, new_test_entry(11));
1885
1886 let program3 = Pubkey::new_unique();
1887 cache.assign_program(&env, program3, 25, new_test_entry(25));
1888
1889 let program4 = Pubkey::new_unique();
1890 cache.assign_program(&env, program4, 0, new_test_entry(0));
1891 cache.assign_program(&env, program4, 5, new_test_entry(5));
1892 cache.assign_program(&env, program4, 15, new_test_entry(15));
1894
1895 let keys = &[program1, program2, program3, program4];
1912 let mut missing = get_entries_to_load(&cache, 22, keys);
1913 assert!(match_missing(&missing, &program2, false));
1914 assert!(match_missing(&missing, &program3, false));
1915 let mut extracted = ProgramCacheForTxBatch::new(22);
1916 cache.extract(&mut missing, &mut extracted, &env, true, true);
1917 assert!(match_slot(&extracted, &program1, 20, 22));
1918 assert!(match_slot(&extracted, &program4, 0, 22));
1919
1920 let mut missing = get_entries_to_load(&cache, 15, keys);
1922 assert!(match_missing(&missing, &program3, false));
1923 let mut extracted = ProgramCacheForTxBatch::new(15);
1924 cache.extract(&mut missing, &mut extracted, &env, true, true);
1925 assert!(match_slot(&extracted, &program1, 0, 15));
1926 assert!(match_slot(&extracted, &program2, 11, 15));
1927 let tombstone = extracted
1930 .find(&program4)
1931 .expect("Failed to find the tombstone");
1932 assert_matches!(tombstone.program, ProgramCacheEntryType::DelayVisibility);
1933 assert_eq!(tombstone.deployment_slot, 15);
1934
1935 let mut missing = get_entries_to_load(&cache, 18, keys);
1937 assert!(match_missing(&missing, &program3, false));
1938 let mut extracted = ProgramCacheForTxBatch::new(18);
1939 cache.extract(&mut missing, &mut extracted, &env, true, true);
1940 assert!(match_slot(&extracted, &program1, 0, 18));
1941 assert!(match_slot(&extracted, &program2, 11, 18));
1942 assert!(match_slot(&extracted, &program4, 15, 18));
1944
1945 let mut missing = get_entries_to_load(&cache, 23, keys);
1947 assert!(match_missing(&missing, &program3, false));
1948 let mut extracted = ProgramCacheForTxBatch::new(23);
1949 cache.extract(&mut missing, &mut extracted, &env, true, true);
1950 assert!(match_slot(&extracted, &program1, 0, 23));
1951 assert!(match_slot(&extracted, &program2, 11, 23));
1952 assert!(match_slot(&extracted, &program4, 15, 23));
1954
1955 let mut missing = get_entries_to_load(&cache, 11, keys);
1957 assert!(match_missing(&missing, &program3, false));
1958 let mut extracted = ProgramCacheForTxBatch::new(11);
1959 cache.extract(&mut missing, &mut extracted, &env, true, true);
1960 assert!(match_slot(&extracted, &program1, 0, 11));
1961 let tombstone = extracted
1963 .find(&program2)
1964 .expect("Failed to find the tombstone");
1965 assert_matches!(tombstone.program, ProgramCacheEntryType::DelayVisibility);
1966 assert_eq!(tombstone.deployment_slot, 11);
1967 assert!(match_slot(&extracted, &program4, 5, 11));
1968
1969 cache.prune(5, None, &fork_graph.read().unwrap());
1970
1971 let mut missing = get_entries_to_load(&cache, 21, keys);
1988 assert!(match_missing(&missing, &program3, false));
1989 let mut extracted = ProgramCacheForTxBatch::new(21);
1990 cache.extract(&mut missing, &mut extracted, &env, true, true);
1991 assert!(match_slot(&extracted, &program1, 0, 21));
1993 assert!(match_slot(&extracted, &program2, 11, 21));
1994 assert!(match_slot(&extracted, &program4, 15, 21));
1995
1996 let mut missing = get_entries_to_load(&cache, 27, keys);
1998 let mut extracted = ProgramCacheForTxBatch::new(27);
1999 cache.extract(&mut missing, &mut extracted, &env, true, true);
2000 assert!(match_slot(&extracted, &program1, 0, 27));
2001 assert!(match_slot(&extracted, &program2, 11, 27));
2002 assert!(match_slot(&extracted, &program3, 25, 27));
2003 assert!(match_slot(&extracted, &program4, 5, 27));
2004
2005 cache.prune(15, None, &fork_graph.read().unwrap());
2006
2007 let mut missing = get_entries_to_load(&cache, 23, keys);
2024 assert!(match_missing(&missing, &program3, false));
2025 let mut extracted = ProgramCacheForTxBatch::new(23);
2026 cache.extract(&mut missing, &mut extracted, &env, true, true);
2027 assert!(match_slot(&extracted, &program1, 0, 23));
2028 assert!(match_slot(&extracted, &program2, 11, 23));
2029 assert!(match_slot(&extracted, &program4, 15, 23));
2030 }
2031
2032 #[test]
2033 fn test_extract_using_deployment_slot() {
2034 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
2035 let env = get_mock_program_runtime_environment();
2036
2037 let mut fork_graph = TestForkGraphSpecific::default();
2053 fork_graph.insert_fork(&[0, 10, 20, 22]);
2054 fork_graph.insert_fork(&[0, 5, 11, 12, 15, 16, 18, 19, 21, 23]);
2055 fork_graph.insert_fork(&[0, 5, 11, 25, 27]);
2056
2057 let fork_graph = Arc::new(RwLock::new(fork_graph));
2058 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2059
2060 let program1 = Pubkey::new_unique();
2061 cache.assign_program(&env, program1, 0, new_test_entry(0));
2062 cache.assign_program(&env, program1, 20, new_test_entry(20));
2063
2064 let program2 = Pubkey::new_unique();
2065 cache.assign_program(&env, program2, 5, new_test_entry(5));
2066 cache.assign_program(&env, program2, 11, new_test_entry(11));
2067
2068 let program3 = Pubkey::new_unique();
2069 cache.assign_program(&env, program3, 25, new_test_entry(25));
2070
2071 let keys = &[program1, program2, program3];
2073 let mut missing = get_entries_to_load(&cache, 12, keys);
2074 assert!(match_missing(&missing, &program3, false));
2075 let mut extracted = ProgramCacheForTxBatch::new(12);
2076 cache.extract(&mut missing, &mut extracted, &env, true, true);
2077 assert!(match_slot(&extracted, &program1, 0, 12));
2078 assert!(match_slot(&extracted, &program2, 11, 12));
2079
2080 let mut missing = get_entries_to_load(&cache, 12, keys);
2082 missing.get_mut(0).unwrap().deployed_on_or_after_slot = 5;
2083 missing.get_mut(1).unwrap().deployed_on_or_after_slot = 5;
2084 assert!(match_missing(&missing, &program3, false));
2085 let mut extracted = ProgramCacheForTxBatch::new(12);
2086 cache.extract(&mut missing, &mut extracted, &env, true, true);
2087 assert!(match_missing(&missing, &program1, true));
2088 assert!(match_slot(&extracted, &program2, 11, 12));
2089 }
2090
2091 #[test]
2092 fn test_extract_unloaded() {
2093 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
2094 let env = get_mock_program_runtime_environment();
2095
2096 let mut fork_graph = TestForkGraphSpecific::default();
2112 fork_graph.insert_fork(&[0, 10, 20, 22]);
2113 fork_graph.insert_fork(&[0, 5, 11, 15, 16, 19, 21, 23]);
2114 fork_graph.insert_fork(&[0, 5, 11, 25, 27]);
2115
2116 let fork_graph = Arc::new(RwLock::new(fork_graph));
2117 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2118
2119 let program1 = Pubkey::new_unique();
2120 cache.assign_program(&env, program1, 0, new_test_entry(0));
2121 cache.assign_program(&env, program1, 20, new_test_entry(20));
2122
2123 let program2 = Pubkey::new_unique();
2124 cache.assign_program(&env, program2, 5, new_test_entry(5));
2125 cache.assign_program(&env, program2, 11, new_test_entry(11));
2126
2127 let program3 = Pubkey::new_unique();
2128 let _ = insert_unloaded_entry(&mut cache, program3, 25);
2130
2131 cache.assign_program(
2135 &env,
2136 program3,
2137 20,
2138 Arc::new(
2139 new_test_entry(20)
2140 .to_unloaded()
2141 .expect("Failed to create unloaded program"),
2142 ),
2143 );
2144
2145 let keys = &[program1, program2, program3];
2147 let mut missing = get_entries_to_load(&cache, 19, keys);
2148 assert!(match_missing(&missing, &program3, false));
2149 let mut extracted = ProgramCacheForTxBatch::new(19);
2150 cache.extract(&mut missing, &mut extracted, &env, true, true);
2151 assert!(match_slot(&extracted, &program1, 0, 19));
2152 assert!(match_slot(&extracted, &program2, 11, 19));
2153
2154 let mut missing = get_entries_to_load(&cache, 27, keys);
2156 let mut extracted = ProgramCacheForTxBatch::new(27);
2157 cache.extract(&mut missing, &mut extracted, &env, true, true);
2158 assert!(match_slot(&extracted, &program1, 0, 27));
2159 assert!(match_slot(&extracted, &program2, 11, 27));
2160 assert!(match_missing(&missing, &program3, true));
2161
2162 let mut missing = get_entries_to_load(&cache, 22, keys);
2164 assert!(match_missing(&missing, &program2, false));
2165 let mut extracted = ProgramCacheForTxBatch::new(22);
2166 cache.extract(&mut missing, &mut extracted, &env, true, true);
2167 assert!(match_slot(&extracted, &program1, 20, 22));
2168 assert!(match_missing(&missing, &program3, true));
2169 }
2170
2171 #[test]
2172 fn test_extract_different_environment() {
2173 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
2174 let env = get_mock_program_runtime_environment();
2175 let other_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
2176
2177 let mut fork_graph = TestForkGraphSpecific::default();
2187 fork_graph.insert_fork(&[0, 10, 20, 22]);
2188
2189 let fork_graph = Arc::new(RwLock::new(fork_graph));
2190 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2191
2192 let program1 = Pubkey::new_unique();
2193 cache.assign_program(
2194 &env,
2195 program1,
2196 10,
2197 Arc::new(ProgramCacheEntry::new_closed_tombstone(
2198 10,
2199 ProgramCacheEntryOwner::LoaderV3,
2200 )),
2201 );
2202 cache.assign_program(&env, program1, 20, new_test_entry(20));
2203
2204 let keys = &[program1];
2206 let mut missing = get_entries_to_load(&cache, 22, keys);
2207 let mut extracted = ProgramCacheForTxBatch::new(22);
2208 cache.extract(&mut missing, &mut extracted, &env, true, true);
2209 assert!(match_slot(&extracted, &program1, 20, 22));
2210
2211 let mut missing = get_entries_to_load(&cache, 22, keys);
2213 let mut extracted = ProgramCacheForTxBatch::new(22);
2214 cache.extract(&mut missing, &mut extracted, &other_env, true, true);
2215 assert!(match_missing(&missing, &program1, true));
2216 }
2217
2218 #[test]
2219 fn test_extract_nonexistent() {
2220 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
2221 let env = get_mock_program_runtime_environment();
2222 let fork_graph = TestForkGraphSpecific::default();
2223 let fork_graph = Arc::new(RwLock::new(fork_graph));
2224 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2225
2226 let program1 = Pubkey::new_unique();
2227 let mut missing = vec![ProgramToLoad {
2228 program_id: &program1,
2229 loader: ProgramCacheEntryOwner::LoaderV3,
2230 deployed_on_or_after_slot: 0,
2231 last_modification_slot: 0,
2232 }];
2233 let mut extracted = ProgramCacheForTxBatch::new(0);
2234 cache.extract(&mut missing, &mut extracted, &env, true, true);
2235 assert!(match_missing(&missing, &program1, true));
2236 }
2237
2238 #[test]
2239 fn test_unloaded() {
2240 let mut cache = ProgramCache::<TestForkGraph>::new(0);
2241 let env = get_mock_program_runtime_environment();
2242 for program_cache_entry_type in [
2243 ProgramCacheEntryType::Closed,
2244 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
2245 ] {
2246 let entry = Arc::new(ProgramCacheEntry {
2247 program: program_cache_entry_type,
2248 account_owner: ProgramCacheEntryOwner::LoaderV2,
2249 deployment_slot: 0,
2250 stats: Arc::default(),
2251 latest_access_slot: AtomicU64::default(),
2252 });
2253 assert!(entry.to_unloaded().is_none());
2254
2255 let program_id = Pubkey::new_unique();
2257 cache.assign_program(&env, program_id, entry.deployment_slot, entry.clone());
2258 cache.unload_program_entry(program_id, entry.deployment_slot, &entry);
2259 assert_eq!(cache.get_slot_versions_for_tests(&program_id).len(), 1);
2260 assert!(cache.stats.evictions.is_empty());
2261 }
2262
2263 let stats = ProgramStatistics {
2264 uses: 3.into(),
2265 ..Default::default()
2266 };
2267 let entry = new_test_entry_with_usage(1, stats);
2268 let unloaded_entry = entry.to_unloaded().unwrap();
2269 assert_eq!(unloaded_entry.deployment_slot, 1);
2270 assert_eq!(unloaded_entry.effective_slot(), 2);
2271 assert_eq!(unloaded_entry.latest_access_slot.load(Ordering::Relaxed), 1);
2272 assert_eq!(unloaded_entry.stats.uses.load(Ordering::Relaxed), 3);
2273
2274 let program_id = Pubkey::new_unique();
2276 cache.assign_program(&env, program_id, entry.deployment_slot, entry.clone());
2277 cache.unload_program_entry(program_id, entry.deployment_slot, &entry);
2278 assert!(cache.stats.evictions.contains_key(&program_id));
2279 }
2280
2281 #[test]
2282 fn test_fork_prune_find_first_ancestor() {
2283 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
2284 let env = get_mock_program_runtime_environment();
2285
2286 let mut fork_graph = TestForkGraphSpecific::default();
2297 fork_graph.insert_fork(&[0, 10, 20]);
2298 fork_graph.insert_fork(&[0, 5]);
2299 let fork_graph = Arc::new(RwLock::new(fork_graph));
2300 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2301
2302 let program1 = Pubkey::new_unique();
2303 cache.assign_program(&env, program1, 0, new_test_entry(0));
2304 cache.assign_program(&env, program1, 5, new_test_entry(5));
2305
2306 cache.prune(10, None, &fork_graph.read().unwrap());
2307
2308 let keys = &[program1];
2309 let mut missing = get_entries_to_load(&cache, 20, keys);
2310 let mut extracted = ProgramCacheForTxBatch::new(20);
2311 cache.extract(&mut missing, &mut extracted, &env, true, true);
2312
2313 assert_eq!(
2315 extracted
2316 .find(&program1)
2317 .expect("Did not find the program")
2318 .deployment_slot,
2319 0
2320 );
2321 }
2322
2323 #[test]
2324 fn test_prune_by_deployment_slot() {
2325 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
2326 let env = get_mock_program_runtime_environment();
2327
2328 let mut fork_graph = TestForkGraphSpecific::default();
2339 fork_graph.insert_fork(&[0, 10, 20]);
2340 fork_graph.insert_fork(&[0, 5, 6]);
2341 let fork_graph = Arc::new(RwLock::new(fork_graph));
2342 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2343
2344 let program1 = Pubkey::new_unique();
2345 cache.assign_program(&env, program1, 0, new_test_entry(0));
2346 cache.assign_program(&env, program1, 5, new_test_entry(5));
2347
2348 let program2 = Pubkey::new_unique();
2349 cache.assign_program(&env, program2, 10, new_test_entry(10));
2350
2351 let keys = &[program1, program2];
2352 let mut missing = get_entries_to_load(&cache, 20, keys);
2353 let mut extracted = ProgramCacheForTxBatch::new(20);
2354 cache.extract(&mut missing, &mut extracted, &env, true, true);
2355 assert!(match_slot(&extracted, &program1, 0, 20));
2356 assert!(match_slot(&extracted, &program2, 10, 20));
2357
2358 let mut missing = get_entries_to_load(&cache, 6, keys);
2359 assert!(match_missing(&missing, &program2, false));
2360 let mut extracted = ProgramCacheForTxBatch::new(6);
2361 cache.extract(&mut missing, &mut extracted, &env, true, true);
2362 assert!(match_slot(&extracted, &program1, 5, 6));
2363
2364 cache.prune_by_deployment_slot(5);
2367
2368 let mut missing = get_entries_to_load(&cache, 20, keys);
2369 let mut extracted = ProgramCacheForTxBatch::new(20);
2370 cache.extract(&mut missing, &mut extracted, &env, true, true);
2371 assert!(match_slot(&extracted, &program1, 0, 20));
2372 assert!(match_slot(&extracted, &program2, 10, 20));
2373
2374 let mut missing = get_entries_to_load(&cache, 6, keys);
2375 assert!(match_missing(&missing, &program2, false));
2376 let mut extracted = ProgramCacheForTxBatch::new(6);
2377 cache.extract(&mut missing, &mut extracted, &env, true, true);
2378 assert!(match_slot(&extracted, &program1, 0, 6));
2379
2380 cache.prune_by_deployment_slot(10);
2383
2384 let mut missing = get_entries_to_load(&cache, 20, keys);
2385 assert!(match_missing(&missing, &program2, false));
2386 let mut extracted = ProgramCacheForTxBatch::new(20);
2387 cache.extract(&mut missing, &mut extracted, &env, true, true);
2388 assert!(match_slot(&extracted, &program1, 0, 20));
2389 }
2390}