solana_program_runtime/
loaded_programs.rs

1use {
2    crate::invoke_context::{BuiltinFunctionWithContext, InvokeContext},
3    log::{debug, error, log_enabled, trace},
4    percentage::PercentageInteger,
5    solana_clock::{Epoch, Slot},
6    solana_pubkey::Pubkey,
7    solana_sbpf::{
8        elf::Executable, program::BuiltinProgram, verifier::RequisiteVerifier, vm::Config,
9    },
10    solana_sdk_ids::{
11        bpf_loader, bpf_loader_deprecated, bpf_loader_upgradeable, loader_v4, native_loader,
12    },
13    solana_svm_type_overrides::{
14        rand::{thread_rng, Rng},
15        sync::{
16            atomic::{AtomicU64, Ordering},
17            Arc, Condvar, Mutex, RwLock,
18        },
19        thread,
20    },
21    std::{
22        collections::{hash_map::Entry, HashMap},
23        fmt::{Debug, Formatter},
24        sync::Weak,
25    },
26};
27#[cfg(feature = "metrics")]
28use {solana_svm_measure::measure::Measure, solana_svm_timings::ExecuteDetailsTimings};
29
30pub type ProgramRuntimeEnvironment = Arc<BuiltinProgram<InvokeContext<'static>>>;
31pub const MAX_LOADED_ENTRY_COUNT: usize = 512;
32pub const DELAY_VISIBILITY_SLOT_OFFSET: Slot = 1;
33
34/// Relationship between two fork IDs
35#[derive(Copy, Clone, Debug, PartialEq)]
36pub enum BlockRelation {
37    /// The slot is on the same fork and is an ancestor of the other slot
38    Ancestor,
39    /// The two slots are equal and are on the same fork
40    Equal,
41    /// The slot is on the same fork and is a descendant of the other slot
42    Descendant,
43    /// The slots are on two different forks and may have had a common ancestor at some point
44    Unrelated,
45    /// Either one or both of the slots are either older than the latest root, or are in future
46    Unknown,
47}
48
49/// Maps relationship between two slots.
50pub trait ForkGraph {
51    /// Returns the BlockRelation of A to B
52    fn relationship(&self, a: Slot, b: Slot) -> BlockRelation;
53}
54
55/// The owner of a programs accounts, thus the loader of a program
56#[derive(Default, Clone, Copy, PartialEq, Eq, Debug)]
57pub enum ProgramCacheEntryOwner {
58    #[default]
59    NativeLoader,
60    LoaderV1,
61    LoaderV2,
62    LoaderV3,
63    LoaderV4,
64}
65
66impl TryFrom<&Pubkey> for ProgramCacheEntryOwner {
67    type Error = ();
68    fn try_from(loader_key: &Pubkey) -> Result<Self, ()> {
69        if native_loader::check_id(loader_key) {
70            Ok(ProgramCacheEntryOwner::NativeLoader)
71        } else if bpf_loader_deprecated::check_id(loader_key) {
72            Ok(ProgramCacheEntryOwner::LoaderV1)
73        } else if bpf_loader::check_id(loader_key) {
74            Ok(ProgramCacheEntryOwner::LoaderV2)
75        } else if bpf_loader_upgradeable::check_id(loader_key) {
76            Ok(ProgramCacheEntryOwner::LoaderV3)
77        } else if loader_v4::check_id(loader_key) {
78            Ok(ProgramCacheEntryOwner::LoaderV4)
79        } else {
80            Err(())
81        }
82    }
83}
84
85impl From<ProgramCacheEntryOwner> for Pubkey {
86    fn from(program_cache_entry_owner: ProgramCacheEntryOwner) -> Self {
87        match program_cache_entry_owner {
88            ProgramCacheEntryOwner::NativeLoader => native_loader::id(),
89            ProgramCacheEntryOwner::LoaderV1 => bpf_loader_deprecated::id(),
90            ProgramCacheEntryOwner::LoaderV2 => bpf_loader::id(),
91            ProgramCacheEntryOwner::LoaderV3 => bpf_loader_upgradeable::id(),
92            ProgramCacheEntryOwner::LoaderV4 => loader_v4::id(),
93        }
94    }
95}
96
97/*
98    The possible ProgramCacheEntryType transitions:
99
100    DelayVisibility is special in that it is never stored in the cache.
101    It is only returned by ProgramCacheForTxBatch::find() when a Loaded entry
102    is encountered which is not effective yet.
103
104    Builtin re/deployment:
105    - Empty => Builtin in TransactionBatchProcessor::add_builtin
106    - Builtin => Builtin in TransactionBatchProcessor::add_builtin
107
108    Un/re/deployment (with delay and cooldown):
109    - Empty / Closed => Loaded in UpgradeableLoaderInstruction::DeployWithMaxDataLen
110    - Loaded / FailedVerification => Loaded in UpgradeableLoaderInstruction::Upgrade
111    - Loaded / FailedVerification => Closed in UpgradeableLoaderInstruction::Close
112
113    Eviction and unloading (in the same slot):
114    - Unloaded => Loaded in ProgramCache::assign_program
115    - Loaded => Unloaded in ProgramCache::unload_program_entry
116
117    At epoch boundary (when feature set and environment changes):
118    - Loaded => FailedVerification in Bank::_new_from_parent
119    - FailedVerification => Loaded in Bank::_new_from_parent
120
121    Through pruning (when on orphan fork or overshadowed on the rooted fork):
122    - Closed / Unloaded / Loaded / Builtin => Empty in ProgramCache::prune
123*/
124
125/// Actual payload of [ProgramCacheEntry].
126#[derive(Default)]
127pub enum ProgramCacheEntryType {
128    /// Tombstone for programs which currently do not pass the verifier but could if the feature set changed.
129    FailedVerification(ProgramRuntimeEnvironment),
130    /// Tombstone for programs that were either explicitly closed or never deployed.
131    ///
132    /// It's also used for accounts belonging to program loaders, that don't actually contain program code (e.g. buffer accounts for LoaderV3 programs).
133    #[default]
134    Closed,
135    /// Tombstone for programs which have recently been modified but the new version is not visible yet.
136    DelayVisibility,
137    /// Successfully verified but not currently compiled.
138    ///
139    /// It continues to track usage statistics even when the compiled executable of the program is evicted from memory.
140    Unloaded(ProgramRuntimeEnvironment),
141    /// Verified and compiled program
142    Loaded(Executable<InvokeContext<'static>>),
143    /// A built-in program which is not stored on-chain but backed into and distributed with the validator
144    Builtin(BuiltinProgram<InvokeContext<'static>>),
145}
146
147impl Debug for ProgramCacheEntryType {
148    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
149        match self {
150            ProgramCacheEntryType::FailedVerification(_) => {
151                write!(f, "ProgramCacheEntryType::FailedVerification")
152            }
153            ProgramCacheEntryType::Closed => write!(f, "ProgramCacheEntryType::Closed"),
154            ProgramCacheEntryType::DelayVisibility => {
155                write!(f, "ProgramCacheEntryType::DelayVisibility")
156            }
157            ProgramCacheEntryType::Unloaded(_) => write!(f, "ProgramCacheEntryType::Unloaded"),
158            ProgramCacheEntryType::Loaded(_) => write!(f, "ProgramCacheEntryType::Loaded"),
159            ProgramCacheEntryType::Builtin(_) => write!(f, "ProgramCacheEntryType::Builtin"),
160        }
161    }
162}
163
164impl ProgramCacheEntryType {
165    /// Returns a reference to its environment if it has one
166    pub fn get_environment(&self) -> Option<&ProgramRuntimeEnvironment> {
167        match self {
168            ProgramCacheEntryType::Loaded(program) => Some(program.get_loader()),
169            ProgramCacheEntryType::FailedVerification(env)
170            | ProgramCacheEntryType::Unloaded(env) => Some(env),
171            _ => None,
172        }
173    }
174}
175
176/// Holds a program version at a specific address and on a specific slot / fork.
177///
178/// It contains the actual program in [ProgramCacheEntryType] and a bunch of meta-data.
179#[derive(Debug, Default)]
180pub struct ProgramCacheEntry {
181    /// The program of this entry
182    pub program: ProgramCacheEntryType,
183    /// The loader of this entry
184    pub account_owner: ProgramCacheEntryOwner,
185    /// Size of account that stores the program and program data
186    pub account_size: usize,
187    /// Slot in which the program was (re)deployed
188    pub deployment_slot: Slot,
189    /// Slot in which this entry will become active (can be in the future)
190    pub effective_slot: Slot,
191    /// How often this entry was used by a transaction
192    pub tx_usage_counter: Arc<AtomicU64>,
193    /// Latest slot in which the entry was used
194    pub latest_access_slot: AtomicU64,
195}
196
197/// Global cache statistics for [ProgramCache].
198#[derive(Debug, Default)]
199pub struct ProgramCacheStats {
200    /// a program was already in the cache
201    pub hits: AtomicU64,
202    /// a program was not found and loaded instead
203    pub misses: AtomicU64,
204    /// a compiled executable was unloaded
205    pub evictions: HashMap<Pubkey, u64>,
206    /// an unloaded program was loaded again (opposite of eviction)
207    pub reloads: AtomicU64,
208    /// a program was loaded or un/re/deployed
209    pub insertions: AtomicU64,
210    /// a program was loaded but can not be extracted on its own fork anymore
211    pub lost_insertions: AtomicU64,
212    /// a program which was already in the cache was reloaded by mistake
213    pub replacements: AtomicU64,
214    /// a program was only used once before being unloaded
215    pub one_hit_wonders: AtomicU64,
216    /// a program became unreachable in the fork graph because of rerooting
217    pub prunes_orphan: AtomicU64,
218    /// a program got pruned because it was not recompiled for the next epoch
219    pub prunes_environment: AtomicU64,
220    /// a program had no entries because all slot versions got pruned
221    pub empty_entries: AtomicU64,
222    /// water level of loaded entries currently cached
223    pub water_level: AtomicU64,
224}
225
226impl ProgramCacheStats {
227    pub fn reset(&mut self) {
228        *self = ProgramCacheStats::default();
229    }
230    pub fn log(&self) {
231        let hits = self.hits.load(Ordering::Relaxed);
232        let misses = self.misses.load(Ordering::Relaxed);
233        let evictions: u64 = self.evictions.values().sum();
234        let reloads = self.reloads.load(Ordering::Relaxed);
235        let insertions = self.insertions.load(Ordering::Relaxed);
236        let lost_insertions = self.lost_insertions.load(Ordering::Relaxed);
237        let replacements = self.replacements.load(Ordering::Relaxed);
238        let one_hit_wonders = self.one_hit_wonders.load(Ordering::Relaxed);
239        let prunes_orphan = self.prunes_orphan.load(Ordering::Relaxed);
240        let prunes_environment = self.prunes_environment.load(Ordering::Relaxed);
241        let empty_entries = self.empty_entries.load(Ordering::Relaxed);
242        let water_level = self.water_level.load(Ordering::Relaxed);
243        debug!(
244            "Loaded Programs Cache Stats -- Hits: {hits}, Misses: {misses}, Evictions: \
245             {evictions}, Reloads: {reloads}, Insertions: {insertions}, Lost-Insertions: \
246             {lost_insertions}, Replacements: {replacements}, One-Hit-Wonders: {one_hit_wonders}, \
247             Prunes-Orphan: {prunes_orphan}, Prunes-Environment: {prunes_environment}, Empty: \
248             {empty_entries}, Water-Level: {water_level}"
249        );
250        if log_enabled!(log::Level::Trace) && !self.evictions.is_empty() {
251            let mut evictions = self.evictions.iter().collect::<Vec<_>>();
252            evictions.sort_by_key(|e| e.1);
253            let evictions = evictions
254                .into_iter()
255                .rev()
256                .map(|(program_id, evictions)| {
257                    format!("  {:<44}  {}", program_id.to_string(), evictions)
258                })
259                .collect::<Vec<_>>();
260            let evictions = evictions.join("\n");
261            trace!(
262                "Eviction Details:\n  {:<44}  {}\n{}",
263                "Program",
264                "Count",
265                evictions
266            );
267        }
268    }
269}
270
271#[cfg(feature = "metrics")]
272/// Time measurements for loading a single [ProgramCacheEntry].
273#[derive(Debug, Default)]
274pub struct LoadProgramMetrics {
275    /// Program address, but as text
276    pub program_id: String,
277    /// Microseconds it took to `create_program_runtime_environment`
278    pub register_syscalls_us: u64,
279    /// Microseconds it took to `Executable::<InvokeContext>::load`
280    pub load_elf_us: u64,
281    /// Microseconds it took to `executable.verify::<RequisiteVerifier>`
282    pub verify_code_us: u64,
283    /// Microseconds it took to `executable.jit_compile`
284    pub jit_compile_us: u64,
285}
286
287#[cfg(feature = "metrics")]
288impl LoadProgramMetrics {
289    pub fn submit_datapoint(&self, timings: &mut ExecuteDetailsTimings) {
290        timings.create_executor_register_syscalls_us += self.register_syscalls_us;
291        timings.create_executor_load_elf_us += self.load_elf_us;
292        timings.create_executor_verify_code_us += self.verify_code_us;
293        timings.create_executor_jit_compile_us += self.jit_compile_us;
294    }
295}
296
297impl PartialEq for ProgramCacheEntry {
298    fn eq(&self, other: &Self) -> bool {
299        self.effective_slot == other.effective_slot
300            && self.deployment_slot == other.deployment_slot
301            && self.is_tombstone() == other.is_tombstone()
302    }
303}
304
305impl ProgramCacheEntry {
306    /// Creates a new user program
307    pub fn new(
308        loader_key: &Pubkey,
309        program_runtime_environment: ProgramRuntimeEnvironment,
310        deployment_slot: Slot,
311        effective_slot: Slot,
312        elf_bytes: &[u8],
313        account_size: usize,
314        #[cfg(feature = "metrics")] metrics: &mut LoadProgramMetrics,
315    ) -> Result<Self, Box<dyn std::error::Error>> {
316        Self::new_internal(
317            loader_key,
318            program_runtime_environment,
319            deployment_slot,
320            effective_slot,
321            elf_bytes,
322            account_size,
323            #[cfg(feature = "metrics")]
324            metrics,
325            false, /* reloading */
326        )
327    }
328
329    /// Reloads a user program, *without* running the verifier.
330    ///
331    /// # Safety
332    ///
333    /// This method is unsafe since it assumes that the program has already been verified. Should
334    /// only be called when the program was previously verified and loaded in the cache, but was
335    /// unloaded due to inactivity. It should also be checked that the `program_runtime_environment`
336    /// hasn't changed since it was unloaded.
337    pub unsafe fn reload(
338        loader_key: &Pubkey,
339        program_runtime_environment: Arc<BuiltinProgram<InvokeContext<'static>>>,
340        deployment_slot: Slot,
341        effective_slot: Slot,
342        elf_bytes: &[u8],
343        account_size: usize,
344        #[cfg(feature = "metrics")] metrics: &mut LoadProgramMetrics,
345    ) -> Result<Self, Box<dyn std::error::Error>> {
346        Self::new_internal(
347            loader_key,
348            program_runtime_environment,
349            deployment_slot,
350            effective_slot,
351            elf_bytes,
352            account_size,
353            #[cfg(feature = "metrics")]
354            metrics,
355            true, /* reloading */
356        )
357    }
358
359    fn new_internal(
360        loader_key: &Pubkey,
361        program_runtime_environment: Arc<BuiltinProgram<InvokeContext<'static>>>,
362        deployment_slot: Slot,
363        effective_slot: Slot,
364        elf_bytes: &[u8],
365        account_size: usize,
366        #[cfg(feature = "metrics")] metrics: &mut LoadProgramMetrics,
367        reloading: bool,
368    ) -> Result<Self, Box<dyn std::error::Error>> {
369        #[cfg(feature = "metrics")]
370        let load_elf_time = Measure::start("load_elf_time");
371        // The following unused_mut exception is needed for architectures that do not
372        // support JIT compilation.
373        #[allow(unused_mut)]
374        let mut executable = Executable::load(elf_bytes, program_runtime_environment.clone())?;
375        #[cfg(feature = "metrics")]
376        {
377            metrics.load_elf_us = load_elf_time.end_as_us();
378        }
379
380        if !reloading {
381            #[cfg(feature = "metrics")]
382            let verify_code_time = Measure::start("verify_code_time");
383            executable.verify::<RequisiteVerifier>()?;
384            #[cfg(feature = "metrics")]
385            {
386                metrics.verify_code_us = verify_code_time.end_as_us();
387            }
388        }
389
390        #[cfg(all(not(target_os = "windows"), target_arch = "x86_64"))]
391        {
392            #[cfg(feature = "metrics")]
393            let jit_compile_time = Measure::start("jit_compile_time");
394            executable.jit_compile()?;
395            #[cfg(feature = "metrics")]
396            {
397                metrics.jit_compile_us = jit_compile_time.end_as_us();
398            }
399        }
400
401        Ok(Self {
402            deployment_slot,
403            account_owner: ProgramCacheEntryOwner::try_from(loader_key).unwrap(),
404            account_size,
405            effective_slot,
406            tx_usage_counter: Arc::<AtomicU64>::default(),
407            program: ProgramCacheEntryType::Loaded(executable),
408            latest_access_slot: AtomicU64::new(0),
409        })
410    }
411
412    pub fn to_unloaded(&self) -> Option<Self> {
413        match &self.program {
414            ProgramCacheEntryType::Loaded(_) => {}
415            ProgramCacheEntryType::FailedVerification(_)
416            | ProgramCacheEntryType::Closed
417            | ProgramCacheEntryType::DelayVisibility
418            | ProgramCacheEntryType::Unloaded(_)
419            | ProgramCacheEntryType::Builtin(_) => {
420                return None;
421            }
422        }
423        Some(Self {
424            program: ProgramCacheEntryType::Unloaded(self.program.get_environment()?.clone()),
425            account_owner: self.account_owner,
426            account_size: self.account_size,
427            deployment_slot: self.deployment_slot,
428            effective_slot: self.effective_slot,
429            tx_usage_counter: self.tx_usage_counter.clone(),
430            latest_access_slot: AtomicU64::new(self.latest_access_slot.load(Ordering::Relaxed)),
431        })
432    }
433
434    /// Creates a new built-in program
435    pub fn new_builtin(
436        deployment_slot: Slot,
437        account_size: usize,
438        builtin_function: BuiltinFunctionWithContext,
439    ) -> Self {
440        let mut program = BuiltinProgram::new_builtin();
441        program
442            .register_function("entrypoint", builtin_function)
443            .unwrap();
444        Self {
445            deployment_slot,
446            account_owner: ProgramCacheEntryOwner::NativeLoader,
447            account_size,
448            effective_slot: deployment_slot,
449            tx_usage_counter: Arc::<AtomicU64>::default(),
450            program: ProgramCacheEntryType::Builtin(program),
451            latest_access_slot: AtomicU64::new(0),
452        }
453    }
454
455    pub fn new_tombstone(
456        slot: Slot,
457        account_owner: ProgramCacheEntryOwner,
458        reason: ProgramCacheEntryType,
459    ) -> Self {
460        Self::new_tombstone_with_usage_counter(
461            slot,
462            account_owner,
463            reason,
464            Arc::<AtomicU64>::default(),
465        )
466    }
467
468    pub fn new_tombstone_with_usage_counter(
469        slot: Slot,
470        account_owner: ProgramCacheEntryOwner,
471        reason: ProgramCacheEntryType,
472        tx_usage_counter: Arc<AtomicU64>,
473    ) -> Self {
474        let tombstone = Self {
475            program: reason,
476            account_owner,
477            account_size: 0,
478            deployment_slot: slot,
479            effective_slot: slot,
480            tx_usage_counter,
481            latest_access_slot: AtomicU64::new(0),
482        };
483        debug_assert!(tombstone.is_tombstone());
484        tombstone
485    }
486
487    pub fn is_tombstone(&self) -> bool {
488        matches!(
489            self.program,
490            ProgramCacheEntryType::FailedVerification(_)
491                | ProgramCacheEntryType::Closed
492                | ProgramCacheEntryType::DelayVisibility
493        )
494    }
495
496    fn is_implicit_delay_visibility_tombstone(&self, slot: Slot) -> bool {
497        !matches!(self.program, ProgramCacheEntryType::Builtin(_))
498            && self.effective_slot.saturating_sub(self.deployment_slot)
499                == DELAY_VISIBILITY_SLOT_OFFSET
500            && slot >= self.deployment_slot
501            && slot < self.effective_slot
502    }
503
504    pub fn update_access_slot(&self, slot: Slot) {
505        let _ = self.latest_access_slot.fetch_max(slot, Ordering::Relaxed);
506    }
507
508    pub fn decayed_usage_counter(&self, now: Slot) -> u64 {
509        let last_access = self.latest_access_slot.load(Ordering::Relaxed);
510        // Shifting the u64 value for more than 63 will cause an overflow.
511        let decaying_for = std::cmp::min(63, now.saturating_sub(last_access));
512        self.tx_usage_counter.load(Ordering::Relaxed) >> decaying_for
513    }
514
515    pub fn account_owner(&self) -> Pubkey {
516        self.account_owner.into()
517    }
518}
519
520/// Globally shared RBPF config and syscall registry
521///
522/// This is only valid in an epoch range as long as no feature affecting RBPF is activated.
523#[derive(Clone, Debug)]
524pub struct ProgramRuntimeEnvironments {
525    /// For program runtime V1
526    pub program_runtime_v1: ProgramRuntimeEnvironment,
527    /// For program runtime V2
528    pub program_runtime_v2: ProgramRuntimeEnvironment,
529}
530
531impl Default for ProgramRuntimeEnvironments {
532    fn default() -> Self {
533        let empty_loader = Arc::new(BuiltinProgram::new_loader(Config::default()));
534        Self {
535            program_runtime_v1: empty_loader.clone(),
536            program_runtime_v2: empty_loader,
537        }
538    }
539}
540
541#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
542pub struct LoadingTaskCookie(u64);
543
544impl LoadingTaskCookie {
545    fn new() -> Self {
546        Self(0)
547    }
548
549    fn update(&mut self) {
550        let LoadingTaskCookie(cookie) = self;
551        *cookie = cookie.wrapping_add(1);
552    }
553}
554
555/// Suspends the thread in case no cooprative loading task was assigned
556#[derive(Debug, Default)]
557pub struct LoadingTaskWaiter {
558    cookie: Mutex<LoadingTaskCookie>,
559    cond: Condvar,
560}
561
562impl LoadingTaskWaiter {
563    pub fn new() -> Self {
564        Self {
565            cookie: Mutex::new(LoadingTaskCookie::new()),
566            cond: Condvar::new(),
567        }
568    }
569
570    pub fn cookie(&self) -> LoadingTaskCookie {
571        *self.cookie.lock().unwrap()
572    }
573
574    pub fn notify(&self) {
575        let mut cookie = self.cookie.lock().unwrap();
576        cookie.update();
577        self.cond.notify_all();
578    }
579
580    pub fn wait(&self, cookie: LoadingTaskCookie) -> LoadingTaskCookie {
581        let cookie_guard = self.cookie.lock().unwrap();
582        *self
583            .cond
584            .wait_while(cookie_guard, |current_cookie| *current_cookie == cookie)
585            .unwrap()
586    }
587}
588
589#[derive(Debug)]
590enum IndexImplementation {
591    /// Fork-graph aware index implementation
592    V1 {
593        /// A two level index:
594        ///
595        /// - the first level is for the address at which programs are deployed
596        /// - the second level for the slot (and thus also fork), sorted by slot number.
597        entries: HashMap<Pubkey, Vec<Arc<ProgramCacheEntry>>>,
598        /// The entries that are getting loaded and have not yet finished loading.
599        ///
600        /// The key is the program address, the value is a tuple of the slot in which the program is
601        /// being loaded and the thread ID doing the load.
602        ///
603        /// It is possible that multiple TX batches from different slots need different versions of a
604        /// program. The deployment slot of a program is only known after load tho,
605        /// so all loads for a given program key are serialized.
606        loading_entries: Mutex<HashMap<Pubkey, (Slot, thread::ThreadId)>>,
607    },
608}
609
610/// This structure is the global cache of loaded, verified and compiled programs.
611///
612/// It ...
613/// - is validator global and fork graph aware, so it can optimize the commonalities across banks.
614/// - handles the visibility rules of un/re/deployments.
615/// - stores the usage statistics and verification status of each program.
616/// - is elastic and uses a probabilistic eviction strategy based on the usage statistics.
617/// - also keeps the compiled executables around, but only for the most used programs.
618/// - supports various kinds of tombstones to avoid loading programs which can not be loaded.
619/// - cleans up entries on orphan branches when the block store is rerooted.
620/// - supports the cache preparation phase before feature activations which can change cached programs.
621/// - manages the environments of the programs and upcoming environments for the next epoch.
622/// - allows for cooperative loading of TX batches which hit the same missing programs simultaneously.
623/// - enforces that all programs used in a batch are eagerly loaded ahead of execution.
624/// - is not persisted to disk or a snapshot, so it needs to cold start and warm up first.
625pub struct ProgramCache<FG: ForkGraph> {
626    /// Index of the cached entries and cooperative loading tasks
627    index: IndexImplementation,
628    /// The slot of the last rerooting
629    pub latest_root_slot: Slot,
630    /// The epoch of the last rerooting
631    pub latest_root_epoch: Epoch,
632    /// Environments of the current epoch
633    pub environments: ProgramRuntimeEnvironments,
634    /// Anticipated replacement for `environments` at the next epoch
635    ///
636    /// This is `None` during most of an epoch, and only `Some` around the boundaries (at the end and beginning of an epoch).
637    /// More precisely, it starts with the cache preparation phase a few hundred slots before the epoch boundary,
638    /// and it ends with the first rerooting after the epoch boundary.
639    pub upcoming_environments: Option<ProgramRuntimeEnvironments>,
640    /// List of loaded programs which should be recompiled before the next epoch (but don't have to).
641    pub programs_to_recompile: Vec<(Pubkey, Arc<ProgramCacheEntry>)>,
642    /// Statistics counters
643    pub stats: ProgramCacheStats,
644    /// Reference to the block store
645    pub fork_graph: Option<Weak<RwLock<FG>>>,
646    /// Coordinates TX batches waiting for others to complete their task during cooperative loading
647    pub loading_task_waiter: Arc<LoadingTaskWaiter>,
648}
649
650impl<FG: ForkGraph> Debug for ProgramCache<FG> {
651    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
652        f.debug_struct("ProgramCache")
653            .field("root slot", &self.latest_root_slot)
654            .field("root epoch", &self.latest_root_epoch)
655            .field("stats", &self.stats)
656            .field("index", &self.index)
657            .finish()
658    }
659}
660
661/// Local view into [ProgramCache] which was extracted for a specific TX batch.
662///
663/// This isolation enables the global [ProgramCache] to continue to evolve (e.g. evictions),
664/// while the TX batch is guaranteed it will continue to find all the programs it requires.
665/// For program management instructions this also buffers them before they are merged back into the global [ProgramCache].
666#[derive(Clone, Debug, Default)]
667pub struct ProgramCacheForTxBatch {
668    /// Pubkey is the address of a program.
669    /// ProgramCacheEntry is the corresponding program entry valid for the slot in which a transaction is being executed.
670    entries: HashMap<Pubkey, Arc<ProgramCacheEntry>>,
671    /// Program entries modified during the transaction batch.
672    modified_entries: HashMap<Pubkey, Arc<ProgramCacheEntry>>,
673    slot: Slot,
674    pub environments: ProgramRuntimeEnvironments,
675    /// Anticipated replacement for `environments` at the next epoch.
676    ///
677    /// This is `None` during most of an epoch, and only `Some` around the boundaries (at the end and beginning of an epoch).
678    /// More precisely, it starts with the cache preparation phase a few hundred slots before the epoch boundary,
679    /// and it ends with the first rerooting after the epoch boundary.
680    /// Needed when a program is deployed at the last slot of an epoch, becomes effective in the next epoch.
681    /// So needs to be compiled with the environment for the next epoch.
682    pub upcoming_environments: Option<ProgramRuntimeEnvironments>,
683    /// The epoch of the last rerooting
684    pub latest_root_epoch: Epoch,
685    pub hit_max_limit: bool,
686    pub loaded_missing: bool,
687    pub merged_modified: bool,
688}
689
690impl ProgramCacheForTxBatch {
691    pub fn new(
692        slot: Slot,
693        environments: ProgramRuntimeEnvironments,
694        upcoming_environments: Option<ProgramRuntimeEnvironments>,
695        latest_root_epoch: Epoch,
696    ) -> Self {
697        Self {
698            entries: HashMap::new(),
699            modified_entries: HashMap::new(),
700            slot,
701            environments,
702            upcoming_environments,
703            latest_root_epoch,
704            hit_max_limit: false,
705            loaded_missing: false,
706            merged_modified: false,
707        }
708    }
709
710    pub fn new_from_cache<FG: ForkGraph>(
711        slot: Slot,
712        epoch: Epoch,
713        cache: &ProgramCache<FG>,
714    ) -> Self {
715        Self {
716            entries: HashMap::new(),
717            modified_entries: HashMap::new(),
718            slot,
719            environments: cache.get_environments_for_epoch(epoch),
720            upcoming_environments: cache.get_upcoming_environments_for_epoch(epoch),
721            latest_root_epoch: cache.latest_root_epoch,
722            hit_max_limit: false,
723            loaded_missing: false,
724            merged_modified: false,
725        }
726    }
727
728    /// Returns the current environments depending on the given epoch
729    pub fn get_environments_for_epoch(&self, epoch: Epoch) -> &ProgramRuntimeEnvironments {
730        if epoch != self.latest_root_epoch {
731            if let Some(upcoming_environments) = self.upcoming_environments.as_ref() {
732                return upcoming_environments;
733            }
734        }
735        &self.environments
736    }
737
738    /// Refill the cache with a single entry. It's typically called during transaction loading, and
739    /// transaction processing (for program management instructions).
740    /// It replaces the existing entry (if any) with the provided entry. The return value contains
741    /// `true` if an entry existed.
742    /// The function also returns the newly inserted value.
743    pub fn replenish(
744        &mut self,
745        key: Pubkey,
746        entry: Arc<ProgramCacheEntry>,
747    ) -> (bool, Arc<ProgramCacheEntry>) {
748        (self.entries.insert(key, entry.clone()).is_some(), entry)
749    }
750
751    /// Store an entry in `modified_entries` for a program modified during the
752    /// transaction batch.
753    pub fn store_modified_entry(&mut self, key: Pubkey, entry: Arc<ProgramCacheEntry>) {
754        self.modified_entries.insert(key, entry);
755    }
756
757    /// Drain the program cache's modified entries, returning the owned
758    /// collection.
759    pub fn drain_modified_entries(&mut self) -> HashMap<Pubkey, Arc<ProgramCacheEntry>> {
760        std::mem::take(&mut self.modified_entries)
761    }
762
763    pub fn find(&self, key: &Pubkey) -> Option<Arc<ProgramCacheEntry>> {
764        // First lookup the cache of the programs modified by the current
765        // transaction. If not found, lookup the cache of the cache of the
766        // programs that are loaded for the transaction batch.
767        self.modified_entries
768            .get(key)
769            .or_else(|| self.entries.get(key))
770            .map(|entry| {
771                if entry.is_implicit_delay_visibility_tombstone(self.slot) {
772                    // Found a program entry on the current fork, but it's not effective
773                    // yet. It indicates that the program has delayed visibility. Return
774                    // the tombstone to reflect that.
775                    Arc::new(ProgramCacheEntry::new_tombstone_with_usage_counter(
776                        entry.deployment_slot,
777                        entry.account_owner,
778                        ProgramCacheEntryType::DelayVisibility,
779                        entry.tx_usage_counter.clone(),
780                    ))
781                } else {
782                    entry.clone()
783                }
784            })
785    }
786
787    pub fn slot(&self) -> Slot {
788        self.slot
789    }
790
791    pub fn set_slot_for_tests(&mut self, slot: Slot) {
792        self.slot = slot;
793    }
794
795    pub fn merge(&mut self, modified_entries: &HashMap<Pubkey, Arc<ProgramCacheEntry>>) {
796        modified_entries.iter().for_each(|(key, entry)| {
797            self.merged_modified = true;
798            self.replenish(*key, entry.clone());
799        })
800    }
801
802    pub fn is_empty(&self) -> bool {
803        self.entries.is_empty()
804    }
805}
806
807pub enum ProgramCacheMatchCriteria {
808    DeployedOnOrAfterSlot(Slot),
809    Tombstone,
810    NoCriteria,
811}
812
813impl<FG: ForkGraph> ProgramCache<FG> {
814    pub fn new(root_slot: Slot, root_epoch: Epoch) -> Self {
815        Self {
816            index: IndexImplementation::V1 {
817                entries: HashMap::new(),
818                loading_entries: Mutex::new(HashMap::new()),
819            },
820            latest_root_slot: root_slot,
821            latest_root_epoch: root_epoch,
822            environments: ProgramRuntimeEnvironments::default(),
823            upcoming_environments: None,
824            programs_to_recompile: Vec::default(),
825            stats: ProgramCacheStats::default(),
826            fork_graph: None,
827            loading_task_waiter: Arc::new(LoadingTaskWaiter::default()),
828        }
829    }
830
831    pub fn set_fork_graph(&mut self, fork_graph: Weak<RwLock<FG>>) {
832        self.fork_graph = Some(fork_graph);
833    }
834
835    /// Returns the current environments depending on the given epoch
836    pub fn get_environments_for_epoch(&self, epoch: Epoch) -> ProgramRuntimeEnvironments {
837        if epoch != self.latest_root_epoch {
838            if let Some(upcoming_environments) = self.upcoming_environments.as_ref() {
839                return upcoming_environments.clone();
840            }
841        }
842        self.environments.clone()
843    }
844
845    /// Returns the upcoming environments depending on the given epoch
846    pub fn get_upcoming_environments_for_epoch(
847        &self,
848        epoch: Epoch,
849    ) -> Option<ProgramRuntimeEnvironments> {
850        if epoch == self.latest_root_epoch {
851            return self.upcoming_environments.clone();
852        }
853        None
854    }
855
856    /// Insert a single entry. It's typically called during transaction loading,
857    /// when the cache doesn't contain the entry corresponding to program `key`.
858    pub fn assign_program(&mut self, key: Pubkey, entry: Arc<ProgramCacheEntry>) -> bool {
859        debug_assert!(!matches!(
860            &entry.program,
861            ProgramCacheEntryType::DelayVisibility
862        ));
863        // This function always returns `true` during normal operation.
864        // Only during the cache preparation phase this can return `false`
865        // for entries with `upcoming_environments`.
866        fn is_current_env(
867            environments: &ProgramRuntimeEnvironments,
868            env_opt: Option<&ProgramRuntimeEnvironment>,
869        ) -> bool {
870            env_opt
871                .map(|env| {
872                    Arc::ptr_eq(env, &environments.program_runtime_v1)
873                        || Arc::ptr_eq(env, &environments.program_runtime_v2)
874                })
875                .unwrap_or(true)
876        }
877        match &mut self.index {
878            IndexImplementation::V1 { entries, .. } => {
879                let slot_versions = &mut entries.entry(key).or_default();
880                match slot_versions.binary_search_by(|at| {
881                    at.effective_slot
882                        .cmp(&entry.effective_slot)
883                        .then(at.deployment_slot.cmp(&entry.deployment_slot))
884                        .then(
885                            // This `.then()` has no effect during normal operation.
886                            // Only during the cache preparation phase this does allow entries
887                            // which only differ in their environment to be interleaved in `slot_versions`.
888                            is_current_env(&self.environments, at.program.get_environment()).cmp(
889                                &is_current_env(
890                                    &self.environments,
891                                    entry.program.get_environment(),
892                                ),
893                            ),
894                        )
895                }) {
896                    Ok(index) => {
897                        let existing = slot_versions.get_mut(index).unwrap();
898                        match (&existing.program, &entry.program) {
899                            (
900                                ProgramCacheEntryType::Builtin(_),
901                                ProgramCacheEntryType::Builtin(_),
902                            )
903                            | (
904                                ProgramCacheEntryType::Unloaded(_),
905                                ProgramCacheEntryType::Loaded(_),
906                            ) => {}
907                            _ => {
908                                // Something is wrong, I can feel it ...
909                                error!(
910                                    "ProgramCache::assign_program() failed key={key:?} \
911                                     existing={slot_versions:?} entry={entry:?}"
912                                );
913                                debug_assert!(false, "Unexpected replacement of an entry");
914                                self.stats.replacements.fetch_add(1, Ordering::Relaxed);
915                                return true;
916                            }
917                        }
918                        // Copy over the usage counter to the new entry
919                        entry.tx_usage_counter.fetch_add(
920                            existing.tx_usage_counter.load(Ordering::Relaxed),
921                            Ordering::Relaxed,
922                        );
923                        *existing = Arc::clone(&entry);
924                        self.stats.reloads.fetch_add(1, Ordering::Relaxed);
925                    }
926                    Err(index) => {
927                        self.stats.insertions.fetch_add(1, Ordering::Relaxed);
928                        slot_versions.insert(index, Arc::clone(&entry));
929                    }
930                }
931            }
932        }
933        false
934    }
935
936    pub fn prune_by_deployment_slot(&mut self, slot: Slot) {
937        match &mut self.index {
938            IndexImplementation::V1 { entries, .. } => {
939                for second_level in entries.values_mut() {
940                    second_level.retain(|entry| entry.deployment_slot != slot);
941                }
942                self.remove_programs_with_no_entries();
943            }
944        }
945    }
946
947    /// Before rerooting the blockstore this removes all superfluous entries
948    pub fn prune(&mut self, new_root_slot: Slot, new_root_epoch: Epoch) {
949        let Some(fork_graph) = self.fork_graph.clone() else {
950            error!("Program cache doesn't have fork graph.");
951            return;
952        };
953        let fork_graph = fork_graph.upgrade().unwrap();
954        let Ok(fork_graph) = fork_graph.read() else {
955            error!("Failed to lock fork graph for reading.");
956            return;
957        };
958        let mut preparation_phase_ends = false;
959        if self.latest_root_epoch != new_root_epoch {
960            self.latest_root_epoch = new_root_epoch;
961            if let Some(upcoming_environments) = self.upcoming_environments.take() {
962                preparation_phase_ends = true;
963                self.environments = upcoming_environments;
964                self.programs_to_recompile.clear();
965            }
966        }
967        match &mut self.index {
968            IndexImplementation::V1 { entries, .. } => {
969                for second_level in entries.values_mut() {
970                    // Remove entries un/re/deployed on orphan forks
971                    let mut first_ancestor_found = false;
972                    let mut first_ancestor_env = None;
973                    *second_level = second_level
974                        .iter()
975                        .rev()
976                        .filter(|entry| {
977                            let relation =
978                                fork_graph.relationship(entry.deployment_slot, new_root_slot);
979                            if entry.deployment_slot >= new_root_slot {
980                                matches!(relation, BlockRelation::Equal | BlockRelation::Descendant)
981                            } else if matches!(relation, BlockRelation::Ancestor)
982                                || entry.deployment_slot <= self.latest_root_slot
983                            {
984                                if !first_ancestor_found {
985                                    first_ancestor_found = true;
986                                    first_ancestor_env = entry.program.get_environment();
987                                    return true;
988                                }
989                                // Do not prune the entry if the runtime environment of the entry is different
990                                // than the entry that was previously found (stored in first_ancestor_env).
991                                // Different environment indicates that this entry might belong to an older
992                                // epoch that had a different environment (e.g. different feature set).
993                                // Once the root moves to the new/current epoch, the entry will get pruned.
994                                // But, until then the entry might still be getting used by an older slot.
995                                if let Some(entry_env) = entry.program.get_environment() {
996                                    if let Some(env) = first_ancestor_env {
997                                        if !Arc::ptr_eq(entry_env, env) {
998                                            return true;
999                                        }
1000                                    }
1001                                }
1002                                self.stats.prunes_orphan.fetch_add(1, Ordering::Relaxed);
1003                                false
1004                            } else {
1005                                self.stats.prunes_orphan.fetch_add(1, Ordering::Relaxed);
1006                                false
1007                            }
1008                        })
1009                        .filter(|entry| {
1010                            // Remove outdated environment of previous feature set
1011                            if preparation_phase_ends
1012                                && !Self::matches_environment(entry, &self.environments)
1013                            {
1014                                self.stats
1015                                    .prunes_environment
1016                                    .fetch_add(1, Ordering::Relaxed);
1017                                return false;
1018                            }
1019                            true
1020                        })
1021                        .cloned()
1022                        .collect();
1023                    second_level.reverse();
1024                }
1025            }
1026        }
1027        self.remove_programs_with_no_entries();
1028        debug_assert!(self.latest_root_slot <= new_root_slot);
1029        self.latest_root_slot = new_root_slot;
1030    }
1031
1032    fn matches_environment(
1033        entry: &Arc<ProgramCacheEntry>,
1034        environments: &ProgramRuntimeEnvironments,
1035    ) -> bool {
1036        let Some(environment) = entry.program.get_environment() else {
1037            return true;
1038        };
1039        Arc::ptr_eq(environment, &environments.program_runtime_v1)
1040            || Arc::ptr_eq(environment, &environments.program_runtime_v2)
1041    }
1042
1043    fn matches_criteria(
1044        program: &Arc<ProgramCacheEntry>,
1045        criteria: &ProgramCacheMatchCriteria,
1046    ) -> bool {
1047        match criteria {
1048            ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(slot) => {
1049                program.deployment_slot >= *slot
1050            }
1051            ProgramCacheMatchCriteria::Tombstone => program.is_tombstone(),
1052            ProgramCacheMatchCriteria::NoCriteria => true,
1053        }
1054    }
1055
1056    /// Extracts a subset of the programs relevant to a transaction batch
1057    /// and returns which program accounts the accounts DB needs to load.
1058    pub fn extract(
1059        &self,
1060        search_for: &mut Vec<(Pubkey, ProgramCacheMatchCriteria)>,
1061        loaded_programs_for_tx_batch: &mut ProgramCacheForTxBatch,
1062        increment_usage_counter: bool,
1063        count_hits_and_misses: bool,
1064    ) -> Option<Pubkey> {
1065        debug_assert!(self.fork_graph.is_some());
1066        let fork_graph = self.fork_graph.as_ref().unwrap().upgrade().unwrap();
1067        let locked_fork_graph = fork_graph.read().unwrap();
1068        let mut cooperative_loading_task = None;
1069        match &self.index {
1070            IndexImplementation::V1 {
1071                entries,
1072                loading_entries,
1073            } => {
1074                search_for.retain(|(key, match_criteria)| {
1075                    if let Some(second_level) = entries.get(key) {
1076                        for entry in second_level.iter().rev() {
1077                            if entry.deployment_slot <= self.latest_root_slot
1078                                || matches!(
1079                                    locked_fork_graph.relationship(
1080                                        entry.deployment_slot,
1081                                        loaded_programs_for_tx_batch.slot
1082                                    ),
1083                                    BlockRelation::Equal | BlockRelation::Ancestor
1084                                )
1085                            {
1086                                let entry_to_return = if loaded_programs_for_tx_batch.slot
1087                                    >= entry.effective_slot
1088                                    && Self::matches_environment(
1089                                        entry,
1090                                        &loaded_programs_for_tx_batch.environments,
1091                                    ) {
1092                                    if !Self::matches_criteria(entry, match_criteria) {
1093                                        break;
1094                                    }
1095                                    if let ProgramCacheEntryType::Unloaded(_environment) =
1096                                        &entry.program
1097                                    {
1098                                        break;
1099                                    }
1100                                    entry.clone()
1101                                } else if entry.is_implicit_delay_visibility_tombstone(
1102                                    loaded_programs_for_tx_batch.slot,
1103                                ) {
1104                                    // Found a program entry on the current fork, but it's not effective
1105                                    // yet. It indicates that the program has delayed visibility. Return
1106                                    // the tombstone to reflect that.
1107                                    Arc::new(ProgramCacheEntry::new_tombstone_with_usage_counter(
1108                                        entry.deployment_slot,
1109                                        entry.account_owner,
1110                                        ProgramCacheEntryType::DelayVisibility,
1111                                        entry.tx_usage_counter.clone(),
1112                                    ))
1113                                } else {
1114                                    continue;
1115                                };
1116                                entry_to_return
1117                                    .update_access_slot(loaded_programs_for_tx_batch.slot);
1118                                if increment_usage_counter {
1119                                    entry_to_return
1120                                        .tx_usage_counter
1121                                        .fetch_add(1, Ordering::Relaxed);
1122                                }
1123                                loaded_programs_for_tx_batch
1124                                    .entries
1125                                    .insert(*key, entry_to_return);
1126                                return false;
1127                            }
1128                        }
1129                    }
1130                    if cooperative_loading_task.is_none() {
1131                        let mut loading_entries = loading_entries.lock().unwrap();
1132                        let entry = loading_entries.entry(*key);
1133                        if let Entry::Vacant(entry) = entry {
1134                            entry.insert((
1135                                loaded_programs_for_tx_batch.slot,
1136                                thread::current().id(),
1137                            ));
1138                            cooperative_loading_task = Some(*key);
1139                        }
1140                    }
1141                    true
1142                });
1143            }
1144        }
1145        drop(locked_fork_graph);
1146        if count_hits_and_misses {
1147            self.stats
1148                .misses
1149                .fetch_add(search_for.len() as u64, Ordering::Relaxed);
1150            self.stats.hits.fetch_add(
1151                loaded_programs_for_tx_batch.entries.len() as u64,
1152                Ordering::Relaxed,
1153            );
1154        }
1155        cooperative_loading_task
1156    }
1157
1158    /// Called by Bank::replenish_program_cache() for each program that is done loading.
1159    pub fn finish_cooperative_loading_task(
1160        &mut self,
1161        slot: Slot,
1162        key: Pubkey,
1163        loaded_program: Arc<ProgramCacheEntry>,
1164    ) -> bool {
1165        match &mut self.index {
1166            IndexImplementation::V1 {
1167                loading_entries, ..
1168            } => {
1169                let loading_thread = loading_entries.get_mut().unwrap().remove(&key);
1170                debug_assert_eq!(loading_thread, Some((slot, thread::current().id())));
1171                // Check that it will be visible to our own fork once inserted
1172                if loaded_program.deployment_slot > self.latest_root_slot
1173                    && !matches!(
1174                        self.fork_graph
1175                            .as_ref()
1176                            .unwrap()
1177                            .upgrade()
1178                            .unwrap()
1179                            .read()
1180                            .unwrap()
1181                            .relationship(loaded_program.deployment_slot, slot),
1182                        BlockRelation::Equal | BlockRelation::Ancestor
1183                    )
1184                {
1185                    self.stats.lost_insertions.fetch_add(1, Ordering::Relaxed);
1186                }
1187                let was_occupied = self.assign_program(key, loaded_program);
1188                self.loading_task_waiter.notify();
1189                was_occupied
1190            }
1191        }
1192    }
1193
1194    pub fn merge(&mut self, modified_entries: &HashMap<Pubkey, Arc<ProgramCacheEntry>>) {
1195        modified_entries.iter().for_each(|(key, entry)| {
1196            self.assign_program(*key, entry.clone());
1197        })
1198    }
1199
1200    /// Returns the list of entries which are verified and compiled.
1201    pub fn get_flattened_entries(
1202        &self,
1203        include_program_runtime_v1: bool,
1204        _include_program_runtime_v2: bool,
1205    ) -> Vec<(Pubkey, Arc<ProgramCacheEntry>)> {
1206        match &self.index {
1207            IndexImplementation::V1 { entries, .. } => entries
1208                .iter()
1209                .flat_map(|(id, second_level)| {
1210                    second_level
1211                        .iter()
1212                        .filter_map(move |program| match program.program {
1213                            ProgramCacheEntryType::Loaded(_) => {
1214                                if include_program_runtime_v1 {
1215                                    Some((*id, program.clone()))
1216                                } else {
1217                                    None
1218                                }
1219                            }
1220                            _ => None,
1221                        })
1222                })
1223                .collect(),
1224        }
1225    }
1226
1227    /// Returns the list of all entries in the cache.
1228    pub fn get_flattened_entries_for_tests(&self) -> Vec<(Pubkey, Arc<ProgramCacheEntry>)> {
1229        match &self.index {
1230            IndexImplementation::V1 { entries, .. } => entries
1231                .iter()
1232                .flat_map(|(id, second_level)| {
1233                    second_level.iter().map(|program| (*id, program.clone()))
1234                })
1235                .collect(),
1236        }
1237    }
1238
1239    /// Returns the slot versions for the given program id.
1240    pub fn get_slot_versions_for_tests(&self, key: &Pubkey) -> &[Arc<ProgramCacheEntry>] {
1241        match &self.index {
1242            IndexImplementation::V1 { entries, .. } => entries
1243                .get(key)
1244                .map(|second_level| second_level.as_ref())
1245                .unwrap_or(&[]),
1246        }
1247    }
1248
1249    /// Unloads programs which were used infrequently
1250    pub fn sort_and_unload(&mut self, shrink_to: PercentageInteger) {
1251        let mut sorted_candidates = self.get_flattened_entries(true, true);
1252        sorted_candidates
1253            .sort_by_cached_key(|(_id, program)| program.tx_usage_counter.load(Ordering::Relaxed));
1254        let num_to_unload = sorted_candidates
1255            .len()
1256            .saturating_sub(shrink_to.apply_to(MAX_LOADED_ENTRY_COUNT));
1257        self.unload_program_entries(sorted_candidates.iter().take(num_to_unload));
1258    }
1259
1260    /// Evicts programs using 2's random selection, choosing the least used program out of the two entries.
1261    /// The eviction is performed enough number of times to reduce the cache usage to the given percentage.
1262    pub fn evict_using_2s_random_selection(&mut self, shrink_to: PercentageInteger, now: Slot) {
1263        let mut candidates = self.get_flattened_entries(true, true);
1264        self.stats
1265            .water_level
1266            .store(candidates.len() as u64, Ordering::Relaxed);
1267        let num_to_unload = candidates
1268            .len()
1269            .saturating_sub(shrink_to.apply_to(MAX_LOADED_ENTRY_COUNT));
1270        fn random_index_and_usage_counter(
1271            candidates: &[(Pubkey, Arc<ProgramCacheEntry>)],
1272            now: Slot,
1273        ) -> (usize, u64) {
1274            let mut rng = thread_rng();
1275            let index = rng.gen_range(0..candidates.len());
1276            let usage_counter = candidates
1277                .get(index)
1278                .expect("Failed to get cached entry")
1279                .1
1280                .decayed_usage_counter(now);
1281            (index, usage_counter)
1282        }
1283
1284        for _ in 0..num_to_unload {
1285            let (index1, usage_counter1) = random_index_and_usage_counter(&candidates, now);
1286            let (index2, usage_counter2) = random_index_and_usage_counter(&candidates, now);
1287
1288            let (program, entry) = if usage_counter1 < usage_counter2 {
1289                candidates.swap_remove(index1)
1290            } else {
1291                candidates.swap_remove(index2)
1292            };
1293            self.unload_program_entry(&program, &entry);
1294        }
1295    }
1296
1297    /// Removes all the entries at the given keys, if they exist
1298    pub fn remove_programs(&mut self, keys: impl Iterator<Item = Pubkey>) {
1299        match &mut self.index {
1300            IndexImplementation::V1 { entries, .. } => {
1301                for k in keys {
1302                    entries.remove(&k);
1303                }
1304            }
1305        }
1306    }
1307
1308    /// This function removes the given entry for the given program from the cache.
1309    /// The function expects that the program and entry exists in the cache. Otherwise it'll panic.
1310    fn unload_program_entry(&mut self, program: &Pubkey, remove_entry: &Arc<ProgramCacheEntry>) {
1311        match &mut self.index {
1312            IndexImplementation::V1 { entries, .. } => {
1313                let second_level = entries.get_mut(program).expect("Cache lookup failed");
1314                let candidate = second_level
1315                    .iter_mut()
1316                    .find(|entry| entry == &remove_entry)
1317                    .expect("Program entry not found");
1318
1319                // Certain entry types cannot be unloaded, such as tombstones, or already unloaded entries.
1320                // For such entries, `to_unloaded()` will return None.
1321                // These entry types do not occupy much memory.
1322                if let Some(unloaded) = candidate.to_unloaded() {
1323                    if candidate.tx_usage_counter.load(Ordering::Relaxed) == 1 {
1324                        self.stats.one_hit_wonders.fetch_add(1, Ordering::Relaxed);
1325                    }
1326                    self.stats
1327                        .evictions
1328                        .entry(*program)
1329                        .and_modify(|c| *c = c.saturating_add(1))
1330                        .or_insert(1);
1331                    *candidate = Arc::new(unloaded);
1332                }
1333            }
1334        }
1335    }
1336
1337    fn unload_program_entries<'a>(
1338        &mut self,
1339        remove: impl Iterator<Item = &'a (Pubkey, Arc<ProgramCacheEntry>)>,
1340    ) {
1341        for (program, entry) in remove {
1342            self.unload_program_entry(program, entry);
1343        }
1344    }
1345
1346    fn remove_programs_with_no_entries(&mut self) {
1347        match &mut self.index {
1348            IndexImplementation::V1 { entries, .. } => {
1349                let num_programs_before_removal = entries.len();
1350                entries.retain(|_key, second_level| !second_level.is_empty());
1351                if entries.len() < num_programs_before_removal {
1352                    self.stats.empty_entries.fetch_add(
1353                        num_programs_before_removal.saturating_sub(entries.len()) as u64,
1354                        Ordering::Relaxed,
1355                    );
1356                }
1357            }
1358        }
1359    }
1360}
1361
1362#[cfg(feature = "frozen-abi")]
1363impl solana_frozen_abi::abi_example::AbiExample for ProgramCacheEntry {
1364    fn example() -> Self {
1365        // ProgramCacheEntry isn't serializable by definition.
1366        Self::default()
1367    }
1368}
1369
1370#[cfg(feature = "frozen-abi")]
1371impl<FG: ForkGraph> solana_frozen_abi::abi_example::AbiExample for ProgramCache<FG> {
1372    fn example() -> Self {
1373        // ProgramCache isn't serializable by definition.
1374        Self::new(Slot::default(), Epoch::default())
1375    }
1376}
1377
1378#[cfg(test)]
1379mod tests {
1380    use {
1381        crate::loaded_programs::{
1382            BlockRelation, ForkGraph, ProgramCache, ProgramCacheEntry, ProgramCacheEntryOwner,
1383            ProgramCacheEntryType, ProgramCacheForTxBatch, ProgramCacheMatchCriteria,
1384            ProgramRuntimeEnvironment, ProgramRuntimeEnvironments, DELAY_VISIBILITY_SLOT_OFFSET,
1385        },
1386        assert_matches::assert_matches,
1387        percentage::Percentage,
1388        solana_clock::Slot,
1389        solana_pubkey::Pubkey,
1390        solana_sbpf::{elf::Executable, program::BuiltinProgram},
1391        std::{
1392            fs::File,
1393            io::Read,
1394            ops::ControlFlow,
1395            sync::{
1396                atomic::{AtomicU64, Ordering},
1397                Arc, RwLock,
1398            },
1399        },
1400        test_case::{test_case, test_matrix},
1401    };
1402
1403    static MOCK_ENVIRONMENT: std::sync::OnceLock<ProgramRuntimeEnvironment> =
1404        std::sync::OnceLock::<ProgramRuntimeEnvironment>::new();
1405
1406    fn get_mock_env() -> ProgramRuntimeEnvironment {
1407        MOCK_ENVIRONMENT
1408            .get_or_init(|| Arc::new(BuiltinProgram::new_mock()))
1409            .clone()
1410    }
1411
1412    fn new_mock_cache<FG: ForkGraph>() -> ProgramCache<FG> {
1413        let mut cache = ProgramCache::new(0, 0);
1414        cache.environments.program_runtime_v1 = get_mock_env();
1415        cache
1416    }
1417
1418    fn new_test_entry(deployment_slot: Slot, effective_slot: Slot) -> Arc<ProgramCacheEntry> {
1419        new_test_entry_with_usage(deployment_slot, effective_slot, AtomicU64::default())
1420    }
1421
1422    fn new_loaded_entry(env: ProgramRuntimeEnvironment) -> ProgramCacheEntryType {
1423        let mut elf = Vec::new();
1424        File::open("../programs/bpf_loader/test_elfs/out/noop_aligned.so")
1425            .unwrap()
1426            .read_to_end(&mut elf)
1427            .unwrap();
1428        let executable = Executable::load(&elf, env).unwrap();
1429        ProgramCacheEntryType::Loaded(executable)
1430    }
1431
1432    fn new_test_entry_with_usage(
1433        deployment_slot: Slot,
1434        effective_slot: Slot,
1435        usage_counter: AtomicU64,
1436    ) -> Arc<ProgramCacheEntry> {
1437        Arc::new(ProgramCacheEntry {
1438            program: new_loaded_entry(get_mock_env()),
1439            account_owner: ProgramCacheEntryOwner::LoaderV2,
1440            account_size: 0,
1441            deployment_slot,
1442            effective_slot,
1443            tx_usage_counter: Arc::new(usage_counter),
1444            latest_access_slot: AtomicU64::new(deployment_slot),
1445        })
1446    }
1447
1448    fn new_test_builtin_entry(
1449        deployment_slot: Slot,
1450        effective_slot: Slot,
1451    ) -> Arc<ProgramCacheEntry> {
1452        Arc::new(ProgramCacheEntry {
1453            program: ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1454            account_owner: ProgramCacheEntryOwner::NativeLoader,
1455            account_size: 0,
1456            deployment_slot,
1457            effective_slot,
1458            tx_usage_counter: Arc::default(),
1459            latest_access_slot: AtomicU64::default(),
1460        })
1461    }
1462
1463    fn set_tombstone<FG: ForkGraph>(
1464        cache: &mut ProgramCache<FG>,
1465        key: Pubkey,
1466        slot: Slot,
1467        reason: ProgramCacheEntryType,
1468    ) -> Arc<ProgramCacheEntry> {
1469        let program = Arc::new(ProgramCacheEntry::new_tombstone(
1470            slot,
1471            ProgramCacheEntryOwner::LoaderV2,
1472            reason,
1473        ));
1474        cache.assign_program(key, program.clone());
1475        program
1476    }
1477
1478    fn insert_unloaded_entry<FG: ForkGraph>(
1479        cache: &mut ProgramCache<FG>,
1480        key: Pubkey,
1481        slot: Slot,
1482    ) -> Arc<ProgramCacheEntry> {
1483        let loaded = new_test_entry_with_usage(slot, slot.saturating_add(1), AtomicU64::default());
1484        let unloaded = Arc::new(loaded.to_unloaded().expect("Failed to unload the program"));
1485        cache.assign_program(key, unloaded.clone());
1486        unloaded
1487    }
1488
1489    fn num_matching_entries<P, FG>(cache: &ProgramCache<FG>, predicate: P) -> usize
1490    where
1491        P: Fn(&ProgramCacheEntryType) -> bool,
1492        FG: ForkGraph,
1493    {
1494        cache
1495            .get_flattened_entries_for_tests()
1496            .iter()
1497            .filter(|(_key, program)| predicate(&program.program))
1498            .count()
1499    }
1500
1501    #[test]
1502    fn test_usage_counter_decay() {
1503        let _cache = new_mock_cache::<TestForkGraph>();
1504        let program = new_test_entry_with_usage(10, 11, AtomicU64::new(32));
1505        program.update_access_slot(15);
1506        assert_eq!(program.decayed_usage_counter(15), 32);
1507        assert_eq!(program.decayed_usage_counter(16), 16);
1508        assert_eq!(program.decayed_usage_counter(17), 8);
1509        assert_eq!(program.decayed_usage_counter(18), 4);
1510        assert_eq!(program.decayed_usage_counter(19), 2);
1511        assert_eq!(program.decayed_usage_counter(20), 1);
1512        assert_eq!(program.decayed_usage_counter(21), 0);
1513        assert_eq!(program.decayed_usage_counter(15), 32);
1514        assert_eq!(program.decayed_usage_counter(14), 32);
1515
1516        program.update_access_slot(18);
1517        assert_eq!(program.decayed_usage_counter(15), 32);
1518        assert_eq!(program.decayed_usage_counter(16), 32);
1519        assert_eq!(program.decayed_usage_counter(17), 32);
1520        assert_eq!(program.decayed_usage_counter(18), 32);
1521        assert_eq!(program.decayed_usage_counter(19), 16);
1522        assert_eq!(program.decayed_usage_counter(20), 8);
1523        assert_eq!(program.decayed_usage_counter(21), 4);
1524
1525        // Decay for 63 or more slots
1526        assert_eq!(program.decayed_usage_counter(18 + 63), 0);
1527        assert_eq!(program.decayed_usage_counter(100), 0);
1528    }
1529
1530    fn program_deploy_test_helper(
1531        cache: &mut ProgramCache<TestForkGraph>,
1532        program: Pubkey,
1533        deployment_slots: Vec<Slot>,
1534        usage_counters: Vec<u64>,
1535        programs: &mut Vec<(Pubkey, Slot, u64)>,
1536    ) {
1537        // Add multiple entries for program
1538        deployment_slots
1539            .iter()
1540            .enumerate()
1541            .for_each(|(i, deployment_slot)| {
1542                let usage_counter = *usage_counters.get(i).unwrap_or(&0);
1543                cache.assign_program(
1544                    program,
1545                    new_test_entry_with_usage(
1546                        *deployment_slot,
1547                        (*deployment_slot).saturating_add(2),
1548                        AtomicU64::new(usage_counter),
1549                    ),
1550                );
1551                programs.push((program, *deployment_slot, usage_counter));
1552            });
1553
1554        // Add tombstones entries for program
1555        let env = Arc::new(BuiltinProgram::new_mock());
1556        for slot in 21..31 {
1557            set_tombstone(
1558                cache,
1559                program,
1560                slot,
1561                ProgramCacheEntryType::FailedVerification(env.clone()),
1562            );
1563        }
1564
1565        // Add unloaded entries for program
1566        for slot in 31..41 {
1567            insert_unloaded_entry(cache, program, slot);
1568        }
1569    }
1570
1571    #[test]
1572    fn test_random_eviction() {
1573        let mut programs = vec![];
1574
1575        let mut cache = new_mock_cache::<TestForkGraph>();
1576
1577        // This test adds different kind of entries to the cache.
1578        // Tombstones and unloaded entries are expected to not be evicted.
1579        // It also adds multiple entries for three programs as it tries to create a typical cache instance.
1580
1581        // Program 1
1582        program_deploy_test_helper(
1583            &mut cache,
1584            Pubkey::new_unique(),
1585            vec![0, 10, 20],
1586            vec![4, 5, 25],
1587            &mut programs,
1588        );
1589
1590        // Program 2
1591        program_deploy_test_helper(
1592            &mut cache,
1593            Pubkey::new_unique(),
1594            vec![5, 11],
1595            vec![0, 2],
1596            &mut programs,
1597        );
1598
1599        // Program 3
1600        program_deploy_test_helper(
1601            &mut cache,
1602            Pubkey::new_unique(),
1603            vec![0, 5, 15],
1604            vec![100, 3, 20],
1605            &mut programs,
1606        );
1607
1608        // 1 for each deployment slot
1609        let num_loaded_expected = 8;
1610        // 10 for each program
1611        let num_unloaded_expected = 30;
1612        // 10 for each program
1613        let num_tombstones_expected = 30;
1614
1615        // Count the number of loaded, unloaded and tombstone entries.
1616        programs.sort_by_key(|(_id, _slot, usage_count)| *usage_count);
1617        let num_loaded = num_matching_entries(&cache, |program_type| {
1618            matches!(program_type, ProgramCacheEntryType::Loaded(_))
1619        });
1620        let num_unloaded = num_matching_entries(&cache, |program_type| {
1621            matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1622        });
1623        let num_tombstones = num_matching_entries(&cache, |program_type| {
1624            matches!(
1625                program_type,
1626                ProgramCacheEntryType::DelayVisibility
1627                    | ProgramCacheEntryType::FailedVerification(_)
1628                    | ProgramCacheEntryType::Closed
1629            )
1630        });
1631
1632        // Test that the cache is constructed with the expected number of entries.
1633        assert_eq!(num_loaded, num_loaded_expected);
1634        assert_eq!(num_unloaded, num_unloaded_expected);
1635        assert_eq!(num_tombstones, num_tombstones_expected);
1636
1637        // Evict entries from the cache
1638        let eviction_pct = 1;
1639
1640        let num_loaded_expected =
1641            Percentage::from(eviction_pct).apply_to(crate::loaded_programs::MAX_LOADED_ENTRY_COUNT);
1642        let num_unloaded_expected = num_unloaded_expected + num_loaded - num_loaded_expected;
1643        cache.evict_using_2s_random_selection(Percentage::from(eviction_pct), 21);
1644
1645        // Count the number of loaded, unloaded and tombstone entries.
1646        let num_loaded = num_matching_entries(&cache, |program_type| {
1647            matches!(program_type, ProgramCacheEntryType::Loaded(_))
1648        });
1649        let num_unloaded = num_matching_entries(&cache, |program_type| {
1650            matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1651        });
1652        let num_tombstones = num_matching_entries(&cache, |program_type| {
1653            matches!(program_type, ProgramCacheEntryType::FailedVerification(_))
1654        });
1655
1656        // However many entries are left after the shrink
1657        assert_eq!(num_loaded, num_loaded_expected);
1658        // The original unloaded entries + the evicted loaded entries
1659        assert_eq!(num_unloaded, num_unloaded_expected);
1660        // The original tombstones are not evicted
1661        assert_eq!(num_tombstones, num_tombstones_expected);
1662    }
1663
1664    #[test]
1665    fn test_eviction() {
1666        let mut programs = vec![];
1667        let mut cache = new_mock_cache::<TestForkGraph>();
1668
1669        // Program 1
1670        program_deploy_test_helper(
1671            &mut cache,
1672            Pubkey::new_unique(),
1673            vec![0, 10, 20],
1674            vec![4, 5, 25],
1675            &mut programs,
1676        );
1677
1678        // Program 2
1679        program_deploy_test_helper(
1680            &mut cache,
1681            Pubkey::new_unique(),
1682            vec![5, 11],
1683            vec![0, 2],
1684            &mut programs,
1685        );
1686
1687        // Program 3
1688        program_deploy_test_helper(
1689            &mut cache,
1690            Pubkey::new_unique(),
1691            vec![0, 5, 15],
1692            vec![100, 3, 20],
1693            &mut programs,
1694        );
1695
1696        // 1 for each deployment slot
1697        let num_loaded_expected = 8;
1698        // 10 for each program
1699        let num_unloaded_expected = 30;
1700        // 10 for each program
1701        let num_tombstones_expected = 30;
1702
1703        // Count the number of loaded, unloaded and tombstone entries.
1704        programs.sort_by_key(|(_id, _slot, usage_count)| *usage_count);
1705        let num_loaded = num_matching_entries(&cache, |program_type| {
1706            matches!(program_type, ProgramCacheEntryType::Loaded(_))
1707        });
1708        let num_unloaded = num_matching_entries(&cache, |program_type| {
1709            matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1710        });
1711        let num_tombstones = num_matching_entries(&cache, |program_type| {
1712            matches!(program_type, ProgramCacheEntryType::FailedVerification(_))
1713        });
1714
1715        // Test that the cache is constructed with the expected number of entries.
1716        assert_eq!(num_loaded, num_loaded_expected);
1717        assert_eq!(num_unloaded, num_unloaded_expected);
1718        assert_eq!(num_tombstones, num_tombstones_expected);
1719
1720        // Evict entries from the cache
1721        let eviction_pct = 1;
1722
1723        let num_loaded_expected =
1724            Percentage::from(eviction_pct).apply_to(crate::loaded_programs::MAX_LOADED_ENTRY_COUNT);
1725        let num_unloaded_expected = num_unloaded_expected + num_loaded - num_loaded_expected;
1726
1727        cache.sort_and_unload(Percentage::from(eviction_pct));
1728
1729        // Check that every program is still in the cache.
1730        let entries = cache.get_flattened_entries_for_tests();
1731        programs.iter().for_each(|entry| {
1732            assert!(entries.iter().any(|(key, _entry)| key == &entry.0));
1733        });
1734
1735        let unloaded = entries
1736            .iter()
1737            .filter_map(|(key, program)| {
1738                matches!(program.program, ProgramCacheEntryType::Unloaded(_))
1739                    .then_some((*key, program.tx_usage_counter.load(Ordering::Relaxed)))
1740            })
1741            .collect::<Vec<(Pubkey, u64)>>();
1742
1743        for index in 0..3 {
1744            let expected = programs.get(index).expect("Missing program");
1745            assert!(unloaded.contains(&(expected.0, expected.2)));
1746        }
1747
1748        // Count the number of loaded, unloaded and tombstone entries.
1749        let num_loaded = num_matching_entries(&cache, |program_type| {
1750            matches!(program_type, ProgramCacheEntryType::Loaded(_))
1751        });
1752        let num_unloaded = num_matching_entries(&cache, |program_type| {
1753            matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1754        });
1755        let num_tombstones = num_matching_entries(&cache, |program_type| {
1756            matches!(
1757                program_type,
1758                ProgramCacheEntryType::DelayVisibility
1759                    | ProgramCacheEntryType::FailedVerification(_)
1760                    | ProgramCacheEntryType::Closed
1761            )
1762        });
1763
1764        // However many entries are left after the shrink
1765        assert_eq!(num_loaded, num_loaded_expected);
1766        // The original unloaded entries + the evicted loaded entries
1767        assert_eq!(num_unloaded, num_unloaded_expected);
1768        // The original tombstones are not evicted
1769        assert_eq!(num_tombstones, num_tombstones_expected);
1770    }
1771
1772    #[test]
1773    fn test_usage_count_of_unloaded_program() {
1774        let mut cache = new_mock_cache::<TestForkGraph>();
1775
1776        let program = Pubkey::new_unique();
1777        let evict_to_pct = 2;
1778        let cache_capacity_after_shrink =
1779            Percentage::from(evict_to_pct).apply_to(crate::loaded_programs::MAX_LOADED_ENTRY_COUNT);
1780        // Add enough programs to the cache to trigger 1 eviction after shrinking.
1781        let num_total_programs = (cache_capacity_after_shrink + 1) as u64;
1782        (0..num_total_programs).for_each(|i| {
1783            cache.assign_program(
1784                program,
1785                new_test_entry_with_usage(i, i + 2, AtomicU64::new(i + 10)),
1786            );
1787        });
1788
1789        cache.sort_and_unload(Percentage::from(evict_to_pct));
1790
1791        let num_unloaded = num_matching_entries(&cache, |program_type| {
1792            matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1793        });
1794        assert_eq!(num_unloaded, 1);
1795
1796        cache
1797            .get_flattened_entries_for_tests()
1798            .iter()
1799            .for_each(|(_key, program)| {
1800                if matches!(program.program, ProgramCacheEntryType::Unloaded(_)) {
1801                    // Test that the usage counter is retained for the unloaded program
1802                    assert_eq!(program.tx_usage_counter.load(Ordering::Relaxed), 10);
1803                    assert_eq!(program.deployment_slot, 0);
1804                    assert_eq!(program.effective_slot, 2);
1805                }
1806            });
1807
1808        // Replenish the program that was just unloaded. Use 0 as the usage counter. This should be
1809        // updated with the usage counter from the unloaded program.
1810        cache.assign_program(program, new_test_entry_with_usage(0, 2, AtomicU64::new(0)));
1811
1812        cache
1813            .get_flattened_entries_for_tests()
1814            .iter()
1815            .for_each(|(_key, program)| {
1816                if matches!(program.program, ProgramCacheEntryType::Unloaded(_))
1817                    && program.deployment_slot == 0
1818                    && program.effective_slot == 2
1819                {
1820                    // Test that the usage counter was correctly updated.
1821                    assert_eq!(program.tx_usage_counter.load(Ordering::Relaxed), 10);
1822                }
1823            });
1824    }
1825
1826    #[test]
1827    fn test_fuzz_assign_program_order() {
1828        use rand::prelude::SliceRandom;
1829        const EXPECTED_ENTRIES: [(u64, u64); 7] =
1830            [(1, 2), (5, 5), (5, 6), (5, 10), (9, 10), (10, 10), (3, 12)];
1831        let mut rng = rand::thread_rng();
1832        let program_id = Pubkey::new_unique();
1833        for _ in 0..1000 {
1834            let mut entries = EXPECTED_ENTRIES.to_vec();
1835            entries.shuffle(&mut rng);
1836            let mut cache = new_mock_cache::<TestForkGraph>();
1837            for (deployment_slot, effective_slot) in entries {
1838                assert!(!cache
1839                    .assign_program(program_id, new_test_entry(deployment_slot, effective_slot)));
1840            }
1841            for ((deployment_slot, effective_slot), entry) in EXPECTED_ENTRIES
1842                .iter()
1843                .zip(cache.get_slot_versions_for_tests(&program_id).iter())
1844            {
1845                assert_eq!(entry.deployment_slot, *deployment_slot);
1846                assert_eq!(entry.effective_slot, *effective_slot);
1847            }
1848        }
1849    }
1850
1851    #[test_matrix(
1852        (
1853            ProgramCacheEntryType::Closed,
1854            ProgramCacheEntryType::FailedVerification(get_mock_env()),
1855            new_loaded_entry(get_mock_env()),
1856        ),
1857        (
1858            ProgramCacheEntryType::FailedVerification(get_mock_env()),
1859            ProgramCacheEntryType::Closed,
1860            ProgramCacheEntryType::Unloaded(get_mock_env()),
1861            new_loaded_entry(get_mock_env()),
1862            ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1863        )
1864    )]
1865    #[test_matrix(
1866        (
1867            ProgramCacheEntryType::Unloaded(get_mock_env()),
1868        ),
1869        (
1870            ProgramCacheEntryType::FailedVerification(get_mock_env()),
1871            ProgramCacheEntryType::Closed,
1872            ProgramCacheEntryType::Unloaded(get_mock_env()),
1873            ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1874        )
1875    )]
1876    #[test_matrix(
1877        (ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),),
1878        (
1879            ProgramCacheEntryType::FailedVerification(get_mock_env()),
1880            ProgramCacheEntryType::Closed,
1881            ProgramCacheEntryType::Unloaded(get_mock_env()),
1882            new_loaded_entry(get_mock_env()),
1883        )
1884    )]
1885    #[should_panic(expected = "Unexpected replacement of an entry")]
1886    fn test_assign_program_failure(old: ProgramCacheEntryType, new: ProgramCacheEntryType) {
1887        let mut cache = new_mock_cache::<TestForkGraph>();
1888        let program_id = Pubkey::new_unique();
1889        assert!(!cache.assign_program(
1890            program_id,
1891            Arc::new(ProgramCacheEntry {
1892                program: old,
1893                account_owner: ProgramCacheEntryOwner::LoaderV2,
1894                account_size: 0,
1895                deployment_slot: 10,
1896                effective_slot: 11,
1897                tx_usage_counter: Arc::default(),
1898                latest_access_slot: AtomicU64::default(),
1899            }),
1900        ));
1901        cache.assign_program(
1902            program_id,
1903            Arc::new(ProgramCacheEntry {
1904                program: new,
1905                account_owner: ProgramCacheEntryOwner::LoaderV2,
1906                account_size: 0,
1907                deployment_slot: 10,
1908                effective_slot: 11,
1909                tx_usage_counter: Arc::default(),
1910                latest_access_slot: AtomicU64::default(),
1911            }),
1912        );
1913    }
1914
1915    #[test_case(
1916        ProgramCacheEntryType::Unloaded(Arc::new(BuiltinProgram::new_mock())),
1917        new_loaded_entry(get_mock_env())
1918    )]
1919    #[test_case(
1920        ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1921        ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock())
1922    )]
1923    fn test_assign_program_success(old: ProgramCacheEntryType, new: ProgramCacheEntryType) {
1924        let mut cache = new_mock_cache::<TestForkGraph>();
1925        let program_id = Pubkey::new_unique();
1926        assert!(!cache.assign_program(
1927            program_id,
1928            Arc::new(ProgramCacheEntry {
1929                program: old,
1930                account_owner: ProgramCacheEntryOwner::LoaderV2,
1931                account_size: 0,
1932                deployment_slot: 10,
1933                effective_slot: 11,
1934                tx_usage_counter: Arc::default(),
1935                latest_access_slot: AtomicU64::default(),
1936            }),
1937        ));
1938        assert!(!cache.assign_program(
1939            program_id,
1940            Arc::new(ProgramCacheEntry {
1941                program: new,
1942                account_owner: ProgramCacheEntryOwner::LoaderV2,
1943                account_size: 0,
1944                deployment_slot: 10,
1945                effective_slot: 11,
1946                tx_usage_counter: Arc::default(),
1947                latest_access_slot: AtomicU64::default(),
1948            }),
1949        ));
1950    }
1951
1952    #[test]
1953    fn test_tombstone() {
1954        let env = Arc::new(BuiltinProgram::new_mock());
1955        let tombstone = ProgramCacheEntry::new_tombstone(
1956            0,
1957            ProgramCacheEntryOwner::LoaderV2,
1958            ProgramCacheEntryType::FailedVerification(env.clone()),
1959        );
1960        assert_matches!(
1961            tombstone.program,
1962            ProgramCacheEntryType::FailedVerification(_)
1963        );
1964        assert!(tombstone.is_tombstone());
1965        assert_eq!(tombstone.deployment_slot, 0);
1966        assert_eq!(tombstone.effective_slot, 0);
1967
1968        let tombstone = ProgramCacheEntry::new_tombstone(
1969            100,
1970            ProgramCacheEntryOwner::LoaderV2,
1971            ProgramCacheEntryType::Closed,
1972        );
1973        assert_matches!(tombstone.program, ProgramCacheEntryType::Closed);
1974        assert!(tombstone.is_tombstone());
1975        assert_eq!(tombstone.deployment_slot, 100);
1976        assert_eq!(tombstone.effective_slot, 100);
1977
1978        let mut cache = new_mock_cache::<TestForkGraph>();
1979        let program1 = Pubkey::new_unique();
1980        let tombstone = set_tombstone(
1981            &mut cache,
1982            program1,
1983            10,
1984            ProgramCacheEntryType::FailedVerification(env.clone()),
1985        );
1986        let slot_versions = cache.get_slot_versions_for_tests(&program1);
1987        assert_eq!(slot_versions.len(), 1);
1988        assert!(slot_versions.first().unwrap().is_tombstone());
1989        assert_eq!(tombstone.deployment_slot, 10);
1990        assert_eq!(tombstone.effective_slot, 10);
1991
1992        // Add a program at slot 50, and a tombstone for the program at slot 60
1993        let program2 = Pubkey::new_unique();
1994        cache.assign_program(program2, new_test_builtin_entry(50, 51));
1995        let slot_versions = cache.get_slot_versions_for_tests(&program2);
1996        assert_eq!(slot_versions.len(), 1);
1997        assert!(!slot_versions.first().unwrap().is_tombstone());
1998
1999        let tombstone = set_tombstone(
2000            &mut cache,
2001            program2,
2002            60,
2003            ProgramCacheEntryType::FailedVerification(env),
2004        );
2005        let slot_versions = cache.get_slot_versions_for_tests(&program2);
2006        assert_eq!(slot_versions.len(), 2);
2007        assert!(!slot_versions.first().unwrap().is_tombstone());
2008        assert!(slot_versions.get(1).unwrap().is_tombstone());
2009        assert!(tombstone.is_tombstone());
2010        assert_eq!(tombstone.deployment_slot, 60);
2011        assert_eq!(tombstone.effective_slot, 60);
2012    }
2013
2014    struct TestForkGraph {
2015        relation: BlockRelation,
2016    }
2017    impl ForkGraph for TestForkGraph {
2018        fn relationship(&self, _a: Slot, _b: Slot) -> BlockRelation {
2019            self.relation
2020        }
2021    }
2022
2023    #[test]
2024    fn test_prune_empty() {
2025        let mut cache = new_mock_cache::<TestForkGraph>();
2026        let fork_graph = Arc::new(RwLock::new(TestForkGraph {
2027            relation: BlockRelation::Unrelated,
2028        }));
2029
2030        cache.set_fork_graph(Arc::downgrade(&fork_graph));
2031
2032        cache.prune(0, 0);
2033        assert!(cache.get_flattened_entries_for_tests().is_empty());
2034
2035        cache.prune(10, 0);
2036        assert!(cache.get_flattened_entries_for_tests().is_empty());
2037
2038        let mut cache = new_mock_cache::<TestForkGraph>();
2039        let fork_graph = Arc::new(RwLock::new(TestForkGraph {
2040            relation: BlockRelation::Ancestor,
2041        }));
2042
2043        cache.set_fork_graph(Arc::downgrade(&fork_graph));
2044
2045        cache.prune(0, 0);
2046        assert!(cache.get_flattened_entries_for_tests().is_empty());
2047
2048        cache.prune(10, 0);
2049        assert!(cache.get_flattened_entries_for_tests().is_empty());
2050
2051        let mut cache = new_mock_cache::<TestForkGraph>();
2052        let fork_graph = Arc::new(RwLock::new(TestForkGraph {
2053            relation: BlockRelation::Descendant,
2054        }));
2055
2056        cache.set_fork_graph(Arc::downgrade(&fork_graph));
2057
2058        cache.prune(0, 0);
2059        assert!(cache.get_flattened_entries_for_tests().is_empty());
2060
2061        cache.prune(10, 0);
2062        assert!(cache.get_flattened_entries_for_tests().is_empty());
2063
2064        let mut cache = new_mock_cache::<TestForkGraph>();
2065        let fork_graph = Arc::new(RwLock::new(TestForkGraph {
2066            relation: BlockRelation::Unknown,
2067        }));
2068        cache.set_fork_graph(Arc::downgrade(&fork_graph));
2069
2070        cache.prune(0, 0);
2071        assert!(cache.get_flattened_entries_for_tests().is_empty());
2072
2073        cache.prune(10, 0);
2074        assert!(cache.get_flattened_entries_for_tests().is_empty());
2075    }
2076
2077    #[test]
2078    fn test_prune_different_env() {
2079        let mut cache = new_mock_cache::<TestForkGraph>();
2080
2081        let fork_graph = Arc::new(RwLock::new(TestForkGraph {
2082            relation: BlockRelation::Ancestor,
2083        }));
2084
2085        cache.set_fork_graph(Arc::downgrade(&fork_graph));
2086
2087        let program1 = Pubkey::new_unique();
2088        cache.assign_program(program1, new_test_entry(10, 10));
2089
2090        let new_env = Arc::new(BuiltinProgram::new_mock());
2091        cache.upcoming_environments = Some(ProgramRuntimeEnvironments {
2092            program_runtime_v1: new_env.clone(),
2093            program_runtime_v2: new_env.clone(),
2094        });
2095        let updated_program = Arc::new(ProgramCacheEntry {
2096            program: new_loaded_entry(new_env.clone()),
2097            account_owner: ProgramCacheEntryOwner::LoaderV2,
2098            account_size: 0,
2099            deployment_slot: 20,
2100            effective_slot: 20,
2101            tx_usage_counter: Arc::default(),
2102            latest_access_slot: AtomicU64::default(),
2103        });
2104        cache.assign_program(program1, updated_program.clone());
2105
2106        // Test that there are 2 entries for the program
2107        assert_eq!(cache.get_slot_versions_for_tests(&program1).len(), 2);
2108
2109        cache.prune(21, cache.latest_root_epoch);
2110
2111        // Test that prune didn't remove the entry, since environments are different.
2112        assert_eq!(cache.get_slot_versions_for_tests(&program1).len(), 2);
2113
2114        cache.prune(22, cache.latest_root_epoch.saturating_add(1));
2115
2116        // Test that prune removed 1 entry, since epoch changed
2117        assert_eq!(cache.get_slot_versions_for_tests(&program1).len(), 1);
2118
2119        let entry = cache
2120            .get_slot_versions_for_tests(&program1)
2121            .first()
2122            .expect("Failed to get the program")
2123            .clone();
2124        // Test that the correct entry remains in the cache
2125        assert_eq!(entry, updated_program);
2126    }
2127
2128    #[derive(Default)]
2129    struct TestForkGraphSpecific {
2130        forks: Vec<Vec<Slot>>,
2131    }
2132
2133    impl TestForkGraphSpecific {
2134        fn insert_fork(&mut self, fork: &[Slot]) {
2135            let mut fork = fork.to_vec();
2136            fork.sort();
2137            self.forks.push(fork)
2138        }
2139    }
2140
2141    impl ForkGraph for TestForkGraphSpecific {
2142        fn relationship(&self, a: Slot, b: Slot) -> BlockRelation {
2143            match self.forks.iter().try_for_each(|fork| {
2144                let relation = fork
2145                    .iter()
2146                    .position(|x| *x == a)
2147                    .and_then(|a_pos| {
2148                        fork.iter().position(|x| *x == b).and_then(|b_pos| {
2149                            (a_pos == b_pos)
2150                                .then_some(BlockRelation::Equal)
2151                                .or_else(|| (a_pos < b_pos).then_some(BlockRelation::Ancestor))
2152                                .or(Some(BlockRelation::Descendant))
2153                        })
2154                    })
2155                    .unwrap_or(BlockRelation::Unrelated);
2156
2157                if relation != BlockRelation::Unrelated {
2158                    return ControlFlow::Break(relation);
2159                }
2160
2161                ControlFlow::Continue(())
2162            }) {
2163                ControlFlow::Break(relation) => relation,
2164                _ => BlockRelation::Unrelated,
2165            }
2166        }
2167    }
2168
2169    fn get_entries_to_load(
2170        cache: &ProgramCache<TestForkGraphSpecific>,
2171        loading_slot: Slot,
2172        keys: &[Pubkey],
2173    ) -> Vec<(Pubkey, ProgramCacheMatchCriteria)> {
2174        let fork_graph = cache.fork_graph.as_ref().unwrap().upgrade().unwrap();
2175        let locked_fork_graph = fork_graph.read().unwrap();
2176        let entries = cache.get_flattened_entries_for_tests();
2177        keys.iter()
2178            .filter_map(|key| {
2179                entries
2180                    .iter()
2181                    .rev()
2182                    .find(|(program_id, entry)| {
2183                        program_id == key
2184                            && matches!(
2185                                locked_fork_graph.relationship(entry.deployment_slot, loading_slot),
2186                                BlockRelation::Equal | BlockRelation::Ancestor,
2187                            )
2188                    })
2189                    .map(|(program_id, _entry)| {
2190                        (*program_id, ProgramCacheMatchCriteria::NoCriteria)
2191                    })
2192            })
2193            .collect()
2194    }
2195
2196    fn match_slot(
2197        extracted: &ProgramCacheForTxBatch,
2198        program: &Pubkey,
2199        deployment_slot: Slot,
2200        working_slot: Slot,
2201    ) -> bool {
2202        assert_eq!(extracted.slot, working_slot);
2203        extracted
2204            .entries
2205            .get(program)
2206            .map(|entry| entry.deployment_slot == deployment_slot)
2207            .unwrap_or(false)
2208    }
2209
2210    fn match_missing(
2211        missing: &[(Pubkey, ProgramCacheMatchCriteria)],
2212        program: &Pubkey,
2213        expected_result: bool,
2214    ) -> bool {
2215        missing.iter().any(|(key, _)| key == program) == expected_result
2216    }
2217
2218    #[test]
2219    fn test_fork_extract_and_prune() {
2220        let mut cache = new_mock_cache::<TestForkGraphSpecific>();
2221
2222        // Fork graph created for the test
2223        //                   0
2224        //                 /   \
2225        //                10    5
2226        //                |     |
2227        //                20    11
2228        //                |     | \
2229        //                22   15  25
2230        //                      |   |
2231        //                     16  27
2232        //                      |
2233        //                     19
2234        //                      |
2235        //                     23
2236
2237        let mut fork_graph = TestForkGraphSpecific::default();
2238        fork_graph.insert_fork(&[0, 10, 20, 22]);
2239        fork_graph.insert_fork(&[0, 5, 11, 15, 16, 18, 19, 21, 23]);
2240        fork_graph.insert_fork(&[0, 5, 11, 25, 27]);
2241
2242        let fork_graph = Arc::new(RwLock::new(fork_graph));
2243        cache.set_fork_graph(Arc::downgrade(&fork_graph));
2244
2245        let program1 = Pubkey::new_unique();
2246        cache.assign_program(program1, new_test_entry(0, 1));
2247        cache.assign_program(program1, new_test_entry(10, 11));
2248        cache.assign_program(program1, new_test_entry(20, 21));
2249
2250        let program2 = Pubkey::new_unique();
2251        cache.assign_program(program2, new_test_entry(5, 6));
2252        cache.assign_program(
2253            program2,
2254            new_test_entry(11, 11 + DELAY_VISIBILITY_SLOT_OFFSET),
2255        );
2256
2257        let program3 = Pubkey::new_unique();
2258        cache.assign_program(program3, new_test_entry(25, 26));
2259
2260        let program4 = Pubkey::new_unique();
2261        cache.assign_program(program4, new_test_entry(0, 1));
2262        cache.assign_program(program4, new_test_entry(5, 6));
2263        // The following is a special case, where effective slot is 3 slots in the future
2264        cache.assign_program(
2265            program4,
2266            new_test_entry(15, 15 + DELAY_VISIBILITY_SLOT_OFFSET),
2267        );
2268
2269        // Current fork graph
2270        //                   0
2271        //                 /   \
2272        //                10    5
2273        //                |     |
2274        //                20    11
2275        //                |     | \
2276        //                22   15  25
2277        //                      |   |
2278        //                     16  27
2279        //                      |
2280        //                     19
2281        //                      |
2282        //                     23
2283
2284        // Testing fork 0 - 10 - 12 - 22 with current slot at 22
2285        let mut missing =
2286            get_entries_to_load(&cache, 22, &[program1, program2, program3, program4]);
2287        assert!(match_missing(&missing, &program2, false));
2288        assert!(match_missing(&missing, &program3, false));
2289        let mut extracted = ProgramCacheForTxBatch::new(22, cache.environments.clone(), None, 0);
2290        cache.extract(&mut missing, &mut extracted, true, true);
2291        assert!(match_slot(&extracted, &program1, 20, 22));
2292        assert!(match_slot(&extracted, &program4, 0, 22));
2293
2294        // Testing fork 0 - 5 - 11 - 15 - 16 with current slot at 15
2295        let mut missing =
2296            get_entries_to_load(&cache, 15, &[program1, program2, program3, program4]);
2297        assert!(match_missing(&missing, &program3, false));
2298        let mut extracted = ProgramCacheForTxBatch::new(15, cache.environments.clone(), None, 0);
2299        cache.extract(&mut missing, &mut extracted, true, true);
2300        assert!(match_slot(&extracted, &program1, 0, 15));
2301        assert!(match_slot(&extracted, &program2, 11, 15));
2302        // The effective slot of program4 deployed in slot 15 is 19. So it should not be usable in slot 16.
2303        // A delay visibility tombstone should be returned here.
2304        let tombstone = extracted
2305            .find(&program4)
2306            .expect("Failed to find the tombstone");
2307        assert_matches!(tombstone.program, ProgramCacheEntryType::DelayVisibility);
2308        assert_eq!(tombstone.deployment_slot, 15);
2309
2310        // Testing the same fork above, but current slot is now 18 (equal to effective slot of program4).
2311        let mut missing =
2312            get_entries_to_load(&cache, 18, &[program1, program2, program3, program4]);
2313        assert!(match_missing(&missing, &program3, false));
2314        let mut extracted = ProgramCacheForTxBatch::new(18, cache.environments.clone(), None, 0);
2315        cache.extract(&mut missing, &mut extracted, true, true);
2316        assert!(match_slot(&extracted, &program1, 0, 18));
2317        assert!(match_slot(&extracted, &program2, 11, 18));
2318        // The effective slot of program4 deployed in slot 15 is 18. So it should be usable in slot 18.
2319        assert!(match_slot(&extracted, &program4, 15, 18));
2320
2321        // Testing the same fork above, but current slot is now 23 (future slot than effective slot of program4).
2322        let mut missing =
2323            get_entries_to_load(&cache, 23, &[program1, program2, program3, program4]);
2324        assert!(match_missing(&missing, &program3, false));
2325        let mut extracted = ProgramCacheForTxBatch::new(23, cache.environments.clone(), None, 0);
2326        cache.extract(&mut missing, &mut extracted, true, true);
2327        assert!(match_slot(&extracted, &program1, 0, 23));
2328        assert!(match_slot(&extracted, &program2, 11, 23));
2329        // The effective slot of program4 deployed in slot 15 is 19. So it should be usable in slot 23.
2330        assert!(match_slot(&extracted, &program4, 15, 23));
2331
2332        // Testing fork 0 - 5 - 11 - 15 - 16 with current slot at 11
2333        let mut missing =
2334            get_entries_to_load(&cache, 11, &[program1, program2, program3, program4]);
2335        assert!(match_missing(&missing, &program3, false));
2336        let mut extracted = ProgramCacheForTxBatch::new(11, cache.environments.clone(), None, 0);
2337        cache.extract(&mut missing, &mut extracted, true, true);
2338        assert!(match_slot(&extracted, &program1, 0, 11));
2339        // program2 was updated at slot 11, but is not effective till slot 12. The result should contain a tombstone.
2340        let tombstone = extracted
2341            .find(&program2)
2342            .expect("Failed to find the tombstone");
2343        assert_matches!(tombstone.program, ProgramCacheEntryType::DelayVisibility);
2344        assert_eq!(tombstone.deployment_slot, 11);
2345        assert!(match_slot(&extracted, &program4, 5, 11));
2346
2347        cache.prune(5, 0);
2348
2349        // Fork graph after pruning
2350        //                   0
2351        //                   |
2352        //                   5
2353        //                   |
2354        //                   11
2355        //                   | \
2356        //                  15  25
2357        //                   |   |
2358        //                  16  27
2359        //                   |
2360        //                  19
2361        //                   |
2362        //                  23
2363
2364        // Testing fork 11 - 15 - 16- 19 - 22 with root at 5 and current slot at 22
2365        let mut missing =
2366            get_entries_to_load(&cache, 21, &[program1, program2, program3, program4]);
2367        assert!(match_missing(&missing, &program3, false));
2368        let mut extracted = ProgramCacheForTxBatch::new(21, cache.environments.clone(), None, 0);
2369        cache.extract(&mut missing, &mut extracted, true, true);
2370        // Since the fork was pruned, we should not find the entry deployed at slot 20.
2371        assert!(match_slot(&extracted, &program1, 0, 21));
2372        assert!(match_slot(&extracted, &program2, 11, 21));
2373        assert!(match_slot(&extracted, &program4, 15, 21));
2374
2375        // Testing fork 0 - 5 - 11 - 25 - 27 with current slot at 27
2376        let mut missing =
2377            get_entries_to_load(&cache, 27, &[program1, program2, program3, program4]);
2378        let mut extracted = ProgramCacheForTxBatch::new(27, cache.environments.clone(), None, 0);
2379        cache.extract(&mut missing, &mut extracted, true, true);
2380        assert!(match_slot(&extracted, &program1, 0, 27));
2381        assert!(match_slot(&extracted, &program2, 11, 27));
2382        assert!(match_slot(&extracted, &program3, 25, 27));
2383        assert!(match_slot(&extracted, &program4, 5, 27));
2384
2385        cache.prune(15, 0);
2386
2387        // Fork graph after pruning
2388        //                  0
2389        //                  |
2390        //                  5
2391        //                  |
2392        //                  11
2393        //                  |
2394        //                  15
2395        //                  |
2396        //                  16
2397        //                  |
2398        //                  19
2399        //                  |
2400        //                  23
2401
2402        // Testing fork 16, 19, 23, with root at 15, current slot at 23
2403        let mut missing =
2404            get_entries_to_load(&cache, 23, &[program1, program2, program3, program4]);
2405        assert!(match_missing(&missing, &program3, false));
2406        let mut extracted = ProgramCacheForTxBatch::new(23, cache.environments.clone(), None, 0);
2407        cache.extract(&mut missing, &mut extracted, true, true);
2408        assert!(match_slot(&extracted, &program1, 0, 23));
2409        assert!(match_slot(&extracted, &program2, 11, 23));
2410        assert!(match_slot(&extracted, &program4, 15, 23));
2411    }
2412
2413    #[test]
2414    fn test_extract_using_deployment_slot() {
2415        let mut cache = new_mock_cache::<TestForkGraphSpecific>();
2416
2417        // Fork graph created for the test
2418        //                   0
2419        //                 /   \
2420        //                10    5
2421        //                |     |
2422        //                20    11
2423        //                |     | \
2424        //                22   15  25
2425        //                      |   |
2426        //                     16  27
2427        //                      |
2428        //                     19
2429        //                      |
2430        //                     23
2431
2432        let mut fork_graph = TestForkGraphSpecific::default();
2433        fork_graph.insert_fork(&[0, 10, 20, 22]);
2434        fork_graph.insert_fork(&[0, 5, 11, 12, 15, 16, 18, 19, 21, 23]);
2435        fork_graph.insert_fork(&[0, 5, 11, 25, 27]);
2436
2437        let fork_graph = Arc::new(RwLock::new(fork_graph));
2438        cache.set_fork_graph(Arc::downgrade(&fork_graph));
2439
2440        let program1 = Pubkey::new_unique();
2441        cache.assign_program(program1, new_test_entry(0, 1));
2442        cache.assign_program(program1, new_test_entry(20, 21));
2443
2444        let program2 = Pubkey::new_unique();
2445        cache.assign_program(program2, new_test_entry(5, 6));
2446        cache.assign_program(program2, new_test_entry(11, 12));
2447
2448        let program3 = Pubkey::new_unique();
2449        cache.assign_program(program3, new_test_entry(25, 26));
2450
2451        // Testing fork 0 - 5 - 11 - 15 - 16 - 19 - 21 - 23 with current slot at 19
2452        let mut missing = get_entries_to_load(&cache, 12, &[program1, program2, program3]);
2453        assert!(match_missing(&missing, &program3, false));
2454        let mut extracted = ProgramCacheForTxBatch::new(12, cache.environments.clone(), None, 0);
2455        cache.extract(&mut missing, &mut extracted, true, true);
2456        assert!(match_slot(&extracted, &program1, 0, 12));
2457        assert!(match_slot(&extracted, &program2, 11, 12));
2458
2459        // Test the same fork, but request the program modified at a later slot than what's in the cache.
2460        let mut missing = get_entries_to_load(&cache, 12, &[program1, program2, program3]);
2461        missing.get_mut(0).unwrap().1 = ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(5);
2462        missing.get_mut(1).unwrap().1 = ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(5);
2463        assert!(match_missing(&missing, &program3, false));
2464        let mut extracted = ProgramCacheForTxBatch::new(12, cache.environments.clone(), None, 0);
2465        cache.extract(&mut missing, &mut extracted, true, true);
2466        assert!(match_missing(&missing, &program1, true));
2467        assert!(match_slot(&extracted, &program2, 11, 12));
2468    }
2469
2470    #[test]
2471    fn test_extract_unloaded() {
2472        let mut cache = new_mock_cache::<TestForkGraphSpecific>();
2473
2474        // Fork graph created for the test
2475        //                   0
2476        //                 /   \
2477        //                10    5
2478        //                |     |
2479        //                20    11
2480        //                |     | \
2481        //                22   15  25
2482        //                      |   |
2483        //                     16  27
2484        //                      |
2485        //                     19
2486        //                      |
2487        //                     23
2488
2489        let mut fork_graph = TestForkGraphSpecific::default();
2490        fork_graph.insert_fork(&[0, 10, 20, 22]);
2491        fork_graph.insert_fork(&[0, 5, 11, 15, 16, 19, 21, 23]);
2492        fork_graph.insert_fork(&[0, 5, 11, 25, 27]);
2493
2494        let fork_graph = Arc::new(RwLock::new(fork_graph));
2495        cache.set_fork_graph(Arc::downgrade(&fork_graph));
2496
2497        let program1 = Pubkey::new_unique();
2498        cache.assign_program(program1, new_test_entry(0, 1));
2499        cache.assign_program(program1, new_test_entry(20, 21));
2500
2501        let program2 = Pubkey::new_unique();
2502        cache.assign_program(program2, new_test_entry(5, 6));
2503        cache.assign_program(program2, new_test_entry(11, 12));
2504
2505        let program3 = Pubkey::new_unique();
2506        // Insert an unloaded program with correct/cache's environment at slot 25
2507        let _ = insert_unloaded_entry(&mut cache, program3, 25);
2508
2509        // Insert another unloaded program with a different environment at slot 20
2510        // Since this entry's environment won't match cache's environment, looking up this
2511        // entry should return missing instead of unloaded entry.
2512        cache.assign_program(
2513            program3,
2514            Arc::new(
2515                new_test_entry(20, 21)
2516                    .to_unloaded()
2517                    .expect("Failed to create unloaded program"),
2518            ),
2519        );
2520
2521        // Testing fork 0 - 5 - 11 - 15 - 16 - 19 - 21 - 23 with current slot at 19
2522        let mut missing = get_entries_to_load(&cache, 19, &[program1, program2, program3]);
2523        assert!(match_missing(&missing, &program3, false));
2524        let mut extracted = ProgramCacheForTxBatch::new(19, cache.environments.clone(), None, 0);
2525        cache.extract(&mut missing, &mut extracted, true, true);
2526        assert!(match_slot(&extracted, &program1, 0, 19));
2527        assert!(match_slot(&extracted, &program2, 11, 19));
2528
2529        // Testing fork 0 - 5 - 11 - 25 - 27 with current slot at 27
2530        let mut missing = get_entries_to_load(&cache, 27, &[program1, program2, program3]);
2531        let mut extracted = ProgramCacheForTxBatch::new(27, cache.environments.clone(), None, 0);
2532        cache.extract(&mut missing, &mut extracted, true, true);
2533        assert!(match_slot(&extracted, &program1, 0, 27));
2534        assert!(match_slot(&extracted, &program2, 11, 27));
2535        assert!(match_missing(&missing, &program3, true));
2536
2537        // Testing fork 0 - 10 - 20 - 22 with current slot at 22
2538        let mut missing = get_entries_to_load(&cache, 22, &[program1, program2, program3]);
2539        assert!(match_missing(&missing, &program2, false));
2540        let mut extracted = ProgramCacheForTxBatch::new(22, cache.environments.clone(), None, 0);
2541        cache.extract(&mut missing, &mut extracted, true, true);
2542        assert!(match_slot(&extracted, &program1, 20, 22));
2543        assert!(match_missing(&missing, &program3, true));
2544    }
2545
2546    #[test]
2547    fn test_extract_nonexistent() {
2548        let mut cache = new_mock_cache::<TestForkGraphSpecific>();
2549        let fork_graph = TestForkGraphSpecific::default();
2550        let fork_graph = Arc::new(RwLock::new(fork_graph));
2551        cache.set_fork_graph(Arc::downgrade(&fork_graph));
2552
2553        let program1 = Pubkey::new_unique();
2554        let mut missing = vec![(program1, ProgramCacheMatchCriteria::NoCriteria)];
2555        let mut extracted = ProgramCacheForTxBatch::new(0, cache.environments.clone(), None, 0);
2556        cache.extract(&mut missing, &mut extracted, true, true);
2557        assert!(match_missing(&missing, &program1, true));
2558    }
2559
2560    #[test]
2561    fn test_unloaded() {
2562        let mut cache = new_mock_cache::<TestForkGraph>();
2563        for program_cache_entry_type in [
2564            ProgramCacheEntryType::FailedVerification(
2565                cache.environments.program_runtime_v1.clone(),
2566            ),
2567            ProgramCacheEntryType::Closed,
2568            ProgramCacheEntryType::Unloaded(cache.environments.program_runtime_v1.clone()),
2569            ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
2570        ] {
2571            let entry = Arc::new(ProgramCacheEntry {
2572                program: program_cache_entry_type,
2573                account_owner: ProgramCacheEntryOwner::LoaderV2,
2574                account_size: 0,
2575                deployment_slot: 0,
2576                effective_slot: 0,
2577                tx_usage_counter: Arc::default(),
2578                latest_access_slot: AtomicU64::default(),
2579            });
2580            assert!(entry.to_unloaded().is_none());
2581
2582            // Check that unload_program_entry() does nothing for this entry
2583            let program_id = Pubkey::new_unique();
2584            cache.assign_program(program_id, entry.clone());
2585            cache.unload_program_entry(&program_id, &entry);
2586            assert_eq!(cache.get_slot_versions_for_tests(&program_id).len(), 1);
2587            assert!(cache.stats.evictions.is_empty());
2588        }
2589
2590        let entry = new_test_entry_with_usage(1, 2, AtomicU64::new(3));
2591        let unloaded_entry = entry.to_unloaded().unwrap();
2592        assert_eq!(unloaded_entry.deployment_slot, 1);
2593        assert_eq!(unloaded_entry.effective_slot, 2);
2594        assert_eq!(unloaded_entry.latest_access_slot.load(Ordering::Relaxed), 1);
2595        assert_eq!(unloaded_entry.tx_usage_counter.load(Ordering::Relaxed), 3);
2596
2597        // Check that unload_program_entry() does its work
2598        let program_id = Pubkey::new_unique();
2599        cache.assign_program(program_id, entry.clone());
2600        cache.unload_program_entry(&program_id, &entry);
2601        assert!(cache.stats.evictions.contains_key(&program_id));
2602    }
2603
2604    #[test]
2605    fn test_fork_prune_find_first_ancestor() {
2606        let mut cache = new_mock_cache::<TestForkGraphSpecific>();
2607
2608        // Fork graph created for the test
2609        //                   0
2610        //                 /   \
2611        //                10    5
2612        //                |
2613        //                20
2614
2615        // Deploy program on slot 0, and slot 5.
2616        // Prune the fork that has slot 5. The cache should still have the program
2617        // deployed at slot 0.
2618        let mut fork_graph = TestForkGraphSpecific::default();
2619        fork_graph.insert_fork(&[0, 10, 20]);
2620        fork_graph.insert_fork(&[0, 5]);
2621        let fork_graph = Arc::new(RwLock::new(fork_graph));
2622        cache.set_fork_graph(Arc::downgrade(&fork_graph));
2623
2624        let program1 = Pubkey::new_unique();
2625        cache.assign_program(program1, new_test_entry(0, 1));
2626        cache.assign_program(program1, new_test_entry(5, 6));
2627
2628        cache.prune(10, 0);
2629
2630        let mut missing = get_entries_to_load(&cache, 20, &[program1]);
2631        let mut extracted = ProgramCacheForTxBatch::new(20, cache.environments.clone(), None, 0);
2632        cache.extract(&mut missing, &mut extracted, true, true);
2633
2634        // The cache should have the program deployed at slot 0
2635        assert_eq!(
2636            extracted
2637                .find(&program1)
2638                .expect("Did not find the program")
2639                .deployment_slot,
2640            0
2641        );
2642    }
2643
2644    #[test]
2645    fn test_prune_by_deployment_slot() {
2646        let mut cache = new_mock_cache::<TestForkGraphSpecific>();
2647
2648        // Fork graph created for the test
2649        //                   0
2650        //                 /   \
2651        //                10    5
2652        //                |
2653        //                20
2654
2655        // Deploy program on slot 0, and slot 5.
2656        // Prune the fork that has slot 5. The cache should still have the program
2657        // deployed at slot 0.
2658        let mut fork_graph = TestForkGraphSpecific::default();
2659        fork_graph.insert_fork(&[0, 10, 20]);
2660        fork_graph.insert_fork(&[0, 5, 6]);
2661        let fork_graph = Arc::new(RwLock::new(fork_graph));
2662        cache.set_fork_graph(Arc::downgrade(&fork_graph));
2663
2664        let program1 = Pubkey::new_unique();
2665        cache.assign_program(program1, new_test_entry(0, 1));
2666        cache.assign_program(program1, new_test_entry(5, 6));
2667
2668        let program2 = Pubkey::new_unique();
2669        cache.assign_program(program2, new_test_entry(10, 11));
2670
2671        let mut missing = get_entries_to_load(&cache, 20, &[program1, program2]);
2672        let mut extracted = ProgramCacheForTxBatch::new(20, cache.environments.clone(), None, 0);
2673        cache.extract(&mut missing, &mut extracted, true, true);
2674        assert!(match_slot(&extracted, &program1, 0, 20));
2675        assert!(match_slot(&extracted, &program2, 10, 20));
2676
2677        let mut missing = get_entries_to_load(&cache, 6, &[program1, program2]);
2678        assert!(match_missing(&missing, &program2, false));
2679        let mut extracted = ProgramCacheForTxBatch::new(6, cache.environments.clone(), None, 0);
2680        cache.extract(&mut missing, &mut extracted, true, true);
2681        assert!(match_slot(&extracted, &program1, 5, 6));
2682
2683        // Pruning slot 5 will remove program1 entry deployed at slot 5.
2684        // On fork chaining from slot 5, the entry deployed at slot 0 will become visible.
2685        cache.prune_by_deployment_slot(5);
2686
2687        let mut missing = get_entries_to_load(&cache, 20, &[program1, program2]);
2688        let mut extracted = ProgramCacheForTxBatch::new(20, cache.environments.clone(), None, 0);
2689        cache.extract(&mut missing, &mut extracted, true, true);
2690        assert!(match_slot(&extracted, &program1, 0, 20));
2691        assert!(match_slot(&extracted, &program2, 10, 20));
2692
2693        let mut missing = get_entries_to_load(&cache, 6, &[program1, program2]);
2694        assert!(match_missing(&missing, &program2, false));
2695        let mut extracted = ProgramCacheForTxBatch::new(6, cache.environments.clone(), None, 0);
2696        cache.extract(&mut missing, &mut extracted, true, true);
2697        assert!(match_slot(&extracted, &program1, 0, 6));
2698
2699        // Pruning slot 10 will remove program2 entry deployed at slot 10.
2700        // As there is no other entry for program2, extract() will return it as missing.
2701        cache.prune_by_deployment_slot(10);
2702
2703        let mut missing = get_entries_to_load(&cache, 20, &[program1, program2]);
2704        assert!(match_missing(&missing, &program2, false));
2705        let mut extracted = ProgramCacheForTxBatch::new(20, cache.environments.clone(), None, 0);
2706        cache.extract(&mut missing, &mut extracted, true, true);
2707        assert!(match_slot(&extracted, &program1, 0, 20));
2708    }
2709
2710    #[test]
2711    fn test_usable_entries_for_slot() {
2712        new_mock_cache::<TestForkGraph>();
2713        let tombstone = Arc::new(ProgramCacheEntry::new_tombstone(
2714            0,
2715            ProgramCacheEntryOwner::LoaderV2,
2716            ProgramCacheEntryType::Closed,
2717        ));
2718
2719        assert!(ProgramCache::<TestForkGraph>::matches_criteria(
2720            &tombstone,
2721            &ProgramCacheMatchCriteria::NoCriteria
2722        ));
2723
2724        assert!(ProgramCache::<TestForkGraph>::matches_criteria(
2725            &tombstone,
2726            &ProgramCacheMatchCriteria::Tombstone
2727        ));
2728
2729        assert!(ProgramCache::<TestForkGraph>::matches_criteria(
2730            &tombstone,
2731            &ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(0)
2732        ));
2733
2734        assert!(!ProgramCache::<TestForkGraph>::matches_criteria(
2735            &tombstone,
2736            &ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(1)
2737        ));
2738
2739        let program = new_test_entry(0, 1);
2740
2741        assert!(ProgramCache::<TestForkGraph>::matches_criteria(
2742            &program,
2743            &ProgramCacheMatchCriteria::NoCriteria
2744        ));
2745
2746        assert!(!ProgramCache::<TestForkGraph>::matches_criteria(
2747            &program,
2748            &ProgramCacheMatchCriteria::Tombstone
2749        ));
2750
2751        assert!(ProgramCache::<TestForkGraph>::matches_criteria(
2752            &program,
2753            &ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(0)
2754        ));
2755
2756        assert!(!ProgramCache::<TestForkGraph>::matches_criteria(
2757            &program,
2758            &ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(1)
2759        ));
2760
2761        let program = Arc::new(new_test_entry_with_usage(0, 1, AtomicU64::default()));
2762
2763        assert!(ProgramCache::<TestForkGraph>::matches_criteria(
2764            &program,
2765            &ProgramCacheMatchCriteria::NoCriteria
2766        ));
2767
2768        assert!(!ProgramCache::<TestForkGraph>::matches_criteria(
2769            &program,
2770            &ProgramCacheMatchCriteria::Tombstone
2771        ));
2772
2773        assert!(ProgramCache::<TestForkGraph>::matches_criteria(
2774            &program,
2775            &ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(0)
2776        ));
2777
2778        assert!(!ProgramCache::<TestForkGraph>::matches_criteria(
2779            &program,
2780            &ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(1)
2781        ));
2782    }
2783}