Skip to main content

rialo_s_program_runtime/
loaded_programs.rs

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