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