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_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_measure::measure::Measure, solana_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: AtomicU64,
193    /// How often this entry was used by an instruction
194    pub ix_usage_counter: AtomicU64,
195    /// Latest slot in which the entry was used
196    pub latest_access_slot: AtomicU64,
197}
198
199/// Global cache statistics for [ProgramCache].
200#[derive(Debug, Default)]
201pub struct ProgramCacheStats {
202    /// a program was already in the cache
203    pub hits: AtomicU64,
204    /// a program was not found and loaded instead
205    pub misses: AtomicU64,
206    /// a compiled executable was unloaded
207    pub evictions: HashMap<Pubkey, u64>,
208    /// an unloaded program was loaded again (opposite of eviction)
209    pub reloads: AtomicU64,
210    /// a program was loaded or un/re/deployed
211    pub insertions: AtomicU64,
212    /// a program was loaded but can not be extracted on its own fork anymore
213    pub lost_insertions: AtomicU64,
214    /// a program which was already in the cache was reloaded by mistake
215    pub replacements: AtomicU64,
216    /// a program was only used once before being unloaded
217    pub one_hit_wonders: AtomicU64,
218    /// a program became unreachable in the fork graph because of rerooting
219    pub prunes_orphan: AtomicU64,
220    /// a program got pruned because it was not recompiled for the next epoch
221    pub prunes_environment: AtomicU64,
222    /// a program had no entries because all slot versions got pruned
223    pub empty_entries: AtomicU64,
224    /// water level of loaded entries currently cached
225    pub water_level: AtomicU64,
226}
227
228impl ProgramCacheStats {
229    pub fn reset(&mut self) {
230        *self = ProgramCacheStats::default();
231    }
232    pub fn log(&self) {
233        let hits = self.hits.load(Ordering::Relaxed);
234        let misses = self.misses.load(Ordering::Relaxed);
235        let evictions: u64 = self.evictions.values().sum();
236        let reloads = self.reloads.load(Ordering::Relaxed);
237        let insertions = self.insertions.load(Ordering::Relaxed);
238        let lost_insertions = self.lost_insertions.load(Ordering::Relaxed);
239        let replacements = self.replacements.load(Ordering::Relaxed);
240        let one_hit_wonders = self.one_hit_wonders.load(Ordering::Relaxed);
241        let prunes_orphan = self.prunes_orphan.load(Ordering::Relaxed);
242        let prunes_environment = self.prunes_environment.load(Ordering::Relaxed);
243        let empty_entries = self.empty_entries.load(Ordering::Relaxed);
244        let water_level = self.water_level.load(Ordering::Relaxed);
245        debug!(
246            "Loaded Programs Cache Stats -- Hits: {}, Misses: {}, Evictions: {}, Reloads: {}, Insertions: {}, Lost-Insertions: {}, Replacements: {}, One-Hit-Wonders: {}, Prunes-Orphan: {}, Prunes-Environment: {}, Empty: {}, Water-Level: {}",
247            hits, misses, evictions, reloads, insertions, lost_insertions, replacements, one_hit_wonders, prunes_orphan, prunes_environment, empty_entries, water_level
248        );
249        if log_enabled!(log::Level::Trace) && !self.evictions.is_empty() {
250            let mut evictions = self.evictions.iter().collect::<Vec<_>>();
251            evictions.sort_by_key(|e| e.1);
252            let evictions = evictions
253                .into_iter()
254                .rev()
255                .map(|(program_id, evictions)| {
256                    format!("  {:<44}  {}", program_id.to_string(), evictions)
257                })
258                .collect::<Vec<_>>();
259            let evictions = evictions.join("\n");
260            trace!(
261                "Eviction Details:\n  {:<44}  {}\n{}",
262                "Program",
263                "Count",
264                evictions
265            );
266        }
267    }
268}
269
270#[cfg(feature = "metrics")]
271/// Time measurements for loading a single [ProgramCacheEntry].
272#[derive(Debug, Default)]
273pub struct LoadProgramMetrics {
274    /// Program address, but as text
275    pub program_id: String,
276    /// Microseconds it took to `create_program_runtime_environment`
277    pub register_syscalls_us: u64,
278    /// Microseconds it took to `Executable::<InvokeContext>::load`
279    pub load_elf_us: u64,
280    /// Microseconds it took to `executable.verify::<RequisiteVerifier>`
281    pub verify_code_us: u64,
282    /// Microseconds it took to `executable.jit_compile`
283    pub jit_compile_us: u64,
284}
285
286#[cfg(feature = "metrics")]
287impl LoadProgramMetrics {
288    pub fn submit_datapoint(&self, timings: &mut ExecuteDetailsTimings) {
289        timings.create_executor_register_syscalls_us += self.register_syscalls_us;
290        timings.create_executor_load_elf_us += self.load_elf_us;
291        timings.create_executor_verify_code_us += self.verify_code_us;
292        timings.create_executor_jit_compile_us += self.jit_compile_us;
293        datapoint_trace!(
294            "create_executor_trace",
295            ("program_id", self.program_id, String),
296            ("register_syscalls_us", self.register_syscalls_us, i64),
297            ("load_elf_us", self.load_elf_us, i64),
298            ("verify_code_us", self.verify_code_us, i64),
299            ("jit_compile_us", self.jit_compile_us, i64),
300        );
301    }
302}
303
304impl PartialEq for ProgramCacheEntry {
305    fn eq(&self, other: &Self) -> bool {
306        self.effective_slot == other.effective_slot
307            && self.deployment_slot == other.deployment_slot
308            && self.is_tombstone() == other.is_tombstone()
309    }
310}
311
312impl ProgramCacheEntry {
313    /// Creates a new user program
314    pub fn new(
315        loader_key: &Pubkey,
316        program_runtime_environment: ProgramRuntimeEnvironment,
317        deployment_slot: Slot,
318        effective_slot: Slot,
319        elf_bytes: &[u8],
320        account_size: usize,
321        #[cfg(feature = "metrics")] metrics: &mut LoadProgramMetrics,
322    ) -> Result<Self, Box<dyn std::error::Error>> {
323        Self::new_internal(
324            loader_key,
325            program_runtime_environment,
326            deployment_slot,
327            effective_slot,
328            elf_bytes,
329            account_size,
330            #[cfg(feature = "metrics")]
331            metrics,
332            false, /* reloading */
333        )
334    }
335
336    /// Reloads a user program, *without* running the verifier.
337    ///
338    /// # Safety
339    ///
340    /// This method is unsafe since it assumes that the program has already been verified. Should
341    /// only be called when the program was previously verified and loaded in the cache, but was
342    /// unloaded due to inactivity. It should also be checked that the `program_runtime_environment`
343    /// hasn't changed since it was unloaded.
344    pub unsafe fn reload(
345        loader_key: &Pubkey,
346        program_runtime_environment: Arc<BuiltinProgram<InvokeContext<'static>>>,
347        deployment_slot: Slot,
348        effective_slot: Slot,
349        elf_bytes: &[u8],
350        account_size: usize,
351        #[cfg(feature = "metrics")] metrics: &mut LoadProgramMetrics,
352    ) -> Result<Self, Box<dyn std::error::Error>> {
353        Self::new_internal(
354            loader_key,
355            program_runtime_environment,
356            deployment_slot,
357            effective_slot,
358            elf_bytes,
359            account_size,
360            #[cfg(feature = "metrics")]
361            metrics,
362            true, /* reloading */
363        )
364    }
365
366    fn new_internal(
367        loader_key: &Pubkey,
368        program_runtime_environment: Arc<BuiltinProgram<InvokeContext<'static>>>,
369        deployment_slot: Slot,
370        effective_slot: Slot,
371        elf_bytes: &[u8],
372        account_size: usize,
373        #[cfg(feature = "metrics")] metrics: &mut LoadProgramMetrics,
374        reloading: bool,
375    ) -> Result<Self, Box<dyn std::error::Error>> {
376        #[cfg(feature = "metrics")]
377        let load_elf_time = Measure::start("load_elf_time");
378        // The following unused_mut exception is needed for architectures that do not
379        // support JIT compilation.
380        #[allow(unused_mut)]
381        let mut executable = Executable::load(elf_bytes, program_runtime_environment.clone())?;
382        #[cfg(feature = "metrics")]
383        {
384            metrics.load_elf_us = load_elf_time.end_as_us();
385        }
386
387        if !reloading {
388            #[cfg(feature = "metrics")]
389            let verify_code_time = Measure::start("verify_code_time");
390            executable.verify::<RequisiteVerifier>()?;
391            #[cfg(feature = "metrics")]
392            {
393                metrics.verify_code_us = verify_code_time.end_as_us();
394            }
395        }
396
397        #[cfg(all(not(target_os = "windows"), target_arch = "x86_64"))]
398        {
399            #[cfg(feature = "metrics")]
400            let jit_compile_time = Measure::start("jit_compile_time");
401            executable.jit_compile()?;
402            #[cfg(feature = "metrics")]
403            {
404                metrics.jit_compile_us = jit_compile_time.end_as_us();
405            }
406        }
407
408        Ok(Self {
409            deployment_slot,
410            account_owner: ProgramCacheEntryOwner::try_from(loader_key).unwrap(),
411            account_size,
412            effective_slot,
413            tx_usage_counter: AtomicU64::new(0),
414            program: ProgramCacheEntryType::Loaded(executable),
415            ix_usage_counter: AtomicU64::new(0),
416            latest_access_slot: AtomicU64::new(0),
417        })
418    }
419
420    pub fn to_unloaded(&self) -> Option<Self> {
421        match &self.program {
422            ProgramCacheEntryType::Loaded(_) => {}
423            ProgramCacheEntryType::FailedVerification(_)
424            | ProgramCacheEntryType::Closed
425            | ProgramCacheEntryType::DelayVisibility
426            | ProgramCacheEntryType::Unloaded(_)
427            | ProgramCacheEntryType::Builtin(_) => {
428                return None;
429            }
430        }
431        Some(Self {
432            program: ProgramCacheEntryType::Unloaded(self.program.get_environment()?.clone()),
433            account_owner: self.account_owner,
434            account_size: self.account_size,
435            deployment_slot: self.deployment_slot,
436            effective_slot: self.effective_slot,
437            tx_usage_counter: AtomicU64::new(self.tx_usage_counter.load(Ordering::Relaxed)),
438            ix_usage_counter: AtomicU64::new(self.ix_usage_counter.load(Ordering::Relaxed)),
439            latest_access_slot: AtomicU64::new(self.latest_access_slot.load(Ordering::Relaxed)),
440        })
441    }
442
443    /// Creates a new built-in program
444    pub fn new_builtin(
445        deployment_slot: Slot,
446        account_size: usize,
447        builtin_function: BuiltinFunctionWithContext,
448    ) -> Self {
449        let mut program = BuiltinProgram::new_builtin();
450        program
451            .register_function("entrypoint", builtin_function)
452            .unwrap();
453        Self {
454            deployment_slot,
455            account_owner: ProgramCacheEntryOwner::NativeLoader,
456            account_size,
457            effective_slot: deployment_slot,
458            tx_usage_counter: AtomicU64::new(0),
459            program: ProgramCacheEntryType::Builtin(program),
460            ix_usage_counter: AtomicU64::new(0),
461            latest_access_slot: AtomicU64::new(0),
462        }
463    }
464
465    pub fn new_tombstone(
466        slot: Slot,
467        account_owner: ProgramCacheEntryOwner,
468        reason: ProgramCacheEntryType,
469    ) -> Self {
470        let tombstone = Self {
471            program: reason,
472            account_owner,
473            account_size: 0,
474            deployment_slot: slot,
475            effective_slot: slot,
476            tx_usage_counter: AtomicU64::default(),
477            ix_usage_counter: AtomicU64::default(),
478            latest_access_slot: AtomicU64::new(0),
479        };
480        debug_assert!(tombstone.is_tombstone());
481        tombstone
482    }
483
484    pub fn is_tombstone(&self) -> bool {
485        matches!(
486            self.program,
487            ProgramCacheEntryType::FailedVerification(_)
488                | ProgramCacheEntryType::Closed
489                | ProgramCacheEntryType::DelayVisibility
490        )
491    }
492
493    fn is_implicit_delay_visibility_tombstone(&self, slot: Slot) -> bool {
494        !matches!(self.program, ProgramCacheEntryType::Builtin(_))
495            && self.effective_slot.saturating_sub(self.deployment_slot)
496                == DELAY_VISIBILITY_SLOT_OFFSET
497            && slot >= self.deployment_slot
498            && slot < self.effective_slot
499    }
500
501    pub fn update_access_slot(&self, slot: Slot) {
502        let _ = self.latest_access_slot.fetch_max(slot, Ordering::Relaxed);
503    }
504
505    pub fn decayed_usage_counter(&self, now: Slot) -> u64 {
506        let last_access = self.latest_access_slot.load(Ordering::Relaxed);
507        // Shifting the u64 value for more than 63 will cause an overflow.
508        let decaying_for = std::cmp::min(63, now.saturating_sub(last_access));
509        self.tx_usage_counter.load(Ordering::Relaxed) >> decaying_for
510    }
511
512    pub fn account_owner(&self) -> Pubkey {
513        self.account_owner.into()
514    }
515}
516
517/// Globally shared RBPF config and syscall registry
518///
519/// This is only valid in an epoch range as long as no feature affecting RBPF is activated.
520#[derive(Clone, Debug)]
521pub struct ProgramRuntimeEnvironments {
522    /// For program runtime V1
523    pub program_runtime_v1: ProgramRuntimeEnvironment,
524    /// For program runtime V2
525    pub program_runtime_v2: ProgramRuntimeEnvironment,
526}
527
528impl Default for ProgramRuntimeEnvironments {
529    fn default() -> Self {
530        let empty_loader = Arc::new(BuiltinProgram::new_loader(Config::default()));
531        Self {
532            program_runtime_v1: empty_loader.clone(),
533            program_runtime_v2: empty_loader,
534        }
535    }
536}
537
538#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
539pub struct LoadingTaskCookie(u64);
540
541impl LoadingTaskCookie {
542    fn new() -> Self {
543        Self(0)
544    }
545
546    fn update(&mut self) {
547        let LoadingTaskCookie(cookie) = self;
548        *cookie = cookie.wrapping_add(1);
549    }
550}
551
552/// Suspends the thread in case no cooprative loading task was assigned
553#[derive(Debug, Default)]
554pub struct LoadingTaskWaiter {
555    cookie: Mutex<LoadingTaskCookie>,
556    cond: Condvar,
557}
558
559impl LoadingTaskWaiter {
560    pub fn new() -> Self {
561        Self {
562            cookie: Mutex::new(LoadingTaskCookie::new()),
563            cond: Condvar::new(),
564        }
565    }
566
567    pub fn cookie(&self) -> LoadingTaskCookie {
568        *self.cookie.lock().unwrap()
569    }
570
571    pub fn notify(&self) {
572        let mut cookie = self.cookie.lock().unwrap();
573        cookie.update();
574        self.cond.notify_all();
575    }
576
577    pub fn wait(&self, cookie: LoadingTaskCookie) -> LoadingTaskCookie {
578        let cookie_guard = self.cookie.lock().unwrap();
579        *self
580            .cond
581            .wait_while(cookie_guard, |current_cookie| *current_cookie == cookie)
582            .unwrap()
583    }
584}
585
586#[derive(Debug)]
587enum IndexImplementation {
588    /// Fork-graph aware index implementation
589    V1 {
590        /// A two level index:
591        ///
592        /// - the first level is for the address at which programs are deployed
593        /// - the second level for the slot (and thus also fork), sorted by slot number.
594        entries: HashMap<Pubkey, Vec<Arc<ProgramCacheEntry>>>,
595        /// The entries that are getting loaded and have not yet finished loading.
596        ///
597        /// The key is the program address, the value is a tuple of the slot in which the program is
598        /// being loaded and the thread ID doing the load.
599        ///
600        /// It is possible that multiple TX batches from different slots need different versions of a
601        /// program. The deployment slot of a program is only known after load tho,
602        /// so all loads for a given program key are serialized.
603        loading_entries: Mutex<HashMap<Pubkey, (Slot, thread::ThreadId)>>,
604    },
605}
606
607/// This structure is the global cache of loaded, verified and compiled programs.
608///
609/// It ...
610/// - is validator global and fork graph aware, so it can optimize the commonalities across banks.
611/// - handles the visibility rules of un/re/deployments.
612/// - stores the usage statistics and verification status of each program.
613/// - is elastic and uses a probabilistic eviction stragety based on the usage statistics.
614/// - also keeps the compiled executables around, but only for the most used programs.
615/// - supports various kinds of tombstones to avoid loading programs which can not be loaded.
616/// - cleans up entries on orphan branches when the block store is rerooted.
617/// - supports the cache preparation phase before feature activations which can change cached programs.
618/// - manages the environments of the programs and upcoming environments for the next epoch.
619/// - allows for cooperative loading of TX batches which hit the same missing programs simultaneously.
620/// - enforces that all programs used in a batch are eagerly loaded ahead of execution.
621/// - is not persisted to disk or a snapshot, so it needs to cold start and warm up first.
622pub struct ProgramCache<FG: ForkGraph> {
623    /// Index of the cached entries and cooperative loading tasks
624    index: IndexImplementation,
625    /// The slot of the last rerooting
626    pub latest_root_slot: Slot,
627    /// The epoch of the last rerooting
628    pub latest_root_epoch: Epoch,
629    /// Environments of the current epoch
630    pub environments: ProgramRuntimeEnvironments,
631    /// Anticipated replacement for `environments` at the next epoch
632    ///
633    /// This is `None` during most of an epoch, and only `Some` around the boundaries (at the end and beginning of an epoch).
634    /// More precisely, it starts with the cache preparation phase a few hundred slots before the epoch boundary,
635    /// and it ends with the first rerooting after the epoch boundary.
636    pub upcoming_environments: Option<ProgramRuntimeEnvironments>,
637    /// List of loaded programs which should be recompiled before the next epoch (but don't have to).
638    pub programs_to_recompile: Vec<(Pubkey, Arc<ProgramCacheEntry>)>,
639    /// Statistics counters
640    pub stats: ProgramCacheStats,
641    /// Reference to the block store
642    pub fork_graph: Option<Weak<RwLock<FG>>>,
643    /// Coordinates TX batches waiting for others to complete their task during cooperative loading
644    pub loading_task_waiter: Arc<LoadingTaskWaiter>,
645}
646
647impl<FG: ForkGraph> Debug for ProgramCache<FG> {
648    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
649        f.debug_struct("ProgramCache")
650            .field("root slot", &self.latest_root_slot)
651            .field("root epoch", &self.latest_root_epoch)
652            .field("stats", &self.stats)
653            .field("index", &self.index)
654            .finish()
655    }
656}
657
658/// Local view into [ProgramCache] which was extracted for a specific TX batch.
659///
660/// This isolation enables the global [ProgramCache] to continue to evolve (e.g. evictions),
661/// while the TX batch is guaranteed it will continue to find all the programs it requires.
662/// For program management instructions this also buffers them before they are merged back into the global [ProgramCache].
663#[derive(Clone, Debug, Default)]
664pub struct ProgramCacheForTxBatch {
665    /// Pubkey is the address of a program.
666    /// ProgramCacheEntry is the corresponding program entry valid for the slot in which a transaction is being executed.
667    entries: HashMap<Pubkey, Arc<ProgramCacheEntry>>,
668    /// Program entries modified during the transaction batch.
669    modified_entries: HashMap<Pubkey, Arc<ProgramCacheEntry>>,
670    slot: Slot,
671    pub environments: ProgramRuntimeEnvironments,
672    /// Anticipated replacement for `environments` at the next epoch.
673    ///
674    /// This is `None` during most of an epoch, and only `Some` around the boundaries (at the end and beginning of an epoch).
675    /// More precisely, it starts with the cache preparation phase a few hundred slots before the epoch boundary,
676    /// and it ends with the first rerooting after the epoch boundary.
677    /// Needed when a program is deployed at the last slot of an epoch, becomes effective in the next epoch.
678    /// So needs to be compiled with the environment for the next epoch.
679    pub upcoming_environments: Option<ProgramRuntimeEnvironments>,
680    /// The epoch of the last rerooting
681    pub latest_root_epoch: Epoch,
682    pub hit_max_limit: bool,
683    pub loaded_missing: bool,
684    pub merged_modified: bool,
685}
686
687impl ProgramCacheForTxBatch {
688    pub fn new(
689        slot: Slot,
690        environments: ProgramRuntimeEnvironments,
691        upcoming_environments: Option<ProgramRuntimeEnvironments>,
692        latest_root_epoch: Epoch,
693    ) -> Self {
694        Self {
695            entries: HashMap::new(),
696            modified_entries: HashMap::new(),
697            slot,
698            environments,
699            upcoming_environments,
700            latest_root_epoch,
701            hit_max_limit: false,
702            loaded_missing: false,
703            merged_modified: false,
704        }
705    }
706
707    pub fn new_from_cache<FG: ForkGraph>(
708        slot: Slot,
709        epoch: Epoch,
710        cache: &ProgramCache<FG>,
711    ) -> Self {
712        Self {
713            entries: HashMap::new(),
714            modified_entries: HashMap::new(),
715            slot,
716            environments: cache.get_environments_for_epoch(epoch),
717            upcoming_environments: cache.get_upcoming_environments_for_epoch(epoch),
718            latest_root_epoch: cache.latest_root_epoch,
719            hit_max_limit: false,
720            loaded_missing: false,
721            merged_modified: false,
722        }
723    }
724
725    /// Returns the current environments depending on the given epoch
726    pub fn get_environments_for_epoch(&self, epoch: Epoch) -> &ProgramRuntimeEnvironments {
727        if epoch != self.latest_root_epoch {
728            if let Some(upcoming_environments) = self.upcoming_environments.as_ref() {
729                return upcoming_environments;
730            }
731        }
732        &self.environments
733    }
734
735    /// Refill the cache with a single entry. It's typically called during transaction loading, and
736    /// transaction processing (for program management instructions).
737    /// It replaces the existing entry (if any) with the provided entry. The return value contains
738    /// `true` if an entry existed.
739    /// The function also returns the newly inserted value.
740    pub fn replenish(
741        &mut self,
742        key: Pubkey,
743        entry: Arc<ProgramCacheEntry>,
744    ) -> (bool, Arc<ProgramCacheEntry>) {
745        (self.entries.insert(key, entry.clone()).is_some(), entry)
746    }
747
748    /// Store an entry in `modified_entries` for a program modified during the
749    /// transaction batch.
750    pub fn store_modified_entry(&mut self, key: Pubkey, entry: Arc<ProgramCacheEntry>) {
751        self.modified_entries.insert(key, entry);
752    }
753
754    /// Drain the program cache's modified entries, returning the owned
755    /// collection.
756    pub fn drain_modified_entries(&mut self) -> HashMap<Pubkey, Arc<ProgramCacheEntry>> {
757        std::mem::take(&mut self.modified_entries)
758    }
759
760    pub fn find(&self, key: &Pubkey) -> Option<Arc<ProgramCacheEntry>> {
761        // First lookup the cache of the programs modified by the current
762        // transaction. If not found, lookup the cache of the cache of the
763        // programs that are loaded for the transaction batch.
764        self.modified_entries
765            .get(key)
766            .or(self.entries.get(key))
767            .map(|entry| {
768                if entry.is_implicit_delay_visibility_tombstone(self.slot) {
769                    // Found a program entry on the current fork, but it's not effective
770                    // yet. It indicates that the program has delayed visibility. Return
771                    // the tombstone to reflect that.
772                    Arc::new(ProgramCacheEntry::new_tombstone(
773                        entry.deployment_slot,
774                        entry.account_owner,
775                        ProgramCacheEntryType::DelayVisibility,
776                    ))
777                } else {
778                    entry.clone()
779                }
780            })
781    }
782
783    pub fn slot(&self) -> Slot {
784        self.slot
785    }
786
787    pub fn set_slot_for_tests(&mut self, slot: Slot) {
788        self.slot = slot;
789    }
790
791    pub fn merge(&mut self, modified_entries: &HashMap<Pubkey, Arc<ProgramCacheEntry>>) {
792        modified_entries.iter().for_each(|(key, entry)| {
793            self.merged_modified = true;
794            self.replenish(*key, entry.clone());
795        })
796    }
797
798    pub fn is_empty(&self) -> bool {
799        self.entries.is_empty()
800    }
801}
802
803pub enum ProgramCacheMatchCriteria {
804    DeployedOnOrAfterSlot(Slot),
805    Tombstone,
806    NoCriteria,
807}
808
809impl<FG: ForkGraph> ProgramCache<FG> {
810    pub fn new(root_slot: Slot, root_epoch: Epoch) -> Self {
811        Self {
812            index: IndexImplementation::V1 {
813                entries: HashMap::new(),
814                loading_entries: Mutex::new(HashMap::new()),
815            },
816            latest_root_slot: root_slot,
817            latest_root_epoch: root_epoch,
818            environments: ProgramRuntimeEnvironments::default(),
819            upcoming_environments: None,
820            programs_to_recompile: Vec::default(),
821            stats: ProgramCacheStats::default(),
822            fork_graph: None,
823            loading_task_waiter: Arc::new(LoadingTaskWaiter::default()),
824        }
825    }
826
827    pub fn set_fork_graph(&mut self, fork_graph: Weak<RwLock<FG>>) {
828        self.fork_graph = Some(fork_graph);
829    }
830
831    /// Returns the current environments depending on the given epoch
832    pub fn get_environments_for_epoch(&self, epoch: Epoch) -> ProgramRuntimeEnvironments {
833        if epoch != self.latest_root_epoch {
834            if let Some(upcoming_environments) = self.upcoming_environments.as_ref() {
835                return upcoming_environments.clone();
836            }
837        }
838        self.environments.clone()
839    }
840
841    /// Returns the upcoming environments depending on the given epoch
842    pub fn get_upcoming_environments_for_epoch(
843        &self,
844        epoch: Epoch,
845    ) -> Option<ProgramRuntimeEnvironments> {
846        if epoch == self.latest_root_epoch {
847            return self.upcoming_environments.clone();
848        }
849        None
850    }
851
852    /// Insert a single entry. It's typically called during transaction loading,
853    /// when the cache doesn't contain the entry corresponding to program `key`.
854    pub fn assign_program(&mut self, key: Pubkey, entry: Arc<ProgramCacheEntry>) -> bool {
855        debug_assert!(!matches!(
856            &entry.program,
857            ProgramCacheEntryType::DelayVisibility
858        ));
859        // This function always returns `true` during normal operation.
860        // Only during the cache preparation phase this can return `false`
861        // for entries with `upcoming_environments`.
862        fn is_current_env(
863            environments: &ProgramRuntimeEnvironments,
864            env_opt: Option<&ProgramRuntimeEnvironment>,
865        ) -> bool {
866            env_opt
867                .map(|env| {
868                    Arc::ptr_eq(env, &environments.program_runtime_v1)
869                        || Arc::ptr_eq(env, &environments.program_runtime_v2)
870                })
871                .unwrap_or(true)
872        }
873        match &mut self.index {
874            IndexImplementation::V1 { entries, .. } => {
875                let slot_versions = &mut entries.entry(key).or_default();
876                match slot_versions.binary_search_by(|at| {
877                    at.effective_slot
878                        .cmp(&entry.effective_slot)
879                        .then(at.deployment_slot.cmp(&entry.deployment_slot))
880                        .then(
881                            // This `.then()` has no effect during normal operation.
882                            // Only during the cache preparation phase this does allow entries
883                            // which only differ in their environment to be interleaved in `slot_versions`.
884                            is_current_env(&self.environments, at.program.get_environment()).cmp(
885                                &is_current_env(
886                                    &self.environments,
887                                    entry.program.get_environment(),
888                                ),
889                            ),
890                        )
891                }) {
892                    Ok(index) => {
893                        let existing = slot_versions.get_mut(index).unwrap();
894                        match (&existing.program, &entry.program) {
895                            (
896                                ProgramCacheEntryType::Builtin(_),
897                                ProgramCacheEntryType::Builtin(_),
898                            )
899                            | (
900                                ProgramCacheEntryType::Unloaded(_),
901                                ProgramCacheEntryType::Loaded(_),
902                            ) => {}
903                            _ => {
904                                // Something is wrong, I can feel it ...
905                                error!("ProgramCache::assign_program() failed key={:?} existing={:?} entry={:?}", key, slot_versions, entry);
906                                debug_assert!(false, "Unexpected replacement of an entry");
907                                self.stats.replacements.fetch_add(1, Ordering::Relaxed);
908                                return true;
909                            }
910                        }
911                        // Copy over the usage counter to the new entry
912                        entry.tx_usage_counter.fetch_add(
913                            existing.tx_usage_counter.load(Ordering::Relaxed),
914                            Ordering::Relaxed,
915                        );
916                        entry.ix_usage_counter.fetch_add(
917                            existing.ix_usage_counter.load(Ordering::Relaxed),
918                            Ordering::Relaxed,
919                        );
920                        *existing = Arc::clone(&entry);
921                        self.stats.reloads.fetch_add(1, Ordering::Relaxed);
922                    }
923                    Err(index) => {
924                        self.stats.insertions.fetch_add(1, Ordering::Relaxed);
925                        slot_versions.insert(index, Arc::clone(&entry));
926                    }
927                }
928            }
929        }
930        false
931    }
932
933    pub fn prune_by_deployment_slot(&mut self, slot: Slot) {
934        match &mut self.index {
935            IndexImplementation::V1 { entries, .. } => {
936                for second_level in entries.values_mut() {
937                    second_level.retain(|entry| entry.deployment_slot != slot);
938                }
939                self.remove_programs_with_no_entries();
940            }
941        }
942    }
943
944    /// Before rerooting the blockstore this removes all superfluous entries
945    pub fn prune(&mut self, new_root_slot: Slot, new_root_epoch: Epoch) {
946        let Some(fork_graph) = self.fork_graph.clone() else {
947            error!("Program cache doesn't have fork graph.");
948            return;
949        };
950        let fork_graph = fork_graph.upgrade().unwrap();
951        let Ok(fork_graph) = fork_graph.read() else {
952            error!("Failed to lock fork graph for reading.");
953            return;
954        };
955        let mut preparation_phase_ends = false;
956        if self.latest_root_epoch != new_root_epoch {
957            self.latest_root_epoch = new_root_epoch;
958            if let Some(upcoming_environments) = self.upcoming_environments.take() {
959                preparation_phase_ends = true;
960                self.environments = upcoming_environments;
961                self.programs_to_recompile.clear();
962            }
963        }
964        match &mut self.index {
965            IndexImplementation::V1 { entries, .. } => {
966                for second_level in entries.values_mut() {
967                    // Remove entries un/re/deployed on orphan forks
968                    let mut first_ancestor_found = false;
969                    let mut first_ancestor_env = None;
970                    *second_level = second_level
971                        .iter()
972                        .rev()
973                        .filter(|entry| {
974                            let relation =
975                                fork_graph.relationship(entry.deployment_slot, new_root_slot);
976                            if entry.deployment_slot >= new_root_slot {
977                                matches!(relation, BlockRelation::Equal | BlockRelation::Descendant)
978                            } else if matches!(relation, BlockRelation::Ancestor)
979                                || entry.deployment_slot <= self.latest_root_slot
980                            {
981                                if !first_ancestor_found {
982                                    first_ancestor_found = true;
983                                    first_ancestor_env = entry.program.get_environment();
984                                    return true;
985                                }
986                                // Do not prune the entry if the runtime environment of the entry is different
987                                // than the entry that was previously found (stored in first_ancestor_env).
988                                // Different environment indicates that this entry might belong to an older
989                                // epoch that had a different environment (e.g. different feature set).
990                                // Once the root moves to the new/current epoch, the entry will get pruned.
991                                // But, until then the entry might still be getting used by an older slot.
992                                if let Some(entry_env) = entry.program.get_environment() {
993                                    if let Some(env) = first_ancestor_env {
994                                        if !Arc::ptr_eq(entry_env, env) {
995                                            return true;
996                                        }
997                                    }
998                                }
999                                self.stats.prunes_orphan.fetch_add(1, Ordering::Relaxed);
1000                                false
1001                            } else {
1002                                self.stats.prunes_orphan.fetch_add(1, Ordering::Relaxed);
1003                                false
1004                            }
1005                        })
1006                        .filter(|entry| {
1007                            // Remove outdated environment of previous feature set
1008                            if preparation_phase_ends
1009                                && !Self::matches_environment(entry, &self.environments)
1010                            {
1011                                self.stats
1012                                    .prunes_environment
1013                                    .fetch_add(1, Ordering::Relaxed);
1014                                return false;
1015                            }
1016                            true
1017                        })
1018                        .cloned()
1019                        .collect();
1020                    second_level.reverse();
1021                }
1022            }
1023        }
1024        self.remove_programs_with_no_entries();
1025        debug_assert!(self.latest_root_slot <= new_root_slot);
1026        self.latest_root_slot = new_root_slot;
1027    }
1028
1029    fn matches_environment(
1030        entry: &Arc<ProgramCacheEntry>,
1031        environments: &ProgramRuntimeEnvironments,
1032    ) -> bool {
1033        let Some(environment) = entry.program.get_environment() else {
1034            return true;
1035        };
1036        Arc::ptr_eq(environment, &environments.program_runtime_v1)
1037            || Arc::ptr_eq(environment, &environments.program_runtime_v2)
1038    }
1039
1040    fn matches_criteria(
1041        program: &Arc<ProgramCacheEntry>,
1042        criteria: &ProgramCacheMatchCriteria,
1043    ) -> bool {
1044        match criteria {
1045            ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(slot) => {
1046                program.deployment_slot >= *slot
1047            }
1048            ProgramCacheMatchCriteria::Tombstone => program.is_tombstone(),
1049            ProgramCacheMatchCriteria::NoCriteria => true,
1050        }
1051    }
1052
1053    /// Extracts a subset of the programs relevant to a transaction batch
1054    /// and returns which program accounts the accounts DB needs to load.
1055    pub fn extract(
1056        &self,
1057        search_for: &mut Vec<(Pubkey, (ProgramCacheMatchCriteria, u64))>,
1058        loaded_programs_for_tx_batch: &mut ProgramCacheForTxBatch,
1059        is_first_round: bool,
1060    ) -> Option<(Pubkey, u64)> {
1061        debug_assert!(self.fork_graph.is_some());
1062        let fork_graph = self.fork_graph.as_ref().unwrap().upgrade().unwrap();
1063        let locked_fork_graph = fork_graph.read().unwrap();
1064        let mut cooperative_loading_task = None;
1065        match &self.index {
1066            IndexImplementation::V1 {
1067                entries,
1068                loading_entries,
1069            } => {
1070                search_for.retain(|(key, (match_criteria, usage_count))| {
1071                    if let Some(second_level) = entries.get(key) {
1072                        for entry in second_level.iter().rev() {
1073                            if entry.deployment_slot <= self.latest_root_slot
1074                                || matches!(
1075                                    locked_fork_graph.relationship(
1076                                        entry.deployment_slot,
1077                                        loaded_programs_for_tx_batch.slot
1078                                    ),
1079                                    BlockRelation::Equal | BlockRelation::Ancestor
1080                                )
1081                            {
1082                                let entry_to_return = if loaded_programs_for_tx_batch.slot
1083                                    >= entry.effective_slot
1084                                    && Self::matches_environment(
1085                                        entry,
1086                                        &loaded_programs_for_tx_batch.environments,
1087                                    ) {
1088                                    if !Self::matches_criteria(entry, match_criteria) {
1089                                        break;
1090                                    }
1091                                    if let ProgramCacheEntryType::Unloaded(_environment) =
1092                                        &entry.program
1093                                    {
1094                                        break;
1095                                    }
1096                                    entry.clone()
1097                                } else if entry.is_implicit_delay_visibility_tombstone(
1098                                    loaded_programs_for_tx_batch.slot,
1099                                ) {
1100                                    // Found a program entry on the current fork, but it's not effective
1101                                    // yet. It indicates that the program has delayed visibility. Return
1102                                    // the tombstone to reflect that.
1103                                    Arc::new(ProgramCacheEntry::new_tombstone(
1104                                        entry.deployment_slot,
1105                                        entry.account_owner,
1106                                        ProgramCacheEntryType::DelayVisibility,
1107                                    ))
1108                                } else {
1109                                    continue;
1110                                };
1111                                entry_to_return
1112                                    .update_access_slot(loaded_programs_for_tx_batch.slot);
1113                                entry_to_return
1114                                    .tx_usage_counter
1115                                    .fetch_add(*usage_count, Ordering::Relaxed);
1116                                loaded_programs_for_tx_batch
1117                                    .entries
1118                                    .insert(*key, entry_to_return);
1119                                return false;
1120                            }
1121                        }
1122                    }
1123                    if cooperative_loading_task.is_none() {
1124                        let mut loading_entries = loading_entries.lock().unwrap();
1125                        let entry = loading_entries.entry(*key);
1126                        if let Entry::Vacant(entry) = entry {
1127                            entry.insert((
1128                                loaded_programs_for_tx_batch.slot,
1129                                thread::current().id(),
1130                            ));
1131                            cooperative_loading_task = Some((*key, *usage_count));
1132                        }
1133                    }
1134                    true
1135                });
1136            }
1137        }
1138        drop(locked_fork_graph);
1139        if is_first_round {
1140            self.stats
1141                .misses
1142                .fetch_add(search_for.len() as u64, Ordering::Relaxed);
1143            self.stats.hits.fetch_add(
1144                loaded_programs_for_tx_batch.entries.len() as u64,
1145                Ordering::Relaxed,
1146            );
1147        }
1148        cooperative_loading_task
1149    }
1150
1151    /// Called by Bank::replenish_program_cache() for each program that is done loading.
1152    pub fn finish_cooperative_loading_task(
1153        &mut self,
1154        slot: Slot,
1155        key: Pubkey,
1156        loaded_program: Arc<ProgramCacheEntry>,
1157    ) -> bool {
1158        match &mut self.index {
1159            IndexImplementation::V1 {
1160                loading_entries, ..
1161            } => {
1162                let loading_thread = loading_entries.get_mut().unwrap().remove(&key);
1163                debug_assert_eq!(loading_thread, Some((slot, thread::current().id())));
1164                // Check that it will be visible to our own fork once inserted
1165                if loaded_program.deployment_slot > self.latest_root_slot
1166                    && !matches!(
1167                        self.fork_graph
1168                            .as_ref()
1169                            .unwrap()
1170                            .upgrade()
1171                            .unwrap()
1172                            .read()
1173                            .unwrap()
1174                            .relationship(loaded_program.deployment_slot, slot),
1175                        BlockRelation::Equal | BlockRelation::Ancestor
1176                    )
1177                {
1178                    self.stats.lost_insertions.fetch_add(1, Ordering::Relaxed);
1179                }
1180                let was_occupied = self.assign_program(key, loaded_program);
1181                self.loading_task_waiter.notify();
1182                was_occupied
1183            }
1184        }
1185    }
1186
1187    pub fn merge(&mut self, modified_entries: &HashMap<Pubkey, Arc<ProgramCacheEntry>>) {
1188        modified_entries.iter().for_each(|(key, entry)| {
1189            self.assign_program(*key, entry.clone());
1190        })
1191    }
1192
1193    /// Returns the list of entries which are verified and compiled.
1194    pub fn get_flattened_entries(
1195        &self,
1196        include_program_runtime_v1: bool,
1197        _include_program_runtime_v2: bool,
1198    ) -> Vec<(Pubkey, Arc<ProgramCacheEntry>)> {
1199        match &self.index {
1200            IndexImplementation::V1 { entries, .. } => entries
1201                .iter()
1202                .flat_map(|(id, second_level)| {
1203                    second_level
1204                        .iter()
1205                        .filter_map(move |program| match program.program {
1206                            ProgramCacheEntryType::Loaded(_) => {
1207                                if include_program_runtime_v1 {
1208                                    Some((*id, program.clone()))
1209                                } else {
1210                                    None
1211                                }
1212                            }
1213                            _ => None,
1214                        })
1215                })
1216                .collect(),
1217        }
1218    }
1219
1220    /// Returns the list of all entries in the cache.
1221    pub fn get_flattened_entries_for_tests(&self) -> Vec<(Pubkey, Arc<ProgramCacheEntry>)> {
1222        match &self.index {
1223            IndexImplementation::V1 { entries, .. } => entries
1224                .iter()
1225                .flat_map(|(id, second_level)| {
1226                    second_level.iter().map(|program| (*id, program.clone()))
1227                })
1228                .collect(),
1229        }
1230    }
1231
1232    /// Returns the slot versions for the given program id.
1233    pub fn get_slot_versions_for_tests(&self, key: &Pubkey) -> &[Arc<ProgramCacheEntry>] {
1234        match &self.index {
1235            IndexImplementation::V1 { entries, .. } => entries
1236                .get(key)
1237                .map(|second_level| second_level.as_ref())
1238                .unwrap_or(&[]),
1239        }
1240    }
1241
1242    /// Unloads programs which were used infrequently
1243    pub fn sort_and_unload(&mut self, shrink_to: PercentageInteger) {
1244        let mut sorted_candidates = self.get_flattened_entries(true, true);
1245        sorted_candidates
1246            .sort_by_cached_key(|(_id, program)| program.tx_usage_counter.load(Ordering::Relaxed));
1247        let num_to_unload = sorted_candidates
1248            .len()
1249            .saturating_sub(shrink_to.apply_to(MAX_LOADED_ENTRY_COUNT));
1250        self.unload_program_entries(sorted_candidates.iter().take(num_to_unload));
1251    }
1252
1253    /// Evicts programs using 2's random selection, choosing the least used program out of the two entries.
1254    /// The eviction is performed enough number of times to reduce the cache usage to the given percentage.
1255    pub fn evict_using_2s_random_selection(&mut self, shrink_to: PercentageInteger, now: Slot) {
1256        let mut candidates = self.get_flattened_entries(true, true);
1257        self.stats
1258            .water_level
1259            .store(candidates.len() as u64, Ordering::Relaxed);
1260        let num_to_unload = candidates
1261            .len()
1262            .saturating_sub(shrink_to.apply_to(MAX_LOADED_ENTRY_COUNT));
1263        fn random_index_and_usage_counter(
1264            candidates: &[(Pubkey, Arc<ProgramCacheEntry>)],
1265            now: Slot,
1266        ) -> (usize, u64) {
1267            let mut rng = thread_rng();
1268            let index = rng.gen_range(0..candidates.len());
1269            let usage_counter = candidates
1270                .get(index)
1271                .expect("Failed to get cached entry")
1272                .1
1273                .decayed_usage_counter(now);
1274            (index, usage_counter)
1275        }
1276
1277        for _ in 0..num_to_unload {
1278            let (index1, usage_counter1) = random_index_and_usage_counter(&candidates, now);
1279            let (index2, usage_counter2) = random_index_and_usage_counter(&candidates, now);
1280
1281            let (program, entry) = if usage_counter1 < usage_counter2 {
1282                candidates.swap_remove(index1)
1283            } else {
1284                candidates.swap_remove(index2)
1285            };
1286            self.unload_program_entry(&program, &entry);
1287        }
1288    }
1289
1290    /// Removes all the entries at the given keys, if they exist
1291    pub fn remove_programs(&mut self, keys: impl Iterator<Item = Pubkey>) {
1292        match &mut self.index {
1293            IndexImplementation::V1 { entries, .. } => {
1294                for k in keys {
1295                    entries.remove(&k);
1296                }
1297            }
1298        }
1299    }
1300
1301    /// This function removes the given entry for the given program from the cache.
1302    /// The function expects that the program and entry exists in the cache. Otherwise it'll panic.
1303    fn unload_program_entry(&mut self, program: &Pubkey, remove_entry: &Arc<ProgramCacheEntry>) {
1304        match &mut self.index {
1305            IndexImplementation::V1 { entries, .. } => {
1306                let second_level = entries.get_mut(program).expect("Cache lookup failed");
1307                let candidate = second_level
1308                    .iter_mut()
1309                    .find(|entry| entry == &remove_entry)
1310                    .expect("Program entry not found");
1311
1312                // Certain entry types cannot be unloaded, such as tombstones, or already unloaded entries.
1313                // For such entries, `to_unloaded()` will return None.
1314                // These entry types do not occupy much memory.
1315                if let Some(unloaded) = candidate.to_unloaded() {
1316                    if candidate.tx_usage_counter.load(Ordering::Relaxed) == 1 {
1317                        self.stats.one_hit_wonders.fetch_add(1, Ordering::Relaxed);
1318                    }
1319                    self.stats
1320                        .evictions
1321                        .entry(*program)
1322                        .and_modify(|c| *c = c.saturating_add(1))
1323                        .or_insert(1);
1324                    *candidate = Arc::new(unloaded);
1325                }
1326            }
1327        }
1328    }
1329
1330    fn unload_program_entries<'a>(
1331        &mut self,
1332        remove: impl Iterator<Item = &'a (Pubkey, Arc<ProgramCacheEntry>)>,
1333    ) {
1334        for (program, entry) in remove {
1335            self.unload_program_entry(program, entry);
1336        }
1337    }
1338
1339    fn remove_programs_with_no_entries(&mut self) {
1340        match &mut self.index {
1341            IndexImplementation::V1 { entries, .. } => {
1342                let num_programs_before_removal = entries.len();
1343                entries.retain(|_key, second_level| !second_level.is_empty());
1344                if entries.len() < num_programs_before_removal {
1345                    self.stats.empty_entries.fetch_add(
1346                        num_programs_before_removal.saturating_sub(entries.len()) as u64,
1347                        Ordering::Relaxed,
1348                    );
1349                }
1350            }
1351        }
1352    }
1353}
1354
1355#[cfg(feature = "frozen-abi")]
1356impl solana_frozen_abi::abi_example::AbiExample for ProgramCacheEntry {
1357    fn example() -> Self {
1358        // ProgramCacheEntry isn't serializable by definition.
1359        Self::default()
1360    }
1361}
1362
1363#[cfg(feature = "frozen-abi")]
1364impl<FG: ForkGraph> solana_frozen_abi::abi_example::AbiExample for ProgramCache<FG> {
1365    fn example() -> Self {
1366        // ProgramCache isn't serializable by definition.
1367        Self::new(Slot::default(), Epoch::default())
1368    }
1369}
1370
1371#[cfg(test)]
1372mod tests {
1373    use {
1374        crate::loaded_programs::{
1375            BlockRelation, ForkGraph, ProgramCache, ProgramCacheEntry, ProgramCacheEntryOwner,
1376            ProgramCacheEntryType, ProgramCacheForTxBatch, ProgramCacheMatchCriteria,
1377            ProgramRuntimeEnvironment, ProgramRuntimeEnvironments, DELAY_VISIBILITY_SLOT_OFFSET,
1378        },
1379        assert_matches::assert_matches,
1380        percentage::Percentage,
1381        solana_clock::Slot,
1382        solana_pubkey::Pubkey,
1383        solana_sbpf::{elf::Executable, program::BuiltinProgram},
1384        std::{
1385            fs::File,
1386            io::Read,
1387            ops::ControlFlow,
1388            sync::{
1389                atomic::{AtomicU64, Ordering},
1390                Arc, RwLock,
1391            },
1392        },
1393        test_case::{test_case, test_matrix},
1394    };
1395
1396    static MOCK_ENVIRONMENT: std::sync::OnceLock<ProgramRuntimeEnvironment> =
1397        std::sync::OnceLock::<ProgramRuntimeEnvironment>::new();
1398
1399    fn get_mock_env() -> ProgramRuntimeEnvironment {
1400        MOCK_ENVIRONMENT
1401            .get_or_init(|| Arc::new(BuiltinProgram::new_mock()))
1402            .clone()
1403    }
1404
1405    fn new_mock_cache<FG: ForkGraph>() -> ProgramCache<FG> {
1406        let mut cache = ProgramCache::new(0, 0);
1407        cache.environments.program_runtime_v1 = get_mock_env();
1408        cache
1409    }
1410
1411    fn new_test_entry(deployment_slot: Slot, effective_slot: Slot) -> Arc<ProgramCacheEntry> {
1412        new_test_entry_with_usage(deployment_slot, effective_slot, AtomicU64::default())
1413    }
1414
1415    fn new_loaded_entry(env: ProgramRuntimeEnvironment) -> ProgramCacheEntryType {
1416        let mut elf = Vec::new();
1417        File::open("../programs/bpf_loader/test_elfs/out/noop_aligned.so")
1418            .unwrap()
1419            .read_to_end(&mut elf)
1420            .unwrap();
1421        let executable = Executable::load(&elf, env).unwrap();
1422        ProgramCacheEntryType::Loaded(executable)
1423    }
1424
1425    fn new_test_entry_with_usage(
1426        deployment_slot: Slot,
1427        effective_slot: Slot,
1428        usage_counter: AtomicU64,
1429    ) -> Arc<ProgramCacheEntry> {
1430        Arc::new(ProgramCacheEntry {
1431            program: new_loaded_entry(get_mock_env()),
1432            account_owner: ProgramCacheEntryOwner::LoaderV2,
1433            account_size: 0,
1434            deployment_slot,
1435            effective_slot,
1436            tx_usage_counter: usage_counter,
1437            ix_usage_counter: AtomicU64::default(),
1438            latest_access_slot: AtomicU64::new(deployment_slot),
1439        })
1440    }
1441
1442    fn new_test_builtin_entry(
1443        deployment_slot: Slot,
1444        effective_slot: Slot,
1445    ) -> Arc<ProgramCacheEntry> {
1446        Arc::new(ProgramCacheEntry {
1447            program: ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1448            account_owner: ProgramCacheEntryOwner::NativeLoader,
1449            account_size: 0,
1450            deployment_slot,
1451            effective_slot,
1452            tx_usage_counter: AtomicU64::default(),
1453            ix_usage_counter: AtomicU64::default(),
1454            latest_access_slot: AtomicU64::default(),
1455        })
1456    }
1457
1458    fn set_tombstone<FG: ForkGraph>(
1459        cache: &mut ProgramCache<FG>,
1460        key: Pubkey,
1461        slot: Slot,
1462        reason: ProgramCacheEntryType,
1463    ) -> Arc<ProgramCacheEntry> {
1464        let program = Arc::new(ProgramCacheEntry::new_tombstone(
1465            slot,
1466            ProgramCacheEntryOwner::LoaderV2,
1467            reason,
1468        ));
1469        cache.assign_program(key, program.clone());
1470        program
1471    }
1472
1473    fn insert_unloaded_entry<FG: ForkGraph>(
1474        cache: &mut ProgramCache<FG>,
1475        key: Pubkey,
1476        slot: Slot,
1477    ) -> Arc<ProgramCacheEntry> {
1478        let loaded = new_test_entry_with_usage(slot, slot.saturating_add(1), AtomicU64::default());
1479        let unloaded = Arc::new(loaded.to_unloaded().expect("Failed to unload the program"));
1480        cache.assign_program(key, unloaded.clone());
1481        unloaded
1482    }
1483
1484    fn num_matching_entries<P, FG>(cache: &ProgramCache<FG>, predicate: P) -> usize
1485    where
1486        P: Fn(&ProgramCacheEntryType) -> bool,
1487        FG: ForkGraph,
1488    {
1489        cache
1490            .get_flattened_entries_for_tests()
1491            .iter()
1492            .filter(|(_key, program)| predicate(&program.program))
1493            .count()
1494    }
1495
1496    #[test]
1497    fn test_usage_counter_decay() {
1498        let _cache = new_mock_cache::<TestForkGraph>();
1499        let program = new_test_entry_with_usage(10, 11, AtomicU64::new(32));
1500        program.update_access_slot(15);
1501        assert_eq!(program.decayed_usage_counter(15), 32);
1502        assert_eq!(program.decayed_usage_counter(16), 16);
1503        assert_eq!(program.decayed_usage_counter(17), 8);
1504        assert_eq!(program.decayed_usage_counter(18), 4);
1505        assert_eq!(program.decayed_usage_counter(19), 2);
1506        assert_eq!(program.decayed_usage_counter(20), 1);
1507        assert_eq!(program.decayed_usage_counter(21), 0);
1508        assert_eq!(program.decayed_usage_counter(15), 32);
1509        assert_eq!(program.decayed_usage_counter(14), 32);
1510
1511        program.update_access_slot(18);
1512        assert_eq!(program.decayed_usage_counter(15), 32);
1513        assert_eq!(program.decayed_usage_counter(16), 32);
1514        assert_eq!(program.decayed_usage_counter(17), 32);
1515        assert_eq!(program.decayed_usage_counter(18), 32);
1516        assert_eq!(program.decayed_usage_counter(19), 16);
1517        assert_eq!(program.decayed_usage_counter(20), 8);
1518        assert_eq!(program.decayed_usage_counter(21), 4);
1519
1520        // Decay for 63 or more slots
1521        assert_eq!(program.decayed_usage_counter(18 + 63), 0);
1522        assert_eq!(program.decayed_usage_counter(100), 0);
1523    }
1524
1525    fn program_deploy_test_helper(
1526        cache: &mut ProgramCache<TestForkGraph>,
1527        program: Pubkey,
1528        deployment_slots: Vec<Slot>,
1529        usage_counters: Vec<u64>,
1530        programs: &mut Vec<(Pubkey, Slot, u64)>,
1531    ) {
1532        // Add multiple entries for program
1533        deployment_slots
1534            .iter()
1535            .enumerate()
1536            .for_each(|(i, deployment_slot)| {
1537                let usage_counter = *usage_counters.get(i).unwrap_or(&0);
1538                cache.assign_program(
1539                    program,
1540                    new_test_entry_with_usage(
1541                        *deployment_slot,
1542                        (*deployment_slot).saturating_add(2),
1543                        AtomicU64::new(usage_counter),
1544                    ),
1545                );
1546                programs.push((program, *deployment_slot, usage_counter));
1547            });
1548
1549        // Add tombstones entries for program
1550        let env = Arc::new(BuiltinProgram::new_mock());
1551        for slot in 21..31 {
1552            set_tombstone(
1553                cache,
1554                program,
1555                slot,
1556                ProgramCacheEntryType::FailedVerification(env.clone()),
1557            );
1558        }
1559
1560        // Add unloaded entries for program
1561        for slot in 31..41 {
1562            insert_unloaded_entry(cache, program, slot);
1563        }
1564    }
1565
1566    #[test]
1567    fn test_random_eviction() {
1568        let mut programs = vec![];
1569
1570        let mut cache = new_mock_cache::<TestForkGraph>();
1571
1572        // This test adds different kind of entries to the cache.
1573        // Tombstones and unloaded entries are expected to not be evicted.
1574        // It also adds multiple entries for three programs as it tries to create a typical cache instance.
1575
1576        // Program 1
1577        program_deploy_test_helper(
1578            &mut cache,
1579            Pubkey::new_unique(),
1580            vec![0, 10, 20],
1581            vec![4, 5, 25],
1582            &mut programs,
1583        );
1584
1585        // Program 2
1586        program_deploy_test_helper(
1587            &mut cache,
1588            Pubkey::new_unique(),
1589            vec![5, 11],
1590            vec![0, 2],
1591            &mut programs,
1592        );
1593
1594        // Program 3
1595        program_deploy_test_helper(
1596            &mut cache,
1597            Pubkey::new_unique(),
1598            vec![0, 5, 15],
1599            vec![100, 3, 20],
1600            &mut programs,
1601        );
1602
1603        // 1 for each deployment slot
1604        let num_loaded_expected = 8;
1605        // 10 for each program
1606        let num_unloaded_expected = 30;
1607        // 10 for each program
1608        let num_tombstones_expected = 30;
1609
1610        // Count the number of loaded, unloaded and tombstone entries.
1611        programs.sort_by_key(|(_id, _slot, usage_count)| *usage_count);
1612        let num_loaded = num_matching_entries(&cache, |program_type| {
1613            matches!(program_type, ProgramCacheEntryType::Loaded(_))
1614        });
1615        let num_unloaded = num_matching_entries(&cache, |program_type| {
1616            matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1617        });
1618        let num_tombstones = num_matching_entries(&cache, |program_type| {
1619            matches!(
1620                program_type,
1621                ProgramCacheEntryType::DelayVisibility
1622                    | ProgramCacheEntryType::FailedVerification(_)
1623                    | ProgramCacheEntryType::Closed
1624            )
1625        });
1626
1627        // Test that the cache is constructed with the expected number of entries.
1628        assert_eq!(num_loaded, num_loaded_expected);
1629        assert_eq!(num_unloaded, num_unloaded_expected);
1630        assert_eq!(num_tombstones, num_tombstones_expected);
1631
1632        // Evict entries from the cache
1633        let eviction_pct = 1;
1634
1635        let num_loaded_expected =
1636            Percentage::from(eviction_pct).apply_to(crate::loaded_programs::MAX_LOADED_ENTRY_COUNT);
1637        let num_unloaded_expected = num_unloaded_expected + num_loaded - num_loaded_expected;
1638        cache.evict_using_2s_random_selection(Percentage::from(eviction_pct), 21);
1639
1640        // Count the number of loaded, unloaded and tombstone entries.
1641        let num_loaded = num_matching_entries(&cache, |program_type| {
1642            matches!(program_type, ProgramCacheEntryType::Loaded(_))
1643        });
1644        let num_unloaded = num_matching_entries(&cache, |program_type| {
1645            matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1646        });
1647        let num_tombstones = num_matching_entries(&cache, |program_type| {
1648            matches!(program_type, ProgramCacheEntryType::FailedVerification(_))
1649        });
1650
1651        // However many entries are left after the shrink
1652        assert_eq!(num_loaded, num_loaded_expected);
1653        // The original unloaded entries + the evicted loaded entries
1654        assert_eq!(num_unloaded, num_unloaded_expected);
1655        // The original tombstones are not evicted
1656        assert_eq!(num_tombstones, num_tombstones_expected);
1657    }
1658
1659    #[test]
1660    fn test_eviction() {
1661        let mut programs = vec![];
1662        let mut cache = new_mock_cache::<TestForkGraph>();
1663
1664        // Program 1
1665        program_deploy_test_helper(
1666            &mut cache,
1667            Pubkey::new_unique(),
1668            vec![0, 10, 20],
1669            vec![4, 5, 25],
1670            &mut programs,
1671        );
1672
1673        // Program 2
1674        program_deploy_test_helper(
1675            &mut cache,
1676            Pubkey::new_unique(),
1677            vec![5, 11],
1678            vec![0, 2],
1679            &mut programs,
1680        );
1681
1682        // Program 3
1683        program_deploy_test_helper(
1684            &mut cache,
1685            Pubkey::new_unique(),
1686            vec![0, 5, 15],
1687            vec![100, 3, 20],
1688            &mut programs,
1689        );
1690
1691        // 1 for each deployment slot
1692        let num_loaded_expected = 8;
1693        // 10 for each program
1694        let num_unloaded_expected = 30;
1695        // 10 for each program
1696        let num_tombstones_expected = 30;
1697
1698        // Count the number of loaded, unloaded and tombstone entries.
1699        programs.sort_by_key(|(_id, _slot, usage_count)| *usage_count);
1700        let num_loaded = num_matching_entries(&cache, |program_type| {
1701            matches!(program_type, ProgramCacheEntryType::Loaded(_))
1702        });
1703        let num_unloaded = num_matching_entries(&cache, |program_type| {
1704            matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1705        });
1706        let num_tombstones = num_matching_entries(&cache, |program_type| {
1707            matches!(program_type, ProgramCacheEntryType::FailedVerification(_))
1708        });
1709
1710        // Test that the cache is constructed with the expected number of entries.
1711        assert_eq!(num_loaded, num_loaded_expected);
1712        assert_eq!(num_unloaded, num_unloaded_expected);
1713        assert_eq!(num_tombstones, num_tombstones_expected);
1714
1715        // Evict entries from the cache
1716        let eviction_pct = 1;
1717
1718        let num_loaded_expected =
1719            Percentage::from(eviction_pct).apply_to(crate::loaded_programs::MAX_LOADED_ENTRY_COUNT);
1720        let num_unloaded_expected = num_unloaded_expected + num_loaded - num_loaded_expected;
1721
1722        cache.sort_and_unload(Percentage::from(eviction_pct));
1723
1724        // Check that every program is still in the cache.
1725        let entries = cache.get_flattened_entries_for_tests();
1726        programs.iter().for_each(|entry| {
1727            assert!(entries.iter().any(|(key, _entry)| key == &entry.0));
1728        });
1729
1730        let unloaded = entries
1731            .iter()
1732            .filter_map(|(key, program)| {
1733                matches!(program.program, ProgramCacheEntryType::Unloaded(_))
1734                    .then_some((*key, program.tx_usage_counter.load(Ordering::Relaxed)))
1735            })
1736            .collect::<Vec<(Pubkey, u64)>>();
1737
1738        for index in 0..3 {
1739            let expected = programs.get(index).expect("Missing program");
1740            assert!(unloaded.contains(&(expected.0, expected.2)));
1741        }
1742
1743        // Count the number of loaded, unloaded and tombstone entries.
1744        let num_loaded = num_matching_entries(&cache, |program_type| {
1745            matches!(program_type, ProgramCacheEntryType::Loaded(_))
1746        });
1747        let num_unloaded = num_matching_entries(&cache, |program_type| {
1748            matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1749        });
1750        let num_tombstones = num_matching_entries(&cache, |program_type| {
1751            matches!(
1752                program_type,
1753                ProgramCacheEntryType::DelayVisibility
1754                    | ProgramCacheEntryType::FailedVerification(_)
1755                    | ProgramCacheEntryType::Closed
1756            )
1757        });
1758
1759        // However many entries are left after the shrink
1760        assert_eq!(num_loaded, num_loaded_expected);
1761        // The original unloaded entries + the evicted loaded entries
1762        assert_eq!(num_unloaded, num_unloaded_expected);
1763        // The original tombstones are not evicted
1764        assert_eq!(num_tombstones, num_tombstones_expected);
1765    }
1766
1767    #[test]
1768    fn test_usage_count_of_unloaded_program() {
1769        let mut cache = new_mock_cache::<TestForkGraph>();
1770
1771        let program = Pubkey::new_unique();
1772        let evict_to_pct = 2;
1773        let cache_capacity_after_shrink =
1774            Percentage::from(evict_to_pct).apply_to(crate::loaded_programs::MAX_LOADED_ENTRY_COUNT);
1775        // Add enough programs to the cache to trigger 1 eviction after shrinking.
1776        let num_total_programs = (cache_capacity_after_shrink + 1) as u64;
1777        (0..num_total_programs).for_each(|i| {
1778            cache.assign_program(
1779                program,
1780                new_test_entry_with_usage(i, i + 2, AtomicU64::new(i + 10)),
1781            );
1782        });
1783
1784        cache.sort_and_unload(Percentage::from(evict_to_pct));
1785
1786        let num_unloaded = num_matching_entries(&cache, |program_type| {
1787            matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1788        });
1789        assert_eq!(num_unloaded, 1);
1790
1791        cache
1792            .get_flattened_entries_for_tests()
1793            .iter()
1794            .for_each(|(_key, program)| {
1795                if matches!(program.program, ProgramCacheEntryType::Unloaded(_)) {
1796                    // Test that the usage counter is retained for the unloaded program
1797                    assert_eq!(program.tx_usage_counter.load(Ordering::Relaxed), 10);
1798                    assert_eq!(program.deployment_slot, 0);
1799                    assert_eq!(program.effective_slot, 2);
1800                }
1801            });
1802
1803        // Replenish the program that was just unloaded. Use 0 as the usage counter. This should be
1804        // updated with the usage counter from the unloaded program.
1805        cache.assign_program(program, new_test_entry_with_usage(0, 2, AtomicU64::new(0)));
1806
1807        cache
1808            .get_flattened_entries_for_tests()
1809            .iter()
1810            .for_each(|(_key, program)| {
1811                if matches!(program.program, ProgramCacheEntryType::Unloaded(_))
1812                    && program.deployment_slot == 0
1813                    && program.effective_slot == 2
1814                {
1815                    // Test that the usage counter was correctly updated.
1816                    assert_eq!(program.tx_usage_counter.load(Ordering::Relaxed), 10);
1817                }
1818            });
1819    }
1820
1821    #[test]
1822    fn test_fuzz_assign_program_order() {
1823        use rand::prelude::SliceRandom;
1824        const EXPECTED_ENTRIES: [(u64, u64); 7] =
1825            [(1, 2), (5, 5), (5, 6), (5, 10), (9, 10), (10, 10), (3, 12)];
1826        let mut rng = rand::thread_rng();
1827        let program_id = Pubkey::new_unique();
1828        for _ in 0..1000 {
1829            let mut entries = EXPECTED_ENTRIES.to_vec();
1830            entries.shuffle(&mut rng);
1831            let mut cache = new_mock_cache::<TestForkGraph>();
1832            for (deployment_slot, effective_slot) in entries {
1833                assert!(!cache
1834                    .assign_program(program_id, new_test_entry(deployment_slot, effective_slot)));
1835            }
1836            for ((deployment_slot, effective_slot), entry) in EXPECTED_ENTRIES
1837                .iter()
1838                .zip(cache.get_slot_versions_for_tests(&program_id).iter())
1839            {
1840                assert_eq!(entry.deployment_slot, *deployment_slot);
1841                assert_eq!(entry.effective_slot, *effective_slot);
1842            }
1843        }
1844    }
1845
1846    #[test_matrix(
1847        (
1848            ProgramCacheEntryType::Closed,
1849            ProgramCacheEntryType::FailedVerification(get_mock_env()),
1850            new_loaded_entry(get_mock_env()),
1851        ),
1852        (
1853            ProgramCacheEntryType::FailedVerification(get_mock_env()),
1854            ProgramCacheEntryType::Closed,
1855            ProgramCacheEntryType::Unloaded(get_mock_env()),
1856            new_loaded_entry(get_mock_env()),
1857            ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1858        )
1859    )]
1860    #[test_matrix(
1861        (
1862            ProgramCacheEntryType::Unloaded(get_mock_env()),
1863        ),
1864        (
1865            ProgramCacheEntryType::FailedVerification(get_mock_env()),
1866            ProgramCacheEntryType::Closed,
1867            ProgramCacheEntryType::Unloaded(get_mock_env()),
1868            ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1869        )
1870    )]
1871    #[test_matrix(
1872        (ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),),
1873        (
1874            ProgramCacheEntryType::FailedVerification(get_mock_env()),
1875            ProgramCacheEntryType::Closed,
1876            ProgramCacheEntryType::Unloaded(get_mock_env()),
1877            new_loaded_entry(get_mock_env()),
1878        )
1879    )]
1880    #[should_panic(expected = "Unexpected replacement of an entry")]
1881    fn test_assign_program_failure(old: ProgramCacheEntryType, new: ProgramCacheEntryType) {
1882        let mut cache = new_mock_cache::<TestForkGraph>();
1883        let program_id = Pubkey::new_unique();
1884        assert!(!cache.assign_program(
1885            program_id,
1886            Arc::new(ProgramCacheEntry {
1887                program: old,
1888                account_owner: ProgramCacheEntryOwner::LoaderV2,
1889                account_size: 0,
1890                deployment_slot: 10,
1891                effective_slot: 11,
1892                tx_usage_counter: AtomicU64::default(),
1893                ix_usage_counter: AtomicU64::default(),
1894                latest_access_slot: AtomicU64::default(),
1895            }),
1896        ));
1897        cache.assign_program(
1898            program_id,
1899            Arc::new(ProgramCacheEntry {
1900                program: new,
1901                account_owner: ProgramCacheEntryOwner::LoaderV2,
1902                account_size: 0,
1903                deployment_slot: 10,
1904                effective_slot: 11,
1905                tx_usage_counter: AtomicU64::default(),
1906                ix_usage_counter: AtomicU64::default(),
1907                latest_access_slot: AtomicU64::default(),
1908            }),
1909        );
1910    }
1911
1912    #[test_case(
1913        ProgramCacheEntryType::Unloaded(Arc::new(BuiltinProgram::new_mock())),
1914        new_loaded_entry(get_mock_env())
1915    )]
1916    #[test_case(
1917        ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1918        ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock())
1919    )]
1920    fn test_assign_program_success(old: ProgramCacheEntryType, new: ProgramCacheEntryType) {
1921        let mut cache = new_mock_cache::<TestForkGraph>();
1922        let program_id = Pubkey::new_unique();
1923        assert!(!cache.assign_program(
1924            program_id,
1925            Arc::new(ProgramCacheEntry {
1926                program: old,
1927                account_owner: ProgramCacheEntryOwner::LoaderV2,
1928                account_size: 0,
1929                deployment_slot: 10,
1930                effective_slot: 11,
1931                tx_usage_counter: AtomicU64::default(),
1932                ix_usage_counter: AtomicU64::default(),
1933                latest_access_slot: AtomicU64::default(),
1934            }),
1935        ));
1936        assert!(!cache.assign_program(
1937            program_id,
1938            Arc::new(ProgramCacheEntry {
1939                program: new,
1940                account_owner: ProgramCacheEntryOwner::LoaderV2,
1941                account_size: 0,
1942                deployment_slot: 10,
1943                effective_slot: 11,
1944                tx_usage_counter: AtomicU64::default(),
1945                ix_usage_counter: AtomicU64::default(),
1946                latest_access_slot: AtomicU64::default(),
1947            }),
1948        ));
1949    }
1950
1951    #[test]
1952    fn test_tombstone() {
1953        let env = Arc::new(BuiltinProgram::new_mock());
1954        let tombstone = ProgramCacheEntry::new_tombstone(
1955            0,
1956            ProgramCacheEntryOwner::LoaderV2,
1957            ProgramCacheEntryType::FailedVerification(env.clone()),
1958        );
1959        assert_matches!(
1960            tombstone.program,
1961            ProgramCacheEntryType::FailedVerification(_)
1962        );
1963        assert!(tombstone.is_tombstone());
1964        assert_eq!(tombstone.deployment_slot, 0);
1965        assert_eq!(tombstone.effective_slot, 0);
1966
1967        let tombstone = ProgramCacheEntry::new_tombstone(
1968            100,
1969            ProgramCacheEntryOwner::LoaderV2,
1970            ProgramCacheEntryType::Closed,
1971        );
1972        assert_matches!(tombstone.program, ProgramCacheEntryType::Closed);
1973        assert!(tombstone.is_tombstone());
1974        assert_eq!(tombstone.deployment_slot, 100);
1975        assert_eq!(tombstone.effective_slot, 100);
1976
1977        let mut cache = new_mock_cache::<TestForkGraph>();
1978        let program1 = Pubkey::new_unique();
1979        let tombstone = set_tombstone(
1980            &mut cache,
1981            program1,
1982            10,
1983            ProgramCacheEntryType::FailedVerification(env.clone()),
1984        );
1985        let slot_versions = cache.get_slot_versions_for_tests(&program1);
1986        assert_eq!(slot_versions.len(), 1);
1987        assert!(slot_versions.first().unwrap().is_tombstone());
1988        assert_eq!(tombstone.deployment_slot, 10);
1989        assert_eq!(tombstone.effective_slot, 10);
1990
1991        // Add a program at slot 50, and a tombstone for the program at slot 60
1992        let program2 = Pubkey::new_unique();
1993        cache.assign_program(program2, new_test_builtin_entry(50, 51));
1994        let slot_versions = cache.get_slot_versions_for_tests(&program2);
1995        assert_eq!(slot_versions.len(), 1);
1996        assert!(!slot_versions.first().unwrap().is_tombstone());
1997
1998        let tombstone = set_tombstone(
1999            &mut cache,
2000            program2,
2001            60,
2002            ProgramCacheEntryType::FailedVerification(env),
2003        );
2004        let slot_versions = cache.get_slot_versions_for_tests(&program2);
2005        assert_eq!(slot_versions.len(), 2);
2006        assert!(!slot_versions.first().unwrap().is_tombstone());
2007        assert!(slot_versions.get(1).unwrap().is_tombstone());
2008        assert!(tombstone.is_tombstone());
2009        assert_eq!(tombstone.deployment_slot, 60);
2010        assert_eq!(tombstone.effective_slot, 60);
2011    }
2012
2013    struct TestForkGraph {
2014        relation: BlockRelation,
2015    }
2016    impl ForkGraph for TestForkGraph {
2017        fn relationship(&self, _a: Slot, _b: Slot) -> BlockRelation {
2018            self.relation
2019        }
2020    }
2021
2022    #[test]
2023    fn test_prune_empty() {
2024        let mut cache = new_mock_cache::<TestForkGraph>();
2025        let fork_graph = Arc::new(RwLock::new(TestForkGraph {
2026            relation: BlockRelation::Unrelated,
2027        }));
2028
2029        cache.set_fork_graph(Arc::downgrade(&fork_graph));
2030
2031        cache.prune(0, 0);
2032        assert!(cache.get_flattened_entries_for_tests().is_empty());
2033
2034        cache.prune(10, 0);
2035        assert!(cache.get_flattened_entries_for_tests().is_empty());
2036
2037        let mut cache = new_mock_cache::<TestForkGraph>();
2038        let fork_graph = Arc::new(RwLock::new(TestForkGraph {
2039            relation: BlockRelation::Ancestor,
2040        }));
2041
2042        cache.set_fork_graph(Arc::downgrade(&fork_graph));
2043
2044        cache.prune(0, 0);
2045        assert!(cache.get_flattened_entries_for_tests().is_empty());
2046
2047        cache.prune(10, 0);
2048        assert!(cache.get_flattened_entries_for_tests().is_empty());
2049
2050        let mut cache = new_mock_cache::<TestForkGraph>();
2051        let fork_graph = Arc::new(RwLock::new(TestForkGraph {
2052            relation: BlockRelation::Descendant,
2053        }));
2054
2055        cache.set_fork_graph(Arc::downgrade(&fork_graph));
2056
2057        cache.prune(0, 0);
2058        assert!(cache.get_flattened_entries_for_tests().is_empty());
2059
2060        cache.prune(10, 0);
2061        assert!(cache.get_flattened_entries_for_tests().is_empty());
2062
2063        let mut cache = new_mock_cache::<TestForkGraph>();
2064        let fork_graph = Arc::new(RwLock::new(TestForkGraph {
2065            relation: BlockRelation::Unknown,
2066        }));
2067        cache.set_fork_graph(Arc::downgrade(&fork_graph));
2068
2069        cache.prune(0, 0);
2070        assert!(cache.get_flattened_entries_for_tests().is_empty());
2071
2072        cache.prune(10, 0);
2073        assert!(cache.get_flattened_entries_for_tests().is_empty());
2074    }
2075
2076    #[test]
2077    fn test_prune_different_env() {
2078        let mut cache = new_mock_cache::<TestForkGraph>();
2079
2080        let fork_graph = Arc::new(RwLock::new(TestForkGraph {
2081            relation: BlockRelation::Ancestor,
2082        }));
2083
2084        cache.set_fork_graph(Arc::downgrade(&fork_graph));
2085
2086        let program1 = Pubkey::new_unique();
2087        cache.assign_program(program1, new_test_entry(10, 10));
2088
2089        let new_env = Arc::new(BuiltinProgram::new_mock());
2090        cache.upcoming_environments = Some(ProgramRuntimeEnvironments {
2091            program_runtime_v1: new_env.clone(),
2092            program_runtime_v2: new_env.clone(),
2093        });
2094        let updated_program = Arc::new(ProgramCacheEntry {
2095            program: new_loaded_entry(new_env.clone()),
2096            account_owner: ProgramCacheEntryOwner::LoaderV2,
2097            account_size: 0,
2098            deployment_slot: 20,
2099            effective_slot: 20,
2100            tx_usage_counter: AtomicU64::default(),
2101            ix_usage_counter: AtomicU64::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, u64))> {
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, 1))
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, u64))],
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);
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);
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);
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);
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);
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);
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);
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);
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);
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 .0 = ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(5);
2462        missing.get_mut(1).unwrap().1 .0 = 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);
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);
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);
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);
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, 1))];
2555        let mut extracted = ProgramCacheForTxBatch::new(0, cache.environments.clone(), None, 0);
2556        cache.extract(&mut missing, &mut extracted, 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: AtomicU64::default(),
2578                ix_usage_counter: AtomicU64::default(),
2579                latest_access_slot: AtomicU64::default(),
2580            });
2581            assert!(entry.to_unloaded().is_none());
2582
2583            // Check that unload_program_entry() does nothing for this entry
2584            let program_id = Pubkey::new_unique();
2585            cache.assign_program(program_id, entry.clone());
2586            cache.unload_program_entry(&program_id, &entry);
2587            assert_eq!(cache.get_slot_versions_for_tests(&program_id).len(), 1);
2588            assert!(cache.stats.evictions.is_empty());
2589        }
2590
2591        let entry = new_test_entry_with_usage(1, 2, AtomicU64::new(3));
2592        let unloaded_entry = entry.to_unloaded().unwrap();
2593        assert_eq!(unloaded_entry.deployment_slot, 1);
2594        assert_eq!(unloaded_entry.effective_slot, 2);
2595        assert_eq!(unloaded_entry.latest_access_slot.load(Ordering::Relaxed), 1);
2596        assert_eq!(unloaded_entry.tx_usage_counter.load(Ordering::Relaxed), 3);
2597
2598        // Check that unload_program_entry() does its work
2599        let program_id = Pubkey::new_unique();
2600        cache.assign_program(program_id, entry.clone());
2601        cache.unload_program_entry(&program_id, &entry);
2602        assert!(cache.stats.evictions.contains_key(&program_id));
2603    }
2604
2605    #[test]
2606    fn test_fork_prune_find_first_ancestor() {
2607        let mut cache = new_mock_cache::<TestForkGraphSpecific>();
2608
2609        // Fork graph created for the test
2610        //                   0
2611        //                 /   \
2612        //                10    5
2613        //                |
2614        //                20
2615
2616        // Deploy program on slot 0, and slot 5.
2617        // Prune the fork that has slot 5. The cache should still have the program
2618        // deployed at slot 0.
2619        let mut fork_graph = TestForkGraphSpecific::default();
2620        fork_graph.insert_fork(&[0, 10, 20]);
2621        fork_graph.insert_fork(&[0, 5]);
2622        let fork_graph = Arc::new(RwLock::new(fork_graph));
2623        cache.set_fork_graph(Arc::downgrade(&fork_graph));
2624
2625        let program1 = Pubkey::new_unique();
2626        cache.assign_program(program1, new_test_entry(0, 1));
2627        cache.assign_program(program1, new_test_entry(5, 6));
2628
2629        cache.prune(10, 0);
2630
2631        let mut missing = get_entries_to_load(&cache, 20, &[program1]);
2632        let mut extracted = ProgramCacheForTxBatch::new(20, cache.environments.clone(), None, 0);
2633        cache.extract(&mut missing, &mut extracted, true);
2634
2635        // The cache should have the program deployed at slot 0
2636        assert_eq!(
2637            extracted
2638                .find(&program1)
2639                .expect("Did not find the program")
2640                .deployment_slot,
2641            0
2642        );
2643    }
2644
2645    #[test]
2646    fn test_prune_by_deployment_slot() {
2647        let mut cache = new_mock_cache::<TestForkGraphSpecific>();
2648
2649        // Fork graph created for the test
2650        //                   0
2651        //                 /   \
2652        //                10    5
2653        //                |
2654        //                20
2655
2656        // Deploy program on slot 0, and slot 5.
2657        // Prune the fork that has slot 5. The cache should still have the program
2658        // deployed at slot 0.
2659        let mut fork_graph = TestForkGraphSpecific::default();
2660        fork_graph.insert_fork(&[0, 10, 20]);
2661        fork_graph.insert_fork(&[0, 5, 6]);
2662        let fork_graph = Arc::new(RwLock::new(fork_graph));
2663        cache.set_fork_graph(Arc::downgrade(&fork_graph));
2664
2665        let program1 = Pubkey::new_unique();
2666        cache.assign_program(program1, new_test_entry(0, 1));
2667        cache.assign_program(program1, new_test_entry(5, 6));
2668
2669        let program2 = Pubkey::new_unique();
2670        cache.assign_program(program2, new_test_entry(10, 11));
2671
2672        let mut missing = get_entries_to_load(&cache, 20, &[program1, program2]);
2673        let mut extracted = ProgramCacheForTxBatch::new(20, cache.environments.clone(), None, 0);
2674        cache.extract(&mut missing, &mut extracted, true);
2675        assert!(match_slot(&extracted, &program1, 0, 20));
2676        assert!(match_slot(&extracted, &program2, 10, 20));
2677
2678        let mut missing = get_entries_to_load(&cache, 6, &[program1, program2]);
2679        assert!(match_missing(&missing, &program2, false));
2680        let mut extracted = ProgramCacheForTxBatch::new(6, cache.environments.clone(), None, 0);
2681        cache.extract(&mut missing, &mut extracted, true);
2682        assert!(match_slot(&extracted, &program1, 5, 6));
2683
2684        // Pruning slot 5 will remove program1 entry deployed at slot 5.
2685        // On fork chaining from slot 5, the entry deployed at slot 0 will become visible.
2686        cache.prune_by_deployment_slot(5);
2687
2688        let mut missing = get_entries_to_load(&cache, 20, &[program1, program2]);
2689        let mut extracted = ProgramCacheForTxBatch::new(20, cache.environments.clone(), None, 0);
2690        cache.extract(&mut missing, &mut extracted, true);
2691        assert!(match_slot(&extracted, &program1, 0, 20));
2692        assert!(match_slot(&extracted, &program2, 10, 20));
2693
2694        let mut missing = get_entries_to_load(&cache, 6, &[program1, program2]);
2695        assert!(match_missing(&missing, &program2, false));
2696        let mut extracted = ProgramCacheForTxBatch::new(6, cache.environments.clone(), None, 0);
2697        cache.extract(&mut missing, &mut extracted, true);
2698        assert!(match_slot(&extracted, &program1, 0, 6));
2699
2700        // Pruning slot 10 will remove program2 entry deployed at slot 10.
2701        // As there is no other entry for program2, extract() will return it as missing.
2702        cache.prune_by_deployment_slot(10);
2703
2704        let mut missing = get_entries_to_load(&cache, 20, &[program1, program2]);
2705        assert!(match_missing(&missing, &program2, false));
2706        let mut extracted = ProgramCacheForTxBatch::new(20, cache.environments.clone(), None, 0);
2707        cache.extract(&mut missing, &mut extracted, true);
2708        assert!(match_slot(&extracted, &program1, 0, 20));
2709    }
2710
2711    #[test]
2712    fn test_usable_entries_for_slot() {
2713        new_mock_cache::<TestForkGraph>();
2714        let tombstone = Arc::new(ProgramCacheEntry::new_tombstone(
2715            0,
2716            ProgramCacheEntryOwner::LoaderV2,
2717            ProgramCacheEntryType::Closed,
2718        ));
2719
2720        assert!(ProgramCache::<TestForkGraph>::matches_criteria(
2721            &tombstone,
2722            &ProgramCacheMatchCriteria::NoCriteria
2723        ));
2724
2725        assert!(ProgramCache::<TestForkGraph>::matches_criteria(
2726            &tombstone,
2727            &ProgramCacheMatchCriteria::Tombstone
2728        ));
2729
2730        assert!(ProgramCache::<TestForkGraph>::matches_criteria(
2731            &tombstone,
2732            &ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(0)
2733        ));
2734
2735        assert!(!ProgramCache::<TestForkGraph>::matches_criteria(
2736            &tombstone,
2737            &ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(1)
2738        ));
2739
2740        let program = new_test_entry(0, 1);
2741
2742        assert!(ProgramCache::<TestForkGraph>::matches_criteria(
2743            &program,
2744            &ProgramCacheMatchCriteria::NoCriteria
2745        ));
2746
2747        assert!(!ProgramCache::<TestForkGraph>::matches_criteria(
2748            &program,
2749            &ProgramCacheMatchCriteria::Tombstone
2750        ));
2751
2752        assert!(ProgramCache::<TestForkGraph>::matches_criteria(
2753            &program,
2754            &ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(0)
2755        ));
2756
2757        assert!(!ProgramCache::<TestForkGraph>::matches_criteria(
2758            &program,
2759            &ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(1)
2760        ));
2761
2762        let program = Arc::new(new_test_entry_with_usage(0, 1, AtomicU64::default()));
2763
2764        assert!(ProgramCache::<TestForkGraph>::matches_criteria(
2765            &program,
2766            &ProgramCacheMatchCriteria::NoCriteria
2767        ));
2768
2769        assert!(!ProgramCache::<TestForkGraph>::matches_criteria(
2770            &program,
2771            &ProgramCacheMatchCriteria::Tombstone
2772        ));
2773
2774        assert!(ProgramCache::<TestForkGraph>::matches_criteria(
2775            &program,
2776            &ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(0)
2777        ));
2778
2779        assert!(!ProgramCache::<TestForkGraph>::matches_criteria(
2780            &program,
2781            &ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(1)
2782        ));
2783    }
2784}