1use {
2 crate::{
3 invoke_context::InvokeContext,
4 loading_task::LoadingTaskWaiter,
5 program_cache_entry::{
6 ProgramCacheEntry, ProgramCacheEntryOwner, ProgramCacheEntryType, retention_score,
7 },
8 program_metrics::{EMA_SCALE, ProgramCacheStats},
9 },
10 log::error,
11 solana_clock::{Epoch, Slot},
12 solana_pubkey::Pubkey,
13 solana_sbpf::program::BuiltinProgram,
14 solana_svm_type_overrides::{
15 rand::{Rng, rng},
16 sync::{Arc, Mutex, RwLock, atomic::Ordering},
17 thread,
18 },
19 std::{
20 collections::{HashMap, hash_map::Entry},
21 sync::Weak,
22 },
23};
24
25#[repr(transparent)]
26#[derive(Clone, Debug)]
27pub struct ProgramRuntimeEnvironment(Arc<BuiltinProgram<InvokeContext<'static, 'static>>>);
28impl std::hash::Hash for ProgramRuntimeEnvironment {
29 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
30 Arc::<BuiltinProgram<InvokeContext<'static, 'static>>>::as_ptr(&self.0).hash(state);
31 }
32}
33impl PartialEq for ProgramRuntimeEnvironment {
34 fn eq(&self, other: &Self) -> bool {
35 Arc::ptr_eq(&self.0, &other.0)
36 }
37}
38impl Eq for ProgramRuntimeEnvironment {}
39impl std::ops::Deref for ProgramRuntimeEnvironment {
40 type Target = Arc<BuiltinProgram<InvokeContext<'static, 'static>>>;
41
42 fn deref(&self) -> &Self::Target {
43 &self.0
44 }
45}
46impl ProgramRuntimeEnvironment {
47 pub fn from(inner: BuiltinProgram<InvokeContext<'static, 'static>>) -> Self {
48 Self(Arc::new(inner))
49 }
50
51 pub const fn from_ref<'a>(
52 inner: &'a Arc<BuiltinProgram<InvokeContext<'static, 'static>>>,
53 ) -> &'a Self {
54 unsafe { std::mem::transmute(inner) }
56 }
57}
58
59pub struct ProgramRuntimeEnvironments {
67 execution: ProgramRuntimeEnvironment,
70 deployment: ProgramRuntimeEnvironment,
73}
74
75impl ProgramRuntimeEnvironments {
76 pub fn new(
79 execution: ProgramRuntimeEnvironment,
80 deployment: ProgramRuntimeEnvironment,
81 ) -> Self {
82 Self {
83 execution,
84 deployment,
85 }
86 }
87
88 pub fn get_env_for_execution(&self) -> &ProgramRuntimeEnvironment {
90 &self.execution
91 }
92
93 pub fn get_env_for_deployment(&self) -> &ProgramRuntimeEnvironment {
95 &self.deployment
96 }
97
98 #[cfg(feature = "dev-context-only-utils")]
99 pub fn mock() -> Self {
100 Self {
101 execution: get_mock_program_runtime_environment(),
102 deployment: get_mock_program_runtime_environment(),
103 }
104 }
105}
106
107#[cfg(feature = "dev-context-only-utils")]
108pub fn get_mock_program_runtime_environment() -> ProgramRuntimeEnvironment {
109 static MOCK_ENVIRONMENT: std::sync::OnceLock<ProgramRuntimeEnvironment> =
110 std::sync::OnceLock::<ProgramRuntimeEnvironment>::new();
111 MOCK_ENVIRONMENT
112 .get_or_init(|| ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock()))
113 .clone()
114}
115
116pub const MAX_LOADED_ENTRY_COUNT: usize = 1024;
117pub const MAX_TOMBSTONE_AGE_IN_SLOTS: u64 = 2250; pub type Percent = u8;
121
122fn percent_of_max_entries(percent: Percent) -> usize {
127 debug_assert!(percent <= 100, "percent must be <= 100");
128 MAX_LOADED_ENTRY_COUNT.saturating_mul(percent as usize) / 100
129}
130
131#[derive(Copy, Clone, Debug, PartialEq)]
133pub enum BlockRelation {
134 Ancestor,
136 Equal,
138 Descendant,
140 Unrelated,
142 Unknown,
144}
145
146pub trait ForkGraph {
148 fn relationship(&self, a: Slot, b: Slot) -> BlockRelation;
150}
151
152#[derive(Debug, Default)]
154pub struct EpochBoundaryPreparation {
155 pub upcoming_epoch: Epoch,
157 pub upcoming_environment: Option<ProgramRuntimeEnvironment>,
163 pub programs_to_recompile: Vec<(Pubkey, Arc<ProgramCacheEntry>)>,
165}
166
167impl EpochBoundaryPreparation {
168 pub fn new(epoch: Epoch) -> Self {
169 Self {
170 upcoming_epoch: epoch,
171 upcoming_environment: None,
172 programs_to_recompile: Vec::default(),
173 }
174 }
175
176 pub fn get_upcoming_environment_for_epoch(
178 &self,
179 epoch: Epoch,
180 ) -> Option<ProgramRuntimeEnvironment> {
181 if epoch == self.upcoming_epoch {
182 return self.upcoming_environment.clone();
183 }
184 None
185 }
186
187 pub fn reroot(&mut self, epoch: Epoch) -> Option<ProgramRuntimeEnvironment> {
189 if epoch == self.upcoming_epoch
190 && let Some(upcoming_environment) = self.upcoming_environment.take()
191 {
192 self.programs_to_recompile.clear();
193 return Some(upcoming_environment);
194 }
195
196 None
197 }
198}
199
200#[derive(Clone, PartialEq, Debug)]
202pub struct ProgramToLoad<'a> {
203 pub program_id: &'a Pubkey,
205 pub loader: ProgramCacheEntryOwner,
207 pub deployment_slot: Slot,
212 pub last_modification_slot: Slot,
214}
215
216#[derive(Debug)]
217pub(crate) enum IndexImplementation {
218 V1 {
220 entries: HashMap<Pubkey, Vec<Arc<ProgramCacheEntry>>>,
226 loading_entries: Mutex<HashMap<Pubkey, (Slot, thread::ThreadId)>>,
235 },
236}
237
238pub struct ProgramCache<FG: ForkGraph> {
254 pub(crate) index: IndexImplementation,
256 pub latest_root_slot: Slot,
258 pub stats: ProgramCacheStats,
260 pub fork_graph: Option<Weak<RwLock<FG>>>,
262 pub loading_task_waiter: Arc<LoadingTaskWaiter>,
264}
265
266impl<FG: ForkGraph> std::fmt::Debug for ProgramCache<FG> {
267 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
268 f.debug_struct("ProgramCache")
269 .field("root slot", &self.latest_root_slot)
270 .field("stats", &self.stats)
271 .field("index", &self.index)
272 .finish()
273 }
274}
275
276#[derive(Clone, Debug, Default)]
282pub struct ProgramCacheForTxBatch {
283 entries: HashMap<Pubkey, Arc<ProgramCacheEntry>>,
286 modified_entries: HashMap<Pubkey, Arc<ProgramCacheEntry>>,
288 slot: Slot,
289 pub hit_max_limit: bool,
290 pub loaded_missing: bool,
291 pub merged_modified: bool,
292}
293
294impl ProgramCacheForTxBatch {
295 pub fn new(slot: Slot) -> Self {
296 Self {
297 entries: HashMap::new(),
298 modified_entries: HashMap::new(),
299 slot,
300 hit_max_limit: false,
301 loaded_missing: false,
302 merged_modified: false,
303 }
304 }
305
306 pub fn replenish(
312 &mut self,
313 key: Pubkey,
314 entry: Arc<ProgramCacheEntry>,
315 ) -> (bool, Arc<ProgramCacheEntry>) {
316 (self.entries.insert(key, entry.clone()).is_some(), entry)
317 }
318
319 pub fn store_modified_entry(&mut self, key: Pubkey, entry: Arc<ProgramCacheEntry>) {
322 self.modified_entries.insert(key, entry);
323 }
324
325 pub fn drain_modified_entries(&mut self) -> HashMap<Pubkey, Arc<ProgramCacheEntry>> {
328 std::mem::take(&mut self.modified_entries)
329 }
330
331 pub fn find(&self, key: &Pubkey) -> Option<Arc<ProgramCacheEntry>> {
332 self.modified_entries
336 .get(key)
337 .or_else(|| self.entries.get(key))
338 .map(|entry| {
339 if entry.is_implicit_delay_visibility_tombstone(self.slot) {
340 Arc::new(ProgramCacheEntry::new_delay_visibility_tombstone(
344 entry.deployment_slot,
345 entry.account_owner,
346 Arc::clone(&entry.stats),
347 ))
348 } else {
349 entry.clone()
350 }
351 })
352 }
353
354 pub fn slot(&self) -> Slot {
355 self.slot
356 }
357
358 pub fn set_slot_for_tests(&mut self, slot: Slot) {
359 self.slot = slot;
360 }
361
362 pub fn merge(&mut self, modified_entries: &HashMap<Pubkey, Arc<ProgramCacheEntry>>) {
363 modified_entries.iter().for_each(|(key, entry)| {
364 self.merged_modified = true;
365 self.replenish(*key, entry.clone());
366 })
367 }
368
369 pub fn remove_entry(&mut self, key: &Pubkey) {
372 self.entries.remove(key);
373 }
374
375 pub fn is_empty(&self) -> bool {
376 self.entries.is_empty()
377 }
378}
379
380impl<FG: ForkGraph> ProgramCache<FG> {
381 pub fn new(root_slot: Slot) -> Self {
382 Self {
383 index: IndexImplementation::V1 {
384 entries: HashMap::new(),
385 loading_entries: Mutex::new(HashMap::new()),
386 },
387 latest_root_slot: root_slot,
388 stats: ProgramCacheStats::default(),
389 fork_graph: None,
390 loading_task_waiter: Arc::new(LoadingTaskWaiter::default()),
391 }
392 }
393
394 pub fn set_fork_graph(&mut self, fork_graph: Weak<RwLock<FG>>) {
395 self.fork_graph = Some(fork_graph);
396 }
397
398 pub fn assign_program(
401 &mut self,
402 program_runtime_environment: &ProgramRuntimeEnvironment,
403 key: Pubkey,
404 _last_modification_slot: Slot,
405 entry: Arc<ProgramCacheEntry>,
406 ) -> bool {
407 debug_assert!(
408 !matches!(&entry.program, ProgramCacheEntryType::DelayVisibility),
409 "Unexpected assignment of a DelayVisibility tombstone"
410 );
411 fn is_current_env(
415 program_runtime_environment: &ProgramRuntimeEnvironment,
416 env_opt: Option<&ProgramRuntimeEnvironment>,
417 ) -> bool {
418 env_opt
419 .map(|env| env == program_runtime_environment)
420 .unwrap_or(true)
421 }
422 match &mut self.index {
423 IndexImplementation::V1 { entries, .. } => {
424 let slot_versions = &mut entries.entry(key).or_default();
425 let insertion_point = slot_versions.binary_search_by(|at| {
426 at.deployment_slot
427 .cmp(&entry.deployment_slot)
428 .then(at.account_owner.cmp(&entry.account_owner))
429 .then(
430 is_current_env(
434 program_runtime_environment,
435 at.program.get_environment(),
436 )
437 .cmp(&is_current_env(
438 program_runtime_environment,
439 entry.program.get_environment(),
440 )),
441 )
442 });
443 match insertion_point {
444 Ok(index) => {
445 let existing = slot_versions.get_mut(index).unwrap();
446 match (&existing.program, &entry.program) {
447 (
448 ProgramCacheEntryType::Builtin(_),
449 ProgramCacheEntryType::Builtin(_),
450 )
451 | (ProgramCacheEntryType::Closed, ProgramCacheEntryType::Unloaded(_))
452 | (
453 ProgramCacheEntryType::Unloaded(_),
454 ProgramCacheEntryType::Loaded(_),
455 )
456 | (
457 ProgramCacheEntryType::Unloaded(_),
458 ProgramCacheEntryType::FailedVerification(_),
459 ) => {}
460 _ => {
461 error!(
463 "ProgramCache::assign_program() failed key={key:?} \
464 existing={slot_versions:?} entry={entry:?}"
465 );
466 debug_assert!(false, "Unexpected replacement of an entry");
467 self.stats.replacements.fetch_add(1, Ordering::Relaxed);
468 return true;
469 }
470 }
471 entry.stats.merge_from(&existing.stats);
472 *existing = Arc::clone(&entry);
473 self.stats.reloads.fetch_add(1, Ordering::Relaxed);
474 }
475 Err(index) => {
476 self.stats.insertions.fetch_add(1, Ordering::Relaxed);
477 slot_versions.insert(index, Arc::clone(&entry));
478 }
479 }
480 slot_versions.retain(|existing| {
484 existing.deployment_slot != entry.deployment_slot
485 || existing
486 .program
487 .get_environment()
488 .zip(entry.program.get_environment())
489 .map(|(a, b)| a != b)
490 .unwrap_or(false)
491 || Arc::ptr_eq(existing, &entry)
492 });
493 }
494 }
495 false
496 }
497
498 pub fn prune_by_deployment_slot(&mut self, slot: Slot) {
499 match &mut self.index {
500 IndexImplementation::V1 { entries, .. } => {
501 for second_level in entries.values_mut() {
502 second_level.retain(|entry| entry.deployment_slot != slot);
503 }
504 self.remove_programs_with_no_entries();
505 }
506 }
507 }
508
509 pub fn prune(
511 &mut self,
512 new_root_slot: Slot,
513 new_environment: Option<ProgramRuntimeEnvironment>,
514 fork_graph: &FG,
515 ) {
516 match &mut self.index {
517 IndexImplementation::V1 { entries, .. } => {
518 let tombstone_slot_cutoff =
519 new_root_slot.saturating_sub(MAX_TOMBSTONE_AGE_IN_SLOTS);
520 entries.retain(|_id, second_level| {
521 if let [candidate] = &second_level[..]
523 && (matches!(candidate.program, ProgramCacheEntryType::Unloaded(_))
524 || candidate.is_tombstone())
525 && candidate.deployment_slot <= self.latest_root_slot
526 && candidate.latest_access_slot.load(Ordering::Relaxed)
527 < tombstone_slot_cutoff
528 {
529 self.stats.prunes_stale.fetch_add(1, Ordering::Relaxed);
530 return false;
531 }
532 let mut first_ancestor_found = false;
534 let mut first_ancestor_env = None;
535 *second_level = second_level
536 .iter()
537 .rev()
538 .filter(|entry| {
539 let relation =
540 fork_graph.relationship(entry.deployment_slot, new_root_slot);
541 if entry.deployment_slot >= new_root_slot {
542 matches!(relation, BlockRelation::Equal | BlockRelation::Descendant)
543 } else if matches!(relation, BlockRelation::Ancestor)
544 || entry.deployment_slot <= self.latest_root_slot
545 {
546 if !first_ancestor_found {
547 first_ancestor_found = true;
548 first_ancestor_env = entry.program.get_environment();
549 return true;
550 }
551 if let Some(entry_env) = entry.program.get_environment()
559 && let Some(env) = first_ancestor_env
560 && entry_env != env
561 {
562 return true;
563 }
564 self.stats.prunes_orphan.fetch_add(1, Ordering::Relaxed);
565 false
566 } else {
567 self.stats.prunes_orphan.fetch_add(1, Ordering::Relaxed);
568 false
569 }
570 })
571 .filter(|entry| {
572 if let Some(new_environment) = new_environment.as_ref()
574 && !Self::matches_environment(entry, new_environment)
575 {
576 self.stats
577 .prunes_environment
578 .fetch_add(1, Ordering::Relaxed);
579 return false;
580 }
581 true
582 })
583 .cloned()
584 .collect();
585 second_level.reverse();
586 true
587 });
588 }
589 }
590 self.remove_programs_with_no_entries();
591 debug_assert!(self.latest_root_slot <= new_root_slot);
592 self.latest_root_slot = new_root_slot;
593 }
594
595 fn matches_environment(
596 entry: &Arc<ProgramCacheEntry>,
597 program_runtime_environment: &ProgramRuntimeEnvironment,
598 ) -> bool {
599 let Some(environment) = entry.program.get_environment() else {
600 return true;
601 };
602 environment == program_runtime_environment
603 }
604
605 pub fn extract(
608 &self,
609 search_for: &mut Vec<ProgramToLoad>,
610 loaded_programs_for_tx_batch: &mut ProgramCacheForTxBatch,
611 program_runtime_environment_for_execution: &ProgramRuntimeEnvironment,
612 increment_usage_counter: bool,
613 count_hits_and_misses: bool,
614 ) -> Option<Pubkey> {
615 debug_assert!(self.fork_graph.is_some());
616 let fork_graph = self.fork_graph.as_ref().unwrap().upgrade().unwrap();
617 let locked_fork_graph = fork_graph.read().unwrap();
618 let entries_in_batch = loaded_programs_for_tx_batch.entries.len();
619 let mut cooperative_loading_task = None;
620 match &self.index {
621 IndexImplementation::V1 {
622 entries,
623 loading_entries,
624 } => {
625 search_for.retain(|program_to_load| {
626 if let Some(second_level) = entries.get(program_to_load.program_id) {
627 for entry in second_level.iter().rev() {
628 if program_to_load.deployment_slot != entry.deployment_slot
631 || program_to_load.loader != entry.account_owner
632 {
633 continue;
634 }
635
636 let entry_in_same_branch = entry.deployment_slot
642 <= self.latest_root_slot
643 || matches!(
644 locked_fork_graph.relationship(
645 entry.deployment_slot,
646 loaded_programs_for_tx_batch.slot
647 ),
648 BlockRelation::Equal | BlockRelation::Ancestor
649 );
650 if entry_in_same_branch {
651 let entry_is_effective =
652 loaded_programs_for_tx_batch.slot >= entry.effective_slot();
653 let entry_to_return = if entry_is_effective {
654 if !Self::matches_environment(
655 entry,
656 program_runtime_environment_for_execution,
657 ) {
658 continue;
662 }
663 if let ProgramCacheEntryType::Unloaded(_environment) =
664 &entry.program
665 {
666 break;
667 }
668 entry.clone()
669 } else if entry.is_implicit_delay_visibility_tombstone(
670 loaded_programs_for_tx_batch.slot,
671 ) {
672 Arc::new(ProgramCacheEntry::new_delay_visibility_tombstone(
676 entry.deployment_slot,
677 entry.account_owner,
678 Arc::clone(&entry.stats),
679 ))
680 } else {
681 continue;
682 };
683 entry.update_access_slot(loaded_programs_for_tx_batch.slot);
684 if increment_usage_counter {
685 entry_to_return.stats.uses.fetch_add(1, Ordering::Relaxed);
686 }
687 loaded_programs_for_tx_batch
688 .entries
689 .insert(*program_to_load.program_id, entry_to_return);
690 return false;
691 }
692 }
693 }
694 if cooperative_loading_task.is_none() {
695 let mut loading_entries = loading_entries.lock().unwrap();
696 let entry = loading_entries.entry(*program_to_load.program_id);
697 if let Entry::Vacant(entry) = entry {
698 entry.insert((
699 loaded_programs_for_tx_batch.slot,
700 thread::current().id(),
701 ));
702 cooperative_loading_task = Some(*program_to_load.program_id);
703 }
704 }
705 true
706 });
707 }
708 }
709 drop(locked_fork_graph);
710 if count_hits_and_misses {
711 let misses = search_for.len() as u64;
712 let hits = loaded_programs_for_tx_batch
713 .entries
714 .len()
715 .saturating_sub(entries_in_batch) as u64;
716 self.stats.misses.fetch_add(misses, Ordering::Relaxed);
717 self.stats.hits.fetch_add(hits, Ordering::Relaxed);
718 }
719 cooperative_loading_task
720 }
721
722 pub fn finish_cooperative_loading_task(
724 &mut self,
725 program_runtime_environment: &ProgramRuntimeEnvironment,
726 current_slot: Slot,
727 key: Pubkey,
728 last_modification_slot: Slot,
729 loaded_program: Arc<ProgramCacheEntry>,
730 ) -> bool {
731 match &mut self.index {
732 IndexImplementation::V1 {
733 loading_entries, ..
734 } => {
735 let loading_thread = loading_entries.get_mut().unwrap().remove(&key);
736 debug_assert_eq!(loading_thread, Some((current_slot, thread::current().id())));
737 if loaded_program.deployment_slot > self.latest_root_slot
739 && !matches!(
740 self.fork_graph
741 .as_ref()
742 .unwrap()
743 .upgrade()
744 .unwrap()
745 .read()
746 .unwrap()
747 .relationship(loaded_program.deployment_slot, current_slot),
748 BlockRelation::Equal | BlockRelation::Ancestor
749 )
750 {
751 self.stats.lost_insertions.fetch_add(1, Ordering::Relaxed);
752 }
753 let was_occupied = self.assign_program(
754 program_runtime_environment,
755 key,
756 last_modification_slot,
757 loaded_program,
758 );
759 self.loading_task_waiter.notify();
760 was_occupied
761 }
762 }
763 }
764
765 pub fn merge(
766 &mut self,
767 program_runtime_environment: &ProgramRuntimeEnvironment,
768 current_slot: Slot,
769 modified_entries: &HashMap<Pubkey, Arc<ProgramCacheEntry>>,
770 ) {
771 modified_entries.iter().for_each(|(key, entry)| {
772 self.assign_program(
773 program_runtime_environment,
774 *key,
775 current_slot,
776 entry.clone(),
777 );
778 })
779 }
780
781 pub fn get_flattened_entries(&self) -> Vec<(Pubkey, Slot, Arc<ProgramCacheEntry>)> {
783 match &self.index {
784 IndexImplementation::V1 { entries, .. } => entries
785 .iter()
786 .flat_map(|(id, second_level)| {
787 second_level
788 .iter()
789 .filter_map(move |program| match program.program {
790 ProgramCacheEntryType::Loaded(_) => Some((*id, 0, program.clone())),
791 _ => None,
792 })
793 })
794 .collect(),
795 }
796 }
797
798 #[cfg(feature = "dev-context-only-utils")]
800 pub fn get_flattened_entries_for_tests(&self) -> Vec<(Pubkey, Arc<ProgramCacheEntry>)> {
801 match &self.index {
802 IndexImplementation::V1 { entries, .. } => entries
803 .iter()
804 .flat_map(|(id, second_level)| {
805 second_level.iter().map(|program| (*id, program.clone()))
806 })
807 .collect(),
808 }
809 }
810
811 pub fn get_slot_versions_for_tests(&self, key: &Pubkey) -> &[Arc<ProgramCacheEntry>] {
813 match &self.index {
814 IndexImplementation::V1 { entries, .. } => entries
815 .get(key)
816 .map(|second_level| second_level.as_ref())
817 .unwrap_or(&[]),
818 }
819 }
820
821 pub fn sort_and_unload(&mut self, shrink_to_percent: Percent) {
823 let mut sorted_candidates = self.get_flattened_entries();
824 sorted_candidates.sort_by_cached_key(|(_id, _last_modification_slot, program)| {
825 program.stats.uses.load(Ordering::Relaxed)
826 });
827 let num_to_unload = sorted_candidates
828 .len()
829 .saturating_sub(percent_of_max_entries(shrink_to_percent));
830 for (program, last_modification_slot, entry) in sorted_candidates.iter().take(num_to_unload)
831 {
832 self.unload_program_entry(*program, *last_modification_slot, entry);
833 }
834 }
835
836 pub fn evict_using_random_selection(&mut self, shrink_to_percent: Percent, now: Slot) {
842 let mut candidates = self.get_flattened_entries();
843 let mut rng = rng();
844 self.stats
845 .water_level
846 .store(candidates.len() as u64, Ordering::Relaxed);
847 let num_to_unload = candidates
848 .len()
849 .saturating_sub(percent_of_max_entries(shrink_to_percent));
850 let mut sample_entry = |candidates: &Vec<(Pubkey, u64, Arc<ProgramCacheEntry>)>| {
851 #[cfg(feature = "shuttle-test")]
854 let index = rng.gen_range(0..candidates.len());
855 #[cfg(not(feature = "shuttle-test"))]
856 let index = rng.random_range(0..candidates.len());
857 let usage_counter = candidates
858 .get(index)
859 .expect("Failed to get cached entry")
860 .2
861 .retention_score();
862 (index, usage_counter)
863 };
864
865 const MAX_ADDITIONAL_SAMPLES: usize = 3;
873 let avoid_evicting_above_score = retention_score(now, 500 * EMA_SCALE, 500);
874 for _ in 0..num_to_unload {
875 let (mut index, mut score) = sample_entry(&candidates);
876 for _ in 0..MAX_ADDITIONAL_SAMPLES {
877 let (sample_index, sample_score) = sample_entry(&candidates);
878 if score > sample_score {
879 index = sample_index;
880 score = sample_score;
881 }
882 if score < avoid_evicting_above_score {
883 break;
884 }
885 }
886 let (id, last_modification_slot, entry) = candidates.swap_remove(index);
887 self.unload_program_entry(id, last_modification_slot, &entry);
888 }
889 }
890
891 pub fn remove_programs(&mut self, keys: impl Iterator<Item = Pubkey>) {
893 match &mut self.index {
894 IndexImplementation::V1 { entries, .. } => {
895 for k in keys {
896 entries.remove(&k);
897 }
898 }
899 }
900 }
901
902 fn unload_program_entry(
905 &mut self,
906 id: Pubkey,
907 _last_modification_slot: Slot,
908 remove_entry: &Arc<ProgramCacheEntry>,
909 ) {
910 match &mut self.index {
911 IndexImplementation::V1 { entries, .. } => {
912 let second_level = entries.get_mut(&id).expect("Cache lookup failed");
913 let candidate = second_level
914 .iter_mut()
915 .find(|entry| Arc::ptr_eq(entry, remove_entry))
916 .expect("Program entry not found");
917
918 if let ProgramCacheEntryType::Loaded(_) = candidate.program
920 && let Some(unloaded) = candidate.to_unloaded()
921 {
922 if candidate.stats.uses.load(Ordering::Relaxed) == 1 {
923 self.stats.one_hit_wonders.fetch_add(1, Ordering::Relaxed);
924 }
925 self.stats
926 .evictions
927 .entry(id)
928 .and_modify(|c| *c = c.saturating_add(1))
929 .or_insert(1);
930 *candidate = Arc::new(unloaded);
931 }
932 }
933 }
934 }
935
936 fn remove_programs_with_no_entries(&mut self) {
937 match &mut self.index {
938 IndexImplementation::V1 { entries, .. } => {
939 let num_programs_before_removal = entries.len();
940 entries.retain(|_key, second_level| !second_level.is_empty());
941 if entries.len() < num_programs_before_removal {
942 self.stats.empty_entries.fetch_add(
943 num_programs_before_removal.saturating_sub(entries.len()) as u64,
944 Ordering::Relaxed,
945 );
946 }
947 }
948 }
949 }
950}
951
952#[cfg(feature = "frozen-abi")]
953impl solana_frozen_abi::abi_example::AbiExample for ProgramCacheEntry {
954 fn example() -> Self {
955 Self::default()
957 }
958}
959
960#[cfg(feature = "frozen-abi")]
961impl<FG: ForkGraph> solana_frozen_abi::abi_example::AbiExample for ProgramCache<FG> {
962 fn example() -> Self {
963 Self::new(Slot::default())
965 }
966}
967
968#[cfg(test)]
969pub(crate) mod tests {
970 use {
971 crate::{
972 loaded_programs::{
973 BlockRelation, ForkGraph, IndexImplementation, MAX_TOMBSTONE_AGE_IN_SLOTS, Percent,
974 ProgramCache, ProgramCacheForTxBatch, ProgramRuntimeEnvironment, ProgramToLoad,
975 get_mock_program_runtime_environment,
976 },
977 program_cache_entry::{
978 ProgramCacheEntry, ProgramCacheEntryOwner, ProgramCacheEntryType,
979 },
980 program_metrics::ProgramStatistics,
981 },
982 assert_matches::assert_matches,
983 solana_clock::Slot,
984 solana_pubkey::Pubkey,
985 solana_sbpf::{elf::Executable, program::BuiltinProgram},
986 solana_svm_type_overrides::{
987 sync::{
988 Arc, RwLock,
989 atomic::{AtomicU64, Ordering},
990 },
991 thread,
992 },
993 std::{fs::File, io::Read, ops::ControlFlow},
994 test_case::{test_case, test_matrix},
995 };
996
997 fn new_test_entry(deployment_slot: Slot) -> Arc<ProgramCacheEntry> {
998 new_test_entry_with_usage(deployment_slot, ProgramStatistics::default())
999 }
1000
1001 fn new_unloaded_entry(env: ProgramRuntimeEnvironment) -> ProgramCacheEntryType {
1002 ProgramCacheEntryType::Unloaded(env)
1003 }
1004
1005 fn new_loaded_entry(env: ProgramRuntimeEnvironment) -> ProgramCacheEntryType {
1006 let mut elf = Vec::new();
1007 File::open("../programs/bpf_loader/test_elfs/out/noop_aligned.so")
1008 .unwrap()
1009 .read_to_end(&mut elf)
1010 .unwrap();
1011 let executable = Executable::load(&elf, Arc::clone(&*env)).unwrap();
1012 ProgramCacheEntryType::Loaded(executable)
1013 }
1014
1015 fn new_test_entry_with_owner(
1016 deployment_slot: Slot,
1017 account_owner: ProgramCacheEntryOwner,
1018 program: ProgramCacheEntryType,
1019 ) -> Arc<ProgramCacheEntry> {
1020 Arc::new(ProgramCacheEntry {
1021 program,
1022 account_owner,
1023 deployment_slot,
1024 stats: Arc::default(),
1025 latest_access_slot: AtomicU64::default(),
1026 })
1027 }
1028
1029 fn new_test_cache_with_fork_graph(
1030 relation: BlockRelation,
1031 ) -> (ProgramCache<TestForkGraph>, Arc<RwLock<TestForkGraph>>) {
1032 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1033 let fork_graph = Arc::new(RwLock::new(TestForkGraph { relation }));
1034 cache.set_fork_graph(Arc::downgrade(&fork_graph));
1035 (cache, fork_graph)
1036 }
1037
1038 pub(crate) fn new_test_entry_with_usage(
1039 deployment_slot: Slot,
1040 stats: ProgramStatistics,
1041 ) -> Arc<ProgramCacheEntry> {
1042 Arc::new(ProgramCacheEntry {
1043 program: new_loaded_entry(get_mock_program_runtime_environment()),
1044 account_owner: ProgramCacheEntryOwner::LoaderV2,
1045 deployment_slot,
1046 stats: Arc::new(stats),
1047 latest_access_slot: AtomicU64::new(deployment_slot),
1048 })
1049 }
1050
1051 fn new_test_builtin_entry(deployment_slot: Slot) -> Arc<ProgramCacheEntry> {
1052 Arc::new(ProgramCacheEntry {
1053 program: ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1054 account_owner: ProgramCacheEntryOwner::NativeLoader,
1055 deployment_slot,
1056 stats: Arc::default(),
1057 latest_access_slot: AtomicU64::default(),
1058 })
1059 }
1060
1061 fn set_failed_verification_tombstone<FG: ForkGraph>(
1062 cache: &mut ProgramCache<FG>,
1063 key: Pubkey,
1064 current_slot: Slot,
1065 env: ProgramRuntimeEnvironment,
1066 ) -> Arc<ProgramCacheEntry> {
1067 let program = Arc::new(ProgramCacheEntry::new_failed_verification_tombstone(
1068 current_slot,
1069 ProgramCacheEntryOwner::LoaderV2,
1070 ProgramRuntimeEnvironment::clone(&env),
1071 ));
1072 cache.assign_program(&env, key, current_slot, program.clone());
1073 program
1074 }
1075
1076 fn insert_unloaded_entry<FG: ForkGraph>(
1077 cache: &mut ProgramCache<FG>,
1078 key: Pubkey,
1079 current_slot: Slot,
1080 ) -> Arc<ProgramCacheEntry> {
1081 let env = get_mock_program_runtime_environment();
1082 let loaded = new_test_entry_with_usage(current_slot, ProgramStatistics::default());
1083 let unloaded = Arc::new(loaded.to_unloaded().expect("Failed to unload the program"));
1084 cache.assign_program(&env, key, current_slot, unloaded.clone());
1085 unloaded
1086 }
1087
1088 fn num_matching_entries<P, FG>(cache: &ProgramCache<FG>, predicate: P) -> usize
1089 where
1090 P: Fn(&ProgramCacheEntryType) -> bool,
1091 FG: ForkGraph,
1092 {
1093 cache
1094 .get_flattened_entries_for_tests()
1095 .iter()
1096 .filter(|(_key, program)| predicate(&program.program))
1097 .count()
1098 }
1099
1100 #[expect(clippy::arithmetic_side_effects)]
1101 fn program_deploy_test_helper(
1102 cache: &mut ProgramCache<TestForkGraph>,
1103 program: Pubkey,
1104 deployment_slots: Vec<Slot>,
1105 usage_counters: Vec<u64>,
1106 programs: &mut Vec<(Pubkey, Slot, u64)>,
1107 ) {
1108 let env = get_mock_program_runtime_environment();
1109 deployment_slots
1111 .iter()
1112 .enumerate()
1113 .for_each(|(i, deployment_slot)| {
1114 let usage_counter = *usage_counters.get(i).unwrap_or(&0);
1115 let stats = ProgramStatistics {
1116 uses: usage_counter.into(),
1117 ..Default::default()
1118 };
1119 cache.assign_program(
1120 &env,
1121 program,
1122 *deployment_slot,
1123 new_test_entry_with_usage(*deployment_slot, stats),
1124 );
1125 programs.push((program, *deployment_slot, usage_counter));
1126 });
1127
1128 let next_slot = deployment_slots.iter().max().map_or(0, |slot| slot + 1);
1129
1130 let env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
1132 for slot in next_slot..next_slot + 10 {
1133 set_failed_verification_tombstone(
1134 cache,
1135 program,
1136 slot,
1137 ProgramRuntimeEnvironment::clone(&env),
1138 );
1139 }
1140
1141 for slot in next_slot + 10..next_slot + 20 {
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, 30, 40],
1161 vec![4, 5, 25, 35, 12],
1162 &mut programs,
1163 );
1164
1165 program_deploy_test_helper(
1167 &mut cache,
1168 Pubkey::new_unique(),
1169 vec![5, 11, 21, 24],
1170 vec![0, 2, 30, 45],
1171 &mut programs,
1172 );
1173
1174 program_deploy_test_helper(
1176 &mut cache,
1177 Pubkey::new_unique(),
1178 vec![0, 5, 15, 25],
1179 vec![100, 3, 20, 40],
1180 &mut programs,
1181 );
1182
1183 let num_loaded_expected = 13;
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, 30, 40],
1248 vec![4, 5, 25, 35, 12],
1249 &mut programs,
1250 );
1251
1252 program_deploy_test_helper(
1254 &mut cache,
1255 Pubkey::new_unique(),
1256 vec![5, 11, 21, 24],
1257 vec![0, 2, 30, 45],
1258 &mut programs,
1259 );
1260
1261 program_deploy_test_helper(
1263 &mut cache,
1264 Pubkey::new_unique(),
1265 vec![0, 5, 15, 25],
1266 vec![100, 3, 20, 40],
1267 &mut programs,
1268 );
1269
1270 let num_loaded_expected = 13;
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 #[should_panic(expected = "Unexpected assignment of a DelayVisibility tombstone")]
1409 fn test_assign_program_delay_visibility_tombstone_panics() {
1410 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1413 let env = get_mock_program_runtime_environment();
1414 cache.assign_program(
1415 &env,
1416 Pubkey::new_unique(),
1417 100,
1418 Arc::new(ProgramCacheEntry::new_delay_visibility_tombstone(
1419 100,
1420 ProgramCacheEntryOwner::LoaderV3,
1421 Arc::default(),
1422 )),
1423 );
1424 }
1425
1426 #[test]
1427 fn test_fuzz_assign_program_order() {
1428 use rand::prelude::SliceRandom;
1429 const EXPECTED_ENTRIES: [(u64, bool); 5] =
1430 [(1, true), (3, false), (5, true), (9, true), (10, false)];
1431 let mut rng = rand::rng();
1432 let program_id = Pubkey::new_unique();
1433 let env = get_mock_program_runtime_environment();
1434 for _ in 0..1000 {
1435 let mut entries = EXPECTED_ENTRIES.to_vec();
1436 entries.shuffle(&mut rng);
1437 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1438 for (deployment_slot, delay_visibility) in entries {
1439 let entry = Arc::new(if delay_visibility {
1440 ProgramCacheEntry {
1441 program: new_loaded_entry(ProgramRuntimeEnvironment::from(
1442 BuiltinProgram::new_mock(),
1443 )), account_owner: ProgramCacheEntryOwner::LoaderV2,
1445 deployment_slot,
1446 stats: Arc::default(),
1447 latest_access_slot: AtomicU64::new(deployment_slot),
1448 }
1449 } else {
1450 ProgramCacheEntry::new_failed_verification_tombstone(
1451 deployment_slot,
1452 ProgramCacheEntryOwner::LoaderV2,
1453 ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock()), )
1455 });
1456 assert!(!cache.assign_program(&env, program_id, deployment_slot, entry));
1457 }
1458 for ((deployment_slot, delay_visibility), entry) in EXPECTED_ENTRIES
1459 .iter()
1460 .zip(cache.get_slot_versions_for_tests(&program_id).iter())
1461 {
1462 assert_eq!(entry.deployment_slot, *deployment_slot);
1463 assert_eq!(
1464 entry.effective_slot(),
1465 deployment_slot.saturating_add(*delay_visibility as u64)
1466 );
1467 }
1468 }
1469 }
1470
1471 #[test_matrix(
1472 (
1473 ProgramCacheEntryType::FailedVerification(get_mock_program_runtime_environment()),
1474 new_loaded_entry(get_mock_program_runtime_environment()),
1475 ),
1476 (
1477 ProgramCacheEntryType::FailedVerification(get_mock_program_runtime_environment()),
1478 ProgramCacheEntryType::Closed,
1479 ProgramCacheEntryType::Unloaded(get_mock_program_runtime_environment()),
1480 new_loaded_entry(get_mock_program_runtime_environment()),
1481 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1482 )
1483 )]
1484 #[test_matrix(
1485 ProgramCacheEntryType::Closed,
1486 (
1487 ProgramCacheEntryType::FailedVerification(get_mock_program_runtime_environment()),
1488 ProgramCacheEntryType::Closed,
1489 new_loaded_entry(get_mock_program_runtime_environment()),
1490 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1491 )
1492 )]
1493 #[test_matrix(
1494 ProgramCacheEntryType::Unloaded(get_mock_program_runtime_environment()),
1495 (
1496 ProgramCacheEntryType::Closed,
1497 ProgramCacheEntryType::Unloaded(get_mock_program_runtime_environment()),
1498 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1499 )
1500 )]
1501 #[test_matrix(
1502 (ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),),
1503 (
1504 ProgramCacheEntryType::FailedVerification(get_mock_program_runtime_environment()),
1505 ProgramCacheEntryType::Closed,
1506 ProgramCacheEntryType::Unloaded(get_mock_program_runtime_environment()),
1507 new_loaded_entry(get_mock_program_runtime_environment()),
1508 )
1509 )]
1510 #[should_panic(expected = "Unexpected replacement of an entry")]
1511 fn test_assign_program_failure(old: ProgramCacheEntryType, new: ProgramCacheEntryType) {
1512 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1513 let env = get_mock_program_runtime_environment();
1514 let program_id = Pubkey::new_unique();
1515 assert!(!cache.assign_program(
1516 &env,
1517 program_id,
1518 10,
1519 Arc::new(ProgramCacheEntry {
1520 program: old,
1521 account_owner: ProgramCacheEntryOwner::LoaderV2,
1522 deployment_slot: 10,
1523 stats: Arc::default(),
1524 latest_access_slot: AtomicU64::default(),
1525 }),
1526 ));
1527 cache.assign_program(
1528 &env,
1529 program_id,
1530 10,
1531 Arc::new(ProgramCacheEntry {
1532 program: new,
1533 account_owner: ProgramCacheEntryOwner::LoaderV2,
1534 deployment_slot: 10,
1535 stats: Arc::default(),
1536 latest_access_slot: AtomicU64::default(),
1537 }),
1538 );
1539 }
1540
1541 #[test_matrix(
1542 ProgramCacheEntryType::Unloaded(get_mock_program_runtime_environment()),
1543 (
1544 new_loaded_entry(get_mock_program_runtime_environment()),
1545 ProgramCacheEntryType::FailedVerification(get_mock_program_runtime_environment()),
1546 )
1547 )]
1548 #[test_case(
1549 ProgramCacheEntryType::Closed,
1550 ProgramCacheEntryType::Unloaded(get_mock_program_runtime_environment())
1551 )]
1552 #[test_case(
1553 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1554 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock())
1555 )]
1556 fn test_assign_program_success(old: ProgramCacheEntryType, new: ProgramCacheEntryType) {
1557 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1558 let env = get_mock_program_runtime_environment();
1559 let program_id = Pubkey::new_unique();
1560 assert!(!cache.assign_program(
1561 &env,
1562 program_id,
1563 10,
1564 Arc::new(ProgramCacheEntry {
1565 program: old,
1566 account_owner: ProgramCacheEntryOwner::LoaderV2,
1567 deployment_slot: 10,
1568 stats: Arc::default(),
1569 latest_access_slot: AtomicU64::default(),
1570 }),
1571 ));
1572 assert!(!cache.assign_program(
1573 &env,
1574 program_id,
1575 10,
1576 Arc::new(ProgramCacheEntry {
1577 program: new,
1578 account_owner: ProgramCacheEntryOwner::LoaderV2,
1579 deployment_slot: 10,
1580 stats: Arc::default(),
1581 latest_access_slot: AtomicU64::default(),
1582 }),
1583 ));
1584 }
1585
1586 #[test]
1587 fn test_assign_program_removes_entries_in_same_slot() {
1588 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1589 let env = get_mock_program_runtime_environment();
1590 let program_id = Pubkey::new_unique();
1591 let closed_other_slot = Arc::new(ProgramCacheEntry {
1592 program: ProgramCacheEntryType::Closed,
1593 account_owner: ProgramCacheEntryOwner::LoaderV2,
1594 deployment_slot: 9,
1595 stats: Arc::default(),
1596 latest_access_slot: AtomicU64::default(),
1597 });
1598 let closed_current_slot = Arc::new(ProgramCacheEntry {
1599 program: ProgramCacheEntryType::Closed,
1600 account_owner: ProgramCacheEntryOwner::LoaderV2,
1601 deployment_slot: 10,
1602 stats: Arc::default(),
1603 latest_access_slot: AtomicU64::default(),
1604 });
1605 let loaded_entry_current_env = Arc::new(ProgramCacheEntry {
1606 program: ProgramCacheEntryType::Unloaded(get_mock_program_runtime_environment()),
1607 account_owner: ProgramCacheEntryOwner::LoaderV2,
1608 deployment_slot: 10,
1609 stats: Arc::default(),
1610 latest_access_slot: AtomicU64::default(),
1611 });
1612 let loaded_entry_upcoming_env = Arc::new(ProgramCacheEntry {
1613 program: ProgramCacheEntryType::Unloaded(ProgramRuntimeEnvironment::from(
1614 BuiltinProgram::new_mock(),
1615 )),
1616 account_owner: ProgramCacheEntryOwner::LoaderV2,
1617 deployment_slot: 10,
1618 stats: Arc::default(),
1619 latest_access_slot: AtomicU64::default(),
1620 });
1621 assert!(!cache.assign_program(&env, program_id, 9, closed_other_slot.clone()));
1622 assert!(!cache.assign_program(&env, program_id, 10, closed_current_slot));
1623 assert!(!cache.assign_program(&env, program_id, 10, loaded_entry_upcoming_env.clone()));
1624 assert!(!cache.assign_program(&env, program_id, 10, loaded_entry_current_env.clone()));
1625 assert_eq!(
1627 cache.get_slot_versions_for_tests(&program_id),
1628 &[
1629 closed_other_slot,
1630 loaded_entry_current_env,
1631 loaded_entry_upcoming_env
1632 ]
1633 );
1634 }
1635
1636 #[test]
1637 fn test_assign_program_reload_merges_statistics() {
1638 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1639 let env = get_mock_program_runtime_environment();
1640 let program_id = Pubkey::new_unique();
1641
1642 let stats = Arc::new(ProgramStatistics {
1643 uses: 1.into(),
1644 compilations: 2.into(),
1645 total_compilation_time_us: 3.into(),
1646 compilation_time_ema: 100.into(),
1647 jit_invocations: 4.into(),
1648 total_jit_execution_time_us: 5.into(),
1649 jit_execution_time_ema: 200.into(),
1650 interpreted_invocations: 6.into(),
1651 total_interpretation_time_us: 7.into(),
1652 interpretation_time_ema: 300.into(),
1653 });
1654 let unloaded = Arc::new(ProgramCacheEntry {
1655 program: ProgramCacheEntryType::Unloaded(env.clone()),
1656 account_owner: ProgramCacheEntryOwner::LoaderV3,
1657 deployment_slot: 100,
1658 stats: Arc::clone(&stats),
1659 latest_access_slot: AtomicU64::default(),
1660 });
1661 cache.assign_program(&env, program_id, 100, unloaded);
1662
1663 let loaded = Arc::new(ProgramCacheEntry {
1665 program: new_loaded_entry(env.clone()),
1666 account_owner: ProgramCacheEntryOwner::LoaderV3,
1667 deployment_slot: 100,
1668 stats: Arc::default(), latest_access_slot: AtomicU64::default(),
1670 });
1671 cache.assign_program(&env, program_id, 100, Arc::clone(&loaded));
1672
1673 assert_eq!(cache.stats.insertions.load(Ordering::Relaxed), 1);
1674 assert_eq!(cache.stats.reloads.load(Ordering::Relaxed), 1);
1675
1676 let merged = &loaded.stats;
1677 let ord = Ordering::Relaxed;
1678 assert_eq!(merged.uses.load(ord), stats.uses.load(ord));
1679 assert_eq!(merged.compilations.load(ord), stats.compilations.load(ord));
1680 assert_eq!(
1681 merged.total_compilation_time_us.load(ord),
1682 stats.total_compilation_time_us.load(ord)
1683 );
1684 assert_eq!(
1685 merged.jit_invocations.load(ord),
1686 stats.jit_invocations.load(ord)
1687 );
1688 assert_eq!(
1689 merged.total_jit_execution_time_us.load(ord),
1690 stats.total_jit_execution_time_us.load(ord)
1691 );
1692 assert_eq!(
1693 merged.interpreted_invocations.load(ord),
1694 stats.interpreted_invocations.load(ord)
1695 );
1696 assert_eq!(
1697 merged.total_interpretation_time_us.load(ord),
1698 stats.total_interpretation_time_us.load(ord)
1699 );
1700
1701 const EMA_DIVISOR: u64 = 2;
1704 assert_eq!(
1705 merged.compilation_time_ema.load(ord),
1706 stats
1707 .compilation_time_ema
1708 .load(ord)
1709 .wrapping_div(EMA_DIVISOR)
1710 );
1711 assert_eq!(
1712 merged.jit_execution_time_ema.load(ord),
1713 stats
1714 .jit_execution_time_ema
1715 .load(ord)
1716 .wrapping_div(EMA_DIVISOR)
1717 );
1718 assert_eq!(
1719 merged.interpretation_time_ema.load(ord),
1720 stats
1721 .interpretation_time_ema
1722 .load(ord)
1723 .wrapping_div(EMA_DIVISOR)
1724 );
1725 }
1726
1727 #[test]
1728 fn test_tombstone() {
1729 let env = get_mock_program_runtime_environment();
1730 let tombstone = ProgramCacheEntry::new_failed_verification_tombstone(
1731 0,
1732 ProgramCacheEntryOwner::LoaderV2,
1733 env.clone(),
1734 );
1735 assert_matches!(
1736 tombstone.program,
1737 ProgramCacheEntryType::FailedVerification(_)
1738 );
1739 assert!(tombstone.is_tombstone());
1740 assert_eq!(tombstone.deployment_slot, 0);
1741 assert_eq!(tombstone.effective_slot(), 0);
1742
1743 let tombstone =
1744 ProgramCacheEntry::new_closed_tombstone(100, ProgramCacheEntryOwner::LoaderV2);
1745 assert_matches!(tombstone.program, ProgramCacheEntryType::Closed);
1746 assert!(tombstone.is_tombstone());
1747 assert_eq!(tombstone.deployment_slot, 100);
1748 assert_eq!(tombstone.effective_slot(), 100);
1749
1750 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1751 let program1 = Pubkey::new_unique();
1752 let tombstone = set_failed_verification_tombstone(&mut cache, program1, 10, env.clone());
1753 let slot_versions = cache.get_slot_versions_for_tests(&program1);
1754 assert_eq!(slot_versions.len(), 1);
1755 assert!(slot_versions.first().unwrap().is_tombstone());
1756 assert_eq!(tombstone.deployment_slot, 10);
1757 assert_eq!(tombstone.effective_slot(), 10);
1758
1759 let program2 = Pubkey::new_unique();
1761 cache.assign_program(&env, program2, 50, new_test_builtin_entry(50));
1762 let slot_versions = cache.get_slot_versions_for_tests(&program2);
1763 assert_eq!(slot_versions.len(), 1);
1764 assert!(!slot_versions.first().unwrap().is_tombstone());
1765
1766 let tombstone = set_failed_verification_tombstone(&mut cache, program2, 60, env);
1767 let slot_versions = cache.get_slot_versions_for_tests(&program2);
1768 assert_eq!(slot_versions.len(), 2);
1769 assert!(!slot_versions.first().unwrap().is_tombstone());
1770 assert!(slot_versions.get(1).unwrap().is_tombstone());
1771 assert!(tombstone.is_tombstone());
1772 assert_eq!(tombstone.deployment_slot, 60);
1773 assert_eq!(tombstone.effective_slot(), 60);
1774 }
1775
1776 struct TestForkGraph {
1777 relation: BlockRelation,
1778 }
1779 impl ForkGraph for TestForkGraph {
1780 fn relationship(&self, _a: Slot, _b: Slot) -> BlockRelation {
1781 self.relation
1782 }
1783 }
1784
1785 #[test]
1786 fn test_prune_empty() {
1787 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1788 let fork_graph = Arc::new(RwLock::new(TestForkGraph {
1789 relation: BlockRelation::Unrelated,
1790 }));
1791
1792 cache.set_fork_graph(Arc::downgrade(&fork_graph));
1793
1794 cache.prune(0, None, &fork_graph.read().unwrap());
1795 assert!(cache.get_flattened_entries_for_tests().is_empty());
1796
1797 cache.prune(10, None, &fork_graph.read().unwrap());
1798 assert!(cache.get_flattened_entries_for_tests().is_empty());
1799
1800 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1801 let fork_graph = Arc::new(RwLock::new(TestForkGraph {
1802 relation: BlockRelation::Ancestor,
1803 }));
1804
1805 cache.set_fork_graph(Arc::downgrade(&fork_graph));
1806
1807 cache.prune(0, None, &fork_graph.read().unwrap());
1808 assert!(cache.get_flattened_entries_for_tests().is_empty());
1809
1810 cache.prune(10, None, &fork_graph.read().unwrap());
1811 assert!(cache.get_flattened_entries_for_tests().is_empty());
1812
1813 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1814 let fork_graph = Arc::new(RwLock::new(TestForkGraph {
1815 relation: BlockRelation::Descendant,
1816 }));
1817
1818 cache.set_fork_graph(Arc::downgrade(&fork_graph));
1819
1820 cache.prune(0, None, &fork_graph.read().unwrap());
1821 assert!(cache.get_flattened_entries_for_tests().is_empty());
1822
1823 cache.prune(10, None, &fork_graph.read().unwrap());
1824 assert!(cache.get_flattened_entries_for_tests().is_empty());
1825
1826 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1827 let fork_graph = Arc::new(RwLock::new(TestForkGraph {
1828 relation: BlockRelation::Unknown,
1829 }));
1830 cache.set_fork_graph(Arc::downgrade(&fork_graph));
1831
1832 cache.prune(0, None, &fork_graph.read().unwrap());
1833 assert!(cache.get_flattened_entries_for_tests().is_empty());
1834
1835 cache.prune(10, None, &fork_graph.read().unwrap());
1836 assert!(cache.get_flattened_entries_for_tests().is_empty());
1837 }
1838
1839 #[test]
1840 fn test_prune_tombstones() {
1841 let env = get_mock_program_runtime_environment();
1842 let fork_graph = Arc::new(RwLock::new(TestForkGraph {
1843 relation: BlockRelation::Ancestor,
1844 }));
1845
1846 let program1 = Pubkey::new_unique();
1847 let entries = [
1848 Arc::new(ProgramCacheEntry::new_unloaded(
1849 20,
1850 ProgramCacheEntryOwner::LoaderV3,
1851 ProgramRuntimeEnvironment::clone(&env),
1852 )),
1853 Arc::new(ProgramCacheEntry::new_closed_tombstone(
1854 20,
1855 ProgramCacheEntryOwner::LoaderV3,
1856 )),
1857 Arc::new(ProgramCacheEntry::new_failed_verification_tombstone(
1858 20,
1859 ProgramCacheEntryOwner::LoaderV3,
1860 ProgramRuntimeEnvironment::clone(&env),
1861 )),
1862 ];
1863 for entry in &entries {
1864 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1865 cache.set_fork_graph(Arc::downgrade(&fork_graph));
1866 cache.assign_program(&env, program1, 10, new_test_entry(10));
1868 cache.assign_program(&env, program1, entry.deployment_slot, Arc::clone(entry));
1869 cache.prune(
1870 MAX_TOMBSTONE_AGE_IN_SLOTS,
1871 None,
1872 &fork_graph.read().unwrap(),
1873 );
1874 let slot_versions = cache.get_slot_versions_for_tests(&program1);
1875 assert_eq!(slot_versions, std::slice::from_ref(entry));
1876 cache.prune(
1878 MAX_TOMBSTONE_AGE_IN_SLOTS
1879 .saturating_add(entry.latest_access_slot.load(Ordering::Relaxed)),
1880 None,
1881 &fork_graph.read().unwrap(),
1882 );
1883 let slot_versions = cache.get_slot_versions_for_tests(&program1);
1884 assert_eq!(slot_versions, std::slice::from_ref(entry));
1885 cache.prune(
1887 MAX_TOMBSTONE_AGE_IN_SLOTS
1888 .saturating_add(entry.latest_access_slot.load(Ordering::Relaxed))
1889 .saturating_add(1),
1890 None,
1891 &fork_graph.read().unwrap(),
1892 );
1893 assert!(cache.get_flattened_entries_for_tests().is_empty());
1894 }
1895 }
1896
1897 #[test]
1898 fn test_prune_with_two_environments_before_epoch_boundary() {
1899 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1900 let env = get_mock_program_runtime_environment();
1901 let new_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
1902 let fork_graph = Arc::new(RwLock::new(TestForkGraph {
1903 relation: BlockRelation::Ancestor,
1904 }));
1905 cache.set_fork_graph(Arc::downgrade(&fork_graph));
1906
1907 let program1 = Pubkey::new_unique();
1908 cache.assign_program(&env, program1, 10, new_test_entry(10));
1909 let updated_program = Arc::new(ProgramCacheEntry {
1910 program: new_loaded_entry(new_env.clone()),
1911 deployment_slot: 20,
1912 ..Default::default()
1913 });
1914 cache.assign_program(&env, program1, 20, updated_program.clone());
1915
1916 assert_eq!(cache.get_slot_versions_for_tests(&program1).len(), 2);
1918
1919 cache.prune(21, None, &fork_graph.read().unwrap());
1920
1921 assert_eq!(cache.get_slot_versions_for_tests(&program1).len(), 2);
1923 }
1924
1925 #[test]
1926 fn test_prune_with_two_environments_after_epoch_boundary() {
1927 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1928 let env = get_mock_program_runtime_environment();
1929 let new_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
1930 let fork_graph = Arc::new(RwLock::new(TestForkGraph {
1931 relation: BlockRelation::Ancestor,
1932 }));
1933 cache.set_fork_graph(Arc::downgrade(&fork_graph));
1934 let program1 = Pubkey::new_unique();
1935
1936 let old_program_old_env = Arc::new(ProgramCacheEntry {
1937 program: new_loaded_entry(env.clone()),
1938 deployment_slot: 10,
1939 ..Default::default()
1940 });
1941 let old_program_new_env = Arc::new(ProgramCacheEntry {
1942 program: new_loaded_entry(new_env.clone()),
1943 deployment_slot: 10,
1944 ..Default::default()
1945 });
1946 let new_program_old_env = Arc::new(ProgramCacheEntry {
1947 program: new_loaded_entry(env.clone()),
1948 deployment_slot: 20,
1949 ..Default::default()
1950 });
1951 cache.assign_program(&env, program1, 10, old_program_old_env.clone());
1952 cache.assign_program(&env, program1, 10, old_program_new_env.clone());
1953 cache.assign_program(&env, program1, 20, new_program_old_env.clone());
1954 let slot_versions = cache.get_slot_versions_for_tests(&program1);
1955 assert_eq!(
1956 &slot_versions,
1957 &[
1958 old_program_new_env.clone(),
1959 old_program_old_env.clone(),
1960 new_program_old_env.clone(),
1961 ]
1962 );
1963
1964 cache.prune(21, Some(new_env.clone()), &fork_graph.read().unwrap());
1965 let slot_versions = cache.get_slot_versions_for_tests(&program1);
1966 assert_eq!(&slot_versions, &[old_program_new_env]);
1967 assert!(matches!(
1968 &slot_versions.first().unwrap().program,
1969 ProgramCacheEntryType::Loaded(_)
1970 ));
1971 }
1972
1973 #[derive(Default)]
1974 struct TestForkGraphSpecific {
1975 forks: Vec<Vec<Slot>>,
1976 }
1977
1978 impl TestForkGraphSpecific {
1979 fn insert_fork(&mut self, fork: &[Slot]) {
1980 let mut fork = fork.to_vec();
1981 fork.sort();
1982 self.forks.push(fork)
1983 }
1984 }
1985
1986 impl ForkGraph for TestForkGraphSpecific {
1987 fn relationship(&self, a: Slot, b: Slot) -> BlockRelation {
1988 match self.forks.iter().try_for_each(|fork| {
1989 let relation = fork
1990 .iter()
1991 .position(|x| *x == a)
1992 .and_then(|a_pos| {
1993 fork.iter().position(|x| *x == b).and_then(|b_pos| {
1994 (a_pos == b_pos)
1995 .then_some(BlockRelation::Equal)
1996 .or_else(|| (a_pos < b_pos).then_some(BlockRelation::Ancestor))
1997 .or(Some(BlockRelation::Descendant))
1998 })
1999 })
2000 .unwrap_or(BlockRelation::Unrelated);
2001
2002 if relation != BlockRelation::Unrelated {
2003 return ControlFlow::Break(relation);
2004 }
2005
2006 ControlFlow::Continue(())
2007 }) {
2008 ControlFlow::Break(relation) => relation,
2009 _ => BlockRelation::Unrelated,
2010 }
2011 }
2012 }
2013
2014 fn get_entries_to_load<'a>(
2015 cache: &ProgramCache<TestForkGraphSpecific>,
2016 loading_slot: Slot,
2017 keys: &'a [Pubkey],
2018 ) -> Vec<ProgramToLoad<'a>> {
2019 let fork_graph = cache.fork_graph.as_ref().unwrap().upgrade().unwrap();
2020 let locked_fork_graph = fork_graph.read().unwrap();
2021 let entries = cache.get_flattened_entries_for_tests();
2022 keys.iter()
2023 .filter_map(|key| {
2024 entries
2025 .iter()
2026 .rev()
2027 .find(|(program_id, entry)| {
2028 program_id == key
2029 && matches!(
2030 locked_fork_graph.relationship(entry.deployment_slot, loading_slot),
2031 BlockRelation::Equal | BlockRelation::Ancestor,
2032 )
2033 })
2034 .map(|(_program_id, entry)| ProgramToLoad {
2035 program_id: key,
2036 loader: entry.account_owner,
2037 deployment_slot: entry.deployment_slot,
2038 last_modification_slot: entry.deployment_slot,
2039 })
2040 })
2041 .collect()
2042 }
2043
2044 fn match_slot(
2045 extracted: &ProgramCacheForTxBatch,
2046 program: &Pubkey,
2047 deployment_slot: Slot,
2048 working_slot: Slot,
2049 ) -> bool {
2050 assert_eq!(extracted.slot, working_slot);
2051 extracted
2052 .entries
2053 .get(program)
2054 .map(|entry| entry.deployment_slot == deployment_slot)
2055 .unwrap_or(false)
2056 }
2057
2058 fn match_missing(
2059 missing: &[ProgramToLoad],
2060 program_id: &Pubkey,
2061 expected_result: bool,
2062 ) -> bool {
2063 missing.iter().any(|entry| entry.program_id == program_id) == expected_result
2064 }
2065
2066 #[test]
2067 fn test_fork_extract_and_prune() {
2068 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
2069 let env = get_mock_program_runtime_environment();
2070
2071 let mut fork_graph = TestForkGraphSpecific::default();
2087 fork_graph.insert_fork(&[0, 10, 20, 22]);
2088 fork_graph.insert_fork(&[0, 5, 11, 15, 16, 18, 19, 21, 23]);
2089 fork_graph.insert_fork(&[0, 5, 11, 25, 27]);
2090
2091 let fork_graph = Arc::new(RwLock::new(fork_graph));
2092 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2093
2094 let program1 = Pubkey::new_unique();
2095 cache.assign_program(&env, program1, 0, new_test_entry(0));
2096 cache.assign_program(&env, program1, 10, new_test_entry(10));
2097 cache.assign_program(&env, program1, 20, new_test_entry(20));
2098
2099 let program2 = Pubkey::new_unique();
2100 cache.assign_program(&env, program2, 5, new_test_entry(5));
2101 cache.assign_program(&env, program2, 11, new_test_entry(11));
2102
2103 let program3 = Pubkey::new_unique();
2104 cache.assign_program(&env, program3, 25, new_test_entry(25));
2105
2106 let program4 = Pubkey::new_unique();
2107 cache.assign_program(&env, program4, 0, new_test_entry(0));
2108 cache.assign_program(&env, program4, 5, new_test_entry(5));
2109 cache.assign_program(&env, program4, 15, new_test_entry(15));
2111
2112 let keys = &[program1, program2, program3, program4];
2129 let mut missing = get_entries_to_load(&cache, 22, keys);
2130 assert!(match_missing(&missing, &program2, false));
2131 assert!(match_missing(&missing, &program3, false));
2132 let mut extracted = ProgramCacheForTxBatch::new(22);
2133 cache.extract(&mut missing, &mut extracted, &env, true, true);
2134 assert!(match_slot(&extracted, &program1, 20, 22));
2135 assert!(match_slot(&extracted, &program4, 0, 22));
2136
2137 let mut missing = get_entries_to_load(&cache, 15, keys);
2139 assert!(match_missing(&missing, &program3, false));
2140 let mut extracted = ProgramCacheForTxBatch::new(15);
2141 cache.extract(&mut missing, &mut extracted, &env, true, true);
2142 assert!(match_slot(&extracted, &program1, 0, 15));
2143 assert!(match_slot(&extracted, &program2, 11, 15));
2144 let tombstone = extracted
2147 .find(&program4)
2148 .expect("Failed to find the tombstone");
2149 assert_matches!(tombstone.program, ProgramCacheEntryType::DelayVisibility);
2150 assert_eq!(tombstone.deployment_slot, 15);
2151
2152 let mut missing = get_entries_to_load(&cache, 18, keys);
2154 assert!(match_missing(&missing, &program3, false));
2155 let mut extracted = ProgramCacheForTxBatch::new(18);
2156 cache.extract(&mut missing, &mut extracted, &env, true, true);
2157 assert!(match_slot(&extracted, &program1, 0, 18));
2158 assert!(match_slot(&extracted, &program2, 11, 18));
2159 assert!(match_slot(&extracted, &program4, 15, 18));
2161
2162 let mut missing = get_entries_to_load(&cache, 23, keys);
2164 assert!(match_missing(&missing, &program3, false));
2165 let mut extracted = ProgramCacheForTxBatch::new(23);
2166 cache.extract(&mut missing, &mut extracted, &env, true, true);
2167 assert!(match_slot(&extracted, &program1, 0, 23));
2168 assert!(match_slot(&extracted, &program2, 11, 23));
2169 assert!(match_slot(&extracted, &program4, 15, 23));
2171
2172 let mut missing = get_entries_to_load(&cache, 11, keys);
2174 assert!(match_missing(&missing, &program3, false));
2175 let mut extracted = ProgramCacheForTxBatch::new(11);
2176 cache.extract(&mut missing, &mut extracted, &env, true, true);
2177 assert!(match_slot(&extracted, &program1, 0, 11));
2178 let tombstone = extracted
2180 .find(&program2)
2181 .expect("Failed to find the tombstone");
2182 assert_matches!(tombstone.program, ProgramCacheEntryType::DelayVisibility);
2183 assert_eq!(tombstone.deployment_slot, 11);
2184 assert!(match_slot(&extracted, &program4, 5, 11));
2185
2186 cache.prune(5, None, &fork_graph.read().unwrap());
2187
2188 let mut missing = get_entries_to_load(&cache, 21, keys);
2205 assert!(match_missing(&missing, &program3, false));
2206 let mut extracted = ProgramCacheForTxBatch::new(21);
2207 cache.extract(&mut missing, &mut extracted, &env, true, true);
2208 assert!(match_slot(&extracted, &program1, 0, 21));
2210 assert!(match_slot(&extracted, &program2, 11, 21));
2211 assert!(match_slot(&extracted, &program4, 15, 21));
2212
2213 let mut missing = get_entries_to_load(&cache, 27, keys);
2215 let mut extracted = ProgramCacheForTxBatch::new(27);
2216 cache.extract(&mut missing, &mut extracted, &env, true, true);
2217 assert!(match_slot(&extracted, &program1, 0, 27));
2218 assert!(match_slot(&extracted, &program2, 11, 27));
2219 assert!(match_slot(&extracted, &program3, 25, 27));
2220 assert!(match_slot(&extracted, &program4, 5, 27));
2221
2222 cache.prune(15, None, &fork_graph.read().unwrap());
2223
2224 let mut missing = get_entries_to_load(&cache, 23, keys);
2241 assert!(match_missing(&missing, &program3, false));
2242 let mut extracted = ProgramCacheForTxBatch::new(23);
2243 cache.extract(&mut missing, &mut extracted, &env, true, true);
2244 assert!(match_slot(&extracted, &program1, 0, 23));
2245 assert!(match_slot(&extracted, &program2, 11, 23));
2246 assert!(match_slot(&extracted, &program4, 15, 23));
2247 }
2248
2249 #[test]
2250 fn test_extract_using_deployment_slot() {
2251 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
2252 let env = get_mock_program_runtime_environment();
2253
2254 let mut fork_graph = TestForkGraphSpecific::default();
2270 fork_graph.insert_fork(&[0, 10, 20, 22]);
2271 fork_graph.insert_fork(&[0, 5, 11, 12, 15, 16, 18, 19, 21, 23]);
2272 fork_graph.insert_fork(&[0, 5, 11, 25, 27]);
2273
2274 let fork_graph = Arc::new(RwLock::new(fork_graph));
2275 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2276
2277 let program1 = Pubkey::new_unique();
2278 cache.assign_program(&env, program1, 0, new_test_entry(0));
2279 cache.assign_program(&env, program1, 20, new_test_entry(20));
2280
2281 let program2 = Pubkey::new_unique();
2282 cache.assign_program(&env, program2, 5, new_test_entry(5));
2283 cache.assign_program(&env, program2, 11, new_test_entry(11));
2284
2285 let program3 = Pubkey::new_unique();
2286 cache.assign_program(&env, program3, 25, new_test_entry(25));
2287
2288 let keys = &[program1, program2, program3];
2290 let mut missing = get_entries_to_load(&cache, 12, keys);
2291 assert!(match_missing(&missing, &program3, false));
2292 let mut extracted = ProgramCacheForTxBatch::new(12);
2293 cache.extract(&mut missing, &mut extracted, &env, true, true);
2294 assert!(match_slot(&extracted, &program1, 0, 12));
2295 assert!(match_slot(&extracted, &program2, 11, 12));
2296
2297 let mut missing = get_entries_to_load(&cache, 12, keys);
2300 missing.get_mut(1).unwrap().deployment_slot = 5;
2303 assert!(match_missing(&missing, &program3, false));
2304 let mut extracted = ProgramCacheForTxBatch::new(12);
2305 cache.extract(&mut missing, &mut extracted, &env, true, true);
2306 assert!(match_slot(&extracted, &program1, 0, 12));
2307 assert!(match_slot(&extracted, &program2, 5, 12));
2308 }
2309
2310 #[test]
2311 fn test_extract_rejects_entry_deployed_after_the_requested_slot() {
2312 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
2313 let env = get_mock_program_runtime_environment();
2314
2315 let mut fork_graph = TestForkGraphSpecific::default();
2316 fork_graph.insert_fork(&[0, 5, 11, 12]);
2317 let fork_graph = Arc::new(RwLock::new(fork_graph));
2318 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2319
2320 let program = Pubkey::new_unique();
2322 cache.assign_program(&env, program, 11, new_test_entry(11));
2323
2324 let mut missing = vec![ProgramToLoad {
2326 program_id: &program,
2327 loader: ProgramCacheEntryOwner::LoaderV2,
2328 deployment_slot: 5,
2329 last_modification_slot: 0,
2330 }];
2331 let mut extracted = ProgramCacheForTxBatch::new(12);
2332 cache.extract(&mut missing, &mut extracted, &env, true, true);
2333 assert!(match_missing(&missing, &program, true));
2334 assert!(extracted.find(&program).is_none());
2335
2336 let mut missing = vec![ProgramToLoad {
2338 program_id: &program,
2339 loader: ProgramCacheEntryOwner::LoaderV2,
2340 deployment_slot: 11,
2341 last_modification_slot: 0,
2342 }];
2343 let mut extracted = ProgramCacheForTxBatch::new(12);
2344 cache.extract(&mut missing, &mut extracted, &env, true, true);
2345 assert!(match_missing(&missing, &program, false));
2346 assert!(match_slot(&extracted, &program, 11, 12));
2347 }
2348
2349 #[test]
2350 fn test_extract_unloaded() {
2351 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
2352 let env = get_mock_program_runtime_environment();
2353
2354 let mut fork_graph = TestForkGraphSpecific::default();
2370 fork_graph.insert_fork(&[0, 10, 20, 22]);
2371 fork_graph.insert_fork(&[0, 5, 11, 15, 16, 19, 21, 23]);
2372 fork_graph.insert_fork(&[0, 5, 11, 25, 27]);
2373
2374 let fork_graph = Arc::new(RwLock::new(fork_graph));
2375 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2376
2377 let program1 = Pubkey::new_unique();
2378 cache.assign_program(&env, program1, 0, new_test_entry(0));
2379 cache.assign_program(&env, program1, 20, new_test_entry(20));
2380
2381 let program2 = Pubkey::new_unique();
2382 cache.assign_program(&env, program2, 5, new_test_entry(5));
2383 cache.assign_program(&env, program2, 11, new_test_entry(11));
2384
2385 let program3 = Pubkey::new_unique();
2386 let _ = insert_unloaded_entry(&mut cache, program3, 25);
2388
2389 cache.assign_program(
2393 &env,
2394 program3,
2395 20,
2396 Arc::new(
2397 new_test_entry(20)
2398 .to_unloaded()
2399 .expect("Failed to create unloaded program"),
2400 ),
2401 );
2402
2403 let keys = &[program1, program2, program3];
2405 let mut missing = get_entries_to_load(&cache, 19, keys);
2406 assert!(match_missing(&missing, &program3, false));
2407 let mut extracted = ProgramCacheForTxBatch::new(19);
2408 cache.extract(&mut missing, &mut extracted, &env, true, true);
2409 assert!(match_slot(&extracted, &program1, 0, 19));
2410 assert!(match_slot(&extracted, &program2, 11, 19));
2411
2412 let mut missing = get_entries_to_load(&cache, 27, keys);
2414 let mut extracted = ProgramCacheForTxBatch::new(27);
2415 cache.extract(&mut missing, &mut extracted, &env, true, true);
2416 assert!(match_slot(&extracted, &program1, 0, 27));
2417 assert!(match_slot(&extracted, &program2, 11, 27));
2418 assert!(match_missing(&missing, &program3, true));
2419
2420 let mut missing = get_entries_to_load(&cache, 22, keys);
2422 assert!(match_missing(&missing, &program2, false));
2423 let mut extracted = ProgramCacheForTxBatch::new(22);
2424 cache.extract(&mut missing, &mut extracted, &env, true, true);
2425 assert!(match_slot(&extracted, &program1, 20, 22));
2426 assert!(match_missing(&missing, &program3, true));
2427 }
2428
2429 #[test]
2430 fn test_extract_different_environment() {
2431 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
2432 let env = get_mock_program_runtime_environment();
2433 let other_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
2434
2435 let mut fork_graph = TestForkGraphSpecific::default();
2445 fork_graph.insert_fork(&[0, 10, 20, 22]);
2446
2447 let fork_graph = Arc::new(RwLock::new(fork_graph));
2448 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2449
2450 let program1 = Pubkey::new_unique();
2451 cache.assign_program(
2452 &env,
2453 program1,
2454 10,
2455 Arc::new(ProgramCacheEntry::new_closed_tombstone(
2456 10,
2457 ProgramCacheEntryOwner::LoaderV3,
2458 )),
2459 );
2460 cache.assign_program(&env, program1, 20, new_test_entry(20));
2461
2462 let keys = &[program1];
2464 let mut missing = get_entries_to_load(&cache, 22, keys);
2465 let mut extracted = ProgramCacheForTxBatch::new(22);
2466 cache.extract(&mut missing, &mut extracted, &env, true, true);
2467 assert!(match_slot(&extracted, &program1, 20, 22));
2468
2469 let mut missing = get_entries_to_load(&cache, 22, keys);
2471 let mut extracted = ProgramCacheForTxBatch::new(22);
2472 cache.extract(&mut missing, &mut extracted, &other_env, true, true);
2473 assert!(match_missing(&missing, &program1, true));
2474 }
2475
2476 #[test_matrix((false, true))]
2477 fn test_extract_no_second_level(empty_second_level: bool) {
2478 let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
2479 let env = get_mock_program_runtime_environment();
2480 let program_id = Pubkey::new_unique();
2481 if empty_second_level {
2482 match &mut cache.index {
2484 IndexImplementation::V1 { entries, .. } => {
2485 entries.insert(program_id, Vec::new());
2486 }
2487 }
2488 }
2489
2490 let mut search_for = vec![ProgramToLoad {
2493 program_id: &program_id,
2494 loader: ProgramCacheEntryOwner::LoaderV3,
2495 deployment_slot: 0,
2496 last_modification_slot: 0,
2497 }];
2498 let mut extracted = ProgramCacheForTxBatch::new(100);
2499 let task = cache.extract(&mut search_for, &mut extracted, &env, true, true);
2500 assert_eq!(search_for.len(), 1);
2501 assert!(extracted.entries.is_empty());
2502 assert_eq!(task, Some(program_id));
2503 }
2504
2505 #[test]
2506 fn test_extract_account_owner_mismatch() {
2507 let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
2508 let env = get_mock_program_runtime_environment();
2509 let program_id = Pubkey::new_unique();
2510 let owned_by_v2 = new_test_entry_with_owner(
2511 100,
2512 ProgramCacheEntryOwner::LoaderV2,
2513 new_loaded_entry(env.clone()),
2514 );
2515 cache.assign_program(&env, program_id, 100, Arc::clone(&owned_by_v2));
2516
2517 let mut search_for = vec![ProgramToLoad {
2520 program_id: &program_id,
2521 loader: ProgramCacheEntryOwner::LoaderV3,
2522 deployment_slot: 100,
2523 last_modification_slot: 0,
2524 }];
2525 let mut extracted = ProgramCacheForTxBatch::new(200);
2526 cache.extract(&mut search_for, &mut extracted, &env, true, true);
2527 assert_eq!(search_for.len(), 1);
2528 assert!(extracted.entries.is_empty());
2529
2530 let owned_by_v3 = new_test_entry_with_owner(
2533 150,
2534 ProgramCacheEntryOwner::LoaderV3,
2535 new_loaded_entry(env.clone()),
2536 );
2537 cache.assign_program(&env, program_id, 150, Arc::clone(&owned_by_v3));
2538
2539 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
2541 assert_eq!(slot_versions.len(), 2);
2542 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &owned_by_v2));
2543 assert!(Arc::ptr_eq(slot_versions.get(1).unwrap(), &owned_by_v3));
2544
2545 let mut search_for = vec![ProgramToLoad {
2548 program_id: &program_id,
2549 loader: ProgramCacheEntryOwner::LoaderV2,
2550 deployment_slot: 100,
2551 last_modification_slot: 0,
2552 }];
2553 let mut extracted = ProgramCacheForTxBatch::new(200);
2554 cache.extract(&mut search_for, &mut extracted, &env, true, true);
2555 assert!(search_for.is_empty());
2556 assert!(Arc::ptr_eq(
2557 extracted.entries.get(&program_id).unwrap(),
2558 &owned_by_v2
2559 ));
2560
2561 let mut search_for = vec![ProgramToLoad {
2564 program_id: &program_id,
2565 loader: ProgramCacheEntryOwner::LoaderV3,
2566 deployment_slot: 150,
2567 last_modification_slot: 0,
2568 }];
2569 let mut extracted = ProgramCacheForTxBatch::new(200);
2570 cache.extract(&mut search_for, &mut extracted, &env, true, true);
2571 assert!(search_for.is_empty());
2572 assert!(Arc::ptr_eq(
2573 extracted.entries.get(&program_id).unwrap(),
2574 &owned_by_v3
2575 ));
2576 }
2577
2578 #[test]
2579 fn test_extract_environment_mismatch() {
2580 let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
2581 let env = get_mock_program_runtime_environment();
2582 let other_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
2583 let program_id = Pubkey::new_unique();
2584 let on_other_env = new_test_entry_with_owner(
2585 100,
2586 ProgramCacheEntryOwner::LoaderV3,
2587 new_loaded_entry(other_env.clone()),
2588 );
2589 cache.assign_program(&other_env, program_id, 100, Arc::clone(&on_other_env));
2590
2591 let mut search_for = vec![ProgramToLoad {
2596 program_id: &program_id,
2597 loader: ProgramCacheEntryOwner::LoaderV3,
2598 deployment_slot: 100,
2599 last_modification_slot: 0,
2600 }];
2601 let mut extracted = ProgramCacheForTxBatch::new(200);
2602 cache.extract(&mut search_for, &mut extracted, &env, true, true);
2603 assert_eq!(search_for.len(), 1);
2604 assert!(extracted.entries.is_empty());
2605
2606 let on_execution_env = new_test_entry_with_owner(
2609 100,
2610 ProgramCacheEntryOwner::LoaderV3,
2611 new_loaded_entry(env.clone()),
2612 );
2613 cache.assign_program(&env, program_id, 100, Arc::clone(&on_execution_env));
2614
2615 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
2618 assert_eq!(slot_versions.len(), 2);
2619 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &on_other_env));
2620 assert!(Arc::ptr_eq(
2621 slot_versions.get(1).unwrap(),
2622 &on_execution_env
2623 ));
2624
2625 let mut search_for = vec![ProgramToLoad {
2628 program_id: &program_id,
2629 loader: ProgramCacheEntryOwner::LoaderV3,
2630 deployment_slot: 100,
2631 last_modification_slot: 0,
2632 }];
2633 let mut extracted = ProgramCacheForTxBatch::new(200);
2634 cache.extract(&mut search_for, &mut extracted, &env, true, true);
2635 assert!(search_for.is_empty());
2636 assert!(Arc::ptr_eq(
2637 extracted.entries.get(&program_id).unwrap(),
2638 &on_execution_env
2639 ));
2640
2641 let mut search_for = vec![ProgramToLoad {
2644 program_id: &program_id,
2645 loader: ProgramCacheEntryOwner::LoaderV3,
2646 deployment_slot: 100,
2647 last_modification_slot: 0,
2648 }];
2649 let mut extracted = ProgramCacheForTxBatch::new(200);
2650 cache.extract(&mut search_for, &mut extracted, &other_env, true, true);
2651 assert!(search_for.is_empty());
2652 assert!(Arc::ptr_eq(
2653 extracted.entries.get(&program_id).unwrap(),
2654 &on_other_env
2655 ));
2656 }
2657
2658 #[test]
2659 fn test_extract_unloaded_entry() {
2660 let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
2661 let env = get_mock_program_runtime_environment();
2662 let program_id = Pubkey::new_unique();
2663 let unloaded = new_test_entry_with_owner(
2664 100,
2665 ProgramCacheEntryOwner::LoaderV3,
2666 new_unloaded_entry(env.clone()),
2667 );
2668 cache.assign_program(&env, program_id, 100, Arc::clone(&unloaded));
2669
2670 let mut search_for = vec![ProgramToLoad {
2674 program_id: &program_id,
2675 loader: ProgramCacheEntryOwner::LoaderV3,
2676 deployment_slot: 100,
2677 last_modification_slot: 0,
2678 }];
2679 let mut extracted = ProgramCacheForTxBatch::new(200);
2680 cache.extract(&mut search_for, &mut extracted, &env, true, true);
2681 assert_eq!(search_for.len(), 1);
2682 assert!(extracted.entries.is_empty());
2683
2684 let loaded = new_test_entry_with_owner(
2686 100,
2687 ProgramCacheEntryOwner::LoaderV3,
2688 new_loaded_entry(env.clone()),
2689 );
2690 cache.assign_program(&env, program_id, 100, Arc::clone(&loaded));
2691 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
2692 assert_eq!(slot_versions.len(), 1);
2693 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &loaded));
2694
2695 let mut search_for = vec![ProgramToLoad {
2697 program_id: &program_id,
2698 loader: ProgramCacheEntryOwner::LoaderV3,
2699 deployment_slot: 100,
2700 last_modification_slot: 0,
2701 }];
2702 let mut extracted = ProgramCacheForTxBatch::new(200);
2703 cache.extract(&mut search_for, &mut extracted, &env, true, true);
2704 assert!(search_for.is_empty());
2705 assert!(Arc::ptr_eq(
2706 extracted.entries.get(&program_id).unwrap(),
2707 &loaded
2708 ));
2709 }
2710
2711 #[test_case(false)]
2712 #[test_case(true)]
2713 fn test_extract_environment_filter_same_slot(other_env_first: bool) {
2714 let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
2715 let env = get_mock_program_runtime_environment();
2716 let other_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
2717 let program_id = Pubkey::new_unique();
2718
2719 let on_other_env = new_test_entry_with_owner(
2721 100,
2722 ProgramCacheEntryOwner::LoaderV3,
2723 new_loaded_entry(other_env.clone()),
2724 );
2725 let on_execution_env = new_test_entry_with_owner(
2726 100,
2727 ProgramCacheEntryOwner::LoaderV3,
2728 new_loaded_entry(env.clone()),
2729 );
2730 if other_env_first {
2731 cache.assign_program(&other_env, program_id, 100, Arc::clone(&on_other_env));
2732 cache.assign_program(&env, program_id, 100, Arc::clone(&on_execution_env));
2733 } else {
2734 cache.assign_program(&env, program_id, 100, Arc::clone(&on_execution_env));
2735 cache.assign_program(&other_env, program_id, 100, Arc::clone(&on_other_env));
2736 }
2737
2738 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
2741 assert_eq!(slot_versions.len(), 2);
2742 let (first, second) = if other_env_first {
2743 (&on_other_env, &on_execution_env)
2744 } else {
2745 (&on_execution_env, &on_other_env)
2746 };
2747 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), first));
2748 assert!(Arc::ptr_eq(slot_versions.get(1).unwrap(), second));
2749
2750 let mut search_for = vec![ProgramToLoad {
2753 program_id: &program_id,
2754 loader: ProgramCacheEntryOwner::LoaderV3,
2755 deployment_slot: 100,
2756 last_modification_slot: 0,
2757 }];
2758 let mut extracted = ProgramCacheForTxBatch::new(200);
2759 cache.extract(&mut search_for, &mut extracted, &env, true, true);
2760 assert!(Arc::ptr_eq(
2761 extracted.entries.get(&program_id).unwrap(),
2762 &on_execution_env
2763 ));
2764
2765 let mut search_for = vec![ProgramToLoad {
2767 program_id: &program_id,
2768 loader: ProgramCacheEntryOwner::LoaderV3,
2769 deployment_slot: 100,
2770 last_modification_slot: 0,
2771 }];
2772 let mut extracted = ProgramCacheForTxBatch::new(200);
2773 cache.extract(&mut search_for, &mut extracted, &other_env, true, true);
2774 assert!(Arc::ptr_eq(
2775 extracted.entries.get(&program_id).unwrap(),
2776 &on_other_env
2777 ));
2778 }
2779
2780 #[test_matrix((false, true))]
2781 fn test_extract_usage_counter(increment_usage_counter: bool) {
2782 let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
2783 let env = get_mock_program_runtime_environment();
2784 let program_id = Pubkey::new_unique();
2785 let entry = new_test_entry_with_owner(
2786 100,
2787 ProgramCacheEntryOwner::LoaderV3,
2788 new_loaded_entry(env.clone()),
2789 );
2790 cache.assign_program(&env, program_id, 100, Arc::clone(&entry));
2791
2792 let mut search_for = vec![ProgramToLoad {
2793 program_id: &program_id,
2794 loader: ProgramCacheEntryOwner::LoaderV3,
2795 deployment_slot: 100,
2796 last_modification_slot: 0,
2797 }];
2798 let mut extracted = ProgramCacheForTxBatch::new(200);
2799 cache.extract(
2800 &mut search_for,
2801 &mut extracted,
2802 &env,
2803 increment_usage_counter,
2804 true,
2805 );
2806
2807 assert_eq!(entry.latest_access_slot.load(Ordering::Relaxed), 200);
2809 assert_eq!(
2810 entry.stats.uses.load(Ordering::Relaxed),
2811 u64::from(increment_usage_counter)
2812 );
2813 }
2814
2815 #[test]
2816 fn test_extract_usage_counter_delayed_visibility() {
2817 let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
2818 let env = get_mock_program_runtime_environment();
2819 let program_id = Pubkey::new_unique();
2820 let entry = new_test_entry_with_owner(
2821 100,
2822 ProgramCacheEntryOwner::LoaderV3,
2823 new_loaded_entry(env.clone()),
2824 );
2825 cache.assign_program(&env, program_id, 100, Arc::clone(&entry));
2826
2827 let mut search_for = vec![ProgramToLoad {
2831 program_id: &program_id,
2832 loader: ProgramCacheEntryOwner::LoaderV3,
2833 deployment_slot: 100,
2834 last_modification_slot: 0,
2835 }];
2836 let mut extracted = ProgramCacheForTxBatch::new(100);
2837 cache.extract(&mut search_for, &mut extracted, &env, true, true);
2838
2839 let tombstone = extracted.entries.get(&program_id).unwrap();
2840 assert!(matches!(
2841 tombstone.program,
2842 ProgramCacheEntryType::DelayVisibility
2843 ));
2844 assert!(!Arc::ptr_eq(tombstone, &entry));
2845
2846 assert_eq!(entry.latest_access_slot.load(Ordering::Relaxed), 100);
2849 assert_eq!(tombstone.latest_access_slot.load(Ordering::Relaxed), 0);
2850
2851 assert!(Arc::ptr_eq(&tombstone.stats, &entry.stats));
2854 assert_eq!(entry.stats.uses.load(Ordering::Relaxed), 1);
2855 }
2856
2857 #[test_matrix((false, true))]
2858 fn test_extract_hits_and_misses(count_hits_and_misses: bool) {
2859 let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
2860 let env = get_mock_program_runtime_environment();
2861 let found = Pubkey::new_unique();
2862 let missing = Pubkey::new_unique();
2863 cache.assign_program(
2864 &env,
2865 found,
2866 100,
2867 new_test_entry_with_owner(
2868 100,
2869 ProgramCacheEntryOwner::LoaderV3,
2870 new_loaded_entry(env.clone()),
2871 ),
2872 );
2873
2874 let mut search_for = vec![
2875 ProgramToLoad {
2876 program_id: &found,
2877 loader: ProgramCacheEntryOwner::LoaderV3,
2878 deployment_slot: 100,
2879 last_modification_slot: 0,
2880 },
2881 ProgramToLoad {
2882 program_id: &missing,
2883 loader: ProgramCacheEntryOwner::LoaderV3,
2884 deployment_slot: 0,
2885 last_modification_slot: 0,
2886 },
2887 ];
2888 let mut extracted = ProgramCacheForTxBatch::new(200);
2889 cache.extract(
2890 &mut search_for,
2891 &mut extracted,
2892 &env,
2893 true,
2894 count_hits_and_misses,
2895 );
2896
2897 let expected = u64::from(count_hits_and_misses);
2898 assert_eq!(cache.stats.hits.load(Ordering::Relaxed), expected);
2899 assert_eq!(cache.stats.misses.load(Ordering::Relaxed), expected);
2900 }
2901
2902 #[test]
2903 fn test_extract_hits_count_only_this_call() {
2904 let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
2905 let env = get_mock_program_runtime_environment();
2906 let program_id = Pubkey::new_unique();
2907 cache.assign_program(
2908 &env,
2909 program_id,
2910 100,
2911 new_test_entry_with_owner(
2912 100,
2913 ProgramCacheEntryOwner::LoaderV3,
2914 new_loaded_entry(env.clone()),
2915 ),
2916 );
2917
2918 let mut extracted = ProgramCacheForTxBatch::new(200);
2921 extracted.replenish(Pubkey::new_unique(), new_test_builtin_entry(0));
2922
2923 let mut search_for = vec![ProgramToLoad {
2926 program_id: &program_id,
2927 loader: ProgramCacheEntryOwner::LoaderV3,
2928 deployment_slot: 100,
2929 last_modification_slot: 0,
2930 }];
2931 cache.extract(&mut search_for, &mut extracted, &env, true, true);
2932 assert_eq!(extracted.entries.len(), 2);
2933 assert_eq!(cache.stats.hits.load(Ordering::Relaxed), 1);
2934 }
2935
2936 #[test]
2937 fn test_extract_cooperative_loading_task() {
2938 let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
2939 let env = get_mock_program_runtime_environment();
2940 let program_ids = [Pubkey::new_unique(), Pubkey::new_unique()];
2941
2942 let mut search_for = program_ids
2944 .iter()
2945 .map(|program_id| ProgramToLoad {
2946 program_id,
2947 loader: ProgramCacheEntryOwner::LoaderV3,
2948 deployment_slot: 0,
2949 last_modification_slot: 0,
2950 })
2951 .collect();
2952 let mut extracted = ProgramCacheForTxBatch::new(100);
2953 let task = cache.extract(&mut search_for, &mut extracted, &env, true, true);
2954 assert_eq!(search_for.len(), 2);
2955 assert_eq!(task, program_ids.first().copied());
2956 match &cache.index {
2957 IndexImplementation::V1 {
2958 loading_entries, ..
2959 } => {
2960 let loading_entries = loading_entries.lock().unwrap();
2961 assert_eq!(loading_entries.len(), 1);
2962 assert_eq!(
2963 loading_entries.get(program_ids.first().unwrap()),
2964 Some(&(100, thread::current().id()))
2965 );
2966 }
2967 }
2968
2969 let mut search_for = vec![ProgramToLoad {
2971 program_id: program_ids.first().unwrap(),
2972 loader: ProgramCacheEntryOwner::LoaderV3,
2973 deployment_slot: 0,
2974 last_modification_slot: 0,
2975 }];
2976 let task = cache.extract(&mut search_for, &mut extracted, &env, true, true);
2977 assert_eq!(search_for.len(), 1);
2978 assert_eq!(task, None);
2979
2980 let cookie = cache.loading_task_waiter.cookie();
2982 let loaded = new_test_entry_with_owner(
2983 50,
2984 ProgramCacheEntryOwner::LoaderV3,
2985 new_loaded_entry(env.clone()),
2986 );
2987 cache.finish_cooperative_loading_task(
2988 &env,
2989 100,
2990 *program_ids.first().unwrap(),
2991 50,
2992 Arc::clone(&loaded),
2993 );
2994 assert_ne!(cache.loading_task_waiter.wait(cookie), cookie);
2995
2996 match &cache.index {
2998 IndexImplementation::V1 {
2999 loading_entries, ..
3000 } => assert!(loading_entries.lock().unwrap().is_empty()),
3001 }
3002 let mut search_for = vec![ProgramToLoad {
3003 program_id: program_ids.first().unwrap(),
3004 loader: ProgramCacheEntryOwner::LoaderV3,
3005 deployment_slot: 50,
3006 last_modification_slot: 0,
3007 }];
3008 let mut extracted = ProgramCacheForTxBatch::new(100);
3009 let task = cache.extract(&mut search_for, &mut extracted, &env, true, true);
3010 assert!(search_for.is_empty());
3011 assert_eq!(task, None);
3012 assert!(Arc::ptr_eq(
3013 extracted.entries.get(program_ids.first().unwrap()).unwrap(),
3014 &loaded
3015 ));
3016 }
3017
3018 #[test]
3019 fn test_extract_cooperative_loading_task_ordering() {
3020 let (cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
3021 let env = get_mock_program_runtime_environment();
3022 let program_ids = [Pubkey::new_unique(), Pubkey::new_unique()];
3023 let mut extracted = ProgramCacheForTxBatch::new(100);
3024
3025 let mut search_for = program_ids
3027 .iter()
3028 .map(|program_id| ProgramToLoad {
3029 program_id,
3030 loader: ProgramCacheEntryOwner::LoaderV3,
3031 deployment_slot: 0,
3032 last_modification_slot: 0,
3033 })
3034 .collect();
3035 let task = cache.extract(&mut search_for, &mut extracted, &env, true, true);
3036 assert_eq!(task, program_ids.first().copied());
3037
3038 let mut search_for = program_ids
3041 .iter()
3042 .rev()
3043 .map(|program_id| ProgramToLoad {
3044 program_id,
3045 loader: ProgramCacheEntryOwner::LoaderV3,
3046 deployment_slot: 0,
3047 last_modification_slot: 0,
3048 })
3049 .collect();
3050 let task = cache.extract(&mut search_for, &mut extracted, &env, true, true);
3051 assert_eq!(task, program_ids.get(1).copied());
3052
3053 match &cache.index {
3055 IndexImplementation::V1 {
3056 loading_entries, ..
3057 } => {
3058 let loading_entries = loading_entries.lock().unwrap();
3059 assert_eq!(loading_entries.len(), 2);
3060 for program_id in &program_ids {
3061 assert_eq!(
3062 loading_entries.get(program_id),
3063 Some(&(100, thread::current().id()))
3064 );
3065 }
3066 }
3067 }
3068
3069 let mut search_for = program_ids
3071 .iter()
3072 .map(|program_id| ProgramToLoad {
3073 program_id,
3074 loader: ProgramCacheEntryOwner::LoaderV3,
3075 deployment_slot: 0,
3076 last_modification_slot: 0,
3077 })
3078 .collect();
3079 let task = cache.extract(&mut search_for, &mut extracted, &env, true, true);
3080 assert_eq!(search_for.len(), 2);
3081 assert_eq!(task, None);
3082 }
3083
3084 #[test]
3085 fn test_extract_entry_not_in_same_branch() {
3086 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
3093 let mut fork_graph = TestForkGraphSpecific::default();
3094 fork_graph.insert_fork(&[0, 50, 200]);
3095 fork_graph.insert_fork(&[0, 100]);
3096 let fork_graph = Arc::new(RwLock::new(fork_graph));
3097 cache.set_fork_graph(Arc::downgrade(&fork_graph));
3098
3099 let env = get_mock_program_runtime_environment();
3100 let program_id = Pubkey::new_unique();
3101 let on_other_fork = new_test_entry_with_owner(
3102 100,
3103 ProgramCacheEntryOwner::LoaderV3,
3104 new_loaded_entry(env.clone()),
3105 );
3106 cache.assign_program(&env, program_id, 100, Arc::clone(&on_other_fork));
3107
3108 let mut search_for = vec![ProgramToLoad {
3118 program_id: &program_id,
3119 loader: ProgramCacheEntryOwner::LoaderV3,
3120 deployment_slot: 100,
3121 last_modification_slot: 0,
3122 }];
3123 let mut extracted = ProgramCacheForTxBatch::new(200);
3124 cache.extract(&mut search_for, &mut extracted, &env, true, true);
3125 assert_eq!(search_for.len(), 1);
3126 assert!(extracted.entries.is_empty());
3127
3128 let on_same_fork = new_test_entry_with_owner(
3130 50,
3131 ProgramCacheEntryOwner::LoaderV3,
3132 new_loaded_entry(env.clone()),
3133 );
3134 cache.assign_program(&env, program_id, 50, Arc::clone(&on_same_fork));
3135
3136 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
3138 assert_eq!(slot_versions.len(), 2);
3139 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &on_same_fork));
3140 assert!(Arc::ptr_eq(slot_versions.get(1).unwrap(), &on_other_fork));
3141
3142 let mut search_for = vec![ProgramToLoad {
3145 program_id: &program_id,
3146 loader: ProgramCacheEntryOwner::LoaderV3,
3147 deployment_slot: 50,
3148 last_modification_slot: 0,
3149 }];
3150 let mut extracted = ProgramCacheForTxBatch::new(200);
3151 cache.extract(&mut search_for, &mut extracted, &env, true, true);
3152 assert!(search_for.is_empty());
3153 assert!(Arc::ptr_eq(
3154 extracted.entries.get(&program_id).unwrap(),
3155 &on_same_fork
3156 ));
3157 }
3158
3159 #[test]
3160 fn test_extract_deployment_slot_mismatch() {
3161 let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
3177 assert_eq!(cache.latest_root_slot, 0);
3178 let env = get_mock_program_runtime_environment();
3179 let program_id = Pubkey::new_unique();
3180 let deployed_at_100 = new_test_entry_with_owner(
3181 100,
3182 ProgramCacheEntryOwner::LoaderV3,
3183 new_loaded_entry(env.clone()),
3184 );
3185 cache.assign_program(&env, program_id, 100, Arc::clone(&deployed_at_100));
3186
3187 let mut search_for = vec![ProgramToLoad {
3191 program_id: &program_id,
3192 loader: ProgramCacheEntryOwner::LoaderV3,
3193 deployment_slot: 150,
3194 last_modification_slot: 0,
3195 }];
3196 let mut extracted = ProgramCacheForTxBatch::new(200);
3197 cache.extract(&mut search_for, &mut extracted, &env, true, true);
3198 assert_eq!(search_for.len(), 1);
3199 assert!(extracted.entries.is_empty());
3200
3201 let deployed_at_150 = new_test_entry_with_owner(
3203 150,
3204 ProgramCacheEntryOwner::LoaderV3,
3205 new_loaded_entry(env.clone()),
3206 );
3207 cache.assign_program(&env, program_id, 150, Arc::clone(&deployed_at_150));
3208
3209 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
3212 assert_eq!(slot_versions.len(), 2);
3213 assert!(Arc::ptr_eq(
3214 slot_versions.first().unwrap(),
3215 &deployed_at_100
3216 ));
3217 assert!(Arc::ptr_eq(slot_versions.get(1).unwrap(), &deployed_at_150));
3218
3219 let mut search_for = vec![ProgramToLoad {
3221 program_id: &program_id,
3222 loader: ProgramCacheEntryOwner::LoaderV3,
3223 deployment_slot: 150,
3224 last_modification_slot: 0,
3225 }];
3226 let mut extracted = ProgramCacheForTxBatch::new(200);
3227 cache.extract(&mut search_for, &mut extracted, &env, true, true);
3228 assert!(search_for.is_empty());
3229 assert!(Arc::ptr_eq(
3230 extracted.entries.get(&program_id).unwrap(),
3231 &deployed_at_150
3232 ));
3233 }
3234
3235 #[test]
3236 fn test_extract_older_entry_on_the_callers_fork() {
3237 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
3244 let mut fork_graph = TestForkGraphSpecific::default();
3245 fork_graph.insert_fork(&[0, 50, 200]);
3246 fork_graph.insert_fork(&[0, 150]);
3247 let fork_graph = Arc::new(RwLock::new(fork_graph));
3248 cache.set_fork_graph(Arc::downgrade(&fork_graph));
3249
3250 let env = get_mock_program_runtime_environment();
3251 let program_id = Pubkey::new_unique();
3252 let on_other_fork = new_test_entry_with_owner(
3253 150,
3254 ProgramCacheEntryOwner::LoaderV3,
3255 new_loaded_entry(env.clone()),
3256 );
3257 let on_same_fork = new_test_entry_with_owner(
3258 50,
3259 ProgramCacheEntryOwner::LoaderV3,
3260 new_loaded_entry(env.clone()),
3261 );
3262 cache.assign_program(&env, program_id, 150, Arc::clone(&on_other_fork));
3263 cache.assign_program(&env, program_id, 50, Arc::clone(&on_same_fork));
3264
3265 let mut search_for = vec![ProgramToLoad {
3269 program_id: &program_id,
3270 loader: ProgramCacheEntryOwner::LoaderV3,
3271 deployment_slot: 50,
3272 last_modification_slot: 0,
3273 }];
3274 let mut extracted = ProgramCacheForTxBatch::new(200);
3275 cache.extract(&mut search_for, &mut extracted, &env, true, true);
3276 assert!(Arc::ptr_eq(
3277 extracted.entries.get(&program_id).unwrap(),
3278 &on_same_fork
3279 ));
3280
3281 let mut search_for = vec![ProgramToLoad {
3293 program_id: &program_id,
3294 loader: ProgramCacheEntryOwner::LoaderV3,
3295 deployment_slot: 150,
3296 last_modification_slot: 0,
3297 }];
3298 let mut extracted = ProgramCacheForTxBatch::new(200);
3299 cache.extract(&mut search_for, &mut extracted, &env, true, true);
3300 assert_eq!(search_for.len(), 1);
3301 assert!(extracted.entries.is_empty());
3302 }
3303
3304 #[test]
3305 fn test_extract_below_deployment_slot() {
3306 let (mut cache, fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
3307 let env = get_mock_program_runtime_environment();
3308 let program_id = Pubkey::new_unique();
3309 let entry = new_test_entry_with_owner(
3310 100,
3311 ProgramCacheEntryOwner::LoaderV3,
3312 new_loaded_entry(env.clone()),
3313 );
3314 cache.assign_program(&env, program_id, 100, Arc::clone(&entry));
3315
3316 cache.prune(200, None, &fork_graph.read().unwrap());
3319 assert_eq!(cache.latest_root_slot, 200);
3320
3321 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
3323 assert_eq!(slot_versions.len(), 1);
3324 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &entry));
3325
3326 fork_graph.write().unwrap().relation = BlockRelation::Unknown;
3330
3331 let mut search_for = vec![ProgramToLoad {
3334 program_id: &program_id,
3335 loader: ProgramCacheEntryOwner::LoaderV3,
3336 deployment_slot: 0,
3337 last_modification_slot: 0,
3338 }];
3339 let mut extracted = ProgramCacheForTxBatch::new(50);
3340 cache.extract(&mut search_for, &mut extracted, &env, true, true);
3341 assert_eq!(search_for.len(), 1);
3342 assert!(extracted.entries.is_empty());
3343 }
3344
3345 #[test]
3346 fn test_extract_entry_older_than_root() {
3347 let (mut cache, fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
3350 let env = get_mock_program_runtime_environment();
3351 let program_id = Pubkey::new_unique();
3352 let entry = new_test_entry_with_owner(
3353 100,
3354 ProgramCacheEntryOwner::LoaderV3,
3355 new_loaded_entry(env.clone()),
3356 );
3357 cache.assign_program(&env, program_id, 100, Arc::clone(&entry));
3358
3359 cache.prune(200, None, &fork_graph.read().unwrap());
3362 assert_eq!(cache.latest_root_slot, 200);
3363
3364 let slot_versions = cache.get_slot_versions_for_tests(&program_id);
3366 assert_eq!(slot_versions.len(), 1);
3367 assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &entry));
3368
3369 fork_graph.write().unwrap().relation = BlockRelation::Unknown;
3373
3374 let mut search_for = vec![ProgramToLoad {
3379 program_id: &program_id,
3380 loader: ProgramCacheEntryOwner::LoaderV3,
3381 deployment_slot: 100,
3382 last_modification_slot: 0,
3383 }];
3384 let mut extracted = ProgramCacheForTxBatch::new(300);
3385 cache.extract(&mut search_for, &mut extracted, &env, true, true);
3386 assert!(search_for.is_empty());
3387 assert!(Arc::ptr_eq(
3388 extracted.entries.get(&program_id).unwrap(),
3389 &entry
3390 ));
3391 }
3392
3393 #[test]
3394 fn test_unloaded() {
3395 let mut cache = ProgramCache::<TestForkGraph>::new(0);
3396 let env = get_mock_program_runtime_environment();
3397 for program_cache_entry_type in [
3398 ProgramCacheEntryType::Closed,
3399 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
3400 ] {
3401 let entry = Arc::new(ProgramCacheEntry {
3402 program: program_cache_entry_type,
3403 account_owner: ProgramCacheEntryOwner::LoaderV2,
3404 deployment_slot: 0,
3405 stats: Arc::default(),
3406 latest_access_slot: AtomicU64::default(),
3407 });
3408 assert!(entry.to_unloaded().is_none());
3409
3410 let program_id = Pubkey::new_unique();
3412 cache.assign_program(&env, program_id, entry.deployment_slot, entry.clone());
3413 cache.unload_program_entry(program_id, entry.deployment_slot, &entry);
3414 assert_eq!(cache.get_slot_versions_for_tests(&program_id).len(), 1);
3415 assert!(cache.stats.evictions.is_empty());
3416 }
3417
3418 let stats = ProgramStatistics {
3419 uses: 3.into(),
3420 ..Default::default()
3421 };
3422 let entry = new_test_entry_with_usage(1, stats);
3423 let unloaded_entry = entry.to_unloaded().unwrap();
3424 assert_eq!(unloaded_entry.deployment_slot, 1);
3425 assert_eq!(unloaded_entry.effective_slot(), 2);
3426 assert_eq!(unloaded_entry.latest_access_slot.load(Ordering::Relaxed), 1);
3427 assert_eq!(unloaded_entry.stats.uses.load(Ordering::Relaxed), 3);
3428
3429 let program_id = Pubkey::new_unique();
3431 cache.assign_program(&env, program_id, entry.deployment_slot, entry.clone());
3432 cache.unload_program_entry(program_id, entry.deployment_slot, &entry);
3433 assert!(cache.stats.evictions.contains_key(&program_id));
3434 }
3435
3436 #[test]
3437 fn test_fork_prune_find_first_ancestor() {
3438 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
3439 let env = get_mock_program_runtime_environment();
3440
3441 let mut fork_graph = TestForkGraphSpecific::default();
3452 fork_graph.insert_fork(&[0, 10, 20]);
3453 fork_graph.insert_fork(&[0, 5]);
3454 let fork_graph = Arc::new(RwLock::new(fork_graph));
3455 cache.set_fork_graph(Arc::downgrade(&fork_graph));
3456
3457 let program1 = Pubkey::new_unique();
3458 cache.assign_program(&env, program1, 0, new_test_entry(0));
3459 cache.assign_program(&env, program1, 5, new_test_entry(5));
3460
3461 cache.prune(10, None, &fork_graph.read().unwrap());
3462
3463 let keys = &[program1];
3464 let mut missing = get_entries_to_load(&cache, 20, keys);
3465 let mut extracted = ProgramCacheForTxBatch::new(20);
3466 cache.extract(&mut missing, &mut extracted, &env, true, true);
3467
3468 assert_eq!(
3470 extracted
3471 .find(&program1)
3472 .expect("Did not find the program")
3473 .deployment_slot,
3474 0
3475 );
3476 }
3477
3478 #[test]
3479 fn test_prune_by_deployment_slot() {
3480 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
3481 let env = get_mock_program_runtime_environment();
3482
3483 let mut fork_graph = TestForkGraphSpecific::default();
3494 fork_graph.insert_fork(&[0, 10, 20]);
3495 fork_graph.insert_fork(&[0, 5, 6]);
3496 let fork_graph = Arc::new(RwLock::new(fork_graph));
3497 cache.set_fork_graph(Arc::downgrade(&fork_graph));
3498
3499 let program1 = Pubkey::new_unique();
3500 cache.assign_program(&env, program1, 0, new_test_entry(0));
3501 cache.assign_program(&env, program1, 5, new_test_entry(5));
3502
3503 let program2 = Pubkey::new_unique();
3504 cache.assign_program(&env, program2, 10, new_test_entry(10));
3505
3506 let keys = &[program1, program2];
3507 let mut missing = get_entries_to_load(&cache, 20, keys);
3508 let mut extracted = ProgramCacheForTxBatch::new(20);
3509 cache.extract(&mut missing, &mut extracted, &env, true, true);
3510 assert!(match_slot(&extracted, &program1, 0, 20));
3511 assert!(match_slot(&extracted, &program2, 10, 20));
3512
3513 let mut missing = get_entries_to_load(&cache, 6, keys);
3514 assert!(match_missing(&missing, &program2, false));
3515 let mut extracted = ProgramCacheForTxBatch::new(6);
3516 cache.extract(&mut missing, &mut extracted, &env, true, true);
3517 assert!(match_slot(&extracted, &program1, 5, 6));
3518
3519 cache.prune_by_deployment_slot(5);
3522
3523 let mut missing = get_entries_to_load(&cache, 20, keys);
3524 let mut extracted = ProgramCacheForTxBatch::new(20);
3525 cache.extract(&mut missing, &mut extracted, &env, true, true);
3526 assert!(match_slot(&extracted, &program1, 0, 20));
3527 assert!(match_slot(&extracted, &program2, 10, 20));
3528
3529 let mut missing = get_entries_to_load(&cache, 6, keys);
3530 assert!(match_missing(&missing, &program2, false));
3531 let mut extracted = ProgramCacheForTxBatch::new(6);
3532 cache.extract(&mut missing, &mut extracted, &env, true, true);
3533 assert!(match_slot(&extracted, &program1, 0, 6));
3534
3535 cache.prune_by_deployment_slot(10);
3538
3539 let mut missing = get_entries_to_load(&cache, 20, keys);
3540 assert!(match_missing(&missing, &program2, false));
3541 let mut extracted = ProgramCacheForTxBatch::new(20);
3542 cache.extract(&mut missing, &mut extracted, &env, true, true);
3543 assert!(match_slot(&extracted, &program1, 0, 20));
3544 }
3545}