1use 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#[derive(Copy, Clone, Debug, PartialEq)]
40pub enum BlockRelation {
41 Ancestor,
43 Equal,
45 Descendant,
47 Unrelated,
49 Unknown,
51}
52
53pub trait ForkGraph: Copy + Clone + Send + Sync {
55 fn relationship(&self, a: Slot, b: Slot) -> BlockRelation;
57}
58
59#[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
105pub enum ExecutableKind {
135 Ebpf(Executable<InvokeContext<'static, 'static>>),
137
138 RiscV(Vec<u8>),
140}
141
142#[derive(Default)]
144pub enum ProgramCacheEntryType {
145 FailedVerification(ProgramRuntimeEnvironment),
147 #[default]
151 Closed,
152 DelayVisibility,
154 Unloaded(ProgramRuntimeEnvironment),
158 Loaded(ExecutableKind),
160 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 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#[derive(Debug, Default)]
199pub struct ProgramCacheEntry {
200 pub program: ProgramCacheEntryType,
202 pub account_owner: ProgramCacheEntryOwner,
204 pub account_size: usize,
206 pub deployment_slot: Slot,
208 pub effective_slot: Slot,
210 pub tx_usage_counter: AtomicU64,
212 pub ix_usage_counter: AtomicU64,
214 pub latest_access_slot: AtomicU64,
216}
217
218#[derive(Debug, Default)]
220pub struct ProgramCacheStats {
221 pub hits: AtomicU64,
223 pub misses: AtomicU64,
225 pub evictions: HashMap<Pubkey, u64>,
227 pub reloads: AtomicU64,
229 pub insertions: AtomicU64,
231 pub lost_insertions: AtomicU64,
233 pub replacements: AtomicU64,
235 pub one_hit_wonders: AtomicU64,
237 pub prunes_orphan: AtomicU64,
239 pub prunes_environment: AtomicU64,
241 pub empty_entries: AtomicU64,
243 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#[derive(Debug, Default)]
292pub struct LoadProgramMetrics {
293 pub program_id: String,
295 pub register_syscalls_us: u64,
297 pub load_elf_us: u64,
299 pub verify_code_us: u64,
301 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 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, )
353 }
354
355 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, )
371 }
372
373 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, )
401 }
402
403 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, )
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 #[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 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 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 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#[derive(Clone, Debug)]
637pub struct ProgramRuntimeEnvironments {
638 pub program_runtime_v1: ProgramRuntimeEnvironment,
640 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#[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 V1 {
706 entries: HashMap<Pubkey, Vec<Arc<ProgramCacheEntry>>>,
711 loading_entries: Mutex<HashMap<Pubkey, (Slot, thread::ThreadId)>>,
720 },
721}
722
723pub struct ProgramCache {
739 index: IndexImplementation,
741 pub latest_root_slot: Slot,
743 pub latest_root_epoch: Epoch,
745 pub environments: ProgramRuntimeEnvironments,
747 pub upcoming_environments: Option<ProgramRuntimeEnvironments>,
753 pub programs_to_recompile: Vec<(Pubkey, Arc<ProgramCacheEntry>)>,
755 pub stats: ProgramCacheStats,
757 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
772pub struct ProgramCacheForTx<'a> {
778 entries: &'a HashMap<Pubkey, Arc<ProgramCacheEntry>>,
781 modified_entries: HashMap<Pubkey, Arc<ProgramCacheEntry>>,
783 slot: Slot,
784 pub environments: &'a ProgramRuntimeEnvironments,
785 pub upcoming_environments: &'a Option<ProgramRuntimeEnvironments>,
793 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 pub fn store_modified_entry(&mut self, key: Pubkey, entry: Arc<ProgramCacheEntry>) {
827 self.modified_entries.insert(key, entry);
828 }
829
830 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 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 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#[derive(Clone, Debug, Default)]
878pub struct ProgramCacheForTxBatch {
879 entries: HashMap<Pubkey, Arc<ProgramCacheEntry>>,
882 modified_entries: HashMap<Pubkey, Arc<ProgramCacheEntry>>,
884 slot: Slot,
885 pub environments: ProgramRuntimeEnvironments,
886 pub upcoming_environments: Option<ProgramRuntimeEnvironments>,
894 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 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 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 pub fn store_modified_entry(&mut self, key: Pubkey, entry: Arc<ProgramCacheEntry>) {
961 self.modified_entries.insert(key, entry);
962 }
963
964 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 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 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 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 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 pub fn assign_program(&mut self, key: Pubkey, entry: Arc<ProgramCacheEntry>) -> bool {
1061 debug_assert!(!matches!(
1062 &entry.program,
1063 ProgramCacheEntryType::DelayVisibility
1064 ));
1065 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 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 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 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 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 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 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 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 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 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 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 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 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 #[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 #[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 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 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 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 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 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 Self::default()
1537 }
1538}
1539
1540#[cfg(feature = "frozen-abi")]
1541impl rialo_frozen_abi::abi_example::AbiExample for ProgramCache {
1542 fn example() -> Self {
1543 Self::new(Slot::default(), Epoch::default())
1545 }
1546}
1547
1548#[cfg(false)] #[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 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 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 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 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 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_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_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 let num_loaded_expected = 8;
1781 let num_unloaded_expected = 30;
1783 let num_tombstones_expected = 30;
1785
1786 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 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 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 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 assert_eq!(num_loaded, num_loaded_expected);
1829 assert_eq!(num_unloaded, num_unloaded_expected);
1831 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_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_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_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 let num_loaded_expected = 8;
1869 let num_unloaded_expected = 30;
1871 let num_tombstones_expected = 30;
1873
1874 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 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 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 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 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 assert_eq!(num_loaded, num_loaded_expected);
1937 assert_eq!(num_unloaded, num_unloaded_expected);
1939 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 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 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 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 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 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 assert_eq!(cache.get_slot_versions_for_tests(&program1).len(), 2);
2250
2251 cache.prune(21, cache.latest_root_epoch);
2252
2253 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 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 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 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 cache.assign_program(
2351 program4,
2352 new_test_entry(15, 15 + DELAY_VISIBILITY_SLOT_OFFSET),
2353 );
2354
2355 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 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 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 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 assert!(match_slot(&extracted, &program4, 15, 18));
2406
2407 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 assert!(match_slot(&extracted, &program4, 15, 23));
2417
2418 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 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 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 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 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 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 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 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 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 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 let _ = insert_unloaded_entry(&mut cache, program3, 25);
2577
2578 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 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 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 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 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 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 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 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 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 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 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}