Skip to main content

solana_program_runtime/
loaded_programs.rs

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