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