Skip to main content

solana_program_runtime/
loaded_programs.rs

1use {
2    crate::{
3        invoke_context::InvokeContext,
4        loading_task::LoadingTaskWaiter,
5        program_cache_entry::{
6            ProgramCacheEntry, ProgramCacheEntryOwner, ProgramCacheEntryType, retention_score,
7        },
8        program_metrics::{EMA_SCALE, ProgramCacheStats},
9    },
10    log::error,
11    solana_clock::{Epoch, Slot},
12    solana_pubkey::Pubkey,
13    solana_sbpf::program::BuiltinProgram,
14    solana_svm_type_overrides::{
15        rand::{Rng, rng},
16        sync::{Arc, Mutex, RwLock, atomic::Ordering},
17        thread,
18    },
19    std::{
20        collections::{HashMap, hash_map::Entry},
21        sync::Weak,
22    },
23};
24
25#[repr(transparent)]
26#[derive(Clone, Debug)]
27pub struct ProgramRuntimeEnvironment(Arc<BuiltinProgram<InvokeContext<'static, 'static>>>);
28impl std::hash::Hash for ProgramRuntimeEnvironment {
29    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
30        Arc::<BuiltinProgram<InvokeContext<'static, 'static>>>::as_ptr(&self.0).hash(state);
31    }
32}
33impl PartialEq for ProgramRuntimeEnvironment {
34    fn eq(&self, other: &Self) -> bool {
35        Arc::ptr_eq(&self.0, &other.0)
36    }
37}
38impl Eq for ProgramRuntimeEnvironment {}
39impl std::ops::Deref for ProgramRuntimeEnvironment {
40    type Target = Arc<BuiltinProgram<InvokeContext<'static, 'static>>>;
41
42    fn deref(&self) -> &Self::Target {
43        &self.0
44    }
45}
46impl ProgramRuntimeEnvironment {
47    pub fn from(inner: BuiltinProgram<InvokeContext<'static, 'static>>) -> Self {
48        Self(Arc::new(inner))
49    }
50
51    pub const fn from_ref<'a>(
52        inner: &'a Arc<BuiltinProgram<InvokeContext<'static, 'static>>>,
53    ) -> &'a Self {
54        // Safety: This wrapper type is transparent and shares the same representation as the underlying type
55        unsafe { std::mem::transmute(inner) }
56    }
57}
58
59/// Paired execution and deployment environments.
60///
61/// Registered functions within each program runtime environment (syscalls)
62/// depend on per-epoch feature gate statuses. In most cases, the list of
63/// registered functions in the two environments will be the same. However,
64/// it's possible that the effective epoch of deployment could be in the
65/// *next epoch*.
66pub struct ProgramRuntimeEnvironments {
67    /// Environment compiled for the current epoch in which programs are
68    /// executing.
69    execution: ProgramRuntimeEnvironment,
70    /// Environment compiled for the epoch of the next slot at which a program
71    /// deployed in the current slot will execute.
72    deployment: ProgramRuntimeEnvironment,
73}
74
75impl ProgramRuntimeEnvironments {
76    /// Create a new ProgramRuntimeEnvironments from an `execution` and
77    /// `deployment` environment.
78    pub fn new(
79        execution: ProgramRuntimeEnvironment,
80        deployment: ProgramRuntimeEnvironment,
81    ) -> Self {
82        Self {
83            execution,
84            deployment,
85        }
86    }
87
88    /// Get the program runtime environment for execution.
89    pub fn get_env_for_execution(&self) -> &ProgramRuntimeEnvironment {
90        &self.execution
91    }
92
93    /// Get the program runtime environment for deployment.
94    pub fn get_env_for_deployment(&self) -> &ProgramRuntimeEnvironment {
95        &self.deployment
96    }
97
98    #[cfg(feature = "dev-context-only-utils")]
99    pub fn mock() -> Self {
100        Self {
101            execution: get_mock_program_runtime_environment(),
102            deployment: get_mock_program_runtime_environment(),
103        }
104    }
105}
106
107#[cfg(feature = "dev-context-only-utils")]
108pub fn get_mock_program_runtime_environment() -> ProgramRuntimeEnvironment {
109    static MOCK_ENVIRONMENT: std::sync::OnceLock<ProgramRuntimeEnvironment> =
110        std::sync::OnceLock::<ProgramRuntimeEnvironment>::new();
111    MOCK_ENVIRONMENT
112        .get_or_init(|| ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock()))
113        .clone()
114}
115
116pub const MAX_LOADED_ENTRY_COUNT: usize = 1024;
117pub const MAX_TOMBSTONE_AGE_IN_SLOTS: u64 = 2250; // 15 Minutes at 400ms slot time
118
119/// A percentage, expected to be in the range `0..=100`.
120pub type Percent = u8;
121
122/// The given percentage of [`MAX_LOADED_ENTRY_COUNT`], as an entry count.
123/// Equivalent to the former `percentage` crate's
124/// `Percentage::from(percent).apply_to(MAX_LOADED_ENTRY_COUNT)`,
125/// i.e. floor(MAX_LOADED_ENTRY_COUNT * percent / 100).
126fn percent_of_max_entries(percent: Percent) -> usize {
127    debug_assert!(percent <= 100, "percent must be <= 100");
128    MAX_LOADED_ENTRY_COUNT.saturating_mul(percent as usize) / 100
129}
130
131/// Relationship between two fork IDs
132#[derive(Copy, Clone, Debug, PartialEq)]
133pub enum BlockRelation {
134    /// The slot is on the same fork and is an ancestor of the other slot
135    Ancestor,
136    /// The two slots are equal and are on the same fork
137    Equal,
138    /// The slot is on the same fork and is a descendant of the other slot
139    Descendant,
140    /// The slots are on two different forks and may have had a common ancestor at some point
141    Unrelated,
142    /// Either one or both of the slots are either older than the latest root, or are in future
143    Unknown,
144}
145
146/// Maps relationship between two slots.
147pub trait ForkGraph {
148    /// Returns the BlockRelation of A to B
149    fn relationship(&self, a: Slot, b: Slot) -> BlockRelation;
150}
151
152/// Globally manages the transition between environments at the epoch boundary
153#[derive(Debug, Default)]
154pub struct EpochBoundaryPreparation {
155    /// The epoch of the upcoming_environment
156    pub upcoming_epoch: Epoch,
157    /// Anticipated replacement for `environments` at the next epoch
158    ///
159    /// This is `None` during most of an epoch, and only `Some` around the boundaries (at the end and beginning of an epoch).
160    /// More precisely, it starts with the cache preparation phase a few hundred slots before the epoch boundary,
161    /// and it ends with the first rerooting after the epoch boundary.
162    pub upcoming_environment: Option<ProgramRuntimeEnvironment>,
163    /// List of loaded programs which should be recompiled before the next epoch (but don't have to).
164    pub programs_to_recompile: Vec<(Pubkey, Arc<ProgramCacheEntry>)>,
165}
166
167impl EpochBoundaryPreparation {
168    pub fn new(epoch: Epoch) -> Self {
169        Self {
170            upcoming_epoch: epoch,
171            upcoming_environment: None,
172            programs_to_recompile: Vec::default(),
173        }
174    }
175
176    /// Returns the upcoming environments depending on the given epoch
177    pub fn get_upcoming_environment_for_epoch(
178        &self,
179        epoch: Epoch,
180    ) -> Option<ProgramRuntimeEnvironment> {
181        if epoch == self.upcoming_epoch {
182            return self.upcoming_environment.clone();
183        }
184        None
185    }
186
187    /// Before rerooting the blockstore this concludes the epoch boundary preparation
188    pub fn reroot(&mut self, epoch: Epoch) -> Option<ProgramRuntimeEnvironment> {
189        if epoch == self.upcoming_epoch
190            && let Some(upcoming_environment) = self.upcoming_environment.take()
191        {
192            self.programs_to_recompile.clear();
193            return Some(upcoming_environment);
194        }
195
196        None
197    }
198}
199
200/// Input of ProgramCache::extract()
201#[derive(Clone, PartialEq, Debug)]
202pub struct ProgramToLoad<'a> {
203    /// The program address
204    pub program_id: &'a Pubkey,
205    /// The program loader
206    pub loader: ProgramCacheEntryOwner,
207    /// The slot the program was (re)deployed in, as reported by the program
208    /// account on the caller's own fork.
209    ///
210    /// For Loader V1/V2 and builtin programs, this is 0.
211    pub deployment_slot: Slot,
212}
213
214#[derive(Debug)]
215pub(crate) enum IndexImplementation {
216    /// Fork-graph aware index implementation
217    V1 {
218        /// A two level index:
219        ///
220        /// - the first level is for the address at which programs are deployed
221        /// - the second level for the slot (and thus also fork), sorted by slot
222        ///   number from smallest to largest.
223        entries: HashMap<Pubkey, Vec<Arc<ProgramCacheEntry>>>,
224        /// The entries that are getting loaded and have not yet finished loading.
225        ///
226        /// The key is the program address, the value is a tuple of the slot in which the program is
227        /// being loaded and the thread ID doing the load.
228        ///
229        /// It is possible that multiple TX batches from different slots need different versions of a
230        /// program. The deployment slot of a program is only known after load tho,
231        /// so all loads for a given program key are serialized.
232        loading_entries: Mutex<HashMap<Pubkey, (Slot, thread::ThreadId)>>,
233    },
234}
235
236/// This structure is the global cache of loaded, verified and compiled programs.
237///
238/// It ...
239/// - is validator global and fork graph aware, so it can optimize the commonalities across banks.
240/// - handles the visibility rules of un/re/deployments.
241/// - stores the usage statistics and verification status of each program.
242/// - is elastic and uses a probabilistic eviction strategy based on the usage statistics.
243/// - also keeps the compiled executables around, but only for the most used programs.
244/// - supports various kinds of tombstones to avoid loading programs which can not be loaded.
245/// - cleans up entries on orphan branches when the block store is rerooted.
246/// - supports the cache preparation phase before feature activations which can change cached programs.
247/// - manages the environments of the programs and upcoming environments for the next epoch.
248/// - allows for cooperative loading of TX batches which hit the same missing programs simultaneously.
249/// - enforces that all programs used in a batch are eagerly loaded ahead of execution.
250/// - is not persisted to disk or a snapshot, so it needs to cold start and warm up first.
251pub struct ProgramCache<FG: ForkGraph> {
252    /// Index of the cached entries and cooperative loading tasks
253    pub(crate) index: IndexImplementation,
254    /// The slot of the last rerooting
255    pub latest_root_slot: Slot,
256    /// Statistics counters
257    pub stats: ProgramCacheStats,
258    /// Reference to the block store
259    pub fork_graph: Option<Weak<RwLock<FG>>>,
260    /// Coordinates TX batches waiting for others to complete their task during cooperative loading
261    pub loading_task_waiter: Arc<LoadingTaskWaiter>,
262}
263
264impl<FG: ForkGraph> std::fmt::Debug for ProgramCache<FG> {
265    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
266        f.debug_struct("ProgramCache")
267            .field("root slot", &self.latest_root_slot)
268            .field("stats", &self.stats)
269            .field("index", &self.index)
270            .finish()
271    }
272}
273
274/// Local view into [ProgramCache] which was extracted for a specific TX batch.
275///
276/// This isolation enables the global [ProgramCache] to continue to evolve (e.g. evictions),
277/// while the TX batch is guaranteed it will continue to find all the programs it requires.
278/// For program management instructions this also buffers them before they are merged back into the global [ProgramCache].
279#[derive(Clone, Debug, Default)]
280pub struct ProgramCacheForTxBatch {
281    /// Pubkey is the address of a program.
282    /// ProgramCacheEntry is the corresponding program entry valid for the slot in which a transaction is being executed.
283    entries: HashMap<Pubkey, Arc<ProgramCacheEntry>>,
284    /// Program entries modified during the transaction batch.
285    modified_entries: HashMap<Pubkey, Arc<ProgramCacheEntry>>,
286    slot: Slot,
287    pub hit_max_limit: bool,
288    pub loaded_missing: bool,
289    pub merged_modified: bool,
290}
291
292impl ProgramCacheForTxBatch {
293    pub fn new(slot: Slot) -> Self {
294        Self {
295            entries: HashMap::new(),
296            modified_entries: HashMap::new(),
297            slot,
298            hit_max_limit: false,
299            loaded_missing: false,
300            merged_modified: false,
301        }
302    }
303
304    /// Refill the cache with a single entry. It's typically called during transaction loading, and
305    /// transaction processing (for program management instructions).
306    /// It replaces the existing entry (if any) with the provided entry. The return value contains
307    /// `true` if an entry existed.
308    /// The function also returns the newly inserted value.
309    pub fn replenish(
310        &mut self,
311        key: Pubkey,
312        entry: Arc<ProgramCacheEntry>,
313    ) -> (bool, Arc<ProgramCacheEntry>) {
314        (self.entries.insert(key, entry.clone()).is_some(), entry)
315    }
316
317    /// Store an entry in `modified_entries` for a program modified during the
318    /// transaction batch.
319    pub fn store_modified_entry(&mut self, key: Pubkey, entry: Arc<ProgramCacheEntry>) {
320        self.modified_entries.insert(key, entry);
321    }
322
323    /// Drain the program cache's modified entries, returning the owned
324    /// collection.
325    pub fn drain_modified_entries(&mut self) -> HashMap<Pubkey, Arc<ProgramCacheEntry>> {
326        std::mem::take(&mut self.modified_entries)
327    }
328
329    pub fn find(&self, key: &Pubkey) -> Option<Arc<ProgramCacheEntry>> {
330        // First lookup the cache of the programs modified by the current
331        // transaction. If not found, lookup the cache of the cache of the
332        // programs that are loaded for the transaction batch.
333        self.modified_entries
334            .get(key)
335            .or_else(|| self.entries.get(key))
336            .map(|entry| {
337                if entry.is_implicit_delay_visibility_tombstone(self.slot) {
338                    // Found a program entry on the current fork, but it's not effective
339                    // yet. It indicates that the program has delayed visibility. Return
340                    // the tombstone to reflect that.
341                    Arc::new(ProgramCacheEntry::new_delay_visibility_tombstone(
342                        entry.deployment_slot,
343                        entry.account_owner,
344                        Arc::clone(&entry.stats),
345                    ))
346                } else {
347                    entry.clone()
348                }
349            })
350    }
351
352    pub fn slot(&self) -> Slot {
353        self.slot
354    }
355
356    pub fn set_slot_for_tests(&mut self, slot: Slot) {
357        self.slot = slot;
358    }
359
360    pub fn merge(&mut self, modified_entries: &HashMap<Pubkey, Arc<ProgramCacheEntry>>) {
361        modified_entries.iter().for_each(|(key, entry)| {
362            self.merged_modified = true;
363            self.replenish(*key, entry.clone());
364        })
365    }
366
367    /// Remove an entry from the `entries` list.
368    /// Note: DOES NOT remove modified entries!
369    pub fn remove_entry(&mut self, key: &Pubkey) {
370        self.entries.remove(key);
371    }
372
373    pub fn is_empty(&self) -> bool {
374        self.entries.is_empty()
375    }
376}
377
378impl<FG: ForkGraph> ProgramCache<FG> {
379    pub fn new(root_slot: Slot) -> Self {
380        Self {
381            index: IndexImplementation::V1 {
382                entries: HashMap::new(),
383                loading_entries: Mutex::new(HashMap::new()),
384            },
385            latest_root_slot: root_slot,
386            stats: ProgramCacheStats::default(),
387            fork_graph: None,
388            loading_task_waiter: Arc::new(LoadingTaskWaiter::default()),
389        }
390    }
391
392    pub fn set_fork_graph(&mut self, fork_graph: Weak<RwLock<FG>>) {
393        self.fork_graph = Some(fork_graph);
394    }
395
396    /// Insert a single entry. It's typically called during transaction loading,
397    /// when the cache doesn't contain the entry corresponding to program `key`.
398    pub fn assign_program(
399        &mut self,
400        program_runtime_environment: &ProgramRuntimeEnvironment,
401        key: Pubkey,
402        _current_slot: Slot,
403        entry: Arc<ProgramCacheEntry>,
404    ) -> bool {
405        debug_assert!(
406            !matches!(&entry.program, ProgramCacheEntryType::DelayVisibility),
407            "Unexpected assignment of a DelayVisibility tombstone"
408        );
409        // This function always returns `true` during normal operation.
410        // Only during the cache preparation phase this can return `false`
411        // for entries with `upcoming_environment`.
412        fn is_current_env(
413            program_runtime_environment: &ProgramRuntimeEnvironment,
414            env_opt: Option<&ProgramRuntimeEnvironment>,
415        ) -> bool {
416            env_opt
417                .map(|env| env == program_runtime_environment)
418                .unwrap_or(true)
419        }
420        match &mut self.index {
421            IndexImplementation::V1 { entries, .. } => {
422                let slot_versions = &mut entries.entry(key).or_default();
423                let insertion_point = slot_versions.binary_search_by(|at| {
424                    at.deployment_slot
425                        .cmp(&entry.deployment_slot)
426                        .then(at.account_owner.cmp(&entry.account_owner))
427                        .then(
428                            // This `.then()` has no effect during normal operation.
429                            // Only during the cache preparation phase this does allow entries
430                            // which only differ in their environment to be interleaved in `slot_versions`.
431                            is_current_env(
432                                program_runtime_environment,
433                                at.program.get_environment(),
434                            )
435                            .cmp(&is_current_env(
436                                program_runtime_environment,
437                                entry.program.get_environment(),
438                            )),
439                        )
440                });
441                match insertion_point {
442                    Ok(index) => {
443                        let existing = slot_versions.get_mut(index).unwrap();
444                        match (&existing.program, &entry.program) {
445                            (
446                                ProgramCacheEntryType::Builtin(_),
447                                ProgramCacheEntryType::Builtin(_),
448                            )
449                            | (ProgramCacheEntryType::Closed, ProgramCacheEntryType::Unloaded(_))
450                            | (
451                                ProgramCacheEntryType::Unloaded(_),
452                                ProgramCacheEntryType::Loaded(_),
453                            )
454                            | (
455                                ProgramCacheEntryType::Unloaded(_),
456                                ProgramCacheEntryType::FailedVerification(_),
457                            ) => {}
458                            _ => {
459                                // Something is wrong, I can feel it ...
460                                error!(
461                                    "ProgramCache::assign_program() failed key={key:?} \
462                                     existing={slot_versions:?} entry={entry:?}"
463                                );
464                                debug_assert!(false, "Unexpected replacement of an entry");
465                                self.stats.replacements.fetch_add(1, Ordering::Relaxed);
466                                return true;
467                            }
468                        }
469                        entry.stats.merge_from(&existing.stats);
470                        *existing = Arc::clone(&entry);
471                        self.stats.reloads.fetch_add(1, Ordering::Relaxed);
472                    }
473                    Err(index) => {
474                        self.stats.insertions.fetch_add(1, Ordering::Relaxed);
475                        slot_versions.insert(index, Arc::clone(&entry));
476                    }
477                }
478                // Remove existing entries in the same deployment slot unless they are for a different
479                // environment.
480                // This overwrites the current status of a program in program management instructions.
481                slot_versions.retain(|existing| {
482                    existing.deployment_slot != entry.deployment_slot
483                        || existing
484                            .program
485                            .get_environment()
486                            .zip(entry.program.get_environment())
487                            .map(|(a, b)| a != b)
488                            .unwrap_or(false)
489                        || Arc::ptr_eq(existing, &entry)
490                });
491            }
492        }
493        false
494    }
495
496    pub fn prune_by_deployment_slot(&mut self, slot: Slot) {
497        match &mut self.index {
498            IndexImplementation::V1 { entries, .. } => {
499                for second_level in entries.values_mut() {
500                    second_level.retain(|entry| entry.deployment_slot != slot);
501                }
502                self.remove_programs_with_no_entries();
503            }
504        }
505    }
506
507    /// Before rerooting the blockstore this removes all superfluous entries
508    pub fn prune(
509        &mut self,
510        new_root_slot: Slot,
511        new_environment: Option<ProgramRuntimeEnvironment>,
512        fork_graph: &FG,
513    ) {
514        match &mut self.index {
515            IndexImplementation::V1 { entries, .. } => {
516                let tombstone_slot_cutoff =
517                    new_root_slot.saturating_sub(MAX_TOMBSTONE_AGE_IN_SLOTS);
518                entries.retain(|_id, second_level| {
519                    // Clean up tombstones and unloaded entries
520                    if let [candidate] = &second_level[..]
521                        && (matches!(candidate.program, ProgramCacheEntryType::Unloaded(_))
522                            || candidate.is_tombstone())
523                        && candidate.deployment_slot <= self.latest_root_slot
524                        && candidate.latest_access_slot.load(Ordering::Relaxed)
525                            < tombstone_slot_cutoff
526                    {
527                        self.stats.prunes_stale.fetch_add(1, Ordering::Relaxed);
528                        return false;
529                    }
530                    // Remove entries un/re/deployed on orphan forks
531                    let mut first_ancestor_found = false;
532                    let mut first_ancestor_env = None;
533                    *second_level = second_level
534                        .iter()
535                        .rev()
536                        .filter(|entry| {
537                            let relation =
538                                fork_graph.relationship(entry.deployment_slot, new_root_slot);
539                            if entry.deployment_slot >= new_root_slot {
540                                let keep = matches!(
541                                    relation,
542                                    BlockRelation::Equal | BlockRelation::Descendant
543                                );
544                                if !keep {
545                                    self.stats.prunes_orphan.fetch_add(1, Ordering::Relaxed);
546                                }
547                                keep
548                            } else if matches!(relation, BlockRelation::Ancestor)
549                                || entry.deployment_slot <= self.latest_root_slot
550                            {
551                                if !first_ancestor_found {
552                                    first_ancestor_found = true;
553                                    first_ancestor_env = entry.program.get_environment();
554                                    return true;
555                                }
556                                // Do not prune the entry if the runtime environment of the entry is
557                                // different than the entry that was previously found (stored in
558                                // first_ancestor_env). Different environment indicates that this entry
559                                // might belong to an older epoch that had a different environment (e.g.
560                                // different feature set). Once the root moves to the new/current epoch,
561                                // the entry will get pruned. But, until then the entry might still be
562                                // getting used by an older slot.
563                                if let Some(entry_env) = entry.program.get_environment()
564                                    && let Some(env) = first_ancestor_env
565                                    && entry_env != env
566                                {
567                                    return true;
568                                }
569                                self.stats.prunes_orphan.fetch_add(1, Ordering::Relaxed);
570                                false
571                            } else {
572                                self.stats.prunes_orphan.fetch_add(1, Ordering::Relaxed);
573                                false
574                            }
575                        })
576                        .filter(|entry| {
577                            // Remove outdated environment of previous feature set
578                            if let Some(new_environment) = new_environment.as_ref()
579                                && !Self::matches_environment(entry, new_environment)
580                            {
581                                self.stats
582                                    .prunes_environment
583                                    .fetch_add(1, Ordering::Relaxed);
584                                return false;
585                            }
586                            true
587                        })
588                        .cloned()
589                        .collect();
590                    second_level.reverse();
591                    true
592                });
593            }
594        }
595        self.remove_programs_with_no_entries();
596        debug_assert!(self.latest_root_slot <= new_root_slot);
597        self.latest_root_slot = new_root_slot;
598    }
599
600    fn matches_environment(
601        entry: &Arc<ProgramCacheEntry>,
602        program_runtime_environment: &ProgramRuntimeEnvironment,
603    ) -> bool {
604        let Some(environment) = entry.program.get_environment() else {
605            return true;
606        };
607        environment == program_runtime_environment
608    }
609
610    /// Extracts a subset of the programs relevant to a transaction batch
611    /// and returns which program accounts the accounts DB needs to load.
612    pub fn extract(
613        &self,
614        search_for: &mut Vec<ProgramToLoad>,
615        loaded_programs_for_tx_batch: &mut ProgramCacheForTxBatch,
616        program_runtime_environment_for_execution: &ProgramRuntimeEnvironment,
617        increment_usage_counter: bool,
618        count_hits_and_misses: bool,
619    ) -> Option<Pubkey> {
620        debug_assert!(self.fork_graph.is_some());
621        let fork_graph = self.fork_graph.as_ref().unwrap().upgrade().unwrap();
622        let locked_fork_graph = fork_graph.read().unwrap();
623        let entries_in_batch = loaded_programs_for_tx_batch.entries.len();
624        let mut cooperative_loading_task = None;
625        match &self.index {
626            IndexImplementation::V1 {
627                entries,
628                loading_entries,
629            } => {
630                search_for.retain(|program_to_load| {
631                    if let Some(second_level) = entries.get(program_to_load.program_id) {
632                        for entry in second_level.iter().rev() {
633                            // The entry must have been deployed in the slot reported by
634                            // the caller's own program account, and by the same loader.
635                            if program_to_load.deployment_slot != entry.deployment_slot
636                                || program_to_load.loader != entry.account_owner
637                            {
638                                continue;
639                            }
640
641                            // At this point we're sitting on an entry with a matching
642                            // deployment slot and owner.
643                            //
644                            // Fork-graph analysis below this is now redundant, and it
645                            // can be removed in follow-up.
646                            let entry_in_same_branch = entry.deployment_slot
647                                <= self.latest_root_slot
648                                || matches!(
649                                    locked_fork_graph.relationship(
650                                        entry.deployment_slot,
651                                        loaded_programs_for_tx_batch.slot
652                                    ),
653                                    BlockRelation::Equal | BlockRelation::Ancestor
654                                );
655                            if entry_in_same_branch {
656                                let entry_is_effective =
657                                    loaded_programs_for_tx_batch.slot >= entry.effective_slot();
658                                let entry_to_return = if entry_is_effective {
659                                    if !Self::matches_environment(
660                                        entry,
661                                        program_runtime_environment_for_execution,
662                                    ) {
663                                        // We found an entry that would work, had its environment
664                                        // matched the one we're planning to use for this slot. A
665                                        // sibling compiled against that environment may follow.
666                                        continue;
667                                    }
668                                    if let ProgramCacheEntryType::Unloaded(_environment) =
669                                        &entry.program
670                                    {
671                                        break;
672                                    }
673                                    entry.clone()
674                                } else if entry.is_implicit_delay_visibility_tombstone(
675                                    loaded_programs_for_tx_batch.slot,
676                                ) {
677                                    // Found a program entry on the current fork, but it's not effective
678                                    // yet. It indicates that the program has delayed visibility. Return
679                                    // the tombstone to reflect that.
680                                    Arc::new(ProgramCacheEntry::new_delay_visibility_tombstone(
681                                        entry.deployment_slot,
682                                        entry.account_owner,
683                                        Arc::clone(&entry.stats),
684                                    ))
685                                } else {
686                                    continue;
687                                };
688                                entry.update_access_slot(loaded_programs_for_tx_batch.slot);
689                                if increment_usage_counter {
690                                    entry_to_return.stats.uses.fetch_add(1, Ordering::Relaxed);
691                                }
692                                loaded_programs_for_tx_batch
693                                    .entries
694                                    .insert(*program_to_load.program_id, entry_to_return);
695                                return false;
696                            }
697                        }
698                    }
699                    if cooperative_loading_task.is_none() {
700                        let mut loading_entries = loading_entries.lock().unwrap();
701                        let entry = loading_entries.entry(*program_to_load.program_id);
702                        if let Entry::Vacant(entry) = entry {
703                            entry.insert((
704                                loaded_programs_for_tx_batch.slot,
705                                thread::current().id(),
706                            ));
707                            cooperative_loading_task = Some(*program_to_load.program_id);
708                        }
709                    }
710                    true
711                });
712            }
713        }
714        drop(locked_fork_graph);
715        if count_hits_and_misses {
716            let misses = search_for.len() as u64;
717            let hits = loaded_programs_for_tx_batch
718                .entries
719                .len()
720                .saturating_sub(entries_in_batch) as u64;
721            self.stats.misses.fetch_add(misses, Ordering::Relaxed);
722            self.stats.hits.fetch_add(hits, Ordering::Relaxed);
723        }
724        cooperative_loading_task
725    }
726
727    /// Called by Bank::replenish_program_cache() for each program that is done loading.
728    pub fn finish_cooperative_loading_task(
729        &mut self,
730        program_runtime_environment: &ProgramRuntimeEnvironment,
731        current_slot: Slot,
732        key: Pubkey,
733        loaded_program: Arc<ProgramCacheEntry>,
734    ) -> bool {
735        match &mut self.index {
736            IndexImplementation::V1 {
737                loading_entries, ..
738            } => {
739                let loading_thread = loading_entries.get_mut().unwrap().remove(&key);
740                debug_assert_eq!(loading_thread, Some((current_slot, thread::current().id())));
741                // Check that it will be visible to our own fork once inserted
742                if loaded_program.deployment_slot > self.latest_root_slot
743                    && !matches!(
744                        self.fork_graph
745                            .as_ref()
746                            .unwrap()
747                            .upgrade()
748                            .unwrap()
749                            .read()
750                            .unwrap()
751                            .relationship(loaded_program.deployment_slot, current_slot),
752                        BlockRelation::Equal | BlockRelation::Ancestor
753                    )
754                {
755                    self.stats.lost_insertions.fetch_add(1, Ordering::Relaxed);
756                }
757                let was_occupied = self.assign_program(
758                    program_runtime_environment,
759                    key,
760                    current_slot,
761                    loaded_program,
762                );
763                self.loading_task_waiter.notify();
764                was_occupied
765            }
766        }
767    }
768
769    pub fn merge(
770        &mut self,
771        program_runtime_environment: &ProgramRuntimeEnvironment,
772        current_slot: Slot,
773        modified_entries: &HashMap<Pubkey, Arc<ProgramCacheEntry>>,
774    ) {
775        modified_entries.iter().for_each(|(key, entry)| {
776            self.assign_program(
777                program_runtime_environment,
778                *key,
779                current_slot,
780                entry.clone(),
781            );
782        })
783    }
784
785    /// Returns the list of entries which are verified and compiled.
786    pub fn get_flattened_entries(&self) -> Vec<(Pubkey, Arc<ProgramCacheEntry>)> {
787        match &self.index {
788            IndexImplementation::V1 { entries, .. } => entries
789                .iter()
790                .flat_map(|(id, second_level)| {
791                    second_level
792                        .iter()
793                        .filter_map(move |program| match program.program {
794                            ProgramCacheEntryType::Loaded(_) => Some((*id, program.clone())),
795                            _ => None,
796                        })
797                })
798                .collect(),
799        }
800    }
801
802    /// Returns the list of all entries in the cache.
803    #[cfg(feature = "dev-context-only-utils")]
804    pub fn get_flattened_entries_for_tests(&self) -> Vec<(Pubkey, Arc<ProgramCacheEntry>)> {
805        match &self.index {
806            IndexImplementation::V1 { entries, .. } => entries
807                .iter()
808                .flat_map(|(id, second_level)| {
809                    second_level.iter().map(|program| (*id, program.clone()))
810                })
811                .collect(),
812        }
813    }
814
815    /// Returns the slot versions for the given program id.
816    pub fn get_slot_versions_for_tests(&self, key: &Pubkey) -> &[Arc<ProgramCacheEntry>] {
817        match &self.index {
818            IndexImplementation::V1 { entries, .. } => entries
819                .get(key)
820                .map(|second_level| second_level.as_ref())
821                .unwrap_or(&[]),
822        }
823    }
824
825    /// Unloads programs which were used infrequently
826    pub fn sort_and_unload(&mut self, shrink_to_percent: Percent) {
827        let mut sorted_candidates = self.get_flattened_entries();
828        sorted_candidates
829            .sort_by_cached_key(|(_id, program)| program.stats.uses.load(Ordering::Relaxed));
830        let num_to_unload = sorted_candidates
831            .len()
832            .saturating_sub(percent_of_max_entries(shrink_to_percent));
833        for (program, entry) in sorted_candidates.iter().take(num_to_unload) {
834            self.unload_program_entry(*program, entry);
835        }
836    }
837
838    /// Evicts programs using random selection, choosing the worst scoring program out of the
839    /// entries sampled.
840    ///
841    /// The eviction is performed enough number of times to reduce the cache usage to the given
842    /// percentage.
843    pub fn evict_using_random_selection(&mut self, shrink_to_percent: Percent, now: Slot) {
844        let mut candidates = self.get_flattened_entries();
845        let mut rng = rng();
846        self.stats
847            .water_level
848            .store(candidates.len() as u64, Ordering::Relaxed);
849        let num_to_unload = candidates
850            .len()
851            .saturating_sub(percent_of_max_entries(shrink_to_percent));
852        let mut sample_entry = |candidates: &Vec<(Pubkey, Arc<ProgramCacheEntry>)>| {
853            // gen_range is deprecated in favor of random_range in rand>=0.9, but we also get
854            // rnd() from shuttle, which doesn't yet support rand 0.9 APIs
855            #[cfg(feature = "shuttle-test")]
856            let index = rng.gen_range(0..candidates.len());
857            #[cfg(not(feature = "shuttle-test"))]
858            let index = rng.random_range(0..candidates.len());
859            let usage_counter = candidates
860                .get(index)
861                .expect("Failed to get cached entry")
862                .1
863                .retention_score();
864            (index, usage_counter)
865        };
866
867        // Random sampling with just 2 choices can frequently lead to a situation where both
868        // entries chosen have relatively high retention scores, having us to pick one out of two
869        // poor options. We can tell what a relatively high retention score is, so we can make a
870        // few additional samples until we hit some other entry that isn't as highly scoring.
871        //
872        // Note that the "high enough" compilation time and use count numbers used here are
873        // relatively arbitrary.
874        const MAX_ADDITIONAL_SAMPLES: usize = 3;
875        let avoid_evicting_above_score = retention_score(now, 500 * EMA_SCALE, 500);
876        for _ in 0..num_to_unload {
877            let (mut index, mut score) = sample_entry(&candidates);
878            for _ in 0..MAX_ADDITIONAL_SAMPLES {
879                let (sample_index, sample_score) = sample_entry(&candidates);
880                if score > sample_score {
881                    index = sample_index;
882                    score = sample_score;
883                }
884                if score < avoid_evicting_above_score {
885                    break;
886                }
887            }
888            let (id, entry) = candidates.swap_remove(index);
889            self.unload_program_entry(id, &entry);
890        }
891    }
892
893    /// Removes all the entries at the given keys, if they exist
894    pub fn remove_programs(&mut self, keys: impl Iterator<Item = Pubkey>) {
895        match &mut self.index {
896            IndexImplementation::V1 { entries, .. } => {
897                for k in keys {
898                    entries.remove(&k);
899                }
900            }
901        }
902    }
903
904    /// This function removes the given entry for the given program from the cache.
905    /// The function expects that the program and entry exists in the cache. Otherwise it'll panic.
906    fn unload_program_entry(&mut self, id: Pubkey, remove_entry: &Arc<ProgramCacheEntry>) {
907        match &mut self.index {
908            IndexImplementation::V1 { entries, .. } => {
909                let second_level = entries.get_mut(&id).expect("Cache lookup failed");
910                let candidate = second_level
911                    .iter_mut()
912                    .find(|entry| Arc::ptr_eq(entry, remove_entry))
913                    .expect("Program entry not found");
914
915                // Only loaded entries shall be unloaded by eviction.
916                if let ProgramCacheEntryType::Loaded(_) = candidate.program
917                    && let Some(unloaded) = candidate.to_unloaded()
918                {
919                    if candidate.stats.uses.load(Ordering::Relaxed) == 1 {
920                        self.stats.one_hit_wonders.fetch_add(1, Ordering::Relaxed);
921                    }
922                    self.stats
923                        .evictions
924                        .entry(id)
925                        .and_modify(|c| *c = c.saturating_add(1))
926                        .or_insert(1);
927                    *candidate = Arc::new(unloaded);
928                }
929            }
930        }
931    }
932
933    fn remove_programs_with_no_entries(&mut self) {
934        match &mut self.index {
935            IndexImplementation::V1 { entries, .. } => {
936                let num_programs_before_removal = entries.len();
937                entries.retain(|_key, second_level| !second_level.is_empty());
938                if entries.len() < num_programs_before_removal {
939                    self.stats.empty_entries.fetch_add(
940                        num_programs_before_removal.saturating_sub(entries.len()) as u64,
941                        Ordering::Relaxed,
942                    );
943                }
944            }
945        }
946    }
947}
948
949#[cfg(feature = "frozen-abi")]
950impl solana_frozen_abi::abi_example::AbiExample for ProgramCacheEntry {
951    fn example() -> Self {
952        // ProgramCacheEntry isn't serializable by definition.
953        Self::default()
954    }
955}
956
957#[cfg(feature = "frozen-abi")]
958impl<FG: ForkGraph> solana_frozen_abi::abi_example::AbiExample for ProgramCache<FG> {
959    fn example() -> Self {
960        // ProgramCache isn't serializable by definition.
961        Self::new(Slot::default())
962    }
963}
964
965#[cfg(test)]
966pub(crate) mod tests {
967    use {
968        crate::{
969            loaded_programs::{
970                BlockRelation, ForkGraph, IndexImplementation, MAX_TOMBSTONE_AGE_IN_SLOTS, Percent,
971                ProgramCache, ProgramCacheForTxBatch, ProgramRuntimeEnvironment, ProgramToLoad,
972                get_mock_program_runtime_environment,
973            },
974            program_cache_entry::{
975                ProgramCacheEntry, ProgramCacheEntryOwner, ProgramCacheEntryType,
976            },
977            program_metrics::ProgramStatistics,
978        },
979        assert_matches::assert_matches,
980        solana_clock::Slot,
981        solana_pubkey::Pubkey,
982        solana_sbpf::{elf::Executable, program::BuiltinProgram},
983        solana_svm_type_overrides::{
984            sync::{
985                Arc, RwLock,
986                atomic::{AtomicU64, Ordering},
987            },
988            thread,
989        },
990        std::{fs::File, io::Read, ops::ControlFlow},
991        test_case::{test_case, test_matrix},
992    };
993
994    fn new_test_entry(deployment_slot: Slot) -> Arc<ProgramCacheEntry> {
995        new_test_entry_with_usage(deployment_slot, ProgramStatistics::default())
996    }
997
998    fn new_closed_entry(_env: ProgramRuntimeEnvironment) -> ProgramCacheEntryType {
999        ProgramCacheEntryType::Closed
1000    }
1001
1002    fn new_builtin_entry(_env: ProgramRuntimeEnvironment) -> ProgramCacheEntryType {
1003        ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock())
1004    }
1005
1006    fn new_failed_verification_entry(env: ProgramRuntimeEnvironment) -> ProgramCacheEntryType {
1007        ProgramCacheEntryType::FailedVerification(env)
1008    }
1009
1010    fn new_unloaded_entry(env: ProgramRuntimeEnvironment) -> ProgramCacheEntryType {
1011        ProgramCacheEntryType::Unloaded(env)
1012    }
1013
1014    fn new_loaded_entry(env: ProgramRuntimeEnvironment) -> ProgramCacheEntryType {
1015        let mut elf = Vec::new();
1016        File::open("../programs/bpf_loader/test_elfs/out/noop_aligned.so")
1017            .unwrap()
1018            .read_to_end(&mut elf)
1019            .unwrap();
1020        let executable = Executable::load(&elf, Arc::clone(&*env)).unwrap();
1021        ProgramCacheEntryType::Loaded(executable)
1022    }
1023
1024    fn new_test_entry_with_owner(
1025        deployment_slot: Slot,
1026        account_owner: ProgramCacheEntryOwner,
1027        program: ProgramCacheEntryType,
1028    ) -> Arc<ProgramCacheEntry> {
1029        Arc::new(ProgramCacheEntry {
1030            program,
1031            account_owner,
1032            deployment_slot,
1033            stats: Arc::default(),
1034            latest_access_slot: AtomicU64::default(),
1035        })
1036    }
1037
1038    fn new_test_cache_with_fork_graph(
1039        relation: BlockRelation,
1040    ) -> (ProgramCache<TestForkGraph>, Arc<RwLock<TestForkGraph>>) {
1041        let mut cache = ProgramCache::<TestForkGraph>::new(0);
1042        let fork_graph = Arc::new(RwLock::new(TestForkGraph { relation }));
1043        cache.set_fork_graph(Arc::downgrade(&fork_graph));
1044        (cache, fork_graph)
1045    }
1046
1047    pub(crate) fn new_test_entry_with_usage(
1048        deployment_slot: Slot,
1049        stats: ProgramStatistics,
1050    ) -> Arc<ProgramCacheEntry> {
1051        Arc::new(ProgramCacheEntry {
1052            program: new_loaded_entry(get_mock_program_runtime_environment()),
1053            account_owner: ProgramCacheEntryOwner::LoaderV2,
1054            deployment_slot,
1055            stats: Arc::new(stats),
1056            latest_access_slot: AtomicU64::new(deployment_slot),
1057        })
1058    }
1059
1060    fn new_test_builtin_entry(deployment_slot: Slot) -> Arc<ProgramCacheEntry> {
1061        Arc::new(ProgramCacheEntry {
1062            program: ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1063            account_owner: ProgramCacheEntryOwner::NativeLoader,
1064            deployment_slot,
1065            stats: Arc::default(),
1066            latest_access_slot: AtomicU64::default(),
1067        })
1068    }
1069
1070    fn set_failed_verification_tombstone<FG: ForkGraph>(
1071        cache: &mut ProgramCache<FG>,
1072        key: Pubkey,
1073        current_slot: Slot,
1074        env: ProgramRuntimeEnvironment,
1075    ) -> Arc<ProgramCacheEntry> {
1076        let program = Arc::new(ProgramCacheEntry::new_failed_verification_tombstone(
1077            current_slot,
1078            ProgramCacheEntryOwner::LoaderV2,
1079            ProgramRuntimeEnvironment::clone(&env),
1080        ));
1081        cache.assign_program(&env, key, current_slot, program.clone());
1082        program
1083    }
1084
1085    fn insert_unloaded_entry<FG: ForkGraph>(
1086        cache: &mut ProgramCache<FG>,
1087        key: Pubkey,
1088        current_slot: Slot,
1089    ) -> Arc<ProgramCacheEntry> {
1090        let env = get_mock_program_runtime_environment();
1091        let loaded = new_test_entry_with_usage(current_slot, ProgramStatistics::default());
1092        let unloaded = Arc::new(loaded.to_unloaded().expect("Failed to unload the program"));
1093        cache.assign_program(&env, key, current_slot, unloaded.clone());
1094        unloaded
1095    }
1096
1097    fn num_matching_entries<P, FG>(cache: &ProgramCache<FG>, predicate: P) -> usize
1098    where
1099        P: Fn(&ProgramCacheEntryType) -> bool,
1100        FG: ForkGraph,
1101    {
1102        cache
1103            .get_flattened_entries_for_tests()
1104            .iter()
1105            .filter(|(_key, program)| predicate(&program.program))
1106            .count()
1107    }
1108
1109    #[expect(clippy::arithmetic_side_effects)]
1110    fn program_deploy_test_helper(
1111        cache: &mut ProgramCache<TestForkGraph>,
1112        program: Pubkey,
1113        deployment_slots: Vec<Slot>,
1114        usage_counters: Vec<u64>,
1115        programs: &mut Vec<(Pubkey, Slot, u64)>,
1116    ) {
1117        let env = get_mock_program_runtime_environment();
1118        // Add multiple entries for program
1119        deployment_slots
1120            .iter()
1121            .enumerate()
1122            .for_each(|(i, deployment_slot)| {
1123                let usage_counter = *usage_counters.get(i).unwrap_or(&0);
1124                let stats = ProgramStatistics {
1125                    uses: usage_counter.into(),
1126                    ..Default::default()
1127                };
1128                cache.assign_program(
1129                    &env,
1130                    program,
1131                    *deployment_slot,
1132                    new_test_entry_with_usage(*deployment_slot, stats),
1133                );
1134                programs.push((program, *deployment_slot, usage_counter));
1135            });
1136
1137        let next_slot = deployment_slots.iter().max().map_or(0, |slot| slot + 1);
1138
1139        // Add tombstones entries for program
1140        let env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
1141        for slot in next_slot..next_slot + 10 {
1142            set_failed_verification_tombstone(
1143                cache,
1144                program,
1145                slot,
1146                ProgramRuntimeEnvironment::clone(&env),
1147            );
1148        }
1149
1150        // Add unloaded entries for program
1151        for slot in next_slot + 10..next_slot + 20 {
1152            insert_unloaded_entry(cache, program, slot);
1153        }
1154    }
1155
1156    #[test]
1157    fn test_random_eviction() {
1158        let mut programs = vec![];
1159        let mut cache = ProgramCache::<TestForkGraph>::new(0);
1160
1161        // This test adds different kind of entries to the cache.
1162        // Tombstones and unloaded entries are expected to not be evicted.
1163        // It also adds multiple entries for three programs as it tries to create a typical cache instance.
1164
1165        // Program 1
1166        program_deploy_test_helper(
1167            &mut cache,
1168            Pubkey::new_unique(),
1169            vec![0, 10, 20, 30, 40],
1170            vec![4, 5, 25, 35, 12],
1171            &mut programs,
1172        );
1173
1174        // Program 2
1175        program_deploy_test_helper(
1176            &mut cache,
1177            Pubkey::new_unique(),
1178            vec![5, 11, 21, 24],
1179            vec![0, 2, 30, 45],
1180            &mut programs,
1181        );
1182
1183        // Program 3
1184        program_deploy_test_helper(
1185            &mut cache,
1186            Pubkey::new_unique(),
1187            vec![0, 5, 15, 25],
1188            vec![100, 3, 20, 40],
1189            &mut programs,
1190        );
1191
1192        // 1 for each deployment slot
1193        let num_loaded_expected = 13;
1194        // 10 for each program
1195        let num_unloaded_expected = 30;
1196        // 10 for each program
1197        let num_tombstones_expected = 30;
1198
1199        // Count the number of loaded, unloaded and tombstone entries.
1200        programs.sort_by_key(|(_id, _slot, usage_count)| *usage_count);
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!(
1209                program_type,
1210                ProgramCacheEntryType::DelayVisibility
1211                    | ProgramCacheEntryType::FailedVerification(_)
1212                    | ProgramCacheEntryType::Closed
1213            )
1214        });
1215
1216        // Test that the cache is constructed with the expected number of entries.
1217        assert_eq!(num_loaded, num_loaded_expected);
1218        assert_eq!(num_unloaded, num_unloaded_expected);
1219        assert_eq!(num_tombstones, num_tombstones_expected);
1220
1221        // Evict entries from the cache
1222        let eviction_pct: Percent = 1;
1223
1224        let num_loaded_expected = crate::loaded_programs::percent_of_max_entries(eviction_pct);
1225        let num_unloaded_expected = num_unloaded_expected + num_loaded - num_loaded_expected;
1226        cache.evict_using_random_selection(eviction_pct, 21);
1227
1228        // Count the number of loaded, unloaded and tombstone entries.
1229        let num_loaded = num_matching_entries(&cache, |program_type| {
1230            matches!(program_type, ProgramCacheEntryType::Loaded(_))
1231        });
1232        let num_unloaded = num_matching_entries(&cache, |program_type| {
1233            matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1234        });
1235        let num_tombstones = num_matching_entries(&cache, |program_type| {
1236            matches!(program_type, ProgramCacheEntryType::FailedVerification(_))
1237        });
1238
1239        // However many entries are left after the shrink
1240        assert_eq!(num_loaded, num_loaded_expected);
1241        // The original unloaded entries + the evicted loaded entries
1242        assert_eq!(num_unloaded, num_unloaded_expected);
1243        // The original tombstones are not evicted
1244        assert_eq!(num_tombstones, num_tombstones_expected);
1245    }
1246
1247    #[test]
1248    fn test_eviction() {
1249        let mut programs = vec![];
1250        let mut cache = ProgramCache::<TestForkGraph>::new(0);
1251
1252        // Program 1
1253        program_deploy_test_helper(
1254            &mut cache,
1255            Pubkey::new_unique(),
1256            vec![0, 10, 20, 30, 40],
1257            vec![4, 5, 25, 35, 12],
1258            &mut programs,
1259        );
1260
1261        // Program 2
1262        program_deploy_test_helper(
1263            &mut cache,
1264            Pubkey::new_unique(),
1265            vec![5, 11, 21, 24],
1266            vec![0, 2, 30, 45],
1267            &mut programs,
1268        );
1269
1270        // Program 3
1271        program_deploy_test_helper(
1272            &mut cache,
1273            Pubkey::new_unique(),
1274            vec![0, 5, 15, 25],
1275            vec![100, 3, 20, 40],
1276            &mut programs,
1277        );
1278
1279        // 1 for each deployment slot
1280        let num_loaded_expected = 13;
1281        // 10 for each program
1282        let num_unloaded_expected = 30;
1283        // 10 for each program
1284        let num_tombstones_expected = 30;
1285
1286        // Count the number of loaded, unloaded and tombstone entries.
1287        programs.sort_by_key(|(_id, _slot, usage_count)| *usage_count);
1288        let num_loaded = num_matching_entries(&cache, |program_type| {
1289            matches!(program_type, ProgramCacheEntryType::Loaded(_))
1290        });
1291        let num_unloaded = num_matching_entries(&cache, |program_type| {
1292            matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1293        });
1294        let num_tombstones = num_matching_entries(&cache, |program_type| {
1295            matches!(program_type, ProgramCacheEntryType::FailedVerification(_))
1296        });
1297
1298        // Test that the cache is constructed with the expected number of entries.
1299        assert_eq!(num_loaded, num_loaded_expected);
1300        assert_eq!(num_unloaded, num_unloaded_expected);
1301        assert_eq!(num_tombstones, num_tombstones_expected);
1302
1303        // Evict entries from the cache
1304        let eviction_pct: Percent = 1;
1305
1306        let num_loaded_expected = crate::loaded_programs::percent_of_max_entries(eviction_pct);
1307        let num_unloaded_expected = num_unloaded_expected + num_loaded - num_loaded_expected;
1308
1309        cache.sort_and_unload(eviction_pct);
1310
1311        // Check that every program is still in the cache.
1312        let entries = cache.get_flattened_entries_for_tests();
1313        programs.iter().for_each(|entry| {
1314            assert!(entries.iter().any(|(key, _entry)| key == &entry.0));
1315        });
1316
1317        let unloaded = entries
1318            .iter()
1319            .filter_map(|(key, program)| {
1320                matches!(program.program, ProgramCacheEntryType::Unloaded(_))
1321                    .then_some((*key, program.stats.uses.load(Ordering::Relaxed)))
1322            })
1323            .collect::<Vec<(Pubkey, u64)>>();
1324
1325        for index in 0..3 {
1326            let expected = programs.get(index).expect("Missing program");
1327            assert!(unloaded.contains(&(expected.0, expected.2)));
1328        }
1329
1330        // Count the number of loaded, unloaded and tombstone entries.
1331        let num_loaded = num_matching_entries(&cache, |program_type| {
1332            matches!(program_type, ProgramCacheEntryType::Loaded(_))
1333        });
1334        let num_unloaded = num_matching_entries(&cache, |program_type| {
1335            matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1336        });
1337        let num_tombstones = num_matching_entries(&cache, |program_type| {
1338            matches!(
1339                program_type,
1340                ProgramCacheEntryType::DelayVisibility
1341                    | ProgramCacheEntryType::FailedVerification(_)
1342                    | ProgramCacheEntryType::Closed
1343            )
1344        });
1345
1346        // However many entries are left after the shrink
1347        assert_eq!(num_loaded, num_loaded_expected);
1348        // The original unloaded entries + the evicted loaded entries
1349        assert_eq!(num_unloaded, num_unloaded_expected);
1350        // The original tombstones are not evicted
1351        assert_eq!(num_tombstones, num_tombstones_expected);
1352    }
1353
1354    #[test]
1355    fn test_usage_count_of_unloaded_program() {
1356        let mut cache = ProgramCache::<TestForkGraph>::new(0);
1357        let env = get_mock_program_runtime_environment();
1358
1359        let program = Pubkey::new_unique();
1360        let evict_to_pct: Percent = 2;
1361        let cache_capacity_after_shrink =
1362            crate::loaded_programs::percent_of_max_entries(evict_to_pct);
1363        // Add enough programs to the cache to trigger 1 eviction after shrinking.
1364        let num_total_programs = (cache_capacity_after_shrink + 1) as u64;
1365        (0..num_total_programs).for_each(|i| {
1366            let stats = ProgramStatistics {
1367                uses: (i + 10).into(),
1368                ..Default::default()
1369            };
1370            let entry = new_test_entry_with_usage(i, stats);
1371            cache.assign_program(&env, program, i, entry);
1372        });
1373
1374        cache.sort_and_unload(evict_to_pct);
1375
1376        let num_unloaded = num_matching_entries(&cache, |program_type| {
1377            matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1378        });
1379        assert_eq!(num_unloaded, 1);
1380
1381        cache
1382            .get_flattened_entries_for_tests()
1383            .iter()
1384            .for_each(|(_key, program)| {
1385                if matches!(program.program, ProgramCacheEntryType::Unloaded(_)) {
1386                    // Test that the usage counter is retained for the unloaded program
1387                    assert_eq!(program.stats.uses.load(Ordering::Relaxed), 10);
1388                    assert_eq!(program.deployment_slot, 0);
1389                    assert_eq!(program.effective_slot(), 1);
1390                }
1391            });
1392
1393        // Replenish the program that was just unloaded. Use 0 as the usage counter. This should be
1394        // updated with the usage counter from the unloaded program.
1395        cache.assign_program(
1396            &env,
1397            program,
1398            0,
1399            new_test_entry_with_usage(0, ProgramStatistics::default()),
1400        );
1401
1402        cache
1403            .get_flattened_entries_for_tests()
1404            .iter()
1405            .for_each(|(_key, program)| {
1406                if matches!(program.program, ProgramCacheEntryType::Unloaded(_))
1407                    && program.deployment_slot == 0
1408                    && program.effective_slot() == 1
1409                {
1410                    // Test that the usage counter was correctly updated.
1411                    assert_eq!(program.stats.uses.load(Ordering::Relaxed), 10);
1412                }
1413            });
1414    }
1415
1416    #[test_matrix(
1417        (
1418            ProgramCacheEntryType::FailedVerification(get_mock_program_runtime_environment()),
1419            ProgramCacheEntryType::Closed,
1420            ProgramCacheEntryType::Unloaded(get_mock_program_runtime_environment()),
1421            new_loaded_entry(get_mock_program_runtime_environment()),
1422            ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1423        ),
1424        (false, true)
1425    )]
1426    fn test_assign_program_no_second_level(
1427        program: ProgramCacheEntryType,
1428        empty_second_level: bool,
1429    ) {
1430        // Here we test the scenario where no second_level entry exists for the
1431        // program. We expect the `second_level.binary_search_by` to return
1432        // `Err(0)` and we expect the single entry to land in the cache.
1433        let mut cache = ProgramCache::<TestForkGraph>::new(0);
1434        let env = get_mock_program_runtime_environment();
1435        let program_id = Pubkey::new_unique();
1436
1437        if empty_second_level {
1438            // Make the entry already exist, but with an empty second level.
1439            match &mut cache.index {
1440                IndexImplementation::V1 { entries, .. } => {
1441                    entries.insert(program_id, Vec::new());
1442                }
1443            }
1444        }
1445
1446        let entry = Arc::new(ProgramCacheEntry {
1447            program,
1448            account_owner: ProgramCacheEntryOwner::LoaderV3,
1449            deployment_slot: 10,
1450            stats: Arc::default(),
1451            latest_access_slot: AtomicU64::default(),
1452        });
1453
1454        cache.assign_program(&env, program_id, 10, Arc::clone(&entry));
1455
1456        // We should have just the one single entry we just inserted.
1457        let slot_versions = cache.get_slot_versions_for_tests(&program_id);
1458        assert_eq!(slot_versions.len(), 1);
1459        assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &entry));
1460
1461        // Stats should be incremented by 1 to exactly 1.
1462        assert_eq!(cache.stats.insertions.load(Ordering::Relaxed), 1);
1463    }
1464
1465    #[test_matrix(
1466        (
1467            new_closed_entry,
1468            new_builtin_entry,
1469            new_failed_verification_entry,
1470            new_unloaded_entry,
1471            new_loaded_entry,
1472        ),
1473        ((50, 0), (150, 1), (250, 2), (350, 3))
1474    )]
1475    fn test_assign_program_new_insertion_deployment_slot(
1476        new_program: fn(ProgramRuntimeEnvironment) -> ProgramCacheEntryType,
1477        case: (Slot, usize),
1478    ) {
1479        let (deployment_slot, expected_index) = case;
1480        let mut cache = ProgramCache::<TestForkGraph>::new(0);
1481        let env = get_mock_program_runtime_environment();
1482        let program_id = Pubkey::new_unique();
1483
1484        // Entries at distinct deployment slots always coexist.
1485        for slot in [100, 200, 300] {
1486            cache.assign_program(
1487                &env,
1488                program_id,
1489                slot,
1490                new_test_entry_with_owner(
1491                    slot,
1492                    ProgramCacheEntryOwner::LoaderV3,
1493                    new_program(env.clone()),
1494                ),
1495            );
1496        }
1497
1498        // Only the deployment slot differs, so it alone decides the index.
1499        let entry = new_test_entry_with_owner(
1500            deployment_slot,
1501            ProgramCacheEntryOwner::LoaderV3,
1502            new_program(env.clone()),
1503        );
1504        cache.assign_program(&env, program_id, deployment_slot, Arc::clone(&entry));
1505
1506        let slot_versions = cache.get_slot_versions_for_tests(&program_id);
1507        assert_eq!(slot_versions.len(), 4);
1508        assert!(Arc::ptr_eq(
1509            slot_versions.get(expected_index).unwrap(),
1510            &entry
1511        ));
1512        assert_eq!(cache.stats.insertions.load(Ordering::Relaxed), 4);
1513    }
1514
1515    #[test_matrix(
1516        (new_failed_verification_entry, new_unloaded_entry, new_loaded_entry),
1517        (
1518            (ProgramCacheEntryOwner::NativeLoader, 0),
1519            (ProgramCacheEntryOwner::LoaderV2, 1),
1520            (ProgramCacheEntryOwner::LoaderV4, 2),
1521        )
1522    )]
1523    fn test_assign_program_new_insertion_account_owner(
1524        new_program: fn(ProgramRuntimeEnvironment) -> ProgramCacheEntryType,
1525        case: (ProgramCacheEntryOwner, usize),
1526    ) {
1527        let (account_owner, expected_index) = case;
1528        let mut cache = ProgramCache::<TestForkGraph>::new(0);
1529        let env = get_mock_program_runtime_environment();
1530        let program_id = Pubkey::new_unique();
1531
1532        // Entries at the same deployment slot only coexist when their
1533        // environments differ, so give each one its own.
1534        for owner in [
1535            ProgramCacheEntryOwner::LoaderV1,
1536            ProgramCacheEntryOwner::LoaderV3,
1537        ] {
1538            cache.assign_program(
1539                &env,
1540                program_id,
1541                100,
1542                new_test_entry_with_owner(
1543                    100,
1544                    owner,
1545                    new_program(ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock())),
1546                ),
1547            );
1548        }
1549
1550        // None of the environments are the current one, so the account owner
1551        // alone decides the index.
1552        let entry = new_test_entry_with_owner(
1553            100,
1554            account_owner,
1555            new_program(ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock())),
1556        );
1557        cache.assign_program(&env, program_id, 100, Arc::clone(&entry));
1558
1559        let slot_versions = cache.get_slot_versions_for_tests(&program_id);
1560        assert_eq!(slot_versions.len(), 3);
1561        assert!(Arc::ptr_eq(
1562            slot_versions.get(expected_index).unwrap(),
1563            &entry
1564        ));
1565        assert_eq!(cache.stats.insertions.load(Ordering::Relaxed), 3);
1566    }
1567
1568    #[test_matrix(
1569        (new_failed_verification_entry, new_unloaded_entry, new_loaded_entry),
1570        (false, true)
1571    )]
1572    fn test_assign_program_new_insertion_environment(
1573        new_program: fn(ProgramRuntimeEnvironment) -> ProgramCacheEntryType,
1574        entry_uses_current_env: bool,
1575    ) {
1576        let mut cache = ProgramCache::<TestForkGraph>::new(0);
1577        let env = get_mock_program_runtime_environment();
1578        let other_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
1579        let program_id = Pubkey::new_unique();
1580
1581        // Deployment slot and account owner are equal, so entries for the
1582        // current environment sort after those which are not.
1583        let (existing_env, entry_env, expected_index) = if entry_uses_current_env {
1584            (other_env, env.clone(), 1)
1585        } else {
1586            (env.clone(), other_env, 0)
1587        };
1588        cache.assign_program(
1589            &env,
1590            program_id,
1591            100,
1592            new_test_entry_with_owner(
1593                100,
1594                ProgramCacheEntryOwner::LoaderV3,
1595                new_program(existing_env),
1596            ),
1597        );
1598
1599        let entry = new_test_entry_with_owner(
1600            100,
1601            ProgramCacheEntryOwner::LoaderV3,
1602            new_program(entry_env),
1603        );
1604        cache.assign_program(&env, program_id, 100, Arc::clone(&entry));
1605
1606        let slot_versions = cache.get_slot_versions_for_tests(&program_id);
1607        assert_eq!(slot_versions.len(), 2);
1608        assert!(Arc::ptr_eq(
1609            slot_versions.get(expected_index).unwrap(),
1610            &entry
1611        ));
1612        assert_eq!(cache.stats.insertions.load(Ordering::Relaxed), 2);
1613    }
1614
1615    #[test]
1616    #[should_panic(expected = "Unexpected assignment of a DelayVisibility tombstone")]
1617    fn test_assign_program_delay_visibility_tombstone_panics() {
1618        // A tombstone minted by `extract` only ever lives in the batch cache.
1619        // Assigning one into the global cache is a caller error.
1620        let mut cache = ProgramCache::<TestForkGraph>::new(0);
1621        let env = get_mock_program_runtime_environment();
1622        cache.assign_program(
1623            &env,
1624            Pubkey::new_unique(),
1625            100,
1626            Arc::new(ProgramCacheEntry::new_delay_visibility_tombstone(
1627                100,
1628                ProgramCacheEntryOwner::LoaderV3,
1629                Arc::default(),
1630            )),
1631        );
1632    }
1633
1634    #[test]
1635    fn test_fuzz_assign_program_order() {
1636        use rand::prelude::SliceRandom;
1637        const EXPECTED_ENTRIES: [(u64, bool); 5] =
1638            [(1, true), (3, false), (5, true), (9, true), (10, false)];
1639        let mut rng = rand::rng();
1640        let program_id = Pubkey::new_unique();
1641        let env = get_mock_program_runtime_environment();
1642        for _ in 0..1000 {
1643            let mut entries = EXPECTED_ENTRIES.to_vec();
1644            entries.shuffle(&mut rng);
1645            let mut cache = ProgramCache::<TestForkGraph>::new(0);
1646            for (deployment_slot, delay_visibility) in entries {
1647                let entry = Arc::new(if delay_visibility {
1648                    ProgramCacheEntry {
1649                        program: new_loaded_entry(ProgramRuntimeEnvironment::from(
1650                            BuiltinProgram::new_mock(),
1651                        )), // Assign them different environments
1652                        account_owner: ProgramCacheEntryOwner::LoaderV2,
1653                        deployment_slot,
1654                        stats: Arc::default(),
1655                        latest_access_slot: AtomicU64::new(deployment_slot),
1656                    }
1657                } else {
1658                    ProgramCacheEntry::new_failed_verification_tombstone(
1659                        deployment_slot,
1660                        ProgramCacheEntryOwner::LoaderV2,
1661                        ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock()), // Assign them different environments
1662                    )
1663                });
1664                assert!(!cache.assign_program(&env, program_id, deployment_slot, entry));
1665            }
1666            for ((deployment_slot, delay_visibility), entry) in EXPECTED_ENTRIES
1667                .iter()
1668                .zip(cache.get_slot_versions_for_tests(&program_id).iter())
1669            {
1670                assert_eq!(entry.deployment_slot, *deployment_slot);
1671                assert_eq!(
1672                    entry.effective_slot(),
1673                    deployment_slot.saturating_add(*delay_visibility as u64)
1674                );
1675            }
1676        }
1677    }
1678
1679    #[test_matrix(
1680        (
1681            ProgramCacheEntryType::FailedVerification(get_mock_program_runtime_environment()),
1682            new_loaded_entry(get_mock_program_runtime_environment()),
1683        ),
1684        (
1685            ProgramCacheEntryType::FailedVerification(get_mock_program_runtime_environment()),
1686            ProgramCacheEntryType::Closed,
1687            ProgramCacheEntryType::Unloaded(get_mock_program_runtime_environment()),
1688            new_loaded_entry(get_mock_program_runtime_environment()),
1689            ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1690        )
1691    )]
1692    #[test_matrix(
1693        ProgramCacheEntryType::Closed,
1694        (
1695            ProgramCacheEntryType::FailedVerification(get_mock_program_runtime_environment()),
1696            ProgramCacheEntryType::Closed,
1697            new_loaded_entry(get_mock_program_runtime_environment()),
1698            ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1699        )
1700    )]
1701    #[test_matrix(
1702        ProgramCacheEntryType::Unloaded(get_mock_program_runtime_environment()),
1703        (
1704            ProgramCacheEntryType::Closed,
1705            ProgramCacheEntryType::Unloaded(get_mock_program_runtime_environment()),
1706            ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1707        )
1708    )]
1709    #[test_matrix(
1710        (ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),),
1711        (
1712            ProgramCacheEntryType::FailedVerification(get_mock_program_runtime_environment()),
1713            ProgramCacheEntryType::Closed,
1714            ProgramCacheEntryType::Unloaded(get_mock_program_runtime_environment()),
1715            new_loaded_entry(get_mock_program_runtime_environment()),
1716        )
1717    )]
1718    #[should_panic(expected = "Unexpected replacement of an entry")]
1719    fn test_assign_program_failure(old: ProgramCacheEntryType, new: ProgramCacheEntryType) {
1720        let mut cache = ProgramCache::<TestForkGraph>::new(0);
1721        let env = get_mock_program_runtime_environment();
1722        let program_id = Pubkey::new_unique();
1723        assert!(!cache.assign_program(
1724            &env,
1725            program_id,
1726            10,
1727            Arc::new(ProgramCacheEntry {
1728                program: old,
1729                account_owner: ProgramCacheEntryOwner::LoaderV2,
1730                deployment_slot: 10,
1731                stats: Arc::default(),
1732                latest_access_slot: AtomicU64::default(),
1733            }),
1734        ));
1735        cache.assign_program(
1736            &env,
1737            program_id,
1738            10,
1739            Arc::new(ProgramCacheEntry {
1740                program: new,
1741                account_owner: ProgramCacheEntryOwner::LoaderV2,
1742                deployment_slot: 10,
1743                stats: Arc::default(),
1744                latest_access_slot: AtomicU64::default(),
1745            }),
1746        );
1747    }
1748
1749    #[test_matrix(
1750        ProgramCacheEntryType::Unloaded(get_mock_program_runtime_environment()),
1751        (
1752            new_loaded_entry(get_mock_program_runtime_environment()),
1753            ProgramCacheEntryType::FailedVerification(get_mock_program_runtime_environment()),
1754        )
1755    )]
1756    #[test_case(
1757        ProgramCacheEntryType::Closed,
1758        ProgramCacheEntryType::Unloaded(get_mock_program_runtime_environment())
1759    )]
1760    #[test_case(
1761        ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1762        ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock())
1763    )]
1764    fn test_assign_program_success(old: ProgramCacheEntryType, new: ProgramCacheEntryType) {
1765        let mut cache = ProgramCache::<TestForkGraph>::new(0);
1766        let env = get_mock_program_runtime_environment();
1767        let program_id = Pubkey::new_unique();
1768        assert!(!cache.assign_program(
1769            &env,
1770            program_id,
1771            10,
1772            Arc::new(ProgramCacheEntry {
1773                program: old,
1774                account_owner: ProgramCacheEntryOwner::LoaderV2,
1775                deployment_slot: 10,
1776                stats: Arc::default(),
1777                latest_access_slot: AtomicU64::default(),
1778            }),
1779        ));
1780        assert!(!cache.assign_program(
1781            &env,
1782            program_id,
1783            10,
1784            Arc::new(ProgramCacheEntry {
1785                program: new,
1786                account_owner: ProgramCacheEntryOwner::LoaderV2,
1787                deployment_slot: 10,
1788                stats: Arc::default(),
1789                latest_access_slot: AtomicU64::default(),
1790            }),
1791        ));
1792    }
1793
1794    #[test]
1795    fn test_assign_program_removes_entries_in_same_slot() {
1796        let mut cache = ProgramCache::<TestForkGraph>::new(0);
1797        let env = get_mock_program_runtime_environment();
1798        let program_id = Pubkey::new_unique();
1799        let closed_other_slot = Arc::new(ProgramCacheEntry {
1800            program: ProgramCacheEntryType::Closed,
1801            account_owner: ProgramCacheEntryOwner::LoaderV2,
1802            deployment_slot: 9,
1803            stats: Arc::default(),
1804            latest_access_slot: AtomicU64::default(),
1805        });
1806        let closed_current_slot = Arc::new(ProgramCacheEntry {
1807            program: ProgramCacheEntryType::Closed,
1808            account_owner: ProgramCacheEntryOwner::LoaderV2,
1809            deployment_slot: 10,
1810            stats: Arc::default(),
1811            latest_access_slot: AtomicU64::default(),
1812        });
1813        let loaded_entry_current_env = Arc::new(ProgramCacheEntry {
1814            program: ProgramCacheEntryType::Unloaded(get_mock_program_runtime_environment()),
1815            account_owner: ProgramCacheEntryOwner::LoaderV2,
1816            deployment_slot: 10,
1817            stats: Arc::default(),
1818            latest_access_slot: AtomicU64::default(),
1819        });
1820        let loaded_entry_upcoming_env = Arc::new(ProgramCacheEntry {
1821            program: ProgramCacheEntryType::Unloaded(ProgramRuntimeEnvironment::from(
1822                BuiltinProgram::new_mock(),
1823            )),
1824            account_owner: ProgramCacheEntryOwner::LoaderV2,
1825            deployment_slot: 10,
1826            stats: Arc::default(),
1827            latest_access_slot: AtomicU64::default(),
1828        });
1829        assert!(!cache.assign_program(&env, program_id, 9, closed_other_slot.clone()));
1830        assert!(!cache.assign_program(&env, program_id, 10, closed_current_slot));
1831        assert!(!cache.assign_program(&env, program_id, 10, loaded_entry_upcoming_env.clone()));
1832        assert!(!cache.assign_program(&env, program_id, 10, loaded_entry_current_env.clone()));
1833        // Only the conflicting entry in the same slot which does not have a different environment is removed
1834        assert_eq!(
1835            cache.get_slot_versions_for_tests(&program_id),
1836            &[
1837                closed_other_slot,
1838                loaded_entry_current_env,
1839                loaded_entry_upcoming_env
1840            ]
1841        );
1842    }
1843
1844    #[test]
1845    fn test_assign_program_reload_merges_statistics() {
1846        let mut cache = ProgramCache::<TestForkGraph>::new(0);
1847        let env = get_mock_program_runtime_environment();
1848        let program_id = Pubkey::new_unique();
1849
1850        let stats = Arc::new(ProgramStatistics {
1851            uses: 1.into(),
1852            compilations: 2.into(),
1853            total_compilation_time_us: 3.into(),
1854            compilation_time_ema: 100.into(),
1855            jit_invocations: 4.into(),
1856            total_jit_execution_time_us: 5.into(),
1857            jit_execution_time_ema: 200.into(),
1858            interpreted_invocations: 6.into(),
1859            total_interpretation_time_us: 7.into(),
1860            interpretation_time_ema: 300.into(),
1861        });
1862        let unloaded = Arc::new(ProgramCacheEntry {
1863            program: ProgramCacheEntryType::Unloaded(env.clone()),
1864            account_owner: ProgramCacheEntryOwner::LoaderV3,
1865            deployment_slot: 100,
1866            stats: Arc::clone(&stats),
1867            latest_access_slot: AtomicU64::default(),
1868        });
1869        cache.assign_program(&env, program_id, 100, unloaded);
1870
1871        // `Unloaded` -> `Loaded` matches the existing entry, so it is a reload.
1872        let loaded = Arc::new(ProgramCacheEntry {
1873            program: new_loaded_entry(env.clone()),
1874            account_owner: ProgramCacheEntryOwner::LoaderV3,
1875            deployment_slot: 100,
1876            stats: Arc::default(), // <-- Empty stats
1877            latest_access_slot: AtomicU64::default(),
1878        });
1879        cache.assign_program(&env, program_id, 100, Arc::clone(&loaded));
1880
1881        assert_eq!(cache.stats.insertions.load(Ordering::Relaxed), 1);
1882        assert_eq!(cache.stats.reloads.load(Ordering::Relaxed), 1);
1883
1884        let merged = &loaded.stats;
1885        let ord = Ordering::Relaxed;
1886        assert_eq!(merged.uses.load(ord), stats.uses.load(ord));
1887        assert_eq!(merged.compilations.load(ord), stats.compilations.load(ord));
1888        assert_eq!(
1889            merged.total_compilation_time_us.load(ord),
1890            stats.total_compilation_time_us.load(ord)
1891        );
1892        assert_eq!(
1893            merged.jit_invocations.load(ord),
1894            stats.jit_invocations.load(ord)
1895        );
1896        assert_eq!(
1897            merged.total_jit_execution_time_us.load(ord),
1898            stats.total_jit_execution_time_us.load(ord)
1899        );
1900        assert_eq!(
1901            merged.interpreted_invocations.load(ord),
1902            stats.interpreted_invocations.load(ord)
1903        );
1904        assert_eq!(
1905            merged.total_interpretation_time_us.load(ord),
1906            stats.total_interpretation_time_us.load(ord)
1907        );
1908
1909        // The moving averages are weighted against the empty ones of the new
1910        // entry, which halves them.
1911        const EMA_DIVISOR: u64 = 2;
1912        assert_eq!(
1913            merged.compilation_time_ema.load(ord),
1914            stats
1915                .compilation_time_ema
1916                .load(ord)
1917                .wrapping_div(EMA_DIVISOR)
1918        );
1919        assert_eq!(
1920            merged.jit_execution_time_ema.load(ord),
1921            stats
1922                .jit_execution_time_ema
1923                .load(ord)
1924                .wrapping_div(EMA_DIVISOR)
1925        );
1926        assert_eq!(
1927            merged.interpretation_time_ema.load(ord),
1928            stats
1929                .interpretation_time_ema
1930                .load(ord)
1931                .wrapping_div(EMA_DIVISOR)
1932        );
1933    }
1934
1935    #[test]
1936    fn test_tombstone() {
1937        let env = get_mock_program_runtime_environment();
1938        let tombstone = ProgramCacheEntry::new_failed_verification_tombstone(
1939            0,
1940            ProgramCacheEntryOwner::LoaderV2,
1941            env.clone(),
1942        );
1943        assert_matches!(
1944            tombstone.program,
1945            ProgramCacheEntryType::FailedVerification(_)
1946        );
1947        assert!(tombstone.is_tombstone());
1948        assert_eq!(tombstone.deployment_slot, 0);
1949        assert_eq!(tombstone.effective_slot(), 0);
1950
1951        let tombstone =
1952            ProgramCacheEntry::new_closed_tombstone(100, ProgramCacheEntryOwner::LoaderV2);
1953        assert_matches!(tombstone.program, ProgramCacheEntryType::Closed);
1954        assert!(tombstone.is_tombstone());
1955        assert_eq!(tombstone.deployment_slot, 100);
1956        assert_eq!(tombstone.effective_slot(), 100);
1957
1958        let mut cache = ProgramCache::<TestForkGraph>::new(0);
1959        let program1 = Pubkey::new_unique();
1960        let tombstone = set_failed_verification_tombstone(&mut cache, program1, 10, env.clone());
1961        let slot_versions = cache.get_slot_versions_for_tests(&program1);
1962        assert_eq!(slot_versions.len(), 1);
1963        assert!(slot_versions.first().unwrap().is_tombstone());
1964        assert_eq!(tombstone.deployment_slot, 10);
1965        assert_eq!(tombstone.effective_slot(), 10);
1966
1967        // Add a program at slot 50, and a tombstone for the program at slot 60
1968        let program2 = Pubkey::new_unique();
1969        cache.assign_program(&env, program2, 50, new_test_builtin_entry(50));
1970        let slot_versions = cache.get_slot_versions_for_tests(&program2);
1971        assert_eq!(slot_versions.len(), 1);
1972        assert!(!slot_versions.first().unwrap().is_tombstone());
1973
1974        let tombstone = set_failed_verification_tombstone(&mut cache, program2, 60, env);
1975        let slot_versions = cache.get_slot_versions_for_tests(&program2);
1976        assert_eq!(slot_versions.len(), 2);
1977        assert!(!slot_versions.first().unwrap().is_tombstone());
1978        assert!(slot_versions.get(1).unwrap().is_tombstone());
1979        assert!(tombstone.is_tombstone());
1980        assert_eq!(tombstone.deployment_slot, 60);
1981        assert_eq!(tombstone.effective_slot(), 60);
1982    }
1983
1984    struct TestForkGraph {
1985        relation: BlockRelation,
1986    }
1987    impl ForkGraph for TestForkGraph {
1988        fn relationship(&self, _a: Slot, _b: Slot) -> BlockRelation {
1989            self.relation
1990        }
1991    }
1992
1993    #[test]
1994    fn test_prune_empty() {
1995        let mut cache = ProgramCache::<TestForkGraph>::new(0);
1996        let fork_graph = Arc::new(RwLock::new(TestForkGraph {
1997            relation: BlockRelation::Unrelated,
1998        }));
1999
2000        cache.set_fork_graph(Arc::downgrade(&fork_graph));
2001
2002        cache.prune(0, None, &fork_graph.read().unwrap());
2003        assert!(cache.get_flattened_entries_for_tests().is_empty());
2004
2005        cache.prune(10, None, &fork_graph.read().unwrap());
2006        assert!(cache.get_flattened_entries_for_tests().is_empty());
2007
2008        let mut cache = ProgramCache::<TestForkGraph>::new(0);
2009        let fork_graph = Arc::new(RwLock::new(TestForkGraph {
2010            relation: BlockRelation::Ancestor,
2011        }));
2012
2013        cache.set_fork_graph(Arc::downgrade(&fork_graph));
2014
2015        cache.prune(0, None, &fork_graph.read().unwrap());
2016        assert!(cache.get_flattened_entries_for_tests().is_empty());
2017
2018        cache.prune(10, None, &fork_graph.read().unwrap());
2019        assert!(cache.get_flattened_entries_for_tests().is_empty());
2020
2021        let mut cache = ProgramCache::<TestForkGraph>::new(0);
2022        let fork_graph = Arc::new(RwLock::new(TestForkGraph {
2023            relation: BlockRelation::Descendant,
2024        }));
2025
2026        cache.set_fork_graph(Arc::downgrade(&fork_graph));
2027
2028        cache.prune(0, None, &fork_graph.read().unwrap());
2029        assert!(cache.get_flattened_entries_for_tests().is_empty());
2030
2031        cache.prune(10, None, &fork_graph.read().unwrap());
2032        assert!(cache.get_flattened_entries_for_tests().is_empty());
2033
2034        let mut cache = ProgramCache::<TestForkGraph>::new(0);
2035        let fork_graph = Arc::new(RwLock::new(TestForkGraph {
2036            relation: BlockRelation::Unknown,
2037        }));
2038        cache.set_fork_graph(Arc::downgrade(&fork_graph));
2039
2040        cache.prune(0, None, &fork_graph.read().unwrap());
2041        assert!(cache.get_flattened_entries_for_tests().is_empty());
2042
2043        cache.prune(10, None, &fork_graph.read().unwrap());
2044        assert!(cache.get_flattened_entries_for_tests().is_empty());
2045    }
2046
2047    #[test]
2048    fn test_prune_removes_programs_with_no_entries() {
2049        let (mut cache, fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Unknown);
2050        let env = get_mock_program_runtime_environment();
2051        let emptied = Pubkey::new_unique();
2052        cache.assign_program(
2053            &env,
2054            emptied,
2055            100,
2056            new_test_entry_with_owner(
2057                100,
2058                ProgramCacheEntryOwner::LoaderV3,
2059                new_loaded_entry(env.clone()),
2060            ),
2061        );
2062
2063        // The entry is dropped, and the key goes with it.
2064        cache.prune(50, None, &fork_graph.read().unwrap());
2065        match &cache.index {
2066            IndexImplementation::V1 { entries, .. } => assert!(!entries.contains_key(&emptied)),
2067        }
2068        assert_eq!(cache.stats.empty_entries.load(Ordering::Relaxed), 1);
2069        assert_eq!(cache.stats.prunes_orphan.load(Ordering::Relaxed), 1);
2070    }
2071
2072    #[test]
2073    fn test_prune_across_the_root() {
2074        // Fork graph created for the test
2075        //                30 - 50 - 70
2076        //
2077        // One program with entries on both sides of the new root.
2078        // Both the ancestor and the descendant are kept.
2079        let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
2080        let mut fork_graph = TestForkGraphSpecific::default();
2081        fork_graph.insert_fork(&[30, 50, 70]);
2082        let fork_graph = Arc::new(RwLock::new(fork_graph));
2083        cache.set_fork_graph(Arc::downgrade(&fork_graph));
2084
2085        let env = get_mock_program_runtime_environment();
2086        let program_id = Pubkey::new_unique();
2087        let below = new_test_entry_with_owner(
2088            30,
2089            ProgramCacheEntryOwner::LoaderV3,
2090            new_loaded_entry(env.clone()),
2091        );
2092        let above = new_test_entry_with_owner(
2093            70,
2094            ProgramCacheEntryOwner::LoaderV3,
2095            new_loaded_entry(env.clone()),
2096        );
2097        cache.assign_program(&env, program_id, 30, Arc::clone(&below));
2098        cache.assign_program(&env, program_id, 70, Arc::clone(&above));
2099
2100        let slot_versions = cache.get_slot_versions_for_tests(&program_id);
2101        assert_eq!(slot_versions.len(), 2);
2102        assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &below));
2103        assert!(Arc::ptr_eq(slot_versions.get(1).unwrap(), &above));
2104
2105        cache.prune(50, None, &fork_graph.read().unwrap());
2106
2107        // Both survive, and in the order they were in.
2108        let slot_versions = cache.get_slot_versions_for_tests(&program_id);
2109        assert_eq!(slot_versions.len(), 2);
2110        assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &below));
2111        assert!(Arc::ptr_eq(slot_versions.get(1).unwrap(), &above));
2112    }
2113
2114    #[test]
2115    fn test_prune_across_the_root_ancestors() {
2116        // Fork graph created for the test
2117        //                10 - 20 - 30 - 50 - 70
2118        //
2119        // Same as above, with more entries deployed before the new root.
2120        // Only the newest of those is the first ancestor.
2121        let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
2122        let mut fork_graph = TestForkGraphSpecific::default();
2123        fork_graph.insert_fork(&[10, 20, 30, 50, 70]);
2124        let fork_graph = Arc::new(RwLock::new(fork_graph));
2125        cache.set_fork_graph(Arc::downgrade(&fork_graph));
2126
2127        let env = get_mock_program_runtime_environment();
2128        let program_id = Pubkey::new_unique();
2129        let oldest = new_test_entry_with_owner(
2130            10,
2131            ProgramCacheEntryOwner::LoaderV3,
2132            new_loaded_entry(env.clone()),
2133        );
2134        let older = new_test_entry_with_owner(
2135            20,
2136            ProgramCacheEntryOwner::LoaderV3,
2137            new_loaded_entry(env.clone()),
2138        );
2139        let below = new_test_entry_with_owner(
2140            30,
2141            ProgramCacheEntryOwner::LoaderV3,
2142            new_loaded_entry(env.clone()),
2143        );
2144        let above = new_test_entry_with_owner(
2145            70,
2146            ProgramCacheEntryOwner::LoaderV3,
2147            new_loaded_entry(env.clone()),
2148        );
2149        cache.assign_program(&env, program_id, 10, Arc::clone(&oldest));
2150        cache.assign_program(&env, program_id, 20, Arc::clone(&older));
2151        cache.assign_program(&env, program_id, 30, Arc::clone(&below));
2152        cache.assign_program(&env, program_id, 70, Arc::clone(&above));
2153
2154        let slot_versions = cache.get_slot_versions_for_tests(&program_id);
2155        assert_eq!(slot_versions.len(), 4);
2156        assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &oldest));
2157        assert!(Arc::ptr_eq(slot_versions.get(1).unwrap(), &older));
2158        assert!(Arc::ptr_eq(slot_versions.get(2).unwrap(), &below));
2159        assert!(Arc::ptr_eq(slot_versions.get(3).unwrap(), &above));
2160
2161        cache.prune(50, None, &fork_graph.read().unwrap());
2162
2163        // The entries at 10 and 20 have been redeployed over by the one at 30,
2164        // so they go, and nothing was on another environment to exempt them.
2165        let slot_versions = cache.get_slot_versions_for_tests(&program_id);
2166        assert_eq!(slot_versions.len(), 2);
2167        assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &below));
2168        assert!(Arc::ptr_eq(slot_versions.get(1).unwrap(), &above));
2169        assert_eq!(cache.stats.prunes_orphan.load(Ordering::Relaxed), 2);
2170    }
2171
2172    #[test]
2173    fn test_prune_entry_older_than_root() {
2174        // Fork graph created for the test
2175        //                5  ?  10  ?  20
2176        //                ^     ^^     ^^
2177        //                |     |      the new root
2178        //                |     the old root
2179        //                the entry is deployed here
2180        //
2181        // The graph answers `BlockRelation::Unknown` for every pair, so
2182        // nothing here is related to anything else.
2183        //
2184        // Here we want to test that an entry the graph cannot place on the
2185        // querying fork is kept anyway, purely because it was deployed before
2186        // the root. Therefore, nothing is pruned and no orphan is counted.
2187        let (mut cache, fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Unknown);
2188        let env = get_mock_program_runtime_environment();
2189        let program_id = Pubkey::new_unique();
2190        let entry = new_test_entry_with_owner(
2191            5,
2192            ProgramCacheEntryOwner::LoaderV3,
2193            new_loaded_entry(env.clone()),
2194        );
2195        cache.assign_program(&env, program_id, 5, Arc::clone(&entry));
2196        cache.latest_root_slot = 10;
2197
2198        let slot_versions = cache.get_slot_versions_for_tests(&program_id);
2199        assert_eq!(slot_versions.len(), 1);
2200        assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &entry));
2201
2202        cache.prune(20, None, &fork_graph.read().unwrap());
2203
2204        // `Unknown` means the graph cannot say the entry belongs to this fork,
2205        // but the `deployment_slot <= latest_root_slot` fallback keeps it
2206        // regardless.
2207        let slot_versions = cache.get_slot_versions_for_tests(&program_id);
2208        assert_eq!(slot_versions.len(), 1);
2209        assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &entry));
2210        assert_eq!(cache.stats.prunes_orphan.load(Ordering::Relaxed), 0);
2211    }
2212
2213    #[test]
2214    fn test_prune_orphan_newer_than_root() {
2215        // Fork graph created for the test
2216        //                50  ?  100
2217        //                ^^     ^^^
2218        //                |      the entry is deployed here
2219        //                the new root
2220        //
2221        // Here we want to test the other side of the root from the test above:
2222        // an entry deployed past it, which the graph cannot place either.
2223        // Therefore it is pruned, where the one behind the root was kept.
2224        let (mut cache, fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Unknown);
2225        let env = get_mock_program_runtime_environment();
2226        let program_id = Pubkey::new_unique();
2227        let orphan = new_test_entry_with_owner(
2228            100,
2229            ProgramCacheEntryOwner::LoaderV3,
2230            new_loaded_entry(env.clone()),
2231        );
2232        cache.assign_program(&env, program_id, 100, Arc::clone(&orphan));
2233
2234        let slot_versions = cache.get_slot_versions_for_tests(&program_id);
2235        assert_eq!(slot_versions.len(), 1);
2236        assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &orphan));
2237
2238        cache.prune(50, None, &fork_graph.read().unwrap());
2239
2240        // Past the root there is no `deployment_slot <= latest_root_slot`
2241        // fallback to keep it, so the entry the graph cannot place goes.
2242        assert!(cache.get_slot_versions_for_tests(&program_id).is_empty());
2243        assert_eq!(cache.stats.prunes_orphan.load(Ordering::Relaxed), 1);
2244    }
2245
2246    #[test]
2247    fn test_prune_tombstones() {
2248        let env = get_mock_program_runtime_environment();
2249        let fork_graph = Arc::new(RwLock::new(TestForkGraph {
2250            relation: BlockRelation::Ancestor,
2251        }));
2252
2253        let program1 = Pubkey::new_unique();
2254        let entries = [
2255            Arc::new(ProgramCacheEntry::new_unloaded(
2256                20,
2257                ProgramCacheEntryOwner::LoaderV3,
2258                ProgramRuntimeEnvironment::clone(&env),
2259            )),
2260            Arc::new(ProgramCacheEntry::new_closed_tombstone(
2261                20,
2262                ProgramCacheEntryOwner::LoaderV3,
2263            )),
2264            Arc::new(ProgramCacheEntry::new_failed_verification_tombstone(
2265                20,
2266                ProgramCacheEntryOwner::LoaderV3,
2267                ProgramRuntimeEnvironment::clone(&env),
2268            )),
2269        ];
2270        for entry in &entries {
2271            let mut cache = ProgramCache::<TestForkGraph>::new(0);
2272            cache.set_fork_graph(Arc::downgrade(&fork_graph));
2273            // Test that multiple entries prevent pruning
2274            cache.assign_program(&env, program1, 10, new_test_entry(10));
2275            cache.assign_program(&env, program1, entry.deployment_slot, Arc::clone(entry));
2276            cache.prune(
2277                MAX_TOMBSTONE_AGE_IN_SLOTS,
2278                None,
2279                &fork_graph.read().unwrap(),
2280            );
2281            let slot_versions = cache.get_slot_versions_for_tests(&program1);
2282            assert_eq!(slot_versions, std::slice::from_ref(entry));
2283            // Test that latest_access_slot prevents pruning
2284            cache.prune(
2285                MAX_TOMBSTONE_AGE_IN_SLOTS
2286                    .saturating_add(entry.latest_access_slot.load(Ordering::Relaxed)),
2287                None,
2288                &fork_graph.read().unwrap(),
2289            );
2290            let slot_versions = cache.get_slot_versions_for_tests(&program1);
2291            assert_eq!(slot_versions, std::slice::from_ref(entry));
2292            // Test that exeeding latest_access_slot + MAX_TOMBSTONE_AGE_IN_SLOTS prunes
2293            cache.prune(
2294                MAX_TOMBSTONE_AGE_IN_SLOTS
2295                    .saturating_add(entry.latest_access_slot.load(Ordering::Relaxed))
2296                    .saturating_add(1),
2297                None,
2298                &fork_graph.read().unwrap(),
2299            );
2300            assert!(cache.get_flattened_entries_for_tests().is_empty());
2301        }
2302    }
2303
2304    #[test]
2305    fn test_prune_tombstone_first_ancestor_takes_the_rest() {
2306        // Fork graph created for the test
2307        //                50 - 60 - 100 - 200
2308        //                          ^^^
2309        //                          the program is closed here
2310        //
2311        // Here we want to test that the pruning step correctly sees that a
2312        // closure - a `Closed` tombstone - is being rooted. Therefore, nothing
2313        // else should be retained in the cache for this entry.
2314        let (mut cache, fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
2315        let env = get_mock_program_runtime_environment();
2316        let other_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
2317        let program_id = Pubkey::new_unique();
2318        let on_other_env = new_test_entry_with_owner(
2319            50,
2320            ProgramCacheEntryOwner::LoaderV3,
2321            new_loaded_entry(other_env.clone()),
2322        );
2323        let on_env = new_test_entry_with_owner(
2324            60,
2325            ProgramCacheEntryOwner::LoaderV3,
2326            new_loaded_entry(env.clone()),
2327        );
2328        let closed = new_test_entry_with_owner(
2329            100,
2330            ProgramCacheEntryOwner::LoaderV3,
2331            new_closed_entry(env.clone()),
2332        );
2333        cache.assign_program(&other_env, program_id, 50, Arc::clone(&on_other_env));
2334        cache.assign_program(&env, program_id, 60, Arc::clone(&on_env));
2335        cache.assign_program(&env, program_id, 100, Arc::clone(&closed));
2336
2337        let slot_versions = cache.get_slot_versions_for_tests(&program_id);
2338        assert_eq!(slot_versions.len(), 3);
2339        assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &on_other_env));
2340        assert!(Arc::ptr_eq(slot_versions.get(1).unwrap(), &on_env));
2341        assert!(Arc::ptr_eq(slot_versions.get(2).unwrap(), &closed));
2342
2343        cache.prune(200, None, &fork_graph.read().unwrap());
2344
2345        // The newest entry deployed before the root is the tombstone, so
2346        // `first_ancestor_env` is `None`. The env-based exemption for entries
2347        // behind the tombstone is unreachable, so they all get pruned.
2348        let slot_versions = cache.get_slot_versions_for_tests(&program_id);
2349        assert_eq!(slot_versions.len(), 1);
2350        assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &closed));
2351        assert_eq!(cache.stats.prunes_orphan.load(Ordering::Relaxed), 2);
2352    }
2353
2354    #[test]
2355    fn test_prune_tombstone_newer_than_root_keeps_the_rest() {
2356        // Fork graph created for the test
2357        //                50 - 60 - 100 - 101
2358        //                                ^^^
2359        //                                the program is closed here
2360        //
2361        // Here we want to test that the pruning step does not see a closure -
2362        // a `Closed` tombstone - being rooted, since it lands after the new
2363        // root. Therefore, everything else should be retained in the cache
2364        // for this program.
2365        let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
2366        let mut fork_graph = TestForkGraphSpecific::default();
2367        fork_graph.insert_fork(&[50, 60, 100, 101]);
2368        let fork_graph = Arc::new(RwLock::new(fork_graph));
2369        cache.set_fork_graph(Arc::downgrade(&fork_graph));
2370
2371        let env = get_mock_program_runtime_environment();
2372        let other_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
2373        let program_id = Pubkey::new_unique();
2374        let on_other_env = new_test_entry_with_owner(
2375            50,
2376            ProgramCacheEntryOwner::LoaderV3,
2377            new_loaded_entry(other_env.clone()),
2378        );
2379        let on_env = new_test_entry_with_owner(
2380            60,
2381            ProgramCacheEntryOwner::LoaderV3,
2382            new_loaded_entry(env.clone()),
2383        );
2384        let closed = new_test_entry_with_owner(
2385            101,
2386            ProgramCacheEntryOwner::LoaderV3,
2387            new_closed_entry(env.clone()),
2388        );
2389        cache.assign_program(&other_env, program_id, 50, Arc::clone(&on_other_env));
2390        cache.assign_program(&env, program_id, 60, Arc::clone(&on_env));
2391        cache.assign_program(&env, program_id, 101, Arc::clone(&closed));
2392
2393        let slot_versions = cache.get_slot_versions_for_tests(&program_id);
2394        assert_eq!(slot_versions.len(), 3);
2395        assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &on_other_env));
2396        assert!(Arc::ptr_eq(slot_versions.get(1).unwrap(), &on_env));
2397        assert!(Arc::ptr_eq(slot_versions.get(2).unwrap(), &closed));
2398
2399        cache.prune(100, None, &fork_graph.read().unwrap());
2400
2401        // The tombstone is kept as a descendant of the root, and never reaches
2402        // the arm which sets `first_ancestor_env`. So the entry at 60 is the
2403        // first ancestor, the one at 50 is exempt for being on another
2404        // environment, and nothing is pruned.
2405        let slot_versions = cache.get_slot_versions_for_tests(&program_id);
2406        assert_eq!(slot_versions.len(), 3);
2407        assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &on_other_env));
2408        assert!(Arc::ptr_eq(slot_versions.get(1).unwrap(), &on_env));
2409        assert!(Arc::ptr_eq(slot_versions.get(2).unwrap(), &closed));
2410        assert_eq!(cache.stats.prunes_orphan.load(Ordering::Relaxed), 0);
2411    }
2412
2413    #[test]
2414    fn test_prune_with_two_environments_before_epoch_boundary() {
2415        let mut cache = ProgramCache::<TestForkGraph>::new(0);
2416        let env = get_mock_program_runtime_environment();
2417        let new_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
2418        let fork_graph = Arc::new(RwLock::new(TestForkGraph {
2419            relation: BlockRelation::Ancestor,
2420        }));
2421        cache.set_fork_graph(Arc::downgrade(&fork_graph));
2422
2423        let program1 = Pubkey::new_unique();
2424        cache.assign_program(&env, program1, 10, new_test_entry(10));
2425        let updated_program = Arc::new(ProgramCacheEntry {
2426            program: new_loaded_entry(new_env.clone()),
2427            deployment_slot: 20,
2428            ..Default::default()
2429        });
2430        cache.assign_program(&env, program1, 20, updated_program.clone());
2431
2432        // Test that there are 2 entries for the program
2433        assert_eq!(cache.get_slot_versions_for_tests(&program1).len(), 2);
2434
2435        cache.prune(21, None, &fork_graph.read().unwrap());
2436
2437        // Test that prune didn't remove the entry, since environments are different.
2438        assert_eq!(cache.get_slot_versions_for_tests(&program1).len(), 2);
2439    }
2440
2441    #[test]
2442    fn test_prune_with_two_environments_after_epoch_boundary() {
2443        let mut cache = ProgramCache::<TestForkGraph>::new(0);
2444        let env = get_mock_program_runtime_environment();
2445        let new_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
2446        let fork_graph = Arc::new(RwLock::new(TestForkGraph {
2447            relation: BlockRelation::Ancestor,
2448        }));
2449        cache.set_fork_graph(Arc::downgrade(&fork_graph));
2450        let program1 = Pubkey::new_unique();
2451
2452        let old_program_old_env = Arc::new(ProgramCacheEntry {
2453            program: new_loaded_entry(env.clone()),
2454            deployment_slot: 10,
2455            ..Default::default()
2456        });
2457        let old_program_new_env = Arc::new(ProgramCacheEntry {
2458            program: new_loaded_entry(new_env.clone()),
2459            deployment_slot: 10,
2460            ..Default::default()
2461        });
2462        let new_program_old_env = Arc::new(ProgramCacheEntry {
2463            program: new_loaded_entry(env.clone()),
2464            deployment_slot: 20,
2465            ..Default::default()
2466        });
2467        cache.assign_program(&env, program1, 10, old_program_old_env.clone());
2468        cache.assign_program(&env, program1, 10, old_program_new_env.clone());
2469        cache.assign_program(&env, program1, 20, new_program_old_env.clone());
2470        let slot_versions = cache.get_slot_versions_for_tests(&program1);
2471        assert_eq!(
2472            &slot_versions,
2473            &[
2474                old_program_new_env.clone(),
2475                old_program_old_env.clone(),
2476                new_program_old_env.clone(),
2477            ]
2478        );
2479
2480        cache.prune(21, Some(new_env.clone()), &fork_graph.read().unwrap());
2481        let slot_versions = cache.get_slot_versions_for_tests(&program1);
2482        assert_eq!(&slot_versions, &[old_program_new_env]);
2483        assert!(matches!(
2484            &slot_versions.first().unwrap().program,
2485            ProgramCacheEntryType::Loaded(_)
2486        ));
2487    }
2488
2489    #[test_matrix(
2490        (
2491            new_closed_entry,
2492            new_builtin_entry,
2493            new_failed_verification_entry,
2494            new_unloaded_entry,
2495            new_loaded_entry,
2496        ),
2497        (false, true)
2498    )]
2499    fn test_prune_environment_sweep_by_entry_type(
2500        new_program: fn(ProgramRuntimeEnvironment) -> ProgramCacheEntryType,
2501        on_new_environment: bool,
2502    ) {
2503        // Fork graph created for the test
2504        //                40 - 50
2505        //
2506        // The entry is deployed after the root the sweep runs at, so the fork
2507        // graph keeps it and the environment decides the rest.
2508        //
2509        // Here we want to test which entry types the sweep can reach.
2510        // Therefore only one which carries an environment, and not the
2511        // incoming one, is taken - and taken outright, rather than unloaded.
2512        let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
2513        let mut fork_graph = TestForkGraphSpecific::default();
2514        fork_graph.insert_fork(&[40, 50]);
2515        let fork_graph = Arc::new(RwLock::new(fork_graph));
2516        cache.set_fork_graph(Arc::downgrade(&fork_graph));
2517
2518        let env = get_mock_program_runtime_environment();
2519        let new_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
2520        let entry_env = if on_new_environment {
2521            new_env.clone()
2522        } else {
2523            env.clone()
2524        };
2525        let program_id = Pubkey::new_unique();
2526        let entry = new_test_entry_with_owner(
2527            50,
2528            ProgramCacheEntryOwner::LoaderV3,
2529            new_program(entry_env.clone()),
2530        );
2531        let carries_an_environment = entry.program.get_environment().is_some();
2532        cache.assign_program(&entry_env, program_id, 50, Arc::clone(&entry));
2533
2534        let slot_versions = cache.get_slot_versions_for_tests(&program_id);
2535        assert_eq!(slot_versions.len(), 1);
2536        assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &entry));
2537
2538        cache.prune(40, Some(new_env.clone()), &fork_graph.read().unwrap());
2539
2540        // Only an entry which carries an environment, and one which is not
2541        // the incoming one, is swept - and it is removed, not unloaded.
2542        let swept = carries_an_environment && !on_new_environment;
2543        assert_eq!(
2544            cache.stats.prunes_environment.load(Ordering::Relaxed),
2545            u64::from(swept)
2546        );
2547        if swept {
2548            assert!(cache.get_slot_versions_for_tests(&program_id).is_empty());
2549        } else {
2550            let slot_versions = cache.get_slot_versions_for_tests(&program_id);
2551            assert_eq!(slot_versions.len(), 1);
2552            assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &entry));
2553        }
2554    }
2555
2556    #[test]
2557    fn test_prune_environment_sweep_keeps_tombstones() {
2558        // Fork graph created for the test
2559        //                0 - 40 - 50 - 60 - 70 - 100
2560        //
2561        // Every entry is deployed after the root the sweep runs at, so `prune`
2562        // keeps all of them on the fork graph alone and the environment is the
2563        // only thing which takes any of them out.
2564        //
2565        // Here we want to test that the sweep can only take an entry which
2566        // carries an environment. Therefore the two built for the outgoing one
2567        // go, and the tombstone survives because it has none to compare
2568        // against.
2569        let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
2570        let mut fork_graph = TestForkGraphSpecific::default();
2571        fork_graph.insert_fork(&[0, 40, 50, 60, 70, 100]);
2572        let fork_graph = Arc::new(RwLock::new(fork_graph));
2573        cache.set_fork_graph(Arc::downgrade(&fork_graph));
2574        let env = get_mock_program_runtime_environment();
2575        let new_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
2576        let program_id = Pubkey::new_unique();
2577        let closed = new_test_entry_with_owner(
2578            50,
2579            ProgramCacheEntryOwner::LoaderV3,
2580            new_closed_entry(env.clone()),
2581        );
2582        let failed_verification = new_test_entry_with_owner(
2583            60,
2584            ProgramCacheEntryOwner::LoaderV3,
2585            // `FailedVerification` carries an environment, so it gets pruned.
2586            new_failed_verification_entry(env.clone()),
2587        );
2588        let loaded = new_test_entry_with_owner(
2589            70,
2590            ProgramCacheEntryOwner::LoaderV3,
2591            new_loaded_entry(env.clone()),
2592        );
2593        cache.assign_program(&env, program_id, 50, Arc::clone(&closed));
2594        cache.assign_program(&env, program_id, 60, Arc::clone(&failed_verification));
2595        cache.assign_program(&env, program_id, 70, Arc::clone(&loaded));
2596
2597        let slot_versions = cache.get_slot_versions_for_tests(&program_id);
2598        assert_eq!(slot_versions.len(), 3);
2599        assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &closed));
2600        assert!(Arc::ptr_eq(
2601            slot_versions.get(1).unwrap(),
2602            &failed_verification
2603        ));
2604        assert!(Arc::ptr_eq(slot_versions.get(2).unwrap(), &loaded));
2605
2606        // The epoch boundary. Both entries which carry an environment are on
2607        // the outgoing one and are swept away.
2608        cache.prune(40, Some(new_env.clone()), &fork_graph.read().unwrap());
2609        assert_eq!(cache.stats.prunes_environment.load(Ordering::Relaxed), 2);
2610        let slot_versions = cache.get_slot_versions_for_tests(&program_id);
2611        assert_eq!(slot_versions.len(), 1);
2612        assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &closed));
2613
2614        // The `Closed` tombstone survives because it carries no environment.
2615        let mut search_for = vec![ProgramToLoad {
2616            program_id: &program_id,
2617            loader: ProgramCacheEntryOwner::LoaderV3,
2618            deployment_slot: 50,
2619        }];
2620        let mut extracted = ProgramCacheForTxBatch::new(100);
2621        cache.extract(&mut search_for, &mut extracted, &new_env, true, true);
2622        assert!(search_for.is_empty());
2623        assert!(Arc::ptr_eq(
2624            extracted.entries.get(&program_id).unwrap(),
2625            &closed
2626        ));
2627
2628        // Try the same search again with the outgoing environment. That would
2629        // not happen in production, since the sweep has just rooted the new
2630        // one, but exercise it anyway.
2631        let mut search_for = vec![ProgramToLoad {
2632            program_id: &program_id,
2633            loader: ProgramCacheEntryOwner::LoaderV3,
2634            deployment_slot: 50,
2635        }];
2636        let mut extracted = ProgramCacheForTxBatch::new(100);
2637        cache.extract(&mut search_for, &mut extracted, &env, true, true);
2638        assert!(search_for.is_empty());
2639        assert!(Arc::ptr_eq(
2640            extracted.entries.get(&program_id).unwrap(),
2641            &closed
2642        ));
2643    }
2644
2645    #[test]
2646    #[should_panic(expected = "self.latest_root_slot <= new_root_slot")]
2647    fn test_prune_backwards_panics() {
2648        let (mut cache, fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
2649        cache.latest_root_slot = 100;
2650
2651        // The `debug_assert!` guarding this runs after the pruning logic,
2652        // not before it.
2653        cache.prune(50, None, &fork_graph.read().unwrap());
2654    }
2655
2656    #[derive(Default)]
2657    struct TestForkGraphSpecific {
2658        forks: Vec<Vec<Slot>>,
2659    }
2660
2661    impl TestForkGraphSpecific {
2662        fn insert_fork(&mut self, fork: &[Slot]) {
2663            let mut fork = fork.to_vec();
2664            fork.sort();
2665            self.forks.push(fork)
2666        }
2667    }
2668
2669    impl ForkGraph for TestForkGraphSpecific {
2670        fn relationship(&self, a: Slot, b: Slot) -> BlockRelation {
2671            match self.forks.iter().try_for_each(|fork| {
2672                let relation = fork
2673                    .iter()
2674                    .position(|x| *x == a)
2675                    .and_then(|a_pos| {
2676                        fork.iter().position(|x| *x == b).and_then(|b_pos| {
2677                            (a_pos == b_pos)
2678                                .then_some(BlockRelation::Equal)
2679                                .or_else(|| (a_pos < b_pos).then_some(BlockRelation::Ancestor))
2680                                .or(Some(BlockRelation::Descendant))
2681                        })
2682                    })
2683                    .unwrap_or(BlockRelation::Unrelated);
2684
2685                if relation != BlockRelation::Unrelated {
2686                    return ControlFlow::Break(relation);
2687                }
2688
2689                ControlFlow::Continue(())
2690            }) {
2691                ControlFlow::Break(relation) => relation,
2692                _ => BlockRelation::Unrelated,
2693            }
2694        }
2695    }
2696
2697    fn get_entries_to_load<'a>(
2698        cache: &ProgramCache<TestForkGraphSpecific>,
2699        loading_slot: Slot,
2700        keys: &'a [Pubkey],
2701    ) -> Vec<ProgramToLoad<'a>> {
2702        let fork_graph = cache.fork_graph.as_ref().unwrap().upgrade().unwrap();
2703        let locked_fork_graph = fork_graph.read().unwrap();
2704        let entries = cache.get_flattened_entries_for_tests();
2705        keys.iter()
2706            .filter_map(|key| {
2707                entries
2708                    .iter()
2709                    .rev()
2710                    .find(|(program_id, entry)| {
2711                        program_id == key
2712                            && matches!(
2713                                locked_fork_graph.relationship(entry.deployment_slot, loading_slot),
2714                                BlockRelation::Equal | BlockRelation::Ancestor,
2715                            )
2716                    })
2717                    .map(|(_program_id, entry)| ProgramToLoad {
2718                        program_id: key,
2719                        loader: entry.account_owner,
2720                        deployment_slot: entry.deployment_slot,
2721                    })
2722            })
2723            .collect()
2724    }
2725
2726    fn match_slot(
2727        extracted: &ProgramCacheForTxBatch,
2728        program: &Pubkey,
2729        deployment_slot: Slot,
2730        working_slot: Slot,
2731    ) -> bool {
2732        assert_eq!(extracted.slot, working_slot);
2733        extracted
2734            .entries
2735            .get(program)
2736            .map(|entry| entry.deployment_slot == deployment_slot)
2737            .unwrap_or(false)
2738    }
2739
2740    fn match_missing(
2741        missing: &[ProgramToLoad],
2742        program_id: &Pubkey,
2743        expected_result: bool,
2744    ) -> bool {
2745        missing.iter().any(|entry| entry.program_id == program_id) == expected_result
2746    }
2747
2748    #[test]
2749    fn test_fork_extract_and_prune() {
2750        let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
2751        let env = get_mock_program_runtime_environment();
2752
2753        // Fork graph created for the test
2754        //                   0
2755        //                 /   \
2756        //                10    5
2757        //                |     |
2758        //                20    11
2759        //                |     | \
2760        //                22   15  25
2761        //                      |   |
2762        //                     16  27
2763        //                      |
2764        //                     19
2765        //                      |
2766        //                     23
2767
2768        let mut fork_graph = TestForkGraphSpecific::default();
2769        fork_graph.insert_fork(&[0, 10, 20, 22]);
2770        fork_graph.insert_fork(&[0, 5, 11, 15, 16, 18, 19, 21, 23]);
2771        fork_graph.insert_fork(&[0, 5, 11, 25, 27]);
2772
2773        let fork_graph = Arc::new(RwLock::new(fork_graph));
2774        cache.set_fork_graph(Arc::downgrade(&fork_graph));
2775
2776        let program1 = Pubkey::new_unique();
2777        cache.assign_program(&env, program1, 0, new_test_entry(0));
2778        cache.assign_program(&env, program1, 10, new_test_entry(10));
2779        cache.assign_program(&env, program1, 20, new_test_entry(20));
2780
2781        let program2 = Pubkey::new_unique();
2782        cache.assign_program(&env, program2, 5, new_test_entry(5));
2783        cache.assign_program(&env, program2, 11, new_test_entry(11));
2784
2785        let program3 = Pubkey::new_unique();
2786        cache.assign_program(&env, program3, 25, new_test_entry(25));
2787
2788        let program4 = Pubkey::new_unique();
2789        cache.assign_program(&env, program4, 0, new_test_entry(0));
2790        cache.assign_program(&env, program4, 5, new_test_entry(5));
2791        // The following is a special case, where effective slot is 3 slots in the future
2792        cache.assign_program(&env, program4, 15, new_test_entry(15));
2793
2794        // Current fork graph
2795        //                   0
2796        //                 /   \
2797        //                10    5
2798        //                |     |
2799        //                20    11
2800        //                |     | \
2801        //                22   15  25
2802        //                      |   |
2803        //                     16  27
2804        //                      |
2805        //                     19
2806        //                      |
2807        //                     23
2808
2809        // Testing fork 0 - 10 - 20 - 22 with current slot at 22
2810        let keys = &[program1, program2, program3, program4];
2811        let mut missing = get_entries_to_load(&cache, 22, keys);
2812        assert!(match_missing(&missing, &program2, false));
2813        assert!(match_missing(&missing, &program3, false));
2814        let mut extracted = ProgramCacheForTxBatch::new(22);
2815        cache.extract(&mut missing, &mut extracted, &env, true, true);
2816        assert!(match_slot(&extracted, &program1, 20, 22));
2817        assert!(match_slot(&extracted, &program4, 0, 22));
2818
2819        // Testing fork 0 - 5 - 11 - 15 - 16 with current slot at 15
2820        let mut missing = get_entries_to_load(&cache, 15, keys);
2821        assert!(match_missing(&missing, &program3, false));
2822        let mut extracted = ProgramCacheForTxBatch::new(15);
2823        cache.extract(&mut missing, &mut extracted, &env, true, true);
2824        assert!(match_slot(&extracted, &program1, 0, 15));
2825        assert!(match_slot(&extracted, &program2, 11, 15));
2826        // The effective slot of program4 deployed in slot 15 is 19. So it should not be usable in slot 16.
2827        // A delay visibility tombstone should be returned here.
2828        let tombstone = extracted
2829            .find(&program4)
2830            .expect("Failed to find the tombstone");
2831        assert_matches!(tombstone.program, ProgramCacheEntryType::DelayVisibility);
2832        assert_eq!(tombstone.deployment_slot, 15);
2833
2834        // Testing the same fork above, but current slot is now 18 (equal to effective slot of program4).
2835        let mut missing = get_entries_to_load(&cache, 18, keys);
2836        assert!(match_missing(&missing, &program3, false));
2837        let mut extracted = ProgramCacheForTxBatch::new(18);
2838        cache.extract(&mut missing, &mut extracted, &env, true, true);
2839        assert!(match_slot(&extracted, &program1, 0, 18));
2840        assert!(match_slot(&extracted, &program2, 11, 18));
2841        // The effective slot of program4 deployed in slot 15 is 18. So it should be usable in slot 18.
2842        assert!(match_slot(&extracted, &program4, 15, 18));
2843
2844        // Testing the same fork above, but current slot is now 23 (future slot than effective slot of program4).
2845        let mut missing = get_entries_to_load(&cache, 23, keys);
2846        assert!(match_missing(&missing, &program3, false));
2847        let mut extracted = ProgramCacheForTxBatch::new(23);
2848        cache.extract(&mut missing, &mut extracted, &env, true, true);
2849        assert!(match_slot(&extracted, &program1, 0, 23));
2850        assert!(match_slot(&extracted, &program2, 11, 23));
2851        // The effective slot of program4 deployed in slot 15 is 19. So it should be usable in slot 23.
2852        assert!(match_slot(&extracted, &program4, 15, 23));
2853
2854        // Testing fork 0 - 5 - 11 - 15 - 16 with current slot at 11
2855        let mut missing = get_entries_to_load(&cache, 11, keys);
2856        assert!(match_missing(&missing, &program3, false));
2857        let mut extracted = ProgramCacheForTxBatch::new(11);
2858        cache.extract(&mut missing, &mut extracted, &env, true, true);
2859        assert!(match_slot(&extracted, &program1, 0, 11));
2860        // program2 was updated at slot 11, but is not effective till slot 12. The result should contain a tombstone.
2861        let tombstone = extracted
2862            .find(&program2)
2863            .expect("Failed to find the tombstone");
2864        assert_matches!(tombstone.program, ProgramCacheEntryType::DelayVisibility);
2865        assert_eq!(tombstone.deployment_slot, 11);
2866        assert!(match_slot(&extracted, &program4, 5, 11));
2867
2868        cache.prune(5, None, &fork_graph.read().unwrap());
2869
2870        // Fork graph after pruning
2871        //                   0
2872        //                   |
2873        //                   5
2874        //                   |
2875        //                   11
2876        //                   | \
2877        //                  15  25
2878        //                   |   |
2879        //                  16  27
2880        //                   |
2881        //                  19
2882        //                   |
2883        //                  23
2884
2885        // Testing fork 11 - 15 - 16- 19 - 22 with root at 5 and current slot at 22
2886        let mut missing = get_entries_to_load(&cache, 21, keys);
2887        assert!(match_missing(&missing, &program3, false));
2888        let mut extracted = ProgramCacheForTxBatch::new(21);
2889        cache.extract(&mut missing, &mut extracted, &env, true, true);
2890        // Since the fork was pruned, we should not find the entry deployed at slot 20.
2891        assert!(match_slot(&extracted, &program1, 0, 21));
2892        assert!(match_slot(&extracted, &program2, 11, 21));
2893        assert!(match_slot(&extracted, &program4, 15, 21));
2894
2895        // Testing fork 0 - 5 - 11 - 25 - 27 with current slot at 27
2896        let mut missing = get_entries_to_load(&cache, 27, keys);
2897        let mut extracted = ProgramCacheForTxBatch::new(27);
2898        cache.extract(&mut missing, &mut extracted, &env, true, true);
2899        assert!(match_slot(&extracted, &program1, 0, 27));
2900        assert!(match_slot(&extracted, &program2, 11, 27));
2901        assert!(match_slot(&extracted, &program3, 25, 27));
2902        assert!(match_slot(&extracted, &program4, 5, 27));
2903
2904        cache.prune(15, None, &fork_graph.read().unwrap());
2905
2906        // Fork graph after pruning
2907        //                  0
2908        //                  |
2909        //                  5
2910        //                  |
2911        //                  11
2912        //                  |
2913        //                  15
2914        //                  |
2915        //                  16
2916        //                  |
2917        //                  19
2918        //                  |
2919        //                  23
2920
2921        // Testing fork 16, 19, 23, with root at 15, current slot at 23
2922        let mut missing = get_entries_to_load(&cache, 23, keys);
2923        assert!(match_missing(&missing, &program3, false));
2924        let mut extracted = ProgramCacheForTxBatch::new(23);
2925        cache.extract(&mut missing, &mut extracted, &env, true, true);
2926        assert!(match_slot(&extracted, &program1, 0, 23));
2927        assert!(match_slot(&extracted, &program2, 11, 23));
2928        assert!(match_slot(&extracted, &program4, 15, 23));
2929    }
2930
2931    #[test]
2932    fn test_extract_using_deployment_slot() {
2933        let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
2934        let env = get_mock_program_runtime_environment();
2935
2936        // Fork graph created for the test
2937        //                   0
2938        //                 /   \
2939        //                10    5
2940        //                |     |
2941        //                20    11
2942        //                |     | \
2943        //                22   15  25
2944        //                      |   |
2945        //                     16  27
2946        //                      |
2947        //                     19
2948        //                      |
2949        //                     23
2950
2951        let mut fork_graph = TestForkGraphSpecific::default();
2952        fork_graph.insert_fork(&[0, 10, 20, 22]);
2953        fork_graph.insert_fork(&[0, 5, 11, 12, 15, 16, 18, 19, 21, 23]);
2954        fork_graph.insert_fork(&[0, 5, 11, 25, 27]);
2955
2956        let fork_graph = Arc::new(RwLock::new(fork_graph));
2957        cache.set_fork_graph(Arc::downgrade(&fork_graph));
2958
2959        let program1 = Pubkey::new_unique();
2960        cache.assign_program(&env, program1, 0, new_test_entry(0));
2961        cache.assign_program(&env, program1, 20, new_test_entry(20));
2962
2963        let program2 = Pubkey::new_unique();
2964        cache.assign_program(&env, program2, 5, new_test_entry(5));
2965        cache.assign_program(&env, program2, 11, new_test_entry(11));
2966
2967        let program3 = Pubkey::new_unique();
2968        cache.assign_program(&env, program3, 25, new_test_entry(25));
2969
2970        // Testing fork 0 - 5 - 11 - 15 - 16 - 19 - 21 - 23 with current slot at 19
2971        let keys = &[program1, program2, program3];
2972        let mut missing = get_entries_to_load(&cache, 12, keys);
2973        assert!(match_missing(&missing, &program3, false));
2974        let mut extracted = ProgramCacheForTxBatch::new(12);
2975        cache.extract(&mut missing, &mut extracted, &env, true, true);
2976        assert!(match_slot(&extracted, &program1, 0, 12));
2977        assert!(match_slot(&extracted, &program2, 11, 12));
2978
2979        // Now try extractions that previously worked under the "deployed on
2980        // or after" criteria, but won't work with exact matching.
2981        let mut missing = get_entries_to_load(&cache, 12, keys);
2982        // Program 2's newest entry is at slot 11. Asking for 5 doesn't extract
2983        // the latest (11) anymore. You get 5.
2984        missing.get_mut(1).unwrap().deployment_slot = 5;
2985        assert!(match_missing(&missing, &program3, false));
2986        let mut extracted = ProgramCacheForTxBatch::new(12);
2987        cache.extract(&mut missing, &mut extracted, &env, true, true);
2988        assert!(match_slot(&extracted, &program1, 0, 12));
2989        assert!(match_slot(&extracted, &program2, 5, 12));
2990    }
2991
2992    #[test]
2993    fn test_extract_rejects_entry_deployed_after_the_requested_slot() {
2994        let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
2995        let env = get_mock_program_runtime_environment();
2996
2997        let mut fork_graph = TestForkGraphSpecific::default();
2998        fork_graph.insert_fork(&[0, 5, 11, 12]);
2999        let fork_graph = Arc::new(RwLock::new(fork_graph));
3000        cache.set_fork_graph(Arc::downgrade(&fork_graph));
3001
3002        // The only cached entry was deployed in slot 11.
3003        let program = Pubkey::new_unique();
3004        cache.assign_program(&env, program, 11, new_test_entry(11));
3005
3006        // A caller whose account state holds slot 5 must not be handed it.
3007        let mut missing = vec![ProgramToLoad {
3008            program_id: &program,
3009            loader: ProgramCacheEntryOwner::LoaderV2,
3010            deployment_slot: 5,
3011        }];
3012        let mut extracted = ProgramCacheForTxBatch::new(12);
3013        cache.extract(&mut missing, &mut extracted, &env, true, true);
3014        assert!(match_missing(&missing, &program, true));
3015        assert!(extracted.find(&program).is_none());
3016
3017        // The same caller requesting slot 11 gets it.
3018        let mut missing = vec![ProgramToLoad {
3019            program_id: &program,
3020            loader: ProgramCacheEntryOwner::LoaderV2,
3021            deployment_slot: 11,
3022        }];
3023        let mut extracted = ProgramCacheForTxBatch::new(12);
3024        cache.extract(&mut missing, &mut extracted, &env, true, true);
3025        assert!(match_missing(&missing, &program, false));
3026        assert!(match_slot(&extracted, &program, 11, 12));
3027    }
3028
3029    #[test]
3030    fn test_extract_unloaded() {
3031        let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
3032        let env = get_mock_program_runtime_environment();
3033
3034        // Fork graph created for the test
3035        //                   0
3036        //                 /   \
3037        //                10    5
3038        //                |     |
3039        //                20    11
3040        //                |     | \
3041        //                22   15  25
3042        //                      |   |
3043        //                     16  27
3044        //                      |
3045        //                     19
3046        //                      |
3047        //                     23
3048
3049        let mut fork_graph = TestForkGraphSpecific::default();
3050        fork_graph.insert_fork(&[0, 10, 20, 22]);
3051        fork_graph.insert_fork(&[0, 5, 11, 15, 16, 19, 21, 23]);
3052        fork_graph.insert_fork(&[0, 5, 11, 25, 27]);
3053
3054        let fork_graph = Arc::new(RwLock::new(fork_graph));
3055        cache.set_fork_graph(Arc::downgrade(&fork_graph));
3056
3057        let program1 = Pubkey::new_unique();
3058        cache.assign_program(&env, program1, 0, new_test_entry(0));
3059        cache.assign_program(&env, program1, 20, new_test_entry(20));
3060
3061        let program2 = Pubkey::new_unique();
3062        cache.assign_program(&env, program2, 5, new_test_entry(5));
3063        cache.assign_program(&env, program2, 11, new_test_entry(11));
3064
3065        let program3 = Pubkey::new_unique();
3066        // Insert an unloaded program with correct/cache's environment at slot 25
3067        let _ = insert_unloaded_entry(&mut cache, program3, 25);
3068
3069        // Insert another unloaded program with a different environment at slot 20
3070        // Since this entry's environment won't match cache's environment, looking up this
3071        // entry should return missing instead of unloaded entry.
3072        cache.assign_program(
3073            &env,
3074            program3,
3075            20,
3076            Arc::new(
3077                new_test_entry(20)
3078                    .to_unloaded()
3079                    .expect("Failed to create unloaded program"),
3080            ),
3081        );
3082
3083        // Testing fork 0 - 5 - 11 - 15 - 16 - 19 - 21 - 23 with current slot at 19
3084        let keys = &[program1, program2, program3];
3085        let mut missing = get_entries_to_load(&cache, 19, keys);
3086        assert!(match_missing(&missing, &program3, false));
3087        let mut extracted = ProgramCacheForTxBatch::new(19);
3088        cache.extract(&mut missing, &mut extracted, &env, true, true);
3089        assert!(match_slot(&extracted, &program1, 0, 19));
3090        assert!(match_slot(&extracted, &program2, 11, 19));
3091
3092        // Testing fork 0 - 5 - 11 - 25 - 27 with current slot at 27
3093        let mut missing = get_entries_to_load(&cache, 27, keys);
3094        let mut extracted = ProgramCacheForTxBatch::new(27);
3095        cache.extract(&mut missing, &mut extracted, &env, true, true);
3096        assert!(match_slot(&extracted, &program1, 0, 27));
3097        assert!(match_slot(&extracted, &program2, 11, 27));
3098        assert!(match_missing(&missing, &program3, true));
3099
3100        // Testing fork 0 - 10 - 20 - 22 with current slot at 22
3101        let mut missing = get_entries_to_load(&cache, 22, keys);
3102        assert!(match_missing(&missing, &program2, false));
3103        let mut extracted = ProgramCacheForTxBatch::new(22);
3104        cache.extract(&mut missing, &mut extracted, &env, true, true);
3105        assert!(match_slot(&extracted, &program1, 20, 22));
3106        assert!(match_missing(&missing, &program3, true));
3107    }
3108
3109    #[test]
3110    fn test_extract_different_environment() {
3111        let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
3112        let env = get_mock_program_runtime_environment();
3113        let other_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
3114
3115        // Fork graph created for the test
3116        //                0
3117        //                |
3118        //                10
3119        //                |
3120        //                20
3121        //                |
3122        //                22
3123
3124        let mut fork_graph = TestForkGraphSpecific::default();
3125        fork_graph.insert_fork(&[0, 10, 20, 22]);
3126
3127        let fork_graph = Arc::new(RwLock::new(fork_graph));
3128        cache.set_fork_graph(Arc::downgrade(&fork_graph));
3129
3130        let program1 = Pubkey::new_unique();
3131        cache.assign_program(
3132            &env,
3133            program1,
3134            10,
3135            Arc::new(ProgramCacheEntry::new_closed_tombstone(
3136                10,
3137                ProgramCacheEntryOwner::LoaderV3,
3138            )),
3139        );
3140        cache.assign_program(&env, program1, 20, new_test_entry(20));
3141
3142        // Testing fork 0 - 10 - 20 - 22 with current slot at 22
3143        let keys = &[program1];
3144        let mut missing = get_entries_to_load(&cache, 22, keys);
3145        let mut extracted = ProgramCacheForTxBatch::new(22);
3146        cache.extract(&mut missing, &mut extracted, &env, true, true);
3147        assert!(match_slot(&extracted, &program1, 20, 22));
3148
3149        // Looking for a different environment
3150        let mut missing = get_entries_to_load(&cache, 22, keys);
3151        let mut extracted = ProgramCacheForTxBatch::new(22);
3152        cache.extract(&mut missing, &mut extracted, &other_env, true, true);
3153        assert!(match_missing(&missing, &program1, true));
3154    }
3155
3156    #[test_matrix((false, true))]
3157    fn test_extract_no_second_level(empty_second_level: bool) {
3158        let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
3159        let env = get_mock_program_runtime_environment();
3160        let program_id = Pubkey::new_unique();
3161        if empty_second_level {
3162            // Make the entry already exist, but with an empty second level.
3163            match &mut cache.index {
3164                IndexImplementation::V1 { entries, .. } => {
3165                    entries.insert(program_id, Vec::new());
3166                }
3167            }
3168        }
3169
3170        // There is nothing to iterate either way, so the program is left to be
3171        // loaded.
3172        let mut search_for = vec![ProgramToLoad {
3173            program_id: &program_id,
3174            loader: ProgramCacheEntryOwner::LoaderV3,
3175            deployment_slot: 0,
3176        }];
3177        let mut extracted = ProgramCacheForTxBatch::new(100);
3178        let task = cache.extract(&mut search_for, &mut extracted, &env, true, true);
3179        assert_eq!(search_for.len(), 1);
3180        assert!(extracted.entries.is_empty());
3181        assert_eq!(task, Some(program_id));
3182    }
3183
3184    #[test]
3185    fn test_extract_account_owner_mismatch() {
3186        let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
3187        let env = get_mock_program_runtime_environment();
3188        let program_id = Pubkey::new_unique();
3189        let owned_by_v2 = new_test_entry_with_owner(
3190            100,
3191            ProgramCacheEntryOwner::LoaderV2,
3192            new_loaded_entry(env.clone()),
3193        );
3194        cache.assign_program(&env, program_id, 100, Arc::clone(&owned_by_v2));
3195
3196        // The only entry has an owner the search does not ask for.
3197        // Nothing is extracted. The caller must reload.
3198        let mut search_for = vec![ProgramToLoad {
3199            program_id: &program_id,
3200            loader: ProgramCacheEntryOwner::LoaderV3,
3201            deployment_slot: 100,
3202        }];
3203        let mut extracted = ProgramCacheForTxBatch::new(200);
3204        cache.extract(&mut search_for, &mut extracted, &env, true, true);
3205        assert_eq!(search_for.len(), 1);
3206        assert!(extracted.entries.is_empty());
3207
3208        // A loader migration, where only the newest entry has the new owner. A
3209        // search for the old one skips it and takes the entry below it.
3210        let owned_by_v3 = new_test_entry_with_owner(
3211            150,
3212            ProgramCacheEntryOwner::LoaderV3,
3213            new_loaded_entry(env.clone()),
3214        );
3215        cache.assign_program(&env, program_id, 150, Arc::clone(&owned_by_v3));
3216
3217        // Here the cache has the original v2 at 100 followed by the v3 at 150.
3218        let slot_versions = cache.get_slot_versions_for_tests(&program_id);
3219        assert_eq!(slot_versions.len(), 2);
3220        assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &owned_by_v2));
3221        assert!(Arc::ptr_eq(slot_versions.get(1).unwrap(), &owned_by_v3));
3222
3223        // Try searching for the v2 version, from some fork that did not see
3224        // the migration. Assert the v2 entry is returned.
3225        let mut search_for = vec![ProgramToLoad {
3226            program_id: &program_id,
3227            loader: ProgramCacheEntryOwner::LoaderV2,
3228            deployment_slot: 100,
3229        }];
3230        let mut extracted = ProgramCacheForTxBatch::new(200);
3231        cache.extract(&mut search_for, &mut extracted, &env, true, true);
3232        assert!(search_for.is_empty());
3233        assert!(Arc::ptr_eq(
3234            extracted.entries.get(&program_id).unwrap(),
3235            &owned_by_v2
3236        ));
3237
3238        // And a fork which did see the migration finds the v3 entry, so both
3239        // owners are reachable from the same second level.
3240        let mut search_for = vec![ProgramToLoad {
3241            program_id: &program_id,
3242            loader: ProgramCacheEntryOwner::LoaderV3,
3243            deployment_slot: 150,
3244        }];
3245        let mut extracted = ProgramCacheForTxBatch::new(200);
3246        cache.extract(&mut search_for, &mut extracted, &env, true, true);
3247        assert!(search_for.is_empty());
3248        assert!(Arc::ptr_eq(
3249            extracted.entries.get(&program_id).unwrap(),
3250            &owned_by_v3
3251        ));
3252    }
3253
3254    #[test]
3255    fn test_extract_environment_mismatch() {
3256        let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
3257        let env = get_mock_program_runtime_environment();
3258        let other_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
3259        let program_id = Pubkey::new_unique();
3260        let on_other_env = new_test_entry_with_owner(
3261            100,
3262            ProgramCacheEntryOwner::LoaderV3,
3263            new_loaded_entry(other_env.clone()),
3264        );
3265        cache.assign_program(&other_env, program_id, 100, Arc::clone(&on_other_env));
3266
3267        // The only entry is in the same branch and effective, but it was built
3268        // for another environment.
3269        // Nothing is extracted. The caller must reload.
3270        // This is "reload when in doubt" in its smallest form.
3271        let mut search_for = vec![ProgramToLoad {
3272            program_id: &program_id,
3273            loader: ProgramCacheEntryOwner::LoaderV3,
3274            deployment_slot: 100,
3275        }];
3276        let mut extracted = ProgramCacheForTxBatch::new(200);
3277        cache.extract(&mut search_for, &mut extracted, &env, true, true);
3278        assert_eq!(search_for.len(), 1);
3279        assert!(extracted.entries.is_empty());
3280
3281        // The same deployment, compiled for the environment which is asked
3282        // for. Both are kept, since they differ in env.
3283        let on_execution_env = new_test_entry_with_owner(
3284            100,
3285            ProgramCacheEntryOwner::LoaderV3,
3286            new_loaded_entry(env.clone()),
3287        );
3288        cache.assign_program(&env, program_id, 100, Arc::clone(&on_execution_env));
3289
3290        // Here the cache has the one on the other environment first, since
3291        // entries for the current one sort last.
3292        let slot_versions = cache.get_slot_versions_for_tests(&program_id);
3293        assert_eq!(slot_versions.len(), 2);
3294        assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &on_other_env));
3295        assert!(Arc::ptr_eq(
3296            slot_versions.get(1).unwrap(),
3297            &on_execution_env
3298        ));
3299
3300        // Try searching for the entry with the current env. Assert it is
3301        // returned.
3302        let mut search_for = vec![ProgramToLoad {
3303            program_id: &program_id,
3304            loader: ProgramCacheEntryOwner::LoaderV3,
3305            deployment_slot: 100,
3306        }];
3307        let mut extracted = ProgramCacheForTxBatch::new(200);
3308        cache.extract(&mut search_for, &mut extracted, &env, true, true);
3309        assert!(search_for.is_empty());
3310        assert!(Arc::ptr_eq(
3311            extracted.entries.get(&program_id).unwrap(),
3312            &on_execution_env
3313        ));
3314
3315        // And searching under the other environment returns the entry built
3316        // for it, so both are reachable from the same second level.
3317        let mut search_for = vec![ProgramToLoad {
3318            program_id: &program_id,
3319            loader: ProgramCacheEntryOwner::LoaderV3,
3320            deployment_slot: 100,
3321        }];
3322        let mut extracted = ProgramCacheForTxBatch::new(200);
3323        cache.extract(&mut search_for, &mut extracted, &other_env, true, true);
3324        assert!(search_for.is_empty());
3325        assert!(Arc::ptr_eq(
3326            extracted.entries.get(&program_id).unwrap(),
3327            &on_other_env
3328        ));
3329    }
3330
3331    #[test]
3332    fn test_extract_unloaded_entry() {
3333        let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
3334        let env = get_mock_program_runtime_environment();
3335        let program_id = Pubkey::new_unique();
3336        let unloaded = new_test_entry_with_owner(
3337            100,
3338            ProgramCacheEntryOwner::LoaderV3,
3339            new_unloaded_entry(env.clone()),
3340        );
3341        cache.assign_program(&env, program_id, 100, Arc::clone(&unloaded));
3342
3343        // The only entry clears every check documented in the previous test,
3344        // but its executable has been evicted.
3345        // Nothing is extracted. The caller must reload.
3346        let mut search_for = vec![ProgramToLoad {
3347            program_id: &program_id,
3348            loader: ProgramCacheEntryOwner::LoaderV3,
3349            deployment_slot: 100,
3350        }];
3351        let mut extracted = ProgramCacheForTxBatch::new(200);
3352        cache.extract(&mut search_for, &mut extracted, &env, true, true);
3353        assert_eq!(search_for.len(), 1);
3354        assert!(extracted.entries.is_empty());
3355
3356        // Reloading it is an allowed replacement, so it takes the same place.
3357        let loaded = new_test_entry_with_owner(
3358            100,
3359            ProgramCacheEntryOwner::LoaderV3,
3360            new_loaded_entry(env.clone()),
3361        );
3362        cache.assign_program(&env, program_id, 100, Arc::clone(&loaded));
3363        let slot_versions = cache.get_slot_versions_for_tests(&program_id);
3364        assert_eq!(slot_versions.len(), 1);
3365        assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &loaded));
3366
3367        // Extracting now gives the loaded entry. The unloaded is gone.
3368        let mut search_for = vec![ProgramToLoad {
3369            program_id: &program_id,
3370            loader: ProgramCacheEntryOwner::LoaderV3,
3371            deployment_slot: 100,
3372        }];
3373        let mut extracted = ProgramCacheForTxBatch::new(200);
3374        cache.extract(&mut search_for, &mut extracted, &env, true, true);
3375        assert!(search_for.is_empty());
3376        assert!(Arc::ptr_eq(
3377            extracted.entries.get(&program_id).unwrap(),
3378            &loaded
3379        ));
3380    }
3381
3382    #[test_matrix(
3383        (
3384            new_closed_entry,
3385            new_builtin_entry,
3386            new_failed_verification_entry,
3387            new_unloaded_entry,
3388            new_loaded_entry,
3389        ),
3390        (100, 101)
3391    )]
3392    fn test_extract_effective_slot(
3393        new_program: fn(ProgramRuntimeEnvironment) -> ProgramCacheEntryType,
3394        batch_slot: Slot,
3395    ) {
3396        // Fork graph created for the test
3397        //                100 - 101
3398        //                ^^^   ^^^
3399        //                |     `Loaded` and `Unloaded` become effective here
3400        //                the entry is deployed here
3401        //
3402        // Only `Loaded` and `Unloaded` have a delay window. The other three
3403        // are effective in the slot they were deployed in.
3404        //
3405        // Here we want to test that for all entry types, inside their
3406        // designated delay window, we get a tombstone standing in for the
3407        // entry, and outside it we get the entry itself (unless it is
3408        // `Unloaded`).
3409        let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
3410        let env = get_mock_program_runtime_environment();
3411        let program_id = Pubkey::new_unique();
3412        let entry = new_test_entry_with_owner(
3413            100,
3414            ProgramCacheEntryOwner::LoaderV3,
3415            new_program(env.clone()),
3416        );
3417        cache.assign_program(&env, program_id, 100, Arc::clone(&entry));
3418
3419        let slot_versions = cache.get_slot_versions_for_tests(&program_id);
3420        assert_eq!(slot_versions.len(), 1);
3421        assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &entry));
3422
3423        let mut search_for = vec![ProgramToLoad {
3424            program_id: &program_id,
3425            loader: ProgramCacheEntryOwner::LoaderV3,
3426            deployment_slot: 100,
3427        }];
3428        let mut extracted = ProgramCacheForTxBatch::new(batch_slot);
3429        cache.extract(&mut search_for, &mut extracted, &env, true, true);
3430
3431        if batch_slot < entry.effective_slot() {
3432            // The entry wasn't effective, so a tombstone was minted to stand
3433            // in for it. It is built at that moment rather than found, so what
3434            // it carries is copied across from the entry.
3435            assert!(search_for.is_empty());
3436            let tombstone = extracted.entries.get(&program_id).unwrap();
3437            assert_matches!(tombstone.program, ProgramCacheEntryType::DelayVisibility);
3438            assert_eq!(tombstone.account_owner, ProgramCacheEntryOwner::LoaderV3);
3439            assert_eq!(tombstone.deployment_slot, 100);
3440            assert!(Arc::ptr_eq(&tombstone.stats, &entry.stats)); // <-- Shared, not copied.
3441            assert_eq!(entry.stats.uses.load(Ordering::Relaxed), 1);
3442
3443            // The access slot is recorded on the entry the tombstone stands in
3444            // for. The tombstone's own is never touched, and is thrown away
3445            // with the batch.
3446            assert_eq!(entry.latest_access_slot.load(Ordering::Relaxed), batch_slot);
3447            assert_eq!(tombstone.latest_access_slot.load(Ordering::Relaxed), 0);
3448        } else if matches!(entry.program, ProgramCacheEntryType::Unloaded(_)) {
3449            // The entry was effective, but there is no binary behind it, so
3450            // the search breaks off and the caller is left to reload. Nothing
3451            // is recorded against the entry.
3452            assert!(extracted.entries.is_empty());
3453            assert_eq!(search_for.len(), 1);
3454            assert_eq!(entry.stats.uses.load(Ordering::Relaxed), 0);
3455            assert_eq!(entry.latest_access_slot.load(Ordering::Relaxed), 0);
3456        } else {
3457            // The entry was effective, so it comes back itself, and the use
3458            // and access slot are recorded on it.
3459            assert!(search_for.is_empty());
3460            assert!(Arc::ptr_eq(
3461                extracted.entries.get(&program_id).unwrap(),
3462                &entry
3463            ));
3464            assert_eq!(entry.stats.uses.load(Ordering::Relaxed), 1);
3465            assert_eq!(entry.latest_access_slot.load(Ordering::Relaxed), batch_slot);
3466        }
3467    }
3468
3469    #[test]
3470    fn test_extract_closed_entry_matches_any_env() {
3471        // Fork graph created for the test
3472        //                100
3473        //                ^^^
3474        //                the program is closed here
3475        //
3476        // Here we want to test that a closed entry carries no environment at
3477        // all, and that `matches_environment` reads that as a match for any of
3478        // them. Therefore, the entry is handed out whichever environment is
3479        // asked for.
3480        let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
3481        let env = get_mock_program_runtime_environment();
3482        let other_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
3483        let program_id = Pubkey::new_unique();
3484        let closed = new_test_entry_with_owner(
3485            100,
3486            ProgramCacheEntryOwner::LoaderV3,
3487            new_closed_entry(env.clone()), // <-- Entry is created with `env`.
3488        );
3489        assert_eq!(closed.effective_slot(), closed.deployment_slot);
3490        cache.assign_program(&env, program_id, 100, Arc::clone(&closed));
3491
3492        let slot_versions = cache.get_slot_versions_for_tests(&program_id);
3493        assert_eq!(slot_versions.len(), 1);
3494        assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &closed));
3495
3496        // Ask for the entry with `env`, matching the one used to assign it.
3497        let mut search_for = vec![ProgramToLoad {
3498            program_id: &program_id,
3499            loader: ProgramCacheEntryOwner::LoaderV3,
3500            deployment_slot: 100,
3501        }];
3502        let mut extracted = ProgramCacheForTxBatch::new(100);
3503        cache.extract(&mut search_for, &mut extracted, &env, true, true);
3504        assert!(Arc::ptr_eq(
3505            extracted.entries.get(&program_id).unwrap(),
3506            &closed
3507        ));
3508        assert!(search_for.is_empty());
3509
3510        // Now ask for it again with `other_env`. Still successful.
3511        let mut search_for = vec![ProgramToLoad {
3512            program_id: &program_id,
3513            loader: ProgramCacheEntryOwner::LoaderV3,
3514            deployment_slot: 100,
3515        }];
3516        let mut extracted = ProgramCacheForTxBatch::new(100);
3517        cache.extract(&mut search_for, &mut extracted, &other_env, true, true);
3518        assert!(Arc::ptr_eq(
3519            extracted.entries.get(&program_id).unwrap(),
3520            &closed
3521        ));
3522        assert!(search_for.is_empty());
3523    }
3524
3525    #[test]
3526    fn test_extract_delay_visibility_tombstone_interleaved_environments() {
3527        // Fork graph created for the test
3528        //                100 - 101
3529        //                ^^^   ^^^
3530        //                |     both entries become effective here
3531        //                both entries are deployed here
3532        //
3533        // Two entries at one deployment slot, one per environment. Here we
3534        // attempt to extract within the delay window (at the deployment slot).
3535        //
3536        // This test demonstrates that `DelayVisibility` tombstones - like
3537        // `Closed` - are indiscriminant about environments. Inside `extract`,
3538        // the delay visibility arm does not check the environment. Thus, any
3539        // entry provided to `extract` will see a `DelayVisibility` tombstone
3540        // if the batch slot falls within the delay window.
3541        //
3542        // Such a scenario is only possible if a program is deployed, loaded,
3543        // recompiled for the upcoming epoch, and extracted all within the same
3544        // slot. Since deployments insert `Unloaded` entries, this isn't
3545        // reachable in production today.
3546        //
3547        // However, this test serves to document this behavior, since it causes
3548        // a stats bug for now and could one day become a wider footgun.
3549        let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
3550        let env = get_mock_program_runtime_environment();
3551        let upcoming_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
3552        let program_id = Pubkey::new_unique();
3553        let on_execution_env = new_test_entry_with_owner(
3554            100,
3555            ProgramCacheEntryOwner::LoaderV3,
3556            new_loaded_entry(env.clone()),
3557        );
3558        let on_upcoming_env = new_test_entry_with_owner(
3559            100,
3560            ProgramCacheEntryOwner::LoaderV3,
3561            new_loaded_entry(upcoming_env.clone()),
3562        );
3563        cache.assign_program(&env, program_id, 100, Arc::clone(&on_execution_env));
3564        cache.assign_program(&upcoming_env, program_id, 100, Arc::clone(&on_upcoming_env));
3565
3566        let slot_versions = cache.get_slot_versions_for_tests(&program_id);
3567        assert_eq!(slot_versions.len(), 2);
3568        assert!(Arc::ptr_eq(
3569            slot_versions.first().unwrap(),
3570            &on_execution_env
3571        ));
3572        assert!(Arc::ptr_eq(slot_versions.get(1).unwrap(), &on_upcoming_env));
3573
3574        // Try the extraction, with the batch slot within the delay window.
3575        let mut search_for = vec![ProgramToLoad {
3576            program_id: &program_id,
3577            loader: ProgramCacheEntryOwner::LoaderV3,
3578            deployment_slot: 100,
3579        }];
3580        let mut extracted = ProgramCacheForTxBatch::new(100);
3581        cache.extract(&mut search_for, &mut extracted, &env, true, true);
3582        assert!(search_for.is_empty());
3583
3584        // As expected, we get a `DelayVisibility` tombstone.
3585        let tombstone = extracted.entries.get(&program_id).unwrap();
3586        assert_matches!(tombstone.program, ProgramCacheEntryType::DelayVisibility);
3587
3588        // TODO: Here's the stats bug, though. The entry the batch is actually
3589        // using (passed to `extract`) is present and reached second. The first
3590        // one reached is `!is_current_env`. Extraction traverses the second
3591        // level in reverse.
3592        //
3593        // So, in a case like this, we're actually updating the stats on the
3594        // wrong underlying `Loaded` entry.
3595        assert!(Arc::ptr_eq(&tombstone.stats, &on_upcoming_env.stats));
3596        assert_eq!(on_upcoming_env.stats.uses.load(Ordering::Relaxed), 1);
3597        assert_eq!(on_execution_env.stats.uses.load(Ordering::Relaxed), 0);
3598
3599        // Now extract one slot later, when the program becomes effective. As
3600        // we know, here environment *does* matter.
3601        let mut search_for = vec![ProgramToLoad {
3602            program_id: &program_id,
3603            loader: ProgramCacheEntryOwner::LoaderV3,
3604            deployment_slot: 100,
3605        }];
3606        let mut extracted = ProgramCacheForTxBatch::new(101);
3607        cache.extract(&mut search_for, &mut extracted, &env, true, true);
3608        assert!(search_for.is_empty());
3609        assert!(Arc::ptr_eq(
3610            extracted.entries.get(&program_id).unwrap(),
3611            &on_execution_env
3612        ));
3613
3614        // Now we see one use on each, since we just pulled `on_execution_env`.
3615        assert_eq!(on_upcoming_env.stats.uses.load(Ordering::Relaxed), 1);
3616        assert_eq!(on_execution_env.stats.uses.load(Ordering::Relaxed), 1);
3617    }
3618
3619    #[test_case(false)]
3620    #[test_case(true)]
3621    fn test_extract_environment_filter_same_slot(other_env_first: bool) {
3622        let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
3623        let env = get_mock_program_runtime_environment();
3624        let other_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
3625        let program_id = Pubkey::new_unique();
3626
3627        // Two entries at one deployment slot, one per environment.
3628        let on_other_env = new_test_entry_with_owner(
3629            100,
3630            ProgramCacheEntryOwner::LoaderV3,
3631            new_loaded_entry(other_env.clone()),
3632        );
3633        let on_execution_env = new_test_entry_with_owner(
3634            100,
3635            ProgramCacheEntryOwner::LoaderV3,
3636            new_loaded_entry(env.clone()),
3637        );
3638        if other_env_first {
3639            cache.assign_program(&other_env, program_id, 100, Arc::clone(&on_other_env));
3640            cache.assign_program(&env, program_id, 100, Arc::clone(&on_execution_env));
3641        } else {
3642            cache.assign_program(&env, program_id, 100, Arc::clone(&on_execution_env));
3643            cache.assign_program(&other_env, program_id, 100, Arc::clone(&on_other_env));
3644        }
3645
3646        // Each is assigned under its own environment, so whichever came
3647        // first sits first.
3648        let slot_versions = cache.get_slot_versions_for_tests(&program_id);
3649        assert_eq!(slot_versions.len(), 2);
3650        let (first, second) = if other_env_first {
3651            (&on_other_env, &on_execution_env)
3652        } else {
3653            (&on_execution_env, &on_other_env)
3654        };
3655        assert!(Arc::ptr_eq(slot_versions.first().unwrap(), first));
3656        assert!(Arc::ptr_eq(slot_versions.get(1).unwrap(), second));
3657
3658        // No matter the ordering, the one on the environment which is asked
3659        // for is the one returned.
3660        let mut search_for = vec![ProgramToLoad {
3661            program_id: &program_id,
3662            loader: ProgramCacheEntryOwner::LoaderV3,
3663            deployment_slot: 100,
3664        }];
3665        let mut extracted = ProgramCacheForTxBatch::new(200);
3666        cache.extract(&mut search_for, &mut extracted, &env, true, true);
3667        assert!(Arc::ptr_eq(
3668            extracted.entries.get(&program_id).unwrap(),
3669            &on_execution_env
3670        ));
3671
3672        // And asking for the other environment reaches the other entry.
3673        let mut search_for = vec![ProgramToLoad {
3674            program_id: &program_id,
3675            loader: ProgramCacheEntryOwner::LoaderV3,
3676            deployment_slot: 100,
3677        }];
3678        let mut extracted = ProgramCacheForTxBatch::new(200);
3679        cache.extract(&mut search_for, &mut extracted, &other_env, true, true);
3680        assert!(Arc::ptr_eq(
3681            extracted.entries.get(&program_id).unwrap(),
3682            &on_other_env
3683        ));
3684    }
3685
3686    #[test_matrix((false, true))]
3687    fn test_extract_usage_counter(increment_usage_counter: bool) {
3688        let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
3689        let env = get_mock_program_runtime_environment();
3690        let program_id = Pubkey::new_unique();
3691        let entry = new_test_entry_with_owner(
3692            100,
3693            ProgramCacheEntryOwner::LoaderV3,
3694            new_loaded_entry(env.clone()),
3695        );
3696        cache.assign_program(&env, program_id, 100, Arc::clone(&entry));
3697
3698        let mut search_for = vec![ProgramToLoad {
3699            program_id: &program_id,
3700            loader: ProgramCacheEntryOwner::LoaderV3,
3701            deployment_slot: 100,
3702        }];
3703        let mut extracted = ProgramCacheForTxBatch::new(200);
3704        cache.extract(
3705            &mut search_for,
3706            &mut extracted,
3707            &env,
3708            increment_usage_counter,
3709            true,
3710        );
3711
3712        // The access slot moves either way, the usage counter only when asked.
3713        assert_eq!(entry.latest_access_slot.load(Ordering::Relaxed), 200);
3714        assert_eq!(
3715            entry.stats.uses.load(Ordering::Relaxed),
3716            u64::from(increment_usage_counter)
3717        );
3718    }
3719
3720    #[test]
3721    fn test_extract_usage_counter_delayed_visibility() {
3722        let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
3723        let env = get_mock_program_runtime_environment();
3724        let program_id = Pubkey::new_unique();
3725        let entry = new_test_entry_with_owner(
3726            100,
3727            ProgramCacheEntryOwner::LoaderV3,
3728            new_loaded_entry(env.clone()),
3729        );
3730        cache.assign_program(&env, program_id, 100, Arc::clone(&entry));
3731
3732        // Extract at the deployment slot itself, which is inside the delay
3733        // visibility window, so a `DelayVisibility` tombstone stands in for
3734        // the entry.
3735        let mut search_for = vec![ProgramToLoad {
3736            program_id: &program_id,
3737            loader: ProgramCacheEntryOwner::LoaderV3,
3738            deployment_slot: 100,
3739        }];
3740        let mut extracted = ProgramCacheForTxBatch::new(100);
3741        cache.extract(&mut search_for, &mut extracted, &env, true, true);
3742
3743        let tombstone = extracted.entries.get(&program_id).unwrap();
3744        assert!(matches!(
3745            tombstone.program,
3746            ProgramCacheEntryType::DelayVisibility
3747        ));
3748        assert!(!Arc::ptr_eq(tombstone, &entry));
3749
3750        // The access slot lands on the entry the tombstone stands in for. The
3751        // tombstone's own is never touched, and is dropped with the batch.
3752        assert_eq!(entry.latest_access_slot.load(Ordering::Relaxed), 100);
3753        assert_eq!(tombstone.latest_access_slot.load(Ordering::Relaxed), 0);
3754
3755        // The usage counter reaches the entry either way, through the
3756        // statistics the two share.
3757        assert!(Arc::ptr_eq(&tombstone.stats, &entry.stats));
3758        assert_eq!(entry.stats.uses.load(Ordering::Relaxed), 1);
3759    }
3760
3761    #[test_matrix((false, true))]
3762    fn test_extract_hits_and_misses(count_hits_and_misses: bool) {
3763        let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
3764        let env = get_mock_program_runtime_environment();
3765        let found = Pubkey::new_unique();
3766        let missing = Pubkey::new_unique();
3767        cache.assign_program(
3768            &env,
3769            found,
3770            100,
3771            new_test_entry_with_owner(
3772                100,
3773                ProgramCacheEntryOwner::LoaderV3,
3774                new_loaded_entry(env.clone()),
3775            ),
3776        );
3777
3778        let mut search_for = vec![
3779            ProgramToLoad {
3780                program_id: &found,
3781                loader: ProgramCacheEntryOwner::LoaderV3,
3782                deployment_slot: 100,
3783            },
3784            ProgramToLoad {
3785                program_id: &missing,
3786                loader: ProgramCacheEntryOwner::LoaderV3,
3787                deployment_slot: 0,
3788            },
3789        ];
3790        let mut extracted = ProgramCacheForTxBatch::new(200);
3791        cache.extract(
3792            &mut search_for,
3793            &mut extracted,
3794            &env,
3795            true,
3796            count_hits_and_misses,
3797        );
3798
3799        let expected = u64::from(count_hits_and_misses);
3800        assert_eq!(cache.stats.hits.load(Ordering::Relaxed), expected);
3801        assert_eq!(cache.stats.misses.load(Ordering::Relaxed), expected);
3802    }
3803
3804    #[test]
3805    fn test_extract_hits_count_only_this_call() {
3806        let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
3807        let env = get_mock_program_runtime_environment();
3808        let program_id = Pubkey::new_unique();
3809        cache.assign_program(
3810            &env,
3811            program_id,
3812            100,
3813            new_test_entry_with_owner(
3814                100,
3815                ProgramCacheEntryOwner::LoaderV3,
3816                new_loaded_entry(env.clone()),
3817            ),
3818        );
3819
3820        // Anything already in the batch, such as the builtins it is seeded
3821        // with.
3822        let mut extracted = ProgramCacheForTxBatch::new(200);
3823        extracted.replenish(Pubkey::new_unique(), new_test_builtin_entry(0));
3824
3825        // One entry is found, and only that one is counted. The entry seeded
3826        // above is still in the batch, but it was not found by this call.
3827        let mut search_for = vec![ProgramToLoad {
3828            program_id: &program_id,
3829            loader: ProgramCacheEntryOwner::LoaderV3,
3830            deployment_slot: 100,
3831        }];
3832        cache.extract(&mut search_for, &mut extracted, &env, true, true);
3833        assert_eq!(extracted.entries.len(), 2);
3834        assert_eq!(cache.stats.hits.load(Ordering::Relaxed), 1);
3835    }
3836
3837    #[test]
3838    fn test_extract_cooperative_loading_task() {
3839        let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
3840        let env = get_mock_program_runtime_environment();
3841        let program_ids = [Pubkey::new_unique(), Pubkey::new_unique()];
3842
3843        // Both are missing, but only the first one becomes a task.
3844        let mut search_for = program_ids
3845            .iter()
3846            .map(|program_id| ProgramToLoad {
3847                program_id,
3848                loader: ProgramCacheEntryOwner::LoaderV3,
3849                deployment_slot: 0,
3850            })
3851            .collect();
3852        let mut extracted = ProgramCacheForTxBatch::new(100);
3853        let task = cache.extract(&mut search_for, &mut extracted, &env, true, true);
3854        assert_eq!(search_for.len(), 2);
3855        assert_eq!(task, program_ids.first().copied());
3856        match &cache.index {
3857            IndexImplementation::V1 {
3858                loading_entries, ..
3859            } => {
3860                let loading_entries = loading_entries.lock().unwrap();
3861                assert_eq!(loading_entries.len(), 1);
3862                assert_eq!(
3863                    loading_entries.get(program_ids.first().unwrap()),
3864                    Some(&(100, thread::current().id()))
3865                );
3866            }
3867        }
3868
3869        // Asking again for the one which is already loading returns nothing.
3870        let mut search_for = vec![ProgramToLoad {
3871            program_id: program_ids.first().unwrap(),
3872            loader: ProgramCacheEntryOwner::LoaderV3,
3873            deployment_slot: 0,
3874        }];
3875        let task = cache.extract(&mut search_for, &mut extracted, &env, true, true);
3876        assert_eq!(search_for.len(), 1);
3877        assert_eq!(task, None);
3878
3879        // Submitting the finished task notifies whoever is waiting on one.
3880        let cookie = cache.loading_task_waiter.cookie();
3881        let loaded = new_test_entry_with_owner(
3882            50,
3883            ProgramCacheEntryOwner::LoaderV3,
3884            new_loaded_entry(env.clone()),
3885        );
3886        cache.finish_cooperative_loading_task(
3887            &env,
3888            100,
3889            *program_ids.first().unwrap(),
3890            Arc::clone(&loaded),
3891        );
3892        assert_ne!(cache.loading_task_waiter.wait(cookie), cookie);
3893
3894        // It is no longer loading, and extracting it now finds it.
3895        match &cache.index {
3896            IndexImplementation::V1 {
3897                loading_entries, ..
3898            } => assert!(loading_entries.lock().unwrap().is_empty()),
3899        }
3900        let mut search_for = vec![ProgramToLoad {
3901            program_id: program_ids.first().unwrap(),
3902            loader: ProgramCacheEntryOwner::LoaderV3,
3903            deployment_slot: 50,
3904        }];
3905        let mut extracted = ProgramCacheForTxBatch::new(100);
3906        let task = cache.extract(&mut search_for, &mut extracted, &env, true, true);
3907        assert!(search_for.is_empty());
3908        assert_eq!(task, None);
3909        assert!(Arc::ptr_eq(
3910            extracted.entries.get(program_ids.first().unwrap()).unwrap(),
3911            &loaded
3912        ));
3913    }
3914
3915    #[test]
3916    fn test_extract_cooperative_loading_task_ordering() {
3917        let (cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
3918        let env = get_mock_program_runtime_environment();
3919        let program_ids = [Pubkey::new_unique(), Pubkey::new_unique()];
3920        let mut extracted = ProgramCacheForTxBatch::new(100);
3921
3922        // The first one reached becomes the task.
3923        let mut search_for = program_ids
3924            .iter()
3925            .map(|program_id| ProgramToLoad {
3926                program_id,
3927                loader: ProgramCacheEntryOwner::LoaderV3,
3928                deployment_slot: 0,
3929            })
3930            .collect();
3931        let task = cache.extract(&mut search_for, &mut extracted, &env, true, true);
3932        assert_eq!(task, program_ids.first().copied());
3933
3934        // Asking again in the reverse order reaches the one which is not
3935        // loading yet first, so that one becomes a task of its own.
3936        let mut search_for = program_ids
3937            .iter()
3938            .rev()
3939            .map(|program_id| ProgramToLoad {
3940                program_id,
3941                loader: ProgramCacheEntryOwner::LoaderV3,
3942                deployment_slot: 0,
3943            })
3944            .collect();
3945        let task = cache.extract(&mut search_for, &mut extracted, &env, true, true);
3946        assert_eq!(task, program_ids.get(1).copied());
3947
3948        // Both are loading now, by this thread and for this slot.
3949        match &cache.index {
3950            IndexImplementation::V1 {
3951                loading_entries, ..
3952            } => {
3953                let loading_entries = loading_entries.lock().unwrap();
3954                assert_eq!(loading_entries.len(), 2);
3955                for program_id in &program_ids {
3956                    assert_eq!(
3957                        loading_entries.get(program_id),
3958                        Some(&(100, thread::current().id()))
3959                    );
3960                }
3961            }
3962        }
3963
3964        // Neither of them can become a task again.
3965        let mut search_for = program_ids
3966            .iter()
3967            .map(|program_id| ProgramToLoad {
3968                program_id,
3969                loader: ProgramCacheEntryOwner::LoaderV3,
3970                deployment_slot: 0,
3971            })
3972            .collect();
3973        let task = cache.extract(&mut search_for, &mut extracted, &env, true, true);
3974        assert_eq!(search_for.len(), 2);
3975        assert_eq!(task, None);
3976    }
3977
3978    #[test]
3979    fn test_extract_entry_not_in_same_branch() {
3980        // Fork graph created for the test
3981        //                0
3982        //              /   \
3983        //            50     100
3984        //             |
3985        //            200
3986        let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
3987        let mut fork_graph = TestForkGraphSpecific::default();
3988        fork_graph.insert_fork(&[0, 50, 200]);
3989        fork_graph.insert_fork(&[0, 100]);
3990        let fork_graph = Arc::new(RwLock::new(fork_graph));
3991        cache.set_fork_graph(Arc::downgrade(&fork_graph));
3992
3993        let env = get_mock_program_runtime_environment();
3994        let program_id = Pubkey::new_unique();
3995        let on_other_fork = new_test_entry_with_owner(
3996            100,
3997            ProgramCacheEntryOwner::LoaderV3,
3998            new_loaded_entry(env.clone()),
3999        );
4000        cache.assign_program(&env, program_id, 100, Arc::clone(&on_other_fork));
4001
4002        // The only entry was deployed on a fork the batch is not on, which is
4003        // still evaluated *in addition to* the exact deployment slot matching.
4004        //
4005        // Once fork-tracking is removed from `extract`, `deployment_slot` is
4006        // assumed to be the slot the caller's account state reports, so naming
4007        // 100 is what places the entry here.
4008        //
4009        // Until then, it cannot be resolved since fork tracking determines it
4010        // to be on another fork.
4011        let mut search_for = vec![ProgramToLoad {
4012            program_id: &program_id,
4013            loader: ProgramCacheEntryOwner::LoaderV3,
4014            deployment_slot: 100,
4015        }];
4016        let mut extracted = ProgramCacheForTxBatch::new(200);
4017        cache.extract(&mut search_for, &mut extracted, &env, true, true);
4018        assert_eq!(search_for.len(), 1);
4019        assert!(extracted.entries.is_empty());
4020
4021        // An older deployment, on the fork the batch is on.
4022        let on_same_fork = new_test_entry_with_owner(
4023            50,
4024            ProgramCacheEntryOwner::LoaderV3,
4025            new_loaded_entry(env.clone()),
4026        );
4027        cache.assign_program(&env, program_id, 50, Arc::clone(&on_same_fork));
4028
4029        // Here the cache has the one at 50 followed by the one at 100.
4030        let slot_versions = cache.get_slot_versions_for_tests(&program_id);
4031        assert_eq!(slot_versions.len(), 2);
4032        assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &on_same_fork));
4033        assert!(Arc::ptr_eq(slot_versions.get(1).unwrap(), &on_other_fork));
4034
4035        // Asking for 50 still reaches the entry at 50, whichever fork the
4036        // one above it is on.
4037        let mut search_for = vec![ProgramToLoad {
4038            program_id: &program_id,
4039            loader: ProgramCacheEntryOwner::LoaderV3,
4040            deployment_slot: 50,
4041        }];
4042        let mut extracted = ProgramCacheForTxBatch::new(200);
4043        cache.extract(&mut search_for, &mut extracted, &env, true, true);
4044        assert!(search_for.is_empty());
4045        assert!(Arc::ptr_eq(
4046            extracted.entries.get(&program_id).unwrap(),
4047            &on_same_fork
4048        ));
4049    }
4050
4051    #[test]
4052    fn test_extract_deployment_slot_mismatch() {
4053        // We keep the cache's `latest_root_slot` at 0 and deploy a loaded
4054        // program entry for slot 100 to avoid running into the infamous
4055        // `entry.deployment_slot <= self.latest_root_slot` check.
4056        //
4057        // As such, the `entry_in_same_branch` conditional depends exclusively
4058        // on the fork graph relationship, which we set to `Ancestor` here.
4059        //
4060        // Unlike the mismatched owner test above, a mismatched deployment slot
4061        // is only a genuine miss when the targeted `deployment_slot`
4062        // is too new.
4063        //
4064        // So, we produce a scenario where `entry_in_same_branch`,
4065        // `entry_is_effective` and `matches_environment` all evaluate to
4066        // `true`, finally trapping and breaking out on
4067        // `entry.deployment_slot < program_to_load.deployment_slot`.
4068        let (mut cache, _fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
4069        assert_eq!(cache.latest_root_slot, 0);
4070        let env = get_mock_program_runtime_environment();
4071        let program_id = Pubkey::new_unique();
4072        let deployed_at_100 = new_test_entry_with_owner(
4073            100,
4074            ProgramCacheEntryOwner::LoaderV3,
4075            new_loaded_entry(env.clone()),
4076        );
4077        cache.assign_program(&env, program_id, 100, Arc::clone(&deployed_at_100));
4078
4079        // The only entry is in the same branch, effective and on the right
4080        // environment, but it is older than the search demands.
4081        // Nothing is extracted. The caller must reload.
4082        let mut search_for = vec![ProgramToLoad {
4083            program_id: &program_id,
4084            loader: ProgramCacheEntryOwner::LoaderV3,
4085            deployment_slot: 150,
4086        }];
4087        let mut extracted = ProgramCacheForTxBatch::new(200);
4088        cache.extract(&mut search_for, &mut extracted, &env, true, true);
4089        assert_eq!(search_for.len(), 1);
4090        assert!(extracted.entries.is_empty());
4091
4092        // A redeployment at the slot the search asks for.
4093        let deployed_at_150 = new_test_entry_with_owner(
4094            150,
4095            ProgramCacheEntryOwner::LoaderV3,
4096            new_loaded_entry(env.clone()),
4097        );
4098        cache.assign_program(&env, program_id, 150, Arc::clone(&deployed_at_150));
4099
4100        // Here the cache has the original entry at 100 followed by the one at
4101        // 150.
4102        let slot_versions = cache.get_slot_versions_for_tests(&program_id);
4103        assert_eq!(slot_versions.len(), 2);
4104        assert!(Arc::ptr_eq(
4105            slot_versions.first().unwrap(),
4106            &deployed_at_100
4107        ));
4108        assert!(Arc::ptr_eq(slot_versions.get(1).unwrap(), &deployed_at_150));
4109
4110        // Which is reached first, and is not older than the search demands.
4111        let mut search_for = vec![ProgramToLoad {
4112            program_id: &program_id,
4113            loader: ProgramCacheEntryOwner::LoaderV3,
4114            deployment_slot: 150,
4115        }];
4116        let mut extracted = ProgramCacheForTxBatch::new(200);
4117        cache.extract(&mut search_for, &mut extracted, &env, true, true);
4118        assert!(search_for.is_empty());
4119        assert!(Arc::ptr_eq(
4120            extracted.entries.get(&program_id).unwrap(),
4121            &deployed_at_150
4122        ));
4123    }
4124
4125    #[test]
4126    fn test_extract_older_entry_on_the_callers_fork() {
4127        // Fork graph created for the test
4128        //                0
4129        //              /   \
4130        //            50     150
4131        //             |
4132        //            200
4133        let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
4134        let mut fork_graph = TestForkGraphSpecific::default();
4135        fork_graph.insert_fork(&[0, 50, 200]);
4136        fork_graph.insert_fork(&[0, 150]);
4137        let fork_graph = Arc::new(RwLock::new(fork_graph));
4138        cache.set_fork_graph(Arc::downgrade(&fork_graph));
4139
4140        let env = get_mock_program_runtime_environment();
4141        let program_id = Pubkey::new_unique();
4142        let on_other_fork = new_test_entry_with_owner(
4143            150,
4144            ProgramCacheEntryOwner::LoaderV3,
4145            new_loaded_entry(env.clone()),
4146        );
4147        let on_same_fork = new_test_entry_with_owner(
4148            50,
4149            ProgramCacheEntryOwner::LoaderV3,
4150            new_loaded_entry(env.clone()),
4151        );
4152        cache.assign_program(&env, program_id, 150, Arc::clone(&on_other_fork));
4153        cache.assign_program(&env, program_id, 50, Arc::clone(&on_same_fork));
4154
4155        // The account on this fork names 50, so the entry at 150 on the other
4156        // fork is not what is asked for and the one at 50 is served. There is
4157        // no fallback involved: the caller named the slot it wanted.
4158        let mut search_for = vec![ProgramToLoad {
4159            program_id: &program_id,
4160            loader: ProgramCacheEntryOwner::LoaderV3,
4161            deployment_slot: 50,
4162        }];
4163        let mut extracted = ProgramCacheForTxBatch::new(200);
4164        cache.extract(&mut search_for, &mut extracted, &env, true, true);
4165        assert!(Arc::ptr_eq(
4166            extracted.entries.get(&program_id).unwrap(),
4167            &on_same_fork
4168        ));
4169
4170        // Similar to the case in `test_extract_entry_not_in_same_branch`,
4171        // because fork tracking is still evaluated *in addition to* the exact
4172        // deployment slot matching, this entry can't be extracted by a batch
4173        // in slot 200.
4174        //
4175        // Once fork-tracking is removed from `extract`, `deployment_slot` is
4176        // assumed to be the slot the caller's account state reports, so naming
4177        // 150 is what places the entry here.
4178        //
4179        // Until then, it cannot be resolved since fork tracking determines it
4180        // to be on another fork.
4181        let mut search_for = vec![ProgramToLoad {
4182            program_id: &program_id,
4183            loader: ProgramCacheEntryOwner::LoaderV3,
4184            deployment_slot: 150,
4185        }];
4186        let mut extracted = ProgramCacheForTxBatch::new(200);
4187        cache.extract(&mut search_for, &mut extracted, &env, true, true);
4188        assert_eq!(search_for.len(), 1);
4189        assert!(extracted.entries.is_empty());
4190    }
4191
4192    #[test]
4193    fn test_extract_below_deployment_slot() {
4194        let (mut cache, fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
4195        let env = get_mock_program_runtime_environment();
4196        let program_id = Pubkey::new_unique();
4197        let entry = new_test_entry_with_owner(
4198            100,
4199            ProgramCacheEntryOwner::LoaderV3,
4200            new_loaded_entry(env.clone()),
4201        );
4202        cache.assign_program(&env, program_id, 100, Arc::clone(&entry));
4203
4204        // Rooting past the entry puts it in the branch without the fork graph
4205        // being consulted.
4206        cache.prune(200, None, &fork_graph.read().unwrap());
4207        assert_eq!(cache.latest_root_slot, 200);
4208
4209        // It survives that, since the fork graph said it was an `Ancestor`.
4210        let slot_versions = cache.get_slot_versions_for_tests(&program_id);
4211        assert_eq!(slot_versions.len(), 1);
4212        assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &entry));
4213
4214        // Overwrite the fork graph to use `BlockRelation::Unknown`, to show
4215        // only the `entry.deployment_slot <= self.latest_root_slot` check is
4216        // evaluated here.
4217        fork_graph.write().unwrap().relation = BlockRelation::Unknown;
4218
4219        // The batch is below the deployment slot, so the entry is neither
4220        // effective nor a delay visibility tombstone.
4221        let mut search_for = vec![ProgramToLoad {
4222            program_id: &program_id,
4223            loader: ProgramCacheEntryOwner::LoaderV3,
4224            deployment_slot: 0,
4225        }];
4226        let mut extracted = ProgramCacheForTxBatch::new(50);
4227        cache.extract(&mut search_for, &mut extracted, &env, true, true);
4228        assert_eq!(search_for.len(), 1);
4229        assert!(extracted.entries.is_empty());
4230    }
4231
4232    #[test]
4233    fn test_extract_entry_older_than_root() {
4234        // The same setup as above, extracted from a slot above the entry
4235        // rather than below it.
4236        let (mut cache, fork_graph) = new_test_cache_with_fork_graph(BlockRelation::Ancestor);
4237        let env = get_mock_program_runtime_environment();
4238        let program_id = Pubkey::new_unique();
4239        let entry = new_test_entry_with_owner(
4240            100,
4241            ProgramCacheEntryOwner::LoaderV3,
4242            new_loaded_entry(env.clone()),
4243        );
4244        cache.assign_program(&env, program_id, 100, Arc::clone(&entry));
4245
4246        // Rooting past the entry puts it in the branch without the fork graph
4247        // being consulted.
4248        cache.prune(200, None, &fork_graph.read().unwrap());
4249        assert_eq!(cache.latest_root_slot, 200);
4250
4251        // It survives that, since the fork graph said it was an `Ancestor`.
4252        let slot_versions = cache.get_slot_versions_for_tests(&program_id);
4253        assert_eq!(slot_versions.len(), 1);
4254        assert!(Arc::ptr_eq(slot_versions.first().unwrap(), &entry));
4255
4256        // Overwrite the fork graph to use `BlockRelation::Unknown`, to show
4257        // only the `entry.deployment_slot <= self.latest_root_slot` check is
4258        // evaluated here.
4259        fork_graph.write().unwrap().relation = BlockRelation::Unknown;
4260
4261        // That check alone is still enough to serve the entry, and there is
4262        // still no telling which fork it belongs to. What keeps it correct is
4263        // that only a caller whose own account names slot 100 can ask for it,
4264        // and such a caller has that deployment on its fork by definition.
4265        let mut search_for = vec![ProgramToLoad {
4266            program_id: &program_id,
4267            loader: ProgramCacheEntryOwner::LoaderV3,
4268            deployment_slot: 100,
4269        }];
4270        let mut extracted = ProgramCacheForTxBatch::new(300);
4271        cache.extract(&mut search_for, &mut extracted, &env, true, true);
4272        assert!(search_for.is_empty());
4273        assert!(Arc::ptr_eq(
4274            extracted.entries.get(&program_id).unwrap(),
4275            &entry
4276        ));
4277    }
4278
4279    #[test]
4280    fn test_unloaded() {
4281        let mut cache = ProgramCache::<TestForkGraph>::new(0);
4282        let env = get_mock_program_runtime_environment();
4283        for program_cache_entry_type in [
4284            ProgramCacheEntryType::Closed,
4285            ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
4286        ] {
4287            let entry = Arc::new(ProgramCacheEntry {
4288                program: program_cache_entry_type,
4289                account_owner: ProgramCacheEntryOwner::LoaderV2,
4290                deployment_slot: 0,
4291                stats: Arc::default(),
4292                latest_access_slot: AtomicU64::default(),
4293            });
4294            assert!(entry.to_unloaded().is_none());
4295
4296            // Check that unload_program_entry() does nothing for this entry
4297            let program_id = Pubkey::new_unique();
4298            cache.assign_program(&env, program_id, entry.deployment_slot, entry.clone());
4299            cache.unload_program_entry(program_id, &entry);
4300            assert_eq!(cache.get_slot_versions_for_tests(&program_id).len(), 1);
4301            assert!(cache.stats.evictions.is_empty());
4302        }
4303
4304        let stats = ProgramStatistics {
4305            uses: 3.into(),
4306            ..Default::default()
4307        };
4308        let entry = new_test_entry_with_usage(1, stats);
4309        let unloaded_entry = entry.to_unloaded().unwrap();
4310        assert_eq!(unloaded_entry.deployment_slot, 1);
4311        assert_eq!(unloaded_entry.effective_slot(), 2);
4312        assert_eq!(unloaded_entry.latest_access_slot.load(Ordering::Relaxed), 1);
4313        assert_eq!(unloaded_entry.stats.uses.load(Ordering::Relaxed), 3);
4314
4315        // Check that unload_program_entry() does its work
4316        let program_id = Pubkey::new_unique();
4317        cache.assign_program(&env, program_id, entry.deployment_slot, entry.clone());
4318        cache.unload_program_entry(program_id, &entry);
4319        assert!(cache.stats.evictions.contains_key(&program_id));
4320    }
4321
4322    #[test]
4323    fn test_fork_prune_find_first_ancestor() {
4324        let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
4325        let env = get_mock_program_runtime_environment();
4326
4327        // Fork graph created for the test
4328        //                   0
4329        //                 /   \
4330        //                10    5
4331        //                |
4332        //                20
4333
4334        // Deploy program on slot 0, and slot 5.
4335        // Prune the fork that has slot 5. The cache should still have the program
4336        // deployed at slot 0.
4337        let mut fork_graph = TestForkGraphSpecific::default();
4338        fork_graph.insert_fork(&[0, 10, 20]);
4339        fork_graph.insert_fork(&[0, 5]);
4340        let fork_graph = Arc::new(RwLock::new(fork_graph));
4341        cache.set_fork_graph(Arc::downgrade(&fork_graph));
4342
4343        let program1 = Pubkey::new_unique();
4344        cache.assign_program(&env, program1, 0, new_test_entry(0));
4345        cache.assign_program(&env, program1, 5, new_test_entry(5));
4346
4347        cache.prune(10, None, &fork_graph.read().unwrap());
4348
4349        let keys = &[program1];
4350        let mut missing = get_entries_to_load(&cache, 20, keys);
4351        let mut extracted = ProgramCacheForTxBatch::new(20);
4352        cache.extract(&mut missing, &mut extracted, &env, true, true);
4353
4354        // The cache should have the program deployed at slot 0
4355        assert_eq!(
4356            extracted
4357                .find(&program1)
4358                .expect("Did not find the program")
4359                .deployment_slot,
4360            0
4361        );
4362    }
4363
4364    #[test]
4365    fn test_prune_by_deployment_slot() {
4366        let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
4367        let env = get_mock_program_runtime_environment();
4368
4369        // Fork graph created for the test
4370        //                   0
4371        //                 /   \
4372        //                10    5
4373        //                |
4374        //                20
4375
4376        // Deploy program on slot 0, and slot 5.
4377        // Prune the fork that has slot 5. The cache should still have the program
4378        // deployed at slot 0.
4379        let mut fork_graph = TestForkGraphSpecific::default();
4380        fork_graph.insert_fork(&[0, 10, 20]);
4381        fork_graph.insert_fork(&[0, 5, 6]);
4382        let fork_graph = Arc::new(RwLock::new(fork_graph));
4383        cache.set_fork_graph(Arc::downgrade(&fork_graph));
4384
4385        let program1 = Pubkey::new_unique();
4386        cache.assign_program(&env, program1, 0, new_test_entry(0));
4387        cache.assign_program(&env, program1, 5, new_test_entry(5));
4388
4389        let program2 = Pubkey::new_unique();
4390        cache.assign_program(&env, program2, 10, new_test_entry(10));
4391
4392        let keys = &[program1, program2];
4393        let mut missing = get_entries_to_load(&cache, 20, keys);
4394        let mut extracted = ProgramCacheForTxBatch::new(20);
4395        cache.extract(&mut missing, &mut extracted, &env, true, true);
4396        assert!(match_slot(&extracted, &program1, 0, 20));
4397        assert!(match_slot(&extracted, &program2, 10, 20));
4398
4399        let mut missing = get_entries_to_load(&cache, 6, keys);
4400        assert!(match_missing(&missing, &program2, false));
4401        let mut extracted = ProgramCacheForTxBatch::new(6);
4402        cache.extract(&mut missing, &mut extracted, &env, true, true);
4403        assert!(match_slot(&extracted, &program1, 5, 6));
4404
4405        // Pruning slot 5 will remove program1 entry deployed at slot 5.
4406        // On fork chaining from slot 5, the entry deployed at slot 0 will become visible.
4407        cache.prune_by_deployment_slot(5);
4408
4409        let mut missing = get_entries_to_load(&cache, 20, keys);
4410        let mut extracted = ProgramCacheForTxBatch::new(20);
4411        cache.extract(&mut missing, &mut extracted, &env, true, true);
4412        assert!(match_slot(&extracted, &program1, 0, 20));
4413        assert!(match_slot(&extracted, &program2, 10, 20));
4414
4415        let mut missing = get_entries_to_load(&cache, 6, keys);
4416        assert!(match_missing(&missing, &program2, false));
4417        let mut extracted = ProgramCacheForTxBatch::new(6);
4418        cache.extract(&mut missing, &mut extracted, &env, true, true);
4419        assert!(match_slot(&extracted, &program1, 0, 6));
4420
4421        // Pruning slot 10 will remove program2 entry deployed at slot 10.
4422        // As there is no other entry for program2, extract() will return it as missing.
4423        cache.prune_by_deployment_slot(10);
4424
4425        let mut missing = get_entries_to_load(&cache, 20, keys);
4426        assert!(match_missing(&missing, &program2, false));
4427        let mut extracted = ProgramCacheForTxBatch::new(20);
4428        cache.extract(&mut missing, &mut extracted, &env, true, true);
4429        assert!(match_slot(&extracted, &program1, 0, 20));
4430    }
4431}