Skip to main content

rialo_s_program_runtime/
loaded_programs.rs

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