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