rialo_s_program_runtime/
loaded_programs.rs

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