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