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