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