atlas_program_runtime/
loaded_programs.rs

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