atlas_program_runtime/
loaded_programs.rs

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