Skip to main content

solana_program_runtime/
loaded_programs.rs

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        // Safety: This wrapper type is transparent and shares the same representation as the underlying type
55        unsafe { std::mem::transmute(inner) }
56    }
57}
58
59/// Paired execution and deployment environments.
60///
61/// Registered functions within each program runtime environment (syscalls)
62/// depend on per-epoch feature gate statuses. In most cases, the list of
63/// registered functions in the two environments will be the same. However,
64/// it's possible that the effective epoch of deployment could be in the
65/// *next epoch*.
66pub struct ProgramRuntimeEnvironments {
67    /// Environment compiled for the current epoch in which programs are
68    /// executing.
69    execution: ProgramRuntimeEnvironment,
70    /// Environment compiled for the epoch of the next slot at which a program
71    /// deployed in the current slot will execute.
72    deployment: ProgramRuntimeEnvironment,
73}
74
75impl ProgramRuntimeEnvironments {
76    /// Create a new ProgramRuntimeEnvironments from an `execution` and
77    /// `deployment` environment.
78    pub fn new(
79        execution: ProgramRuntimeEnvironment,
80        deployment: ProgramRuntimeEnvironment,
81    ) -> Self {
82        Self {
83            execution,
84            deployment,
85        }
86    }
87
88    /// Get the program runtime environment for execution.
89    pub fn get_env_for_execution(&self) -> &ProgramRuntimeEnvironment {
90        &self.execution
91    }
92
93    /// Get the program runtime environment for deployment.
94    pub fn get_env_for_deployment(&self) -> &ProgramRuntimeEnvironment {
95        &self.deployment
96    }
97
98    #[cfg(feature = "dev-context-only-utils")]
99    pub fn mock() -> Self {
100        Self {
101            execution: get_mock_program_runtime_environment(),
102            deployment: get_mock_program_runtime_environment(),
103        }
104    }
105}
106
107#[cfg(feature = "dev-context-only-utils")]
108pub fn get_mock_program_runtime_environment() -> ProgramRuntimeEnvironment {
109    static MOCK_ENVIRONMENT: std::sync::OnceLock<ProgramRuntimeEnvironment> =
110        std::sync::OnceLock::<ProgramRuntimeEnvironment>::new();
111    MOCK_ENVIRONMENT
112        .get_or_init(|| ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock()))
113        .clone()
114}
115
116pub const MAX_LOADED_ENTRY_COUNT: usize = 1024;
117
118/// A percentage, expected to be in the range `0..=100`.
119pub type Percent = u8;
120
121/// The given percentage of [`MAX_LOADED_ENTRY_COUNT`], as an entry count.
122/// Equivalent to the former `percentage` crate's
123/// `Percentage::from(percent).apply_to(MAX_LOADED_ENTRY_COUNT)`,
124/// i.e. floor(MAX_LOADED_ENTRY_COUNT * percent / 100).
125fn percent_of_max_entries(percent: Percent) -> usize {
126    debug_assert!(percent <= 100, "percent must be <= 100");
127    MAX_LOADED_ENTRY_COUNT.saturating_mul(percent as usize) / 100
128}
129
130/// Relationship between two fork IDs
131#[derive(Copy, Clone, Debug, PartialEq)]
132pub enum BlockRelation {
133    /// The slot is on the same fork and is an ancestor of the other slot
134    Ancestor,
135    /// The two slots are equal and are on the same fork
136    Equal,
137    /// The slot is on the same fork and is a descendant of the other slot
138    Descendant,
139    /// The slots are on two different forks and may have had a common ancestor at some point
140    Unrelated,
141    /// Either one or both of the slots are either older than the latest root, or are in future
142    Unknown,
143}
144
145/// Maps relationship between two slots.
146pub trait ForkGraph {
147    /// Returns the BlockRelation of A to B
148    fn relationship(&self, a: Slot, b: Slot) -> BlockRelation;
149}
150
151/// Globally manages the transition between environments at the epoch boundary
152#[derive(Debug, Default)]
153pub struct EpochBoundaryPreparation {
154    /// The epoch of the upcoming_environment
155    pub upcoming_epoch: Epoch,
156    /// Anticipated replacement for `environments` at the next epoch
157    ///
158    /// This is `None` during most of an epoch, and only `Some` around the boundaries (at the end and beginning of an epoch).
159    /// More precisely, it starts with the cache preparation phase a few hundred slots before the epoch boundary,
160    /// and it ends with the first rerooting after the epoch boundary.
161    pub upcoming_environment: Option<ProgramRuntimeEnvironment>,
162    /// List of loaded programs which should be recompiled before the next epoch (but don't have to).
163    pub programs_to_recompile: Vec<(Pubkey, Arc<ProgramCacheEntry>)>,
164}
165
166impl EpochBoundaryPreparation {
167    pub fn new(epoch: Epoch) -> Self {
168        Self {
169            upcoming_epoch: epoch,
170            upcoming_environment: None,
171            programs_to_recompile: Vec::default(),
172        }
173    }
174
175    /// Returns the upcoming environments depending on the given epoch
176    pub fn get_upcoming_environment_for_epoch(
177        &self,
178        epoch: Epoch,
179    ) -> Option<ProgramRuntimeEnvironment> {
180        if epoch == self.upcoming_epoch {
181            return self.upcoming_environment.clone();
182        }
183        None
184    }
185
186    /// Before rerooting the blockstore this concludes the epoch boundary preparation
187    pub fn reroot(&mut self, epoch: Epoch) -> Option<ProgramRuntimeEnvironment> {
188        if epoch == self.upcoming_epoch
189            && let Some(upcoming_environment) = self.upcoming_environment.take()
190        {
191            self.programs_to_recompile.clear();
192            return Some(upcoming_environment);
193        }
194
195        None
196    }
197}
198
199/// Input of ProgramCache::extract()
200#[derive(Clone, PartialEq, Debug)]
201pub struct ProgramToLoad<'a> {
202    /// The program address
203    pub program_id: &'a Pubkey,
204    /// The program loader
205    pub loader: ProgramCacheEntryOwner,
206    /// Potentially filter out / ignore some entries during the start up / catch up phase
207    pub match_criteria: ProgramCacheMatchCriteria,
208    /// When the program account was last written to (might be after the deployment slot)
209    pub last_modification_slot: Slot,
210}
211
212#[derive(Debug)]
213pub(crate) enum IndexImplementation {
214    /// Fork-graph aware index implementation
215    V1 {
216        /// A two level index:
217        ///
218        /// - the first level is for the address at which programs are deployed
219        /// - the second level for the slot (and thus also fork), sorted by slot number.
220        entries: HashMap<Pubkey, Vec<Arc<ProgramCacheEntry>>>,
221        /// The entries that are getting loaded and have not yet finished loading.
222        ///
223        /// The key is the program address, the value is a tuple of the slot in which the program is
224        /// being loaded and the thread ID doing the load.
225        ///
226        /// It is possible that multiple TX batches from different slots need different versions of a
227        /// program. The deployment slot of a program is only known after load tho,
228        /// so all loads for a given program key are serialized.
229        loading_entries: Mutex<HashMap<Pubkey, (Slot, thread::ThreadId)>>,
230    },
231}
232
233/// This structure is the global cache of loaded, verified and compiled programs.
234///
235/// It ...
236/// - is validator global and fork graph aware, so it can optimize the commonalities across banks.
237/// - handles the visibility rules of un/re/deployments.
238/// - stores the usage statistics and verification status of each program.
239/// - is elastic and uses a probabilistic eviction strategy based on the usage statistics.
240/// - also keeps the compiled executables around, but only for the most used programs.
241/// - supports various kinds of tombstones to avoid loading programs which can not be loaded.
242/// - cleans up entries on orphan branches when the block store is rerooted.
243/// - supports the cache preparation phase before feature activations which can change cached programs.
244/// - manages the environments of the programs and upcoming environments for the next epoch.
245/// - allows for cooperative loading of TX batches which hit the same missing programs simultaneously.
246/// - enforces that all programs used in a batch are eagerly loaded ahead of execution.
247/// - is not persisted to disk or a snapshot, so it needs to cold start and warm up first.
248pub struct ProgramCache<FG: ForkGraph> {
249    /// Index of the cached entries and cooperative loading tasks
250    pub(crate) index: IndexImplementation,
251    /// The slot of the last rerooting
252    pub latest_root_slot: Slot,
253    /// Statistics counters
254    pub stats: ProgramCacheStats,
255    /// Reference to the block store
256    pub fork_graph: Option<Weak<RwLock<FG>>>,
257    /// Coordinates TX batches waiting for others to complete their task during cooperative loading
258    pub loading_task_waiter: Arc<LoadingTaskWaiter>,
259}
260
261impl<FG: ForkGraph> std::fmt::Debug for ProgramCache<FG> {
262    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
263        f.debug_struct("ProgramCache")
264            .field("root slot", &self.latest_root_slot)
265            .field("stats", &self.stats)
266            .field("index", &self.index)
267            .finish()
268    }
269}
270
271/// Local view into [ProgramCache] which was extracted for a specific TX batch.
272///
273/// This isolation enables the global [ProgramCache] to continue to evolve (e.g. evictions),
274/// while the TX batch is guaranteed it will continue to find all the programs it requires.
275/// For program management instructions this also buffers them before they are merged back into the global [ProgramCache].
276#[derive(Clone, Debug, Default)]
277pub struct ProgramCacheForTxBatch {
278    /// Pubkey is the address of a program.
279    /// ProgramCacheEntry is the corresponding program entry valid for the slot in which a transaction is being executed.
280    entries: HashMap<Pubkey, Arc<ProgramCacheEntry>>,
281    /// Program entries modified during the transaction batch.
282    modified_entries: HashMap<Pubkey, Arc<ProgramCacheEntry>>,
283    slot: Slot,
284    pub hit_max_limit: bool,
285    pub loaded_missing: bool,
286    pub merged_modified: bool,
287}
288
289impl ProgramCacheForTxBatch {
290    pub fn new(slot: Slot) -> Self {
291        Self {
292            entries: HashMap::new(),
293            modified_entries: HashMap::new(),
294            slot,
295            hit_max_limit: false,
296            loaded_missing: false,
297            merged_modified: false,
298        }
299    }
300
301    /// Refill the cache with a single entry. It's typically called during transaction loading, and
302    /// transaction processing (for program management instructions).
303    /// It replaces the existing entry (if any) with the provided entry. The return value contains
304    /// `true` if an entry existed.
305    /// The function also returns the newly inserted value.
306    pub fn replenish(
307        &mut self,
308        key: Pubkey,
309        entry: Arc<ProgramCacheEntry>,
310    ) -> (bool, Arc<ProgramCacheEntry>) {
311        (self.entries.insert(key, entry.clone()).is_some(), entry)
312    }
313
314    /// Store an entry in `modified_entries` for a program modified during the
315    /// transaction batch.
316    pub fn store_modified_entry(&mut self, key: Pubkey, entry: Arc<ProgramCacheEntry>) {
317        self.modified_entries.insert(key, entry);
318    }
319
320    /// Drain the program cache's modified entries, returning the owned
321    /// collection.
322    pub fn drain_modified_entries(&mut self) -> HashMap<Pubkey, Arc<ProgramCacheEntry>> {
323        std::mem::take(&mut self.modified_entries)
324    }
325
326    pub fn find(&self, key: &Pubkey) -> Option<Arc<ProgramCacheEntry>> {
327        // First lookup the cache of the programs modified by the current
328        // transaction. If not found, lookup the cache of the cache of the
329        // programs that are loaded for the transaction batch.
330        self.modified_entries
331            .get(key)
332            .or_else(|| self.entries.get(key))
333            .map(|entry| {
334                if entry.is_implicit_delay_visibility_tombstone(self.slot) {
335                    // Found a program entry on the current fork, but it's not effective
336                    // yet. It indicates that the program has delayed visibility. Return
337                    // the tombstone to reflect that.
338                    Arc::new(ProgramCacheEntry::new_delay_visibility_tombstone(
339                        entry.deployment_slot,
340                        entry.account_owner,
341                        Arc::clone(&entry.stats),
342                    ))
343                } else {
344                    entry.clone()
345                }
346            })
347    }
348
349    pub fn slot(&self) -> Slot {
350        self.slot
351    }
352
353    pub fn set_slot_for_tests(&mut self, slot: Slot) {
354        self.slot = slot;
355    }
356
357    pub fn merge(&mut self, modified_entries: &HashMap<Pubkey, Arc<ProgramCacheEntry>>) {
358        modified_entries.iter().for_each(|(key, entry)| {
359            self.merged_modified = true;
360            self.replenish(*key, entry.clone());
361        })
362    }
363
364    pub fn is_empty(&self) -> bool {
365        self.entries.is_empty()
366    }
367}
368
369#[derive(Clone, PartialEq, Debug)]
370pub enum ProgramCacheMatchCriteria {
371    DeployedOnOrAfterSlot(Slot),
372    NoCriteria,
373}
374
375impl<FG: ForkGraph> ProgramCache<FG> {
376    pub fn new(root_slot: Slot) -> Self {
377        Self {
378            index: IndexImplementation::V1 {
379                entries: HashMap::new(),
380                loading_entries: Mutex::new(HashMap::new()),
381            },
382            latest_root_slot: root_slot,
383            stats: ProgramCacheStats::default(),
384            fork_graph: None,
385            loading_task_waiter: Arc::new(LoadingTaskWaiter::default()),
386        }
387    }
388
389    pub fn set_fork_graph(&mut self, fork_graph: Weak<RwLock<FG>>) {
390        self.fork_graph = Some(fork_graph);
391    }
392
393    /// Insert a single entry. It's typically called during transaction loading,
394    /// when the cache doesn't contain the entry corresponding to program `key`.
395    pub fn assign_program(
396        &mut self,
397        program_runtime_environment: &ProgramRuntimeEnvironment,
398        key: Pubkey,
399        _last_modification_slot: Slot,
400        entry: Arc<ProgramCacheEntry>,
401    ) -> bool {
402        debug_assert!(!matches!(
403            &entry.program,
404            ProgramCacheEntryType::DelayVisibility
405        ));
406        // This function always returns `true` during normal operation.
407        // Only during the cache preparation phase this can return `false`
408        // for entries with `upcoming_environment`.
409        fn is_current_env(
410            program_runtime_environment: &ProgramRuntimeEnvironment,
411            env_opt: Option<&ProgramRuntimeEnvironment>,
412        ) -> bool {
413            env_opt
414                .map(|env| env == program_runtime_environment)
415                .unwrap_or(true)
416        }
417        match &mut self.index {
418            IndexImplementation::V1 { entries, .. } => {
419                let slot_versions = &mut entries.entry(key).or_default();
420                let insertion_point = slot_versions.binary_search_by(|at| {
421                    at.deployment_slot
422                        .cmp(&entry.deployment_slot)
423                        .then(at.account_owner.cmp(&entry.account_owner))
424                        .then(
425                            // This `.then()` has no effect during normal operation.
426                            // Only during the cache preparation phase this does allow entries
427                            // which only differ in their environment to be interleaved in `slot_versions`.
428                            is_current_env(
429                                program_runtime_environment,
430                                at.program.get_environment(),
431                            )
432                            .cmp(&is_current_env(
433                                program_runtime_environment,
434                                entry.program.get_environment(),
435                            )),
436                        )
437                });
438                match insertion_point {
439                    Ok(index) => {
440                        let existing = slot_versions.get_mut(index).unwrap();
441                        match (&existing.program, &entry.program) {
442                            (
443                                ProgramCacheEntryType::Builtin(_),
444                                ProgramCacheEntryType::Builtin(_),
445                            )
446                            | (ProgramCacheEntryType::Closed, ProgramCacheEntryType::Unloaded(_))
447                            | (
448                                ProgramCacheEntryType::Unloaded(_),
449                                ProgramCacheEntryType::Loaded(_),
450                            )
451                            | (
452                                ProgramCacheEntryType::Unloaded(_),
453                                ProgramCacheEntryType::FailedVerification(_),
454                            ) => {}
455                            _ => {
456                                // Something is wrong, I can feel it ...
457                                error!(
458                                    "ProgramCache::assign_program() failed key={key:?} \
459                                     existing={slot_versions:?} entry={entry:?}"
460                                );
461                                debug_assert!(false, "Unexpected replacement of an entry");
462                                self.stats.replacements.fetch_add(1, Ordering::Relaxed);
463                                return true;
464                            }
465                        }
466                        entry.stats.merge_from(&existing.stats);
467                        *existing = Arc::clone(&entry);
468                        self.stats.reloads.fetch_add(1, Ordering::Relaxed);
469                    }
470                    Err(index) => {
471                        self.stats.insertions.fetch_add(1, Ordering::Relaxed);
472                        slot_versions.insert(index, Arc::clone(&entry));
473                    }
474                }
475                // Remove existing entries in the same deployment slot unless they are for a different
476                // environment.
477                // This overwrites the current status of a program in program management instructions.
478                slot_versions.retain(|existing| {
479                    existing.deployment_slot != entry.deployment_slot
480                        || existing
481                            .program
482                            .get_environment()
483                            .zip(entry.program.get_environment())
484                            .map(|(a, b)| a != b)
485                            .unwrap_or(false)
486                        || Arc::ptr_eq(existing, &entry)
487                });
488            }
489        }
490        false
491    }
492
493    pub fn prune_by_deployment_slot(&mut self, slot: Slot) {
494        match &mut self.index {
495            IndexImplementation::V1 { entries, .. } => {
496                for second_level in entries.values_mut() {
497                    second_level.retain(|entry| entry.deployment_slot != slot);
498                }
499                self.remove_programs_with_no_entries();
500            }
501        }
502    }
503
504    /// Before rerooting the blockstore this removes all superfluous entries
505    pub fn prune(
506        &mut self,
507        new_root_slot: Slot,
508        new_environment: Option<ProgramRuntimeEnvironment>,
509        fork_graph: &FG,
510    ) {
511        match &mut self.index {
512            IndexImplementation::V1 { entries, .. } => {
513                for second_level in entries.values_mut() {
514                    // Remove entries un/re/deployed on orphan forks
515                    let mut first_ancestor_found = false;
516                    let mut first_ancestor_env = None;
517                    *second_level = second_level
518                        .iter()
519                        .rev()
520                        .filter(|entry| {
521                            let relation =
522                                fork_graph.relationship(entry.deployment_slot, new_root_slot);
523                            if entry.deployment_slot >= new_root_slot {
524                                matches!(relation, BlockRelation::Equal | BlockRelation::Descendant)
525                            } else if matches!(relation, BlockRelation::Ancestor)
526                                || entry.deployment_slot <= self.latest_root_slot
527                            {
528                                if !first_ancestor_found {
529                                    first_ancestor_found = true;
530                                    first_ancestor_env = entry.program.get_environment();
531                                    return true;
532                                }
533                                // Do not prune the entry if the runtime environment of the entry is
534                                // different than the entry that was previously found (stored in
535                                // first_ancestor_env). Different environment indicates that this entry
536                                // might belong to an older epoch that had a different environment (e.g.
537                                // different feature set). Once the root moves to the new/current epoch,
538                                // the entry will get pruned. But, until then the entry might still be
539                                // getting used by an older slot.
540                                if let Some(entry_env) = entry.program.get_environment()
541                                    && let Some(env) = first_ancestor_env
542                                    && entry_env != env
543                                {
544                                    return true;
545                                }
546                                self.stats.prunes_orphan.fetch_add(1, Ordering::Relaxed);
547                                false
548                            } else {
549                                self.stats.prunes_orphan.fetch_add(1, Ordering::Relaxed);
550                                false
551                            }
552                        })
553                        .filter(|entry| {
554                            // Remove outdated environment of previous feature set
555                            if let Some(new_environment) = new_environment.as_ref()
556                                && !Self::matches_environment(entry, new_environment)
557                            {
558                                self.stats
559                                    .prunes_environment
560                                    .fetch_add(1, Ordering::Relaxed);
561                                return false;
562                            }
563                            true
564                        })
565                        .cloned()
566                        .collect();
567                    second_level.reverse();
568                }
569            }
570        }
571        self.remove_programs_with_no_entries();
572        debug_assert!(self.latest_root_slot <= new_root_slot);
573        self.latest_root_slot = new_root_slot;
574    }
575
576    fn matches_environment(
577        entry: &Arc<ProgramCacheEntry>,
578        program_runtime_environment: &ProgramRuntimeEnvironment,
579    ) -> bool {
580        let Some(environment) = entry.program.get_environment() else {
581            return true;
582        };
583        environment == program_runtime_environment
584    }
585
586    fn matches_criteria(
587        program: &Arc<ProgramCacheEntry>,
588        criteria: &ProgramCacheMatchCriteria,
589    ) -> bool {
590        match criteria {
591            ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(slot) => {
592                program.deployment_slot >= *slot
593            }
594            ProgramCacheMatchCriteria::NoCriteria => true,
595        }
596    }
597
598    /// Extracts a subset of the programs relevant to a transaction batch
599    /// and returns which program accounts the accounts DB needs to load.
600    pub fn extract(
601        &self,
602        search_for: &mut Vec<ProgramToLoad>,
603        loaded_programs_for_tx_batch: &mut ProgramCacheForTxBatch,
604        program_runtime_environment_for_execution: &ProgramRuntimeEnvironment,
605        increment_usage_counter: bool,
606        count_hits_and_misses: bool,
607    ) -> Option<Pubkey> {
608        debug_assert!(self.fork_graph.is_some());
609        let fork_graph = self.fork_graph.as_ref().unwrap().upgrade().unwrap();
610        let locked_fork_graph = fork_graph.read().unwrap();
611        let mut cooperative_loading_task = None;
612        match &self.index {
613            IndexImplementation::V1 {
614                entries,
615                loading_entries,
616            } => {
617                search_for.retain(|program_to_load| {
618                    if let Some(second_level) = entries.get(program_to_load.program_id) {
619                        let mut filter_by_deployment_slot = None;
620                        for entry in second_level.iter().rev() {
621                            let required_deployment_slot =
622                                filter_by_deployment_slot.unwrap_or(entry.deployment_slot);
623                            if required_deployment_slot != entry.deployment_slot
624                                || program_to_load.loader != entry.account_owner
625                            {
626                                continue;
627                            }
628                            let entry_in_same_branch = entry.deployment_slot
629                                <= self.latest_root_slot
630                                || matches!(
631                                    locked_fork_graph.relationship(
632                                        entry.deployment_slot,
633                                        loaded_programs_for_tx_batch.slot
634                                    ),
635                                    BlockRelation::Equal | BlockRelation::Ancestor
636                                );
637                            if entry_in_same_branch {
638                                let entry_is_effective =
639                                    loaded_programs_for_tx_batch.slot >= entry.effective_slot();
640                                let entry_to_return = if entry_is_effective {
641                                    if !Self::matches_environment(
642                                        entry,
643                                        program_runtime_environment_for_execution,
644                                    ) {
645                                        // We found an entry that would work, had its environment matched
646                                        // the one we're planning to use for this slot.
647                                        //
648                                        // At this point we know that whatever the "current version" of
649                                        // program is, it must have had a deployment slot equal to the
650                                        // program we're looking at in this iteration. We just have to find
651                                        // one with the correct environment and can skip entries for any
652                                        // other deployment slot while searching further.
653                                        filter_by_deployment_slot = filter_by_deployment_slot
654                                            .or(Some(entry.deployment_slot));
655                                        continue;
656                                    }
657                                    if !Self::matches_criteria(
658                                        entry,
659                                        &program_to_load.match_criteria,
660                                    ) {
661                                        break;
662                                    }
663                                    if let ProgramCacheEntryType::Unloaded(_environment) =
664                                        &entry.program
665                                    {
666                                        break;
667                                    }
668                                    entry.clone()
669                                } else if entry.is_implicit_delay_visibility_tombstone(
670                                    loaded_programs_for_tx_batch.slot,
671                                ) {
672                                    // Found a program entry on the current fork, but it's not effective
673                                    // yet. It indicates that the program has delayed visibility. Return
674                                    // the tombstone to reflect that.
675                                    Arc::new(ProgramCacheEntry::new_delay_visibility_tombstone(
676                                        entry.deployment_slot,
677                                        entry.account_owner,
678                                        Arc::clone(&entry.stats),
679                                    ))
680                                } else {
681                                    continue;
682                                };
683                                entry_to_return
684                                    .update_access_slot(loaded_programs_for_tx_batch.slot);
685                                if increment_usage_counter {
686                                    entry_to_return.stats.uses.fetch_add(1, Ordering::Relaxed);
687                                }
688                                loaded_programs_for_tx_batch
689                                    .entries
690                                    .insert(*program_to_load.program_id, entry_to_return);
691                                return false;
692                            }
693                        }
694                    }
695                    if cooperative_loading_task.is_none() {
696                        let mut loading_entries = loading_entries.lock().unwrap();
697                        let entry = loading_entries.entry(*program_to_load.program_id);
698                        if let Entry::Vacant(entry) = entry {
699                            entry.insert((
700                                loaded_programs_for_tx_batch.slot,
701                                thread::current().id(),
702                            ));
703                            cooperative_loading_task = Some(*program_to_load.program_id);
704                        }
705                    }
706                    true
707                });
708            }
709        }
710        drop(locked_fork_graph);
711        if count_hits_and_misses {
712            self.stats
713                .misses
714                .fetch_add(search_for.len() as u64, Ordering::Relaxed);
715            self.stats.hits.fetch_add(
716                loaded_programs_for_tx_batch.entries.len() as u64,
717                Ordering::Relaxed,
718            );
719        }
720        cooperative_loading_task
721    }
722
723    /// Called by Bank::replenish_program_cache() for each program that is done loading.
724    pub fn finish_cooperative_loading_task(
725        &mut self,
726        program_runtime_environment: &ProgramRuntimeEnvironment,
727        current_slot: Slot,
728        key: Pubkey,
729        last_modification_slot: Slot,
730        loaded_program: Arc<ProgramCacheEntry>,
731    ) -> bool {
732        match &mut self.index {
733            IndexImplementation::V1 {
734                loading_entries, ..
735            } => {
736                let loading_thread = loading_entries.get_mut().unwrap().remove(&key);
737                debug_assert_eq!(loading_thread, Some((current_slot, thread::current().id())));
738                // Check that it will be visible to our own fork once inserted
739                if loaded_program.deployment_slot > self.latest_root_slot
740                    && !matches!(
741                        self.fork_graph
742                            .as_ref()
743                            .unwrap()
744                            .upgrade()
745                            .unwrap()
746                            .read()
747                            .unwrap()
748                            .relationship(loaded_program.deployment_slot, current_slot),
749                        BlockRelation::Equal | BlockRelation::Ancestor
750                    )
751                {
752                    self.stats.lost_insertions.fetch_add(1, Ordering::Relaxed);
753                }
754                let was_occupied = self.assign_program(
755                    program_runtime_environment,
756                    key,
757                    last_modification_slot,
758                    loaded_program,
759                );
760                self.loading_task_waiter.notify();
761                was_occupied
762            }
763        }
764    }
765
766    pub fn merge(
767        &mut self,
768        program_runtime_environment: &ProgramRuntimeEnvironment,
769        current_slot: Slot,
770        modified_entries: &HashMap<Pubkey, Arc<ProgramCacheEntry>>,
771    ) {
772        modified_entries.iter().for_each(|(key, entry)| {
773            self.assign_program(
774                program_runtime_environment,
775                *key,
776                current_slot,
777                entry.clone(),
778            );
779        })
780    }
781
782    /// Returns the list of entries which are verified and compiled.
783    pub fn get_flattened_entries(&self) -> Vec<(Pubkey, Slot, Arc<ProgramCacheEntry>)> {
784        match &self.index {
785            IndexImplementation::V1 { entries, .. } => entries
786                .iter()
787                .flat_map(|(id, second_level)| {
788                    second_level
789                        .iter()
790                        .filter_map(move |program| match program.program {
791                            ProgramCacheEntryType::Loaded(_) => Some((*id, 0, program.clone())),
792                            _ => None,
793                        })
794                })
795                .collect(),
796        }
797    }
798
799    /// Returns the list of all entries in the cache.
800    #[cfg(feature = "dev-context-only-utils")]
801    pub fn get_flattened_entries_for_tests(&self) -> Vec<(Pubkey, Arc<ProgramCacheEntry>)> {
802        match &self.index {
803            IndexImplementation::V1 { entries, .. } => entries
804                .iter()
805                .flat_map(|(id, second_level)| {
806                    second_level.iter().map(|program| (*id, program.clone()))
807                })
808                .collect(),
809        }
810    }
811
812    /// Returns the slot versions for the given program id.
813    pub fn get_slot_versions_for_tests(&self, key: &Pubkey) -> &[Arc<ProgramCacheEntry>] {
814        match &self.index {
815            IndexImplementation::V1 { entries, .. } => entries
816                .get(key)
817                .map(|second_level| second_level.as_ref())
818                .unwrap_or(&[]),
819        }
820    }
821
822    /// Unloads programs which were used infrequently
823    pub fn sort_and_unload(&mut self, shrink_to_percent: Percent) {
824        let mut sorted_candidates = self.get_flattened_entries();
825        sorted_candidates.sort_by_cached_key(|(_id, _last_modification_slot, program)| {
826            program.stats.uses.load(Ordering::Relaxed)
827        });
828        let num_to_unload = sorted_candidates
829            .len()
830            .saturating_sub(percent_of_max_entries(shrink_to_percent));
831        for (program, last_modification_slot, entry) in sorted_candidates.iter().take(num_to_unload)
832        {
833            self.unload_program_entry(*program, *last_modification_slot, entry);
834        }
835    }
836
837    /// Evicts programs using random selection, choosing the worst scoring program out of the
838    /// entries sampled.
839    ///
840    /// The eviction is performed enough number of times to reduce the cache usage to the given
841    /// percentage.
842    pub fn evict_using_random_selection(&mut self, shrink_to_percent: Percent, now: Slot) {
843        let mut candidates = self.get_flattened_entries();
844        let mut rng = rng();
845        self.stats
846            .water_level
847            .store(candidates.len() as u64, Ordering::Relaxed);
848        let num_to_unload = candidates
849            .len()
850            .saturating_sub(percent_of_max_entries(shrink_to_percent));
851        let mut sample_entry = |candidates: &Vec<(Pubkey, u64, Arc<ProgramCacheEntry>)>| {
852            // gen_range is deprecated in favor of random_range in rand>=0.9, but we also get
853            // rnd() from shuttle, which doesn't yet support rand 0.9 APIs
854            #[cfg(feature = "shuttle-test")]
855            let index = rng.gen_range(0..candidates.len());
856            #[cfg(not(feature = "shuttle-test"))]
857            let index = rng.random_range(0..candidates.len());
858            let usage_counter = candidates
859                .get(index)
860                .expect("Failed to get cached entry")
861                .2
862                .retention_score();
863            (index, usage_counter)
864        };
865
866        // Random sampling with just 2 choices can frequently lead to a situation where both
867        // entries chosen have relatively high retention scores, having us to pick one out of two
868        // poor options. We can tell what a relatively high retention score is, so we can make a
869        // few additional samples until we hit some other entry that isn't as highly scoring.
870        //
871        // Note that the "high enough" compilation time and use count numbers used here are
872        // relatively arbitrary.
873        const MAX_ADDITIONAL_SAMPLES: usize = 3;
874        let avoid_evicting_above_score = retention_score(now, 500 * EMA_SCALE, 500);
875        for _ in 0..num_to_unload {
876            let (mut index, mut score) = sample_entry(&candidates);
877            for _ in 0..MAX_ADDITIONAL_SAMPLES {
878                let (sample_index, sample_score) = sample_entry(&candidates);
879                if score > sample_score {
880                    index = sample_index;
881                    score = sample_score;
882                }
883                if score < avoid_evicting_above_score {
884                    break;
885                }
886            }
887            let (id, last_modification_slot, entry) = candidates.swap_remove(index);
888            self.unload_program_entry(id, last_modification_slot, &entry);
889        }
890    }
891
892    /// Removes all the entries at the given keys, if they exist
893    pub fn remove_programs(&mut self, keys: impl Iterator<Item = Pubkey>) {
894        match &mut self.index {
895            IndexImplementation::V1 { entries, .. } => {
896                for k in keys {
897                    entries.remove(&k);
898                }
899            }
900        }
901    }
902
903    /// This function removes the given entry for the given program from the cache.
904    /// The function expects that the program and entry exists in the cache. Otherwise it'll panic.
905    fn unload_program_entry(
906        &mut self,
907        id: Pubkey,
908        _last_modification_slot: Slot,
909        remove_entry: &Arc<ProgramCacheEntry>,
910    ) {
911        match &mut self.index {
912            IndexImplementation::V1 { entries, .. } => {
913                let second_level = entries.get_mut(&id).expect("Cache lookup failed");
914                let candidate = second_level
915                    .iter_mut()
916                    .find(|entry| Arc::ptr_eq(entry, remove_entry))
917                    .expect("Program entry not found");
918
919                // Only loaded entries shall be unloaded by eviction.
920                if let ProgramCacheEntryType::Loaded(_) = candidate.program
921                    && let Some(unloaded) = candidate.to_unloaded()
922                {
923                    if candidate.stats.uses.load(Ordering::Relaxed) == 1 {
924                        self.stats.one_hit_wonders.fetch_add(1, Ordering::Relaxed);
925                    }
926                    self.stats
927                        .evictions
928                        .entry(id)
929                        .and_modify(|c| *c = c.saturating_add(1))
930                        .or_insert(1);
931                    *candidate = Arc::new(unloaded);
932                }
933            }
934        }
935    }
936
937    fn remove_programs_with_no_entries(&mut self) {
938        match &mut self.index {
939            IndexImplementation::V1 { entries, .. } => {
940                let num_programs_before_removal = entries.len();
941                entries.retain(|_key, second_level| !second_level.is_empty());
942                if entries.len() < num_programs_before_removal {
943                    self.stats.empty_entries.fetch_add(
944                        num_programs_before_removal.saturating_sub(entries.len()) as u64,
945                        Ordering::Relaxed,
946                    );
947                }
948            }
949        }
950    }
951}
952
953#[cfg(feature = "frozen-abi")]
954impl solana_frozen_abi::abi_example::AbiExample for ProgramCacheEntry {
955    fn example() -> Self {
956        // ProgramCacheEntry isn't serializable by definition.
957        Self::default()
958    }
959}
960
961#[cfg(feature = "frozen-abi")]
962impl<FG: ForkGraph> solana_frozen_abi::abi_example::AbiExample for ProgramCache<FG> {
963    fn example() -> Self {
964        // ProgramCache isn't serializable by definition.
965        Self::new(Slot::default())
966    }
967}
968
969#[cfg(test)]
970pub(crate) mod tests {
971    use {
972        crate::{
973            loaded_programs::{
974                BlockRelation, ForkGraph, Percent, ProgramCache, ProgramCacheForTxBatch,
975                ProgramCacheMatchCriteria, ProgramRuntimeEnvironment, ProgramToLoad,
976                get_mock_program_runtime_environment,
977            },
978            program_cache_entry::{
979                ProgramCacheEntry, ProgramCacheEntryOwner, ProgramCacheEntryType,
980            },
981            program_metrics::ProgramStatistics,
982        },
983        assert_matches::assert_matches,
984        solana_clock::Slot,
985        solana_pubkey::Pubkey,
986        solana_sbpf::{elf::Executable, program::BuiltinProgram},
987        solana_svm_type_overrides::sync::{
988            Arc, RwLock,
989            atomic::{AtomicU64, Ordering},
990        },
991        std::{fs::File, io::Read, ops::ControlFlow},
992        test_case::test_matrix,
993    };
994
995    fn new_test_entry(deployment_slot: Slot) -> Arc<ProgramCacheEntry> {
996        new_test_entry_with_usage(deployment_slot, ProgramStatistics::default())
997    }
998
999    fn new_loaded_entry(env: ProgramRuntimeEnvironment) -> ProgramCacheEntryType {
1000        let mut elf = Vec::new();
1001        File::open("../programs/bpf_loader/test_elfs/out/noop_aligned.so")
1002            .unwrap()
1003            .read_to_end(&mut elf)
1004            .unwrap();
1005        let executable = Executable::load(&elf, Arc::clone(&*env)).unwrap();
1006        ProgramCacheEntryType::Loaded(executable)
1007    }
1008
1009    pub(crate) fn new_test_entry_with_usage(
1010        deployment_slot: Slot,
1011        stats: ProgramStatistics,
1012    ) -> Arc<ProgramCacheEntry> {
1013        Arc::new(ProgramCacheEntry {
1014            program: new_loaded_entry(get_mock_program_runtime_environment()),
1015            account_owner: ProgramCacheEntryOwner::LoaderV2,
1016            deployment_slot,
1017            stats: Arc::new(stats),
1018            latest_access_slot: AtomicU64::new(deployment_slot),
1019        })
1020    }
1021
1022    fn new_test_builtin_entry(deployment_slot: Slot) -> Arc<ProgramCacheEntry> {
1023        Arc::new(ProgramCacheEntry {
1024            program: ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1025            account_owner: ProgramCacheEntryOwner::NativeLoader,
1026            deployment_slot,
1027            stats: Arc::default(),
1028            latest_access_slot: AtomicU64::default(),
1029        })
1030    }
1031
1032    fn set_failed_verification_tombstone<FG: ForkGraph>(
1033        cache: &mut ProgramCache<FG>,
1034        key: Pubkey,
1035        current_slot: Slot,
1036        env: ProgramRuntimeEnvironment,
1037    ) -> Arc<ProgramCacheEntry> {
1038        let program = Arc::new(ProgramCacheEntry::new_failed_verification_tombstone(
1039            current_slot,
1040            ProgramCacheEntryOwner::LoaderV2,
1041            ProgramRuntimeEnvironment::clone(&env),
1042        ));
1043        cache.assign_program(&env, key, current_slot, program.clone());
1044        program
1045    }
1046
1047    fn insert_unloaded_entry<FG: ForkGraph>(
1048        cache: &mut ProgramCache<FG>,
1049        key: Pubkey,
1050        current_slot: Slot,
1051    ) -> Arc<ProgramCacheEntry> {
1052        let env = get_mock_program_runtime_environment();
1053        let loaded = new_test_entry_with_usage(current_slot, ProgramStatistics::default());
1054        let unloaded = Arc::new(loaded.to_unloaded().expect("Failed to unload the program"));
1055        cache.assign_program(&env, key, current_slot, unloaded.clone());
1056        unloaded
1057    }
1058
1059    fn num_matching_entries<P, FG>(cache: &ProgramCache<FG>, predicate: P) -> usize
1060    where
1061        P: Fn(&ProgramCacheEntryType) -> bool,
1062        FG: ForkGraph,
1063    {
1064        cache
1065            .get_flattened_entries_for_tests()
1066            .iter()
1067            .filter(|(_key, program)| predicate(&program.program))
1068            .count()
1069    }
1070
1071    #[expect(clippy::arithmetic_side_effects)]
1072    fn program_deploy_test_helper(
1073        cache: &mut ProgramCache<TestForkGraph>,
1074        program: Pubkey,
1075        deployment_slots: Vec<Slot>,
1076        usage_counters: Vec<u64>,
1077        programs: &mut Vec<(Pubkey, Slot, u64)>,
1078    ) {
1079        let env = get_mock_program_runtime_environment();
1080        // Add multiple entries for program
1081        deployment_slots
1082            .iter()
1083            .enumerate()
1084            .for_each(|(i, deployment_slot)| {
1085                let usage_counter = *usage_counters.get(i).unwrap_or(&0);
1086                let stats = ProgramStatistics {
1087                    uses: usage_counter.into(),
1088                    ..Default::default()
1089                };
1090                cache.assign_program(
1091                    &env,
1092                    program,
1093                    *deployment_slot,
1094                    new_test_entry_with_usage(*deployment_slot, stats),
1095                );
1096                programs.push((program, *deployment_slot, usage_counter));
1097            });
1098
1099        let next_slot = deployment_slots.iter().max().map_or(0, |slot| slot + 1);
1100
1101        // Add tombstones entries for program
1102        let env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
1103        for slot in next_slot..next_slot + 10 {
1104            set_failed_verification_tombstone(
1105                cache,
1106                program,
1107                slot,
1108                ProgramRuntimeEnvironment::clone(&env),
1109            );
1110        }
1111
1112        // Add unloaded entries for program
1113        for slot in next_slot + 10..next_slot + 20 {
1114            insert_unloaded_entry(cache, program, slot);
1115        }
1116    }
1117
1118    #[test]
1119    fn test_random_eviction() {
1120        let mut programs = vec![];
1121        let mut cache = ProgramCache::<TestForkGraph>::new(0);
1122
1123        // This test adds different kind of entries to the cache.
1124        // Tombstones and unloaded entries are expected to not be evicted.
1125        // It also adds multiple entries for three programs as it tries to create a typical cache instance.
1126
1127        // Program 1
1128        program_deploy_test_helper(
1129            &mut cache,
1130            Pubkey::new_unique(),
1131            vec![0, 10, 20, 30, 40],
1132            vec![4, 5, 25, 35, 12],
1133            &mut programs,
1134        );
1135
1136        // Program 2
1137        program_deploy_test_helper(
1138            &mut cache,
1139            Pubkey::new_unique(),
1140            vec![5, 11, 21, 24],
1141            vec![0, 2, 30, 45],
1142            &mut programs,
1143        );
1144
1145        // Program 3
1146        program_deploy_test_helper(
1147            &mut cache,
1148            Pubkey::new_unique(),
1149            vec![0, 5, 15, 25],
1150            vec![100, 3, 20, 40],
1151            &mut programs,
1152        );
1153
1154        // 1 for each deployment slot
1155        let num_loaded_expected = 13;
1156        // 10 for each program
1157        let num_unloaded_expected = 30;
1158        // 10 for each program
1159        let num_tombstones_expected = 30;
1160
1161        // Count the number of loaded, unloaded and tombstone entries.
1162        programs.sort_by_key(|(_id, _slot, usage_count)| *usage_count);
1163        let num_loaded = num_matching_entries(&cache, |program_type| {
1164            matches!(program_type, ProgramCacheEntryType::Loaded(_))
1165        });
1166        let num_unloaded = num_matching_entries(&cache, |program_type| {
1167            matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1168        });
1169        let num_tombstones = num_matching_entries(&cache, |program_type| {
1170            matches!(
1171                program_type,
1172                ProgramCacheEntryType::DelayVisibility
1173                    | ProgramCacheEntryType::FailedVerification(_)
1174                    | ProgramCacheEntryType::Closed
1175            )
1176        });
1177
1178        // Test that the cache is constructed with the expected number of entries.
1179        assert_eq!(num_loaded, num_loaded_expected);
1180        assert_eq!(num_unloaded, num_unloaded_expected);
1181        assert_eq!(num_tombstones, num_tombstones_expected);
1182
1183        // Evict entries from the cache
1184        let eviction_pct: Percent = 1;
1185
1186        let num_loaded_expected = crate::loaded_programs::percent_of_max_entries(eviction_pct);
1187        let num_unloaded_expected = num_unloaded_expected + num_loaded - num_loaded_expected;
1188        cache.evict_using_random_selection(eviction_pct, 21);
1189
1190        // Count the number of loaded, unloaded and tombstone entries.
1191        let num_loaded = num_matching_entries(&cache, |program_type| {
1192            matches!(program_type, ProgramCacheEntryType::Loaded(_))
1193        });
1194        let num_unloaded = num_matching_entries(&cache, |program_type| {
1195            matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1196        });
1197        let num_tombstones = num_matching_entries(&cache, |program_type| {
1198            matches!(program_type, ProgramCacheEntryType::FailedVerification(_))
1199        });
1200
1201        // However many entries are left after the shrink
1202        assert_eq!(num_loaded, num_loaded_expected);
1203        // The original unloaded entries + the evicted loaded entries
1204        assert_eq!(num_unloaded, num_unloaded_expected);
1205        // The original tombstones are not evicted
1206        assert_eq!(num_tombstones, num_tombstones_expected);
1207    }
1208
1209    #[test]
1210    fn test_eviction() {
1211        let mut programs = vec![];
1212        let mut cache = ProgramCache::<TestForkGraph>::new(0);
1213
1214        // Program 1
1215        program_deploy_test_helper(
1216            &mut cache,
1217            Pubkey::new_unique(),
1218            vec![0, 10, 20, 30, 40],
1219            vec![4, 5, 25, 35, 12],
1220            &mut programs,
1221        );
1222
1223        // Program 2
1224        program_deploy_test_helper(
1225            &mut cache,
1226            Pubkey::new_unique(),
1227            vec![5, 11, 21, 24],
1228            vec![0, 2, 30, 45],
1229            &mut programs,
1230        );
1231
1232        // Program 3
1233        program_deploy_test_helper(
1234            &mut cache,
1235            Pubkey::new_unique(),
1236            vec![0, 5, 15, 25],
1237            vec![100, 3, 20, 40],
1238            &mut programs,
1239        );
1240
1241        // 1 for each deployment slot
1242        let num_loaded_expected = 13;
1243        // 10 for each program
1244        let num_unloaded_expected = 30;
1245        // 10 for each program
1246        let num_tombstones_expected = 30;
1247
1248        // Count the number of loaded, unloaded and tombstone entries.
1249        programs.sort_by_key(|(_id, _slot, usage_count)| *usage_count);
1250        let num_loaded = num_matching_entries(&cache, |program_type| {
1251            matches!(program_type, ProgramCacheEntryType::Loaded(_))
1252        });
1253        let num_unloaded = num_matching_entries(&cache, |program_type| {
1254            matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1255        });
1256        let num_tombstones = num_matching_entries(&cache, |program_type| {
1257            matches!(program_type, ProgramCacheEntryType::FailedVerification(_))
1258        });
1259
1260        // Test that the cache is constructed with the expected number of entries.
1261        assert_eq!(num_loaded, num_loaded_expected);
1262        assert_eq!(num_unloaded, num_unloaded_expected);
1263        assert_eq!(num_tombstones, num_tombstones_expected);
1264
1265        // Evict entries from the cache
1266        let eviction_pct: Percent = 1;
1267
1268        let num_loaded_expected = crate::loaded_programs::percent_of_max_entries(eviction_pct);
1269        let num_unloaded_expected = num_unloaded_expected + num_loaded - num_loaded_expected;
1270
1271        cache.sort_and_unload(eviction_pct);
1272
1273        // Check that every program is still in the cache.
1274        let entries = cache.get_flattened_entries_for_tests();
1275        programs.iter().for_each(|entry| {
1276            assert!(entries.iter().any(|(key, _entry)| key == &entry.0));
1277        });
1278
1279        let unloaded = entries
1280            .iter()
1281            .filter_map(|(key, program)| {
1282                matches!(program.program, ProgramCacheEntryType::Unloaded(_))
1283                    .then_some((*key, program.stats.uses.load(Ordering::Relaxed)))
1284            })
1285            .collect::<Vec<(Pubkey, u64)>>();
1286
1287        for index in 0..3 {
1288            let expected = programs.get(index).expect("Missing program");
1289            assert!(unloaded.contains(&(expected.0, expected.2)));
1290        }
1291
1292        // Count the number of loaded, unloaded and tombstone entries.
1293        let num_loaded = num_matching_entries(&cache, |program_type| {
1294            matches!(program_type, ProgramCacheEntryType::Loaded(_))
1295        });
1296        let num_unloaded = num_matching_entries(&cache, |program_type| {
1297            matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1298        });
1299        let num_tombstones = num_matching_entries(&cache, |program_type| {
1300            matches!(
1301                program_type,
1302                ProgramCacheEntryType::DelayVisibility
1303                    | ProgramCacheEntryType::FailedVerification(_)
1304                    | ProgramCacheEntryType::Closed
1305            )
1306        });
1307
1308        // However many entries are left after the shrink
1309        assert_eq!(num_loaded, num_loaded_expected);
1310        // The original unloaded entries + the evicted loaded entries
1311        assert_eq!(num_unloaded, num_unloaded_expected);
1312        // The original tombstones are not evicted
1313        assert_eq!(num_tombstones, num_tombstones_expected);
1314    }
1315
1316    #[test]
1317    fn test_usage_count_of_unloaded_program() {
1318        let mut cache = ProgramCache::<TestForkGraph>::new(0);
1319        let env = get_mock_program_runtime_environment();
1320
1321        let program = Pubkey::new_unique();
1322        let evict_to_pct: Percent = 2;
1323        let cache_capacity_after_shrink =
1324            crate::loaded_programs::percent_of_max_entries(evict_to_pct);
1325        // Add enough programs to the cache to trigger 1 eviction after shrinking.
1326        let num_total_programs = (cache_capacity_after_shrink + 1) as u64;
1327        (0..num_total_programs).for_each(|i| {
1328            let stats = ProgramStatistics {
1329                uses: (i + 10).into(),
1330                ..Default::default()
1331            };
1332            let entry = new_test_entry_with_usage(i, stats);
1333            cache.assign_program(&env, program, i, entry);
1334        });
1335
1336        cache.sort_and_unload(evict_to_pct);
1337
1338        let num_unloaded = num_matching_entries(&cache, |program_type| {
1339            matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1340        });
1341        assert_eq!(num_unloaded, 1);
1342
1343        cache
1344            .get_flattened_entries_for_tests()
1345            .iter()
1346            .for_each(|(_key, program)| {
1347                if matches!(program.program, ProgramCacheEntryType::Unloaded(_)) {
1348                    // Test that the usage counter is retained for the unloaded program
1349                    assert_eq!(program.stats.uses.load(Ordering::Relaxed), 10);
1350                    assert_eq!(program.deployment_slot, 0);
1351                    assert_eq!(program.effective_slot(), 1);
1352                }
1353            });
1354
1355        // Replenish the program that was just unloaded. Use 0 as the usage counter. This should be
1356        // updated with the usage counter from the unloaded program.
1357        cache.assign_program(
1358            &env,
1359            program,
1360            0,
1361            new_test_entry_with_usage(0, ProgramStatistics::default()),
1362        );
1363
1364        cache
1365            .get_flattened_entries_for_tests()
1366            .iter()
1367            .for_each(|(_key, program)| {
1368                if matches!(program.program, ProgramCacheEntryType::Unloaded(_))
1369                    && program.deployment_slot == 0
1370                    && program.effective_slot() == 1
1371                {
1372                    // Test that the usage counter was correctly updated.
1373                    assert_eq!(program.stats.uses.load(Ordering::Relaxed), 10);
1374                }
1375            });
1376    }
1377
1378    #[test]
1379    fn test_fuzz_assign_program_order() {
1380        use rand::prelude::SliceRandom;
1381        const EXPECTED_ENTRIES: [(u64, bool); 5] =
1382            [(1, true), (3, false), (5, true), (9, true), (10, false)];
1383        let mut rng = rand::rng();
1384        let program_id = Pubkey::new_unique();
1385        let env = get_mock_program_runtime_environment();
1386        for _ in 0..1000 {
1387            let mut entries = EXPECTED_ENTRIES.to_vec();
1388            entries.shuffle(&mut rng);
1389            let mut cache = ProgramCache::<TestForkGraph>::new(0);
1390            for (deployment_slot, delay_visibility) in entries {
1391                let entry = Arc::new(if delay_visibility {
1392                    ProgramCacheEntry {
1393                        program: new_loaded_entry(ProgramRuntimeEnvironment::from(
1394                            BuiltinProgram::new_mock(),
1395                        )), // Assign them different environments
1396                        account_owner: ProgramCacheEntryOwner::LoaderV2,
1397                        deployment_slot,
1398                        stats: Arc::default(),
1399                        latest_access_slot: AtomicU64::new(deployment_slot),
1400                    }
1401                } else {
1402                    ProgramCacheEntry::new_failed_verification_tombstone(
1403                        deployment_slot,
1404                        ProgramCacheEntryOwner::LoaderV2,
1405                        ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock()), // Assign them different environments
1406                    )
1407                });
1408                assert!(!cache.assign_program(&env, program_id, deployment_slot, entry));
1409            }
1410            for ((deployment_slot, delay_visibility), entry) in EXPECTED_ENTRIES
1411                .iter()
1412                .zip(cache.get_slot_versions_for_tests(&program_id).iter())
1413            {
1414                assert_eq!(entry.deployment_slot, *deployment_slot);
1415                assert_eq!(
1416                    entry.effective_slot(),
1417                    deployment_slot.saturating_add(*delay_visibility as u64)
1418                );
1419            }
1420        }
1421    }
1422
1423    #[test_matrix(
1424        (
1425            ProgramCacheEntryType::FailedVerification(get_mock_program_runtime_environment()),
1426            new_loaded_entry(get_mock_program_runtime_environment()),
1427        ),
1428        (
1429            ProgramCacheEntryType::FailedVerification(get_mock_program_runtime_environment()),
1430            ProgramCacheEntryType::Closed,
1431            ProgramCacheEntryType::Unloaded(get_mock_program_runtime_environment()),
1432            new_loaded_entry(get_mock_program_runtime_environment()),
1433            ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1434        )
1435    )]
1436    #[test_matrix(
1437        ProgramCacheEntryType::Closed,
1438        (
1439            ProgramCacheEntryType::FailedVerification(get_mock_program_runtime_environment()),
1440            ProgramCacheEntryType::Closed,
1441            new_loaded_entry(get_mock_program_runtime_environment()),
1442            ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1443        )
1444    )]
1445    #[test_matrix(
1446        ProgramCacheEntryType::Unloaded(get_mock_program_runtime_environment()),
1447        (
1448            ProgramCacheEntryType::Closed,
1449            ProgramCacheEntryType::Unloaded(get_mock_program_runtime_environment()),
1450            ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1451        )
1452    )]
1453    #[test_matrix(
1454        (ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),),
1455        (
1456            ProgramCacheEntryType::FailedVerification(get_mock_program_runtime_environment()),
1457            ProgramCacheEntryType::Closed,
1458            ProgramCacheEntryType::Unloaded(get_mock_program_runtime_environment()),
1459            new_loaded_entry(get_mock_program_runtime_environment()),
1460        )
1461    )]
1462    #[should_panic(expected = "Unexpected replacement of an entry")]
1463    fn test_assign_program_failure(old: ProgramCacheEntryType, new: ProgramCacheEntryType) {
1464        let mut cache = ProgramCache::<TestForkGraph>::new(0);
1465        let env = get_mock_program_runtime_environment();
1466        let program_id = Pubkey::new_unique();
1467        assert!(!cache.assign_program(
1468            &env,
1469            program_id,
1470            10,
1471            Arc::new(ProgramCacheEntry {
1472                program: old,
1473                account_owner: ProgramCacheEntryOwner::LoaderV2,
1474                deployment_slot: 10,
1475                stats: Arc::default(),
1476                latest_access_slot: AtomicU64::default(),
1477            }),
1478        ));
1479        cache.assign_program(
1480            &env,
1481            program_id,
1482            10,
1483            Arc::new(ProgramCacheEntry {
1484                program: new,
1485                account_owner: ProgramCacheEntryOwner::LoaderV2,
1486                deployment_slot: 10,
1487                stats: Arc::default(),
1488                latest_access_slot: AtomicU64::default(),
1489            }),
1490        );
1491    }
1492
1493    #[test_matrix(
1494        ProgramCacheEntryType::Unloaded(get_mock_program_runtime_environment()),
1495        (
1496            new_loaded_entry(get_mock_program_runtime_environment()),
1497            ProgramCacheEntryType::FailedVerification(get_mock_program_runtime_environment()),
1498        )
1499    )]
1500    #[test_case(
1501        ProgramCacheEntryType::Closed,
1502        ProgramCacheEntryType::Unloaded(get_mock_program_runtime_environment())
1503    )]
1504    #[test_case(
1505        ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1506        ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock())
1507    )]
1508    fn test_assign_program_success(old: ProgramCacheEntryType, new: ProgramCacheEntryType) {
1509        let mut cache = ProgramCache::<TestForkGraph>::new(0);
1510        let env = get_mock_program_runtime_environment();
1511        let program_id = Pubkey::new_unique();
1512        assert!(!cache.assign_program(
1513            &env,
1514            program_id,
1515            10,
1516            Arc::new(ProgramCacheEntry {
1517                program: old,
1518                account_owner: ProgramCacheEntryOwner::LoaderV2,
1519                deployment_slot: 10,
1520                stats: Arc::default(),
1521                latest_access_slot: AtomicU64::default(),
1522            }),
1523        ));
1524        assert!(!cache.assign_program(
1525            &env,
1526            program_id,
1527            10,
1528            Arc::new(ProgramCacheEntry {
1529                program: new,
1530                account_owner: ProgramCacheEntryOwner::LoaderV2,
1531                deployment_slot: 10,
1532                stats: Arc::default(),
1533                latest_access_slot: AtomicU64::default(),
1534            }),
1535        ));
1536    }
1537
1538    #[test]
1539    fn test_assign_program_removes_entries_in_same_slot() {
1540        let mut cache = ProgramCache::<TestForkGraph>::new(0);
1541        let env = get_mock_program_runtime_environment();
1542        let program_id = Pubkey::new_unique();
1543        let closed_other_slot = Arc::new(ProgramCacheEntry {
1544            program: ProgramCacheEntryType::Closed,
1545            account_owner: ProgramCacheEntryOwner::LoaderV2,
1546            deployment_slot: 9,
1547            stats: Arc::default(),
1548            latest_access_slot: AtomicU64::default(),
1549        });
1550        let closed_current_slot = Arc::new(ProgramCacheEntry {
1551            program: ProgramCacheEntryType::Closed,
1552            account_owner: ProgramCacheEntryOwner::LoaderV2,
1553            deployment_slot: 10,
1554            stats: Arc::default(),
1555            latest_access_slot: AtomicU64::default(),
1556        });
1557        let loaded_entry_current_env = Arc::new(ProgramCacheEntry {
1558            program: ProgramCacheEntryType::Unloaded(get_mock_program_runtime_environment()),
1559            account_owner: ProgramCacheEntryOwner::LoaderV2,
1560            deployment_slot: 10,
1561            stats: Arc::default(),
1562            latest_access_slot: AtomicU64::default(),
1563        });
1564        let loaded_entry_upcoming_env = Arc::new(ProgramCacheEntry {
1565            program: ProgramCacheEntryType::Unloaded(ProgramRuntimeEnvironment::from(
1566                BuiltinProgram::new_mock(),
1567            )),
1568            account_owner: ProgramCacheEntryOwner::LoaderV2,
1569            deployment_slot: 10,
1570            stats: Arc::default(),
1571            latest_access_slot: AtomicU64::default(),
1572        });
1573        assert!(!cache.assign_program(&env, program_id, 9, closed_other_slot.clone()));
1574        assert!(!cache.assign_program(&env, program_id, 10, closed_current_slot));
1575        assert!(!cache.assign_program(&env, program_id, 10, loaded_entry_upcoming_env.clone()));
1576        assert!(!cache.assign_program(&env, program_id, 10, loaded_entry_current_env.clone()));
1577        // Only the conflicting entry in the same slot which does not have a different environment is removed
1578        assert_eq!(
1579            cache.get_slot_versions_for_tests(&program_id),
1580            &[
1581                closed_other_slot,
1582                loaded_entry_current_env,
1583                loaded_entry_upcoming_env
1584            ]
1585        );
1586    }
1587
1588    #[test]
1589    fn test_tombstone() {
1590        let env = get_mock_program_runtime_environment();
1591        let tombstone = ProgramCacheEntry::new_failed_verification_tombstone(
1592            0,
1593            ProgramCacheEntryOwner::LoaderV2,
1594            env.clone(),
1595        );
1596        assert_matches!(
1597            tombstone.program,
1598            ProgramCacheEntryType::FailedVerification(_)
1599        );
1600        assert!(tombstone.is_tombstone());
1601        assert_eq!(tombstone.deployment_slot, 0);
1602        assert_eq!(tombstone.effective_slot(), 0);
1603
1604        let tombstone =
1605            ProgramCacheEntry::new_closed_tombstone(100, ProgramCacheEntryOwner::LoaderV2);
1606        assert_matches!(tombstone.program, ProgramCacheEntryType::Closed);
1607        assert!(tombstone.is_tombstone());
1608        assert_eq!(tombstone.deployment_slot, 100);
1609        assert_eq!(tombstone.effective_slot(), 100);
1610
1611        let mut cache = ProgramCache::<TestForkGraph>::new(0);
1612        let program1 = Pubkey::new_unique();
1613        let tombstone = set_failed_verification_tombstone(&mut cache, program1, 10, env.clone());
1614        let slot_versions = cache.get_slot_versions_for_tests(&program1);
1615        assert_eq!(slot_versions.len(), 1);
1616        assert!(slot_versions.first().unwrap().is_tombstone());
1617        assert_eq!(tombstone.deployment_slot, 10);
1618        assert_eq!(tombstone.effective_slot(), 10);
1619
1620        // Add a program at slot 50, and a tombstone for the program at slot 60
1621        let program2 = Pubkey::new_unique();
1622        cache.assign_program(&env, program2, 50, new_test_builtin_entry(50));
1623        let slot_versions = cache.get_slot_versions_for_tests(&program2);
1624        assert_eq!(slot_versions.len(), 1);
1625        assert!(!slot_versions.first().unwrap().is_tombstone());
1626
1627        let tombstone = set_failed_verification_tombstone(&mut cache, program2, 60, env);
1628        let slot_versions = cache.get_slot_versions_for_tests(&program2);
1629        assert_eq!(slot_versions.len(), 2);
1630        assert!(!slot_versions.first().unwrap().is_tombstone());
1631        assert!(slot_versions.get(1).unwrap().is_tombstone());
1632        assert!(tombstone.is_tombstone());
1633        assert_eq!(tombstone.deployment_slot, 60);
1634        assert_eq!(tombstone.effective_slot(), 60);
1635    }
1636
1637    struct TestForkGraph {
1638        relation: BlockRelation,
1639    }
1640    impl ForkGraph for TestForkGraph {
1641        fn relationship(&self, _a: Slot, _b: Slot) -> BlockRelation {
1642            self.relation
1643        }
1644    }
1645
1646    #[test]
1647    fn test_prune_empty() {
1648        let mut cache = ProgramCache::<TestForkGraph>::new(0);
1649        let fork_graph = Arc::new(RwLock::new(TestForkGraph {
1650            relation: BlockRelation::Unrelated,
1651        }));
1652
1653        cache.set_fork_graph(Arc::downgrade(&fork_graph));
1654
1655        cache.prune(0, None, &fork_graph.read().unwrap());
1656        assert!(cache.get_flattened_entries_for_tests().is_empty());
1657
1658        cache.prune(10, None, &fork_graph.read().unwrap());
1659        assert!(cache.get_flattened_entries_for_tests().is_empty());
1660
1661        let mut cache = ProgramCache::<TestForkGraph>::new(0);
1662        let fork_graph = Arc::new(RwLock::new(TestForkGraph {
1663            relation: BlockRelation::Ancestor,
1664        }));
1665
1666        cache.set_fork_graph(Arc::downgrade(&fork_graph));
1667
1668        cache.prune(0, None, &fork_graph.read().unwrap());
1669        assert!(cache.get_flattened_entries_for_tests().is_empty());
1670
1671        cache.prune(10, None, &fork_graph.read().unwrap());
1672        assert!(cache.get_flattened_entries_for_tests().is_empty());
1673
1674        let mut cache = ProgramCache::<TestForkGraph>::new(0);
1675        let fork_graph = Arc::new(RwLock::new(TestForkGraph {
1676            relation: BlockRelation::Descendant,
1677        }));
1678
1679        cache.set_fork_graph(Arc::downgrade(&fork_graph));
1680
1681        cache.prune(0, None, &fork_graph.read().unwrap());
1682        assert!(cache.get_flattened_entries_for_tests().is_empty());
1683
1684        cache.prune(10, None, &fork_graph.read().unwrap());
1685        assert!(cache.get_flattened_entries_for_tests().is_empty());
1686
1687        let mut cache = ProgramCache::<TestForkGraph>::new(0);
1688        let fork_graph = Arc::new(RwLock::new(TestForkGraph {
1689            relation: BlockRelation::Unknown,
1690        }));
1691        cache.set_fork_graph(Arc::downgrade(&fork_graph));
1692
1693        cache.prune(0, None, &fork_graph.read().unwrap());
1694        assert!(cache.get_flattened_entries_for_tests().is_empty());
1695
1696        cache.prune(10, None, &fork_graph.read().unwrap());
1697        assert!(cache.get_flattened_entries_for_tests().is_empty());
1698    }
1699
1700    #[test]
1701    fn test_prune_with_two_environments_before_epoch_boundary() {
1702        let mut cache = ProgramCache::<TestForkGraph>::new(0);
1703        let env = get_mock_program_runtime_environment();
1704        let new_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
1705        let fork_graph = Arc::new(RwLock::new(TestForkGraph {
1706            relation: BlockRelation::Ancestor,
1707        }));
1708        cache.set_fork_graph(Arc::downgrade(&fork_graph));
1709
1710        let program1 = Pubkey::new_unique();
1711        cache.assign_program(&env, program1, 10, new_test_entry(10));
1712        let updated_program = Arc::new(ProgramCacheEntry {
1713            program: new_loaded_entry(new_env.clone()),
1714            deployment_slot: 20,
1715            ..Default::default()
1716        });
1717        cache.assign_program(&env, program1, 20, updated_program.clone());
1718
1719        // Test that there are 2 entries for the program
1720        assert_eq!(cache.get_slot_versions_for_tests(&program1).len(), 2);
1721
1722        cache.prune(21, None, &fork_graph.read().unwrap());
1723
1724        // Test that prune didn't remove the entry, since environments are different.
1725        assert_eq!(cache.get_slot_versions_for_tests(&program1).len(), 2);
1726    }
1727
1728    #[test]
1729    fn test_prune_with_two_environments_after_epoch_boundary() {
1730        let mut cache = ProgramCache::<TestForkGraph>::new(0);
1731        let env = get_mock_program_runtime_environment();
1732        let new_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
1733        let fork_graph = Arc::new(RwLock::new(TestForkGraph {
1734            relation: BlockRelation::Ancestor,
1735        }));
1736        cache.set_fork_graph(Arc::downgrade(&fork_graph));
1737        let program1 = Pubkey::new_unique();
1738
1739        let old_program_old_env = Arc::new(ProgramCacheEntry {
1740            program: new_loaded_entry(env.clone()),
1741            deployment_slot: 10,
1742            ..Default::default()
1743        });
1744        let old_program_new_env = Arc::new(ProgramCacheEntry {
1745            program: new_loaded_entry(new_env.clone()),
1746            deployment_slot: 10,
1747            ..Default::default()
1748        });
1749        let new_program_old_env = Arc::new(ProgramCacheEntry {
1750            program: new_loaded_entry(env.clone()),
1751            deployment_slot: 20,
1752            ..Default::default()
1753        });
1754        cache.assign_program(&env, program1, 10, old_program_old_env.clone());
1755        cache.assign_program(&env, program1, 10, old_program_new_env.clone());
1756        cache.assign_program(&env, program1, 20, new_program_old_env.clone());
1757        let slot_versions = cache.get_slot_versions_for_tests(&program1);
1758        assert_eq!(
1759            &slot_versions,
1760            &[
1761                old_program_new_env.clone(),
1762                old_program_old_env.clone(),
1763                new_program_old_env.clone(),
1764            ]
1765        );
1766
1767        cache.prune(21, Some(new_env.clone()), &fork_graph.read().unwrap());
1768        let slot_versions = cache.get_slot_versions_for_tests(&program1);
1769        assert_eq!(&slot_versions, &[old_program_new_env]);
1770        assert!(matches!(
1771            &slot_versions.first().unwrap().program,
1772            ProgramCacheEntryType::Loaded(_)
1773        ));
1774    }
1775
1776    #[derive(Default)]
1777    struct TestForkGraphSpecific {
1778        forks: Vec<Vec<Slot>>,
1779    }
1780
1781    impl TestForkGraphSpecific {
1782        fn insert_fork(&mut self, fork: &[Slot]) {
1783            let mut fork = fork.to_vec();
1784            fork.sort();
1785            self.forks.push(fork)
1786        }
1787    }
1788
1789    impl ForkGraph for TestForkGraphSpecific {
1790        fn relationship(&self, a: Slot, b: Slot) -> BlockRelation {
1791            match self.forks.iter().try_for_each(|fork| {
1792                let relation = fork
1793                    .iter()
1794                    .position(|x| *x == a)
1795                    .and_then(|a_pos| {
1796                        fork.iter().position(|x| *x == b).and_then(|b_pos| {
1797                            (a_pos == b_pos)
1798                                .then_some(BlockRelation::Equal)
1799                                .or_else(|| (a_pos < b_pos).then_some(BlockRelation::Ancestor))
1800                                .or(Some(BlockRelation::Descendant))
1801                        })
1802                    })
1803                    .unwrap_or(BlockRelation::Unrelated);
1804
1805                if relation != BlockRelation::Unrelated {
1806                    return ControlFlow::Break(relation);
1807                }
1808
1809                ControlFlow::Continue(())
1810            }) {
1811                ControlFlow::Break(relation) => relation,
1812                _ => BlockRelation::Unrelated,
1813            }
1814        }
1815    }
1816
1817    fn get_entries_to_load<'a>(
1818        cache: &ProgramCache<TestForkGraphSpecific>,
1819        loading_slot: Slot,
1820        keys: &'a [Pubkey],
1821    ) -> Vec<ProgramToLoad<'a>> {
1822        let fork_graph = cache.fork_graph.as_ref().unwrap().upgrade().unwrap();
1823        let locked_fork_graph = fork_graph.read().unwrap();
1824        let entries = cache.get_flattened_entries_for_tests();
1825        keys.iter()
1826            .filter_map(|key| {
1827                entries
1828                    .iter()
1829                    .rev()
1830                    .find(|(program_id, entry)| {
1831                        program_id == key
1832                            && matches!(
1833                                locked_fork_graph.relationship(entry.deployment_slot, loading_slot),
1834                                BlockRelation::Equal | BlockRelation::Ancestor,
1835                            )
1836                    })
1837                    .map(|(_program_id, entry)| ProgramToLoad {
1838                        program_id: key,
1839                        loader: entry.account_owner,
1840                        match_criteria: ProgramCacheMatchCriteria::NoCriteria,
1841                        last_modification_slot: entry.deployment_slot,
1842                    })
1843            })
1844            .collect()
1845    }
1846
1847    fn match_slot(
1848        extracted: &ProgramCacheForTxBatch,
1849        program: &Pubkey,
1850        deployment_slot: Slot,
1851        working_slot: Slot,
1852    ) -> bool {
1853        assert_eq!(extracted.slot, working_slot);
1854        extracted
1855            .entries
1856            .get(program)
1857            .map(|entry| entry.deployment_slot == deployment_slot)
1858            .unwrap_or(false)
1859    }
1860
1861    fn match_missing(
1862        missing: &[ProgramToLoad],
1863        program_id: &Pubkey,
1864        expected_result: bool,
1865    ) -> bool {
1866        missing.iter().any(|entry| entry.program_id == program_id) == expected_result
1867    }
1868
1869    #[test]
1870    fn test_fork_extract_and_prune() {
1871        let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
1872        let env = get_mock_program_runtime_environment();
1873
1874        // Fork graph created for the test
1875        //                   0
1876        //                 /   \
1877        //                10    5
1878        //                |     |
1879        //                20    11
1880        //                |     | \
1881        //                22   15  25
1882        //                      |   |
1883        //                     16  27
1884        //                      |
1885        //                     19
1886        //                      |
1887        //                     23
1888
1889        let mut fork_graph = TestForkGraphSpecific::default();
1890        fork_graph.insert_fork(&[0, 10, 20, 22]);
1891        fork_graph.insert_fork(&[0, 5, 11, 15, 16, 18, 19, 21, 23]);
1892        fork_graph.insert_fork(&[0, 5, 11, 25, 27]);
1893
1894        let fork_graph = Arc::new(RwLock::new(fork_graph));
1895        cache.set_fork_graph(Arc::downgrade(&fork_graph));
1896
1897        let program1 = Pubkey::new_unique();
1898        cache.assign_program(&env, program1, 0, new_test_entry(0));
1899        cache.assign_program(&env, program1, 10, new_test_entry(10));
1900        cache.assign_program(&env, program1, 20, new_test_entry(20));
1901
1902        let program2 = Pubkey::new_unique();
1903        cache.assign_program(&env, program2, 5, new_test_entry(5));
1904        cache.assign_program(&env, program2, 11, new_test_entry(11));
1905
1906        let program3 = Pubkey::new_unique();
1907        cache.assign_program(&env, program3, 25, new_test_entry(25));
1908
1909        let program4 = Pubkey::new_unique();
1910        cache.assign_program(&env, program4, 0, new_test_entry(0));
1911        cache.assign_program(&env, program4, 5, new_test_entry(5));
1912        // The following is a special case, where effective slot is 3 slots in the future
1913        cache.assign_program(&env, program4, 15, new_test_entry(15));
1914
1915        // Current fork graph
1916        //                   0
1917        //                 /   \
1918        //                10    5
1919        //                |     |
1920        //                20    11
1921        //                |     | \
1922        //                22   15  25
1923        //                      |   |
1924        //                     16  27
1925        //                      |
1926        //                     19
1927        //                      |
1928        //                     23
1929
1930        // Testing fork 0 - 10 - 20 - 22 with current slot at 22
1931        let keys = &[program1, program2, program3, program4];
1932        let mut missing = get_entries_to_load(&cache, 22, keys);
1933        assert!(match_missing(&missing, &program2, false));
1934        assert!(match_missing(&missing, &program3, false));
1935        let mut extracted = ProgramCacheForTxBatch::new(22);
1936        cache.extract(&mut missing, &mut extracted, &env, true, true);
1937        assert!(match_slot(&extracted, &program1, 20, 22));
1938        assert!(match_slot(&extracted, &program4, 0, 22));
1939
1940        // Testing fork 0 - 5 - 11 - 15 - 16 with current slot at 15
1941        let mut missing = get_entries_to_load(&cache, 15, keys);
1942        assert!(match_missing(&missing, &program3, false));
1943        let mut extracted = ProgramCacheForTxBatch::new(15);
1944        cache.extract(&mut missing, &mut extracted, &env, true, true);
1945        assert!(match_slot(&extracted, &program1, 0, 15));
1946        assert!(match_slot(&extracted, &program2, 11, 15));
1947        // The effective slot of program4 deployed in slot 15 is 19. So it should not be usable in slot 16.
1948        // A delay visibility tombstone should be returned here.
1949        let tombstone = extracted
1950            .find(&program4)
1951            .expect("Failed to find the tombstone");
1952        assert_matches!(tombstone.program, ProgramCacheEntryType::DelayVisibility);
1953        assert_eq!(tombstone.deployment_slot, 15);
1954
1955        // Testing the same fork above, but current slot is now 18 (equal to effective slot of program4).
1956        let mut missing = get_entries_to_load(&cache, 18, keys);
1957        assert!(match_missing(&missing, &program3, false));
1958        let mut extracted = ProgramCacheForTxBatch::new(18);
1959        cache.extract(&mut missing, &mut extracted, &env, true, true);
1960        assert!(match_slot(&extracted, &program1, 0, 18));
1961        assert!(match_slot(&extracted, &program2, 11, 18));
1962        // The effective slot of program4 deployed in slot 15 is 18. So it should be usable in slot 18.
1963        assert!(match_slot(&extracted, &program4, 15, 18));
1964
1965        // Testing the same fork above, but current slot is now 23 (future slot than effective slot of program4).
1966        let mut missing = get_entries_to_load(&cache, 23, keys);
1967        assert!(match_missing(&missing, &program3, false));
1968        let mut extracted = ProgramCacheForTxBatch::new(23);
1969        cache.extract(&mut missing, &mut extracted, &env, true, true);
1970        assert!(match_slot(&extracted, &program1, 0, 23));
1971        assert!(match_slot(&extracted, &program2, 11, 23));
1972        // The effective slot of program4 deployed in slot 15 is 19. So it should be usable in slot 23.
1973        assert!(match_slot(&extracted, &program4, 15, 23));
1974
1975        // Testing fork 0 - 5 - 11 - 15 - 16 with current slot at 11
1976        let mut missing = get_entries_to_load(&cache, 11, keys);
1977        assert!(match_missing(&missing, &program3, false));
1978        let mut extracted = ProgramCacheForTxBatch::new(11);
1979        cache.extract(&mut missing, &mut extracted, &env, true, true);
1980        assert!(match_slot(&extracted, &program1, 0, 11));
1981        // program2 was updated at slot 11, but is not effective till slot 12. The result should contain a tombstone.
1982        let tombstone = extracted
1983            .find(&program2)
1984            .expect("Failed to find the tombstone");
1985        assert_matches!(tombstone.program, ProgramCacheEntryType::DelayVisibility);
1986        assert_eq!(tombstone.deployment_slot, 11);
1987        assert!(match_slot(&extracted, &program4, 5, 11));
1988
1989        cache.prune(5, None, &fork_graph.read().unwrap());
1990
1991        // Fork graph after pruning
1992        //                   0
1993        //                   |
1994        //                   5
1995        //                   |
1996        //                   11
1997        //                   | \
1998        //                  15  25
1999        //                   |   |
2000        //                  16  27
2001        //                   |
2002        //                  19
2003        //                   |
2004        //                  23
2005
2006        // Testing fork 11 - 15 - 16- 19 - 22 with root at 5 and current slot at 22
2007        let mut missing = get_entries_to_load(&cache, 21, keys);
2008        assert!(match_missing(&missing, &program3, false));
2009        let mut extracted = ProgramCacheForTxBatch::new(21);
2010        cache.extract(&mut missing, &mut extracted, &env, true, true);
2011        // Since the fork was pruned, we should not find the entry deployed at slot 20.
2012        assert!(match_slot(&extracted, &program1, 0, 21));
2013        assert!(match_slot(&extracted, &program2, 11, 21));
2014        assert!(match_slot(&extracted, &program4, 15, 21));
2015
2016        // Testing fork 0 - 5 - 11 - 25 - 27 with current slot at 27
2017        let mut missing = get_entries_to_load(&cache, 27, keys);
2018        let mut extracted = ProgramCacheForTxBatch::new(27);
2019        cache.extract(&mut missing, &mut extracted, &env, true, true);
2020        assert!(match_slot(&extracted, &program1, 0, 27));
2021        assert!(match_slot(&extracted, &program2, 11, 27));
2022        assert!(match_slot(&extracted, &program3, 25, 27));
2023        assert!(match_slot(&extracted, &program4, 5, 27));
2024
2025        cache.prune(15, None, &fork_graph.read().unwrap());
2026
2027        // Fork graph after pruning
2028        //                  0
2029        //                  |
2030        //                  5
2031        //                  |
2032        //                  11
2033        //                  |
2034        //                  15
2035        //                  |
2036        //                  16
2037        //                  |
2038        //                  19
2039        //                  |
2040        //                  23
2041
2042        // Testing fork 16, 19, 23, with root at 15, current slot at 23
2043        let mut missing = get_entries_to_load(&cache, 23, keys);
2044        assert!(match_missing(&missing, &program3, false));
2045        let mut extracted = ProgramCacheForTxBatch::new(23);
2046        cache.extract(&mut missing, &mut extracted, &env, true, true);
2047        assert!(match_slot(&extracted, &program1, 0, 23));
2048        assert!(match_slot(&extracted, &program2, 11, 23));
2049        assert!(match_slot(&extracted, &program4, 15, 23));
2050    }
2051
2052    #[test]
2053    fn test_extract_using_deployment_slot() {
2054        let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
2055        let env = get_mock_program_runtime_environment();
2056
2057        // Fork graph created for the test
2058        //                   0
2059        //                 /   \
2060        //                10    5
2061        //                |     |
2062        //                20    11
2063        //                |     | \
2064        //                22   15  25
2065        //                      |   |
2066        //                     16  27
2067        //                      |
2068        //                     19
2069        //                      |
2070        //                     23
2071
2072        let mut fork_graph = TestForkGraphSpecific::default();
2073        fork_graph.insert_fork(&[0, 10, 20, 22]);
2074        fork_graph.insert_fork(&[0, 5, 11, 12, 15, 16, 18, 19, 21, 23]);
2075        fork_graph.insert_fork(&[0, 5, 11, 25, 27]);
2076
2077        let fork_graph = Arc::new(RwLock::new(fork_graph));
2078        cache.set_fork_graph(Arc::downgrade(&fork_graph));
2079
2080        let program1 = Pubkey::new_unique();
2081        cache.assign_program(&env, program1, 0, new_test_entry(0));
2082        cache.assign_program(&env, program1, 20, new_test_entry(20));
2083
2084        let program2 = Pubkey::new_unique();
2085        cache.assign_program(&env, program2, 5, new_test_entry(5));
2086        cache.assign_program(&env, program2, 11, new_test_entry(11));
2087
2088        let program3 = Pubkey::new_unique();
2089        cache.assign_program(&env, program3, 25, new_test_entry(25));
2090
2091        // Testing fork 0 - 5 - 11 - 15 - 16 - 19 - 21 - 23 with current slot at 19
2092        let keys = &[program1, program2, program3];
2093        let mut missing = get_entries_to_load(&cache, 12, keys);
2094        assert!(match_missing(&missing, &program3, false));
2095        let mut extracted = ProgramCacheForTxBatch::new(12);
2096        cache.extract(&mut missing, &mut extracted, &env, true, true);
2097        assert!(match_slot(&extracted, &program1, 0, 12));
2098        assert!(match_slot(&extracted, &program2, 11, 12));
2099
2100        // Test the same fork, but request the program modified at a later slot than what's in the cache.
2101        let mut missing = get_entries_to_load(&cache, 12, keys);
2102        missing.get_mut(0).unwrap().match_criteria =
2103            ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(5);
2104        missing.get_mut(1).unwrap().match_criteria =
2105            ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(5);
2106        assert!(match_missing(&missing, &program3, false));
2107        let mut extracted = ProgramCacheForTxBatch::new(12);
2108        cache.extract(&mut missing, &mut extracted, &env, true, true);
2109        assert!(match_missing(&missing, &program1, true));
2110        assert!(match_slot(&extracted, &program2, 11, 12));
2111    }
2112
2113    #[test]
2114    fn test_extract_unloaded() {
2115        let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
2116        let env = get_mock_program_runtime_environment();
2117
2118        // Fork graph created for the test
2119        //                   0
2120        //                 /   \
2121        //                10    5
2122        //                |     |
2123        //                20    11
2124        //                |     | \
2125        //                22   15  25
2126        //                      |   |
2127        //                     16  27
2128        //                      |
2129        //                     19
2130        //                      |
2131        //                     23
2132
2133        let mut fork_graph = TestForkGraphSpecific::default();
2134        fork_graph.insert_fork(&[0, 10, 20, 22]);
2135        fork_graph.insert_fork(&[0, 5, 11, 15, 16, 19, 21, 23]);
2136        fork_graph.insert_fork(&[0, 5, 11, 25, 27]);
2137
2138        let fork_graph = Arc::new(RwLock::new(fork_graph));
2139        cache.set_fork_graph(Arc::downgrade(&fork_graph));
2140
2141        let program1 = Pubkey::new_unique();
2142        cache.assign_program(&env, program1, 0, new_test_entry(0));
2143        cache.assign_program(&env, program1, 20, new_test_entry(20));
2144
2145        let program2 = Pubkey::new_unique();
2146        cache.assign_program(&env, program2, 5, new_test_entry(5));
2147        cache.assign_program(&env, program2, 11, new_test_entry(11));
2148
2149        let program3 = Pubkey::new_unique();
2150        // Insert an unloaded program with correct/cache's environment at slot 25
2151        let _ = insert_unloaded_entry(&mut cache, program3, 25);
2152
2153        // Insert another unloaded program with a different environment at slot 20
2154        // Since this entry's environment won't match cache's environment, looking up this
2155        // entry should return missing instead of unloaded entry.
2156        cache.assign_program(
2157            &env,
2158            program3,
2159            20,
2160            Arc::new(
2161                new_test_entry(20)
2162                    .to_unloaded()
2163                    .expect("Failed to create unloaded program"),
2164            ),
2165        );
2166
2167        // Testing fork 0 - 5 - 11 - 15 - 16 - 19 - 21 - 23 with current slot at 19
2168        let keys = &[program1, program2, program3];
2169        let mut missing = get_entries_to_load(&cache, 19, keys);
2170        assert!(match_missing(&missing, &program3, false));
2171        let mut extracted = ProgramCacheForTxBatch::new(19);
2172        cache.extract(&mut missing, &mut extracted, &env, true, true);
2173        assert!(match_slot(&extracted, &program1, 0, 19));
2174        assert!(match_slot(&extracted, &program2, 11, 19));
2175
2176        // Testing fork 0 - 5 - 11 - 25 - 27 with current slot at 27
2177        let mut missing = get_entries_to_load(&cache, 27, keys);
2178        let mut extracted = ProgramCacheForTxBatch::new(27);
2179        cache.extract(&mut missing, &mut extracted, &env, true, true);
2180        assert!(match_slot(&extracted, &program1, 0, 27));
2181        assert!(match_slot(&extracted, &program2, 11, 27));
2182        assert!(match_missing(&missing, &program3, true));
2183
2184        // Testing fork 0 - 10 - 20 - 22 with current slot at 22
2185        let mut missing = get_entries_to_load(&cache, 22, keys);
2186        assert!(match_missing(&missing, &program2, false));
2187        let mut extracted = ProgramCacheForTxBatch::new(22);
2188        cache.extract(&mut missing, &mut extracted, &env, true, true);
2189        assert!(match_slot(&extracted, &program1, 20, 22));
2190        assert!(match_missing(&missing, &program3, true));
2191    }
2192
2193    #[test]
2194    fn test_extract_different_environment() {
2195        let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
2196        let env = get_mock_program_runtime_environment();
2197        let other_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
2198
2199        // Fork graph created for the test
2200        //                0
2201        //                |
2202        //                10
2203        //                |
2204        //                20
2205        //                |
2206        //                22
2207
2208        let mut fork_graph = TestForkGraphSpecific::default();
2209        fork_graph.insert_fork(&[0, 10, 20, 22]);
2210
2211        let fork_graph = Arc::new(RwLock::new(fork_graph));
2212        cache.set_fork_graph(Arc::downgrade(&fork_graph));
2213
2214        let program1 = Pubkey::new_unique();
2215        cache.assign_program(
2216            &env,
2217            program1,
2218            10,
2219            Arc::new(ProgramCacheEntry::new_closed_tombstone(
2220                10,
2221                ProgramCacheEntryOwner::LoaderV3,
2222            )),
2223        );
2224        cache.assign_program(&env, program1, 20, new_test_entry(20));
2225
2226        // Testing fork 0 - 10 - 20 - 22 with current slot at 22
2227        let keys = &[program1];
2228        let mut missing = get_entries_to_load(&cache, 22, keys);
2229        let mut extracted = ProgramCacheForTxBatch::new(22);
2230        cache.extract(&mut missing, &mut extracted, &env, true, true);
2231        assert!(match_slot(&extracted, &program1, 20, 22));
2232
2233        // Looking for a different environment
2234        let mut missing = get_entries_to_load(&cache, 22, keys);
2235        let mut extracted = ProgramCacheForTxBatch::new(22);
2236        cache.extract(&mut missing, &mut extracted, &other_env, true, true);
2237        assert!(match_missing(&missing, &program1, true));
2238    }
2239
2240    #[test]
2241    fn test_extract_nonexistent() {
2242        let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
2243        let env = get_mock_program_runtime_environment();
2244        let fork_graph = TestForkGraphSpecific::default();
2245        let fork_graph = Arc::new(RwLock::new(fork_graph));
2246        cache.set_fork_graph(Arc::downgrade(&fork_graph));
2247
2248        let program1 = Pubkey::new_unique();
2249        let mut missing = vec![ProgramToLoad {
2250            program_id: &program1,
2251            loader: ProgramCacheEntryOwner::LoaderV3,
2252            match_criteria: ProgramCacheMatchCriteria::NoCriteria,
2253            last_modification_slot: 0,
2254        }];
2255        let mut extracted = ProgramCacheForTxBatch::new(0);
2256        cache.extract(&mut missing, &mut extracted, &env, true, true);
2257        assert!(match_missing(&missing, &program1, true));
2258    }
2259
2260    #[test]
2261    fn test_unloaded() {
2262        let mut cache = ProgramCache::<TestForkGraph>::new(0);
2263        let env = get_mock_program_runtime_environment();
2264        for program_cache_entry_type in [
2265            ProgramCacheEntryType::Closed,
2266            ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
2267        ] {
2268            let entry = Arc::new(ProgramCacheEntry {
2269                program: program_cache_entry_type,
2270                account_owner: ProgramCacheEntryOwner::LoaderV2,
2271                deployment_slot: 0,
2272                stats: Arc::default(),
2273                latest_access_slot: AtomicU64::default(),
2274            });
2275            assert!(entry.to_unloaded().is_none());
2276
2277            // Check that unload_program_entry() does nothing for this entry
2278            let program_id = Pubkey::new_unique();
2279            cache.assign_program(&env, program_id, entry.deployment_slot, entry.clone());
2280            cache.unload_program_entry(program_id, entry.deployment_slot, &entry);
2281            assert_eq!(cache.get_slot_versions_for_tests(&program_id).len(), 1);
2282            assert!(cache.stats.evictions.is_empty());
2283        }
2284
2285        let stats = ProgramStatistics {
2286            uses: 3.into(),
2287            ..Default::default()
2288        };
2289        let entry = new_test_entry_with_usage(1, stats);
2290        let unloaded_entry = entry.to_unloaded().unwrap();
2291        assert_eq!(unloaded_entry.deployment_slot, 1);
2292        assert_eq!(unloaded_entry.effective_slot(), 2);
2293        assert_eq!(unloaded_entry.latest_access_slot.load(Ordering::Relaxed), 1);
2294        assert_eq!(unloaded_entry.stats.uses.load(Ordering::Relaxed), 3);
2295
2296        // Check that unload_program_entry() does its work
2297        let program_id = Pubkey::new_unique();
2298        cache.assign_program(&env, program_id, entry.deployment_slot, entry.clone());
2299        cache.unload_program_entry(program_id, entry.deployment_slot, &entry);
2300        assert!(cache.stats.evictions.contains_key(&program_id));
2301    }
2302
2303    #[test]
2304    fn test_fork_prune_find_first_ancestor() {
2305        let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
2306        let env = get_mock_program_runtime_environment();
2307
2308        // Fork graph created for the test
2309        //                   0
2310        //                 /   \
2311        //                10    5
2312        //                |
2313        //                20
2314
2315        // Deploy program on slot 0, and slot 5.
2316        // Prune the fork that has slot 5. The cache should still have the program
2317        // deployed at slot 0.
2318        let mut fork_graph = TestForkGraphSpecific::default();
2319        fork_graph.insert_fork(&[0, 10, 20]);
2320        fork_graph.insert_fork(&[0, 5]);
2321        let fork_graph = Arc::new(RwLock::new(fork_graph));
2322        cache.set_fork_graph(Arc::downgrade(&fork_graph));
2323
2324        let program1 = Pubkey::new_unique();
2325        cache.assign_program(&env, program1, 0, new_test_entry(0));
2326        cache.assign_program(&env, program1, 5, new_test_entry(5));
2327
2328        cache.prune(10, None, &fork_graph.read().unwrap());
2329
2330        let keys = &[program1];
2331        let mut missing = get_entries_to_load(&cache, 20, keys);
2332        let mut extracted = ProgramCacheForTxBatch::new(20);
2333        cache.extract(&mut missing, &mut extracted, &env, true, true);
2334
2335        // The cache should have the program deployed at slot 0
2336        assert_eq!(
2337            extracted
2338                .find(&program1)
2339                .expect("Did not find the program")
2340                .deployment_slot,
2341            0
2342        );
2343    }
2344
2345    #[test]
2346    fn test_prune_by_deployment_slot() {
2347        let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
2348        let env = get_mock_program_runtime_environment();
2349
2350        // Fork graph created for the test
2351        //                   0
2352        //                 /   \
2353        //                10    5
2354        //                |
2355        //                20
2356
2357        // Deploy program on slot 0, and slot 5.
2358        // Prune the fork that has slot 5. The cache should still have the program
2359        // deployed at slot 0.
2360        let mut fork_graph = TestForkGraphSpecific::default();
2361        fork_graph.insert_fork(&[0, 10, 20]);
2362        fork_graph.insert_fork(&[0, 5, 6]);
2363        let fork_graph = Arc::new(RwLock::new(fork_graph));
2364        cache.set_fork_graph(Arc::downgrade(&fork_graph));
2365
2366        let program1 = Pubkey::new_unique();
2367        cache.assign_program(&env, program1, 0, new_test_entry(0));
2368        cache.assign_program(&env, program1, 5, new_test_entry(5));
2369
2370        let program2 = Pubkey::new_unique();
2371        cache.assign_program(&env, program2, 10, new_test_entry(10));
2372
2373        let keys = &[program1, program2];
2374        let mut missing = get_entries_to_load(&cache, 20, keys);
2375        let mut extracted = ProgramCacheForTxBatch::new(20);
2376        cache.extract(&mut missing, &mut extracted, &env, true, true);
2377        assert!(match_slot(&extracted, &program1, 0, 20));
2378        assert!(match_slot(&extracted, &program2, 10, 20));
2379
2380        let mut missing = get_entries_to_load(&cache, 6, keys);
2381        assert!(match_missing(&missing, &program2, false));
2382        let mut extracted = ProgramCacheForTxBatch::new(6);
2383        cache.extract(&mut missing, &mut extracted, &env, true, true);
2384        assert!(match_slot(&extracted, &program1, 5, 6));
2385
2386        // Pruning slot 5 will remove program1 entry deployed at slot 5.
2387        // On fork chaining from slot 5, the entry deployed at slot 0 will become visible.
2388        cache.prune_by_deployment_slot(5);
2389
2390        let mut missing = get_entries_to_load(&cache, 20, keys);
2391        let mut extracted = ProgramCacheForTxBatch::new(20);
2392        cache.extract(&mut missing, &mut extracted, &env, true, true);
2393        assert!(match_slot(&extracted, &program1, 0, 20));
2394        assert!(match_slot(&extracted, &program2, 10, 20));
2395
2396        let mut missing = get_entries_to_load(&cache, 6, keys);
2397        assert!(match_missing(&missing, &program2, false));
2398        let mut extracted = ProgramCacheForTxBatch::new(6);
2399        cache.extract(&mut missing, &mut extracted, &env, true, true);
2400        assert!(match_slot(&extracted, &program1, 0, 6));
2401
2402        // Pruning slot 10 will remove program2 entry deployed at slot 10.
2403        // As there is no other entry for program2, extract() will return it as missing.
2404        cache.prune_by_deployment_slot(10);
2405
2406        let mut missing = get_entries_to_load(&cache, 20, keys);
2407        assert!(match_missing(&missing, &program2, false));
2408        let mut extracted = ProgramCacheForTxBatch::new(20);
2409        cache.extract(&mut missing, &mut extracted, &env, true, true);
2410        assert!(match_slot(&extracted, &program1, 0, 20));
2411    }
2412
2413    #[test]
2414    fn test_usable_entries_for_slot() {
2415        ProgramCache::<TestForkGraph>::new(0);
2416        let tombstone = Arc::new(ProgramCacheEntry::new_closed_tombstone(
2417            0,
2418            ProgramCacheEntryOwner::LoaderV2,
2419        ));
2420
2421        assert!(ProgramCache::<TestForkGraph>::matches_criteria(
2422            &tombstone,
2423            &ProgramCacheMatchCriteria::NoCriteria
2424        ));
2425
2426        assert!(ProgramCache::<TestForkGraph>::matches_criteria(
2427            &tombstone,
2428            &ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(0)
2429        ));
2430
2431        assert!(!ProgramCache::<TestForkGraph>::matches_criteria(
2432            &tombstone,
2433            &ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(1)
2434        ));
2435
2436        let program = new_test_entry(0);
2437
2438        assert!(ProgramCache::<TestForkGraph>::matches_criteria(
2439            &program,
2440            &ProgramCacheMatchCriteria::NoCriteria
2441        ));
2442
2443        assert!(ProgramCache::<TestForkGraph>::matches_criteria(
2444            &program,
2445            &ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(0)
2446        ));
2447
2448        assert!(!ProgramCache::<TestForkGraph>::matches_criteria(
2449            &program,
2450            &ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(1)
2451        ));
2452
2453        let program = Arc::new(new_test_entry_with_usage(0, ProgramStatistics::default()));
2454
2455        assert!(ProgramCache::<TestForkGraph>::matches_criteria(
2456            &program,
2457            &ProgramCacheMatchCriteria::NoCriteria
2458        ));
2459
2460        assert!(ProgramCache::<TestForkGraph>::matches_criteria(
2461            &program,
2462            &ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(0)
2463        ));
2464
2465        assert!(!ProgramCache::<TestForkGraph>::matches_criteria(
2466            &program,
2467            &ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(1)
2468        ));
2469    }
2470}