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