miraland_program_runtime/
loaded_programs.rs

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