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 percentage::PercentageInteger,
12 solana_clock::{Epoch, Slot},
13 solana_pubkey::Pubkey,
14 solana_sbpf::program::BuiltinProgram,
15 solana_svm_type_overrides::{
16 rand::{Rng, rng},
17 sync::{Arc, Mutex, RwLock, atomic::Ordering},
18 thread,
19 },
20 std::{
21 collections::{HashMap, hash_map::Entry},
22 sync::Weak,
23 },
24};
25
26#[repr(transparent)]
27#[derive(Clone, Debug)]
28pub struct ProgramRuntimeEnvironment(Arc<BuiltinProgram<InvokeContext<'static, 'static>>>);
29impl std::hash::Hash for ProgramRuntimeEnvironment {
30 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
31 Arc::<BuiltinProgram<InvokeContext<'static, 'static>>>::as_ptr(&self.0).hash(state);
32 }
33}
34impl PartialEq for ProgramRuntimeEnvironment {
35 fn eq(&self, other: &Self) -> bool {
36 Arc::ptr_eq(&self.0, &other.0)
37 }
38}
39impl Eq for ProgramRuntimeEnvironment {}
40impl std::ops::Deref for ProgramRuntimeEnvironment {
41 type Target = Arc<BuiltinProgram<InvokeContext<'static, 'static>>>;
42
43 fn deref(&self) -> &Self::Target {
44 &self.0
45 }
46}
47impl ProgramRuntimeEnvironment {
48 pub fn from(inner: BuiltinProgram<InvokeContext<'static, 'static>>) -> Self {
49 Self(Arc::new(inner))
50 }
51
52 pub const fn from_ref<'a>(
53 inner: &'a Arc<BuiltinProgram<InvokeContext<'static, 'static>>>,
54 ) -> &'a Self {
55 unsafe { std::mem::transmute(inner) }
57 }
58}
59
60pub struct ProgramRuntimeEnvironments {
68 execution: ProgramRuntimeEnvironment,
71 deployment: ProgramRuntimeEnvironment,
74}
75
76impl ProgramRuntimeEnvironments {
77 pub fn new(
80 execution: ProgramRuntimeEnvironment,
81 deployment: ProgramRuntimeEnvironment,
82 ) -> Self {
83 Self {
84 execution,
85 deployment,
86 }
87 }
88
89 pub fn get_env_for_execution(&self) -> &ProgramRuntimeEnvironment {
91 &self.execution
92 }
93
94 pub fn get_env_for_deployment(&self) -> &ProgramRuntimeEnvironment {
96 &self.deployment
97 }
98
99 #[cfg(feature = "dev-context-only-utils")]
100 pub fn mock() -> Self {
101 Self {
102 execution: get_mock_program_runtime_environment(),
103 deployment: get_mock_program_runtime_environment(),
104 }
105 }
106}
107
108#[cfg(feature = "dev-context-only-utils")]
109pub fn get_mock_program_runtime_environment() -> ProgramRuntimeEnvironment {
110 static MOCK_ENVIRONMENT: std::sync::OnceLock<ProgramRuntimeEnvironment> =
111 std::sync::OnceLock::<ProgramRuntimeEnvironment>::new();
112 MOCK_ENVIRONMENT
113 .get_or_init(|| ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock()))
114 .clone()
115}
116
117pub const MAX_LOADED_ENTRY_COUNT: usize = 512;
118
119#[derive(Copy, Clone, Debug, PartialEq)]
121pub enum BlockRelation {
122 Ancestor,
124 Equal,
126 Descendant,
128 Unrelated,
130 Unknown,
132}
133
134pub trait ForkGraph {
136 fn relationship(&self, a: Slot, b: Slot) -> BlockRelation;
138}
139
140#[derive(Debug, Default)]
142pub struct EpochBoundaryPreparation {
143 pub upcoming_epoch: Epoch,
145 pub upcoming_environment: Option<ProgramRuntimeEnvironment>,
151 pub programs_to_recompile: Vec<(Pubkey, Arc<ProgramCacheEntry>)>,
153}
154
155impl EpochBoundaryPreparation {
156 pub fn new(epoch: Epoch) -> Self {
157 Self {
158 upcoming_epoch: epoch,
159 upcoming_environment: None,
160 programs_to_recompile: Vec::default(),
161 }
162 }
163
164 pub fn get_upcoming_environment_for_epoch(
166 &self,
167 epoch: Epoch,
168 ) -> Option<ProgramRuntimeEnvironment> {
169 if epoch == self.upcoming_epoch {
170 return self.upcoming_environment.clone();
171 }
172 None
173 }
174
175 pub fn reroot(&mut self, epoch: Epoch) -> Option<ProgramRuntimeEnvironment> {
177 if epoch == self.upcoming_epoch
178 && let Some(upcoming_environment) = self.upcoming_environment.take()
179 {
180 self.programs_to_recompile.clear();
181 return Some(upcoming_environment);
182 }
183
184 None
185 }
186}
187
188#[derive(Clone, PartialEq, Debug)]
190pub struct ProgramToLoad<'a> {
191 pub program_id: &'a Pubkey,
193 pub loader: ProgramCacheEntryOwner,
195 pub match_criteria: ProgramCacheMatchCriteria,
197 pub last_modification_slot: Slot,
199}
200
201#[derive(Debug)]
202pub(crate) enum IndexImplementation {
203 V1 {
205 entries: HashMap<Pubkey, Vec<Arc<ProgramCacheEntry>>>,
210 loading_entries: Mutex<HashMap<Pubkey, (Slot, thread::ThreadId)>>,
219 },
220}
221
222pub struct ProgramCache<FG: ForkGraph> {
238 pub(crate) index: IndexImplementation,
240 pub latest_root_slot: Slot,
242 pub stats: ProgramCacheStats,
244 pub fork_graph: Option<Weak<RwLock<FG>>>,
246 pub loading_task_waiter: Arc<LoadingTaskWaiter>,
248}
249
250impl<FG: ForkGraph> std::fmt::Debug for ProgramCache<FG> {
251 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
252 f.debug_struct("ProgramCache")
253 .field("root slot", &self.latest_root_slot)
254 .field("stats", &self.stats)
255 .field("index", &self.index)
256 .finish()
257 }
258}
259
260#[derive(Clone, Debug, Default)]
266pub struct ProgramCacheForTxBatch {
267 entries: HashMap<Pubkey, Arc<ProgramCacheEntry>>,
270 modified_entries: HashMap<Pubkey, Arc<ProgramCacheEntry>>,
272 slot: Slot,
273 pub hit_max_limit: bool,
274 pub loaded_missing: bool,
275 pub merged_modified: bool,
276}
277
278impl ProgramCacheForTxBatch {
279 pub fn new(slot: Slot) -> Self {
280 Self {
281 entries: HashMap::new(),
282 modified_entries: HashMap::new(),
283 slot,
284 hit_max_limit: false,
285 loaded_missing: false,
286 merged_modified: false,
287 }
288 }
289
290 pub fn replenish(
296 &mut self,
297 key: Pubkey,
298 entry: Arc<ProgramCacheEntry>,
299 ) -> (bool, Arc<ProgramCacheEntry>) {
300 (self.entries.insert(key, entry.clone()).is_some(), entry)
301 }
302
303 pub fn store_modified_entry(&mut self, key: Pubkey, entry: Arc<ProgramCacheEntry>) {
306 self.modified_entries.insert(key, entry);
307 }
308
309 pub fn drain_modified_entries(&mut self) -> HashMap<Pubkey, Arc<ProgramCacheEntry>> {
312 std::mem::take(&mut self.modified_entries)
313 }
314
315 pub fn find(&self, key: &Pubkey) -> Option<Arc<ProgramCacheEntry>> {
316 self.modified_entries
320 .get(key)
321 .or_else(|| self.entries.get(key))
322 .map(|entry| {
323 if entry.is_implicit_delay_visibility_tombstone(self.slot) {
324 Arc::new(ProgramCacheEntry::new_tombstone_with_stats(
328 entry.deployment_slot,
329 entry.account_owner,
330 ProgramCacheEntryType::DelayVisibility,
331 Arc::clone(&entry.stats),
332 ))
333 } else {
334 entry.clone()
335 }
336 })
337 }
338
339 pub fn slot(&self) -> Slot {
340 self.slot
341 }
342
343 pub fn set_slot_for_tests(&mut self, slot: Slot) {
344 self.slot = slot;
345 }
346
347 pub fn merge(&mut self, modified_entries: &HashMap<Pubkey, Arc<ProgramCacheEntry>>) {
348 modified_entries.iter().for_each(|(key, entry)| {
349 self.merged_modified = true;
350 self.replenish(*key, entry.clone());
351 })
352 }
353
354 pub fn is_empty(&self) -> bool {
355 self.entries.is_empty()
356 }
357}
358
359#[derive(Clone, PartialEq, Debug)]
360pub enum ProgramCacheMatchCriteria {
361 DeployedOnOrAfterSlot(Slot),
362 Tombstone,
363 NoCriteria,
364}
365
366impl<FG: ForkGraph> ProgramCache<FG> {
367 pub fn new(root_slot: Slot) -> Self {
368 Self {
369 index: IndexImplementation::V1 {
370 entries: HashMap::new(),
371 loading_entries: Mutex::new(HashMap::new()),
372 },
373 latest_root_slot: root_slot,
374 stats: ProgramCacheStats::default(),
375 fork_graph: None,
376 loading_task_waiter: Arc::new(LoadingTaskWaiter::default()),
377 }
378 }
379
380 pub fn set_fork_graph(&mut self, fork_graph: Weak<RwLock<FG>>) {
381 self.fork_graph = Some(fork_graph);
382 }
383
384 pub fn assign_program(
387 &mut self,
388 program_runtime_environment: &ProgramRuntimeEnvironment,
389 key: Pubkey,
390 _last_modification_slot: Slot,
391 entry: Arc<ProgramCacheEntry>,
392 ) -> bool {
393 debug_assert!(!matches!(
394 &entry.program,
395 ProgramCacheEntryType::DelayVisibility
396 ));
397 fn is_current_env(
401 program_runtime_environment: &ProgramRuntimeEnvironment,
402 env_opt: Option<&ProgramRuntimeEnvironment>,
403 ) -> bool {
404 env_opt
405 .map(|env| env == program_runtime_environment)
406 .unwrap_or(true)
407 }
408 match &mut self.index {
409 IndexImplementation::V1 { entries, .. } => {
410 let slot_versions = &mut entries.entry(key).or_default();
411 let insertion_point = slot_versions.binary_search_by(|at| {
412 at.effective_slot
413 .cmp(&entry.effective_slot)
414 .then(at.deployment_slot.cmp(&entry.deployment_slot))
415 .then(at.account_owner.cmp(&entry.account_owner))
416 .then(
417 is_current_env(
421 program_runtime_environment,
422 at.program.get_environment(),
423 )
424 .cmp(&is_current_env(
425 program_runtime_environment,
426 entry.program.get_environment(),
427 )),
428 )
429 });
430 match insertion_point {
431 Ok(index) => {
432 let existing = slot_versions.get_mut(index).unwrap();
433 match (&existing.program, &entry.program) {
434 (
435 ProgramCacheEntryType::Builtin(_),
436 ProgramCacheEntryType::Builtin(_),
437 )
438 | (
439 ProgramCacheEntryType::Unloaded(_),
440 ProgramCacheEntryType::Loaded(_),
441 ) => {}
442 _ => {
443 error!(
445 "ProgramCache::assign_program() failed key={key:?} \
446 existing={slot_versions:?} entry={entry:?}"
447 );
448 debug_assert!(false, "Unexpected replacement of an entry");
449 self.stats.replacements.fetch_add(1, Ordering::Relaxed);
450 return true;
451 }
452 }
453 entry.stats.merge_from(&existing.stats);
454 *existing = Arc::clone(&entry);
455 self.stats.reloads.fetch_add(1, Ordering::Relaxed);
456 }
457 Err(index) => {
458 self.stats.insertions.fetch_add(1, Ordering::Relaxed);
459 slot_versions.insert(index, Arc::clone(&entry));
460 }
461 }
462 slot_versions.retain(|existing| {
466 existing.deployment_slot != entry.deployment_slot
467 || existing
468 .program
469 .get_environment()
470 .zip(entry.program.get_environment())
471 .map(|(a, b)| a != b)
472 .unwrap_or(false)
473 || existing == &entry
474 });
475 }
476 }
477 false
478 }
479
480 pub fn prune_by_deployment_slot(&mut self, slot: Slot) {
481 match &mut self.index {
482 IndexImplementation::V1 { entries, .. } => {
483 for second_level in entries.values_mut() {
484 second_level.retain(|entry| entry.deployment_slot != slot);
485 }
486 self.remove_programs_with_no_entries();
487 }
488 }
489 }
490
491 pub fn prune(
493 &mut self,
494 new_root_slot: Slot,
495 new_environment: Option<ProgramRuntimeEnvironment>,
496 fork_graph: &FG,
497 ) {
498 match &mut self.index {
499 IndexImplementation::V1 { entries, .. } => {
500 for second_level in entries.values_mut() {
501 let mut first_ancestor_found = false;
503 let mut first_ancestor_env = None;
504 *second_level = second_level
505 .iter()
506 .rev()
507 .filter(|entry| {
508 let relation =
509 fork_graph.relationship(entry.deployment_slot, new_root_slot);
510 if entry.deployment_slot >= new_root_slot {
511 matches!(relation, BlockRelation::Equal | BlockRelation::Descendant)
512 } else if matches!(relation, BlockRelation::Ancestor)
513 || entry.deployment_slot <= self.latest_root_slot
514 {
515 if !first_ancestor_found {
516 first_ancestor_found = true;
517 first_ancestor_env = entry.program.get_environment();
518 return true;
519 }
520 if let Some(entry_env) = entry.program.get_environment()
528 && let Some(env) = first_ancestor_env
529 && entry_env != env
530 {
531 return true;
532 }
533 self.stats.prunes_orphan.fetch_add(1, Ordering::Relaxed);
534 false
535 } else {
536 self.stats.prunes_orphan.fetch_add(1, Ordering::Relaxed);
537 false
538 }
539 })
540 .filter(|entry| {
541 if let Some(new_environment) = new_environment.as_ref()
543 && !Self::matches_environment(entry, new_environment)
544 {
545 self.stats
546 .prunes_environment
547 .fetch_add(1, Ordering::Relaxed);
548 return false;
549 }
550 true
551 })
552 .cloned()
553 .collect();
554 second_level.reverse();
555 }
556 }
557 }
558 self.remove_programs_with_no_entries();
559 debug_assert!(self.latest_root_slot <= new_root_slot);
560 self.latest_root_slot = new_root_slot;
561 }
562
563 fn matches_environment(
564 entry: &Arc<ProgramCacheEntry>,
565 program_runtime_environment: &ProgramRuntimeEnvironment,
566 ) -> bool {
567 let Some(environment) = entry.program.get_environment() else {
568 return true;
569 };
570 environment == program_runtime_environment
571 }
572
573 fn matches_criteria(
574 program: &Arc<ProgramCacheEntry>,
575 criteria: &ProgramCacheMatchCriteria,
576 ) -> bool {
577 match criteria {
578 ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(slot) => {
579 program.deployment_slot >= *slot
580 }
581 ProgramCacheMatchCriteria::Tombstone => program.is_tombstone(),
582 ProgramCacheMatchCriteria::NoCriteria => true,
583 }
584 }
585
586 pub fn extract(
589 &self,
590 search_for: &mut Vec<ProgramToLoad>,
591 loaded_programs_for_tx_batch: &mut ProgramCacheForTxBatch,
592 program_runtime_environment_for_execution: &ProgramRuntimeEnvironment,
593 increment_usage_counter: bool,
594 count_hits_and_misses: bool,
595 ) -> Option<Pubkey> {
596 debug_assert!(self.fork_graph.is_some());
597 let fork_graph = self.fork_graph.as_ref().unwrap().upgrade().unwrap();
598 let locked_fork_graph = fork_graph.read().unwrap();
599 let mut cooperative_loading_task = None;
600 match &self.index {
601 IndexImplementation::V1 {
602 entries,
603 loading_entries,
604 } => {
605 search_for.retain(|program_to_load| {
606 if let Some(second_level) = entries.get(program_to_load.program_id) {
607 let mut filter_by_deployment_slot = None;
608 for entry in second_level.iter().rev() {
609 let required_deployment_slot =
610 filter_by_deployment_slot.unwrap_or(entry.deployment_slot);
611 if required_deployment_slot != entry.deployment_slot
612 || program_to_load.loader != entry.account_owner
613 {
614 continue;
615 }
616 let entry_in_same_branch = entry.deployment_slot
617 <= self.latest_root_slot
618 || matches!(
619 locked_fork_graph.relationship(
620 entry.deployment_slot,
621 loaded_programs_for_tx_batch.slot
622 ),
623 BlockRelation::Equal | BlockRelation::Ancestor
624 );
625 if entry_in_same_branch {
626 let entry_is_effective =
627 loaded_programs_for_tx_batch.slot >= entry.effective_slot;
628 let entry_to_return = if entry_is_effective {
629 if !Self::matches_environment(
630 entry,
631 program_runtime_environment_for_execution,
632 ) {
633 filter_by_deployment_slot = filter_by_deployment_slot
642 .or(Some(entry.deployment_slot));
643 continue;
644 }
645 if !Self::matches_criteria(
646 entry,
647 &program_to_load.match_criteria,
648 ) {
649 break;
650 }
651 if let ProgramCacheEntryType::Unloaded(_environment) =
652 &entry.program
653 {
654 break;
655 }
656 entry.clone()
657 } else if entry.is_implicit_delay_visibility_tombstone(
658 loaded_programs_for_tx_batch.slot,
659 ) {
660 Arc::new(ProgramCacheEntry::new_tombstone_with_stats(
664 entry.deployment_slot,
665 entry.account_owner,
666 ProgramCacheEntryType::DelayVisibility,
667 Arc::clone(&entry.stats),
668 ))
669 } else {
670 continue;
671 };
672 entry_to_return
673 .update_access_slot(loaded_programs_for_tx_batch.slot);
674 if increment_usage_counter {
675 entry_to_return.stats.uses.fetch_add(1, Ordering::Relaxed);
676 }
677 loaded_programs_for_tx_batch
678 .entries
679 .insert(*program_to_load.program_id, entry_to_return);
680 return false;
681 }
682 }
683 }
684 if cooperative_loading_task.is_none() {
685 let mut loading_entries = loading_entries.lock().unwrap();
686 let entry = loading_entries.entry(*program_to_load.program_id);
687 if let Entry::Vacant(entry) = entry {
688 entry.insert((
689 loaded_programs_for_tx_batch.slot,
690 thread::current().id(),
691 ));
692 cooperative_loading_task = Some(*program_to_load.program_id);
693 }
694 }
695 true
696 });
697 }
698 }
699 drop(locked_fork_graph);
700 if count_hits_and_misses {
701 self.stats
702 .misses
703 .fetch_add(search_for.len() as u64, Ordering::Relaxed);
704 self.stats.hits.fetch_add(
705 loaded_programs_for_tx_batch.entries.len() as u64,
706 Ordering::Relaxed,
707 );
708 }
709 cooperative_loading_task
710 }
711
712 pub fn finish_cooperative_loading_task(
714 &mut self,
715 program_runtime_environment: &ProgramRuntimeEnvironment,
716 current_slot: Slot,
717 key: Pubkey,
718 last_modification_slot: Slot,
719 loaded_program: Arc<ProgramCacheEntry>,
720 ) -> bool {
721 match &mut self.index {
722 IndexImplementation::V1 {
723 loading_entries, ..
724 } => {
725 let loading_thread = loading_entries.get_mut().unwrap().remove(&key);
726 debug_assert_eq!(loading_thread, Some((current_slot, thread::current().id())));
727 if loaded_program.deployment_slot > self.latest_root_slot
729 && !matches!(
730 self.fork_graph
731 .as_ref()
732 .unwrap()
733 .upgrade()
734 .unwrap()
735 .read()
736 .unwrap()
737 .relationship(loaded_program.deployment_slot, current_slot),
738 BlockRelation::Equal | BlockRelation::Ancestor
739 )
740 {
741 self.stats.lost_insertions.fetch_add(1, Ordering::Relaxed);
742 }
743 let was_occupied = self.assign_program(
744 program_runtime_environment,
745 key,
746 last_modification_slot,
747 loaded_program,
748 );
749 self.loading_task_waiter.notify();
750 was_occupied
751 }
752 }
753 }
754
755 pub fn merge(
756 &mut self,
757 program_runtime_environment: &ProgramRuntimeEnvironment,
758 current_slot: Slot,
759 modified_entries: &HashMap<Pubkey, Arc<ProgramCacheEntry>>,
760 ) {
761 modified_entries.iter().for_each(|(key, entry)| {
762 self.assign_program(
763 program_runtime_environment,
764 *key,
765 current_slot,
766 entry.clone(),
767 );
768 })
769 }
770
771 pub fn get_flattened_entries(&self) -> Vec<(Pubkey, Slot, Arc<ProgramCacheEntry>)> {
773 match &self.index {
774 IndexImplementation::V1 { entries, .. } => entries
775 .iter()
776 .flat_map(|(id, second_level)| {
777 second_level
778 .iter()
779 .filter_map(move |program| match program.program {
780 ProgramCacheEntryType::Loaded(_) => Some((*id, 0, program.clone())),
781 _ => None,
782 })
783 })
784 .collect(),
785 }
786 }
787
788 #[cfg(feature = "dev-context-only-utils")]
790 pub fn get_flattened_entries_for_tests(&self) -> Vec<(Pubkey, Arc<ProgramCacheEntry>)> {
791 match &self.index {
792 IndexImplementation::V1 { entries, .. } => entries
793 .iter()
794 .flat_map(|(id, second_level)| {
795 second_level.iter().map(|program| (*id, program.clone()))
796 })
797 .collect(),
798 }
799 }
800
801 pub fn get_slot_versions_for_tests(&self, key: &Pubkey) -> &[Arc<ProgramCacheEntry>] {
803 match &self.index {
804 IndexImplementation::V1 { entries, .. } => entries
805 .get(key)
806 .map(|second_level| second_level.as_ref())
807 .unwrap_or(&[]),
808 }
809 }
810
811 pub fn sort_and_unload(&mut self, shrink_to: PercentageInteger) {
813 let mut sorted_candidates = self.get_flattened_entries();
814 sorted_candidates.sort_by_cached_key(|(_id, _last_modification_slot, program)| {
815 program.stats.uses.load(Ordering::Relaxed)
816 });
817 let num_to_unload = sorted_candidates
818 .len()
819 .saturating_sub(shrink_to.apply_to(MAX_LOADED_ENTRY_COUNT));
820 for (program, last_modification_slot, entry) in sorted_candidates.iter().take(num_to_unload)
821 {
822 self.unload_program_entry(*program, *last_modification_slot, entry);
823 }
824 }
825
826 pub fn evict_using_random_selection(&mut self, shrink_to: PercentageInteger, now: Slot) {
832 let mut candidates = self.get_flattened_entries();
833 let mut rng = rng();
834 self.stats
835 .water_level
836 .store(candidates.len() as u64, Ordering::Relaxed);
837 let num_to_unload = candidates
838 .len()
839 .saturating_sub(shrink_to.apply_to(MAX_LOADED_ENTRY_COUNT));
840 let mut sample_entry = |candidates: &Vec<(Pubkey, u64, Arc<ProgramCacheEntry>)>| {
841 #[cfg(feature = "shuttle-test")]
844 let index = rng.gen_range(0..candidates.len());
845 #[cfg(not(feature = "shuttle-test"))]
846 let index = rng.random_range(0..candidates.len());
847 let usage_counter = candidates
848 .get(index)
849 .expect("Failed to get cached entry")
850 .2
851 .retention_score();
852 (index, usage_counter)
853 };
854
855 const MAX_ADDITIONAL_SAMPLES: usize = 3;
863 let avoid_evicting_above_score = retention_score(now, 500 * EMA_SCALE, 500);
864 for _ in 0..num_to_unload {
865 let (mut index, mut score) = sample_entry(&candidates);
866 for _ in 0..MAX_ADDITIONAL_SAMPLES {
867 let (sample_index, sample_score) = sample_entry(&candidates);
868 if score > sample_score {
869 index = sample_index;
870 score = sample_score;
871 }
872 if score < avoid_evicting_above_score {
873 break;
874 }
875 }
876 let (id, last_modification_slot, entry) = candidates.swap_remove(index);
877 self.unload_program_entry(id, last_modification_slot, &entry);
878 }
879 }
880
881 pub fn remove_programs(&mut self, keys: impl Iterator<Item = Pubkey>) {
883 match &mut self.index {
884 IndexImplementation::V1 { entries, .. } => {
885 for k in keys {
886 entries.remove(&k);
887 }
888 }
889 }
890 }
891
892 fn unload_program_entry(
895 &mut self,
896 id: Pubkey,
897 _last_modification_slot: Slot,
898 remove_entry: &Arc<ProgramCacheEntry>,
899 ) {
900 match &mut self.index {
901 IndexImplementation::V1 { entries, .. } => {
902 let second_level = entries.get_mut(&id).expect("Cache lookup failed");
903 let candidate = second_level
904 .iter_mut()
905 .find(|entry| entry == &remove_entry)
906 .expect("Program entry not found");
907
908 if let Some(unloaded) = candidate.to_unloaded() {
912 if candidate.stats.uses.load(Ordering::Relaxed) == 1 {
913 self.stats.one_hit_wonders.fetch_add(1, Ordering::Relaxed);
914 }
915 self.stats
916 .evictions
917 .entry(id)
918 .and_modify(|c| *c = c.saturating_add(1))
919 .or_insert(1);
920 *candidate = Arc::new(unloaded);
921 }
922 }
923 }
924 }
925
926 fn remove_programs_with_no_entries(&mut self) {
927 match &mut self.index {
928 IndexImplementation::V1 { entries, .. } => {
929 let num_programs_before_removal = entries.len();
930 entries.retain(|_key, second_level| !second_level.is_empty());
931 if entries.len() < num_programs_before_removal {
932 self.stats.empty_entries.fetch_add(
933 num_programs_before_removal.saturating_sub(entries.len()) as u64,
934 Ordering::Relaxed,
935 );
936 }
937 }
938 }
939 }
940}
941
942#[cfg(feature = "frozen-abi")]
943impl solana_frozen_abi::abi_example::AbiExample for ProgramCacheEntry {
944 fn example() -> Self {
945 Self::default()
947 }
948}
949
950#[cfg(feature = "frozen-abi")]
951impl<FG: ForkGraph> solana_frozen_abi::abi_example::AbiExample for ProgramCache<FG> {
952 fn example() -> Self {
953 Self::new(Slot::default())
955 }
956}
957
958#[cfg(test)]
959pub(crate) mod tests {
960 use {
961 crate::{
962 loaded_programs::{
963 BlockRelation, ForkGraph, ProgramCache, ProgramCacheForTxBatch,
964 ProgramCacheMatchCriteria, ProgramRuntimeEnvironment, ProgramToLoad,
965 get_mock_program_runtime_environment,
966 },
967 program_cache_entry::{
968 DELAY_VISIBILITY_SLOT_OFFSET, ProgramCacheEntry, ProgramCacheEntryOwner,
969 ProgramCacheEntryType,
970 },
971 program_metrics::ProgramStatistics,
972 },
973 assert_matches::assert_matches,
974 percentage::Percentage,
975 solana_clock::Slot,
976 solana_pubkey::Pubkey,
977 solana_sbpf::{elf::Executable, program::BuiltinProgram},
978 solana_svm_type_overrides::sync::{
979 Arc, RwLock,
980 atomic::{AtomicU64, Ordering},
981 },
982 std::{fs::File, io::Read, ops::ControlFlow},
983 test_case::{test_case, test_matrix},
984 };
985
986 fn new_test_entry(deployment_slot: Slot, effective_slot: Slot) -> Arc<ProgramCacheEntry> {
987 new_test_entry_with_usage(
988 deployment_slot,
989 effective_slot,
990 ProgramStatistics::default(),
991 )
992 }
993
994 fn new_loaded_entry(env: ProgramRuntimeEnvironment) -> ProgramCacheEntryType {
995 let mut elf = Vec::new();
996 File::open("../programs/bpf_loader/test_elfs/out/noop_aligned.so")
997 .unwrap()
998 .read_to_end(&mut elf)
999 .unwrap();
1000 let executable = Executable::load(&elf, Arc::clone(&*env)).unwrap();
1001 ProgramCacheEntryType::Loaded(executable)
1002 }
1003
1004 pub(crate) fn new_test_entry_with_usage(
1005 deployment_slot: Slot,
1006 effective_slot: Slot,
1007 stats: ProgramStatistics,
1008 ) -> Arc<ProgramCacheEntry> {
1009 Arc::new(ProgramCacheEntry {
1010 program: new_loaded_entry(get_mock_program_runtime_environment()),
1011 account_owner: ProgramCacheEntryOwner::LoaderV2,
1012 account_size: 0,
1013 deployment_slot,
1014 effective_slot,
1015 stats: Arc::new(stats),
1016 latest_access_slot: AtomicU64::new(deployment_slot),
1017 })
1018 }
1019
1020 fn new_test_builtin_entry(
1021 deployment_slot: Slot,
1022 effective_slot: Slot,
1023 ) -> Arc<ProgramCacheEntry> {
1024 Arc::new(ProgramCacheEntry {
1025 program: ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1026 account_owner: ProgramCacheEntryOwner::NativeLoader,
1027 account_size: 0,
1028 deployment_slot,
1029 effective_slot,
1030 stats: Arc::default(),
1031 latest_access_slot: AtomicU64::default(),
1032 })
1033 }
1034
1035 fn set_tombstone<FG: ForkGraph>(
1036 cache: &mut ProgramCache<FG>,
1037 key: Pubkey,
1038 current_slot: Slot,
1039 reason: ProgramCacheEntryType,
1040 ) -> Arc<ProgramCacheEntry> {
1041 let env = get_mock_program_runtime_environment();
1042 let program = Arc::new(ProgramCacheEntry::new_tombstone(
1043 current_slot,
1044 ProgramCacheEntryOwner::LoaderV2,
1045 reason,
1046 ));
1047 cache.assign_program(&env, key, current_slot, program.clone());
1048 program
1049 }
1050
1051 fn insert_unloaded_entry<FG: ForkGraph>(
1052 cache: &mut ProgramCache<FG>,
1053 key: Pubkey,
1054 current_slot: Slot,
1055 ) -> Arc<ProgramCacheEntry> {
1056 let env = get_mock_program_runtime_environment();
1057 let loaded = new_test_entry_with_usage(
1058 current_slot,
1059 current_slot.saturating_add(1),
1060 ProgramStatistics::default(),
1061 );
1062 let unloaded = Arc::new(loaded.to_unloaded().expect("Failed to unload the program"));
1063 cache.assign_program(&env, key, current_slot, unloaded.clone());
1064 unloaded
1065 }
1066
1067 fn num_matching_entries<P, FG>(cache: &ProgramCache<FG>, predicate: P) -> usize
1068 where
1069 P: Fn(&ProgramCacheEntryType) -> bool,
1070 FG: ForkGraph,
1071 {
1072 cache
1073 .get_flattened_entries_for_tests()
1074 .iter()
1075 .filter(|(_key, program)| predicate(&program.program))
1076 .count()
1077 }
1078
1079 fn program_deploy_test_helper(
1080 cache: &mut ProgramCache<TestForkGraph>,
1081 program: Pubkey,
1082 deployment_slots: Vec<Slot>,
1083 usage_counters: Vec<u64>,
1084 programs: &mut Vec<(Pubkey, Slot, u64)>,
1085 ) {
1086 let env = get_mock_program_runtime_environment();
1087 deployment_slots
1089 .iter()
1090 .enumerate()
1091 .for_each(|(i, deployment_slot)| {
1092 let usage_counter = *usage_counters.get(i).unwrap_or(&0);
1093 let stats = ProgramStatistics {
1094 uses: usage_counter.into(),
1095 ..Default::default()
1096 };
1097 cache.assign_program(
1098 &env,
1099 program,
1100 *deployment_slot,
1101 new_test_entry_with_usage(
1102 *deployment_slot,
1103 (*deployment_slot).saturating_add(2),
1104 stats,
1105 ),
1106 );
1107 programs.push((program, *deployment_slot, usage_counter));
1108 });
1109
1110 let env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
1112 for slot in 21..31 {
1113 set_tombstone(
1114 cache,
1115 program,
1116 slot,
1117 ProgramCacheEntryType::FailedVerification(env.clone()),
1118 );
1119 }
1120
1121 for slot in 31..41 {
1123 insert_unloaded_entry(cache, program, slot);
1124 }
1125 }
1126
1127 #[test]
1128 fn test_random_eviction() {
1129 let mut programs = vec![];
1130 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1131
1132 program_deploy_test_helper(
1138 &mut cache,
1139 Pubkey::new_unique(),
1140 vec![0, 10, 20],
1141 vec![4, 5, 25],
1142 &mut programs,
1143 );
1144
1145 program_deploy_test_helper(
1147 &mut cache,
1148 Pubkey::new_unique(),
1149 vec![5, 11],
1150 vec![0, 2],
1151 &mut programs,
1152 );
1153
1154 program_deploy_test_helper(
1156 &mut cache,
1157 Pubkey::new_unique(),
1158 vec![0, 5, 15],
1159 vec![100, 3, 20],
1160 &mut programs,
1161 );
1162
1163 let num_loaded_expected = 8;
1165 let num_unloaded_expected = 30;
1167 let num_tombstones_expected = 30;
1169
1170 programs.sort_by_key(|(_id, _slot, usage_count)| *usage_count);
1172 let num_loaded = num_matching_entries(&cache, |program_type| {
1173 matches!(program_type, ProgramCacheEntryType::Loaded(_))
1174 });
1175 let num_unloaded = num_matching_entries(&cache, |program_type| {
1176 matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1177 });
1178 let num_tombstones = num_matching_entries(&cache, |program_type| {
1179 matches!(
1180 program_type,
1181 ProgramCacheEntryType::DelayVisibility
1182 | ProgramCacheEntryType::FailedVerification(_)
1183 | ProgramCacheEntryType::Closed
1184 )
1185 });
1186
1187 assert_eq!(num_loaded, num_loaded_expected);
1189 assert_eq!(num_unloaded, num_unloaded_expected);
1190 assert_eq!(num_tombstones, num_tombstones_expected);
1191
1192 let eviction_pct = 1;
1194
1195 let num_loaded_expected =
1196 Percentage::from(eviction_pct).apply_to(crate::loaded_programs::MAX_LOADED_ENTRY_COUNT);
1197 let num_unloaded_expected = num_unloaded_expected + num_loaded - num_loaded_expected;
1198 cache.evict_using_random_selection(Percentage::from(eviction_pct), 21);
1199
1200 let num_loaded = num_matching_entries(&cache, |program_type| {
1202 matches!(program_type, ProgramCacheEntryType::Loaded(_))
1203 });
1204 let num_unloaded = num_matching_entries(&cache, |program_type| {
1205 matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1206 });
1207 let num_tombstones = num_matching_entries(&cache, |program_type| {
1208 matches!(program_type, ProgramCacheEntryType::FailedVerification(_))
1209 });
1210
1211 assert_eq!(num_loaded, num_loaded_expected);
1213 assert_eq!(num_unloaded, num_unloaded_expected);
1215 assert_eq!(num_tombstones, num_tombstones_expected);
1217 }
1218
1219 #[test]
1220 fn test_eviction() {
1221 let mut programs = vec![];
1222 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1223
1224 program_deploy_test_helper(
1226 &mut cache,
1227 Pubkey::new_unique(),
1228 vec![0, 10, 20],
1229 vec![4, 5, 25],
1230 &mut programs,
1231 );
1232
1233 program_deploy_test_helper(
1235 &mut cache,
1236 Pubkey::new_unique(),
1237 vec![5, 11],
1238 vec![0, 2],
1239 &mut programs,
1240 );
1241
1242 program_deploy_test_helper(
1244 &mut cache,
1245 Pubkey::new_unique(),
1246 vec![0, 5, 15],
1247 vec![100, 3, 20],
1248 &mut programs,
1249 );
1250
1251 let num_loaded_expected = 8;
1253 let num_unloaded_expected = 30;
1255 let num_tombstones_expected = 30;
1257
1258 programs.sort_by_key(|(_id, _slot, usage_count)| *usage_count);
1260 let num_loaded = num_matching_entries(&cache, |program_type| {
1261 matches!(program_type, ProgramCacheEntryType::Loaded(_))
1262 });
1263 let num_unloaded = num_matching_entries(&cache, |program_type| {
1264 matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1265 });
1266 let num_tombstones = num_matching_entries(&cache, |program_type| {
1267 matches!(program_type, ProgramCacheEntryType::FailedVerification(_))
1268 });
1269
1270 assert_eq!(num_loaded, num_loaded_expected);
1272 assert_eq!(num_unloaded, num_unloaded_expected);
1273 assert_eq!(num_tombstones, num_tombstones_expected);
1274
1275 let eviction_pct = 1;
1277
1278 let num_loaded_expected =
1279 Percentage::from(eviction_pct).apply_to(crate::loaded_programs::MAX_LOADED_ENTRY_COUNT);
1280 let num_unloaded_expected = num_unloaded_expected + num_loaded - num_loaded_expected;
1281
1282 cache.sort_and_unload(Percentage::from(eviction_pct));
1283
1284 let entries = cache.get_flattened_entries_for_tests();
1286 programs.iter().for_each(|entry| {
1287 assert!(entries.iter().any(|(key, _entry)| key == &entry.0));
1288 });
1289
1290 let unloaded = entries
1291 .iter()
1292 .filter_map(|(key, program)| {
1293 matches!(program.program, ProgramCacheEntryType::Unloaded(_))
1294 .then_some((*key, program.stats.uses.load(Ordering::Relaxed)))
1295 })
1296 .collect::<Vec<(Pubkey, u64)>>();
1297
1298 for index in 0..3 {
1299 let expected = programs.get(index).expect("Missing program");
1300 assert!(unloaded.contains(&(expected.0, expected.2)));
1301 }
1302
1303 let num_loaded = num_matching_entries(&cache, |program_type| {
1305 matches!(program_type, ProgramCacheEntryType::Loaded(_))
1306 });
1307 let num_unloaded = num_matching_entries(&cache, |program_type| {
1308 matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1309 });
1310 let num_tombstones = num_matching_entries(&cache, |program_type| {
1311 matches!(
1312 program_type,
1313 ProgramCacheEntryType::DelayVisibility
1314 | ProgramCacheEntryType::FailedVerification(_)
1315 | ProgramCacheEntryType::Closed
1316 )
1317 });
1318
1319 assert_eq!(num_loaded, num_loaded_expected);
1321 assert_eq!(num_unloaded, num_unloaded_expected);
1323 assert_eq!(num_tombstones, num_tombstones_expected);
1325 }
1326
1327 #[test]
1328 fn test_usage_count_of_unloaded_program() {
1329 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1330 let env = get_mock_program_runtime_environment();
1331
1332 let program = Pubkey::new_unique();
1333 let evict_to_pct = 2;
1334 let cache_capacity_after_shrink =
1335 Percentage::from(evict_to_pct).apply_to(crate::loaded_programs::MAX_LOADED_ENTRY_COUNT);
1336 let num_total_programs = (cache_capacity_after_shrink + 1) as u64;
1338 (0..num_total_programs).for_each(|i| {
1339 let stats = ProgramStatistics {
1340 uses: (i + 10).into(),
1341 ..Default::default()
1342 };
1343 let entry = new_test_entry_with_usage(i, i + 2, stats);
1344 cache.assign_program(&env, program, i, entry);
1345 });
1346
1347 cache.sort_and_unload(Percentage::from(evict_to_pct));
1348
1349 let num_unloaded = num_matching_entries(&cache, |program_type| {
1350 matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1351 });
1352 assert_eq!(num_unloaded, 1);
1353
1354 cache
1355 .get_flattened_entries_for_tests()
1356 .iter()
1357 .for_each(|(_key, program)| {
1358 if matches!(program.program, ProgramCacheEntryType::Unloaded(_)) {
1359 assert_eq!(program.stats.uses.load(Ordering::Relaxed), 10);
1361 assert_eq!(program.deployment_slot, 0);
1362 assert_eq!(program.effective_slot, 2);
1363 }
1364 });
1365
1366 cache.assign_program(
1369 &env,
1370 program,
1371 0,
1372 new_test_entry_with_usage(0, 2, ProgramStatistics::default()),
1373 );
1374
1375 cache
1376 .get_flattened_entries_for_tests()
1377 .iter()
1378 .for_each(|(_key, program)| {
1379 if matches!(program.program, ProgramCacheEntryType::Unloaded(_))
1380 && program.deployment_slot == 0
1381 && program.effective_slot == 2
1382 {
1383 assert_eq!(program.stats.uses.load(Ordering::Relaxed), 10);
1385 }
1386 });
1387 }
1388
1389 #[test]
1390 fn test_fuzz_assign_program_order() {
1391 use rand::prelude::SliceRandom;
1392 const EXPECTED_ENTRIES: [(u64, u64); 7] =
1393 [(1, 2), (5, 5), (5, 6), (5, 10), (9, 10), (10, 10), (3, 12)];
1394 let mut rng = rand::rng();
1395 let program_id = Pubkey::new_unique();
1396 let env = get_mock_program_runtime_environment();
1397 for _ in 0..1000 {
1398 let mut entries = EXPECTED_ENTRIES.to_vec();
1399 entries.shuffle(&mut rng);
1400 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1401 for (deployment_slot, effective_slot) in entries {
1402 let entry = Arc::new(ProgramCacheEntry {
1403 program: new_loaded_entry(ProgramRuntimeEnvironment::from(
1404 BuiltinProgram::new_mock(),
1405 )), account_owner: ProgramCacheEntryOwner::LoaderV2,
1407 account_size: 0,
1408 deployment_slot,
1409 effective_slot,
1410 stats: Arc::default(),
1411 latest_access_slot: AtomicU64::new(deployment_slot),
1412 });
1413 assert!(!cache.assign_program(&env, program_id, deployment_slot, entry));
1414 }
1415 for ((deployment_slot, effective_slot), entry) in EXPECTED_ENTRIES
1416 .iter()
1417 .zip(cache.get_slot_versions_for_tests(&program_id).iter())
1418 {
1419 assert_eq!(entry.deployment_slot, *deployment_slot);
1420 assert_eq!(entry.effective_slot, *effective_slot);
1421 }
1422 }
1423 }
1424
1425 #[test_matrix(
1426 (
1427 ProgramCacheEntryType::Closed,
1428 ProgramCacheEntryType::FailedVerification(get_mock_program_runtime_environment()),
1429 new_loaded_entry(get_mock_program_runtime_environment()),
1430 ),
1431 (
1432 ProgramCacheEntryType::FailedVerification(get_mock_program_runtime_environment()),
1433 ProgramCacheEntryType::Closed,
1434 ProgramCacheEntryType::Unloaded(get_mock_program_runtime_environment()),
1435 new_loaded_entry(get_mock_program_runtime_environment()),
1436 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1437 )
1438 )]
1439 #[test_matrix(
1440 (
1441 ProgramCacheEntryType::Unloaded(get_mock_program_runtime_environment()),
1442 ),
1443 (
1444 ProgramCacheEntryType::FailedVerification(get_mock_program_runtime_environment()),
1445 ProgramCacheEntryType::Closed,
1446 ProgramCacheEntryType::Unloaded(get_mock_program_runtime_environment()),
1447 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1448 )
1449 )]
1450 #[test_matrix(
1451 (ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),),
1452 (
1453 ProgramCacheEntryType::FailedVerification(get_mock_program_runtime_environment()),
1454 ProgramCacheEntryType::Closed,
1455 ProgramCacheEntryType::Unloaded(get_mock_program_runtime_environment()),
1456 new_loaded_entry(get_mock_program_runtime_environment()),
1457 )
1458 )]
1459 #[should_panic(expected = "Unexpected replacement of an entry")]
1460 fn test_assign_program_failure(old: ProgramCacheEntryType, new: ProgramCacheEntryType) {
1461 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1462 let env = get_mock_program_runtime_environment();
1463 let program_id = Pubkey::new_unique();
1464 assert!(!cache.assign_program(
1465 &env,
1466 program_id,
1467 10,
1468 Arc::new(ProgramCacheEntry {
1469 program: old,
1470 account_owner: ProgramCacheEntryOwner::LoaderV2,
1471 account_size: 0,
1472 deployment_slot: 10,
1473 effective_slot: 11,
1474 stats: Arc::default(),
1475 latest_access_slot: AtomicU64::default(),
1476 }),
1477 ));
1478 cache.assign_program(
1479 &env,
1480 program_id,
1481 10,
1482 Arc::new(ProgramCacheEntry {
1483 program: new,
1484 account_owner: ProgramCacheEntryOwner::LoaderV2,
1485 account_size: 0,
1486 deployment_slot: 10,
1487 effective_slot: 11,
1488 stats: Arc::default(),
1489 latest_access_slot: AtomicU64::default(),
1490 }),
1491 );
1492 }
1493
1494 #[test_case(
1495 ProgramCacheEntryType::Unloaded(ProgramRuntimeEnvironment::from(
1496 BuiltinProgram::new_mock()
1497 )),
1498 new_loaded_entry(get_mock_program_runtime_environment())
1499 )]
1500 #[test_case(
1501 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1502 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock())
1503 )]
1504 fn test_assign_program_success(old: ProgramCacheEntryType, new: ProgramCacheEntryType) {
1505 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1506 let env = get_mock_program_runtime_environment();
1507 let program_id = Pubkey::new_unique();
1508 assert!(!cache.assign_program(
1509 &env,
1510 program_id,
1511 10,
1512 Arc::new(ProgramCacheEntry {
1513 program: old,
1514 account_owner: ProgramCacheEntryOwner::LoaderV2,
1515 account_size: 0,
1516 deployment_slot: 10,
1517 effective_slot: 11,
1518 stats: Arc::default(),
1519 latest_access_slot: AtomicU64::default(),
1520 }),
1521 ));
1522 assert!(!cache.assign_program(
1523 &env,
1524 program_id,
1525 10,
1526 Arc::new(ProgramCacheEntry {
1527 program: new,
1528 account_owner: ProgramCacheEntryOwner::LoaderV2,
1529 account_size: 0,
1530 deployment_slot: 10,
1531 effective_slot: 11,
1532 stats: Arc::default(),
1533 latest_access_slot: AtomicU64::default(),
1534 }),
1535 ));
1536 }
1537
1538 #[test]
1539 fn test_assign_program_removes_entries_in_same_slot() {
1540 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1541 let env = get_mock_program_runtime_environment();
1542 let program_id = Pubkey::new_unique();
1543 let closed_other_slot = Arc::new(ProgramCacheEntry {
1544 program: ProgramCacheEntryType::Closed,
1545 account_owner: ProgramCacheEntryOwner::LoaderV2,
1546 account_size: 0,
1547 deployment_slot: 9,
1548 effective_slot: 9,
1549 stats: Arc::default(),
1550 latest_access_slot: AtomicU64::default(),
1551 });
1552 let closed_current_slot = Arc::new(ProgramCacheEntry {
1553 program: ProgramCacheEntryType::Closed,
1554 account_owner: ProgramCacheEntryOwner::LoaderV2,
1555 account_size: 0,
1556 deployment_slot: 10,
1557 effective_slot: 10,
1558 stats: Arc::default(),
1559 latest_access_slot: AtomicU64::default(),
1560 });
1561 let loaded_entry_current_env = Arc::new(ProgramCacheEntry {
1562 program: ProgramCacheEntryType::Unloaded(get_mock_program_runtime_environment()),
1563 account_owner: ProgramCacheEntryOwner::LoaderV2,
1564 account_size: 0,
1565 deployment_slot: 10,
1566 effective_slot: 11,
1567 stats: Arc::default(),
1568 latest_access_slot: AtomicU64::default(),
1569 });
1570 let loaded_entry_upcoming_env = Arc::new(ProgramCacheEntry {
1571 program: ProgramCacheEntryType::Unloaded(ProgramRuntimeEnvironment::from(
1572 BuiltinProgram::new_mock(),
1573 )),
1574 account_owner: ProgramCacheEntryOwner::LoaderV2,
1575 account_size: 0,
1576 deployment_slot: 10,
1577 effective_slot: 11,
1578 stats: Arc::default(),
1579 latest_access_slot: AtomicU64::default(),
1580 });
1581 assert!(!cache.assign_program(&env, program_id, 9, closed_other_slot.clone()));
1582 assert!(!cache.assign_program(&env, program_id, 10, closed_current_slot));
1583 assert!(!cache.assign_program(&env, program_id, 10, loaded_entry_upcoming_env.clone()));
1584 assert!(!cache.assign_program(&env, program_id, 10, loaded_entry_current_env.clone()));
1585 assert_eq!(
1587 cache.get_slot_versions_for_tests(&program_id),
1588 &[
1589 closed_other_slot,
1590 loaded_entry_current_env,
1591 loaded_entry_upcoming_env
1592 ]
1593 );
1594 }
1595
1596 #[test]
1597 fn test_tombstone() {
1598 let env = get_mock_program_runtime_environment();
1599 let tombstone = ProgramCacheEntry::new_tombstone(
1600 0,
1601 ProgramCacheEntryOwner::LoaderV2,
1602 ProgramCacheEntryType::FailedVerification(env.clone()),
1603 );
1604 assert_matches!(
1605 tombstone.program,
1606 ProgramCacheEntryType::FailedVerification(_)
1607 );
1608 assert!(tombstone.is_tombstone());
1609 assert_eq!(tombstone.deployment_slot, 0);
1610 assert_eq!(tombstone.effective_slot, 0);
1611
1612 let tombstone = ProgramCacheEntry::new_tombstone(
1613 100,
1614 ProgramCacheEntryOwner::LoaderV2,
1615 ProgramCacheEntryType::Closed,
1616 );
1617 assert_matches!(tombstone.program, ProgramCacheEntryType::Closed);
1618 assert!(tombstone.is_tombstone());
1619 assert_eq!(tombstone.deployment_slot, 100);
1620 assert_eq!(tombstone.effective_slot, 100);
1621
1622 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1623 let program1 = Pubkey::new_unique();
1624 let tombstone = set_tombstone(
1625 &mut cache,
1626 program1,
1627 10,
1628 ProgramCacheEntryType::FailedVerification(env.clone()),
1629 );
1630 let slot_versions = cache.get_slot_versions_for_tests(&program1);
1631 assert_eq!(slot_versions.len(), 1);
1632 assert!(slot_versions.first().unwrap().is_tombstone());
1633 assert_eq!(tombstone.deployment_slot, 10);
1634 assert_eq!(tombstone.effective_slot, 10);
1635
1636 let program2 = Pubkey::new_unique();
1638 cache.assign_program(&env, program2, 50, new_test_builtin_entry(50, 51));
1639 let slot_versions = cache.get_slot_versions_for_tests(&program2);
1640 assert_eq!(slot_versions.len(), 1);
1641 assert!(!slot_versions.first().unwrap().is_tombstone());
1642
1643 let tombstone = set_tombstone(
1644 &mut cache,
1645 program2,
1646 60,
1647 ProgramCacheEntryType::FailedVerification(env),
1648 );
1649 let slot_versions = cache.get_slot_versions_for_tests(&program2);
1650 assert_eq!(slot_versions.len(), 2);
1651 assert!(!slot_versions.first().unwrap().is_tombstone());
1652 assert!(slot_versions.get(1).unwrap().is_tombstone());
1653 assert!(tombstone.is_tombstone());
1654 assert_eq!(tombstone.deployment_slot, 60);
1655 assert_eq!(tombstone.effective_slot, 60);
1656 }
1657
1658 struct TestForkGraph {
1659 relation: BlockRelation,
1660 }
1661 impl ForkGraph for TestForkGraph {
1662 fn relationship(&self, _a: Slot, _b: Slot) -> BlockRelation {
1663 self.relation
1664 }
1665 }
1666
1667 #[test]
1668 fn test_prune_empty() {
1669 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1670 let fork_graph = Arc::new(RwLock::new(TestForkGraph {
1671 relation: BlockRelation::Unrelated,
1672 }));
1673
1674 cache.set_fork_graph(Arc::downgrade(&fork_graph));
1675
1676 cache.prune(0, None, &fork_graph.read().unwrap());
1677 assert!(cache.get_flattened_entries_for_tests().is_empty());
1678
1679 cache.prune(10, None, &fork_graph.read().unwrap());
1680 assert!(cache.get_flattened_entries_for_tests().is_empty());
1681
1682 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1683 let fork_graph = Arc::new(RwLock::new(TestForkGraph {
1684 relation: BlockRelation::Ancestor,
1685 }));
1686
1687 cache.set_fork_graph(Arc::downgrade(&fork_graph));
1688
1689 cache.prune(0, None, &fork_graph.read().unwrap());
1690 assert!(cache.get_flattened_entries_for_tests().is_empty());
1691
1692 cache.prune(10, None, &fork_graph.read().unwrap());
1693 assert!(cache.get_flattened_entries_for_tests().is_empty());
1694
1695 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1696 let fork_graph = Arc::new(RwLock::new(TestForkGraph {
1697 relation: BlockRelation::Descendant,
1698 }));
1699
1700 cache.set_fork_graph(Arc::downgrade(&fork_graph));
1701
1702 cache.prune(0, None, &fork_graph.read().unwrap());
1703 assert!(cache.get_flattened_entries_for_tests().is_empty());
1704
1705 cache.prune(10, None, &fork_graph.read().unwrap());
1706 assert!(cache.get_flattened_entries_for_tests().is_empty());
1707
1708 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1709 let fork_graph = Arc::new(RwLock::new(TestForkGraph {
1710 relation: BlockRelation::Unknown,
1711 }));
1712 cache.set_fork_graph(Arc::downgrade(&fork_graph));
1713
1714 cache.prune(0, None, &fork_graph.read().unwrap());
1715 assert!(cache.get_flattened_entries_for_tests().is_empty());
1716
1717 cache.prune(10, None, &fork_graph.read().unwrap());
1718 assert!(cache.get_flattened_entries_for_tests().is_empty());
1719 }
1720
1721 #[test]
1722 fn test_prune_different_env() {
1723 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1724 let env = get_mock_program_runtime_environment();
1725
1726 let fork_graph = Arc::new(RwLock::new(TestForkGraph {
1727 relation: BlockRelation::Ancestor,
1728 }));
1729
1730 cache.set_fork_graph(Arc::downgrade(&fork_graph));
1731
1732 let program1 = Pubkey::new_unique();
1733 cache.assign_program(&env, program1, 10, new_test_entry(10, 10));
1734 let new_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
1735 let upcoming_environment = Some(new_env.clone());
1736 let updated_program = Arc::new(ProgramCacheEntry {
1737 program: new_loaded_entry(new_env.clone()),
1738 deployment_slot: 20,
1739 effective_slot: 20,
1740 ..Default::default()
1741 });
1742 cache.assign_program(
1743 &env,
1744 program1,
1745 updated_program.deployment_slot,
1746 updated_program.clone(),
1747 );
1748
1749 assert_eq!(cache.get_slot_versions_for_tests(&program1).len(), 2);
1751
1752 cache.prune(21, None, &fork_graph.read().unwrap());
1753
1754 assert_eq!(cache.get_slot_versions_for_tests(&program1).len(), 2);
1756
1757 cache.prune(22, upcoming_environment, &fork_graph.read().unwrap());
1758
1759 assert_eq!(cache.get_slot_versions_for_tests(&program1).len(), 1);
1761
1762 let entry = cache
1763 .get_slot_versions_for_tests(&program1)
1764 .first()
1765 .expect("Failed to get the program")
1766 .clone();
1767 assert_eq!(entry, updated_program);
1769 }
1770
1771 #[derive(Default)]
1772 struct TestForkGraphSpecific {
1773 forks: Vec<Vec<Slot>>,
1774 }
1775
1776 impl TestForkGraphSpecific {
1777 fn insert_fork(&mut self, fork: &[Slot]) {
1778 let mut fork = fork.to_vec();
1779 fork.sort();
1780 self.forks.push(fork)
1781 }
1782 }
1783
1784 impl ForkGraph for TestForkGraphSpecific {
1785 fn relationship(&self, a: Slot, b: Slot) -> BlockRelation {
1786 match self.forks.iter().try_for_each(|fork| {
1787 let relation = fork
1788 .iter()
1789 .position(|x| *x == a)
1790 .and_then(|a_pos| {
1791 fork.iter().position(|x| *x == b).and_then(|b_pos| {
1792 (a_pos == b_pos)
1793 .then_some(BlockRelation::Equal)
1794 .or_else(|| (a_pos < b_pos).then_some(BlockRelation::Ancestor))
1795 .or(Some(BlockRelation::Descendant))
1796 })
1797 })
1798 .unwrap_or(BlockRelation::Unrelated);
1799
1800 if relation != BlockRelation::Unrelated {
1801 return ControlFlow::Break(relation);
1802 }
1803
1804 ControlFlow::Continue(())
1805 }) {
1806 ControlFlow::Break(relation) => relation,
1807 _ => BlockRelation::Unrelated,
1808 }
1809 }
1810 }
1811
1812 fn get_entries_to_load<'a>(
1813 cache: &ProgramCache<TestForkGraphSpecific>,
1814 loading_slot: Slot,
1815 keys: &'a [Pubkey],
1816 ) -> Vec<ProgramToLoad<'a>> {
1817 let fork_graph = cache.fork_graph.as_ref().unwrap().upgrade().unwrap();
1818 let locked_fork_graph = fork_graph.read().unwrap();
1819 let entries = cache.get_flattened_entries_for_tests();
1820 keys.iter()
1821 .filter_map(|key| {
1822 entries
1823 .iter()
1824 .rev()
1825 .find(|(program_id, entry)| {
1826 program_id == key
1827 && matches!(
1828 locked_fork_graph.relationship(entry.deployment_slot, loading_slot),
1829 BlockRelation::Equal | BlockRelation::Ancestor,
1830 )
1831 })
1832 .map(|(_program_id, entry)| ProgramToLoad {
1833 program_id: key,
1834 loader: entry.account_owner,
1835 match_criteria: ProgramCacheMatchCriteria::NoCriteria,
1836 last_modification_slot: entry.deployment_slot,
1837 })
1838 })
1839 .collect()
1840 }
1841
1842 fn match_slot(
1843 extracted: &ProgramCacheForTxBatch,
1844 program: &Pubkey,
1845 deployment_slot: Slot,
1846 working_slot: Slot,
1847 ) -> bool {
1848 assert_eq!(extracted.slot, working_slot);
1849 extracted
1850 .entries
1851 .get(program)
1852 .map(|entry| entry.deployment_slot == deployment_slot)
1853 .unwrap_or(false)
1854 }
1855
1856 fn match_missing(
1857 missing: &[ProgramToLoad],
1858 program_id: &Pubkey,
1859 expected_result: bool,
1860 ) -> bool {
1861 missing.iter().any(|entry| entry.program_id == program_id) == expected_result
1862 }
1863
1864 #[test]
1865 fn test_fork_extract_and_prune() {
1866 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
1867 let env = get_mock_program_runtime_environment();
1868
1869 let mut fork_graph = TestForkGraphSpecific::default();
1885 fork_graph.insert_fork(&[0, 10, 20, 22]);
1886 fork_graph.insert_fork(&[0, 5, 11, 15, 16, 18, 19, 21, 23]);
1887 fork_graph.insert_fork(&[0, 5, 11, 25, 27]);
1888
1889 let fork_graph = Arc::new(RwLock::new(fork_graph));
1890 cache.set_fork_graph(Arc::downgrade(&fork_graph));
1891
1892 let program1 = Pubkey::new_unique();
1893 cache.assign_program(&env, program1, 0, new_test_entry(0, 1));
1894 cache.assign_program(&env, program1, 10, new_test_entry(10, 11));
1895 cache.assign_program(&env, program1, 20, new_test_entry(20, 21));
1896
1897 let program2 = Pubkey::new_unique();
1898 cache.assign_program(&env, program2, 5, new_test_entry(5, 6));
1899 cache.assign_program(
1900 &env,
1901 program2,
1902 11,
1903 new_test_entry(11, 11 + DELAY_VISIBILITY_SLOT_OFFSET),
1904 );
1905
1906 let program3 = Pubkey::new_unique();
1907 cache.assign_program(&env, program3, 25, new_test_entry(25, 26));
1908
1909 let program4 = Pubkey::new_unique();
1910 cache.assign_program(&env, program4, 0, new_test_entry(0, 1));
1911 cache.assign_program(&env, program4, 5, new_test_entry(5, 6));
1912 cache.assign_program(
1914 &env,
1915 program4,
1916 15,
1917 new_test_entry(15, 15 + DELAY_VISIBILITY_SLOT_OFFSET),
1918 );
1919
1920 let keys = &[program1, program2, program3, program4];
1937 let mut missing = get_entries_to_load(&cache, 22, keys);
1938 assert!(match_missing(&missing, &program2, false));
1939 assert!(match_missing(&missing, &program3, false));
1940 let mut extracted = ProgramCacheForTxBatch::new(22);
1941 cache.extract(&mut missing, &mut extracted, &env, true, true);
1942 assert!(match_slot(&extracted, &program1, 20, 22));
1943 assert!(match_slot(&extracted, &program4, 0, 22));
1944
1945 let mut missing = get_entries_to_load(&cache, 15, keys);
1947 assert!(match_missing(&missing, &program3, false));
1948 let mut extracted = ProgramCacheForTxBatch::new(15);
1949 cache.extract(&mut missing, &mut extracted, &env, true, true);
1950 assert!(match_slot(&extracted, &program1, 0, 15));
1951 assert!(match_slot(&extracted, &program2, 11, 15));
1952 let tombstone = extracted
1955 .find(&program4)
1956 .expect("Failed to find the tombstone");
1957 assert_matches!(tombstone.program, ProgramCacheEntryType::DelayVisibility);
1958 assert_eq!(tombstone.deployment_slot, 15);
1959
1960 let mut missing = get_entries_to_load(&cache, 18, keys);
1962 assert!(match_missing(&missing, &program3, false));
1963 let mut extracted = ProgramCacheForTxBatch::new(18);
1964 cache.extract(&mut missing, &mut extracted, &env, true, true);
1965 assert!(match_slot(&extracted, &program1, 0, 18));
1966 assert!(match_slot(&extracted, &program2, 11, 18));
1967 assert!(match_slot(&extracted, &program4, 15, 18));
1969
1970 let mut missing = get_entries_to_load(&cache, 23, keys);
1972 assert!(match_missing(&missing, &program3, false));
1973 let mut extracted = ProgramCacheForTxBatch::new(23);
1974 cache.extract(&mut missing, &mut extracted, &env, true, true);
1975 assert!(match_slot(&extracted, &program1, 0, 23));
1976 assert!(match_slot(&extracted, &program2, 11, 23));
1977 assert!(match_slot(&extracted, &program4, 15, 23));
1979
1980 let mut missing = get_entries_to_load(&cache, 11, keys);
1982 assert!(match_missing(&missing, &program3, false));
1983 let mut extracted = ProgramCacheForTxBatch::new(11);
1984 cache.extract(&mut missing, &mut extracted, &env, true, true);
1985 assert!(match_slot(&extracted, &program1, 0, 11));
1986 let tombstone = extracted
1988 .find(&program2)
1989 .expect("Failed to find the tombstone");
1990 assert_matches!(tombstone.program, ProgramCacheEntryType::DelayVisibility);
1991 assert_eq!(tombstone.deployment_slot, 11);
1992 assert!(match_slot(&extracted, &program4, 5, 11));
1993
1994 cache.prune(5, None, &fork_graph.read().unwrap());
1995
1996 let mut missing = get_entries_to_load(&cache, 21, keys);
2013 assert!(match_missing(&missing, &program3, false));
2014 let mut extracted = ProgramCacheForTxBatch::new(21);
2015 cache.extract(&mut missing, &mut extracted, &env, true, true);
2016 assert!(match_slot(&extracted, &program1, 0, 21));
2018 assert!(match_slot(&extracted, &program2, 11, 21));
2019 assert!(match_slot(&extracted, &program4, 15, 21));
2020
2021 let mut missing = get_entries_to_load(&cache, 27, keys);
2023 let mut extracted = ProgramCacheForTxBatch::new(27);
2024 cache.extract(&mut missing, &mut extracted, &env, true, true);
2025 assert!(match_slot(&extracted, &program1, 0, 27));
2026 assert!(match_slot(&extracted, &program2, 11, 27));
2027 assert!(match_slot(&extracted, &program3, 25, 27));
2028 assert!(match_slot(&extracted, &program4, 5, 27));
2029
2030 cache.prune(15, None, &fork_graph.read().unwrap());
2031
2032 let mut missing = get_entries_to_load(&cache, 23, keys);
2049 assert!(match_missing(&missing, &program3, false));
2050 let mut extracted = ProgramCacheForTxBatch::new(23);
2051 cache.extract(&mut missing, &mut extracted, &env, true, true);
2052 assert!(match_slot(&extracted, &program1, 0, 23));
2053 assert!(match_slot(&extracted, &program2, 11, 23));
2054 assert!(match_slot(&extracted, &program4, 15, 23));
2055 }
2056
2057 #[test]
2058 fn test_extract_using_deployment_slot() {
2059 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
2060 let env = get_mock_program_runtime_environment();
2061
2062 let mut fork_graph = TestForkGraphSpecific::default();
2078 fork_graph.insert_fork(&[0, 10, 20, 22]);
2079 fork_graph.insert_fork(&[0, 5, 11, 12, 15, 16, 18, 19, 21, 23]);
2080 fork_graph.insert_fork(&[0, 5, 11, 25, 27]);
2081
2082 let fork_graph = Arc::new(RwLock::new(fork_graph));
2083 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2084
2085 let program1 = Pubkey::new_unique();
2086 cache.assign_program(&env, program1, 0, new_test_entry(0, 1));
2087 cache.assign_program(&env, program1, 20, new_test_entry(20, 21));
2088
2089 let program2 = Pubkey::new_unique();
2090 cache.assign_program(&env, program2, 5, new_test_entry(5, 6));
2091 cache.assign_program(&env, program2, 11, new_test_entry(11, 12));
2092
2093 let program3 = Pubkey::new_unique();
2094 cache.assign_program(&env, program3, 25, new_test_entry(25, 26));
2095
2096 let keys = &[program1, program2, program3];
2098 let mut missing = get_entries_to_load(&cache, 12, keys);
2099 assert!(match_missing(&missing, &program3, false));
2100 let mut extracted = ProgramCacheForTxBatch::new(12);
2101 cache.extract(&mut missing, &mut extracted, &env, true, true);
2102 assert!(match_slot(&extracted, &program1, 0, 12));
2103 assert!(match_slot(&extracted, &program2, 11, 12));
2104
2105 let mut missing = get_entries_to_load(&cache, 12, keys);
2107 missing.get_mut(0).unwrap().match_criteria =
2108 ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(5);
2109 missing.get_mut(1).unwrap().match_criteria =
2110 ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(5);
2111 assert!(match_missing(&missing, &program3, false));
2112 let mut extracted = ProgramCacheForTxBatch::new(12);
2113 cache.extract(&mut missing, &mut extracted, &env, true, true);
2114 assert!(match_missing(&missing, &program1, true));
2115 assert!(match_slot(&extracted, &program2, 11, 12));
2116 }
2117
2118 #[test]
2119 fn test_extract_unloaded() {
2120 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
2121 let env = get_mock_program_runtime_environment();
2122
2123 let mut fork_graph = TestForkGraphSpecific::default();
2139 fork_graph.insert_fork(&[0, 10, 20, 22]);
2140 fork_graph.insert_fork(&[0, 5, 11, 15, 16, 19, 21, 23]);
2141 fork_graph.insert_fork(&[0, 5, 11, 25, 27]);
2142
2143 let fork_graph = Arc::new(RwLock::new(fork_graph));
2144 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2145
2146 let program1 = Pubkey::new_unique();
2147 cache.assign_program(&env, program1, 0, new_test_entry(0, 1));
2148 cache.assign_program(&env, program1, 20, new_test_entry(20, 21));
2149
2150 let program2 = Pubkey::new_unique();
2151 cache.assign_program(&env, program2, 5, new_test_entry(5, 6));
2152 cache.assign_program(&env, program2, 11, new_test_entry(11, 12));
2153
2154 let program3 = Pubkey::new_unique();
2155 let _ = insert_unloaded_entry(&mut cache, program3, 25);
2157
2158 cache.assign_program(
2162 &env,
2163 program3,
2164 20,
2165 Arc::new(
2166 new_test_entry(20, 21)
2167 .to_unloaded()
2168 .expect("Failed to create unloaded program"),
2169 ),
2170 );
2171
2172 let keys = &[program1, program2, program3];
2174 let mut missing = get_entries_to_load(&cache, 19, keys);
2175 assert!(match_missing(&missing, &program3, false));
2176 let mut extracted = ProgramCacheForTxBatch::new(19);
2177 cache.extract(&mut missing, &mut extracted, &env, true, true);
2178 assert!(match_slot(&extracted, &program1, 0, 19));
2179 assert!(match_slot(&extracted, &program2, 11, 19));
2180
2181 let mut missing = get_entries_to_load(&cache, 27, keys);
2183 let mut extracted = ProgramCacheForTxBatch::new(27);
2184 cache.extract(&mut missing, &mut extracted, &env, true, true);
2185 assert!(match_slot(&extracted, &program1, 0, 27));
2186 assert!(match_slot(&extracted, &program2, 11, 27));
2187 assert!(match_missing(&missing, &program3, true));
2188
2189 let mut missing = get_entries_to_load(&cache, 22, keys);
2191 assert!(match_missing(&missing, &program2, false));
2192 let mut extracted = ProgramCacheForTxBatch::new(22);
2193 cache.extract(&mut missing, &mut extracted, &env, true, true);
2194 assert!(match_slot(&extracted, &program1, 20, 22));
2195 assert!(match_missing(&missing, &program3, true));
2196 }
2197
2198 #[test]
2199 fn test_extract_different_environment() {
2200 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
2201 let env = get_mock_program_runtime_environment();
2202 let other_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
2203
2204 let mut fork_graph = TestForkGraphSpecific::default();
2214 fork_graph.insert_fork(&[0, 10, 20, 22]);
2215
2216 let fork_graph = Arc::new(RwLock::new(fork_graph));
2217 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2218
2219 let program1 = Pubkey::new_unique();
2220 cache.assign_program(
2221 &env,
2222 program1,
2223 10,
2224 Arc::new(ProgramCacheEntry::new_tombstone(
2225 10,
2226 ProgramCacheEntryOwner::LoaderV3,
2227 ProgramCacheEntryType::Closed,
2228 )),
2229 );
2230 cache.assign_program(&env, program1, 20, new_test_entry(20, 21));
2231
2232 let keys = &[program1];
2234 let mut missing = get_entries_to_load(&cache, 22, keys);
2235 let mut extracted = ProgramCacheForTxBatch::new(22);
2236 cache.extract(&mut missing, &mut extracted, &env, true, true);
2237 assert!(match_slot(&extracted, &program1, 20, 22));
2238
2239 let mut missing = get_entries_to_load(&cache, 22, keys);
2241 let mut extracted = ProgramCacheForTxBatch::new(22);
2242 cache.extract(&mut missing, &mut extracted, &other_env, true, true);
2243 assert!(match_missing(&missing, &program1, true));
2244 }
2245
2246 #[test]
2247 fn test_extract_nonexistent() {
2248 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
2249 let env = get_mock_program_runtime_environment();
2250 let fork_graph = TestForkGraphSpecific::default();
2251 let fork_graph = Arc::new(RwLock::new(fork_graph));
2252 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2253
2254 let program1 = Pubkey::new_unique();
2255 let mut missing = vec![ProgramToLoad {
2256 program_id: &program1,
2257 loader: ProgramCacheEntryOwner::LoaderV3,
2258 match_criteria: ProgramCacheMatchCriteria::NoCriteria,
2259 last_modification_slot: 0,
2260 }];
2261 let mut extracted = ProgramCacheForTxBatch::new(0);
2262 cache.extract(&mut missing, &mut extracted, &env, true, true);
2263 assert!(match_missing(&missing, &program1, true));
2264 }
2265
2266 #[test]
2267 fn test_unloaded() {
2268 let mut cache = ProgramCache::<TestForkGraph>::new(0);
2269 let env = get_mock_program_runtime_environment();
2270 for program_cache_entry_type in [
2271 ProgramCacheEntryType::FailedVerification(get_mock_program_runtime_environment()),
2272 ProgramCacheEntryType::Closed,
2273 ProgramCacheEntryType::Unloaded(get_mock_program_runtime_environment()),
2274 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
2275 ] {
2276 let entry = Arc::new(ProgramCacheEntry {
2277 program: program_cache_entry_type,
2278 account_owner: ProgramCacheEntryOwner::LoaderV2,
2279 account_size: 0,
2280 deployment_slot: 0,
2281 effective_slot: 0,
2282 stats: Arc::default(),
2283 latest_access_slot: AtomicU64::default(),
2284 });
2285 assert!(entry.to_unloaded().is_none());
2286
2287 let program_id = Pubkey::new_unique();
2289 cache.assign_program(&env, program_id, entry.deployment_slot, entry.clone());
2290 cache.unload_program_entry(program_id, entry.deployment_slot, &entry);
2291 assert_eq!(cache.get_slot_versions_for_tests(&program_id).len(), 1);
2292 assert!(cache.stats.evictions.is_empty());
2293 }
2294
2295 let stats = ProgramStatistics {
2296 uses: 3.into(),
2297 ..Default::default()
2298 };
2299 let entry = new_test_entry_with_usage(1, 2, stats);
2300 let unloaded_entry = entry.to_unloaded().unwrap();
2301 assert_eq!(unloaded_entry.deployment_slot, 1);
2302 assert_eq!(unloaded_entry.effective_slot, 2);
2303 assert_eq!(unloaded_entry.latest_access_slot.load(Ordering::Relaxed), 1);
2304 assert_eq!(unloaded_entry.stats.uses.load(Ordering::Relaxed), 3);
2305
2306 let program_id = Pubkey::new_unique();
2308 cache.assign_program(&env, program_id, entry.deployment_slot, entry.clone());
2309 cache.unload_program_entry(program_id, entry.deployment_slot, &entry);
2310 assert!(cache.stats.evictions.contains_key(&program_id));
2311 }
2312
2313 #[test]
2314 fn test_fork_prune_find_first_ancestor() {
2315 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
2316 let env = get_mock_program_runtime_environment();
2317
2318 let mut fork_graph = TestForkGraphSpecific::default();
2329 fork_graph.insert_fork(&[0, 10, 20]);
2330 fork_graph.insert_fork(&[0, 5]);
2331 let fork_graph = Arc::new(RwLock::new(fork_graph));
2332 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2333
2334 let program1 = Pubkey::new_unique();
2335 cache.assign_program(&env, program1, 0, new_test_entry(0, 1));
2336 cache.assign_program(&env, program1, 5, new_test_entry(5, 6));
2337
2338 cache.prune(10, None, &fork_graph.read().unwrap());
2339
2340 let keys = &[program1];
2341 let mut missing = get_entries_to_load(&cache, 20, keys);
2342 let mut extracted = ProgramCacheForTxBatch::new(20);
2343 cache.extract(&mut missing, &mut extracted, &env, true, true);
2344
2345 assert_eq!(
2347 extracted
2348 .find(&program1)
2349 .expect("Did not find the program")
2350 .deployment_slot,
2351 0
2352 );
2353 }
2354
2355 #[test]
2356 fn test_prune_by_deployment_slot() {
2357 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
2358 let env = get_mock_program_runtime_environment();
2359
2360 let mut fork_graph = TestForkGraphSpecific::default();
2371 fork_graph.insert_fork(&[0, 10, 20]);
2372 fork_graph.insert_fork(&[0, 5, 6]);
2373 let fork_graph = Arc::new(RwLock::new(fork_graph));
2374 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2375
2376 let program1 = Pubkey::new_unique();
2377 cache.assign_program(&env, program1, 0, new_test_entry(0, 1));
2378 cache.assign_program(&env, program1, 5, new_test_entry(5, 6));
2379
2380 let program2 = Pubkey::new_unique();
2381 cache.assign_program(&env, program2, 10, new_test_entry(10, 11));
2382
2383 let keys = &[program1, program2];
2384 let mut missing = get_entries_to_load(&cache, 20, keys);
2385 let mut extracted = ProgramCacheForTxBatch::new(20);
2386 cache.extract(&mut missing, &mut extracted, &env, true, true);
2387 assert!(match_slot(&extracted, &program1, 0, 20));
2388 assert!(match_slot(&extracted, &program2, 10, 20));
2389
2390 let mut missing = get_entries_to_load(&cache, 6, keys);
2391 assert!(match_missing(&missing, &program2, false));
2392 let mut extracted = ProgramCacheForTxBatch::new(6);
2393 cache.extract(&mut missing, &mut extracted, &env, true, true);
2394 assert!(match_slot(&extracted, &program1, 5, 6));
2395
2396 cache.prune_by_deployment_slot(5);
2399
2400 let mut missing = get_entries_to_load(&cache, 20, keys);
2401 let mut extracted = ProgramCacheForTxBatch::new(20);
2402 cache.extract(&mut missing, &mut extracted, &env, true, true);
2403 assert!(match_slot(&extracted, &program1, 0, 20));
2404 assert!(match_slot(&extracted, &program2, 10, 20));
2405
2406 let mut missing = get_entries_to_load(&cache, 6, keys);
2407 assert!(match_missing(&missing, &program2, false));
2408 let mut extracted = ProgramCacheForTxBatch::new(6);
2409 cache.extract(&mut missing, &mut extracted, &env, true, true);
2410 assert!(match_slot(&extracted, &program1, 0, 6));
2411
2412 cache.prune_by_deployment_slot(10);
2415
2416 let mut missing = get_entries_to_load(&cache, 20, keys);
2417 assert!(match_missing(&missing, &program2, false));
2418 let mut extracted = ProgramCacheForTxBatch::new(20);
2419 cache.extract(&mut missing, &mut extracted, &env, true, true);
2420 assert!(match_slot(&extracted, &program1, 0, 20));
2421 }
2422
2423 #[test]
2424 fn test_usable_entries_for_slot() {
2425 ProgramCache::<TestForkGraph>::new(0);
2426 let tombstone = Arc::new(ProgramCacheEntry::new_tombstone(
2427 0,
2428 ProgramCacheEntryOwner::LoaderV2,
2429 ProgramCacheEntryType::Closed,
2430 ));
2431
2432 assert!(ProgramCache::<TestForkGraph>::matches_criteria(
2433 &tombstone,
2434 &ProgramCacheMatchCriteria::NoCriteria
2435 ));
2436
2437 assert!(ProgramCache::<TestForkGraph>::matches_criteria(
2438 &tombstone,
2439 &ProgramCacheMatchCriteria::Tombstone
2440 ));
2441
2442 assert!(ProgramCache::<TestForkGraph>::matches_criteria(
2443 &tombstone,
2444 &ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(0)
2445 ));
2446
2447 assert!(!ProgramCache::<TestForkGraph>::matches_criteria(
2448 &tombstone,
2449 &ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(1)
2450 ));
2451
2452 let program = new_test_entry(0, 1);
2453
2454 assert!(ProgramCache::<TestForkGraph>::matches_criteria(
2455 &program,
2456 &ProgramCacheMatchCriteria::NoCriteria
2457 ));
2458
2459 assert!(!ProgramCache::<TestForkGraph>::matches_criteria(
2460 &program,
2461 &ProgramCacheMatchCriteria::Tombstone
2462 ));
2463
2464 assert!(ProgramCache::<TestForkGraph>::matches_criteria(
2465 &program,
2466 &ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(0)
2467 ));
2468
2469 assert!(!ProgramCache::<TestForkGraph>::matches_criteria(
2470 &program,
2471 &ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(1)
2472 ));
2473
2474 let program = Arc::new(new_test_entry_with_usage(
2475 0,
2476 1,
2477 ProgramStatistics::default(),
2478 ));
2479
2480 assert!(ProgramCache::<TestForkGraph>::matches_criteria(
2481 &program,
2482 &ProgramCacheMatchCriteria::NoCriteria
2483 ));
2484
2485 assert!(!ProgramCache::<TestForkGraph>::matches_criteria(
2486 &program,
2487 &ProgramCacheMatchCriteria::Tombstone
2488 ));
2489
2490 assert!(ProgramCache::<TestForkGraph>::matches_criteria(
2491 &program,
2492 &ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(0)
2493 ));
2494
2495 assert!(!ProgramCache::<TestForkGraph>::matches_criteria(
2496 &program,
2497 &ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(1)
2498 ));
2499 }
2500}