1use std::{
2 collections::{hash_map::Entry, HashMap},
3 fmt::{Debug, Formatter},
4};
5
6use log::{debug, error, log_enabled, trace};
7use percentage::PercentageInteger;
8use rialo_s_clock::{Epoch, Slot};
9use rialo_s_pubkey::Pubkey;
10use rialo_s_sdk_ids::{
11 bpf_loader, bpf_loader_deprecated, bpf_loader_upgradeable, loader_v4, native_loader,
12};
13use rialo_s_type_overrides::{
14 rand::{thread_rng, Rng},
15 sync::{
16 atomic::{AtomicU64, Ordering},
17 Arc, Condvar, Mutex,
18 },
19 thread,
20};
21use solana_sbpf::{
22 elf::Executable, program::BuiltinProgram, verifier::RequisiteVerifier, vm::Config,
23};
24#[cfg(feature = "metrics")]
25use {rialo_s_measure::measure::Measure, rialo_s_timings::ExecuteDetailsTimings};
26
27use crate::invoke_context::{BuiltinFunctionWithContext, InvokeContext};
28
29pub type ProgramRuntimeEnvironment = Arc<BuiltinProgram<InvokeContext<'static>>>;
30pub const MAX_LOADED_ENTRY_COUNT: usize = 512;
31pub const DELAY_VISIBILITY_SLOT_OFFSET: Slot = 1;
32
33#[derive(Copy, Clone, Debug, PartialEq)]
35pub enum BlockRelation {
36 Ancestor,
38 Equal,
40 Descendant,
42 Unrelated,
44 Unknown,
46}
47
48pub trait ForkGraph: Copy + Clone + Send + Sync {
50 fn relationship(&self, a: Slot, b: Slot) -> BlockRelation;
52}
53
54#[derive(Default, Clone, Copy, PartialEq, Eq, Debug)]
56pub enum ProgramCacheEntryOwner {
57 #[default]
58 NativeLoader,
59 LoaderV1,
60 LoaderV2,
61 LoaderV3,
62 LoaderV4,
63 RiscVLoader,
64}
65
66impl TryFrom<&Pubkey> for ProgramCacheEntryOwner {
67 type Error = ();
68 fn try_from(loader_key: &Pubkey) -> Result<Self, ()> {
69 if native_loader::check_id(loader_key) {
70 Ok(ProgramCacheEntryOwner::NativeLoader)
71 } else if bpf_loader_deprecated::check_id(loader_key) {
72 Ok(ProgramCacheEntryOwner::LoaderV1)
73 } else if bpf_loader::check_id(loader_key) {
74 Ok(ProgramCacheEntryOwner::LoaderV2)
75 } else if bpf_loader_upgradeable::check_id(loader_key) {
76 Ok(ProgramCacheEntryOwner::LoaderV3)
77 } else if loader_v4::check_id(loader_key) {
78 Ok(ProgramCacheEntryOwner::LoaderV4)
79 } else if rialo_risc_v_loader_interface::check_id(loader_key) {
80 Ok(ProgramCacheEntryOwner::RiscVLoader)
81 } else {
82 Err(())
83 }
84 }
85}
86
87impl From<ProgramCacheEntryOwner> for Pubkey {
88 fn from(program_cache_entry_owner: ProgramCacheEntryOwner) -> Self {
89 match program_cache_entry_owner {
90 ProgramCacheEntryOwner::NativeLoader => native_loader::id(),
91 ProgramCacheEntryOwner::LoaderV1 => bpf_loader_deprecated::id(),
92 ProgramCacheEntryOwner::LoaderV2 => bpf_loader::id(),
93 ProgramCacheEntryOwner::LoaderV3 => bpf_loader_upgradeable::id(),
94 ProgramCacheEntryOwner::LoaderV4 => loader_v4::id(),
95 ProgramCacheEntryOwner::RiscVLoader => rialo_risc_v_loader_interface::id(),
96 }
97 }
98}
99
100pub enum ExecutableKind {
130 Ebpf(Executable<InvokeContext<'static>>),
132
133 RiscV(Vec<u8>),
135}
136
137#[derive(Default)]
139pub enum ProgramCacheEntryType {
140 FailedVerification(ProgramRuntimeEnvironment),
142 #[default]
146 Closed,
147 DelayVisibility,
149 Unloaded(ProgramRuntimeEnvironment),
153 Loaded(ExecutableKind),
155 Builtin(BuiltinProgram<InvokeContext<'static>>),
157}
158
159impl Debug for ProgramCacheEntryType {
160 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
161 match self {
162 ProgramCacheEntryType::FailedVerification(_) => {
163 write!(f, "ProgramCacheEntryType::FailedVerification")
164 }
165 ProgramCacheEntryType::Closed => write!(f, "ProgramCacheEntryType::Closed"),
166 ProgramCacheEntryType::DelayVisibility => {
167 write!(f, "ProgramCacheEntryType::DelayVisibility")
168 }
169 ProgramCacheEntryType::Unloaded(_) => write!(f, "ProgramCacheEntryType::Unloaded"),
170 ProgramCacheEntryType::Loaded(_) => write!(f, "ProgramCacheEntryType::Loaded"),
171 ProgramCacheEntryType::Builtin(_) => write!(f, "ProgramCacheEntryType::Builtin"),
172 }
173 }
174}
175
176impl ProgramCacheEntryType {
177 pub fn get_environment(&self) -> Option<&ProgramRuntimeEnvironment> {
179 match self {
180 ProgramCacheEntryType::Loaded(ExecutableKind::Ebpf(program)) => {
181 Some(program.get_loader())
182 }
183 ProgramCacheEntryType::FailedVerification(env)
184 | ProgramCacheEntryType::Unloaded(env) => Some(env),
185 _ => None,
186 }
187 }
188}
189
190#[derive(Debug, Default)]
194pub struct ProgramCacheEntry {
195 pub program: ProgramCacheEntryType,
197 pub account_owner: ProgramCacheEntryOwner,
199 pub account_size: usize,
201 pub deployment_slot: Slot,
203 pub effective_slot: Slot,
205 pub tx_usage_counter: AtomicU64,
207 pub ix_usage_counter: AtomicU64,
209 pub latest_access_slot: AtomicU64,
211}
212
213#[derive(Debug, Default)]
215pub struct ProgramCacheStats {
216 pub hits: AtomicU64,
218 pub misses: AtomicU64,
220 pub evictions: HashMap<Pubkey, u64>,
222 pub reloads: AtomicU64,
224 pub insertions: AtomicU64,
226 pub lost_insertions: AtomicU64,
228 pub replacements: AtomicU64,
230 pub one_hit_wonders: AtomicU64,
232 pub prunes_orphan: AtomicU64,
234 pub prunes_environment: AtomicU64,
236 pub empty_entries: AtomicU64,
238 pub water_level: AtomicU64,
240}
241
242impl ProgramCacheStats {
243 pub fn reset(&mut self) {
244 *self = ProgramCacheStats::default();
245 }
246 pub fn log(&self) {
247 let hits = self.hits.load(Ordering::Relaxed);
248 let misses = self.misses.load(Ordering::Relaxed);
249 let evictions: u64 = self.evictions.values().sum();
250 let reloads = self.reloads.load(Ordering::Relaxed);
251 let insertions = self.insertions.load(Ordering::Relaxed);
252 let lost_insertions = self.lost_insertions.load(Ordering::Relaxed);
253 let replacements = self.replacements.load(Ordering::Relaxed);
254 let one_hit_wonders = self.one_hit_wonders.load(Ordering::Relaxed);
255 let prunes_orphan = self.prunes_orphan.load(Ordering::Relaxed);
256 let prunes_environment = self.prunes_environment.load(Ordering::Relaxed);
257 let empty_entries = self.empty_entries.load(Ordering::Relaxed);
258 let water_level = self.water_level.load(Ordering::Relaxed);
259 debug!(
260 "Loaded Programs Cache Stats -- Hits: {}, Misses: {}, Evictions: {}, Reloads: {}, Insertions: {}, Lost-Insertions: {}, Replacements: {}, One-Hit-Wonders: {}, Prunes-Orphan: {}, Prunes-Environment: {}, Empty: {}, Water-Level: {}",
261 hits, misses, evictions, reloads, insertions, lost_insertions, replacements, one_hit_wonders, prunes_orphan, prunes_environment, empty_entries, water_level
262 );
263 if log_enabled!(log::Level::Trace) && !self.evictions.is_empty() {
264 let mut evictions = self.evictions.iter().collect::<Vec<_>>();
265 evictions.sort_by_key(|e| e.1);
266 let evictions = evictions
267 .into_iter()
268 .rev()
269 .map(|(program_id, evictions)| {
270 format!(" {:<44} {}", program_id.to_string(), evictions)
271 })
272 .collect::<Vec<_>>();
273 let evictions = evictions.join("\n");
274 trace!(
275 "Eviction Details:\n {:<44} {}\n{}",
276 "Program",
277 "Count",
278 evictions
279 );
280 }
281 }
282}
283
284#[cfg(feature = "metrics")]
285#[derive(Debug, Default)]
287pub struct LoadProgramMetrics {
288 pub program_id: String,
290 pub register_syscalls_us: u64,
292 pub load_elf_us: u64,
294 pub verify_code_us: u64,
296 pub jit_compile_us: u64,
298}
299
300#[cfg(feature = "metrics")]
301impl LoadProgramMetrics {
302 pub fn submit_datapoint(&self, timings: &mut ExecuteDetailsTimings) {
303 timings.create_executor_register_syscalls_us += self.register_syscalls_us;
304 timings.create_executor_load_elf_us += self.load_elf_us;
305 timings.create_executor_verify_code_us += self.verify_code_us;
306 timings.create_executor_jit_compile_us += self.jit_compile_us;
307 datapoint_trace!(
308 "create_executor_trace",
309 ("program_id", self.program_id, String),
310 ("register_syscalls_us", self.register_syscalls_us, i64),
311 ("load_elf_us", self.load_elf_us, i64),
312 ("verify_code_us", self.verify_code_us, i64),
313 ("jit_compile_us", self.jit_compile_us, i64),
314 );
315 }
316}
317
318impl PartialEq for ProgramCacheEntry {
319 fn eq(&self, other: &Self) -> bool {
320 self.effective_slot == other.effective_slot
321 && self.deployment_slot == other.deployment_slot
322 && self.is_tombstone() == other.is_tombstone()
323 }
324}
325
326impl ProgramCacheEntry {
327 pub fn new(
329 loader_key: &Pubkey,
330 program_runtime_environment: ProgramRuntimeEnvironment,
331 deployment_slot: Slot,
332 effective_slot: Slot,
333 elf_bytes: &[u8],
334 account_size: usize,
335 #[cfg(feature = "metrics")] metrics: &mut LoadProgramMetrics,
336 ) -> Result<Self, Box<dyn std::error::Error>> {
337 Self::new_internal(
338 loader_key,
339 program_runtime_environment,
340 deployment_slot,
341 effective_slot,
342 elf_bytes,
343 account_size,
344 #[cfg(feature = "metrics")]
345 metrics,
346 false, )
348 }
349
350 pub fn new_risc_v(
352 loader_key: &Pubkey,
353 deployment_slot: Slot,
354 effective_slot: Slot,
355 elf_bytes: &[u8],
356 account_size: usize,
357 ) -> Result<Self, Box<dyn std::error::Error>> {
358 Self::new_internal_risc_v(
359 loader_key,
360 deployment_slot,
361 effective_slot,
362 elf_bytes,
363 account_size,
364 false, )
366 }
367
368 pub unsafe fn reload(
377 loader_key: &Pubkey,
378 program_runtime_environment: Arc<BuiltinProgram<InvokeContext<'static>>>,
379 deployment_slot: Slot,
380 effective_slot: Slot,
381 elf_bytes: &[u8],
382 account_size: usize,
383 #[cfg(feature = "metrics")] metrics: &mut LoadProgramMetrics,
384 ) -> Result<Self, Box<dyn std::error::Error>> {
385 Self::new_internal(
386 loader_key,
387 program_runtime_environment,
388 deployment_slot,
389 effective_slot,
390 elf_bytes,
391 account_size,
392 #[cfg(feature = "metrics")]
393 metrics,
394 true, )
396 }
397
398 pub unsafe fn reload_risc_v(
407 loader_key: &Pubkey,
408 deployment_slot: Slot,
409 effective_slot: Slot,
410 elf_bytes: &[u8],
411 account_size: usize,
412 ) -> Result<Self, Box<dyn std::error::Error>> {
413 Self::new_internal_risc_v(
414 loader_key,
415 deployment_slot,
416 effective_slot,
417 elf_bytes,
418 account_size,
419 true, )
421 }
422
423 fn new_internal(
424 loader_key: &Pubkey,
425 program_runtime_environment: Arc<BuiltinProgram<InvokeContext<'static>>>,
426 deployment_slot: Slot,
427 effective_slot: Slot,
428 elf_bytes: &[u8],
429 account_size: usize,
430 #[cfg(feature = "metrics")] metrics: &mut LoadProgramMetrics,
431 reloading: bool,
432 ) -> Result<Self, Box<dyn std::error::Error>> {
433 #[cfg(feature = "metrics")]
434 let load_elf_time = Measure::start("load_elf_time");
435 #[allow(unused_mut)]
438 let mut executable = Executable::load(elf_bytes, program_runtime_environment.clone())?;
439 #[cfg(feature = "metrics")]
440 {
441 metrics.load_elf_us = load_elf_time.end_as_us();
442 }
443
444 if !reloading {
445 #[cfg(feature = "metrics")]
446 let verify_code_time = Measure::start("verify_code_time");
447 executable.verify::<RequisiteVerifier>()?;
448 #[cfg(feature = "metrics")]
449 {
450 metrics.verify_code_us = verify_code_time.end_as_us();
451 }
452 }
453
454 #[cfg(all(not(target_os = "windows"), target_arch = "x86_64"))]
455 {
456 #[cfg(feature = "metrics")]
457 let jit_compile_time = Measure::start("jit_compile_time");
458 executable.jit_compile()?;
459 #[cfg(feature = "metrics")]
460 {
461 metrics.jit_compile_us = jit_compile_time.end_as_us();
462 }
463 }
464
465 Ok(Self {
466 deployment_slot,
467 account_owner: ProgramCacheEntryOwner::try_from(loader_key).unwrap(),
468 account_size,
469 effective_slot,
470 tx_usage_counter: AtomicU64::new(0),
471 program: ProgramCacheEntryType::Loaded(ExecutableKind::Ebpf(executable)),
472 ix_usage_counter: AtomicU64::new(0),
473 latest_access_slot: AtomicU64::new(0),
474 })
475 }
476
477 fn new_internal_risc_v(
478 loader_key: &Pubkey,
479 deployment_slot: Slot,
480 effective_slot: Slot,
481 elf_bytes: &[u8],
482 account_size: usize,
483 _reloading: bool,
484 ) -> Result<Self, Box<dyn std::error::Error>> {
485 Ok(Self {
486 deployment_slot,
487 account_owner: ProgramCacheEntryOwner::try_from(loader_key).unwrap(),
488 account_size,
489 effective_slot,
490 tx_usage_counter: AtomicU64::new(0),
491 program: ProgramCacheEntryType::Loaded(ExecutableKind::RiscV(elf_bytes.to_vec())),
492 ix_usage_counter: AtomicU64::new(0),
493 latest_access_slot: AtomicU64::new(0),
494 })
495 }
496
497 pub fn to_unloaded(&self) -> Option<Self> {
498 match &self.program {
499 ProgramCacheEntryType::Loaded(_) => {}
500 ProgramCacheEntryType::FailedVerification(_)
501 | ProgramCacheEntryType::Closed
502 | ProgramCacheEntryType::DelayVisibility
503 | ProgramCacheEntryType::Unloaded(_)
504 | ProgramCacheEntryType::Builtin(_) => {
505 return None;
506 }
507 }
508 Some(Self {
509 program: ProgramCacheEntryType::Unloaded(self.program.get_environment()?.clone()),
510 account_owner: self.account_owner,
511 account_size: self.account_size,
512 deployment_slot: self.deployment_slot,
513 effective_slot: self.effective_slot,
514 tx_usage_counter: AtomicU64::new(self.tx_usage_counter.load(Ordering::Relaxed)),
515 ix_usage_counter: AtomicU64::new(self.ix_usage_counter.load(Ordering::Relaxed)),
516 latest_access_slot: AtomicU64::new(self.latest_access_slot.load(Ordering::Relaxed)),
517 })
518 }
519
520 pub fn new_builtin(
522 deployment_slot: Slot,
523 account_size: usize,
524 builtin_function: BuiltinFunctionWithContext,
525 ) -> Self {
526 let mut program = BuiltinProgram::new_builtin();
527 program
528 .register_function("entrypoint", builtin_function)
529 .unwrap();
530 Self {
531 deployment_slot,
532 account_owner: ProgramCacheEntryOwner::NativeLoader,
533 account_size,
534 effective_slot: deployment_slot,
535 tx_usage_counter: AtomicU64::new(0),
536 program: ProgramCacheEntryType::Builtin(program),
537 ix_usage_counter: AtomicU64::new(0),
538 latest_access_slot: AtomicU64::new(0),
539 }
540 }
541
542 pub fn new_tombstone(
543 slot: Slot,
544 account_owner: ProgramCacheEntryOwner,
545 reason: ProgramCacheEntryType,
546 ) -> Self {
547 let tombstone = Self {
548 program: reason,
549 account_owner,
550 account_size: 0,
551 deployment_slot: slot,
552 effective_slot: slot,
553 tx_usage_counter: AtomicU64::default(),
554 ix_usage_counter: AtomicU64::default(),
555 latest_access_slot: AtomicU64::new(0),
556 };
557 debug_assert!(tombstone.is_tombstone());
558 tombstone
559 }
560
561 pub fn is_tombstone(&self) -> bool {
562 matches!(
563 self.program,
564 ProgramCacheEntryType::FailedVerification(_)
565 | ProgramCacheEntryType::Closed
566 | ProgramCacheEntryType::DelayVisibility
567 )
568 }
569
570 fn is_implicit_delay_visibility_tombstone(&self, slot: Slot) -> bool {
571 !matches!(self.program, ProgramCacheEntryType::Builtin(_))
572 && self.effective_slot.saturating_sub(self.deployment_slot)
573 == DELAY_VISIBILITY_SLOT_OFFSET
574 && slot >= self.deployment_slot
575 && slot < self.effective_slot
576 }
577
578 pub fn update_access_slot(&self, slot: Slot) {
579 let _ = self.latest_access_slot.fetch_max(slot, Ordering::Relaxed);
580 }
581
582 pub fn decayed_usage_counter(&self, now: Slot) -> u64 {
583 let last_access = self.latest_access_slot.load(Ordering::Relaxed);
584 let decaying_for = std::cmp::min(63, now.saturating_sub(last_access));
586 self.tx_usage_counter.load(Ordering::Relaxed) >> decaying_for
587 }
588
589 pub fn account_owner(&self) -> Pubkey {
590 self.account_owner.into()
591 }
592}
593
594#[derive(Clone, Debug)]
598pub struct ProgramRuntimeEnvironments {
599 pub program_runtime_v1: ProgramRuntimeEnvironment,
601 pub program_runtime_v2: ProgramRuntimeEnvironment,
603}
604
605impl Default for ProgramRuntimeEnvironments {
606 fn default() -> Self {
607 let empty_loader = Arc::new(BuiltinProgram::new_loader(Config::default()));
608 Self {
609 program_runtime_v1: empty_loader.clone(),
610 program_runtime_v2: empty_loader,
611 }
612 }
613}
614
615#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
616pub struct LoadingTaskCookie(u64);
617
618impl LoadingTaskCookie {
619 fn new() -> Self {
620 Self(0)
621 }
622
623 fn update(&mut self) {
624 let LoadingTaskCookie(cookie) = self;
625 *cookie = cookie.wrapping_add(1);
626 }
627}
628
629#[derive(Debug, Default)]
631pub struct LoadingTaskWaiter {
632 cookie: Mutex<LoadingTaskCookie>,
633 cond: Condvar,
634}
635
636impl LoadingTaskWaiter {
637 pub fn new() -> Self {
638 Self {
639 cookie: Mutex::new(LoadingTaskCookie::new()),
640 cond: Condvar::new(),
641 }
642 }
643
644 pub fn cookie(&self) -> LoadingTaskCookie {
645 *self.cookie.lock().unwrap()
646 }
647
648 pub fn notify(&self) {
649 let mut cookie = self.cookie.lock().unwrap();
650 cookie.update();
651 self.cond.notify_all();
652 }
653
654 pub fn wait(&self, cookie: LoadingTaskCookie) -> LoadingTaskCookie {
655 let cookie_guard = self.cookie.lock().unwrap();
656 *self
657 .cond
658 .wait_while(cookie_guard, |current_cookie| *current_cookie == cookie)
659 .unwrap()
660 }
661}
662
663#[derive(Debug)]
664enum IndexImplementation {
665 V1 {
667 entries: HashMap<Pubkey, Vec<Arc<ProgramCacheEntry>>>,
672 loading_entries: Mutex<HashMap<Pubkey, (Slot, thread::ThreadId)>>,
681 },
682}
683
684pub struct ProgramCache {
700 index: IndexImplementation,
702 pub latest_root_slot: Slot,
704 pub latest_root_epoch: Epoch,
706 pub environments: ProgramRuntimeEnvironments,
708 pub upcoming_environments: Option<ProgramRuntimeEnvironments>,
714 pub programs_to_recompile: Vec<(Pubkey, Arc<ProgramCacheEntry>)>,
716 pub stats: ProgramCacheStats,
718 pub loading_task_waiter: Arc<LoadingTaskWaiter>,
720}
721
722impl Debug for ProgramCache {
723 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
724 f.debug_struct("ProgramCache")
725 .field("root slot", &self.latest_root_slot)
726 .field("root epoch", &self.latest_root_epoch)
727 .field("stats", &self.stats)
728 .field("index", &self.index)
729 .finish()
730 }
731}
732
733#[derive(Clone, Debug, Default)]
739pub struct ProgramCacheForTxBatch {
740 entries: HashMap<Pubkey, Arc<ProgramCacheEntry>>,
743 modified_entries: HashMap<Pubkey, Arc<ProgramCacheEntry>>,
745 slot: Slot,
746 pub environments: ProgramRuntimeEnvironments,
747 pub upcoming_environments: Option<ProgramRuntimeEnvironments>,
755 pub latest_root_epoch: Epoch,
757 pub hit_max_limit: bool,
758 pub loaded_missing: bool,
759 pub merged_modified: bool,
760}
761
762impl ProgramCacheForTxBatch {
763 pub fn new(
764 slot: Slot,
765 environments: ProgramRuntimeEnvironments,
766 upcoming_environments: Option<ProgramRuntimeEnvironments>,
767 latest_root_epoch: Epoch,
768 ) -> Self {
769 Self {
770 entries: HashMap::new(),
771 modified_entries: HashMap::new(),
772 slot,
773 environments,
774 upcoming_environments,
775 latest_root_epoch,
776 hit_max_limit: false,
777 loaded_missing: false,
778 merged_modified: false,
779 }
780 }
781
782 pub fn new_from_cache(slot: Slot, epoch: Epoch, cache: &ProgramCache) -> Self {
783 Self {
784 entries: HashMap::new(),
785 modified_entries: HashMap::new(),
786 slot,
787 environments: cache.get_environments_for_epoch(epoch),
788 upcoming_environments: cache.get_upcoming_environments_for_epoch(epoch),
789 latest_root_epoch: cache.latest_root_epoch,
790 hit_max_limit: false,
791 loaded_missing: false,
792 merged_modified: false,
793 }
794 }
795
796 pub fn get_environments_for_epoch(&self, epoch: Epoch) -> &ProgramRuntimeEnvironments {
798 if epoch != self.latest_root_epoch {
799 if let Some(upcoming_environments) = self.upcoming_environments.as_ref() {
800 return upcoming_environments;
801 }
802 }
803 &self.environments
804 }
805
806 pub fn replenish(
812 &mut self,
813 key: Pubkey,
814 entry: Arc<ProgramCacheEntry>,
815 ) -> (bool, Arc<ProgramCacheEntry>) {
816 (self.entries.insert(key, entry.clone()).is_some(), entry)
817 }
818
819 pub fn store_modified_entry(&mut self, key: Pubkey, entry: Arc<ProgramCacheEntry>) {
822 self.modified_entries.insert(key, entry);
823 }
824
825 pub fn drain_modified_entries(&mut self) -> HashMap<Pubkey, Arc<ProgramCacheEntry>> {
828 std::mem::take(&mut self.modified_entries)
829 }
830
831 pub fn find(&self, key: &Pubkey) -> Option<Arc<ProgramCacheEntry>> {
832 self.modified_entries
836 .get(key)
837 .or(self.entries.get(key))
838 .map(|entry| {
839 if entry.is_implicit_delay_visibility_tombstone(self.slot) {
840 Arc::new(ProgramCacheEntry::new_tombstone(
844 entry.deployment_slot,
845 entry.account_owner,
846 ProgramCacheEntryType::DelayVisibility,
847 ))
848 } else {
849 entry.clone()
850 }
851 })
852 }
853
854 pub fn slot(&self) -> Slot {
855 self.slot
856 }
857
858 pub fn set_slot_for_tests(&mut self, slot: Slot) {
859 self.slot = slot;
860 }
861
862 pub fn merge(&mut self, modified_entries: &HashMap<Pubkey, Arc<ProgramCacheEntry>>) {
863 modified_entries.iter().for_each(|(key, entry)| {
864 self.merged_modified = true;
865 self.replenish(*key, entry.clone());
866 })
867 }
868
869 pub fn is_empty(&self) -> bool {
870 self.entries.is_empty()
871 }
872}
873
874pub enum ProgramCacheMatchCriteria {
875 DeployedOnOrAfterSlot(Slot),
876 Tombstone,
877 NoCriteria,
878}
879
880impl ProgramCache {
881 pub fn new(root_slot: Slot, root_epoch: Epoch) -> Self {
882 Self {
883 index: IndexImplementation::V1 {
884 entries: HashMap::new(),
885 loading_entries: Mutex::new(HashMap::new()),
886 },
887 latest_root_slot: root_slot,
888 latest_root_epoch: root_epoch,
889 environments: ProgramRuntimeEnvironments::default(),
890 upcoming_environments: None,
891 programs_to_recompile: Vec::default(),
892 stats: ProgramCacheStats::default(),
893 loading_task_waiter: Arc::new(LoadingTaskWaiter::default()),
894 }
895 }
896
897 pub fn get_environments_for_epoch(&self, epoch: Epoch) -> ProgramRuntimeEnvironments {
899 if epoch != self.latest_root_epoch {
900 if let Some(upcoming_environments) = self.upcoming_environments.as_ref() {
901 return upcoming_environments.clone();
902 }
903 }
904 self.environments.clone()
905 }
906
907 pub fn get_upcoming_environments_for_epoch(
909 &self,
910 epoch: Epoch,
911 ) -> Option<ProgramRuntimeEnvironments> {
912 if epoch == self.latest_root_epoch {
913 return self.upcoming_environments.clone();
914 }
915 None
916 }
917
918 pub fn assign_program(&mut self, key: Pubkey, entry: Arc<ProgramCacheEntry>) -> bool {
921 debug_assert!(!matches!(
922 &entry.program,
923 ProgramCacheEntryType::DelayVisibility
924 ));
925 fn is_current_env(
929 environments: &ProgramRuntimeEnvironments,
930 env_opt: Option<&ProgramRuntimeEnvironment>,
931 ) -> bool {
932 env_opt
933 .map(|env| {
934 Arc::ptr_eq(env, &environments.program_runtime_v1)
935 || Arc::ptr_eq(env, &environments.program_runtime_v2)
936 })
937 .unwrap_or(true)
938 }
939 match &mut self.index {
940 IndexImplementation::V1 { entries, .. } => {
941 let slot_versions = &mut entries.entry(key).or_default();
942 match slot_versions.binary_search_by(|at| {
943 at.effective_slot
944 .cmp(&entry.effective_slot)
945 .then(at.deployment_slot.cmp(&entry.deployment_slot))
946 .then(
947 is_current_env(&self.environments, at.program.get_environment()).cmp(
951 &is_current_env(
952 &self.environments,
953 entry.program.get_environment(),
954 ),
955 ),
956 )
957 }) {
958 Ok(index) => {
959 let existing = slot_versions.get_mut(index).unwrap();
960 match (&existing.program, &entry.program) {
961 (
962 ProgramCacheEntryType::Builtin(_),
963 ProgramCacheEntryType::Builtin(_),
964 )
965 | (
966 ProgramCacheEntryType::Unloaded(_),
967 ProgramCacheEntryType::Loaded(_),
968 ) => {}
969 _ => {
970 error!("ProgramCache::assign_program() failed key={:?} existing={:?} entry={:?}", key, slot_versions, entry);
972 debug_assert!(false, "Unexpected replacement of an entry");
973 self.stats.replacements.fetch_add(1, Ordering::Relaxed);
974 return true;
975 }
976 }
977 entry.tx_usage_counter.fetch_add(
979 existing.tx_usage_counter.load(Ordering::Relaxed),
980 Ordering::Relaxed,
981 );
982 entry.ix_usage_counter.fetch_add(
983 existing.ix_usage_counter.load(Ordering::Relaxed),
984 Ordering::Relaxed,
985 );
986 *existing = Arc::clone(&entry);
987 self.stats.reloads.fetch_add(1, Ordering::Relaxed);
988 }
989 Err(index) => {
990 self.stats.insertions.fetch_add(1, Ordering::Relaxed);
991 slot_versions.insert(index, Arc::clone(&entry));
992 }
993 }
994 }
995 }
996 false
997 }
998
999 pub fn prune_by_deployment_slot(&mut self, slot: Slot) {
1000 match &mut self.index {
1001 IndexImplementation::V1 { entries, .. } => {
1002 for second_level in entries.values_mut() {
1003 second_level.retain(|entry| entry.deployment_slot != slot);
1004 }
1005 self.remove_programs_with_no_entries();
1006 }
1007 }
1008 }
1009
1010 pub fn prune(&mut self, new_root_slot: Slot, new_root_epoch: Epoch) {
1012 let mut preparation_phase_ends = false;
1013 if self.latest_root_epoch != new_root_epoch {
1014 self.latest_root_epoch = new_root_epoch;
1015 if let Some(upcoming_environments) = self.upcoming_environments.take() {
1016 preparation_phase_ends = true;
1017 self.environments = upcoming_environments;
1018 self.programs_to_recompile.clear();
1019 }
1020 }
1021 match &mut self.index {
1022 IndexImplementation::V1 { entries, .. } => {
1023 for second_level in entries.values_mut() {
1024 let mut first_ancestor_found = false;
1026 let mut first_ancestor_env = None;
1027 *second_level = second_level
1028 .iter()
1029 .rev()
1030 .filter(|entry| {
1031 if entry.deployment_slot >= new_root_slot {
1032 true
1033 } else if entry.deployment_slot < new_root_slot
1034 || entry.deployment_slot <= self.latest_root_slot
1035 {
1036 if !first_ancestor_found {
1037 first_ancestor_found = true;
1038 first_ancestor_env = entry.program.get_environment();
1039 return true;
1040 }
1041 if let Some(entry_env) = entry.program.get_environment() {
1048 if let Some(env) = first_ancestor_env {
1049 if !Arc::ptr_eq(entry_env, env) {
1050 return true;
1051 }
1052 }
1053 }
1054 self.stats.prunes_orphan.fetch_add(1, Ordering::Relaxed);
1055 false
1056 } else {
1057 self.stats.prunes_orphan.fetch_add(1, Ordering::Relaxed);
1058 false
1059 }
1060 })
1061 .filter(|entry| {
1062 if preparation_phase_ends
1064 && !Self::matches_environment(entry, &self.environments)
1065 {
1066 self.stats
1067 .prunes_environment
1068 .fetch_add(1, Ordering::Relaxed);
1069 return false;
1070 }
1071 true
1072 })
1073 .cloned()
1074 .collect();
1075 second_level.reverse();
1076 }
1077 }
1078 }
1079 self.remove_programs_with_no_entries();
1080 debug_assert!(self.latest_root_slot <= new_root_slot);
1081 self.latest_root_slot = new_root_slot;
1082 }
1083
1084 fn matches_environment(
1085 entry: &Arc<ProgramCacheEntry>,
1086 environments: &ProgramRuntimeEnvironments,
1087 ) -> bool {
1088 let Some(environment) = entry.program.get_environment() else {
1089 return true;
1090 };
1091 Arc::ptr_eq(environment, &environments.program_runtime_v1)
1092 || Arc::ptr_eq(environment, &environments.program_runtime_v2)
1093 }
1094
1095 fn matches_criteria(
1096 program: &Arc<ProgramCacheEntry>,
1097 criteria: &ProgramCacheMatchCriteria,
1098 ) -> bool {
1099 match criteria {
1100 ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(slot) => {
1101 program.deployment_slot >= *slot
1102 }
1103 ProgramCacheMatchCriteria::Tombstone => program.is_tombstone(),
1104 ProgramCacheMatchCriteria::NoCriteria => true,
1105 }
1106 }
1107
1108 pub fn extract(
1111 &self,
1112 search_for: &mut Vec<(Pubkey, (ProgramCacheMatchCriteria, u64))>,
1113 loaded_programs_for_tx_batch: &mut ProgramCacheForTxBatch,
1114 is_first_round: bool,
1115 ) -> Option<(Pubkey, u64)> {
1116 let mut cooperative_loading_task = None;
1117 match &self.index {
1118 IndexImplementation::V1 {
1119 entries,
1120 loading_entries,
1121 } => {
1122 search_for.retain(|(key, (match_criteria, usage_count))| {
1123 if let Some(second_level) = entries.get(key) {
1124 for entry in second_level.iter().rev() {
1125 if entry.deployment_slot <= self.latest_root_slot
1126 || entry.deployment_slot <= loaded_programs_for_tx_batch.slot
1127 {
1128 let entry_to_return = if loaded_programs_for_tx_batch.slot
1129 >= entry.effective_slot
1130 && Self::matches_environment(
1131 entry,
1132 &loaded_programs_for_tx_batch.environments,
1133 ) {
1134 if !Self::matches_criteria(entry, match_criteria) {
1135 break;
1136 }
1137 if let ProgramCacheEntryType::Unloaded(_environment) =
1138 &entry.program
1139 {
1140 break;
1141 }
1142 entry.clone()
1143 } else if entry.is_implicit_delay_visibility_tombstone(
1144 loaded_programs_for_tx_batch.slot,
1145 ) {
1146 Arc::new(ProgramCacheEntry::new_tombstone(
1150 entry.deployment_slot,
1151 entry.account_owner,
1152 ProgramCacheEntryType::DelayVisibility,
1153 ))
1154 } else {
1155 continue;
1156 };
1157 entry_to_return
1158 .update_access_slot(loaded_programs_for_tx_batch.slot);
1159 entry_to_return
1160 .tx_usage_counter
1161 .fetch_add(*usage_count, Ordering::Relaxed);
1162 loaded_programs_for_tx_batch
1163 .entries
1164 .insert(*key, entry_to_return);
1165 return false;
1166 }
1167 }
1168 }
1169 if cooperative_loading_task.is_none() {
1170 let mut loading_entries = loading_entries.lock().unwrap();
1171 let entry = loading_entries.entry(*key);
1172 if let Entry::Vacant(entry) = entry {
1173 entry.insert((
1174 loaded_programs_for_tx_batch.slot,
1175 thread::current().id(),
1176 ));
1177 cooperative_loading_task = Some((*key, *usage_count));
1178 }
1179 }
1180 true
1181 });
1182 }
1183 }
1184 if is_first_round {
1185 self.stats
1186 .misses
1187 .fetch_add(search_for.len() as u64, Ordering::Relaxed);
1188 self.stats.hits.fetch_add(
1189 loaded_programs_for_tx_batch.entries.len() as u64,
1190 Ordering::Relaxed,
1191 );
1192 }
1193 cooperative_loading_task
1194 }
1195
1196 pub fn finish_cooperative_loading_task(
1198 &mut self,
1199 slot: Slot,
1200 key: Pubkey,
1201 loaded_program: Arc<ProgramCacheEntry>,
1202 ) -> bool {
1203 match &mut self.index {
1204 IndexImplementation::V1 {
1205 loading_entries, ..
1206 } => {
1207 let loading_thread = loading_entries.get_mut().unwrap().remove(&key);
1208 debug_assert_eq!(loading_thread, Some((slot, thread::current().id())));
1209 if loaded_program.deployment_slot > self.latest_root_slot
1211 && loaded_program.deployment_slot > slot
1212 {
1213 self.stats.lost_insertions.fetch_add(1, Ordering::Relaxed);
1214 }
1215 let was_occupied = self.assign_program(key, loaded_program);
1216 self.loading_task_waiter.notify();
1217 was_occupied
1218 }
1219 }
1220 }
1221
1222 pub fn merge(&mut self, modified_entries: &HashMap<Pubkey, Arc<ProgramCacheEntry>>) {
1223 modified_entries.iter().for_each(|(key, entry)| {
1224 self.assign_program(*key, entry.clone());
1225 })
1226 }
1227
1228 pub fn get_flattened_entries(
1230 &self,
1231 include_program_runtime_v1: bool,
1232 _include_program_runtime_v2: bool,
1233 ) -> Vec<(Pubkey, Arc<ProgramCacheEntry>)> {
1234 match &self.index {
1235 IndexImplementation::V1 { entries, .. } => entries
1236 .iter()
1237 .flat_map(|(id, second_level)| {
1238 second_level
1239 .iter()
1240 .filter_map(move |program| match program.program {
1241 ProgramCacheEntryType::Loaded(_) => {
1242 if include_program_runtime_v1 {
1243 Some((*id, program.clone()))
1244 } else {
1245 None
1246 }
1247 }
1248 _ => None,
1249 })
1250 })
1251 .collect(),
1252 }
1253 }
1254
1255 pub fn get_flattened_entries_for_tests(&self) -> Vec<(Pubkey, Arc<ProgramCacheEntry>)> {
1257 match &self.index {
1258 IndexImplementation::V1 { entries, .. } => entries
1259 .iter()
1260 .flat_map(|(id, second_level)| {
1261 second_level.iter().map(|program| (*id, program.clone()))
1262 })
1263 .collect(),
1264 }
1265 }
1266
1267 pub fn get_slot_versions_for_tests(&self, key: &Pubkey) -> &[Arc<ProgramCacheEntry>] {
1269 match &self.index {
1270 IndexImplementation::V1 { entries, .. } => entries
1271 .get(key)
1272 .map(|second_level| second_level.as_ref())
1273 .unwrap_or(&[]),
1274 }
1275 }
1276
1277 pub fn sort_and_unload(&mut self, shrink_to: PercentageInteger) {
1279 let mut sorted_candidates = self.get_flattened_entries(true, true);
1280 sorted_candidates
1281 .sort_by_cached_key(|(_id, program)| program.tx_usage_counter.load(Ordering::Relaxed));
1282 let num_to_unload = sorted_candidates
1283 .len()
1284 .saturating_sub(shrink_to.apply_to(MAX_LOADED_ENTRY_COUNT));
1285 self.unload_program_entries(sorted_candidates.iter().take(num_to_unload));
1286 }
1287
1288 pub fn evict_using_2s_random_selection(&mut self, shrink_to: PercentageInteger, now: Slot) {
1291 let mut candidates = self.get_flattened_entries(true, true);
1292 self.stats
1293 .water_level
1294 .store(candidates.len() as u64, Ordering::Relaxed);
1295 let num_to_unload = candidates
1296 .len()
1297 .saturating_sub(shrink_to.apply_to(MAX_LOADED_ENTRY_COUNT));
1298 fn random_index_and_usage_counter(
1299 candidates: &[(Pubkey, Arc<ProgramCacheEntry>)],
1300 now: Slot,
1301 ) -> (usize, u64) {
1302 let mut rng = thread_rng();
1303 let index = rng.gen_range(0..candidates.len());
1304 let usage_counter = candidates
1305 .get(index)
1306 .expect("Failed to get cached entry")
1307 .1
1308 .decayed_usage_counter(now);
1309 (index, usage_counter)
1310 }
1311
1312 for _ in 0..num_to_unload {
1313 let (index1, usage_counter1) = random_index_and_usage_counter(&candidates, now);
1314 let (index2, usage_counter2) = random_index_and_usage_counter(&candidates, now);
1315
1316 let (program, entry) = if usage_counter1 < usage_counter2 {
1317 candidates.swap_remove(index1)
1318 } else {
1319 candidates.swap_remove(index2)
1320 };
1321 self.unload_program_entry(&program, &entry);
1322 }
1323 }
1324
1325 pub fn remove_programs(&mut self, keys: impl Iterator<Item = Pubkey>) {
1327 match &mut self.index {
1328 IndexImplementation::V1 { entries, .. } => {
1329 for k in keys {
1330 entries.remove(&k);
1331 }
1332 }
1333 }
1334 }
1335
1336 fn unload_program_entry(&mut self, program: &Pubkey, remove_entry: &Arc<ProgramCacheEntry>) {
1339 match &mut self.index {
1340 IndexImplementation::V1 { entries, .. } => {
1341 let second_level = entries.get_mut(program).expect("Cache lookup failed");
1342 let candidate = second_level
1343 .iter_mut()
1344 .find(|entry| entry == &remove_entry)
1345 .expect("Program entry not found");
1346
1347 if let Some(unloaded) = candidate.to_unloaded() {
1351 if candidate.tx_usage_counter.load(Ordering::Relaxed) == 1 {
1352 self.stats.one_hit_wonders.fetch_add(1, Ordering::Relaxed);
1353 }
1354 self.stats
1355 .evictions
1356 .entry(*program)
1357 .and_modify(|c| *c = c.saturating_add(1))
1358 .or_insert(1);
1359 *candidate = Arc::new(unloaded);
1360 }
1361 }
1362 }
1363 }
1364
1365 fn unload_program_entries<'a>(
1366 &mut self,
1367 remove: impl Iterator<Item = &'a (Pubkey, Arc<ProgramCacheEntry>)>,
1368 ) {
1369 for (program, entry) in remove {
1370 self.unload_program_entry(program, entry);
1371 }
1372 }
1373
1374 fn remove_programs_with_no_entries(&mut self) {
1375 match &mut self.index {
1376 IndexImplementation::V1 { entries, .. } => {
1377 let num_programs_before_removal = entries.len();
1378 entries.retain(|_key, second_level| !second_level.is_empty());
1379 if entries.len() < num_programs_before_removal {
1380 self.stats.empty_entries.fetch_add(
1381 num_programs_before_removal.saturating_sub(entries.len()) as u64,
1382 Ordering::Relaxed,
1383 );
1384 }
1385 }
1386 }
1387 }
1388}
1389
1390#[cfg(feature = "frozen-abi")]
1391impl rialo_frozen_abi::abi_example::AbiExample for ProgramCacheEntry {
1392 fn example() -> Self {
1393 Self::default()
1395 }
1396}
1397
1398#[cfg(feature = "frozen-abi")]
1399impl rialo_frozen_abi::abi_example::AbiExample for ProgramCache {
1400 fn example() -> Self {
1401 Self::new(Slot::default(), Epoch::default())
1403 }
1404}
1405
1406#[cfg(test)]
1407mod tests {
1408 use std::{
1409 fs::File,
1410 io::Read,
1411 sync::{
1412 atomic::{AtomicU64, Ordering},
1413 Arc,
1414 },
1415 };
1416
1417 use assert_matches::assert_matches;
1418 use percentage::Percentage;
1419 use rialo_s_clock::Slot;
1420 use rialo_s_pubkey::Pubkey;
1421 use solana_sbpf::{elf::Executable, program::BuiltinProgram};
1422 use test_case::{test_case, test_matrix};
1423
1424 use crate::loaded_programs::{
1425 ProgramCache, ProgramCacheEntry, ProgramCacheEntryOwner, ProgramCacheEntryType,
1426 ProgramCacheForTxBatch, ProgramCacheMatchCriteria, ProgramRuntimeEnvironment,
1427 ProgramRuntimeEnvironments, DELAY_VISIBILITY_SLOT_OFFSET,
1428 };
1429
1430 static MOCK_ENVIRONMENT: std::sync::OnceLock<ProgramRuntimeEnvironment> =
1431 std::sync::OnceLock::<ProgramRuntimeEnvironment>::new();
1432
1433 fn get_mock_env() -> ProgramRuntimeEnvironment {
1434 MOCK_ENVIRONMENT
1435 .get_or_init(|| Arc::new(BuiltinProgram::new_mock()))
1436 .clone()
1437 }
1438
1439 fn new_mock_cache() -> ProgramCache {
1440 let mut cache = ProgramCache::new(0, 0);
1441 cache.environments.program_runtime_v1 = get_mock_env();
1442 cache
1443 }
1444
1445 fn new_test_entry(deployment_slot: Slot, effective_slot: Slot) -> Arc<ProgramCacheEntry> {
1446 new_test_entry_with_usage(deployment_slot, effective_slot, AtomicU64::default())
1447 }
1448
1449 fn new_loaded_entry(env: ProgramRuntimeEnvironment) -> ProgramCacheEntryType {
1450 let mut elf = Vec::new();
1451 File::open("../programs/bpf_loader/test_elfs/out/noop_aligned.so")
1452 .unwrap()
1453 .read_to_end(&mut elf)
1454 .unwrap();
1455 let executable = Executable::load(&elf, env).unwrap();
1456 ProgramCacheEntryType::Loaded(super::ExecutableKind::Ebpf(executable))
1457 }
1458
1459 fn new_test_entry_with_usage(
1460 deployment_slot: Slot,
1461 effective_slot: Slot,
1462 usage_counter: AtomicU64,
1463 ) -> Arc<ProgramCacheEntry> {
1464 Arc::new(ProgramCacheEntry {
1465 program: new_loaded_entry(get_mock_env()),
1466 account_owner: ProgramCacheEntryOwner::LoaderV2,
1467 account_size: 0,
1468 deployment_slot,
1469 effective_slot,
1470 tx_usage_counter: usage_counter,
1471 ix_usage_counter: AtomicU64::default(),
1472 latest_access_slot: AtomicU64::new(deployment_slot),
1473 })
1474 }
1475
1476 fn new_test_builtin_entry(
1477 deployment_slot: Slot,
1478 effective_slot: Slot,
1479 ) -> Arc<ProgramCacheEntry> {
1480 Arc::new(ProgramCacheEntry {
1481 program: ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1482 account_owner: ProgramCacheEntryOwner::NativeLoader,
1483 account_size: 0,
1484 deployment_slot,
1485 effective_slot,
1486 tx_usage_counter: AtomicU64::default(),
1487 ix_usage_counter: AtomicU64::default(),
1488 latest_access_slot: AtomicU64::default(),
1489 })
1490 }
1491
1492 fn set_tombstone(
1493 cache: &mut ProgramCache,
1494 key: Pubkey,
1495 slot: Slot,
1496 reason: ProgramCacheEntryType,
1497 ) -> Arc<ProgramCacheEntry> {
1498 let program = Arc::new(ProgramCacheEntry::new_tombstone(
1499 slot,
1500 ProgramCacheEntryOwner::LoaderV2,
1501 reason,
1502 ));
1503 cache.assign_program(key, program.clone());
1504 program
1505 }
1506
1507 fn insert_unloaded_entry(
1508 cache: &mut ProgramCache,
1509 key: Pubkey,
1510 slot: Slot,
1511 ) -> Arc<ProgramCacheEntry> {
1512 let loaded = new_test_entry_with_usage(slot, slot.saturating_add(1), AtomicU64::default());
1513 let unloaded = Arc::new(loaded.to_unloaded().expect("Failed to unload the program"));
1514 cache.assign_program(key, unloaded.clone());
1515 unloaded
1516 }
1517
1518 fn num_matching_entries<P>(cache: &ProgramCache, predicate: P) -> usize
1519 where
1520 P: Fn(&ProgramCacheEntryType) -> bool,
1521 {
1522 cache
1523 .get_flattened_entries_for_tests()
1524 .iter()
1525 .filter(|(_key, program)| predicate(&program.program))
1526 .count()
1527 }
1528
1529 #[test]
1530 fn test_usage_counter_decay() {
1531 let _cache = new_mock_cache();
1532 let program = new_test_entry_with_usage(10, 11, AtomicU64::new(32));
1533 program.update_access_slot(15);
1534 assert_eq!(program.decayed_usage_counter(15), 32);
1535 assert_eq!(program.decayed_usage_counter(16), 16);
1536 assert_eq!(program.decayed_usage_counter(17), 8);
1537 assert_eq!(program.decayed_usage_counter(18), 4);
1538 assert_eq!(program.decayed_usage_counter(19), 2);
1539 assert_eq!(program.decayed_usage_counter(20), 1);
1540 assert_eq!(program.decayed_usage_counter(21), 0);
1541 assert_eq!(program.decayed_usage_counter(15), 32);
1542 assert_eq!(program.decayed_usage_counter(14), 32);
1543
1544 program.update_access_slot(18);
1545 assert_eq!(program.decayed_usage_counter(15), 32);
1546 assert_eq!(program.decayed_usage_counter(16), 32);
1547 assert_eq!(program.decayed_usage_counter(17), 32);
1548 assert_eq!(program.decayed_usage_counter(18), 32);
1549 assert_eq!(program.decayed_usage_counter(19), 16);
1550 assert_eq!(program.decayed_usage_counter(20), 8);
1551 assert_eq!(program.decayed_usage_counter(21), 4);
1552
1553 assert_eq!(program.decayed_usage_counter(18 + 63), 0);
1555 assert_eq!(program.decayed_usage_counter(100), 0);
1556 }
1557
1558 fn program_deploy_test_helper(
1559 cache: &mut ProgramCache,
1560 program: Pubkey,
1561 deployment_slots: Vec<Slot>,
1562 usage_counters: Vec<u64>,
1563 programs: &mut Vec<(Pubkey, Slot, u64)>,
1564 ) {
1565 deployment_slots
1567 .iter()
1568 .enumerate()
1569 .for_each(|(i, deployment_slot)| {
1570 let usage_counter = *usage_counters.get(i).unwrap_or(&0);
1571 cache.assign_program(
1572 program,
1573 new_test_entry_with_usage(
1574 *deployment_slot,
1575 (*deployment_slot).saturating_add(2),
1576 AtomicU64::new(usage_counter),
1577 ),
1578 );
1579 programs.push((program, *deployment_slot, usage_counter));
1580 });
1581
1582 let env = Arc::new(BuiltinProgram::new_mock());
1584 for slot in 21..31 {
1585 set_tombstone(
1586 cache,
1587 program,
1588 slot,
1589 ProgramCacheEntryType::FailedVerification(env.clone()),
1590 );
1591 }
1592
1593 for slot in 31..41 {
1595 insert_unloaded_entry(cache, program, slot);
1596 }
1597 }
1598
1599 #[test]
1600 fn test_random_eviction() {
1601 let mut programs = vec![];
1602
1603 let mut cache = new_mock_cache();
1604
1605 program_deploy_test_helper(
1611 &mut cache,
1612 Pubkey::new_unique(),
1613 vec![0, 10, 20],
1614 vec![4, 5, 25],
1615 &mut programs,
1616 );
1617
1618 program_deploy_test_helper(
1620 &mut cache,
1621 Pubkey::new_unique(),
1622 vec![5, 11],
1623 vec![0, 2],
1624 &mut programs,
1625 );
1626
1627 program_deploy_test_helper(
1629 &mut cache,
1630 Pubkey::new_unique(),
1631 vec![0, 5, 15],
1632 vec![100, 3, 20],
1633 &mut programs,
1634 );
1635
1636 let num_loaded_expected = 8;
1638 let num_unloaded_expected = 30;
1640 let num_tombstones_expected = 30;
1642
1643 programs.sort_by_key(|(_id, _slot, usage_count)| *usage_count);
1645 let num_loaded = num_matching_entries(&cache, |program_type| {
1646 matches!(program_type, ProgramCacheEntryType::Loaded(_))
1647 });
1648 let num_unloaded = num_matching_entries(&cache, |program_type| {
1649 matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1650 });
1651 let num_tombstones = num_matching_entries(&cache, |program_type| {
1652 matches!(
1653 program_type,
1654 ProgramCacheEntryType::DelayVisibility
1655 | ProgramCacheEntryType::FailedVerification(_)
1656 | ProgramCacheEntryType::Closed
1657 )
1658 });
1659
1660 assert_eq!(num_loaded, num_loaded_expected);
1662 assert_eq!(num_unloaded, num_unloaded_expected);
1663 assert_eq!(num_tombstones, num_tombstones_expected);
1664
1665 let eviction_pct = 1;
1667
1668 let num_loaded_expected =
1669 Percentage::from(eviction_pct).apply_to(crate::loaded_programs::MAX_LOADED_ENTRY_COUNT);
1670 let num_unloaded_expected = num_unloaded_expected + num_loaded - num_loaded_expected;
1671 cache.evict_using_2s_random_selection(Percentage::from(eviction_pct), 21);
1672
1673 let num_loaded = num_matching_entries(&cache, |program_type| {
1675 matches!(program_type, ProgramCacheEntryType::Loaded(_))
1676 });
1677 let num_unloaded = num_matching_entries(&cache, |program_type| {
1678 matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1679 });
1680 let num_tombstones = num_matching_entries(&cache, |program_type| {
1681 matches!(program_type, ProgramCacheEntryType::FailedVerification(_))
1682 });
1683
1684 assert_eq!(num_loaded, num_loaded_expected);
1686 assert_eq!(num_unloaded, num_unloaded_expected);
1688 assert_eq!(num_tombstones, num_tombstones_expected);
1690 }
1691
1692 #[test]
1693 fn test_eviction() {
1694 let mut programs = vec![];
1695 let mut cache = new_mock_cache();
1696
1697 program_deploy_test_helper(
1699 &mut cache,
1700 Pubkey::new_unique(),
1701 vec![0, 10, 20],
1702 vec![4, 5, 25],
1703 &mut programs,
1704 );
1705
1706 program_deploy_test_helper(
1708 &mut cache,
1709 Pubkey::new_unique(),
1710 vec![5, 11],
1711 vec![0, 2],
1712 &mut programs,
1713 );
1714
1715 program_deploy_test_helper(
1717 &mut cache,
1718 Pubkey::new_unique(),
1719 vec![0, 5, 15],
1720 vec![100, 3, 20],
1721 &mut programs,
1722 );
1723
1724 let num_loaded_expected = 8;
1726 let num_unloaded_expected = 30;
1728 let num_tombstones_expected = 30;
1730
1731 programs.sort_by_key(|(_id, _slot, usage_count)| *usage_count);
1733 let num_loaded = num_matching_entries(&cache, |program_type| {
1734 matches!(program_type, ProgramCacheEntryType::Loaded(_))
1735 });
1736 let num_unloaded = num_matching_entries(&cache, |program_type| {
1737 matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1738 });
1739 let num_tombstones = num_matching_entries(&cache, |program_type| {
1740 matches!(program_type, ProgramCacheEntryType::FailedVerification(_))
1741 });
1742
1743 assert_eq!(num_loaded, num_loaded_expected);
1745 assert_eq!(num_unloaded, num_unloaded_expected);
1746 assert_eq!(num_tombstones, num_tombstones_expected);
1747
1748 let eviction_pct = 1;
1750
1751 let num_loaded_expected =
1752 Percentage::from(eviction_pct).apply_to(crate::loaded_programs::MAX_LOADED_ENTRY_COUNT);
1753 let num_unloaded_expected = num_unloaded_expected + num_loaded - num_loaded_expected;
1754
1755 cache.sort_and_unload(Percentage::from(eviction_pct));
1756
1757 let entries = cache.get_flattened_entries_for_tests();
1759 programs.iter().for_each(|entry| {
1760 assert!(entries.iter().any(|(key, _entry)| key == &entry.0));
1761 });
1762
1763 let unloaded = entries
1764 .iter()
1765 .filter_map(|(key, program)| {
1766 matches!(program.program, ProgramCacheEntryType::Unloaded(_))
1767 .then_some((*key, program.tx_usage_counter.load(Ordering::Relaxed)))
1768 })
1769 .collect::<Vec<(Pubkey, u64)>>();
1770
1771 for index in 0..3 {
1772 let expected = programs.get(index).expect("Missing program");
1773 assert!(unloaded.contains(&(expected.0, expected.2)));
1774 }
1775
1776 let num_loaded = num_matching_entries(&cache, |program_type| {
1778 matches!(program_type, ProgramCacheEntryType::Loaded(_))
1779 });
1780 let num_unloaded = num_matching_entries(&cache, |program_type| {
1781 matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1782 });
1783 let num_tombstones = num_matching_entries(&cache, |program_type| {
1784 matches!(
1785 program_type,
1786 ProgramCacheEntryType::DelayVisibility
1787 | ProgramCacheEntryType::FailedVerification(_)
1788 | ProgramCacheEntryType::Closed
1789 )
1790 });
1791
1792 assert_eq!(num_loaded, num_loaded_expected);
1794 assert_eq!(num_unloaded, num_unloaded_expected);
1796 assert_eq!(num_tombstones, num_tombstones_expected);
1798 }
1799
1800 #[test]
1801 fn test_usage_count_of_unloaded_program() {
1802 let mut cache = new_mock_cache();
1803
1804 let program = Pubkey::new_unique();
1805 let evict_to_pct = 2;
1806 let cache_capacity_after_shrink =
1807 Percentage::from(evict_to_pct).apply_to(crate::loaded_programs::MAX_LOADED_ENTRY_COUNT);
1808 let num_total_programs = (cache_capacity_after_shrink + 1) as u64;
1810 (0..num_total_programs).for_each(|i| {
1811 cache.assign_program(
1812 program,
1813 new_test_entry_with_usage(i, i + 2, AtomicU64::new(i + 10)),
1814 );
1815 });
1816
1817 cache.sort_and_unload(Percentage::from(evict_to_pct));
1818
1819 let num_unloaded = num_matching_entries(&cache, |program_type| {
1820 matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1821 });
1822 assert_eq!(num_unloaded, 1);
1823
1824 cache
1825 .get_flattened_entries_for_tests()
1826 .iter()
1827 .for_each(|(_key, program)| {
1828 if matches!(program.program, ProgramCacheEntryType::Unloaded(_)) {
1829 assert_eq!(program.tx_usage_counter.load(Ordering::Relaxed), 10);
1831 assert_eq!(program.deployment_slot, 0);
1832 assert_eq!(program.effective_slot, 2);
1833 }
1834 });
1835
1836 cache.assign_program(program, new_test_entry_with_usage(0, 2, AtomicU64::new(0)));
1839
1840 cache
1841 .get_flattened_entries_for_tests()
1842 .iter()
1843 .for_each(|(_key, program)| {
1844 if matches!(program.program, ProgramCacheEntryType::Unloaded(_))
1845 && program.deployment_slot == 0
1846 && program.effective_slot == 2
1847 {
1848 assert_eq!(program.tx_usage_counter.load(Ordering::Relaxed), 10);
1850 }
1851 });
1852 }
1853
1854 #[test]
1855 fn test_fuzz_assign_program_order() {
1856 use rand::prelude::SliceRandom;
1857 const EXPECTED_ENTRIES: [(u64, u64); 7] =
1858 [(1, 2), (5, 5), (5, 6), (5, 10), (9, 10), (10, 10), (3, 12)];
1859 let mut rng = rand::thread_rng();
1860 let program_id = Pubkey::new_unique();
1861 for _ in 0..1000 {
1862 let mut entries = EXPECTED_ENTRIES.to_vec();
1863 entries.shuffle(&mut rng);
1864 let mut cache = new_mock_cache();
1865 for (deployment_slot, effective_slot) in entries {
1866 assert!(!cache
1867 .assign_program(program_id, new_test_entry(deployment_slot, effective_slot)));
1868 }
1869 for ((deployment_slot, effective_slot), entry) in EXPECTED_ENTRIES
1870 .iter()
1871 .zip(cache.get_slot_versions_for_tests(&program_id).iter())
1872 {
1873 assert_eq!(entry.deployment_slot, *deployment_slot);
1874 assert_eq!(entry.effective_slot, *effective_slot);
1875 }
1876 }
1877 }
1878
1879 #[test_matrix(
1880 (
1881 ProgramCacheEntryType::Closed,
1882 ProgramCacheEntryType::FailedVerification(get_mock_env()),
1883 new_loaded_entry(get_mock_env()),
1884 ),
1885 (
1886 ProgramCacheEntryType::FailedVerification(get_mock_env()),
1887 ProgramCacheEntryType::Closed,
1888 ProgramCacheEntryType::Unloaded(get_mock_env()),
1889 new_loaded_entry(get_mock_env()),
1890 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1891 )
1892 )]
1893 #[test_matrix(
1894 (
1895 ProgramCacheEntryType::Unloaded(get_mock_env()),
1896 ),
1897 (
1898 ProgramCacheEntryType::FailedVerification(get_mock_env()),
1899 ProgramCacheEntryType::Closed,
1900 ProgramCacheEntryType::Unloaded(get_mock_env()),
1901 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1902 )
1903 )]
1904 #[test_matrix(
1905 (ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),),
1906 (
1907 ProgramCacheEntryType::FailedVerification(get_mock_env()),
1908 ProgramCacheEntryType::Closed,
1909 ProgramCacheEntryType::Unloaded(get_mock_env()),
1910 new_loaded_entry(get_mock_env()),
1911 )
1912 )]
1913 #[should_panic(expected = "Unexpected replacement of an entry")]
1914 fn test_assign_program_failure(old: ProgramCacheEntryType, new: ProgramCacheEntryType) {
1915 let mut cache = new_mock_cache();
1916 let program_id = Pubkey::new_unique();
1917 assert!(!cache.assign_program(
1918 program_id,
1919 Arc::new(ProgramCacheEntry {
1920 program: old,
1921 account_owner: ProgramCacheEntryOwner::LoaderV2,
1922 account_size: 0,
1923 deployment_slot: 10,
1924 effective_slot: 11,
1925 tx_usage_counter: AtomicU64::default(),
1926 ix_usage_counter: AtomicU64::default(),
1927 latest_access_slot: AtomicU64::default(),
1928 }),
1929 ));
1930 cache.assign_program(
1931 program_id,
1932 Arc::new(ProgramCacheEntry {
1933 program: new,
1934 account_owner: ProgramCacheEntryOwner::LoaderV2,
1935 account_size: 0,
1936 deployment_slot: 10,
1937 effective_slot: 11,
1938 tx_usage_counter: AtomicU64::default(),
1939 ix_usage_counter: AtomicU64::default(),
1940 latest_access_slot: AtomicU64::default(),
1941 }),
1942 );
1943 }
1944
1945 #[test_case(
1946 ProgramCacheEntryType::Unloaded(Arc::new(BuiltinProgram::new_mock())),
1947 new_loaded_entry(get_mock_env())
1948 )]
1949 #[test_case(
1950 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1951 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock())
1952 )]
1953 fn test_assign_program_success(old: ProgramCacheEntryType, new: ProgramCacheEntryType) {
1954 let mut cache = new_mock_cache();
1955 let program_id = Pubkey::new_unique();
1956 assert!(!cache.assign_program(
1957 program_id,
1958 Arc::new(ProgramCacheEntry {
1959 program: old,
1960 account_owner: ProgramCacheEntryOwner::LoaderV2,
1961 account_size: 0,
1962 deployment_slot: 10,
1963 effective_slot: 11,
1964 tx_usage_counter: AtomicU64::default(),
1965 ix_usage_counter: AtomicU64::default(),
1966 latest_access_slot: AtomicU64::default(),
1967 }),
1968 ));
1969 assert!(!cache.assign_program(
1970 program_id,
1971 Arc::new(ProgramCacheEntry {
1972 program: new,
1973 account_owner: ProgramCacheEntryOwner::LoaderV2,
1974 account_size: 0,
1975 deployment_slot: 10,
1976 effective_slot: 11,
1977 tx_usage_counter: AtomicU64::default(),
1978 ix_usage_counter: AtomicU64::default(),
1979 latest_access_slot: AtomicU64::default(),
1980 }),
1981 ));
1982 }
1983
1984 #[test]
1985 fn test_tombstone() {
1986 let env = Arc::new(BuiltinProgram::new_mock());
1987 let tombstone = ProgramCacheEntry::new_tombstone(
1988 0,
1989 ProgramCacheEntryOwner::LoaderV2,
1990 ProgramCacheEntryType::FailedVerification(env.clone()),
1991 );
1992 assert_matches!(
1993 tombstone.program,
1994 ProgramCacheEntryType::FailedVerification(_)
1995 );
1996 assert!(tombstone.is_tombstone());
1997 assert_eq!(tombstone.deployment_slot, 0);
1998 assert_eq!(tombstone.effective_slot, 0);
1999
2000 let tombstone = ProgramCacheEntry::new_tombstone(
2001 100,
2002 ProgramCacheEntryOwner::LoaderV2,
2003 ProgramCacheEntryType::Closed,
2004 );
2005 assert_matches!(tombstone.program, ProgramCacheEntryType::Closed);
2006 assert!(tombstone.is_tombstone());
2007 assert_eq!(tombstone.deployment_slot, 100);
2008 assert_eq!(tombstone.effective_slot, 100);
2009
2010 let mut cache = new_mock_cache();
2011 let program1 = Pubkey::new_unique();
2012 let tombstone = set_tombstone(
2013 &mut cache,
2014 program1,
2015 10,
2016 ProgramCacheEntryType::FailedVerification(env.clone()),
2017 );
2018 let slot_versions = cache.get_slot_versions_for_tests(&program1);
2019 assert_eq!(slot_versions.len(), 1);
2020 assert!(slot_versions.first().unwrap().is_tombstone());
2021 assert_eq!(tombstone.deployment_slot, 10);
2022 assert_eq!(tombstone.effective_slot, 10);
2023
2024 let program2 = Pubkey::new_unique();
2026 cache.assign_program(program2, new_test_builtin_entry(50, 51));
2027 let slot_versions = cache.get_slot_versions_for_tests(&program2);
2028 assert_eq!(slot_versions.len(), 1);
2029 assert!(!slot_versions.first().unwrap().is_tombstone());
2030
2031 let tombstone = set_tombstone(
2032 &mut cache,
2033 program2,
2034 60,
2035 ProgramCacheEntryType::FailedVerification(env),
2036 );
2037 let slot_versions = cache.get_slot_versions_for_tests(&program2);
2038 assert_eq!(slot_versions.len(), 2);
2039 assert!(!slot_versions.first().unwrap().is_tombstone());
2040 assert!(slot_versions.get(1).unwrap().is_tombstone());
2041 assert!(tombstone.is_tombstone());
2042 assert_eq!(tombstone.deployment_slot, 60);
2043 assert_eq!(tombstone.effective_slot, 60);
2044 }
2045
2046 #[test]
2047 fn test_prune_empty() {
2048 let mut cache = new_mock_cache();
2049
2050 cache.prune(0, 0);
2051 assert!(cache.get_flattened_entries_for_tests().is_empty());
2052
2053 cache.prune(10, 0);
2054 assert!(cache.get_flattened_entries_for_tests().is_empty());
2055
2056 let mut cache = new_mock_cache();
2057
2058 cache.prune(0, 0);
2059 assert!(cache.get_flattened_entries_for_tests().is_empty());
2060
2061 cache.prune(10, 0);
2062 assert!(cache.get_flattened_entries_for_tests().is_empty());
2063
2064 let mut cache = new_mock_cache();
2065
2066 cache.prune(0, 0);
2067 assert!(cache.get_flattened_entries_for_tests().is_empty());
2068
2069 cache.prune(10, 0);
2070 assert!(cache.get_flattened_entries_for_tests().is_empty());
2071
2072 let mut cache = new_mock_cache();
2073
2074 cache.prune(0, 0);
2075 assert!(cache.get_flattened_entries_for_tests().is_empty());
2076
2077 cache.prune(10, 0);
2078 assert!(cache.get_flattened_entries_for_tests().is_empty());
2079 }
2080
2081 #[test]
2082 fn test_prune_different_env() {
2083 let mut cache = new_mock_cache();
2084
2085 let program1 = Pubkey::new_unique();
2086 cache.assign_program(program1, new_test_entry(10, 10));
2087
2088 let new_env = Arc::new(BuiltinProgram::new_mock());
2089 cache.upcoming_environments = Some(ProgramRuntimeEnvironments {
2090 program_runtime_v1: new_env.clone(),
2091 program_runtime_v2: new_env.clone(),
2092 });
2093 let updated_program = Arc::new(ProgramCacheEntry {
2094 program: new_loaded_entry(new_env.clone()),
2095 account_owner: ProgramCacheEntryOwner::LoaderV2,
2096 account_size: 0,
2097 deployment_slot: 20,
2098 effective_slot: 20,
2099 tx_usage_counter: AtomicU64::default(),
2100 ix_usage_counter: AtomicU64::default(),
2101 latest_access_slot: AtomicU64::default(),
2102 });
2103 cache.assign_program(program1, updated_program.clone());
2104
2105 assert_eq!(cache.get_slot_versions_for_tests(&program1).len(), 2);
2107
2108 cache.prune(21, cache.latest_root_epoch);
2109
2110 assert_eq!(cache.get_slot_versions_for_tests(&program1).len(), 2);
2112
2113 cache.prune(22, cache.latest_root_epoch.saturating_add(1));
2114
2115 assert_eq!(cache.get_slot_versions_for_tests(&program1).len(), 1);
2117
2118 let entry = cache
2119 .get_slot_versions_for_tests(&program1)
2120 .first()
2121 .expect("Failed to get the program")
2122 .clone();
2123 assert_eq!(entry, updated_program);
2125 }
2126
2127 fn get_entries_to_load(
2128 cache: &ProgramCache,
2129 loading_slot: Slot,
2130 keys: &[Pubkey],
2131 ) -> Vec<(Pubkey, (ProgramCacheMatchCriteria, u64))> {
2132 let entries = cache.get_flattened_entries_for_tests();
2133 keys.iter()
2134 .filter_map(|key| {
2135 entries
2136 .iter()
2137 .rev()
2138 .find(|(program_id, entry)| {
2139 program_id == key && entry.deployment_slot <= loading_slot
2140 })
2141 .map(|(program_id, _entry)| {
2142 (*program_id, (ProgramCacheMatchCriteria::NoCriteria, 1))
2143 })
2144 })
2145 .collect()
2146 }
2147
2148 fn match_slot(
2149 extracted: &ProgramCacheForTxBatch,
2150 program: &Pubkey,
2151 deployment_slot: Slot,
2152 working_slot: Slot,
2153 ) -> bool {
2154 assert_eq!(extracted.slot, working_slot);
2155 extracted
2156 .entries
2157 .get(program)
2158 .map(|entry| entry.deployment_slot == deployment_slot)
2159 .unwrap_or(false)
2160 }
2161
2162 fn match_missing(
2163 missing: &[(Pubkey, (ProgramCacheMatchCriteria, u64))],
2164 program: &Pubkey,
2165 expected_result: bool,
2166 ) -> bool {
2167 missing.iter().any(|(key, _)| key == program) == expected_result
2168 }
2169
2170 #[test]
2171 fn test_fork_extract_and_prune() {
2172 let mut cache = new_mock_cache();
2173
2174 let program1 = Pubkey::new_unique();
2189 cache.assign_program(program1, new_test_entry(0, 1));
2190 cache.assign_program(program1, new_test_entry(10, 11));
2191 cache.assign_program(program1, new_test_entry(20, 21));
2192
2193 let program2 = Pubkey::new_unique();
2194 cache.assign_program(program2, new_test_entry(5, 6));
2195 cache.assign_program(
2196 program2,
2197 new_test_entry(11, 11 + DELAY_VISIBILITY_SLOT_OFFSET),
2198 );
2199
2200 let program3 = Pubkey::new_unique();
2201 cache.assign_program(program3, new_test_entry(25, 26));
2202
2203 let program4 = Pubkey::new_unique();
2204 cache.assign_program(program4, new_test_entry(0, 1));
2205 cache.assign_program(program4, new_test_entry(5, 6));
2206 cache.assign_program(
2208 program4,
2209 new_test_entry(15, 15 + DELAY_VISIBILITY_SLOT_OFFSET),
2210 );
2211
2212 let mut missing =
2229 get_entries_to_load(&cache, 22, &[program1, program2, program3, program4]);
2230 assert!(match_missing(&missing, &program2, false));
2231 assert!(match_missing(&missing, &program3, false));
2232 let mut extracted = ProgramCacheForTxBatch::new(22, cache.environments.clone(), None, 0);
2233 cache.extract(&mut missing, &mut extracted, true);
2234 assert!(match_slot(&extracted, &program1, 20, 22));
2235 assert!(match_slot(&extracted, &program4, 0, 22));
2236
2237 let mut missing =
2239 get_entries_to_load(&cache, 15, &[program1, program2, program3, program4]);
2240 assert!(match_missing(&missing, &program3, false));
2241 let mut extracted = ProgramCacheForTxBatch::new(15, cache.environments.clone(), None, 0);
2242 cache.extract(&mut missing, &mut extracted, true);
2243 assert!(match_slot(&extracted, &program1, 0, 15));
2244 assert!(match_slot(&extracted, &program2, 11, 15));
2245 let tombstone = extracted
2248 .find(&program4)
2249 .expect("Failed to find the tombstone");
2250 assert_matches!(tombstone.program, ProgramCacheEntryType::DelayVisibility);
2251 assert_eq!(tombstone.deployment_slot, 15);
2252
2253 let mut missing =
2255 get_entries_to_load(&cache, 18, &[program1, program2, program3, program4]);
2256 assert!(match_missing(&missing, &program3, false));
2257 let mut extracted = ProgramCacheForTxBatch::new(18, cache.environments.clone(), None, 0);
2258 cache.extract(&mut missing, &mut extracted, true);
2259 assert!(match_slot(&extracted, &program1, 0, 18));
2260 assert!(match_slot(&extracted, &program2, 11, 18));
2261 assert!(match_slot(&extracted, &program4, 15, 18));
2263
2264 let mut missing =
2266 get_entries_to_load(&cache, 23, &[program1, program2, program3, program4]);
2267 assert!(match_missing(&missing, &program3, false));
2268 let mut extracted = ProgramCacheForTxBatch::new(23, cache.environments.clone(), None, 0);
2269 cache.extract(&mut missing, &mut extracted, true);
2270 assert!(match_slot(&extracted, &program1, 0, 23));
2271 assert!(match_slot(&extracted, &program2, 11, 23));
2272 assert!(match_slot(&extracted, &program4, 15, 23));
2274
2275 let mut missing =
2277 get_entries_to_load(&cache, 11, &[program1, program2, program3, program4]);
2278 assert!(match_missing(&missing, &program3, false));
2279 let mut extracted = ProgramCacheForTxBatch::new(11, cache.environments.clone(), None, 0);
2280 cache.extract(&mut missing, &mut extracted, true);
2281 assert!(match_slot(&extracted, &program1, 0, 11));
2282 let tombstone = extracted
2284 .find(&program2)
2285 .expect("Failed to find the tombstone");
2286 assert_matches!(tombstone.program, ProgramCacheEntryType::DelayVisibility);
2287 assert_eq!(tombstone.deployment_slot, 11);
2288 assert!(match_slot(&extracted, &program4, 5, 11));
2289
2290 cache.prune(5, 0);
2291
2292 let mut missing =
2309 get_entries_to_load(&cache, 21, &[program1, program2, program3, program4]);
2310 assert!(match_missing(&missing, &program3, false));
2311 let mut extracted = ProgramCacheForTxBatch::new(21, cache.environments.clone(), None, 0);
2312 cache.extract(&mut missing, &mut extracted, true);
2313 assert!(match_slot(&extracted, &program1, 0, 21));
2315 assert!(match_slot(&extracted, &program2, 11, 21));
2316 assert!(match_slot(&extracted, &program4, 15, 21));
2317
2318 let mut missing =
2320 get_entries_to_load(&cache, 27, &[program1, program2, program3, program4]);
2321 let mut extracted = ProgramCacheForTxBatch::new(27, cache.environments.clone(), None, 0);
2322 cache.extract(&mut missing, &mut extracted, true);
2323 assert!(match_slot(&extracted, &program1, 0, 27));
2324 assert!(match_slot(&extracted, &program2, 11, 27));
2325 assert!(match_slot(&extracted, &program3, 25, 27));
2326 assert!(match_slot(&extracted, &program4, 5, 27));
2327
2328 cache.prune(15, 0);
2329
2330 let mut missing =
2347 get_entries_to_load(&cache, 23, &[program1, program2, program3, program4]);
2348 assert!(match_missing(&missing, &program3, false));
2349 let mut extracted = ProgramCacheForTxBatch::new(23, cache.environments.clone(), None, 0);
2350 cache.extract(&mut missing, &mut extracted, true);
2351 assert!(match_slot(&extracted, &program1, 0, 23));
2352 assert!(match_slot(&extracted, &program2, 11, 23));
2353 assert!(match_slot(&extracted, &program4, 15, 23));
2354 }
2355
2356 #[test]
2357 fn test_extract_using_deployment_slot() {
2358 let mut cache = new_mock_cache();
2359
2360 let program1 = Pubkey::new_unique();
2375 cache.assign_program(program1, new_test_entry(0, 1));
2376 cache.assign_program(program1, new_test_entry(20, 21));
2377
2378 let program2 = Pubkey::new_unique();
2379 cache.assign_program(program2, new_test_entry(5, 6));
2380 cache.assign_program(program2, new_test_entry(11, 12));
2381
2382 let program3 = Pubkey::new_unique();
2383 cache.assign_program(program3, new_test_entry(25, 26));
2384
2385 let mut missing = get_entries_to_load(&cache, 12, &[program1, program2, program3]);
2387 assert!(match_missing(&missing, &program3, false));
2388 let mut extracted = ProgramCacheForTxBatch::new(12, cache.environments.clone(), None, 0);
2389 cache.extract(&mut missing, &mut extracted, true);
2390 assert!(match_slot(&extracted, &program1, 0, 12));
2391 assert!(match_slot(&extracted, &program2, 11, 12));
2392
2393 let mut missing = get_entries_to_load(&cache, 12, &[program1, program2, program3]);
2395 missing.get_mut(0).unwrap().1 .0 = ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(5);
2396 missing.get_mut(1).unwrap().1 .0 = ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(5);
2397 assert!(match_missing(&missing, &program3, false));
2398 let mut extracted = ProgramCacheForTxBatch::new(12, cache.environments.clone(), None, 0);
2399 cache.extract(&mut missing, &mut extracted, true);
2400 assert!(match_missing(&missing, &program1, true));
2401 assert!(match_slot(&extracted, &program2, 11, 12));
2402 }
2403
2404 #[test]
2405 fn test_extract_unloaded() {
2406 let mut cache = new_mock_cache();
2407
2408 let program1 = Pubkey::new_unique();
2424 cache.assign_program(program1, new_test_entry(0, 1));
2425 cache.assign_program(program1, new_test_entry(20, 21));
2426
2427 let program2 = Pubkey::new_unique();
2428 cache.assign_program(program2, new_test_entry(5, 6));
2429 cache.assign_program(program2, new_test_entry(11, 12));
2430
2431 let program3 = Pubkey::new_unique();
2432 let _ = insert_unloaded_entry(&mut cache, program3, 25);
2434
2435 cache.assign_program(
2439 program3,
2440 Arc::new(
2441 new_test_entry(20, 21)
2442 .to_unloaded()
2443 .expect("Failed to create unloaded program"),
2444 ),
2445 );
2446
2447 let mut missing = get_entries_to_load(&cache, 19, &[program1, program2, program3]);
2449 assert!(match_missing(&missing, &program3, false));
2450 let mut extracted = ProgramCacheForTxBatch::new(19, cache.environments.clone(), None, 0);
2451 cache.extract(&mut missing, &mut extracted, true);
2452 assert!(match_slot(&extracted, &program1, 0, 19));
2453 assert!(match_slot(&extracted, &program2, 11, 19));
2454
2455 let mut missing = get_entries_to_load(&cache, 27, &[program1, program2, program3]);
2457 let mut extracted = ProgramCacheForTxBatch::new(27, cache.environments.clone(), None, 0);
2458 cache.extract(&mut missing, &mut extracted, true);
2459 assert!(match_slot(&extracted, &program1, 0, 27));
2460 assert!(match_slot(&extracted, &program2, 11, 27));
2461 assert!(match_missing(&missing, &program3, true));
2462
2463 let mut missing = get_entries_to_load(&cache, 22, &[program1, program2, program3]);
2465 assert!(match_missing(&missing, &program2, false));
2466 let mut extracted = ProgramCacheForTxBatch::new(22, cache.environments.clone(), None, 0);
2467 cache.extract(&mut missing, &mut extracted, true);
2468 assert!(match_slot(&extracted, &program1, 20, 22));
2469 assert!(match_missing(&missing, &program3, true));
2470 }
2471
2472 #[test]
2473 fn test_extract_nonexistent() {
2474 let cache = new_mock_cache();
2475
2476 let program1 = Pubkey::new_unique();
2477 let mut missing = vec![(program1, (ProgramCacheMatchCriteria::NoCriteria, 1))];
2478 let mut extracted = ProgramCacheForTxBatch::new(0, cache.environments.clone(), None, 0);
2479 cache.extract(&mut missing, &mut extracted, true);
2480 assert!(match_missing(&missing, &program1, true));
2481 }
2482
2483 #[test]
2484 fn test_unloaded() {
2485 let mut cache = new_mock_cache();
2486 for program_cache_entry_type in [
2487 ProgramCacheEntryType::FailedVerification(
2488 cache.environments.program_runtime_v1.clone(),
2489 ),
2490 ProgramCacheEntryType::Closed,
2491 ProgramCacheEntryType::Unloaded(cache.environments.program_runtime_v1.clone()),
2492 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
2493 ] {
2494 let entry = Arc::new(ProgramCacheEntry {
2495 program: program_cache_entry_type,
2496 account_owner: ProgramCacheEntryOwner::LoaderV2,
2497 account_size: 0,
2498 deployment_slot: 0,
2499 effective_slot: 0,
2500 tx_usage_counter: AtomicU64::default(),
2501 ix_usage_counter: AtomicU64::default(),
2502 latest_access_slot: AtomicU64::default(),
2503 });
2504 assert!(entry.to_unloaded().is_none());
2505
2506 let program_id = Pubkey::new_unique();
2508 cache.assign_program(program_id, entry.clone());
2509 cache.unload_program_entry(&program_id, &entry);
2510 assert_eq!(cache.get_slot_versions_for_tests(&program_id).len(), 1);
2511 assert!(cache.stats.evictions.is_empty());
2512 }
2513
2514 let entry = new_test_entry_with_usage(1, 2, AtomicU64::new(3));
2515 let unloaded_entry = entry.to_unloaded().unwrap();
2516 assert_eq!(unloaded_entry.deployment_slot, 1);
2517 assert_eq!(unloaded_entry.effective_slot, 2);
2518 assert_eq!(unloaded_entry.latest_access_slot.load(Ordering::Relaxed), 1);
2519 assert_eq!(unloaded_entry.tx_usage_counter.load(Ordering::Relaxed), 3);
2520
2521 let program_id = Pubkey::new_unique();
2523 cache.assign_program(program_id, entry.clone());
2524 cache.unload_program_entry(&program_id, &entry);
2525 assert!(cache.stats.evictions.contains_key(&program_id));
2526 }
2527
2528 #[test]
2529 fn test_fork_prune_find_first_ancestor() {
2530 let mut cache = new_mock_cache();
2531
2532 let program1 = Pubkey::new_unique();
2543 cache.assign_program(program1, new_test_entry(0, 1));
2544 cache.assign_program(program1, new_test_entry(5, 6));
2545
2546 cache.prune(10, 0);
2547
2548 let mut missing = get_entries_to_load(&cache, 20, &[program1]);
2549 let mut extracted = ProgramCacheForTxBatch::new(20, cache.environments.clone(), None, 0);
2550 cache.extract(&mut missing, &mut extracted, true);
2551
2552 assert_eq!(
2554 extracted
2555 .find(&program1)
2556 .expect("Did not find the program")
2557 .deployment_slot,
2558 0
2559 );
2560 }
2561
2562 #[test]
2563 fn test_prune_by_deployment_slot() {
2564 let mut cache = new_mock_cache();
2565
2566 let program1 = Pubkey::new_unique();
2577 cache.assign_program(program1, new_test_entry(0, 1));
2578 cache.assign_program(program1, new_test_entry(5, 6));
2579
2580 let program2 = Pubkey::new_unique();
2581 cache.assign_program(program2, new_test_entry(10, 11));
2582
2583 let mut missing = get_entries_to_load(&cache, 20, &[program1, program2]);
2584 let mut extracted = ProgramCacheForTxBatch::new(20, cache.environments.clone(), None, 0);
2585 cache.extract(&mut missing, &mut extracted, true);
2586 assert!(match_slot(&extracted, &program1, 0, 20));
2587 assert!(match_slot(&extracted, &program2, 10, 20));
2588
2589 let mut missing = get_entries_to_load(&cache, 6, &[program1, program2]);
2590 assert!(match_missing(&missing, &program2, false));
2591 let mut extracted = ProgramCacheForTxBatch::new(6, cache.environments.clone(), None, 0);
2592 cache.extract(&mut missing, &mut extracted, true);
2593 assert!(match_slot(&extracted, &program1, 5, 6));
2594
2595 cache.prune_by_deployment_slot(5);
2598
2599 let mut missing = get_entries_to_load(&cache, 20, &[program1, program2]);
2600 let mut extracted = ProgramCacheForTxBatch::new(20, cache.environments.clone(), None, 0);
2601 cache.extract(&mut missing, &mut extracted, true);
2602 assert!(match_slot(&extracted, &program1, 0, 20));
2603 assert!(match_slot(&extracted, &program2, 10, 20));
2604
2605 let mut missing = get_entries_to_load(&cache, 6, &[program1, program2]);
2606 assert!(match_missing(&missing, &program2, false));
2607 let mut extracted = ProgramCacheForTxBatch::new(6, cache.environments.clone(), None, 0);
2608 cache.extract(&mut missing, &mut extracted, true);
2609 assert!(match_slot(&extracted, &program1, 0, 6));
2610
2611 cache.prune_by_deployment_slot(10);
2614
2615 let mut missing = get_entries_to_load(&cache, 20, &[program1, program2]);
2616 assert!(match_missing(&missing, &program2, false));
2617 let mut extracted = ProgramCacheForTxBatch::new(20, cache.environments.clone(), None, 0);
2618 cache.extract(&mut missing, &mut extracted, true);
2619 assert!(match_slot(&extracted, &program1, 0, 20));
2620 }
2621
2622 #[test]
2623 fn test_usable_entries_for_slot() {
2624 new_mock_cache();
2625 let tombstone = Arc::new(ProgramCacheEntry::new_tombstone(
2626 0,
2627 ProgramCacheEntryOwner::LoaderV2,
2628 ProgramCacheEntryType::Closed,
2629 ));
2630
2631 assert!(ProgramCache::matches_criteria(
2632 &tombstone,
2633 &ProgramCacheMatchCriteria::NoCriteria
2634 ));
2635
2636 assert!(ProgramCache::matches_criteria(
2637 &tombstone,
2638 &ProgramCacheMatchCriteria::Tombstone
2639 ));
2640
2641 assert!(ProgramCache::matches_criteria(
2642 &tombstone,
2643 &ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(0)
2644 ));
2645
2646 assert!(!ProgramCache::matches_criteria(
2647 &tombstone,
2648 &ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(1)
2649 ));
2650
2651 let program = new_test_entry(0, 1);
2652
2653 assert!(ProgramCache::matches_criteria(
2654 &program,
2655 &ProgramCacheMatchCriteria::NoCriteria
2656 ));
2657
2658 assert!(!ProgramCache::matches_criteria(
2659 &program,
2660 &ProgramCacheMatchCriteria::Tombstone
2661 ));
2662
2663 assert!(ProgramCache::matches_criteria(
2664 &program,
2665 &ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(0)
2666 ));
2667
2668 assert!(!ProgramCache::matches_criteria(
2669 &program,
2670 &ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(1)
2671 ));
2672
2673 let program = Arc::new(new_test_entry_with_usage(0, 1, AtomicU64::default()));
2674
2675 assert!(ProgramCache::matches_criteria(
2676 &program,
2677 &ProgramCacheMatchCriteria::NoCriteria
2678 ));
2679
2680 assert!(!ProgramCache::matches_criteria(
2681 &program,
2682 &ProgramCacheMatchCriteria::Tombstone
2683 ));
2684
2685 assert!(ProgramCache::matches_criteria(
2686 &program,
2687 &ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(0)
2688 ));
2689
2690 assert!(!ProgramCache::matches_criteria(
2691 &program,
2692 &ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(1)
2693 ));
2694 }
2695}