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;
117
118/// A percentage, expected to be in the range `0..=100`.
119pub type Percent = u8;
120
121/// The given percentage of [`MAX_LOADED_ENTRY_COUNT`], as an entry count.
122/// Equivalent to the former `percentage` crate's
123/// `Percentage::from(percent).apply_to(MAX_LOADED_ENTRY_COUNT)`,
124/// i.e. floor(MAX_LOADED_ENTRY_COUNT * percent / 100).
125fn percent_of_max_entries(percent: Percent) -> usize {
126    debug_assert!(percent <= 100, "percent must be <= 100");
127    MAX_LOADED_ENTRY_COUNT.saturating_mul(percent as usize) / 100
128}
129
130/// Relationship between two fork IDs
131#[derive(Copy, Clone, Debug, PartialEq)]
132pub enum BlockRelation {
133    /// The slot is on the same fork and is an ancestor of the other slot
134    Ancestor,
135    /// The two slots are equal and are on the same fork
136    Equal,
137    /// The slot is on the same fork and is a descendant of the other slot
138    Descendant,
139    /// The slots are on two different forks and may have had a common ancestor at some point
140    Unrelated,
141    /// Either one or both of the slots are either older than the latest root, or are in future
142    Unknown,
143}
144
145/// Maps relationship between two slots.
146pub trait ForkGraph {
147    /// Returns the BlockRelation of A to B
148    fn relationship(&self, a: Slot, b: Slot) -> BlockRelation;
149}
150
151/// Globally manages the transition between environments at the epoch boundary
152#[derive(Debug, Default)]
153pub struct EpochBoundaryPreparation {
154    /// The epoch of the upcoming_environment
155    pub upcoming_epoch: Epoch,
156    /// Anticipated replacement for `environments` at the next epoch
157    ///
158    /// This is `None` during most of an epoch, and only `Some` around the boundaries (at the end and beginning of an epoch).
159    /// More precisely, it starts with the cache preparation phase a few hundred slots before the epoch boundary,
160    /// and it ends with the first rerooting after the epoch boundary.
161    pub upcoming_environment: Option<ProgramRuntimeEnvironment>,
162    /// List of loaded programs which should be recompiled before the next epoch (but don't have to).
163    pub programs_to_recompile: Vec<(Pubkey, Arc<ProgramCacheEntry>)>,
164}
165
166impl EpochBoundaryPreparation {
167    pub fn new(epoch: Epoch) -> Self {
168        Self {
169            upcoming_epoch: epoch,
170            upcoming_environment: None,
171            programs_to_recompile: Vec::default(),
172        }
173    }
174
175    /// Returns the upcoming environments depending on the given epoch
176    pub fn get_upcoming_environment_for_epoch(
177        &self,
178        epoch: Epoch,
179    ) -> Option<ProgramRuntimeEnvironment> {
180        if epoch == self.upcoming_epoch {
181            return self.upcoming_environment.clone();
182        }
183        None
184    }
185
186    /// Before rerooting the blockstore this concludes the epoch boundary preparation
187    pub fn reroot(&mut self, epoch: Epoch) -> Option<ProgramRuntimeEnvironment> {
188        if epoch == self.upcoming_epoch
189            && let Some(upcoming_environment) = self.upcoming_environment.take()
190        {
191            self.programs_to_recompile.clear();
192            return Some(upcoming_environment);
193        }
194
195        None
196    }
197}
198
199/// Input of ProgramCache::extract()
200#[derive(Clone, PartialEq, Debug)]
201pub struct ProgramToLoad<'a> {
202    /// The program address
203    pub program_id: &'a Pubkey,
204    /// The program loader
205    pub loader: ProgramCacheEntryOwner,
206    /// Ignore entries deployed before this slot.
207    pub deployed_on_or_after_slot: Slot,
208    /// When the program account was last written to (might be after the deployment slot)
209    pub last_modification_slot: Slot,
210}
211
212#[derive(Debug)]
213pub(crate) enum IndexImplementation {
214    /// Fork-graph aware index implementation
215    V1 {
216        /// A two level index:
217        ///
218        /// - the first level is for the address at which programs are deployed
219        /// - the second level for the slot (and thus also fork), sorted by slot number.
220        entries: HashMap<Pubkey, Vec<Arc<ProgramCacheEntry>>>,
221        /// The entries that are getting loaded and have not yet finished loading.
222        ///
223        /// The key is the program address, the value is a tuple of the slot in which the program is
224        /// being loaded and the thread ID doing the load.
225        ///
226        /// It is possible that multiple TX batches from different slots need different versions of a
227        /// program. The deployment slot of a program is only known after load tho,
228        /// so all loads for a given program key are serialized.
229        loading_entries: Mutex<HashMap<Pubkey, (Slot, thread::ThreadId)>>,
230    },
231}
232
233/// This structure is the global cache of loaded, verified and compiled programs.
234///
235/// It ...
236/// - is validator global and fork graph aware, so it can optimize the commonalities across banks.
237/// - handles the visibility rules of un/re/deployments.
238/// - stores the usage statistics and verification status of each program.
239/// - is elastic and uses a probabilistic eviction strategy based on the usage statistics.
240/// - also keeps the compiled executables around, but only for the most used programs.
241/// - supports various kinds of tombstones to avoid loading programs which can not be loaded.
242/// - cleans up entries on orphan branches when the block store is rerooted.
243/// - supports the cache preparation phase before feature activations which can change cached programs.
244/// - manages the environments of the programs and upcoming environments for the next epoch.
245/// - allows for cooperative loading of TX batches which hit the same missing programs simultaneously.
246/// - enforces that all programs used in a batch are eagerly loaded ahead of execution.
247/// - is not persisted to disk or a snapshot, so it needs to cold start and warm up first.
248pub struct ProgramCache<FG: ForkGraph> {
249    /// Index of the cached entries and cooperative loading tasks
250    pub(crate) index: IndexImplementation,
251    /// The slot of the last rerooting
252    pub latest_root_slot: Slot,
253    /// Statistics counters
254    pub stats: ProgramCacheStats,
255    /// Reference to the block store
256    pub fork_graph: Option<Weak<RwLock<FG>>>,
257    /// Coordinates TX batches waiting for others to complete their task during cooperative loading
258    pub loading_task_waiter: Arc<LoadingTaskWaiter>,
259}
260
261impl<FG: ForkGraph> std::fmt::Debug for ProgramCache<FG> {
262    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
263        f.debug_struct("ProgramCache")
264            .field("root slot", &self.latest_root_slot)
265            .field("stats", &self.stats)
266            .field("index", &self.index)
267            .finish()
268    }
269}
270
271/// Local view into [ProgramCache] which was extracted for a specific TX batch.
272///
273/// This isolation enables the global [ProgramCache] to continue to evolve (e.g. evictions),
274/// while the TX batch is guaranteed it will continue to find all the programs it requires.
275/// For program management instructions this also buffers them before they are merged back into the global [ProgramCache].
276#[derive(Clone, Debug, Default)]
277pub struct ProgramCacheForTxBatch {
278    /// Pubkey is the address of a program.
279    /// ProgramCacheEntry is the corresponding program entry valid for the slot in which a transaction is being executed.
280    entries: HashMap<Pubkey, Arc<ProgramCacheEntry>>,
281    /// Program entries modified during the transaction batch.
282    modified_entries: HashMap<Pubkey, Arc<ProgramCacheEntry>>,
283    slot: Slot,
284    pub hit_max_limit: bool,
285    pub loaded_missing: bool,
286    pub merged_modified: bool,
287}
288
289impl ProgramCacheForTxBatch {
290    pub fn new(slot: Slot) -> Self {
291        Self {
292            entries: HashMap::new(),
293            modified_entries: HashMap::new(),
294            slot,
295            hit_max_limit: false,
296            loaded_missing: false,
297            merged_modified: false,
298        }
299    }
300
301    /// Refill the cache with a single entry. It's typically called during transaction loading, and
302    /// transaction processing (for program management instructions).
303    /// It replaces the existing entry (if any) with the provided entry. The return value contains
304    /// `true` if an entry existed.
305    /// The function also returns the newly inserted value.
306    pub fn replenish(
307        &mut self,
308        key: Pubkey,
309        entry: Arc<ProgramCacheEntry>,
310    ) -> (bool, Arc<ProgramCacheEntry>) {
311        (self.entries.insert(key, entry.clone()).is_some(), entry)
312    }
313
314    /// Store an entry in `modified_entries` for a program modified during the
315    /// transaction batch.
316    pub fn store_modified_entry(&mut self, key: Pubkey, entry: Arc<ProgramCacheEntry>) {
317        self.modified_entries.insert(key, entry);
318    }
319
320    /// Drain the program cache's modified entries, returning the owned
321    /// collection.
322    pub fn drain_modified_entries(&mut self) -> HashMap<Pubkey, Arc<ProgramCacheEntry>> {
323        std::mem::take(&mut self.modified_entries)
324    }
325
326    pub fn find(&self, key: &Pubkey) -> Option<Arc<ProgramCacheEntry>> {
327        // First lookup the cache of the programs modified by the current
328        // transaction. If not found, lookup the cache of the cache of the
329        // programs that are loaded for the transaction batch.
330        self.modified_entries
331            .get(key)
332            .or_else(|| self.entries.get(key))
333            .map(|entry| {
334                if entry.is_implicit_delay_visibility_tombstone(self.slot) {
335                    // Found a program entry on the current fork, but it's not effective
336                    // yet. It indicates that the program has delayed visibility. Return
337                    // the tombstone to reflect that.
338                    Arc::new(ProgramCacheEntry::new_delay_visibility_tombstone(
339                        entry.deployment_slot,
340                        entry.account_owner,
341                        Arc::clone(&entry.stats),
342                    ))
343                } else {
344                    entry.clone()
345                }
346            })
347    }
348
349    pub fn slot(&self) -> Slot {
350        self.slot
351    }
352
353    pub fn set_slot_for_tests(&mut self, slot: Slot) {
354        self.slot = slot;
355    }
356
357    pub fn merge(&mut self, modified_entries: &HashMap<Pubkey, Arc<ProgramCacheEntry>>) {
358        modified_entries.iter().for_each(|(key, entry)| {
359            self.merged_modified = true;
360            self.replenish(*key, entry.clone());
361        })
362    }
363
364    pub fn is_empty(&self) -> bool {
365        self.entries.is_empty()
366    }
367}
368
369impl<FG: ForkGraph> ProgramCache<FG> {
370    pub fn new(root_slot: Slot) -> Self {
371        Self {
372            index: IndexImplementation::V1 {
373                entries: HashMap::new(),
374                loading_entries: Mutex::new(HashMap::new()),
375            },
376            latest_root_slot: root_slot,
377            stats: ProgramCacheStats::default(),
378            fork_graph: None,
379            loading_task_waiter: Arc::new(LoadingTaskWaiter::default()),
380        }
381    }
382
383    pub fn set_fork_graph(&mut self, fork_graph: Weak<RwLock<FG>>) {
384        self.fork_graph = Some(fork_graph);
385    }
386
387    /// Insert a single entry. It's typically called during transaction loading,
388    /// when the cache doesn't contain the entry corresponding to program `key`.
389    pub fn assign_program(
390        &mut self,
391        program_runtime_environment: &ProgramRuntimeEnvironment,
392        key: Pubkey,
393        _last_modification_slot: Slot,
394        entry: Arc<ProgramCacheEntry>,
395    ) -> bool {
396        debug_assert!(!matches!(
397            &entry.program,
398            ProgramCacheEntryType::DelayVisibility
399        ));
400        // This function always returns `true` during normal operation.
401        // Only during the cache preparation phase this can return `false`
402        // for entries with `upcoming_environment`.
403        fn is_current_env(
404            program_runtime_environment: &ProgramRuntimeEnvironment,
405            env_opt: Option<&ProgramRuntimeEnvironment>,
406        ) -> bool {
407            env_opt
408                .map(|env| env == program_runtime_environment)
409                .unwrap_or(true)
410        }
411        match &mut self.index {
412            IndexImplementation::V1 { entries, .. } => {
413                let slot_versions = &mut entries.entry(key).or_default();
414                let insertion_point = slot_versions.binary_search_by(|at| {
415                    at.deployment_slot
416                        .cmp(&entry.deployment_slot)
417                        .then(at.account_owner.cmp(&entry.account_owner))
418                        .then(
419                            // This `.then()` has no effect during normal operation.
420                            // Only during the cache preparation phase this does allow entries
421                            // which only differ in their environment to be interleaved in `slot_versions`.
422                            is_current_env(
423                                program_runtime_environment,
424                                at.program.get_environment(),
425                            )
426                            .cmp(&is_current_env(
427                                program_runtime_environment,
428                                entry.program.get_environment(),
429                            )),
430                        )
431                });
432                match insertion_point {
433                    Ok(index) => {
434                        let existing = slot_versions.get_mut(index).unwrap();
435                        match (&existing.program, &entry.program) {
436                            (
437                                ProgramCacheEntryType::Builtin(_),
438                                ProgramCacheEntryType::Builtin(_),
439                            )
440                            | (ProgramCacheEntryType::Closed, ProgramCacheEntryType::Unloaded(_))
441                            | (
442                                ProgramCacheEntryType::Unloaded(_),
443                                ProgramCacheEntryType::Loaded(_),
444                            )
445                            | (
446                                ProgramCacheEntryType::Unloaded(_),
447                                ProgramCacheEntryType::FailedVerification(_),
448                            ) => {}
449                            _ => {
450                                // Something is wrong, I can feel it ...
451                                error!(
452                                    "ProgramCache::assign_program() failed key={key:?} \
453                                     existing={slot_versions:?} entry={entry:?}"
454                                );
455                                debug_assert!(false, "Unexpected replacement of an entry");
456                                self.stats.replacements.fetch_add(1, Ordering::Relaxed);
457                                return true;
458                            }
459                        }
460                        entry.stats.merge_from(&existing.stats);
461                        *existing = Arc::clone(&entry);
462                        self.stats.reloads.fetch_add(1, Ordering::Relaxed);
463                    }
464                    Err(index) => {
465                        self.stats.insertions.fetch_add(1, Ordering::Relaxed);
466                        slot_versions.insert(index, Arc::clone(&entry));
467                    }
468                }
469                // Remove existing entries in the same deployment slot unless they are for a different
470                // environment.
471                // This overwrites the current status of a program in program management instructions.
472                slot_versions.retain(|existing| {
473                    existing.deployment_slot != entry.deployment_slot
474                        || existing
475                            .program
476                            .get_environment()
477                            .zip(entry.program.get_environment())
478                            .map(|(a, b)| a != b)
479                            .unwrap_or(false)
480                        || Arc::ptr_eq(existing, &entry)
481                });
482            }
483        }
484        false
485    }
486
487    pub fn prune_by_deployment_slot(&mut self, slot: Slot) {
488        match &mut self.index {
489            IndexImplementation::V1 { entries, .. } => {
490                for second_level in entries.values_mut() {
491                    second_level.retain(|entry| entry.deployment_slot != slot);
492                }
493                self.remove_programs_with_no_entries();
494            }
495        }
496    }
497
498    /// Before rerooting the blockstore this removes all superfluous entries
499    pub fn prune(
500        &mut self,
501        new_root_slot: Slot,
502        new_environment: Option<ProgramRuntimeEnvironment>,
503        fork_graph: &FG,
504    ) {
505        match &mut self.index {
506            IndexImplementation::V1 { entries, .. } => {
507                for second_level in entries.values_mut() {
508                    // Remove entries un/re/deployed on orphan forks
509                    let mut first_ancestor_found = false;
510                    let mut first_ancestor_env = None;
511                    *second_level = second_level
512                        .iter()
513                        .rev()
514                        .filter(|entry| {
515                            let relation =
516                                fork_graph.relationship(entry.deployment_slot, new_root_slot);
517                            if entry.deployment_slot >= new_root_slot {
518                                matches!(relation, BlockRelation::Equal | BlockRelation::Descendant)
519                            } else if matches!(relation, BlockRelation::Ancestor)
520                                || entry.deployment_slot <= self.latest_root_slot
521                            {
522                                if !first_ancestor_found {
523                                    first_ancestor_found = true;
524                                    first_ancestor_env = entry.program.get_environment();
525                                    return true;
526                                }
527                                // Do not prune the entry if the runtime environment of the entry is
528                                // different than the entry that was previously found (stored in
529                                // first_ancestor_env). Different environment indicates that this entry
530                                // might belong to an older epoch that had a different environment (e.g.
531                                // different feature set). Once the root moves to the new/current epoch,
532                                // the entry will get pruned. But, until then the entry might still be
533                                // getting used by an older slot.
534                                if let Some(entry_env) = entry.program.get_environment()
535                                    && let Some(env) = first_ancestor_env
536                                    && entry_env != env
537                                {
538                                    return true;
539                                }
540                                self.stats.prunes_orphan.fetch_add(1, Ordering::Relaxed);
541                                false
542                            } else {
543                                self.stats.prunes_orphan.fetch_add(1, Ordering::Relaxed);
544                                false
545                            }
546                        })
547                        .filter(|entry| {
548                            // Remove outdated environment of previous feature set
549                            if let Some(new_environment) = new_environment.as_ref()
550                                && !Self::matches_environment(entry, new_environment)
551                            {
552                                self.stats
553                                    .prunes_environment
554                                    .fetch_add(1, Ordering::Relaxed);
555                                return false;
556                            }
557                            true
558                        })
559                        .cloned()
560                        .collect();
561                    second_level.reverse();
562                }
563            }
564        }
565        self.remove_programs_with_no_entries();
566        debug_assert!(self.latest_root_slot <= new_root_slot);
567        self.latest_root_slot = new_root_slot;
568    }
569
570    fn matches_environment(
571        entry: &Arc<ProgramCacheEntry>,
572        program_runtime_environment: &ProgramRuntimeEnvironment,
573    ) -> bool {
574        let Some(environment) = entry.program.get_environment() else {
575            return true;
576        };
577        environment == program_runtime_environment
578    }
579
580    /// Extracts a subset of the programs relevant to a transaction batch
581    /// and returns which program accounts the accounts DB needs to load.
582    pub fn extract(
583        &self,
584        search_for: &mut Vec<ProgramToLoad>,
585        loaded_programs_for_tx_batch: &mut ProgramCacheForTxBatch,
586        program_runtime_environment_for_execution: &ProgramRuntimeEnvironment,
587        increment_usage_counter: bool,
588        count_hits_and_misses: bool,
589    ) -> Option<Pubkey> {
590        debug_assert!(self.fork_graph.is_some());
591        let fork_graph = self.fork_graph.as_ref().unwrap().upgrade().unwrap();
592        let locked_fork_graph = fork_graph.read().unwrap();
593        let mut cooperative_loading_task = None;
594        match &self.index {
595            IndexImplementation::V1 {
596                entries,
597                loading_entries,
598            } => {
599                search_for.retain(|program_to_load| {
600                    if let Some(second_level) = entries.get(program_to_load.program_id) {
601                        let mut filter_by_deployment_slot = None;
602                        for entry in second_level.iter().rev() {
603                            let required_deployment_slot =
604                                filter_by_deployment_slot.unwrap_or(entry.deployment_slot);
605                            if required_deployment_slot != entry.deployment_slot
606                                || program_to_load.loader != entry.account_owner
607                            {
608                                continue;
609                            }
610                            let entry_in_same_branch = entry.deployment_slot
611                                <= self.latest_root_slot
612                                || matches!(
613                                    locked_fork_graph.relationship(
614                                        entry.deployment_slot,
615                                        loaded_programs_for_tx_batch.slot
616                                    ),
617                                    BlockRelation::Equal | BlockRelation::Ancestor
618                                );
619                            if entry_in_same_branch {
620                                let entry_is_effective =
621                                    loaded_programs_for_tx_batch.slot >= entry.effective_slot();
622                                let entry_to_return = if entry_is_effective {
623                                    if !Self::matches_environment(
624                                        entry,
625                                        program_runtime_environment_for_execution,
626                                    ) {
627                                        // We found an entry that would work, had its environment matched
628                                        // the one we're planning to use for this slot.
629                                        //
630                                        // At this point we know that whatever the "current version" of
631                                        // program is, it must have had a deployment slot equal to the
632                                        // program we're looking at in this iteration. We just have to find
633                                        // one with the correct environment and can skip entries for any
634                                        // other deployment slot while searching further.
635                                        filter_by_deployment_slot = filter_by_deployment_slot
636                                            .or(Some(entry.deployment_slot));
637                                        continue;
638                                    }
639                                    if entry.deployment_slot
640                                        < program_to_load.deployed_on_or_after_slot
641                                    {
642                                        break;
643                                    }
644                                    if let ProgramCacheEntryType::Unloaded(_environment) =
645                                        &entry.program
646                                    {
647                                        break;
648                                    }
649                                    entry.clone()
650                                } else if entry.is_implicit_delay_visibility_tombstone(
651                                    loaded_programs_for_tx_batch.slot,
652                                ) {
653                                    // Found a program entry on the current fork, but it's not effective
654                                    // yet. It indicates that the program has delayed visibility. Return
655                                    // the tombstone to reflect that.
656                                    Arc::new(ProgramCacheEntry::new_delay_visibility_tombstone(
657                                        entry.deployment_slot,
658                                        entry.account_owner,
659                                        Arc::clone(&entry.stats),
660                                    ))
661                                } else {
662                                    continue;
663                                };
664                                entry_to_return
665                                    .update_access_slot(loaded_programs_for_tx_batch.slot);
666                                if increment_usage_counter {
667                                    entry_to_return.stats.uses.fetch_add(1, Ordering::Relaxed);
668                                }
669                                loaded_programs_for_tx_batch
670                                    .entries
671                                    .insert(*program_to_load.program_id, entry_to_return);
672                                return false;
673                            }
674                        }
675                    }
676                    if cooperative_loading_task.is_none() {
677                        let mut loading_entries = loading_entries.lock().unwrap();
678                        let entry = loading_entries.entry(*program_to_load.program_id);
679                        if let Entry::Vacant(entry) = entry {
680                            entry.insert((
681                                loaded_programs_for_tx_batch.slot,
682                                thread::current().id(),
683                            ));
684                            cooperative_loading_task = Some(*program_to_load.program_id);
685                        }
686                    }
687                    true
688                });
689            }
690        }
691        drop(locked_fork_graph);
692        if count_hits_and_misses {
693            self.stats
694                .misses
695                .fetch_add(search_for.len() as u64, Ordering::Relaxed);
696            self.stats.hits.fetch_add(
697                loaded_programs_for_tx_batch.entries.len() as u64,
698                Ordering::Relaxed,
699            );
700        }
701        cooperative_loading_task
702    }
703
704    /// Called by Bank::replenish_program_cache() for each program that is done loading.
705    pub fn finish_cooperative_loading_task(
706        &mut self,
707        program_runtime_environment: &ProgramRuntimeEnvironment,
708        current_slot: Slot,
709        key: Pubkey,
710        last_modification_slot: Slot,
711        loaded_program: Arc<ProgramCacheEntry>,
712    ) -> bool {
713        match &mut self.index {
714            IndexImplementation::V1 {
715                loading_entries, ..
716            } => {
717                let loading_thread = loading_entries.get_mut().unwrap().remove(&key);
718                debug_assert_eq!(loading_thread, Some((current_slot, thread::current().id())));
719                // Check that it will be visible to our own fork once inserted
720                if loaded_program.deployment_slot > self.latest_root_slot
721                    && !matches!(
722                        self.fork_graph
723                            .as_ref()
724                            .unwrap()
725                            .upgrade()
726                            .unwrap()
727                            .read()
728                            .unwrap()
729                            .relationship(loaded_program.deployment_slot, current_slot),
730                        BlockRelation::Equal | BlockRelation::Ancestor
731                    )
732                {
733                    self.stats.lost_insertions.fetch_add(1, Ordering::Relaxed);
734                }
735                let was_occupied = self.assign_program(
736                    program_runtime_environment,
737                    key,
738                    last_modification_slot,
739                    loaded_program,
740                );
741                self.loading_task_waiter.notify();
742                was_occupied
743            }
744        }
745    }
746
747    pub fn merge(
748        &mut self,
749        program_runtime_environment: &ProgramRuntimeEnvironment,
750        current_slot: Slot,
751        modified_entries: &HashMap<Pubkey, Arc<ProgramCacheEntry>>,
752    ) {
753        modified_entries.iter().for_each(|(key, entry)| {
754            self.assign_program(
755                program_runtime_environment,
756                *key,
757                current_slot,
758                entry.clone(),
759            );
760        })
761    }
762
763    /// Returns the list of entries which are verified and compiled.
764    pub fn get_flattened_entries(&self) -> Vec<(Pubkey, Slot, Arc<ProgramCacheEntry>)> {
765        match &self.index {
766            IndexImplementation::V1 { entries, .. } => entries
767                .iter()
768                .flat_map(|(id, second_level)| {
769                    second_level
770                        .iter()
771                        .filter_map(move |program| match program.program {
772                            ProgramCacheEntryType::Loaded(_) => Some((*id, 0, program.clone())),
773                            _ => None,
774                        })
775                })
776                .collect(),
777        }
778    }
779
780    /// Returns the list of all entries in the cache.
781    #[cfg(feature = "dev-context-only-utils")]
782    pub fn get_flattened_entries_for_tests(&self) -> Vec<(Pubkey, Arc<ProgramCacheEntry>)> {
783        match &self.index {
784            IndexImplementation::V1 { entries, .. } => entries
785                .iter()
786                .flat_map(|(id, second_level)| {
787                    second_level.iter().map(|program| (*id, program.clone()))
788                })
789                .collect(),
790        }
791    }
792
793    /// Returns the slot versions for the given program id.
794    pub fn get_slot_versions_for_tests(&self, key: &Pubkey) -> &[Arc<ProgramCacheEntry>] {
795        match &self.index {
796            IndexImplementation::V1 { entries, .. } => entries
797                .get(key)
798                .map(|second_level| second_level.as_ref())
799                .unwrap_or(&[]),
800        }
801    }
802
803    /// Unloads programs which were used infrequently
804    pub fn sort_and_unload(&mut self, shrink_to_percent: Percent) {
805        let mut sorted_candidates = self.get_flattened_entries();
806        sorted_candidates.sort_by_cached_key(|(_id, _last_modification_slot, program)| {
807            program.stats.uses.load(Ordering::Relaxed)
808        });
809        let num_to_unload = sorted_candidates
810            .len()
811            .saturating_sub(percent_of_max_entries(shrink_to_percent));
812        for (program, last_modification_slot, entry) in sorted_candidates.iter().take(num_to_unload)
813        {
814            self.unload_program_entry(*program, *last_modification_slot, entry);
815        }
816    }
817
818    /// Evicts programs using random selection, choosing the worst scoring program out of the
819    /// entries sampled.
820    ///
821    /// The eviction is performed enough number of times to reduce the cache usage to the given
822    /// percentage.
823    pub fn evict_using_random_selection(&mut self, shrink_to_percent: Percent, now: Slot) {
824        let mut candidates = self.get_flattened_entries();
825        let mut rng = rng();
826        self.stats
827            .water_level
828            .store(candidates.len() as u64, Ordering::Relaxed);
829        let num_to_unload = candidates
830            .len()
831            .saturating_sub(percent_of_max_entries(shrink_to_percent));
832        let mut sample_entry = |candidates: &Vec<(Pubkey, u64, Arc<ProgramCacheEntry>)>| {
833            // gen_range is deprecated in favor of random_range in rand>=0.9, but we also get
834            // rnd() from shuttle, which doesn't yet support rand 0.9 APIs
835            #[cfg(feature = "shuttle-test")]
836            let index = rng.gen_range(0..candidates.len());
837            #[cfg(not(feature = "shuttle-test"))]
838            let index = rng.random_range(0..candidates.len());
839            let usage_counter = candidates
840                .get(index)
841                .expect("Failed to get cached entry")
842                .2
843                .retention_score();
844            (index, usage_counter)
845        };
846
847        // Random sampling with just 2 choices can frequently lead to a situation where both
848        // entries chosen have relatively high retention scores, having us to pick one out of two
849        // poor options. We can tell what a relatively high retention score is, so we can make a
850        // few additional samples until we hit some other entry that isn't as highly scoring.
851        //
852        // Note that the "high enough" compilation time and use count numbers used here are
853        // relatively arbitrary.
854        const MAX_ADDITIONAL_SAMPLES: usize = 3;
855        let avoid_evicting_above_score = retention_score(now, 500 * EMA_SCALE, 500);
856        for _ in 0..num_to_unload {
857            let (mut index, mut score) = sample_entry(&candidates);
858            for _ in 0..MAX_ADDITIONAL_SAMPLES {
859                let (sample_index, sample_score) = sample_entry(&candidates);
860                if score > sample_score {
861                    index = sample_index;
862                    score = sample_score;
863                }
864                if score < avoid_evicting_above_score {
865                    break;
866                }
867            }
868            let (id, last_modification_slot, entry) = candidates.swap_remove(index);
869            self.unload_program_entry(id, last_modification_slot, &entry);
870        }
871    }
872
873    /// Removes all the entries at the given keys, if they exist
874    pub fn remove_programs(&mut self, keys: impl Iterator<Item = Pubkey>) {
875        match &mut self.index {
876            IndexImplementation::V1 { entries, .. } => {
877                for k in keys {
878                    entries.remove(&k);
879                }
880            }
881        }
882    }
883
884    /// This function removes the given entry for the given program from the cache.
885    /// The function expects that the program and entry exists in the cache. Otherwise it'll panic.
886    fn unload_program_entry(
887        &mut self,
888        id: Pubkey,
889        _last_modification_slot: Slot,
890        remove_entry: &Arc<ProgramCacheEntry>,
891    ) {
892        match &mut self.index {
893            IndexImplementation::V1 { entries, .. } => {
894                let second_level = entries.get_mut(&id).expect("Cache lookup failed");
895                let candidate = second_level
896                    .iter_mut()
897                    .find(|entry| Arc::ptr_eq(entry, remove_entry))
898                    .expect("Program entry not found");
899
900                // Only loaded entries shall be unloaded by eviction.
901                if let ProgramCacheEntryType::Loaded(_) = candidate.program
902                    && let Some(unloaded) = candidate.to_unloaded()
903                {
904                    if candidate.stats.uses.load(Ordering::Relaxed) == 1 {
905                        self.stats.one_hit_wonders.fetch_add(1, Ordering::Relaxed);
906                    }
907                    self.stats
908                        .evictions
909                        .entry(id)
910                        .and_modify(|c| *c = c.saturating_add(1))
911                        .or_insert(1);
912                    *candidate = Arc::new(unloaded);
913                }
914            }
915        }
916    }
917
918    fn remove_programs_with_no_entries(&mut self) {
919        match &mut self.index {
920            IndexImplementation::V1 { entries, .. } => {
921                let num_programs_before_removal = entries.len();
922                entries.retain(|_key, second_level| !second_level.is_empty());
923                if entries.len() < num_programs_before_removal {
924                    self.stats.empty_entries.fetch_add(
925                        num_programs_before_removal.saturating_sub(entries.len()) as u64,
926                        Ordering::Relaxed,
927                    );
928                }
929            }
930        }
931    }
932}
933
934#[cfg(feature = "frozen-abi")]
935impl solana_frozen_abi::abi_example::AbiExample for ProgramCacheEntry {
936    fn example() -> Self {
937        // ProgramCacheEntry isn't serializable by definition.
938        Self::default()
939    }
940}
941
942#[cfg(feature = "frozen-abi")]
943impl<FG: ForkGraph> solana_frozen_abi::abi_example::AbiExample for ProgramCache<FG> {
944    fn example() -> Self {
945        // ProgramCache isn't serializable by definition.
946        Self::new(Slot::default())
947    }
948}
949
950#[cfg(test)]
951pub(crate) mod tests {
952    use {
953        crate::{
954            loaded_programs::{
955                BlockRelation, ForkGraph, Percent, ProgramCache, ProgramCacheForTxBatch,
956                ProgramRuntimeEnvironment, ProgramToLoad, get_mock_program_runtime_environment,
957            },
958            program_cache_entry::{
959                ProgramCacheEntry, ProgramCacheEntryOwner, ProgramCacheEntryType,
960            },
961            program_metrics::ProgramStatistics,
962        },
963        assert_matches::assert_matches,
964        solana_clock::Slot,
965        solana_pubkey::Pubkey,
966        solana_sbpf::{elf::Executable, program::BuiltinProgram},
967        solana_svm_type_overrides::sync::{
968            Arc, RwLock,
969            atomic::{AtomicU64, Ordering},
970        },
971        std::{fs::File, io::Read, ops::ControlFlow},
972        test_case::test_matrix,
973    };
974
975    fn new_test_entry(deployment_slot: Slot) -> Arc<ProgramCacheEntry> {
976        new_test_entry_with_usage(deployment_slot, ProgramStatistics::default())
977    }
978
979    fn new_loaded_entry(env: ProgramRuntimeEnvironment) -> ProgramCacheEntryType {
980        let mut elf = Vec::new();
981        File::open("../programs/bpf_loader/test_elfs/out/noop_aligned.so")
982            .unwrap()
983            .read_to_end(&mut elf)
984            .unwrap();
985        let executable = Executable::load(&elf, Arc::clone(&*env)).unwrap();
986        ProgramCacheEntryType::Loaded(executable)
987    }
988
989    pub(crate) fn new_test_entry_with_usage(
990        deployment_slot: Slot,
991        stats: ProgramStatistics,
992    ) -> Arc<ProgramCacheEntry> {
993        Arc::new(ProgramCacheEntry {
994            program: new_loaded_entry(get_mock_program_runtime_environment()),
995            account_owner: ProgramCacheEntryOwner::LoaderV2,
996            deployment_slot,
997            stats: Arc::new(stats),
998            latest_access_slot: AtomicU64::new(deployment_slot),
999        })
1000    }
1001
1002    fn new_test_builtin_entry(deployment_slot: Slot) -> Arc<ProgramCacheEntry> {
1003        Arc::new(ProgramCacheEntry {
1004            program: ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1005            account_owner: ProgramCacheEntryOwner::NativeLoader,
1006            deployment_slot,
1007            stats: Arc::default(),
1008            latest_access_slot: AtomicU64::default(),
1009        })
1010    }
1011
1012    fn set_failed_verification_tombstone<FG: ForkGraph>(
1013        cache: &mut ProgramCache<FG>,
1014        key: Pubkey,
1015        current_slot: Slot,
1016        env: ProgramRuntimeEnvironment,
1017    ) -> Arc<ProgramCacheEntry> {
1018        let program = Arc::new(ProgramCacheEntry::new_failed_verification_tombstone(
1019            current_slot,
1020            ProgramCacheEntryOwner::LoaderV2,
1021            ProgramRuntimeEnvironment::clone(&env),
1022        ));
1023        cache.assign_program(&env, key, current_slot, program.clone());
1024        program
1025    }
1026
1027    fn insert_unloaded_entry<FG: ForkGraph>(
1028        cache: &mut ProgramCache<FG>,
1029        key: Pubkey,
1030        current_slot: Slot,
1031    ) -> Arc<ProgramCacheEntry> {
1032        let env = get_mock_program_runtime_environment();
1033        let loaded = new_test_entry_with_usage(current_slot, ProgramStatistics::default());
1034        let unloaded = Arc::new(loaded.to_unloaded().expect("Failed to unload the program"));
1035        cache.assign_program(&env, key, current_slot, unloaded.clone());
1036        unloaded
1037    }
1038
1039    fn num_matching_entries<P, FG>(cache: &ProgramCache<FG>, predicate: P) -> usize
1040    where
1041        P: Fn(&ProgramCacheEntryType) -> bool,
1042        FG: ForkGraph,
1043    {
1044        cache
1045            .get_flattened_entries_for_tests()
1046            .iter()
1047            .filter(|(_key, program)| predicate(&program.program))
1048            .count()
1049    }
1050
1051    #[expect(clippy::arithmetic_side_effects)]
1052    fn program_deploy_test_helper(
1053        cache: &mut ProgramCache<TestForkGraph>,
1054        program: Pubkey,
1055        deployment_slots: Vec<Slot>,
1056        usage_counters: Vec<u64>,
1057        programs: &mut Vec<(Pubkey, Slot, u64)>,
1058    ) {
1059        let env = get_mock_program_runtime_environment();
1060        // Add multiple entries for program
1061        deployment_slots
1062            .iter()
1063            .enumerate()
1064            .for_each(|(i, deployment_slot)| {
1065                let usage_counter = *usage_counters.get(i).unwrap_or(&0);
1066                let stats = ProgramStatistics {
1067                    uses: usage_counter.into(),
1068                    ..Default::default()
1069                };
1070                cache.assign_program(
1071                    &env,
1072                    program,
1073                    *deployment_slot,
1074                    new_test_entry_with_usage(*deployment_slot, stats),
1075                );
1076                programs.push((program, *deployment_slot, usage_counter));
1077            });
1078
1079        let next_slot = deployment_slots.iter().max().map_or(0, |slot| slot + 1);
1080
1081        // Add tombstones entries for program
1082        let env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
1083        for slot in next_slot..next_slot + 10 {
1084            set_failed_verification_tombstone(
1085                cache,
1086                program,
1087                slot,
1088                ProgramRuntimeEnvironment::clone(&env),
1089            );
1090        }
1091
1092        // Add unloaded entries for program
1093        for slot in next_slot + 10..next_slot + 20 {
1094            insert_unloaded_entry(cache, program, slot);
1095        }
1096    }
1097
1098    #[test]
1099    fn test_random_eviction() {
1100        let mut programs = vec![];
1101        let mut cache = ProgramCache::<TestForkGraph>::new(0);
1102
1103        // This test adds different kind of entries to the cache.
1104        // Tombstones and unloaded entries are expected to not be evicted.
1105        // It also adds multiple entries for three programs as it tries to create a typical cache instance.
1106
1107        // Program 1
1108        program_deploy_test_helper(
1109            &mut cache,
1110            Pubkey::new_unique(),
1111            vec![0, 10, 20, 30, 40],
1112            vec![4, 5, 25, 35, 12],
1113            &mut programs,
1114        );
1115
1116        // Program 2
1117        program_deploy_test_helper(
1118            &mut cache,
1119            Pubkey::new_unique(),
1120            vec![5, 11, 21, 24],
1121            vec![0, 2, 30, 45],
1122            &mut programs,
1123        );
1124
1125        // Program 3
1126        program_deploy_test_helper(
1127            &mut cache,
1128            Pubkey::new_unique(),
1129            vec![0, 5, 15, 25],
1130            vec![100, 3, 20, 40],
1131            &mut programs,
1132        );
1133
1134        // 1 for each deployment slot
1135        let num_loaded_expected = 13;
1136        // 10 for each program
1137        let num_unloaded_expected = 30;
1138        // 10 for each program
1139        let num_tombstones_expected = 30;
1140
1141        // Count the number of loaded, unloaded and tombstone entries.
1142        programs.sort_by_key(|(_id, _slot, usage_count)| *usage_count);
1143        let num_loaded = num_matching_entries(&cache, |program_type| {
1144            matches!(program_type, ProgramCacheEntryType::Loaded(_))
1145        });
1146        let num_unloaded = num_matching_entries(&cache, |program_type| {
1147            matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1148        });
1149        let num_tombstones = num_matching_entries(&cache, |program_type| {
1150            matches!(
1151                program_type,
1152                ProgramCacheEntryType::DelayVisibility
1153                    | ProgramCacheEntryType::FailedVerification(_)
1154                    | ProgramCacheEntryType::Closed
1155            )
1156        });
1157
1158        // Test that the cache is constructed with the expected number of entries.
1159        assert_eq!(num_loaded, num_loaded_expected);
1160        assert_eq!(num_unloaded, num_unloaded_expected);
1161        assert_eq!(num_tombstones, num_tombstones_expected);
1162
1163        // Evict entries from the cache
1164        let eviction_pct: Percent = 1;
1165
1166        let num_loaded_expected = crate::loaded_programs::percent_of_max_entries(eviction_pct);
1167        let num_unloaded_expected = num_unloaded_expected + num_loaded - num_loaded_expected;
1168        cache.evict_using_random_selection(eviction_pct, 21);
1169
1170        // Count the number of loaded, unloaded and tombstone entries.
1171        let num_loaded = num_matching_entries(&cache, |program_type| {
1172            matches!(program_type, ProgramCacheEntryType::Loaded(_))
1173        });
1174        let num_unloaded = num_matching_entries(&cache, |program_type| {
1175            matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1176        });
1177        let num_tombstones = num_matching_entries(&cache, |program_type| {
1178            matches!(program_type, ProgramCacheEntryType::FailedVerification(_))
1179        });
1180
1181        // However many entries are left after the shrink
1182        assert_eq!(num_loaded, num_loaded_expected);
1183        // The original unloaded entries + the evicted loaded entries
1184        assert_eq!(num_unloaded, num_unloaded_expected);
1185        // The original tombstones are not evicted
1186        assert_eq!(num_tombstones, num_tombstones_expected);
1187    }
1188
1189    #[test]
1190    fn test_eviction() {
1191        let mut programs = vec![];
1192        let mut cache = ProgramCache::<TestForkGraph>::new(0);
1193
1194        // Program 1
1195        program_deploy_test_helper(
1196            &mut cache,
1197            Pubkey::new_unique(),
1198            vec![0, 10, 20, 30, 40],
1199            vec![4, 5, 25, 35, 12],
1200            &mut programs,
1201        );
1202
1203        // Program 2
1204        program_deploy_test_helper(
1205            &mut cache,
1206            Pubkey::new_unique(),
1207            vec![5, 11, 21, 24],
1208            vec![0, 2, 30, 45],
1209            &mut programs,
1210        );
1211
1212        // Program 3
1213        program_deploy_test_helper(
1214            &mut cache,
1215            Pubkey::new_unique(),
1216            vec![0, 5, 15, 25],
1217            vec![100, 3, 20, 40],
1218            &mut programs,
1219        );
1220
1221        // 1 for each deployment slot
1222        let num_loaded_expected = 13;
1223        // 10 for each program
1224        let num_unloaded_expected = 30;
1225        // 10 for each program
1226        let num_tombstones_expected = 30;
1227
1228        // Count the number of loaded, unloaded and tombstone entries.
1229        programs.sort_by_key(|(_id, _slot, usage_count)| *usage_count);
1230        let num_loaded = num_matching_entries(&cache, |program_type| {
1231            matches!(program_type, ProgramCacheEntryType::Loaded(_))
1232        });
1233        let num_unloaded = num_matching_entries(&cache, |program_type| {
1234            matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1235        });
1236        let num_tombstones = num_matching_entries(&cache, |program_type| {
1237            matches!(program_type, ProgramCacheEntryType::FailedVerification(_))
1238        });
1239
1240        // Test that the cache is constructed with the expected number of entries.
1241        assert_eq!(num_loaded, num_loaded_expected);
1242        assert_eq!(num_unloaded, num_unloaded_expected);
1243        assert_eq!(num_tombstones, num_tombstones_expected);
1244
1245        // Evict entries from the cache
1246        let eviction_pct: Percent = 1;
1247
1248        let num_loaded_expected = crate::loaded_programs::percent_of_max_entries(eviction_pct);
1249        let num_unloaded_expected = num_unloaded_expected + num_loaded - num_loaded_expected;
1250
1251        cache.sort_and_unload(eviction_pct);
1252
1253        // Check that every program is still in the cache.
1254        let entries = cache.get_flattened_entries_for_tests();
1255        programs.iter().for_each(|entry| {
1256            assert!(entries.iter().any(|(key, _entry)| key == &entry.0));
1257        });
1258
1259        let unloaded = entries
1260            .iter()
1261            .filter_map(|(key, program)| {
1262                matches!(program.program, ProgramCacheEntryType::Unloaded(_))
1263                    .then_some((*key, program.stats.uses.load(Ordering::Relaxed)))
1264            })
1265            .collect::<Vec<(Pubkey, u64)>>();
1266
1267        for index in 0..3 {
1268            let expected = programs.get(index).expect("Missing program");
1269            assert!(unloaded.contains(&(expected.0, expected.2)));
1270        }
1271
1272        // Count the number of loaded, unloaded and tombstone entries.
1273        let num_loaded = num_matching_entries(&cache, |program_type| {
1274            matches!(program_type, ProgramCacheEntryType::Loaded(_))
1275        });
1276        let num_unloaded = num_matching_entries(&cache, |program_type| {
1277            matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1278        });
1279        let num_tombstones = num_matching_entries(&cache, |program_type| {
1280            matches!(
1281                program_type,
1282                ProgramCacheEntryType::DelayVisibility
1283                    | ProgramCacheEntryType::FailedVerification(_)
1284                    | ProgramCacheEntryType::Closed
1285            )
1286        });
1287
1288        // However many entries are left after the shrink
1289        assert_eq!(num_loaded, num_loaded_expected);
1290        // The original unloaded entries + the evicted loaded entries
1291        assert_eq!(num_unloaded, num_unloaded_expected);
1292        // The original tombstones are not evicted
1293        assert_eq!(num_tombstones, num_tombstones_expected);
1294    }
1295
1296    #[test]
1297    fn test_usage_count_of_unloaded_program() {
1298        let mut cache = ProgramCache::<TestForkGraph>::new(0);
1299        let env = get_mock_program_runtime_environment();
1300
1301        let program = Pubkey::new_unique();
1302        let evict_to_pct: Percent = 2;
1303        let cache_capacity_after_shrink =
1304            crate::loaded_programs::percent_of_max_entries(evict_to_pct);
1305        // Add enough programs to the cache to trigger 1 eviction after shrinking.
1306        let num_total_programs = (cache_capacity_after_shrink + 1) as u64;
1307        (0..num_total_programs).for_each(|i| {
1308            let stats = ProgramStatistics {
1309                uses: (i + 10).into(),
1310                ..Default::default()
1311            };
1312            let entry = new_test_entry_with_usage(i, stats);
1313            cache.assign_program(&env, program, i, entry);
1314        });
1315
1316        cache.sort_and_unload(evict_to_pct);
1317
1318        let num_unloaded = num_matching_entries(&cache, |program_type| {
1319            matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1320        });
1321        assert_eq!(num_unloaded, 1);
1322
1323        cache
1324            .get_flattened_entries_for_tests()
1325            .iter()
1326            .for_each(|(_key, program)| {
1327                if matches!(program.program, ProgramCacheEntryType::Unloaded(_)) {
1328                    // Test that the usage counter is retained for the unloaded program
1329                    assert_eq!(program.stats.uses.load(Ordering::Relaxed), 10);
1330                    assert_eq!(program.deployment_slot, 0);
1331                    assert_eq!(program.effective_slot(), 1);
1332                }
1333            });
1334
1335        // Replenish the program that was just unloaded. Use 0 as the usage counter. This should be
1336        // updated with the usage counter from the unloaded program.
1337        cache.assign_program(
1338            &env,
1339            program,
1340            0,
1341            new_test_entry_with_usage(0, ProgramStatistics::default()),
1342        );
1343
1344        cache
1345            .get_flattened_entries_for_tests()
1346            .iter()
1347            .for_each(|(_key, program)| {
1348                if matches!(program.program, ProgramCacheEntryType::Unloaded(_))
1349                    && program.deployment_slot == 0
1350                    && program.effective_slot() == 1
1351                {
1352                    // Test that the usage counter was correctly updated.
1353                    assert_eq!(program.stats.uses.load(Ordering::Relaxed), 10);
1354                }
1355            });
1356    }
1357
1358    #[test]
1359    fn test_fuzz_assign_program_order() {
1360        use rand::prelude::SliceRandom;
1361        const EXPECTED_ENTRIES: [(u64, bool); 5] =
1362            [(1, true), (3, false), (5, true), (9, true), (10, false)];
1363        let mut rng = rand::rng();
1364        let program_id = Pubkey::new_unique();
1365        let env = get_mock_program_runtime_environment();
1366        for _ in 0..1000 {
1367            let mut entries = EXPECTED_ENTRIES.to_vec();
1368            entries.shuffle(&mut rng);
1369            let mut cache = ProgramCache::<TestForkGraph>::new(0);
1370            for (deployment_slot, delay_visibility) in entries {
1371                let entry = Arc::new(if delay_visibility {
1372                    ProgramCacheEntry {
1373                        program: new_loaded_entry(ProgramRuntimeEnvironment::from(
1374                            BuiltinProgram::new_mock(),
1375                        )), // Assign them different environments
1376                        account_owner: ProgramCacheEntryOwner::LoaderV2,
1377                        deployment_slot,
1378                        stats: Arc::default(),
1379                        latest_access_slot: AtomicU64::new(deployment_slot),
1380                    }
1381                } else {
1382                    ProgramCacheEntry::new_failed_verification_tombstone(
1383                        deployment_slot,
1384                        ProgramCacheEntryOwner::LoaderV2,
1385                        ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock()), // Assign them different environments
1386                    )
1387                });
1388                assert!(!cache.assign_program(&env, program_id, deployment_slot, entry));
1389            }
1390            for ((deployment_slot, delay_visibility), entry) in EXPECTED_ENTRIES
1391                .iter()
1392                .zip(cache.get_slot_versions_for_tests(&program_id).iter())
1393            {
1394                assert_eq!(entry.deployment_slot, *deployment_slot);
1395                assert_eq!(
1396                    entry.effective_slot(),
1397                    deployment_slot.saturating_add(*delay_visibility as u64)
1398                );
1399            }
1400        }
1401    }
1402
1403    #[test_matrix(
1404        (
1405            ProgramCacheEntryType::FailedVerification(get_mock_program_runtime_environment()),
1406            new_loaded_entry(get_mock_program_runtime_environment()),
1407        ),
1408        (
1409            ProgramCacheEntryType::FailedVerification(get_mock_program_runtime_environment()),
1410            ProgramCacheEntryType::Closed,
1411            ProgramCacheEntryType::Unloaded(get_mock_program_runtime_environment()),
1412            new_loaded_entry(get_mock_program_runtime_environment()),
1413            ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1414        )
1415    )]
1416    #[test_matrix(
1417        ProgramCacheEntryType::Closed,
1418        (
1419            ProgramCacheEntryType::FailedVerification(get_mock_program_runtime_environment()),
1420            ProgramCacheEntryType::Closed,
1421            new_loaded_entry(get_mock_program_runtime_environment()),
1422            ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1423        )
1424    )]
1425    #[test_matrix(
1426        ProgramCacheEntryType::Unloaded(get_mock_program_runtime_environment()),
1427        (
1428            ProgramCacheEntryType::Closed,
1429            ProgramCacheEntryType::Unloaded(get_mock_program_runtime_environment()),
1430            ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1431        )
1432    )]
1433    #[test_matrix(
1434        (ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),),
1435        (
1436            ProgramCacheEntryType::FailedVerification(get_mock_program_runtime_environment()),
1437            ProgramCacheEntryType::Closed,
1438            ProgramCacheEntryType::Unloaded(get_mock_program_runtime_environment()),
1439            new_loaded_entry(get_mock_program_runtime_environment()),
1440        )
1441    )]
1442    #[should_panic(expected = "Unexpected replacement of an entry")]
1443    fn test_assign_program_failure(old: ProgramCacheEntryType, new: ProgramCacheEntryType) {
1444        let mut cache = ProgramCache::<TestForkGraph>::new(0);
1445        let env = get_mock_program_runtime_environment();
1446        let program_id = Pubkey::new_unique();
1447        assert!(!cache.assign_program(
1448            &env,
1449            program_id,
1450            10,
1451            Arc::new(ProgramCacheEntry {
1452                program: old,
1453                account_owner: ProgramCacheEntryOwner::LoaderV2,
1454                deployment_slot: 10,
1455                stats: Arc::default(),
1456                latest_access_slot: AtomicU64::default(),
1457            }),
1458        ));
1459        cache.assign_program(
1460            &env,
1461            program_id,
1462            10,
1463            Arc::new(ProgramCacheEntry {
1464                program: new,
1465                account_owner: ProgramCacheEntryOwner::LoaderV2,
1466                deployment_slot: 10,
1467                stats: Arc::default(),
1468                latest_access_slot: AtomicU64::default(),
1469            }),
1470        );
1471    }
1472
1473    #[test_matrix(
1474        ProgramCacheEntryType::Unloaded(get_mock_program_runtime_environment()),
1475        (
1476            new_loaded_entry(get_mock_program_runtime_environment()),
1477            ProgramCacheEntryType::FailedVerification(get_mock_program_runtime_environment()),
1478        )
1479    )]
1480    #[test_case(
1481        ProgramCacheEntryType::Closed,
1482        ProgramCacheEntryType::Unloaded(get_mock_program_runtime_environment())
1483    )]
1484    #[test_case(
1485        ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1486        ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock())
1487    )]
1488    fn test_assign_program_success(old: ProgramCacheEntryType, new: ProgramCacheEntryType) {
1489        let mut cache = ProgramCache::<TestForkGraph>::new(0);
1490        let env = get_mock_program_runtime_environment();
1491        let program_id = Pubkey::new_unique();
1492        assert!(!cache.assign_program(
1493            &env,
1494            program_id,
1495            10,
1496            Arc::new(ProgramCacheEntry {
1497                program: old,
1498                account_owner: ProgramCacheEntryOwner::LoaderV2,
1499                deployment_slot: 10,
1500                stats: Arc::default(),
1501                latest_access_slot: AtomicU64::default(),
1502            }),
1503        ));
1504        assert!(!cache.assign_program(
1505            &env,
1506            program_id,
1507            10,
1508            Arc::new(ProgramCacheEntry {
1509                program: new,
1510                account_owner: ProgramCacheEntryOwner::LoaderV2,
1511                deployment_slot: 10,
1512                stats: Arc::default(),
1513                latest_access_slot: AtomicU64::default(),
1514            }),
1515        ));
1516    }
1517
1518    #[test]
1519    fn test_assign_program_removes_entries_in_same_slot() {
1520        let mut cache = ProgramCache::<TestForkGraph>::new(0);
1521        let env = get_mock_program_runtime_environment();
1522        let program_id = Pubkey::new_unique();
1523        let closed_other_slot = Arc::new(ProgramCacheEntry {
1524            program: ProgramCacheEntryType::Closed,
1525            account_owner: ProgramCacheEntryOwner::LoaderV2,
1526            deployment_slot: 9,
1527            stats: Arc::default(),
1528            latest_access_slot: AtomicU64::default(),
1529        });
1530        let closed_current_slot = Arc::new(ProgramCacheEntry {
1531            program: ProgramCacheEntryType::Closed,
1532            account_owner: ProgramCacheEntryOwner::LoaderV2,
1533            deployment_slot: 10,
1534            stats: Arc::default(),
1535            latest_access_slot: AtomicU64::default(),
1536        });
1537        let loaded_entry_current_env = Arc::new(ProgramCacheEntry {
1538            program: ProgramCacheEntryType::Unloaded(get_mock_program_runtime_environment()),
1539            account_owner: ProgramCacheEntryOwner::LoaderV2,
1540            deployment_slot: 10,
1541            stats: Arc::default(),
1542            latest_access_slot: AtomicU64::default(),
1543        });
1544        let loaded_entry_upcoming_env = Arc::new(ProgramCacheEntry {
1545            program: ProgramCacheEntryType::Unloaded(ProgramRuntimeEnvironment::from(
1546                BuiltinProgram::new_mock(),
1547            )),
1548            account_owner: ProgramCacheEntryOwner::LoaderV2,
1549            deployment_slot: 10,
1550            stats: Arc::default(),
1551            latest_access_slot: AtomicU64::default(),
1552        });
1553        assert!(!cache.assign_program(&env, program_id, 9, closed_other_slot.clone()));
1554        assert!(!cache.assign_program(&env, program_id, 10, closed_current_slot));
1555        assert!(!cache.assign_program(&env, program_id, 10, loaded_entry_upcoming_env.clone()));
1556        assert!(!cache.assign_program(&env, program_id, 10, loaded_entry_current_env.clone()));
1557        // Only the conflicting entry in the same slot which does not have a different environment is removed
1558        assert_eq!(
1559            cache.get_slot_versions_for_tests(&program_id),
1560            &[
1561                closed_other_slot,
1562                loaded_entry_current_env,
1563                loaded_entry_upcoming_env
1564            ]
1565        );
1566    }
1567
1568    #[test]
1569    fn test_tombstone() {
1570        let env = get_mock_program_runtime_environment();
1571        let tombstone = ProgramCacheEntry::new_failed_verification_tombstone(
1572            0,
1573            ProgramCacheEntryOwner::LoaderV2,
1574            env.clone(),
1575        );
1576        assert_matches!(
1577            tombstone.program,
1578            ProgramCacheEntryType::FailedVerification(_)
1579        );
1580        assert!(tombstone.is_tombstone());
1581        assert_eq!(tombstone.deployment_slot, 0);
1582        assert_eq!(tombstone.effective_slot(), 0);
1583
1584        let tombstone =
1585            ProgramCacheEntry::new_closed_tombstone(100, ProgramCacheEntryOwner::LoaderV2);
1586        assert_matches!(tombstone.program, ProgramCacheEntryType::Closed);
1587        assert!(tombstone.is_tombstone());
1588        assert_eq!(tombstone.deployment_slot, 100);
1589        assert_eq!(tombstone.effective_slot(), 100);
1590
1591        let mut cache = ProgramCache::<TestForkGraph>::new(0);
1592        let program1 = Pubkey::new_unique();
1593        let tombstone = set_failed_verification_tombstone(&mut cache, program1, 10, env.clone());
1594        let slot_versions = cache.get_slot_versions_for_tests(&program1);
1595        assert_eq!(slot_versions.len(), 1);
1596        assert!(slot_versions.first().unwrap().is_tombstone());
1597        assert_eq!(tombstone.deployment_slot, 10);
1598        assert_eq!(tombstone.effective_slot(), 10);
1599
1600        // Add a program at slot 50, and a tombstone for the program at slot 60
1601        let program2 = Pubkey::new_unique();
1602        cache.assign_program(&env, program2, 50, new_test_builtin_entry(50));
1603        let slot_versions = cache.get_slot_versions_for_tests(&program2);
1604        assert_eq!(slot_versions.len(), 1);
1605        assert!(!slot_versions.first().unwrap().is_tombstone());
1606
1607        let tombstone = set_failed_verification_tombstone(&mut cache, program2, 60, env);
1608        let slot_versions = cache.get_slot_versions_for_tests(&program2);
1609        assert_eq!(slot_versions.len(), 2);
1610        assert!(!slot_versions.first().unwrap().is_tombstone());
1611        assert!(slot_versions.get(1).unwrap().is_tombstone());
1612        assert!(tombstone.is_tombstone());
1613        assert_eq!(tombstone.deployment_slot, 60);
1614        assert_eq!(tombstone.effective_slot(), 60);
1615    }
1616
1617    struct TestForkGraph {
1618        relation: BlockRelation,
1619    }
1620    impl ForkGraph for TestForkGraph {
1621        fn relationship(&self, _a: Slot, _b: Slot) -> BlockRelation {
1622            self.relation
1623        }
1624    }
1625
1626    #[test]
1627    fn test_prune_empty() {
1628        let mut cache = ProgramCache::<TestForkGraph>::new(0);
1629        let fork_graph = Arc::new(RwLock::new(TestForkGraph {
1630            relation: BlockRelation::Unrelated,
1631        }));
1632
1633        cache.set_fork_graph(Arc::downgrade(&fork_graph));
1634
1635        cache.prune(0, None, &fork_graph.read().unwrap());
1636        assert!(cache.get_flattened_entries_for_tests().is_empty());
1637
1638        cache.prune(10, None, &fork_graph.read().unwrap());
1639        assert!(cache.get_flattened_entries_for_tests().is_empty());
1640
1641        let mut cache = ProgramCache::<TestForkGraph>::new(0);
1642        let fork_graph = Arc::new(RwLock::new(TestForkGraph {
1643            relation: BlockRelation::Ancestor,
1644        }));
1645
1646        cache.set_fork_graph(Arc::downgrade(&fork_graph));
1647
1648        cache.prune(0, None, &fork_graph.read().unwrap());
1649        assert!(cache.get_flattened_entries_for_tests().is_empty());
1650
1651        cache.prune(10, None, &fork_graph.read().unwrap());
1652        assert!(cache.get_flattened_entries_for_tests().is_empty());
1653
1654        let mut cache = ProgramCache::<TestForkGraph>::new(0);
1655        let fork_graph = Arc::new(RwLock::new(TestForkGraph {
1656            relation: BlockRelation::Descendant,
1657        }));
1658
1659        cache.set_fork_graph(Arc::downgrade(&fork_graph));
1660
1661        cache.prune(0, None, &fork_graph.read().unwrap());
1662        assert!(cache.get_flattened_entries_for_tests().is_empty());
1663
1664        cache.prune(10, None, &fork_graph.read().unwrap());
1665        assert!(cache.get_flattened_entries_for_tests().is_empty());
1666
1667        let mut cache = ProgramCache::<TestForkGraph>::new(0);
1668        let fork_graph = Arc::new(RwLock::new(TestForkGraph {
1669            relation: BlockRelation::Unknown,
1670        }));
1671        cache.set_fork_graph(Arc::downgrade(&fork_graph));
1672
1673        cache.prune(0, None, &fork_graph.read().unwrap());
1674        assert!(cache.get_flattened_entries_for_tests().is_empty());
1675
1676        cache.prune(10, None, &fork_graph.read().unwrap());
1677        assert!(cache.get_flattened_entries_for_tests().is_empty());
1678    }
1679
1680    #[test]
1681    fn test_prune_with_two_environments_before_epoch_boundary() {
1682        let mut cache = ProgramCache::<TestForkGraph>::new(0);
1683        let env = get_mock_program_runtime_environment();
1684        let new_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
1685        let fork_graph = Arc::new(RwLock::new(TestForkGraph {
1686            relation: BlockRelation::Ancestor,
1687        }));
1688        cache.set_fork_graph(Arc::downgrade(&fork_graph));
1689
1690        let program1 = Pubkey::new_unique();
1691        cache.assign_program(&env, program1, 10, new_test_entry(10));
1692        let updated_program = Arc::new(ProgramCacheEntry {
1693            program: new_loaded_entry(new_env.clone()),
1694            deployment_slot: 20,
1695            ..Default::default()
1696        });
1697        cache.assign_program(&env, program1, 20, updated_program.clone());
1698
1699        // Test that there are 2 entries for the program
1700        assert_eq!(cache.get_slot_versions_for_tests(&program1).len(), 2);
1701
1702        cache.prune(21, None, &fork_graph.read().unwrap());
1703
1704        // Test that prune didn't remove the entry, since environments are different.
1705        assert_eq!(cache.get_slot_versions_for_tests(&program1).len(), 2);
1706    }
1707
1708    #[test]
1709    fn test_prune_with_two_environments_after_epoch_boundary() {
1710        let mut cache = ProgramCache::<TestForkGraph>::new(0);
1711        let env = get_mock_program_runtime_environment();
1712        let new_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
1713        let fork_graph = Arc::new(RwLock::new(TestForkGraph {
1714            relation: BlockRelation::Ancestor,
1715        }));
1716        cache.set_fork_graph(Arc::downgrade(&fork_graph));
1717        let program1 = Pubkey::new_unique();
1718
1719        let old_program_old_env = Arc::new(ProgramCacheEntry {
1720            program: new_loaded_entry(env.clone()),
1721            deployment_slot: 10,
1722            ..Default::default()
1723        });
1724        let old_program_new_env = Arc::new(ProgramCacheEntry {
1725            program: new_loaded_entry(new_env.clone()),
1726            deployment_slot: 10,
1727            ..Default::default()
1728        });
1729        let new_program_old_env = Arc::new(ProgramCacheEntry {
1730            program: new_loaded_entry(env.clone()),
1731            deployment_slot: 20,
1732            ..Default::default()
1733        });
1734        cache.assign_program(&env, program1, 10, old_program_old_env.clone());
1735        cache.assign_program(&env, program1, 10, old_program_new_env.clone());
1736        cache.assign_program(&env, program1, 20, new_program_old_env.clone());
1737        let slot_versions = cache.get_slot_versions_for_tests(&program1);
1738        assert_eq!(
1739            &slot_versions,
1740            &[
1741                old_program_new_env.clone(),
1742                old_program_old_env.clone(),
1743                new_program_old_env.clone(),
1744            ]
1745        );
1746
1747        cache.prune(21, Some(new_env.clone()), &fork_graph.read().unwrap());
1748        let slot_versions = cache.get_slot_versions_for_tests(&program1);
1749        assert_eq!(&slot_versions, &[old_program_new_env]);
1750        assert!(matches!(
1751            &slot_versions.first().unwrap().program,
1752            ProgramCacheEntryType::Loaded(_)
1753        ));
1754    }
1755
1756    #[derive(Default)]
1757    struct TestForkGraphSpecific {
1758        forks: Vec<Vec<Slot>>,
1759    }
1760
1761    impl TestForkGraphSpecific {
1762        fn insert_fork(&mut self, fork: &[Slot]) {
1763            let mut fork = fork.to_vec();
1764            fork.sort();
1765            self.forks.push(fork)
1766        }
1767    }
1768
1769    impl ForkGraph for TestForkGraphSpecific {
1770        fn relationship(&self, a: Slot, b: Slot) -> BlockRelation {
1771            match self.forks.iter().try_for_each(|fork| {
1772                let relation = fork
1773                    .iter()
1774                    .position(|x| *x == a)
1775                    .and_then(|a_pos| {
1776                        fork.iter().position(|x| *x == b).and_then(|b_pos| {
1777                            (a_pos == b_pos)
1778                                .then_some(BlockRelation::Equal)
1779                                .or_else(|| (a_pos < b_pos).then_some(BlockRelation::Ancestor))
1780                                .or(Some(BlockRelation::Descendant))
1781                        })
1782                    })
1783                    .unwrap_or(BlockRelation::Unrelated);
1784
1785                if relation != BlockRelation::Unrelated {
1786                    return ControlFlow::Break(relation);
1787                }
1788
1789                ControlFlow::Continue(())
1790            }) {
1791                ControlFlow::Break(relation) => relation,
1792                _ => BlockRelation::Unrelated,
1793            }
1794        }
1795    }
1796
1797    fn get_entries_to_load<'a>(
1798        cache: &ProgramCache<TestForkGraphSpecific>,
1799        loading_slot: Slot,
1800        keys: &'a [Pubkey],
1801    ) -> Vec<ProgramToLoad<'a>> {
1802        let fork_graph = cache.fork_graph.as_ref().unwrap().upgrade().unwrap();
1803        let locked_fork_graph = fork_graph.read().unwrap();
1804        let entries = cache.get_flattened_entries_for_tests();
1805        keys.iter()
1806            .filter_map(|key| {
1807                entries
1808                    .iter()
1809                    .rev()
1810                    .find(|(program_id, entry)| {
1811                        program_id == key
1812                            && matches!(
1813                                locked_fork_graph.relationship(entry.deployment_slot, loading_slot),
1814                                BlockRelation::Equal | BlockRelation::Ancestor,
1815                            )
1816                    })
1817                    .map(|(_program_id, entry)| ProgramToLoad {
1818                        program_id: key,
1819                        loader: entry.account_owner,
1820                        deployed_on_or_after_slot: 0,
1821                        last_modification_slot: entry.deployment_slot,
1822                    })
1823            })
1824            .collect()
1825    }
1826
1827    fn match_slot(
1828        extracted: &ProgramCacheForTxBatch,
1829        program: &Pubkey,
1830        deployment_slot: Slot,
1831        working_slot: Slot,
1832    ) -> bool {
1833        assert_eq!(extracted.slot, working_slot);
1834        extracted
1835            .entries
1836            .get(program)
1837            .map(|entry| entry.deployment_slot == deployment_slot)
1838            .unwrap_or(false)
1839    }
1840
1841    fn match_missing(
1842        missing: &[ProgramToLoad],
1843        program_id: &Pubkey,
1844        expected_result: bool,
1845    ) -> bool {
1846        missing.iter().any(|entry| entry.program_id == program_id) == expected_result
1847    }
1848
1849    #[test]
1850    fn test_fork_extract_and_prune() {
1851        let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
1852        let env = get_mock_program_runtime_environment();
1853
1854        // Fork graph created for the test
1855        //                   0
1856        //                 /   \
1857        //                10    5
1858        //                |     |
1859        //                20    11
1860        //                |     | \
1861        //                22   15  25
1862        //                      |   |
1863        //                     16  27
1864        //                      |
1865        //                     19
1866        //                      |
1867        //                     23
1868
1869        let mut fork_graph = TestForkGraphSpecific::default();
1870        fork_graph.insert_fork(&[0, 10, 20, 22]);
1871        fork_graph.insert_fork(&[0, 5, 11, 15, 16, 18, 19, 21, 23]);
1872        fork_graph.insert_fork(&[0, 5, 11, 25, 27]);
1873
1874        let fork_graph = Arc::new(RwLock::new(fork_graph));
1875        cache.set_fork_graph(Arc::downgrade(&fork_graph));
1876
1877        let program1 = Pubkey::new_unique();
1878        cache.assign_program(&env, program1, 0, new_test_entry(0));
1879        cache.assign_program(&env, program1, 10, new_test_entry(10));
1880        cache.assign_program(&env, program1, 20, new_test_entry(20));
1881
1882        let program2 = Pubkey::new_unique();
1883        cache.assign_program(&env, program2, 5, new_test_entry(5));
1884        cache.assign_program(&env, program2, 11, new_test_entry(11));
1885
1886        let program3 = Pubkey::new_unique();
1887        cache.assign_program(&env, program3, 25, new_test_entry(25));
1888
1889        let program4 = Pubkey::new_unique();
1890        cache.assign_program(&env, program4, 0, new_test_entry(0));
1891        cache.assign_program(&env, program4, 5, new_test_entry(5));
1892        // The following is a special case, where effective slot is 3 slots in the future
1893        cache.assign_program(&env, program4, 15, new_test_entry(15));
1894
1895        // Current fork graph
1896        //                   0
1897        //                 /   \
1898        //                10    5
1899        //                |     |
1900        //                20    11
1901        //                |     | \
1902        //                22   15  25
1903        //                      |   |
1904        //                     16  27
1905        //                      |
1906        //                     19
1907        //                      |
1908        //                     23
1909
1910        // Testing fork 0 - 10 - 20 - 22 with current slot at 22
1911        let keys = &[program1, program2, program3, program4];
1912        let mut missing = get_entries_to_load(&cache, 22, keys);
1913        assert!(match_missing(&missing, &program2, false));
1914        assert!(match_missing(&missing, &program3, false));
1915        let mut extracted = ProgramCacheForTxBatch::new(22);
1916        cache.extract(&mut missing, &mut extracted, &env, true, true);
1917        assert!(match_slot(&extracted, &program1, 20, 22));
1918        assert!(match_slot(&extracted, &program4, 0, 22));
1919
1920        // Testing fork 0 - 5 - 11 - 15 - 16 with current slot at 15
1921        let mut missing = get_entries_to_load(&cache, 15, keys);
1922        assert!(match_missing(&missing, &program3, false));
1923        let mut extracted = ProgramCacheForTxBatch::new(15);
1924        cache.extract(&mut missing, &mut extracted, &env, true, true);
1925        assert!(match_slot(&extracted, &program1, 0, 15));
1926        assert!(match_slot(&extracted, &program2, 11, 15));
1927        // The effective slot of program4 deployed in slot 15 is 19. So it should not be usable in slot 16.
1928        // A delay visibility tombstone should be returned here.
1929        let tombstone = extracted
1930            .find(&program4)
1931            .expect("Failed to find the tombstone");
1932        assert_matches!(tombstone.program, ProgramCacheEntryType::DelayVisibility);
1933        assert_eq!(tombstone.deployment_slot, 15);
1934
1935        // Testing the same fork above, but current slot is now 18 (equal to effective slot of program4).
1936        let mut missing = get_entries_to_load(&cache, 18, keys);
1937        assert!(match_missing(&missing, &program3, false));
1938        let mut extracted = ProgramCacheForTxBatch::new(18);
1939        cache.extract(&mut missing, &mut extracted, &env, true, true);
1940        assert!(match_slot(&extracted, &program1, 0, 18));
1941        assert!(match_slot(&extracted, &program2, 11, 18));
1942        // The effective slot of program4 deployed in slot 15 is 18. So it should be usable in slot 18.
1943        assert!(match_slot(&extracted, &program4, 15, 18));
1944
1945        // Testing the same fork above, but current slot is now 23 (future slot than effective slot of program4).
1946        let mut missing = get_entries_to_load(&cache, 23, keys);
1947        assert!(match_missing(&missing, &program3, false));
1948        let mut extracted = ProgramCacheForTxBatch::new(23);
1949        cache.extract(&mut missing, &mut extracted, &env, true, true);
1950        assert!(match_slot(&extracted, &program1, 0, 23));
1951        assert!(match_slot(&extracted, &program2, 11, 23));
1952        // The effective slot of program4 deployed in slot 15 is 19. So it should be usable in slot 23.
1953        assert!(match_slot(&extracted, &program4, 15, 23));
1954
1955        // Testing fork 0 - 5 - 11 - 15 - 16 with current slot at 11
1956        let mut missing = get_entries_to_load(&cache, 11, keys);
1957        assert!(match_missing(&missing, &program3, false));
1958        let mut extracted = ProgramCacheForTxBatch::new(11);
1959        cache.extract(&mut missing, &mut extracted, &env, true, true);
1960        assert!(match_slot(&extracted, &program1, 0, 11));
1961        // program2 was updated at slot 11, but is not effective till slot 12. The result should contain a tombstone.
1962        let tombstone = extracted
1963            .find(&program2)
1964            .expect("Failed to find the tombstone");
1965        assert_matches!(tombstone.program, ProgramCacheEntryType::DelayVisibility);
1966        assert_eq!(tombstone.deployment_slot, 11);
1967        assert!(match_slot(&extracted, &program4, 5, 11));
1968
1969        cache.prune(5, None, &fork_graph.read().unwrap());
1970
1971        // Fork graph after pruning
1972        //                   0
1973        //                   |
1974        //                   5
1975        //                   |
1976        //                   11
1977        //                   | \
1978        //                  15  25
1979        //                   |   |
1980        //                  16  27
1981        //                   |
1982        //                  19
1983        //                   |
1984        //                  23
1985
1986        // Testing fork 11 - 15 - 16- 19 - 22 with root at 5 and current slot at 22
1987        let mut missing = get_entries_to_load(&cache, 21, keys);
1988        assert!(match_missing(&missing, &program3, false));
1989        let mut extracted = ProgramCacheForTxBatch::new(21);
1990        cache.extract(&mut missing, &mut extracted, &env, true, true);
1991        // Since the fork was pruned, we should not find the entry deployed at slot 20.
1992        assert!(match_slot(&extracted, &program1, 0, 21));
1993        assert!(match_slot(&extracted, &program2, 11, 21));
1994        assert!(match_slot(&extracted, &program4, 15, 21));
1995
1996        // Testing fork 0 - 5 - 11 - 25 - 27 with current slot at 27
1997        let mut missing = get_entries_to_load(&cache, 27, keys);
1998        let mut extracted = ProgramCacheForTxBatch::new(27);
1999        cache.extract(&mut missing, &mut extracted, &env, true, true);
2000        assert!(match_slot(&extracted, &program1, 0, 27));
2001        assert!(match_slot(&extracted, &program2, 11, 27));
2002        assert!(match_slot(&extracted, &program3, 25, 27));
2003        assert!(match_slot(&extracted, &program4, 5, 27));
2004
2005        cache.prune(15, None, &fork_graph.read().unwrap());
2006
2007        // Fork graph after pruning
2008        //                  0
2009        //                  |
2010        //                  5
2011        //                  |
2012        //                  11
2013        //                  |
2014        //                  15
2015        //                  |
2016        //                  16
2017        //                  |
2018        //                  19
2019        //                  |
2020        //                  23
2021
2022        // Testing fork 16, 19, 23, with root at 15, current slot at 23
2023        let mut missing = get_entries_to_load(&cache, 23, keys);
2024        assert!(match_missing(&missing, &program3, false));
2025        let mut extracted = ProgramCacheForTxBatch::new(23);
2026        cache.extract(&mut missing, &mut extracted, &env, true, true);
2027        assert!(match_slot(&extracted, &program1, 0, 23));
2028        assert!(match_slot(&extracted, &program2, 11, 23));
2029        assert!(match_slot(&extracted, &program4, 15, 23));
2030    }
2031
2032    #[test]
2033    fn test_extract_using_deployment_slot() {
2034        let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
2035        let env = get_mock_program_runtime_environment();
2036
2037        // Fork graph created for the test
2038        //                   0
2039        //                 /   \
2040        //                10    5
2041        //                |     |
2042        //                20    11
2043        //                |     | \
2044        //                22   15  25
2045        //                      |   |
2046        //                     16  27
2047        //                      |
2048        //                     19
2049        //                      |
2050        //                     23
2051
2052        let mut fork_graph = TestForkGraphSpecific::default();
2053        fork_graph.insert_fork(&[0, 10, 20, 22]);
2054        fork_graph.insert_fork(&[0, 5, 11, 12, 15, 16, 18, 19, 21, 23]);
2055        fork_graph.insert_fork(&[0, 5, 11, 25, 27]);
2056
2057        let fork_graph = Arc::new(RwLock::new(fork_graph));
2058        cache.set_fork_graph(Arc::downgrade(&fork_graph));
2059
2060        let program1 = Pubkey::new_unique();
2061        cache.assign_program(&env, program1, 0, new_test_entry(0));
2062        cache.assign_program(&env, program1, 20, new_test_entry(20));
2063
2064        let program2 = Pubkey::new_unique();
2065        cache.assign_program(&env, program2, 5, new_test_entry(5));
2066        cache.assign_program(&env, program2, 11, new_test_entry(11));
2067
2068        let program3 = Pubkey::new_unique();
2069        cache.assign_program(&env, program3, 25, new_test_entry(25));
2070
2071        // Testing fork 0 - 5 - 11 - 15 - 16 - 19 - 21 - 23 with current slot at 19
2072        let keys = &[program1, program2, program3];
2073        let mut missing = get_entries_to_load(&cache, 12, keys);
2074        assert!(match_missing(&missing, &program3, false));
2075        let mut extracted = ProgramCacheForTxBatch::new(12);
2076        cache.extract(&mut missing, &mut extracted, &env, true, true);
2077        assert!(match_slot(&extracted, &program1, 0, 12));
2078        assert!(match_slot(&extracted, &program2, 11, 12));
2079
2080        // Test the same fork, but request the program modified at a later slot than what's in the cache.
2081        let mut missing = get_entries_to_load(&cache, 12, keys);
2082        missing.get_mut(0).unwrap().deployed_on_or_after_slot = 5;
2083        missing.get_mut(1).unwrap().deployed_on_or_after_slot = 5;
2084        assert!(match_missing(&missing, &program3, false));
2085        let mut extracted = ProgramCacheForTxBatch::new(12);
2086        cache.extract(&mut missing, &mut extracted, &env, true, true);
2087        assert!(match_missing(&missing, &program1, true));
2088        assert!(match_slot(&extracted, &program2, 11, 12));
2089    }
2090
2091    #[test]
2092    fn test_extract_unloaded() {
2093        let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
2094        let env = get_mock_program_runtime_environment();
2095
2096        // Fork graph created for the test
2097        //                   0
2098        //                 /   \
2099        //                10    5
2100        //                |     |
2101        //                20    11
2102        //                |     | \
2103        //                22   15  25
2104        //                      |   |
2105        //                     16  27
2106        //                      |
2107        //                     19
2108        //                      |
2109        //                     23
2110
2111        let mut fork_graph = TestForkGraphSpecific::default();
2112        fork_graph.insert_fork(&[0, 10, 20, 22]);
2113        fork_graph.insert_fork(&[0, 5, 11, 15, 16, 19, 21, 23]);
2114        fork_graph.insert_fork(&[0, 5, 11, 25, 27]);
2115
2116        let fork_graph = Arc::new(RwLock::new(fork_graph));
2117        cache.set_fork_graph(Arc::downgrade(&fork_graph));
2118
2119        let program1 = Pubkey::new_unique();
2120        cache.assign_program(&env, program1, 0, new_test_entry(0));
2121        cache.assign_program(&env, program1, 20, new_test_entry(20));
2122
2123        let program2 = Pubkey::new_unique();
2124        cache.assign_program(&env, program2, 5, new_test_entry(5));
2125        cache.assign_program(&env, program2, 11, new_test_entry(11));
2126
2127        let program3 = Pubkey::new_unique();
2128        // Insert an unloaded program with correct/cache's environment at slot 25
2129        let _ = insert_unloaded_entry(&mut cache, program3, 25);
2130
2131        // Insert another unloaded program with a different environment at slot 20
2132        // Since this entry's environment won't match cache's environment, looking up this
2133        // entry should return missing instead of unloaded entry.
2134        cache.assign_program(
2135            &env,
2136            program3,
2137            20,
2138            Arc::new(
2139                new_test_entry(20)
2140                    .to_unloaded()
2141                    .expect("Failed to create unloaded program"),
2142            ),
2143        );
2144
2145        // Testing fork 0 - 5 - 11 - 15 - 16 - 19 - 21 - 23 with current slot at 19
2146        let keys = &[program1, program2, program3];
2147        let mut missing = get_entries_to_load(&cache, 19, keys);
2148        assert!(match_missing(&missing, &program3, false));
2149        let mut extracted = ProgramCacheForTxBatch::new(19);
2150        cache.extract(&mut missing, &mut extracted, &env, true, true);
2151        assert!(match_slot(&extracted, &program1, 0, 19));
2152        assert!(match_slot(&extracted, &program2, 11, 19));
2153
2154        // Testing fork 0 - 5 - 11 - 25 - 27 with current slot at 27
2155        let mut missing = get_entries_to_load(&cache, 27, keys);
2156        let mut extracted = ProgramCacheForTxBatch::new(27);
2157        cache.extract(&mut missing, &mut extracted, &env, true, true);
2158        assert!(match_slot(&extracted, &program1, 0, 27));
2159        assert!(match_slot(&extracted, &program2, 11, 27));
2160        assert!(match_missing(&missing, &program3, true));
2161
2162        // Testing fork 0 - 10 - 20 - 22 with current slot at 22
2163        let mut missing = get_entries_to_load(&cache, 22, keys);
2164        assert!(match_missing(&missing, &program2, false));
2165        let mut extracted = ProgramCacheForTxBatch::new(22);
2166        cache.extract(&mut missing, &mut extracted, &env, true, true);
2167        assert!(match_slot(&extracted, &program1, 20, 22));
2168        assert!(match_missing(&missing, &program3, true));
2169    }
2170
2171    #[test]
2172    fn test_extract_different_environment() {
2173        let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
2174        let env = get_mock_program_runtime_environment();
2175        let other_env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
2176
2177        // Fork graph created for the test
2178        //                0
2179        //                |
2180        //                10
2181        //                |
2182        //                20
2183        //                |
2184        //                22
2185
2186        let mut fork_graph = TestForkGraphSpecific::default();
2187        fork_graph.insert_fork(&[0, 10, 20, 22]);
2188
2189        let fork_graph = Arc::new(RwLock::new(fork_graph));
2190        cache.set_fork_graph(Arc::downgrade(&fork_graph));
2191
2192        let program1 = Pubkey::new_unique();
2193        cache.assign_program(
2194            &env,
2195            program1,
2196            10,
2197            Arc::new(ProgramCacheEntry::new_closed_tombstone(
2198                10,
2199                ProgramCacheEntryOwner::LoaderV3,
2200            )),
2201        );
2202        cache.assign_program(&env, program1, 20, new_test_entry(20));
2203
2204        // Testing fork 0 - 10 - 20 - 22 with current slot at 22
2205        let keys = &[program1];
2206        let mut missing = get_entries_to_load(&cache, 22, keys);
2207        let mut extracted = ProgramCacheForTxBatch::new(22);
2208        cache.extract(&mut missing, &mut extracted, &env, true, true);
2209        assert!(match_slot(&extracted, &program1, 20, 22));
2210
2211        // Looking for a different environment
2212        let mut missing = get_entries_to_load(&cache, 22, keys);
2213        let mut extracted = ProgramCacheForTxBatch::new(22);
2214        cache.extract(&mut missing, &mut extracted, &other_env, true, true);
2215        assert!(match_missing(&missing, &program1, true));
2216    }
2217
2218    #[test]
2219    fn test_extract_nonexistent() {
2220        let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
2221        let env = get_mock_program_runtime_environment();
2222        let fork_graph = TestForkGraphSpecific::default();
2223        let fork_graph = Arc::new(RwLock::new(fork_graph));
2224        cache.set_fork_graph(Arc::downgrade(&fork_graph));
2225
2226        let program1 = Pubkey::new_unique();
2227        let mut missing = vec![ProgramToLoad {
2228            program_id: &program1,
2229            loader: ProgramCacheEntryOwner::LoaderV3,
2230            deployed_on_or_after_slot: 0,
2231            last_modification_slot: 0,
2232        }];
2233        let mut extracted = ProgramCacheForTxBatch::new(0);
2234        cache.extract(&mut missing, &mut extracted, &env, true, true);
2235        assert!(match_missing(&missing, &program1, true));
2236    }
2237
2238    #[test]
2239    fn test_unloaded() {
2240        let mut cache = ProgramCache::<TestForkGraph>::new(0);
2241        let env = get_mock_program_runtime_environment();
2242        for program_cache_entry_type in [
2243            ProgramCacheEntryType::Closed,
2244            ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
2245        ] {
2246            let entry = Arc::new(ProgramCacheEntry {
2247                program: program_cache_entry_type,
2248                account_owner: ProgramCacheEntryOwner::LoaderV2,
2249                deployment_slot: 0,
2250                stats: Arc::default(),
2251                latest_access_slot: AtomicU64::default(),
2252            });
2253            assert!(entry.to_unloaded().is_none());
2254
2255            // Check that unload_program_entry() does nothing for this entry
2256            let program_id = Pubkey::new_unique();
2257            cache.assign_program(&env, program_id, entry.deployment_slot, entry.clone());
2258            cache.unload_program_entry(program_id, entry.deployment_slot, &entry);
2259            assert_eq!(cache.get_slot_versions_for_tests(&program_id).len(), 1);
2260            assert!(cache.stats.evictions.is_empty());
2261        }
2262
2263        let stats = ProgramStatistics {
2264            uses: 3.into(),
2265            ..Default::default()
2266        };
2267        let entry = new_test_entry_with_usage(1, stats);
2268        let unloaded_entry = entry.to_unloaded().unwrap();
2269        assert_eq!(unloaded_entry.deployment_slot, 1);
2270        assert_eq!(unloaded_entry.effective_slot(), 2);
2271        assert_eq!(unloaded_entry.latest_access_slot.load(Ordering::Relaxed), 1);
2272        assert_eq!(unloaded_entry.stats.uses.load(Ordering::Relaxed), 3);
2273
2274        // Check that unload_program_entry() does its work
2275        let program_id = Pubkey::new_unique();
2276        cache.assign_program(&env, program_id, entry.deployment_slot, entry.clone());
2277        cache.unload_program_entry(program_id, entry.deployment_slot, &entry);
2278        assert!(cache.stats.evictions.contains_key(&program_id));
2279    }
2280
2281    #[test]
2282    fn test_fork_prune_find_first_ancestor() {
2283        let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
2284        let env = get_mock_program_runtime_environment();
2285
2286        // Fork graph created for the test
2287        //                   0
2288        //                 /   \
2289        //                10    5
2290        //                |
2291        //                20
2292
2293        // Deploy program on slot 0, and slot 5.
2294        // Prune the fork that has slot 5. The cache should still have the program
2295        // deployed at slot 0.
2296        let mut fork_graph = TestForkGraphSpecific::default();
2297        fork_graph.insert_fork(&[0, 10, 20]);
2298        fork_graph.insert_fork(&[0, 5]);
2299        let fork_graph = Arc::new(RwLock::new(fork_graph));
2300        cache.set_fork_graph(Arc::downgrade(&fork_graph));
2301
2302        let program1 = Pubkey::new_unique();
2303        cache.assign_program(&env, program1, 0, new_test_entry(0));
2304        cache.assign_program(&env, program1, 5, new_test_entry(5));
2305
2306        cache.prune(10, None, &fork_graph.read().unwrap());
2307
2308        let keys = &[program1];
2309        let mut missing = get_entries_to_load(&cache, 20, keys);
2310        let mut extracted = ProgramCacheForTxBatch::new(20);
2311        cache.extract(&mut missing, &mut extracted, &env, true, true);
2312
2313        // The cache should have the program deployed at slot 0
2314        assert_eq!(
2315            extracted
2316                .find(&program1)
2317                .expect("Did not find the program")
2318                .deployment_slot,
2319            0
2320        );
2321    }
2322
2323    #[test]
2324    fn test_prune_by_deployment_slot() {
2325        let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
2326        let env = get_mock_program_runtime_environment();
2327
2328        // Fork graph created for the test
2329        //                   0
2330        //                 /   \
2331        //                10    5
2332        //                |
2333        //                20
2334
2335        // Deploy program on slot 0, and slot 5.
2336        // Prune the fork that has slot 5. The cache should still have the program
2337        // deployed at slot 0.
2338        let mut fork_graph = TestForkGraphSpecific::default();
2339        fork_graph.insert_fork(&[0, 10, 20]);
2340        fork_graph.insert_fork(&[0, 5, 6]);
2341        let fork_graph = Arc::new(RwLock::new(fork_graph));
2342        cache.set_fork_graph(Arc::downgrade(&fork_graph));
2343
2344        let program1 = Pubkey::new_unique();
2345        cache.assign_program(&env, program1, 0, new_test_entry(0));
2346        cache.assign_program(&env, program1, 5, new_test_entry(5));
2347
2348        let program2 = Pubkey::new_unique();
2349        cache.assign_program(&env, program2, 10, new_test_entry(10));
2350
2351        let keys = &[program1, program2];
2352        let mut missing = get_entries_to_load(&cache, 20, keys);
2353        let mut extracted = ProgramCacheForTxBatch::new(20);
2354        cache.extract(&mut missing, &mut extracted, &env, true, true);
2355        assert!(match_slot(&extracted, &program1, 0, 20));
2356        assert!(match_slot(&extracted, &program2, 10, 20));
2357
2358        let mut missing = get_entries_to_load(&cache, 6, keys);
2359        assert!(match_missing(&missing, &program2, false));
2360        let mut extracted = ProgramCacheForTxBatch::new(6);
2361        cache.extract(&mut missing, &mut extracted, &env, true, true);
2362        assert!(match_slot(&extracted, &program1, 5, 6));
2363
2364        // Pruning slot 5 will remove program1 entry deployed at slot 5.
2365        // On fork chaining from slot 5, the entry deployed at slot 0 will become visible.
2366        cache.prune_by_deployment_slot(5);
2367
2368        let mut missing = get_entries_to_load(&cache, 20, keys);
2369        let mut extracted = ProgramCacheForTxBatch::new(20);
2370        cache.extract(&mut missing, &mut extracted, &env, true, true);
2371        assert!(match_slot(&extracted, &program1, 0, 20));
2372        assert!(match_slot(&extracted, &program2, 10, 20));
2373
2374        let mut missing = get_entries_to_load(&cache, 6, keys);
2375        assert!(match_missing(&missing, &program2, false));
2376        let mut extracted = ProgramCacheForTxBatch::new(6);
2377        cache.extract(&mut missing, &mut extracted, &env, true, true);
2378        assert!(match_slot(&extracted, &program1, 0, 6));
2379
2380        // Pruning slot 10 will remove program2 entry deployed at slot 10.
2381        // As there is no other entry for program2, extract() will return it as missing.
2382        cache.prune_by_deployment_slot(10);
2383
2384        let mut missing = get_entries_to_load(&cache, 20, keys);
2385        assert!(match_missing(&missing, &program2, false));
2386        let mut extracted = ProgramCacheForTxBatch::new(20);
2387        cache.extract(&mut missing, &mut extracted, &env, true, true);
2388        assert!(match_slot(&extracted, &program1, 0, 20));
2389    }
2390}