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 = 512;
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 .cloned()
554 .collect();
555 second_level.reverse();
556 if let Some(new_environment) = new_environment.as_ref() {
558 let retain_flags = (0..second_level.len())
559 .map(|index_in_second_level| {
560 let entry = second_level.get(index_in_second_level).unwrap();
561 if Self::matches_environment(entry, new_environment) {
562 return true;
563 }
564 let prev_entry = index_in_second_level
568 .checked_sub(1)
569 .and_then(|idx| second_level.get(idx));
570 let next_entry = index_in_second_level
571 .checked_add(1)
572 .and_then(|idx| second_level.get(idx));
573 let other_entry_with_same_deployment_slot_exists = prev_entry
574 .map(|e| e.deployment_slot)
575 == Some(entry.deployment_slot)
576 || next_entry.map(|e| e.deployment_slot)
577 == Some(entry.deployment_slot);
578 if other_entry_with_same_deployment_slot_exists {
579 self.stats
580 .prunes_environment
581 .fetch_add(1, Ordering::Relaxed);
582 return false;
583 } else if let Some(unloaded_entry) = entry.to_unloaded_in_env(
584 ProgramRuntimeEnvironment::clone(new_environment),
585 ) && let Some(entry) =
586 second_level.get_mut(index_in_second_level)
587 {
588 *entry = Arc::new(unloaded_entry);
589 }
590 true
591 })
592 .collect::<Vec<bool>>();
593 let mut index_in_second_level = 0;
594 second_level.retain(|_entry| {
595 let retain_flag = *retain_flags.get(index_in_second_level).unwrap();
596 index_in_second_level = index_in_second_level.saturating_add(1);
597 retain_flag
598 });
599 }
600 }
601 }
602 }
603 self.remove_programs_with_no_entries();
604 debug_assert!(self.latest_root_slot <= new_root_slot);
605 self.latest_root_slot = new_root_slot;
606 }
607
608 fn matches_environment(
609 entry: &Arc<ProgramCacheEntry>,
610 program_runtime_environment: &ProgramRuntimeEnvironment,
611 ) -> bool {
612 let Some(environment) = entry.program.get_environment() else {
613 return true;
614 };
615 environment == program_runtime_environment
616 }
617
618 fn matches_criteria(
619 program: &Arc<ProgramCacheEntry>,
620 criteria: &ProgramCacheMatchCriteria,
621 ) -> bool {
622 match criteria {
623 ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(slot) => {
624 program.deployment_slot >= *slot
625 }
626 ProgramCacheMatchCriteria::NoCriteria => true,
627 }
628 }
629
630 pub fn extract(
633 &self,
634 search_for: &mut Vec<ProgramToLoad>,
635 loaded_programs_for_tx_batch: &mut ProgramCacheForTxBatch,
636 program_runtime_environment_for_execution: &ProgramRuntimeEnvironment,
637 increment_usage_counter: bool,
638 count_hits_and_misses: bool,
639 ) -> Option<Pubkey> {
640 debug_assert!(self.fork_graph.is_some());
641 let fork_graph = self.fork_graph.as_ref().unwrap().upgrade().unwrap();
642 let locked_fork_graph = fork_graph.read().unwrap();
643 let mut cooperative_loading_task = None;
644 match &self.index {
645 IndexImplementation::V1 {
646 entries,
647 loading_entries,
648 } => {
649 search_for.retain(|program_to_load| {
650 if let Some(second_level) = entries.get(program_to_load.program_id) {
651 let mut filter_by_deployment_slot = None;
652 for entry in second_level.iter().rev() {
653 let required_deployment_slot =
654 filter_by_deployment_slot.unwrap_or(entry.deployment_slot);
655 if required_deployment_slot != entry.deployment_slot
656 || program_to_load.loader != entry.account_owner
657 {
658 continue;
659 }
660 let entry_in_same_branch = entry.deployment_slot
661 <= self.latest_root_slot
662 || matches!(
663 locked_fork_graph.relationship(
664 entry.deployment_slot,
665 loaded_programs_for_tx_batch.slot
666 ),
667 BlockRelation::Equal | BlockRelation::Ancestor
668 );
669 if entry_in_same_branch {
670 let entry_is_effective =
671 loaded_programs_for_tx_batch.slot >= entry.effective_slot();
672 let entry_to_return = if entry_is_effective {
673 if !Self::matches_environment(
674 entry,
675 program_runtime_environment_for_execution,
676 ) {
677 filter_by_deployment_slot = filter_by_deployment_slot
686 .or(Some(entry.deployment_slot));
687 continue;
688 }
689 if !Self::matches_criteria(
690 entry,
691 &program_to_load.match_criteria,
692 ) {
693 break;
694 }
695 if let ProgramCacheEntryType::Unloaded(_environment) =
696 &entry.program
697 {
698 break;
699 }
700 entry.clone()
701 } else if entry.is_implicit_delay_visibility_tombstone(
702 loaded_programs_for_tx_batch.slot,
703 ) {
704 Arc::new(ProgramCacheEntry::new_delay_visibility_tombstone(
708 entry.deployment_slot,
709 entry.account_owner,
710 Arc::clone(&entry.stats),
711 ))
712 } else {
713 continue;
714 };
715 entry_to_return
716 .update_access_slot(loaded_programs_for_tx_batch.slot);
717 if increment_usage_counter {
718 entry_to_return.stats.uses.fetch_add(1, Ordering::Relaxed);
719 }
720 loaded_programs_for_tx_batch
721 .entries
722 .insert(*program_to_load.program_id, entry_to_return);
723 return false;
724 }
725 }
726 }
727 if cooperative_loading_task.is_none() {
728 let mut loading_entries = loading_entries.lock().unwrap();
729 let entry = loading_entries.entry(*program_to_load.program_id);
730 if let Entry::Vacant(entry) = entry {
731 entry.insert((
732 loaded_programs_for_tx_batch.slot,
733 thread::current().id(),
734 ));
735 cooperative_loading_task = Some(*program_to_load.program_id);
736 }
737 }
738 true
739 });
740 }
741 }
742 drop(locked_fork_graph);
743 if count_hits_and_misses {
744 self.stats
745 .misses
746 .fetch_add(search_for.len() as u64, Ordering::Relaxed);
747 self.stats.hits.fetch_add(
748 loaded_programs_for_tx_batch.entries.len() as u64,
749 Ordering::Relaxed,
750 );
751 }
752 cooperative_loading_task
753 }
754
755 pub fn finish_cooperative_loading_task(
757 &mut self,
758 program_runtime_environment: &ProgramRuntimeEnvironment,
759 current_slot: Slot,
760 key: Pubkey,
761 last_modification_slot: Slot,
762 loaded_program: Arc<ProgramCacheEntry>,
763 ) -> bool {
764 match &mut self.index {
765 IndexImplementation::V1 {
766 loading_entries, ..
767 } => {
768 let loading_thread = loading_entries.get_mut().unwrap().remove(&key);
769 debug_assert_eq!(loading_thread, Some((current_slot, thread::current().id())));
770 if loaded_program.deployment_slot > self.latest_root_slot
772 && !matches!(
773 self.fork_graph
774 .as_ref()
775 .unwrap()
776 .upgrade()
777 .unwrap()
778 .read()
779 .unwrap()
780 .relationship(loaded_program.deployment_slot, current_slot),
781 BlockRelation::Equal | BlockRelation::Ancestor
782 )
783 {
784 self.stats.lost_insertions.fetch_add(1, Ordering::Relaxed);
785 }
786 let was_occupied = self.assign_program(
787 program_runtime_environment,
788 key,
789 last_modification_slot,
790 loaded_program,
791 );
792 self.loading_task_waiter.notify();
793 was_occupied
794 }
795 }
796 }
797
798 pub fn merge(
799 &mut self,
800 program_runtime_environment: &ProgramRuntimeEnvironment,
801 current_slot: Slot,
802 modified_entries: &HashMap<Pubkey, Arc<ProgramCacheEntry>>,
803 ) {
804 modified_entries.iter().for_each(|(key, entry)| {
805 self.assign_program(
806 program_runtime_environment,
807 *key,
808 current_slot,
809 entry.clone(),
810 );
811 })
812 }
813
814 pub fn get_flattened_entries(&self) -> Vec<(Pubkey, Slot, Arc<ProgramCacheEntry>)> {
816 match &self.index {
817 IndexImplementation::V1 { entries, .. } => entries
818 .iter()
819 .flat_map(|(id, second_level)| {
820 second_level
821 .iter()
822 .filter_map(move |program| match program.program {
823 ProgramCacheEntryType::Loaded(_) => Some((*id, 0, program.clone())),
824 _ => None,
825 })
826 })
827 .collect(),
828 }
829 }
830
831 #[cfg(feature = "dev-context-only-utils")]
833 pub fn get_flattened_entries_for_tests(&self) -> Vec<(Pubkey, Arc<ProgramCacheEntry>)> {
834 match &self.index {
835 IndexImplementation::V1 { entries, .. } => entries
836 .iter()
837 .flat_map(|(id, second_level)| {
838 second_level.iter().map(|program| (*id, program.clone()))
839 })
840 .collect(),
841 }
842 }
843
844 pub fn get_slot_versions_for_tests(&self, key: &Pubkey) -> &[Arc<ProgramCacheEntry>] {
846 match &self.index {
847 IndexImplementation::V1 { entries, .. } => entries
848 .get(key)
849 .map(|second_level| second_level.as_ref())
850 .unwrap_or(&[]),
851 }
852 }
853
854 pub fn sort_and_unload(&mut self, shrink_to_percent: Percent) {
856 let mut sorted_candidates = self.get_flattened_entries();
857 sorted_candidates.sort_by_cached_key(|(_id, _last_modification_slot, program)| {
858 program.stats.uses.load(Ordering::Relaxed)
859 });
860 let num_to_unload = sorted_candidates
861 .len()
862 .saturating_sub(percent_of_max_entries(shrink_to_percent));
863 for (program, last_modification_slot, entry) in sorted_candidates.iter().take(num_to_unload)
864 {
865 self.unload_program_entry(*program, *last_modification_slot, entry);
866 }
867 }
868
869 pub fn evict_using_random_selection(&mut self, shrink_to_percent: Percent, now: Slot) {
875 let mut candidates = self.get_flattened_entries();
876 let mut rng = rng();
877 self.stats
878 .water_level
879 .store(candidates.len() as u64, Ordering::Relaxed);
880 let num_to_unload = candidates
881 .len()
882 .saturating_sub(percent_of_max_entries(shrink_to_percent));
883 let mut sample_entry = |candidates: &Vec<(Pubkey, u64, Arc<ProgramCacheEntry>)>| {
884 #[cfg(feature = "shuttle-test")]
887 let index = rng.gen_range(0..candidates.len());
888 #[cfg(not(feature = "shuttle-test"))]
889 let index = rng.random_range(0..candidates.len());
890 let usage_counter = candidates
891 .get(index)
892 .expect("Failed to get cached entry")
893 .2
894 .retention_score();
895 (index, usage_counter)
896 };
897
898 const MAX_ADDITIONAL_SAMPLES: usize = 3;
906 let avoid_evicting_above_score = retention_score(now, 500 * EMA_SCALE, 500);
907 for _ in 0..num_to_unload {
908 let (mut index, mut score) = sample_entry(&candidates);
909 for _ in 0..MAX_ADDITIONAL_SAMPLES {
910 let (sample_index, sample_score) = sample_entry(&candidates);
911 if score > sample_score {
912 index = sample_index;
913 score = sample_score;
914 }
915 if score < avoid_evicting_above_score {
916 break;
917 }
918 }
919 let (id, last_modification_slot, entry) = candidates.swap_remove(index);
920 self.unload_program_entry(id, last_modification_slot, &entry);
921 }
922 }
923
924 pub fn remove_programs(&mut self, keys: impl Iterator<Item = Pubkey>) {
926 match &mut self.index {
927 IndexImplementation::V1 { entries, .. } => {
928 for k in keys {
929 entries.remove(&k);
930 }
931 }
932 }
933 }
934
935 fn unload_program_entry(
938 &mut self,
939 id: Pubkey,
940 _last_modification_slot: Slot,
941 remove_entry: &Arc<ProgramCacheEntry>,
942 ) {
943 match &mut self.index {
944 IndexImplementation::V1 { entries, .. } => {
945 let second_level = entries.get_mut(&id).expect("Cache lookup failed");
946 let candidate = second_level
947 .iter_mut()
948 .find(|entry| Arc::ptr_eq(entry, remove_entry))
949 .expect("Program entry not found");
950
951 if let ProgramCacheEntryType::Loaded(_) = candidate.program
953 && let Some(unloaded) = candidate.to_unloaded()
954 {
955 if candidate.stats.uses.load(Ordering::Relaxed) == 1 {
956 self.stats.one_hit_wonders.fetch_add(1, Ordering::Relaxed);
957 }
958 self.stats
959 .evictions
960 .entry(id)
961 .and_modify(|c| *c = c.saturating_add(1))
962 .or_insert(1);
963 *candidate = Arc::new(unloaded);
964 }
965 }
966 }
967 }
968
969 fn remove_programs_with_no_entries(&mut self) {
970 match &mut self.index {
971 IndexImplementation::V1 { entries, .. } => {
972 let num_programs_before_removal = entries.len();
973 entries.retain(|_key, second_level| !second_level.is_empty());
974 if entries.len() < num_programs_before_removal {
975 self.stats.empty_entries.fetch_add(
976 num_programs_before_removal.saturating_sub(entries.len()) as u64,
977 Ordering::Relaxed,
978 );
979 }
980 }
981 }
982 }
983}
984
985#[cfg(feature = "frozen-abi")]
986impl solana_frozen_abi::abi_example::AbiExample for ProgramCacheEntry {
987 fn example() -> Self {
988 Self::default()
990 }
991}
992
993#[cfg(feature = "frozen-abi")]
994impl<FG: ForkGraph> solana_frozen_abi::abi_example::AbiExample for ProgramCache<FG> {
995 fn example() -> Self {
996 Self::new(Slot::default())
998 }
999}
1000
1001#[cfg(test)]
1002pub(crate) mod tests {
1003 use {
1004 crate::{
1005 loaded_programs::{
1006 BlockRelation, ForkGraph, Percent, ProgramCache, ProgramCacheForTxBatch,
1007 ProgramCacheMatchCriteria, ProgramRuntimeEnvironment, ProgramToLoad,
1008 get_mock_program_runtime_environment,
1009 },
1010 program_cache_entry::{
1011 ProgramCacheEntry, ProgramCacheEntryOwner, ProgramCacheEntryType,
1012 },
1013 program_metrics::ProgramStatistics,
1014 },
1015 assert_matches::assert_matches,
1016 solana_clock::Slot,
1017 solana_pubkey::Pubkey,
1018 solana_sbpf::{elf::Executable, program::BuiltinProgram},
1019 solana_svm_type_overrides::sync::{
1020 Arc, RwLock,
1021 atomic::{AtomicU64, Ordering},
1022 },
1023 std::{fs::File, io::Read, ops::ControlFlow},
1024 test_case::test_matrix,
1025 };
1026
1027 fn new_test_entry(deployment_slot: Slot) -> Arc<ProgramCacheEntry> {
1028 new_test_entry_with_usage(deployment_slot, ProgramStatistics::default())
1029 }
1030
1031 fn new_loaded_entry(env: ProgramRuntimeEnvironment) -> ProgramCacheEntryType {
1032 let mut elf = Vec::new();
1033 File::open("../programs/bpf_loader/test_elfs/out/noop_aligned.so")
1034 .unwrap()
1035 .read_to_end(&mut elf)
1036 .unwrap();
1037 let executable = Executable::load(&elf, Arc::clone(&*env)).unwrap();
1038 ProgramCacheEntryType::Loaded(executable)
1039 }
1040
1041 pub(crate) fn new_test_entry_with_usage(
1042 deployment_slot: Slot,
1043 stats: ProgramStatistics,
1044 ) -> Arc<ProgramCacheEntry> {
1045 Arc::new(ProgramCacheEntry {
1046 program: new_loaded_entry(get_mock_program_runtime_environment()),
1047 account_owner: ProgramCacheEntryOwner::LoaderV2,
1048 deployment_slot,
1049 stats: Arc::new(stats),
1050 latest_access_slot: AtomicU64::new(deployment_slot),
1051 })
1052 }
1053
1054 fn new_test_builtin_entry(deployment_slot: Slot) -> Arc<ProgramCacheEntry> {
1055 Arc::new(ProgramCacheEntry {
1056 program: ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1057 account_owner: ProgramCacheEntryOwner::NativeLoader,
1058 deployment_slot,
1059 stats: Arc::default(),
1060 latest_access_slot: AtomicU64::default(),
1061 })
1062 }
1063
1064 fn set_failed_verification_tombstone<FG: ForkGraph>(
1065 cache: &mut ProgramCache<FG>,
1066 key: Pubkey,
1067 current_slot: Slot,
1068 env: ProgramRuntimeEnvironment,
1069 ) -> Arc<ProgramCacheEntry> {
1070 let program = Arc::new(ProgramCacheEntry::new_failed_verification_tombstone(
1071 current_slot,
1072 ProgramCacheEntryOwner::LoaderV2,
1073 ProgramRuntimeEnvironment::clone(&env),
1074 ));
1075 cache.assign_program(&env, key, current_slot, program.clone());
1076 program
1077 }
1078
1079 fn insert_unloaded_entry<FG: ForkGraph>(
1080 cache: &mut ProgramCache<FG>,
1081 key: Pubkey,
1082 current_slot: Slot,
1083 ) -> Arc<ProgramCacheEntry> {
1084 let env = get_mock_program_runtime_environment();
1085 let loaded = new_test_entry_with_usage(current_slot, ProgramStatistics::default());
1086 let unloaded = Arc::new(loaded.to_unloaded().expect("Failed to unload the program"));
1087 cache.assign_program(&env, key, current_slot, unloaded.clone());
1088 unloaded
1089 }
1090
1091 fn num_matching_entries<P, FG>(cache: &ProgramCache<FG>, predicate: P) -> usize
1092 where
1093 P: Fn(&ProgramCacheEntryType) -> bool,
1094 FG: ForkGraph,
1095 {
1096 cache
1097 .get_flattened_entries_for_tests()
1098 .iter()
1099 .filter(|(_key, program)| predicate(&program.program))
1100 .count()
1101 }
1102
1103 fn program_deploy_test_helper(
1104 cache: &mut ProgramCache<TestForkGraph>,
1105 program: Pubkey,
1106 deployment_slots: Vec<Slot>,
1107 usage_counters: Vec<u64>,
1108 programs: &mut Vec<(Pubkey, Slot, u64)>,
1109 ) {
1110 let env = get_mock_program_runtime_environment();
1111 deployment_slots
1113 .iter()
1114 .enumerate()
1115 .for_each(|(i, deployment_slot)| {
1116 let usage_counter = *usage_counters.get(i).unwrap_or(&0);
1117 let stats = ProgramStatistics {
1118 uses: usage_counter.into(),
1119 ..Default::default()
1120 };
1121 cache.assign_program(
1122 &env,
1123 program,
1124 *deployment_slot,
1125 new_test_entry_with_usage(*deployment_slot, stats),
1126 );
1127 programs.push((program, *deployment_slot, usage_counter));
1128 });
1129
1130 let env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
1132 for slot in 21..31 {
1133 set_failed_verification_tombstone(
1134 cache,
1135 program,
1136 slot,
1137 ProgramRuntimeEnvironment::clone(&env),
1138 );
1139 }
1140
1141 for slot in 31..41 {
1143 insert_unloaded_entry(cache, program, slot);
1144 }
1145 }
1146
1147 #[test]
1148 fn test_random_eviction() {
1149 let mut programs = vec![];
1150 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1151
1152 program_deploy_test_helper(
1158 &mut cache,
1159 Pubkey::new_unique(),
1160 vec![0, 10, 20],
1161 vec![4, 5, 25],
1162 &mut programs,
1163 );
1164
1165 program_deploy_test_helper(
1167 &mut cache,
1168 Pubkey::new_unique(),
1169 vec![5, 11],
1170 vec![0, 2],
1171 &mut programs,
1172 );
1173
1174 program_deploy_test_helper(
1176 &mut cache,
1177 Pubkey::new_unique(),
1178 vec![0, 5, 15],
1179 vec![100, 3, 20],
1180 &mut programs,
1181 );
1182
1183 let num_loaded_expected = 8;
1185 let num_unloaded_expected = 30;
1187 let num_tombstones_expected = 30;
1189
1190 programs.sort_by_key(|(_id, _slot, usage_count)| *usage_count);
1192 let num_loaded = num_matching_entries(&cache, |program_type| {
1193 matches!(program_type, ProgramCacheEntryType::Loaded(_))
1194 });
1195 let num_unloaded = num_matching_entries(&cache, |program_type| {
1196 matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1197 });
1198 let num_tombstones = num_matching_entries(&cache, |program_type| {
1199 matches!(
1200 program_type,
1201 ProgramCacheEntryType::DelayVisibility
1202 | ProgramCacheEntryType::FailedVerification(_)
1203 | ProgramCacheEntryType::Closed
1204 )
1205 });
1206
1207 assert_eq!(num_loaded, num_loaded_expected);
1209 assert_eq!(num_unloaded, num_unloaded_expected);
1210 assert_eq!(num_tombstones, num_tombstones_expected);
1211
1212 let eviction_pct: Percent = 1;
1214
1215 let num_loaded_expected = crate::loaded_programs::percent_of_max_entries(eviction_pct);
1216 let num_unloaded_expected = num_unloaded_expected + num_loaded - num_loaded_expected;
1217 cache.evict_using_random_selection(eviction_pct, 21);
1218
1219 let num_loaded = num_matching_entries(&cache, |program_type| {
1221 matches!(program_type, ProgramCacheEntryType::Loaded(_))
1222 });
1223 let num_unloaded = num_matching_entries(&cache, |program_type| {
1224 matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1225 });
1226 let num_tombstones = num_matching_entries(&cache, |program_type| {
1227 matches!(program_type, ProgramCacheEntryType::FailedVerification(_))
1228 });
1229
1230 assert_eq!(num_loaded, num_loaded_expected);
1232 assert_eq!(num_unloaded, num_unloaded_expected);
1234 assert_eq!(num_tombstones, num_tombstones_expected);
1236 }
1237
1238 #[test]
1239 fn test_eviction() {
1240 let mut programs = vec![];
1241 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1242
1243 program_deploy_test_helper(
1245 &mut cache,
1246 Pubkey::new_unique(),
1247 vec![0, 10, 20],
1248 vec![4, 5, 25],
1249 &mut programs,
1250 );
1251
1252 program_deploy_test_helper(
1254 &mut cache,
1255 Pubkey::new_unique(),
1256 vec![5, 11],
1257 vec![0, 2],
1258 &mut programs,
1259 );
1260
1261 program_deploy_test_helper(
1263 &mut cache,
1264 Pubkey::new_unique(),
1265 vec![0, 5, 15],
1266 vec![100, 3, 20],
1267 &mut programs,
1268 );
1269
1270 let num_loaded_expected = 8;
1272 let num_unloaded_expected = 30;
1274 let num_tombstones_expected = 30;
1276
1277 programs.sort_by_key(|(_id, _slot, usage_count)| *usage_count);
1279 let num_loaded = num_matching_entries(&cache, |program_type| {
1280 matches!(program_type, ProgramCacheEntryType::Loaded(_))
1281 });
1282 let num_unloaded = num_matching_entries(&cache, |program_type| {
1283 matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1284 });
1285 let num_tombstones = num_matching_entries(&cache, |program_type| {
1286 matches!(program_type, ProgramCacheEntryType::FailedVerification(_))
1287 });
1288
1289 assert_eq!(num_loaded, num_loaded_expected);
1291 assert_eq!(num_unloaded, num_unloaded_expected);
1292 assert_eq!(num_tombstones, num_tombstones_expected);
1293
1294 let eviction_pct: Percent = 1;
1296
1297 let num_loaded_expected = crate::loaded_programs::percent_of_max_entries(eviction_pct);
1298 let num_unloaded_expected = num_unloaded_expected + num_loaded - num_loaded_expected;
1299
1300 cache.sort_and_unload(eviction_pct);
1301
1302 let entries = cache.get_flattened_entries_for_tests();
1304 programs.iter().for_each(|entry| {
1305 assert!(entries.iter().any(|(key, _entry)| key == &entry.0));
1306 });
1307
1308 let unloaded = entries
1309 .iter()
1310 .filter_map(|(key, program)| {
1311 matches!(program.program, ProgramCacheEntryType::Unloaded(_))
1312 .then_some((*key, program.stats.uses.load(Ordering::Relaxed)))
1313 })
1314 .collect::<Vec<(Pubkey, u64)>>();
1315
1316 for index in 0..3 {
1317 let expected = programs.get(index).expect("Missing program");
1318 assert!(unloaded.contains(&(expected.0, expected.2)));
1319 }
1320
1321 let num_loaded = num_matching_entries(&cache, |program_type| {
1323 matches!(program_type, ProgramCacheEntryType::Loaded(_))
1324 });
1325 let num_unloaded = num_matching_entries(&cache, |program_type| {
1326 matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1327 });
1328 let num_tombstones = num_matching_entries(&cache, |program_type| {
1329 matches!(
1330 program_type,
1331 ProgramCacheEntryType::DelayVisibility
1332 | ProgramCacheEntryType::FailedVerification(_)
1333 | ProgramCacheEntryType::Closed
1334 )
1335 });
1336
1337 assert_eq!(num_loaded, num_loaded_expected);
1339 assert_eq!(num_unloaded, num_unloaded_expected);
1341 assert_eq!(num_tombstones, num_tombstones_expected);
1343 }
1344
1345 #[test]
1346 fn test_usage_count_of_unloaded_program() {
1347 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1348 let env = get_mock_program_runtime_environment();
1349
1350 let program = Pubkey::new_unique();
1351 let evict_to_pct: Percent = 2;
1352 let cache_capacity_after_shrink =
1353 crate::loaded_programs::percent_of_max_entries(evict_to_pct);
1354 let num_total_programs = (cache_capacity_after_shrink + 1) as u64;
1356 (0..num_total_programs).for_each(|i| {
1357 let stats = ProgramStatistics {
1358 uses: (i + 10).into(),
1359 ..Default::default()
1360 };
1361 let entry = new_test_entry_with_usage(i, stats);
1362 cache.assign_program(&env, program, i, entry);
1363 });
1364
1365 cache.sort_and_unload(evict_to_pct);
1366
1367 let num_unloaded = num_matching_entries(&cache, |program_type| {
1368 matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1369 });
1370 assert_eq!(num_unloaded, 1);
1371
1372 cache
1373 .get_flattened_entries_for_tests()
1374 .iter()
1375 .for_each(|(_key, program)| {
1376 if matches!(program.program, ProgramCacheEntryType::Unloaded(_)) {
1377 assert_eq!(program.stats.uses.load(Ordering::Relaxed), 10);
1379 assert_eq!(program.deployment_slot, 0);
1380 assert_eq!(program.effective_slot(), 1);
1381 }
1382 });
1383
1384 cache.assign_program(
1387 &env,
1388 program,
1389 0,
1390 new_test_entry_with_usage(0, ProgramStatistics::default()),
1391 );
1392
1393 cache
1394 .get_flattened_entries_for_tests()
1395 .iter()
1396 .for_each(|(_key, program)| {
1397 if matches!(program.program, ProgramCacheEntryType::Unloaded(_))
1398 && program.deployment_slot == 0
1399 && program.effective_slot() == 1
1400 {
1401 assert_eq!(program.stats.uses.load(Ordering::Relaxed), 10);
1403 }
1404 });
1405 }
1406
1407 #[test]
1408 fn test_fuzz_assign_program_order() {
1409 use rand::prelude::SliceRandom;
1410 const EXPECTED_ENTRIES: [(u64, bool); 5] =
1411 [(1, true), (3, false), (5, true), (9, true), (10, false)];
1412 let mut rng = rand::rng();
1413 let program_id = Pubkey::new_unique();
1414 let env = get_mock_program_runtime_environment();
1415 for _ in 0..1000 {
1416 let mut entries = EXPECTED_ENTRIES.to_vec();
1417 entries.shuffle(&mut rng);
1418 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1419 for (deployment_slot, delay_visibility) in entries {
1420 let entry = Arc::new(if delay_visibility {
1421 ProgramCacheEntry {
1422 program: new_loaded_entry(ProgramRuntimeEnvironment::from(
1423 BuiltinProgram::new_mock(),
1424 )), account_owner: ProgramCacheEntryOwner::LoaderV2,
1426 deployment_slot,
1427 stats: Arc::default(),
1428 latest_access_slot: AtomicU64::new(deployment_slot),
1429 }
1430 } else {
1431 ProgramCacheEntry::new_failed_verification_tombstone(
1432 deployment_slot,
1433 ProgramCacheEntryOwner::LoaderV2,
1434 ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock()), )
1436 });
1437 assert!(!cache.assign_program(&env, program_id, deployment_slot, entry));
1438 }
1439 for ((deployment_slot, delay_visibility), entry) in EXPECTED_ENTRIES
1440 .iter()
1441 .zip(cache.get_slot_versions_for_tests(&program_id).iter())
1442 {
1443 assert_eq!(entry.deployment_slot, *deployment_slot);
1444 assert_eq!(
1445 entry.effective_slot(),
1446 deployment_slot.saturating_add(*delay_visibility as u64)
1447 );
1448 }
1449 }
1450 }
1451
1452 #[test_matrix(
1453 (
1454 ProgramCacheEntryType::FailedVerification(get_mock_program_runtime_environment()),
1455 new_loaded_entry(get_mock_program_runtime_environment()),
1456 ),
1457 (
1458 ProgramCacheEntryType::FailedVerification(get_mock_program_runtime_environment()),
1459 ProgramCacheEntryType::Closed,
1460 ProgramCacheEntryType::Unloaded(get_mock_program_runtime_environment()),
1461 new_loaded_entry(get_mock_program_runtime_environment()),
1462 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1463 )
1464 )]
1465 #[test_matrix(
1466 ProgramCacheEntryType::Closed,
1467 (
1468 ProgramCacheEntryType::FailedVerification(get_mock_program_runtime_environment()),
1469 ProgramCacheEntryType::Closed,
1470 new_loaded_entry(get_mock_program_runtime_environment()),
1471 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1472 )
1473 )]
1474 #[test_matrix(
1475 ProgramCacheEntryType::Unloaded(get_mock_program_runtime_environment()),
1476 (
1477 ProgramCacheEntryType::Closed,
1478 ProgramCacheEntryType::Unloaded(get_mock_program_runtime_environment()),
1479 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1480 )
1481 )]
1482 #[test_matrix(
1483 (ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),),
1484 (
1485 ProgramCacheEntryType::FailedVerification(get_mock_program_runtime_environment()),
1486 ProgramCacheEntryType::Closed,
1487 ProgramCacheEntryType::Unloaded(get_mock_program_runtime_environment()),
1488 new_loaded_entry(get_mock_program_runtime_environment()),
1489 )
1490 )]
1491 #[should_panic(expected = "Unexpected replacement of an entry")]
1492 fn test_assign_program_failure(old: ProgramCacheEntryType, new: ProgramCacheEntryType) {
1493 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1494 let env = get_mock_program_runtime_environment();
1495 let program_id = Pubkey::new_unique();
1496 assert!(!cache.assign_program(
1497 &env,
1498 program_id,
1499 10,
1500 Arc::new(ProgramCacheEntry {
1501 program: old,
1502 account_owner: ProgramCacheEntryOwner::LoaderV2,
1503 deployment_slot: 10,
1504 stats: Arc::default(),
1505 latest_access_slot: AtomicU64::default(),
1506 }),
1507 ));
1508 cache.assign_program(
1509 &env,
1510 program_id,
1511 10,
1512 Arc::new(ProgramCacheEntry {
1513 program: new,
1514 account_owner: ProgramCacheEntryOwner::LoaderV2,
1515 deployment_slot: 10,
1516 stats: Arc::default(),
1517 latest_access_slot: AtomicU64::default(),
1518 }),
1519 );
1520 }
1521
1522 #[test_matrix(
1523 ProgramCacheEntryType::Unloaded(get_mock_program_runtime_environment()),
1524 (
1525 new_loaded_entry(get_mock_program_runtime_environment()),
1526 ProgramCacheEntryType::FailedVerification(get_mock_program_runtime_environment()),
1527 )
1528 )]
1529 #[test_case(
1530 ProgramCacheEntryType::Closed,
1531 ProgramCacheEntryType::Unloaded(get_mock_program_runtime_environment())
1532 )]
1533 #[test_case(
1534 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1535 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock())
1536 )]
1537 fn test_assign_program_success(old: ProgramCacheEntryType, new: ProgramCacheEntryType) {
1538 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1539 let env = get_mock_program_runtime_environment();
1540 let program_id = Pubkey::new_unique();
1541 assert!(!cache.assign_program(
1542 &env,
1543 program_id,
1544 10,
1545 Arc::new(ProgramCacheEntry {
1546 program: old,
1547 account_owner: ProgramCacheEntryOwner::LoaderV2,
1548 deployment_slot: 10,
1549 stats: Arc::default(),
1550 latest_access_slot: AtomicU64::default(),
1551 }),
1552 ));
1553 assert!(!cache.assign_program(
1554 &env,
1555 program_id,
1556 10,
1557 Arc::new(ProgramCacheEntry {
1558 program: new,
1559 account_owner: ProgramCacheEntryOwner::LoaderV2,
1560 deployment_slot: 10,
1561 stats: Arc::default(),
1562 latest_access_slot: AtomicU64::default(),
1563 }),
1564 ));
1565 }
1566
1567 #[test]
1568 fn test_assign_program_removes_entries_in_same_slot() {
1569 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1570 let env = get_mock_program_runtime_environment();
1571 let program_id = Pubkey::new_unique();
1572 let closed_other_slot = Arc::new(ProgramCacheEntry {
1573 program: ProgramCacheEntryType::Closed,
1574 account_owner: ProgramCacheEntryOwner::LoaderV2,
1575 deployment_slot: 9,
1576 stats: Arc::default(),
1577 latest_access_slot: AtomicU64::default(),
1578 });
1579 let closed_current_slot = Arc::new(ProgramCacheEntry {
1580 program: ProgramCacheEntryType::Closed,
1581 account_owner: ProgramCacheEntryOwner::LoaderV2,
1582 deployment_slot: 10,
1583 stats: Arc::default(),
1584 latest_access_slot: AtomicU64::default(),
1585 });
1586 let loaded_entry_current_env = Arc::new(ProgramCacheEntry {
1587 program: ProgramCacheEntryType::Unloaded(get_mock_program_runtime_environment()),
1588 account_owner: ProgramCacheEntryOwner::LoaderV2,
1589 deployment_slot: 10,
1590 stats: Arc::default(),
1591 latest_access_slot: AtomicU64::default(),
1592 });
1593 let loaded_entry_upcoming_env = Arc::new(ProgramCacheEntry {
1594 program: ProgramCacheEntryType::Unloaded(ProgramRuntimeEnvironment::from(
1595 BuiltinProgram::new_mock(),
1596 )),
1597 account_owner: ProgramCacheEntryOwner::LoaderV2,
1598 deployment_slot: 10,
1599 stats: Arc::default(),
1600 latest_access_slot: AtomicU64::default(),
1601 });
1602 assert!(!cache.assign_program(&env, program_id, 9, closed_other_slot.clone()));
1603 assert!(!cache.assign_program(&env, program_id, 10, closed_current_slot));
1604 assert!(!cache.assign_program(&env, program_id, 10, loaded_entry_upcoming_env.clone()));
1605 assert!(!cache.assign_program(&env, program_id, 10, loaded_entry_current_env.clone()));
1606 assert_eq!(
1608 cache.get_slot_versions_for_tests(&program_id),
1609 &[
1610 closed_other_slot,
1611 loaded_entry_current_env,
1612 loaded_entry_upcoming_env
1613 ]
1614 );
1615 }
1616
1617 #[test]
1618 fn test_tombstone() {
1619 let env = get_mock_program_runtime_environment();
1620 let tombstone = ProgramCacheEntry::new_failed_verification_tombstone(
1621 0,
1622 ProgramCacheEntryOwner::LoaderV2,
1623 env.clone(),
1624 );
1625 assert_matches!(
1626 tombstone.program,
1627 ProgramCacheEntryType::FailedVerification(_)
1628 );
1629 assert!(tombstone.is_tombstone());
1630 assert_eq!(tombstone.deployment_slot, 0);
1631 assert_eq!(tombstone.effective_slot(), 0);
1632
1633 let tombstone =
1634 ProgramCacheEntry::new_closed_tombstone(100, ProgramCacheEntryOwner::LoaderV2);
1635 assert_matches!(tombstone.program, ProgramCacheEntryType::Closed);
1636 assert!(tombstone.is_tombstone());
1637 assert_eq!(tombstone.deployment_slot, 100);
1638 assert_eq!(tombstone.effective_slot(), 100);
1639
1640 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1641 let program1 = Pubkey::new_unique();
1642 let tombstone = set_failed_verification_tombstone(&mut cache, program1, 10, env.clone());
1643 let slot_versions = cache.get_slot_versions_for_tests(&program1);
1644 assert_eq!(slot_versions.len(), 1);
1645 assert!(slot_versions.first().unwrap().is_tombstone());
1646 assert_eq!(tombstone.deployment_slot, 10);
1647 assert_eq!(tombstone.effective_slot(), 10);
1648
1649 let program2 = Pubkey::new_unique();
1651 cache.assign_program(&env, program2, 50, new_test_builtin_entry(50));
1652 let slot_versions = cache.get_slot_versions_for_tests(&program2);
1653 assert_eq!(slot_versions.len(), 1);
1654 assert!(!slot_versions.first().unwrap().is_tombstone());
1655
1656 let tombstone = set_failed_verification_tombstone(&mut cache, program2, 60, env);
1657 let slot_versions = cache.get_slot_versions_for_tests(&program2);
1658 assert_eq!(slot_versions.len(), 2);
1659 assert!(!slot_versions.first().unwrap().is_tombstone());
1660 assert!(slot_versions.get(1).unwrap().is_tombstone());
1661 assert!(tombstone.is_tombstone());
1662 assert_eq!(tombstone.deployment_slot, 60);
1663 assert_eq!(tombstone.effective_slot(), 60);
1664 }
1665
1666 struct TestForkGraph {
1667 relation: BlockRelation,
1668 }
1669 impl ForkGraph for TestForkGraph {
1670 fn relationship(&self, _a: Slot, _b: Slot) -> BlockRelation {
1671 self.relation
1672 }
1673 }
1674
1675 #[test]
1676 fn test_prune_empty() {
1677 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1678 let fork_graph = Arc::new(RwLock::new(TestForkGraph {
1679 relation: BlockRelation::Unrelated,
1680 }));
1681
1682 cache.set_fork_graph(Arc::downgrade(&fork_graph));
1683
1684 cache.prune(0, None, &fork_graph.read().unwrap());
1685 assert!(cache.get_flattened_entries_for_tests().is_empty());
1686
1687 cache.prune(10, None, &fork_graph.read().unwrap());
1688 assert!(cache.get_flattened_entries_for_tests().is_empty());
1689
1690 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1691 let fork_graph = Arc::new(RwLock::new(TestForkGraph {
1692 relation: BlockRelation::Ancestor,
1693 }));
1694
1695 cache.set_fork_graph(Arc::downgrade(&fork_graph));
1696
1697 cache.prune(0, None, &fork_graph.read().unwrap());
1698 assert!(cache.get_flattened_entries_for_tests().is_empty());
1699
1700 cache.prune(10, None, &fork_graph.read().unwrap());
1701 assert!(cache.get_flattened_entries_for_tests().is_empty());
1702
1703 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1704 let fork_graph = Arc::new(RwLock::new(TestForkGraph {
1705 relation: BlockRelation::Descendant,
1706 }));
1707
1708 cache.set_fork_graph(Arc::downgrade(&fork_graph));
1709
1710 cache.prune(0, None, &fork_graph.read().unwrap());
1711 assert!(cache.get_flattened_entries_for_tests().is_empty());
1712
1713 cache.prune(10, None, &fork_graph.read().unwrap());
1714 assert!(cache.get_flattened_entries_for_tests().is_empty());
1715
1716 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1717 let fork_graph = Arc::new(RwLock::new(TestForkGraph {
1718 relation: BlockRelation::Unknown,
1719 }));
1720 cache.set_fork_graph(Arc::downgrade(&fork_graph));
1721
1722 cache.prune(0, None, &fork_graph.read().unwrap());
1723 assert!(cache.get_flattened_entries_for_tests().is_empty());
1724
1725 cache.prune(10, None, &fork_graph.read().unwrap());
1726 assert!(cache.get_flattened_entries_for_tests().is_empty());
1727 }
1728
1729 #[test]
1730 fn test_prune_with_two_environments_before_epoch_boundary() {
1731 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1732 let env = get_mock_program_runtime_environment();
1733 let new_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
1734 let fork_graph = Arc::new(RwLock::new(TestForkGraph {
1735 relation: BlockRelation::Ancestor,
1736 }));
1737 cache.set_fork_graph(Arc::downgrade(&fork_graph));
1738
1739 let program1 = Pubkey::new_unique();
1740 cache.assign_program(&env, program1, 10, new_test_entry(10));
1741 let updated_program = Arc::new(ProgramCacheEntry {
1742 program: new_loaded_entry(new_env.clone()),
1743 deployment_slot: 20,
1744 ..Default::default()
1745 });
1746 cache.assign_program(&env, program1, 20, updated_program.clone());
1747
1748 assert_eq!(cache.get_slot_versions_for_tests(&program1).len(), 2);
1750
1751 cache.prune(21, None, &fork_graph.read().unwrap());
1752
1753 assert_eq!(cache.get_slot_versions_for_tests(&program1).len(), 2);
1755 }
1756
1757 #[test]
1758 fn test_prune_with_two_environments_after_epoch_boundary() {
1759 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1760 let env = get_mock_program_runtime_environment();
1761 let new_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
1762 let fork_graph = Arc::new(RwLock::new(TestForkGraph {
1763 relation: BlockRelation::Ancestor,
1764 }));
1765 cache.set_fork_graph(Arc::downgrade(&fork_graph));
1766 let program1 = Pubkey::new_unique();
1767
1768 let old_program_old_env = Arc::new(ProgramCacheEntry {
1769 program: new_loaded_entry(env.clone()),
1770 deployment_slot: 10,
1771 ..Default::default()
1772 });
1773 let old_program_new_env = Arc::new(ProgramCacheEntry {
1774 program: new_loaded_entry(new_env.clone()),
1775 deployment_slot: 10,
1776 ..Default::default()
1777 });
1778 let new_program_old_env = Arc::new(ProgramCacheEntry {
1779 program: new_loaded_entry(env.clone()),
1780 deployment_slot: 20,
1781 ..Default::default()
1782 });
1783 let new_program_new_env = Arc::new(ProgramCacheEntry {
1784 program: new_loaded_entry(new_env.clone()),
1785 deployment_slot: 20,
1786 ..Default::default()
1787 });
1788 cache.assign_program(&env, program1, 10, old_program_old_env.clone());
1789 cache.assign_program(&env, program1, 10, old_program_new_env.clone());
1790 cache.assign_program(&env, program1, 20, new_program_old_env.clone());
1791 let slot_versions = cache.get_slot_versions_for_tests(&program1);
1792 assert_eq!(
1793 &slot_versions,
1794 &[
1795 old_program_new_env.clone(),
1796 old_program_old_env.clone(),
1797 new_program_old_env.clone(),
1798 ]
1799 );
1800
1801 cache.prune(21, Some(new_env.clone()), &fork_graph.read().unwrap());
1802 let slot_versions = cache.get_slot_versions_for_tests(&program1);
1803 assert_eq!(
1804 &slot_versions,
1805 &[old_program_new_env.clone(), new_program_new_env.clone()],
1806 );
1807 assert!(matches!(
1808 &slot_versions.first().unwrap().program,
1809 ProgramCacheEntryType::Loaded(_)
1810 ));
1811 assert!(matches!(
1812 &slot_versions.get(1).unwrap().program,
1813 ProgramCacheEntryType::Unloaded(_)
1814 ));
1815 }
1816
1817 #[derive(Default)]
1818 struct TestForkGraphSpecific {
1819 forks: Vec<Vec<Slot>>,
1820 }
1821
1822 impl TestForkGraphSpecific {
1823 fn insert_fork(&mut self, fork: &[Slot]) {
1824 let mut fork = fork.to_vec();
1825 fork.sort();
1826 self.forks.push(fork)
1827 }
1828 }
1829
1830 impl ForkGraph for TestForkGraphSpecific {
1831 fn relationship(&self, a: Slot, b: Slot) -> BlockRelation {
1832 match self.forks.iter().try_for_each(|fork| {
1833 let relation = fork
1834 .iter()
1835 .position(|x| *x == a)
1836 .and_then(|a_pos| {
1837 fork.iter().position(|x| *x == b).and_then(|b_pos| {
1838 (a_pos == b_pos)
1839 .then_some(BlockRelation::Equal)
1840 .or_else(|| (a_pos < b_pos).then_some(BlockRelation::Ancestor))
1841 .or(Some(BlockRelation::Descendant))
1842 })
1843 })
1844 .unwrap_or(BlockRelation::Unrelated);
1845
1846 if relation != BlockRelation::Unrelated {
1847 return ControlFlow::Break(relation);
1848 }
1849
1850 ControlFlow::Continue(())
1851 }) {
1852 ControlFlow::Break(relation) => relation,
1853 _ => BlockRelation::Unrelated,
1854 }
1855 }
1856 }
1857
1858 fn get_entries_to_load<'a>(
1859 cache: &ProgramCache<TestForkGraphSpecific>,
1860 loading_slot: Slot,
1861 keys: &'a [Pubkey],
1862 ) -> Vec<ProgramToLoad<'a>> {
1863 let fork_graph = cache.fork_graph.as_ref().unwrap().upgrade().unwrap();
1864 let locked_fork_graph = fork_graph.read().unwrap();
1865 let entries = cache.get_flattened_entries_for_tests();
1866 keys.iter()
1867 .filter_map(|key| {
1868 entries
1869 .iter()
1870 .rev()
1871 .find(|(program_id, entry)| {
1872 program_id == key
1873 && matches!(
1874 locked_fork_graph.relationship(entry.deployment_slot, loading_slot),
1875 BlockRelation::Equal | BlockRelation::Ancestor,
1876 )
1877 })
1878 .map(|(_program_id, entry)| ProgramToLoad {
1879 program_id: key,
1880 loader: entry.account_owner,
1881 match_criteria: ProgramCacheMatchCriteria::NoCriteria,
1882 last_modification_slot: entry.deployment_slot,
1883 })
1884 })
1885 .collect()
1886 }
1887
1888 fn match_slot(
1889 extracted: &ProgramCacheForTxBatch,
1890 program: &Pubkey,
1891 deployment_slot: Slot,
1892 working_slot: Slot,
1893 ) -> bool {
1894 assert_eq!(extracted.slot, working_slot);
1895 extracted
1896 .entries
1897 .get(program)
1898 .map(|entry| entry.deployment_slot == deployment_slot)
1899 .unwrap_or(false)
1900 }
1901
1902 fn match_missing(
1903 missing: &[ProgramToLoad],
1904 program_id: &Pubkey,
1905 expected_result: bool,
1906 ) -> bool {
1907 missing.iter().any(|entry| entry.program_id == program_id) == expected_result
1908 }
1909
1910 #[test]
1911 fn test_fork_extract_and_prune() {
1912 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
1913 let env = get_mock_program_runtime_environment();
1914
1915 let mut fork_graph = TestForkGraphSpecific::default();
1931 fork_graph.insert_fork(&[0, 10, 20, 22]);
1932 fork_graph.insert_fork(&[0, 5, 11, 15, 16, 18, 19, 21, 23]);
1933 fork_graph.insert_fork(&[0, 5, 11, 25, 27]);
1934
1935 let fork_graph = Arc::new(RwLock::new(fork_graph));
1936 cache.set_fork_graph(Arc::downgrade(&fork_graph));
1937
1938 let program1 = Pubkey::new_unique();
1939 cache.assign_program(&env, program1, 0, new_test_entry(0));
1940 cache.assign_program(&env, program1, 10, new_test_entry(10));
1941 cache.assign_program(&env, program1, 20, new_test_entry(20));
1942
1943 let program2 = Pubkey::new_unique();
1944 cache.assign_program(&env, program2, 5, new_test_entry(5));
1945 cache.assign_program(&env, program2, 11, new_test_entry(11));
1946
1947 let program3 = Pubkey::new_unique();
1948 cache.assign_program(&env, program3, 25, new_test_entry(25));
1949
1950 let program4 = Pubkey::new_unique();
1951 cache.assign_program(&env, program4, 0, new_test_entry(0));
1952 cache.assign_program(&env, program4, 5, new_test_entry(5));
1953 cache.assign_program(&env, program4, 15, new_test_entry(15));
1955
1956 let keys = &[program1, program2, program3, program4];
1973 let mut missing = get_entries_to_load(&cache, 22, keys);
1974 assert!(match_missing(&missing, &program2, false));
1975 assert!(match_missing(&missing, &program3, false));
1976 let mut extracted = ProgramCacheForTxBatch::new(22);
1977 cache.extract(&mut missing, &mut extracted, &env, true, true);
1978 assert!(match_slot(&extracted, &program1, 20, 22));
1979 assert!(match_slot(&extracted, &program4, 0, 22));
1980
1981 let mut missing = get_entries_to_load(&cache, 15, keys);
1983 assert!(match_missing(&missing, &program3, false));
1984 let mut extracted = ProgramCacheForTxBatch::new(15);
1985 cache.extract(&mut missing, &mut extracted, &env, true, true);
1986 assert!(match_slot(&extracted, &program1, 0, 15));
1987 assert!(match_slot(&extracted, &program2, 11, 15));
1988 let tombstone = extracted
1991 .find(&program4)
1992 .expect("Failed to find the tombstone");
1993 assert_matches!(tombstone.program, ProgramCacheEntryType::DelayVisibility);
1994 assert_eq!(tombstone.deployment_slot, 15);
1995
1996 let mut missing = get_entries_to_load(&cache, 18, keys);
1998 assert!(match_missing(&missing, &program3, false));
1999 let mut extracted = ProgramCacheForTxBatch::new(18);
2000 cache.extract(&mut missing, &mut extracted, &env, true, true);
2001 assert!(match_slot(&extracted, &program1, 0, 18));
2002 assert!(match_slot(&extracted, &program2, 11, 18));
2003 assert!(match_slot(&extracted, &program4, 15, 18));
2005
2006 let mut missing = get_entries_to_load(&cache, 23, keys);
2008 assert!(match_missing(&missing, &program3, false));
2009 let mut extracted = ProgramCacheForTxBatch::new(23);
2010 cache.extract(&mut missing, &mut extracted, &env, true, true);
2011 assert!(match_slot(&extracted, &program1, 0, 23));
2012 assert!(match_slot(&extracted, &program2, 11, 23));
2013 assert!(match_slot(&extracted, &program4, 15, 23));
2015
2016 let mut missing = get_entries_to_load(&cache, 11, keys);
2018 assert!(match_missing(&missing, &program3, false));
2019 let mut extracted = ProgramCacheForTxBatch::new(11);
2020 cache.extract(&mut missing, &mut extracted, &env, true, true);
2021 assert!(match_slot(&extracted, &program1, 0, 11));
2022 let tombstone = extracted
2024 .find(&program2)
2025 .expect("Failed to find the tombstone");
2026 assert_matches!(tombstone.program, ProgramCacheEntryType::DelayVisibility);
2027 assert_eq!(tombstone.deployment_slot, 11);
2028 assert!(match_slot(&extracted, &program4, 5, 11));
2029
2030 cache.prune(5, None, &fork_graph.read().unwrap());
2031
2032 let mut missing = get_entries_to_load(&cache, 21, keys);
2049 assert!(match_missing(&missing, &program3, false));
2050 let mut extracted = ProgramCacheForTxBatch::new(21);
2051 cache.extract(&mut missing, &mut extracted, &env, true, true);
2052 assert!(match_slot(&extracted, &program1, 0, 21));
2054 assert!(match_slot(&extracted, &program2, 11, 21));
2055 assert!(match_slot(&extracted, &program4, 15, 21));
2056
2057 let mut missing = get_entries_to_load(&cache, 27, keys);
2059 let mut extracted = ProgramCacheForTxBatch::new(27);
2060 cache.extract(&mut missing, &mut extracted, &env, true, true);
2061 assert!(match_slot(&extracted, &program1, 0, 27));
2062 assert!(match_slot(&extracted, &program2, 11, 27));
2063 assert!(match_slot(&extracted, &program3, 25, 27));
2064 assert!(match_slot(&extracted, &program4, 5, 27));
2065
2066 cache.prune(15, None, &fork_graph.read().unwrap());
2067
2068 let mut missing = get_entries_to_load(&cache, 23, keys);
2085 assert!(match_missing(&missing, &program3, false));
2086 let mut extracted = ProgramCacheForTxBatch::new(23);
2087 cache.extract(&mut missing, &mut extracted, &env, true, true);
2088 assert!(match_slot(&extracted, &program1, 0, 23));
2089 assert!(match_slot(&extracted, &program2, 11, 23));
2090 assert!(match_slot(&extracted, &program4, 15, 23));
2091 }
2092
2093 #[test]
2094 fn test_extract_using_deployment_slot() {
2095 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
2096 let env = get_mock_program_runtime_environment();
2097
2098 let mut fork_graph = TestForkGraphSpecific::default();
2114 fork_graph.insert_fork(&[0, 10, 20, 22]);
2115 fork_graph.insert_fork(&[0, 5, 11, 12, 15, 16, 18, 19, 21, 23]);
2116 fork_graph.insert_fork(&[0, 5, 11, 25, 27]);
2117
2118 let fork_graph = Arc::new(RwLock::new(fork_graph));
2119 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2120
2121 let program1 = Pubkey::new_unique();
2122 cache.assign_program(&env, program1, 0, new_test_entry(0));
2123 cache.assign_program(&env, program1, 20, new_test_entry(20));
2124
2125 let program2 = Pubkey::new_unique();
2126 cache.assign_program(&env, program2, 5, new_test_entry(5));
2127 cache.assign_program(&env, program2, 11, new_test_entry(11));
2128
2129 let program3 = Pubkey::new_unique();
2130 cache.assign_program(&env, program3, 25, new_test_entry(25));
2131
2132 let keys = &[program1, program2, program3];
2134 let mut missing = get_entries_to_load(&cache, 12, keys);
2135 assert!(match_missing(&missing, &program3, false));
2136 let mut extracted = ProgramCacheForTxBatch::new(12);
2137 cache.extract(&mut missing, &mut extracted, &env, true, true);
2138 assert!(match_slot(&extracted, &program1, 0, 12));
2139 assert!(match_slot(&extracted, &program2, 11, 12));
2140
2141 let mut missing = get_entries_to_load(&cache, 12, keys);
2143 missing.get_mut(0).unwrap().match_criteria =
2144 ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(5);
2145 missing.get_mut(1).unwrap().match_criteria =
2146 ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(5);
2147 assert!(match_missing(&missing, &program3, false));
2148 let mut extracted = ProgramCacheForTxBatch::new(12);
2149 cache.extract(&mut missing, &mut extracted, &env, true, true);
2150 assert!(match_missing(&missing, &program1, true));
2151 assert!(match_slot(&extracted, &program2, 11, 12));
2152 }
2153
2154 #[test]
2155 fn test_extract_unloaded() {
2156 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
2157 let env = get_mock_program_runtime_environment();
2158
2159 let mut fork_graph = TestForkGraphSpecific::default();
2175 fork_graph.insert_fork(&[0, 10, 20, 22]);
2176 fork_graph.insert_fork(&[0, 5, 11, 15, 16, 19, 21, 23]);
2177 fork_graph.insert_fork(&[0, 5, 11, 25, 27]);
2178
2179 let fork_graph = Arc::new(RwLock::new(fork_graph));
2180 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2181
2182 let program1 = Pubkey::new_unique();
2183 cache.assign_program(&env, program1, 0, new_test_entry(0));
2184 cache.assign_program(&env, program1, 20, new_test_entry(20));
2185
2186 let program2 = Pubkey::new_unique();
2187 cache.assign_program(&env, program2, 5, new_test_entry(5));
2188 cache.assign_program(&env, program2, 11, new_test_entry(11));
2189
2190 let program3 = Pubkey::new_unique();
2191 let _ = insert_unloaded_entry(&mut cache, program3, 25);
2193
2194 cache.assign_program(
2198 &env,
2199 program3,
2200 20,
2201 Arc::new(
2202 new_test_entry(20)
2203 .to_unloaded()
2204 .expect("Failed to create unloaded program"),
2205 ),
2206 );
2207
2208 let keys = &[program1, program2, program3];
2210 let mut missing = get_entries_to_load(&cache, 19, keys);
2211 assert!(match_missing(&missing, &program3, false));
2212 let mut extracted = ProgramCacheForTxBatch::new(19);
2213 cache.extract(&mut missing, &mut extracted, &env, true, true);
2214 assert!(match_slot(&extracted, &program1, 0, 19));
2215 assert!(match_slot(&extracted, &program2, 11, 19));
2216
2217 let mut missing = get_entries_to_load(&cache, 27, keys);
2219 let mut extracted = ProgramCacheForTxBatch::new(27);
2220 cache.extract(&mut missing, &mut extracted, &env, true, true);
2221 assert!(match_slot(&extracted, &program1, 0, 27));
2222 assert!(match_slot(&extracted, &program2, 11, 27));
2223 assert!(match_missing(&missing, &program3, true));
2224
2225 let mut missing = get_entries_to_load(&cache, 22, keys);
2227 assert!(match_missing(&missing, &program2, false));
2228 let mut extracted = ProgramCacheForTxBatch::new(22);
2229 cache.extract(&mut missing, &mut extracted, &env, true, true);
2230 assert!(match_slot(&extracted, &program1, 20, 22));
2231 assert!(match_missing(&missing, &program3, true));
2232 }
2233
2234 #[test]
2235 fn test_extract_different_environment() {
2236 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
2237 let env = get_mock_program_runtime_environment();
2238 let other_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
2239
2240 let mut fork_graph = TestForkGraphSpecific::default();
2250 fork_graph.insert_fork(&[0, 10, 20, 22]);
2251
2252 let fork_graph = Arc::new(RwLock::new(fork_graph));
2253 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2254
2255 let program1 = Pubkey::new_unique();
2256 cache.assign_program(
2257 &env,
2258 program1,
2259 10,
2260 Arc::new(ProgramCacheEntry::new_closed_tombstone(
2261 10,
2262 ProgramCacheEntryOwner::LoaderV3,
2263 )),
2264 );
2265 cache.assign_program(&env, program1, 20, new_test_entry(20));
2266
2267 let keys = &[program1];
2269 let mut missing = get_entries_to_load(&cache, 22, keys);
2270 let mut extracted = ProgramCacheForTxBatch::new(22);
2271 cache.extract(&mut missing, &mut extracted, &env, true, true);
2272 assert!(match_slot(&extracted, &program1, 20, 22));
2273
2274 let mut missing = get_entries_to_load(&cache, 22, keys);
2276 let mut extracted = ProgramCacheForTxBatch::new(22);
2277 cache.extract(&mut missing, &mut extracted, &other_env, true, true);
2278 assert!(match_missing(&missing, &program1, true));
2279 }
2280
2281 #[test]
2282 fn test_extract_nonexistent() {
2283 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
2284 let env = get_mock_program_runtime_environment();
2285 let fork_graph = TestForkGraphSpecific::default();
2286 let fork_graph = Arc::new(RwLock::new(fork_graph));
2287 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2288
2289 let program1 = Pubkey::new_unique();
2290 let mut missing = vec![ProgramToLoad {
2291 program_id: &program1,
2292 loader: ProgramCacheEntryOwner::LoaderV3,
2293 match_criteria: ProgramCacheMatchCriteria::NoCriteria,
2294 last_modification_slot: 0,
2295 }];
2296 let mut extracted = ProgramCacheForTxBatch::new(0);
2297 cache.extract(&mut missing, &mut extracted, &env, true, true);
2298 assert!(match_missing(&missing, &program1, true));
2299 }
2300
2301 #[test]
2302 fn test_unloaded() {
2303 let mut cache = ProgramCache::<TestForkGraph>::new(0);
2304 let env = get_mock_program_runtime_environment();
2305 for program_cache_entry_type in [
2306 ProgramCacheEntryType::Closed,
2307 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
2308 ] {
2309 let entry = Arc::new(ProgramCacheEntry {
2310 program: program_cache_entry_type,
2311 account_owner: ProgramCacheEntryOwner::LoaderV2,
2312 deployment_slot: 0,
2313 stats: Arc::default(),
2314 latest_access_slot: AtomicU64::default(),
2315 });
2316 assert!(entry.to_unloaded().is_none());
2317
2318 let program_id = Pubkey::new_unique();
2320 cache.assign_program(&env, program_id, entry.deployment_slot, entry.clone());
2321 cache.unload_program_entry(program_id, entry.deployment_slot, &entry);
2322 assert_eq!(cache.get_slot_versions_for_tests(&program_id).len(), 1);
2323 assert!(cache.stats.evictions.is_empty());
2324 }
2325
2326 let stats = ProgramStatistics {
2327 uses: 3.into(),
2328 ..Default::default()
2329 };
2330 let entry = new_test_entry_with_usage(1, stats);
2331 let unloaded_entry = entry.to_unloaded().unwrap();
2332 assert_eq!(unloaded_entry.deployment_slot, 1);
2333 assert_eq!(unloaded_entry.effective_slot(), 2);
2334 assert_eq!(unloaded_entry.latest_access_slot.load(Ordering::Relaxed), 1);
2335 assert_eq!(unloaded_entry.stats.uses.load(Ordering::Relaxed), 3);
2336
2337 let program_id = Pubkey::new_unique();
2339 cache.assign_program(&env, program_id, entry.deployment_slot, entry.clone());
2340 cache.unload_program_entry(program_id, entry.deployment_slot, &entry);
2341 assert!(cache.stats.evictions.contains_key(&program_id));
2342 }
2343
2344 #[test]
2345 fn test_fork_prune_find_first_ancestor() {
2346 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
2347 let env = get_mock_program_runtime_environment();
2348
2349 let mut fork_graph = TestForkGraphSpecific::default();
2360 fork_graph.insert_fork(&[0, 10, 20]);
2361 fork_graph.insert_fork(&[0, 5]);
2362 let fork_graph = Arc::new(RwLock::new(fork_graph));
2363 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2364
2365 let program1 = Pubkey::new_unique();
2366 cache.assign_program(&env, program1, 0, new_test_entry(0));
2367 cache.assign_program(&env, program1, 5, new_test_entry(5));
2368
2369 cache.prune(10, None, &fork_graph.read().unwrap());
2370
2371 let keys = &[program1];
2372 let mut missing = get_entries_to_load(&cache, 20, keys);
2373 let mut extracted = ProgramCacheForTxBatch::new(20);
2374 cache.extract(&mut missing, &mut extracted, &env, true, true);
2375
2376 assert_eq!(
2378 extracted
2379 .find(&program1)
2380 .expect("Did not find the program")
2381 .deployment_slot,
2382 0
2383 );
2384 }
2385
2386 #[test]
2387 fn test_prune_by_deployment_slot() {
2388 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
2389 let env = get_mock_program_runtime_environment();
2390
2391 let mut fork_graph = TestForkGraphSpecific::default();
2402 fork_graph.insert_fork(&[0, 10, 20]);
2403 fork_graph.insert_fork(&[0, 5, 6]);
2404 let fork_graph = Arc::new(RwLock::new(fork_graph));
2405 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2406
2407 let program1 = Pubkey::new_unique();
2408 cache.assign_program(&env, program1, 0, new_test_entry(0));
2409 cache.assign_program(&env, program1, 5, new_test_entry(5));
2410
2411 let program2 = Pubkey::new_unique();
2412 cache.assign_program(&env, program2, 10, new_test_entry(10));
2413
2414 let keys = &[program1, program2];
2415 let mut missing = get_entries_to_load(&cache, 20, keys);
2416 let mut extracted = ProgramCacheForTxBatch::new(20);
2417 cache.extract(&mut missing, &mut extracted, &env, true, true);
2418 assert!(match_slot(&extracted, &program1, 0, 20));
2419 assert!(match_slot(&extracted, &program2, 10, 20));
2420
2421 let mut missing = get_entries_to_load(&cache, 6, keys);
2422 assert!(match_missing(&missing, &program2, false));
2423 let mut extracted = ProgramCacheForTxBatch::new(6);
2424 cache.extract(&mut missing, &mut extracted, &env, true, true);
2425 assert!(match_slot(&extracted, &program1, 5, 6));
2426
2427 cache.prune_by_deployment_slot(5);
2430
2431 let mut missing = get_entries_to_load(&cache, 20, keys);
2432 let mut extracted = ProgramCacheForTxBatch::new(20);
2433 cache.extract(&mut missing, &mut extracted, &env, true, true);
2434 assert!(match_slot(&extracted, &program1, 0, 20));
2435 assert!(match_slot(&extracted, &program2, 10, 20));
2436
2437 let mut missing = get_entries_to_load(&cache, 6, keys);
2438 assert!(match_missing(&missing, &program2, false));
2439 let mut extracted = ProgramCacheForTxBatch::new(6);
2440 cache.extract(&mut missing, &mut extracted, &env, true, true);
2441 assert!(match_slot(&extracted, &program1, 0, 6));
2442
2443 cache.prune_by_deployment_slot(10);
2446
2447 let mut missing = get_entries_to_load(&cache, 20, keys);
2448 assert!(match_missing(&missing, &program2, false));
2449 let mut extracted = ProgramCacheForTxBatch::new(20);
2450 cache.extract(&mut missing, &mut extracted, &env, true, true);
2451 assert!(match_slot(&extracted, &program1, 0, 20));
2452 }
2453
2454 #[test]
2455 fn test_usable_entries_for_slot() {
2456 ProgramCache::<TestForkGraph>::new(0);
2457 let tombstone = Arc::new(ProgramCacheEntry::new_closed_tombstone(
2458 0,
2459 ProgramCacheEntryOwner::LoaderV2,
2460 ));
2461
2462 assert!(ProgramCache::<TestForkGraph>::matches_criteria(
2463 &tombstone,
2464 &ProgramCacheMatchCriteria::NoCriteria
2465 ));
2466
2467 assert!(ProgramCache::<TestForkGraph>::matches_criteria(
2468 &tombstone,
2469 &ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(0)
2470 ));
2471
2472 assert!(!ProgramCache::<TestForkGraph>::matches_criteria(
2473 &tombstone,
2474 &ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(1)
2475 ));
2476
2477 let program = new_test_entry(0);
2478
2479 assert!(ProgramCache::<TestForkGraph>::matches_criteria(
2480 &program,
2481 &ProgramCacheMatchCriteria::NoCriteria
2482 ));
2483
2484 assert!(ProgramCache::<TestForkGraph>::matches_criteria(
2485 &program,
2486 &ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(0)
2487 ));
2488
2489 assert!(!ProgramCache::<TestForkGraph>::matches_criteria(
2490 &program,
2491 &ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(1)
2492 ));
2493
2494 let program = Arc::new(new_test_entry_with_usage(0, ProgramStatistics::default()));
2495
2496 assert!(ProgramCache::<TestForkGraph>::matches_criteria(
2497 &program,
2498 &ProgramCacheMatchCriteria::NoCriteria
2499 ));
2500
2501 assert!(ProgramCache::<TestForkGraph>::matches_criteria(
2502 &program,
2503 &ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(0)
2504 ));
2505
2506 assert!(!ProgramCache::<TestForkGraph>::matches_criteria(
2507 &program,
2508 &ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(1)
2509 ));
2510 }
2511}