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::Slot;
14use rialo_s_pubkey::Pubkey;
15use rialo_s_sdk_ids::native_loader;
16#[cfg(feature = "metrics")]
17use rialo_s_timings::ExecuteDetailsTimings;
18use rialo_s_type_overrides::{
19 rand::{thread_rng, Rng},
20 sync::{
21 atomic::{AtomicU64, Ordering},
22 Arc, Condvar, Mutex,
23 },
24 thread,
25};
26
27use crate::invoke_context::BuiltinFunctionWithContext;
28
29pub const MAX_LOADED_ENTRY_COUNT: usize = 512;
30pub const DELAY_VISIBILITY_SLOT_OFFSET: Slot = 1;
31
32#[derive(Copy, Clone, Debug, PartialEq)]
34pub enum BlockRelation {
35 Ancestor,
37 Equal,
39 Descendant,
41 Unrelated,
43 Unknown,
45}
46
47pub trait ForkGraph: Copy + Clone + Send + Sync {
49 fn relationship(&self, a: Slot, b: Slot) -> BlockRelation;
51}
52
53#[derive(Default, Clone, Copy, PartialEq, Eq, Debug)]
55pub enum ProgramCacheEntryOwner {
56 #[default]
57 NativeLoader,
58 RiscVLoader,
59}
60
61impl TryFrom<&Pubkey> for ProgramCacheEntryOwner {
62 type Error = ();
63 fn try_from(loader_key: &Pubkey) -> Result<Self, ()> {
64 if native_loader::check_id(loader_key) {
65 Ok(ProgramCacheEntryOwner::NativeLoader)
66 } else if rialo_risc_v_loader_interface::check_id(loader_key) {
67 Ok(ProgramCacheEntryOwner::RiscVLoader)
68 } else {
69 Err(())
70 }
71 }
72}
73
74impl From<ProgramCacheEntryOwner> for Pubkey {
75 fn from(program_cache_entry_owner: ProgramCacheEntryOwner) -> Self {
76 match program_cache_entry_owner {
77 ProgramCacheEntryOwner::NativeLoader => native_loader::id(),
78 ProgramCacheEntryOwner::RiscVLoader => rialo_risc_v_loader_interface::id(),
79 }
80 }
81}
82
83pub enum ExecutableKind {
113 RiscV(Vec<u8>),
115}
116
117#[derive(Default)]
119pub enum ProgramCacheEntryType {
120 FailedVerification,
122 #[default]
126 Closed,
127 DelayVisibility,
129 Unloaded,
133 Loaded(ExecutableKind),
135 Builtin(BuiltinFunctionWithContext),
137}
138
139impl Debug for ProgramCacheEntryType {
140 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
141 match self {
142 ProgramCacheEntryType::FailedVerification => {
143 write!(f, "ProgramCacheEntryType::FailedVerification")
144 }
145 ProgramCacheEntryType::Closed => write!(f, "ProgramCacheEntryType::Closed"),
146 ProgramCacheEntryType::DelayVisibility => {
147 write!(f, "ProgramCacheEntryType::DelayVisibility")
148 }
149 ProgramCacheEntryType::Unloaded => write!(f, "ProgramCacheEntryType::Unloaded"),
150 ProgramCacheEntryType::Loaded(_) => write!(f, "ProgramCacheEntryType::Loaded"),
151 ProgramCacheEntryType::Builtin(_) => write!(f, "ProgramCacheEntryType::Builtin"),
152 }
153 }
154}
155
156#[derive(Debug, Default)]
160pub struct ProgramCacheEntry {
161 pub program: ProgramCacheEntryType,
163 pub account_owner: ProgramCacheEntryOwner,
165 pub account_size: usize,
167 pub deployment_slot: Slot,
169 pub effective_slot: Slot,
171 pub tx_usage_counter: AtomicU64,
173 pub ix_usage_counter: AtomicU64,
175 pub latest_access_slot: AtomicU64,
177}
178
179#[derive(Debug, Default)]
181pub struct ProgramCacheStats {
182 pub hits: AtomicU64,
184 pub misses: AtomicU64,
186 #[allow(
195 clippy::disallowed_types,
196 reason = "private; reachable from outside this module only through total_evictions, and the one in-module iteration sorts explicitly by count for a trace-level log line"
197 )]
198 evictions: HashMap<Pubkey, u64>,
199 pub reloads: AtomicU64,
201 pub insertions: AtomicU64,
203 pub lost_insertions: AtomicU64,
205 pub replacements: AtomicU64,
207 pub one_hit_wonders: AtomicU64,
209 pub empty_entries: AtomicU64,
211 pub water_level: AtomicU64,
213}
214
215impl ProgramCacheStats {
216 pub fn reset(&mut self) {
217 *self = ProgramCacheStats::default();
218 }
219 pub fn total_evictions(&self) -> u64 {
225 self.evictions.values().sum()
226 }
227 pub fn log(&self) {
228 let hits = self.hits.load(Ordering::Relaxed);
229 let misses = self.misses.load(Ordering::Relaxed);
230 let evictions = self.total_evictions();
231 let reloads = self.reloads.load(Ordering::Relaxed);
232 let insertions = self.insertions.load(Ordering::Relaxed);
233 let lost_insertions = self.lost_insertions.load(Ordering::Relaxed);
234 let replacements = self.replacements.load(Ordering::Relaxed);
235 let one_hit_wonders = self.one_hit_wonders.load(Ordering::Relaxed);
236 let empty_entries = self.empty_entries.load(Ordering::Relaxed);
237 let water_level = self.water_level.load(Ordering::Relaxed);
238 debug!(
239 "Loaded Programs Cache Stats -- Hits: {}, Misses: {}, Evictions: {}, Reloads: {}, Insertions: {}, Lost-Insertions: {}, Replacements: {}, One-Hit-Wonders: {}, Empty: {}, Water-Level: {}",
240 hits, misses, evictions, reloads, insertions, lost_insertions, replacements, one_hit_wonders, empty_entries, water_level
241 );
242 if log_enabled!(log::Level::Trace) && !self.evictions.is_empty() {
243 let evictions = self
244 .sorted_eviction_report()
245 .into_iter()
246 .rev()
247 .map(|(program_id, evictions)| {
248 format!(" {:<44} {}", program_id.to_string(), evictions)
249 })
250 .collect::<Vec<_>>()
251 .join("\n");
252 trace!(
253 "Eviction Details:\n {:<44} {}\n{}",
254 "Program",
255 "Count",
256 evictions
257 );
258 }
259 }
260
261 fn sorted_eviction_report(&self) -> Vec<(Pubkey, u64)> {
265 let mut evictions: Vec<(Pubkey, u64)> =
266 self.evictions.iter().map(|(k, v)| (*k, *v)).collect();
267 evictions.sort_by_key(|e| e.1);
268 evictions
269 }
270}
271
272#[cfg(feature = "metrics")]
273#[derive(Debug, Default)]
275pub struct LoadProgramMetrics {
276 pub program_id: String,
278 pub register_syscalls_us: u64,
280}
281
282#[cfg(feature = "metrics")]
283impl LoadProgramMetrics {
284 pub fn submit_datapoint(&self, timings: &mut ExecuteDetailsTimings) {
285 timings.create_executor_register_syscalls_us += self.register_syscalls_us;
286 datapoint_trace!(
287 "create_executor_trace",
288 ("program_id", self.program_id, String),
289 ("register_syscalls_us", self.register_syscalls_us, i64),
290 );
291 }
292}
293
294impl PartialEq for ProgramCacheEntry {
295 fn eq(&self, other: &Self) -> bool {
296 self.effective_slot == other.effective_slot
297 && self.deployment_slot == other.deployment_slot
298 && self.is_tombstone() == other.is_tombstone()
299 }
300}
301
302impl ProgramCacheEntry {
303 pub fn new_risc_v(
305 loader_key: &Pubkey,
306 deployment_slot: Slot,
307 effective_slot: Slot,
308 elf_bytes: &[u8],
309 account_size: usize,
310 ) -> Result<Self, Box<dyn std::error::Error>> {
311 Self::new_internal_risc_v(
312 loader_key,
313 deployment_slot,
314 effective_slot,
315 elf_bytes,
316 account_size,
317 false, )
319 }
320
321 pub unsafe fn reload_risc_v(
330 loader_key: &Pubkey,
331 deployment_slot: Slot,
332 effective_slot: Slot,
333 elf_bytes: &[u8],
334 account_size: usize,
335 ) -> Result<Self, Box<dyn std::error::Error>> {
336 Self::new_internal_risc_v(
337 loader_key,
338 deployment_slot,
339 effective_slot,
340 elf_bytes,
341 account_size,
342 true, )
344 }
345
346 fn new_internal_risc_v(
347 loader_key: &Pubkey,
348 deployment_slot: Slot,
349 effective_slot: Slot,
350 elf_bytes: &[u8],
351 account_size: usize,
352 reloading: bool,
353 ) -> Result<Self, Box<dyn std::error::Error>> {
354 if !reloading {
362 const MAX_STACK_SIZE: u32 = 262144;
363
364 let program = Into::<ArcBytes>::into(elf_bytes);
365
366 let program_parts = ProgramParts::from_bytes(program.clone())
367 .map_err(|error| format!("failed to parse program: {error:?}"))?;
368
369 if !program_parts.is_64_bit {
370 return Err(String::from("program is not a 64-bit program").into());
371 }
372
373 if program_parts.stack_size > MAX_STACK_SIZE {
374 return Err(String::from("program stack is too large").into());
375 }
376
377 let blob = ProgramBlob::parse(program)
378 .map_err(|error| format!("failed to parse program into program blob: {error:?}"))?;
379
380 if !blob
381 .exports()
382 .any(|entry| entry.symbol().as_bytes() == b"entrypoint")
383 {
384 return Err(String::from("program doesn't have an entrypoint").into());
385 }
386 }
387
388 Ok(Self {
389 deployment_slot,
390 account_owner: ProgramCacheEntryOwner::try_from(loader_key).unwrap(),
391 account_size,
392 effective_slot,
393 tx_usage_counter: AtomicU64::new(0),
394 program: ProgramCacheEntryType::Loaded(ExecutableKind::RiscV(elf_bytes.to_vec())),
395 ix_usage_counter: AtomicU64::new(0),
396 latest_access_slot: AtomicU64::new(0),
397 })
398 }
399
400 pub fn to_unloaded(&self) -> Option<Self> {
401 match &self.program {
402 ProgramCacheEntryType::Loaded(_) => {}
403 ProgramCacheEntryType::FailedVerification
404 | ProgramCacheEntryType::Closed
405 | ProgramCacheEntryType::DelayVisibility
406 | ProgramCacheEntryType::Unloaded
407 | ProgramCacheEntryType::Builtin(_) => {
408 return None;
409 }
410 }
411 Some(Self {
412 program: ProgramCacheEntryType::Unloaded,
413 account_owner: self.account_owner,
414 account_size: self.account_size,
415 deployment_slot: self.deployment_slot,
416 effective_slot: self.effective_slot,
417 tx_usage_counter: AtomicU64::new(self.tx_usage_counter.load(Ordering::Relaxed)),
418 ix_usage_counter: AtomicU64::new(self.ix_usage_counter.load(Ordering::Relaxed)),
419 latest_access_slot: AtomicU64::new(self.latest_access_slot.load(Ordering::Relaxed)),
420 })
421 }
422
423 pub fn new_builtin(
425 deployment_slot: Slot,
426 account_size: usize,
427 builtin_function: BuiltinFunctionWithContext,
428 ) -> Self {
429 Self {
430 deployment_slot,
431 account_owner: ProgramCacheEntryOwner::NativeLoader,
432 account_size,
433 effective_slot: deployment_slot,
434 tx_usage_counter: AtomicU64::new(0),
435 program: ProgramCacheEntryType::Builtin(builtin_function),
436 ix_usage_counter: AtomicU64::new(0),
437 latest_access_slot: AtomicU64::new(0),
438 }
439 }
440
441 pub fn new_tombstone(
442 slot: Slot,
443 account_owner: ProgramCacheEntryOwner,
444 reason: ProgramCacheEntryType,
445 ) -> Self {
446 let tombstone = Self {
447 program: reason,
448 account_owner,
449 account_size: 0,
450 deployment_slot: slot,
451 effective_slot: slot,
452 tx_usage_counter: AtomicU64::default(),
453 ix_usage_counter: AtomicU64::default(),
454 latest_access_slot: AtomicU64::new(0),
455 };
456 debug_assert!(tombstone.is_tombstone());
457 tombstone
458 }
459
460 pub fn is_tombstone(&self) -> bool {
461 matches!(
462 self.program,
463 ProgramCacheEntryType::FailedVerification
464 | ProgramCacheEntryType::Closed
465 | ProgramCacheEntryType::DelayVisibility
466 )
467 }
468
469 fn is_implicit_delay_visibility_tombstone(&self, slot: Slot) -> bool {
470 !matches!(self.program, ProgramCacheEntryType::Builtin(_))
471 && self.effective_slot.saturating_sub(self.deployment_slot)
472 == DELAY_VISIBILITY_SLOT_OFFSET
473 && slot >= self.deployment_slot
474 && slot < self.effective_slot
475 }
476
477 pub fn update_access_slot(&self, slot: Slot) {
478 let _ = self.latest_access_slot.fetch_max(slot, Ordering::Relaxed);
479 }
480
481 pub fn decayed_usage_counter(&self, now: Slot) -> u64 {
482 let last_access = self.latest_access_slot.load(Ordering::Relaxed);
483 let decaying_for = std::cmp::min(63, now.saturating_sub(last_access));
485 self.tx_usage_counter.load(Ordering::Relaxed) >> decaying_for
486 }
487
488 pub fn account_owner(&self) -> Pubkey {
489 self.account_owner.into()
490 }
491}
492
493#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
494pub struct LoadingTaskCookie(u64);
495
496impl LoadingTaskCookie {
497 fn new() -> Self {
498 Self(0)
499 }
500
501 fn update(&mut self) {
502 let LoadingTaskCookie(cookie) = self;
503 *cookie = cookie.wrapping_add(1);
504 }
505}
506
507#[derive(Debug, Default)]
509pub struct LoadingTaskWaiter {
510 cookie: Mutex<LoadingTaskCookie>,
511 cond: Condvar,
512}
513
514impl LoadingTaskWaiter {
515 pub fn new() -> Self {
516 Self {
517 cookie: Mutex::new(LoadingTaskCookie::new()),
518 cond: Condvar::new(),
519 }
520 }
521
522 pub fn cookie(&self) -> LoadingTaskCookie {
523 *self.cookie.lock().unwrap()
524 }
525
526 pub fn notify(&self) {
527 let mut cookie = self.cookie.lock().unwrap();
528 cookie.update();
529 self.cond.notify_all();
530 }
531
532 pub fn wait(&self, cookie: LoadingTaskCookie) -> LoadingTaskCookie {
533 let cookie_guard = self.cookie.lock().unwrap();
534 *self
535 .cond
536 .wait_while(cookie_guard, |current_cookie| *current_cookie == cookie)
537 .unwrap()
538 }
539}
540
541#[derive(Debug)]
542enum IndexImplementation {
543 V1 {
545 #[allow(
565 clippy::disallowed_types,
566 reason = "keyed lookup during batch program loading; iterated only for local cache eviction and diagnostics, never for committed output"
567 )]
568 entries: HashMap<Pubkey, Vec<Arc<ProgramCacheEntry>>>,
569 #[allow(
578 clippy::disallowed_types,
579 reason = "cooperative-loading coordination keyed by program id; the only iteration is by this enum's derived Debug, which does not depend on order"
580 )]
581 loading_entries: Mutex<HashMap<Pubkey, (Slot, thread::ThreadId)>>,
582 },
583}
584
585pub struct ProgramCache {
601 index: IndexImplementation,
603 pub stats: ProgramCacheStats,
605 pub loading_task_waiter: Arc<LoadingTaskWaiter>,
607}
608
609impl Debug for ProgramCache {
610 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
611 f.debug_struct("ProgramCache")
612 .field("stats", &self.stats)
613 .field("index", &self.index)
614 .finish()
615 }
616}
617
618pub struct ProgramCacheForTx<'a> {
631 #[allow(
634 clippy::disallowed_types,
635 reason = "looked up by key only, see struct doc"
636 )]
637 entries: &'a HashMap<Pubkey, Arc<ProgramCacheEntry>>,
638 #[allow(
640 clippy::disallowed_types,
641 reason = "inserted/drained by key only, see struct doc"
642 )]
643 modified_entries: HashMap<Pubkey, Arc<ProgramCacheEntry>>,
644 slot: Slot,
645 pub hit_max_limit: bool,
646 pub loaded_missing: bool,
647 pub merged_modified: bool,
648}
649
650impl<'a> ProgramCacheForTx<'a> {
651 #[allow(
652 clippy::disallowed_types,
653 reason = "constructs modified_entries, see struct doc"
654 )]
655 pub fn from_cache(cache: &'a ProgramCacheForTxBatch) -> Self {
656 Self {
657 entries: &cache.entries,
658 modified_entries: HashMap::new(),
659 slot: cache.slot,
660 hit_max_limit: cache.hit_max_limit,
661 loaded_missing: cache.loaded_missing,
662 merged_modified: cache.merged_modified,
663 }
664 }
665
666 pub fn store_modified_entry(&mut self, key: Pubkey, entry: Arc<ProgramCacheEntry>) {
669 self.modified_entries.insert(key, entry);
670 }
671
672 #[allow(
675 clippy::disallowed_types,
676 reason = "return type shared with svm-execution, see struct doc"
677 )]
678 pub fn drain_modified_entries(&mut self) -> HashMap<Pubkey, Arc<ProgramCacheEntry>> {
679 std::mem::take(&mut self.modified_entries)
680 }
681
682 pub fn find(&self, key: &Pubkey) -> Option<Arc<ProgramCacheEntry>> {
683 self.modified_entries
687 .get(key)
688 .or(self.entries.get(key))
689 .map(|entry| {
690 if entry.is_implicit_delay_visibility_tombstone(self.slot) {
691 Arc::new(ProgramCacheEntry::new_tombstone(
695 entry.deployment_slot,
696 entry.account_owner,
697 ProgramCacheEntryType::DelayVisibility,
698 ))
699 } else {
700 entry.clone()
701 }
702 })
703 }
704
705 pub fn slot(&self) -> Slot {
706 self.slot
707 }
708
709 pub fn set_slot_for_tests(&mut self, slot: Slot) {
710 self.slot = slot;
711 }
712
713 pub fn is_empty(&self) -> bool {
714 self.entries.is_empty()
715 }
716}
717
718#[derive(Clone, Debug, Default)]
724pub struct ProgramCacheForTxBatch {
725 #[allow(
728 clippy::disallowed_types,
729 reason = "looked up by key only; the only iteration is by this struct's derived Clone and Debug, neither of which depends on order"
730 )]
731 entries: HashMap<Pubkey, Arc<ProgramCacheEntry>>,
732 #[allow(
734 clippy::disallowed_types,
735 reason = "inserted/drained by key only; the only iteration is by this struct's derived Clone and Debug, neither of which depends on order"
736 )]
737 modified_entries: HashMap<Pubkey, Arc<ProgramCacheEntry>>,
738 slot: Slot,
739 pub hit_max_limit: bool,
740 pub loaded_missing: bool,
741 pub merged_modified: bool,
742}
743
744impl ProgramCacheForTxBatch {
745 #[allow(
746 clippy::disallowed_types,
747 reason = "constructs the fields declared above"
748 )]
749 pub fn new(slot: Slot) -> Self {
750 Self {
751 entries: HashMap::new(),
752 modified_entries: HashMap::new(),
753 slot,
754 hit_max_limit: false,
755 loaded_missing: false,
756 merged_modified: false,
757 }
758 }
759
760 pub fn replenish(
766 &mut self,
767 key: Pubkey,
768 entry: Arc<ProgramCacheEntry>,
769 ) -> (bool, Arc<ProgramCacheEntry>) {
770 (self.entries.insert(key, entry.clone()).is_some(), entry)
771 }
772
773 pub fn store_modified_entry(&mut self, key: Pubkey, entry: Arc<ProgramCacheEntry>) {
776 self.modified_entries.insert(key, entry);
777 }
778
779 #[allow(
782 clippy::disallowed_types,
783 reason = "return type shared with svm-execution's TransactionExecutionResult"
784 )]
785 pub fn drain_modified_entries(&mut self) -> HashMap<Pubkey, Arc<ProgramCacheEntry>> {
786 std::mem::take(&mut self.modified_entries)
787 }
788
789 pub fn find(&self, key: &Pubkey) -> Option<Arc<ProgramCacheEntry>> {
790 self.modified_entries
794 .get(key)
795 .or(self.entries.get(key))
796 .map(|entry| {
797 if entry.is_implicit_delay_visibility_tombstone(self.slot) {
798 Arc::new(ProgramCacheEntry::new_tombstone(
802 entry.deployment_slot,
803 entry.account_owner,
804 ProgramCacheEntryType::DelayVisibility,
805 ))
806 } else {
807 entry.clone()
808 }
809 })
810 }
811
812 pub fn slot(&self) -> Slot {
813 self.slot
814 }
815
816 #[cfg(any(test, feature = "dev-context-only-utils"))]
817 pub fn set_slot_for_tests(&mut self, slot: Slot) {
818 self.slot = slot;
819 }
820
821 #[allow(
824 clippy::disallowed_types,
825 reason = "parameter type shared with svm-execution's TransactionExecutionResult"
826 )]
827 pub fn merge(&mut self, modified_entries: &HashMap<Pubkey, Arc<ProgramCacheEntry>>) {
828 modified_entries.iter().for_each(|(key, entry)| {
829 self.merged_modified = true;
830 self.replenish(*key, entry.clone());
831 })
832 }
833
834 pub fn is_empty(&self) -> bool {
835 self.entries.is_empty()
836 }
837}
838
839pub enum ProgramCacheMatchCriteria {
840 DeployedOnOrAfterSlot(Slot),
841 Tombstone,
842 NoCriteria,
843}
844
845impl Default for ProgramCache {
846 fn default() -> Self {
847 Self::new()
848 }
849}
850
851impl ProgramCache {
852 #[allow(
853 clippy::disallowed_types,
854 reason = "constructs the index fields declared above, both retained as HashMap"
855 )]
856 pub fn new() -> Self {
857 Self {
858 index: IndexImplementation::V1 {
859 entries: HashMap::new(),
860 loading_entries: Mutex::new(HashMap::new()),
861 },
862 stats: ProgramCacheStats::default(),
863 loading_task_waiter: Arc::new(LoadingTaskWaiter::default()),
864 }
865 }
866
867 pub fn assign_program(&mut self, key: Pubkey, entry: Arc<ProgramCacheEntry>) -> bool {
870 debug_assert!(!matches!(
871 &entry.program,
872 ProgramCacheEntryType::DelayVisibility
873 ));
874 match &mut self.index {
875 IndexImplementation::V1 { entries, .. } => {
876 let slot_versions = &mut entries.entry(key).or_default();
877 match slot_versions.binary_search_by(|at| {
878 at.effective_slot
879 .cmp(&entry.effective_slot)
880 .then(at.deployment_slot.cmp(&entry.deployment_slot))
881 }) {
882 Ok(index) => {
883 let existing = slot_versions.get_mut(index).unwrap();
884 match (&existing.program, &entry.program) {
885 (
886 ProgramCacheEntryType::Builtin(_),
887 ProgramCacheEntryType::Builtin(_),
888 )
889 | (ProgramCacheEntryType::Unloaded, ProgramCacheEntryType::Loaded(_)) =>
890 {}
891 _ => {
892 error!("ProgramCache::assign_program() failed key={:?} existing={:?} entry={:?}", key, slot_versions, entry);
894 debug_assert!(false, "Unexpected replacement of an entry");
895 self.stats.replacements.fetch_add(1, Ordering::Relaxed);
896 return true;
897 }
898 }
899 entry.tx_usage_counter.fetch_add(
901 existing.tx_usage_counter.load(Ordering::Relaxed),
902 Ordering::Relaxed,
903 );
904 entry.ix_usage_counter.fetch_add(
905 existing.ix_usage_counter.load(Ordering::Relaxed),
906 Ordering::Relaxed,
907 );
908 *existing = Arc::clone(&entry);
909 self.stats.reloads.fetch_add(1, Ordering::Relaxed);
910 }
911 Err(index) => {
912 self.stats.insertions.fetch_add(1, Ordering::Relaxed);
913 slot_versions.insert(index, Arc::clone(&entry));
914 }
915 }
916 }
917 }
918 false
919 }
920
921 fn matches_criteria(
922 program: &Arc<ProgramCacheEntry>,
923 criteria: &ProgramCacheMatchCriteria,
924 ) -> bool {
925 match criteria {
926 ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(slot) => {
927 program.deployment_slot >= *slot
928 }
929 ProgramCacheMatchCriteria::Tombstone => program.is_tombstone(),
930 ProgramCacheMatchCriteria::NoCriteria => true,
931 }
932 }
933
934 pub fn extract(
937 &self,
938 search_for: &mut Vec<(Pubkey, (ProgramCacheMatchCriteria, u64))>,
939 loaded_programs_for_tx_batch: &mut ProgramCacheForTxBatch,
940 is_first_round: bool,
941 ) -> Option<(Pubkey, u64)> {
942 let mut cooperative_loading_task = None;
943 match &self.index {
944 IndexImplementation::V1 {
945 entries,
946 loading_entries,
947 } => {
948 search_for.retain(|(key, (match_criteria, usage_count))| {
949 if let Some(second_level) = entries.get(key) {
950 for entry in second_level.iter().rev() {
951 if entry.deployment_slot <= loaded_programs_for_tx_batch.slot {
952 let entry_to_return = if loaded_programs_for_tx_batch.slot
953 >= entry.effective_slot
954 {
955 if !Self::matches_criteria(entry, match_criteria) {
956 break;
957 }
958 if matches!(&entry.program, ProgramCacheEntryType::Unloaded) {
959 break;
960 }
961 entry.clone()
962 } else if entry.is_implicit_delay_visibility_tombstone(
963 loaded_programs_for_tx_batch.slot,
964 ) {
965 Arc::new(ProgramCacheEntry::new_tombstone(
969 entry.deployment_slot,
970 entry.account_owner,
971 ProgramCacheEntryType::DelayVisibility,
972 ))
973 } else {
974 continue;
975 };
976 entry_to_return
977 .update_access_slot(loaded_programs_for_tx_batch.slot);
978 entry_to_return
979 .tx_usage_counter
980 .fetch_add(*usage_count, Ordering::Relaxed);
981 loaded_programs_for_tx_batch
982 .entries
983 .insert(*key, entry_to_return);
984 return false;
985 }
986 }
987 }
988 if cooperative_loading_task.is_none() {
989 let mut loading_entries = loading_entries.lock().unwrap();
990 let entry = loading_entries.entry(*key);
991 if let Entry::Vacant(entry) = entry {
992 entry.insert((
993 loaded_programs_for_tx_batch.slot,
994 thread::current().id(),
995 ));
996 cooperative_loading_task = Some((*key, *usage_count));
997 }
998 }
999 true
1000 });
1001 }
1002 }
1003 if is_first_round {
1004 self.stats
1005 .misses
1006 .fetch_add(search_for.len() as u64, Ordering::Relaxed);
1007 self.stats.hits.fetch_add(
1008 loaded_programs_for_tx_batch.entries.len() as u64,
1009 Ordering::Relaxed,
1010 );
1011 }
1012 cooperative_loading_task
1013 }
1014
1015 pub fn finish_cooperative_loading_task(
1017 &mut self,
1018 slot: Slot,
1019 key: Pubkey,
1020 loaded_program: Arc<ProgramCacheEntry>,
1021 ) -> bool {
1022 match &mut self.index {
1023 IndexImplementation::V1 {
1024 loading_entries, ..
1025 } => {
1026 let loading_thread = loading_entries.get_mut().unwrap().remove(&key);
1027 debug_assert_eq!(loading_thread, Some((slot, thread::current().id())));
1028 if loaded_program.deployment_slot > slot {
1030 self.stats.lost_insertions.fetch_add(1, Ordering::Relaxed);
1031 }
1032 let was_occupied = self.assign_program(key, loaded_program);
1033 self.loading_task_waiter.notify();
1034 was_occupied
1035 }
1036 }
1037 }
1038
1039 #[allow(
1054 clippy::disallowed_types,
1055 reason = "parameter type shared with svm-execution's TransactionExecutionResult"
1056 )]
1057 pub fn merge(&mut self, modified_entries: &HashMap<Pubkey, Arc<ProgramCacheEntry>>) {
1058 modified_entries.iter().for_each(|(key, entry)| {
1059 self.assign_program(*key, entry.clone());
1060 })
1061 }
1062
1063 fn get_flattened_entries(&self) -> Vec<(Pubkey, Arc<ProgramCacheEntry>)> {
1065 match &self.index {
1066 IndexImplementation::V1 { entries, .. } => entries
1067 .iter()
1068 .flat_map(|(id, second_level)| {
1069 second_level
1070 .iter()
1071 .filter_map(move |program| match program.program {
1072 ProgramCacheEntryType::Loaded(_) => Some((*id, program.clone())),
1073 _ => None,
1074 })
1075 })
1076 .collect(),
1077 }
1078 }
1079
1080 #[cfg(any(test, feature = "dev-context-only-utils"))]
1082 pub fn get_flattened_entries_for_tests(&self) -> Vec<(Pubkey, Arc<ProgramCacheEntry>)> {
1083 match &self.index {
1084 IndexImplementation::V1 { entries, .. } => entries
1085 .iter()
1086 .flat_map(|(id, second_level)| {
1087 second_level.iter().map(|program| (*id, program.clone()))
1088 })
1089 .collect(),
1090 }
1091 }
1092
1093 #[cfg(any(test, feature = "dev-context-only-utils"))]
1095 pub fn get_slot_versions_for_tests(&self, key: &Pubkey) -> &[Arc<ProgramCacheEntry>] {
1096 match &self.index {
1097 IndexImplementation::V1 { entries, .. } => entries
1098 .get(key)
1099 .map(|second_level| second_level.as_ref())
1100 .unwrap_or(&[]),
1101 }
1102 }
1103
1104 pub fn sort_and_unload(&mut self, shrink_to: PercentageInteger) {
1106 let mut sorted_candidates = self.get_flattened_entries();
1107 sorted_candidates
1108 .sort_by_cached_key(|(_id, program)| program.tx_usage_counter.load(Ordering::Relaxed));
1109 let num_to_unload = sorted_candidates
1110 .len()
1111 .saturating_sub(shrink_to.apply_to(MAX_LOADED_ENTRY_COUNT));
1112 self.unload_program_entries(sorted_candidates.iter().take(num_to_unload));
1113 }
1114
1115 pub fn evict_using_2s_random_selection(&mut self, shrink_to: PercentageInteger, now: Slot) {
1118 let mut candidates = self.get_flattened_entries();
1119 self.stats
1120 .water_level
1121 .store(candidates.len() as u64, Ordering::Relaxed);
1122 let num_to_unload = candidates
1123 .len()
1124 .saturating_sub(shrink_to.apply_to(MAX_LOADED_ENTRY_COUNT));
1125 fn random_index_and_usage_counter(
1126 candidates: &[(Pubkey, Arc<ProgramCacheEntry>)],
1127 now: Slot,
1128 ) -> (usize, u64) {
1129 let mut rng = thread_rng();
1130 let index = rng.gen_range(0..candidates.len());
1131 let usage_counter = candidates
1132 .get(index)
1133 .expect("Failed to get cached entry")
1134 .1
1135 .decayed_usage_counter(now);
1136 (index, usage_counter)
1137 }
1138
1139 for _ in 0..num_to_unload {
1140 let (index1, usage_counter1) = random_index_and_usage_counter(&candidates, now);
1141 let (index2, usage_counter2) = random_index_and_usage_counter(&candidates, now);
1142
1143 let (program, entry) = if usage_counter1 < usage_counter2 {
1144 candidates.swap_remove(index1)
1145 } else {
1146 candidates.swap_remove(index2)
1147 };
1148 self.unload_program_entry(&program, &entry);
1149 }
1150 }
1151
1152 pub fn remove_programs(&mut self, keys: impl Iterator<Item = Pubkey>) {
1154 match &mut self.index {
1155 IndexImplementation::V1 { entries, .. } => {
1156 for k in keys {
1157 entries.remove(&k);
1158 }
1159 }
1160 }
1161 }
1162
1163 fn unload_program_entry(&mut self, program: &Pubkey, remove_entry: &Arc<ProgramCacheEntry>) {
1166 match &mut self.index {
1167 IndexImplementation::V1 { entries, .. } => {
1168 let second_level = entries.get_mut(program).expect("Cache lookup failed");
1169 let candidate = second_level
1170 .iter_mut()
1171 .find(|entry| entry == &remove_entry)
1172 .expect("Program entry not found");
1173
1174 if let Some(unloaded) = candidate.to_unloaded() {
1178 if candidate.tx_usage_counter.load(Ordering::Relaxed) == 1 {
1179 self.stats.one_hit_wonders.fetch_add(1, Ordering::Relaxed);
1180 }
1181 self.stats
1182 .evictions
1183 .entry(*program)
1184 .and_modify(|c| *c = c.saturating_add(1))
1185 .or_insert(1);
1186 *candidate = Arc::new(unloaded);
1187 }
1188 }
1189 }
1190 }
1191
1192 fn unload_program_entries<'a>(
1193 &mut self,
1194 remove: impl Iterator<Item = &'a (Pubkey, Arc<ProgramCacheEntry>)>,
1195 ) {
1196 for (program, entry) in remove {
1197 self.unload_program_entry(program, entry);
1198 }
1199 }
1200}
1201
1202#[cfg(feature = "frozen-abi")]
1203impl rialo_frozen_abi::abi_example::AbiExample for ProgramCacheEntry {
1204 fn example() -> Self {
1205 Self::default()
1207 }
1208}
1209
1210#[cfg(feature = "frozen-abi")]
1211impl rialo_frozen_abi::abi_example::AbiExample for ProgramCache {
1212 fn example() -> Self {
1213 Self::new()
1215 }
1216}
1217
1218#[cfg(false)] #[cfg(test)]
1220mod tests {
1221 use std::{
1222 fs::File,
1223 io::Read,
1224 sync::{
1225 atomic::{AtomicU64, Ordering},
1226 Arc,
1227 },
1228 };
1229
1230 use assert_matches::assert_matches;
1231 use percentage::Percentage;
1232 use rialo_s_clock::Slot;
1233 use rialo_s_pubkey::Pubkey;
1234 use solana_sbpf::{elf::Executable, program::BuiltinProgram};
1235 use test_case::{test_case, test_matrix};
1236
1237 use crate::loaded_programs::{
1238 ProgramCache, ProgramCacheEntry, ProgramCacheEntryOwner, ProgramCacheEntryType,
1239 ProgramCacheForTxBatch, ProgramCacheMatchCriteria, ProgramRuntimeEnvironment,
1240 ProgramRuntimeEnvironments, DELAY_VISIBILITY_SLOT_OFFSET,
1241 };
1242
1243 static MOCK_ENVIRONMENT: std::sync::OnceLock<ProgramRuntimeEnvironment> =
1244 std::sync::OnceLock::<ProgramRuntimeEnvironment>::new();
1245
1246 fn get_mock_env() -> ProgramRuntimeEnvironment {
1247 MOCK_ENVIRONMENT
1248 .get_or_init(|| Arc::new(BuiltinProgram::new_mock()))
1249 .clone()
1250 }
1251
1252 fn new_mock_cache() -> ProgramCache {
1253 let mut cache = ProgramCache::new(0, 0);
1254 cache.environments.program_runtime_v1 = get_mock_env();
1255 cache
1256 }
1257
1258 fn new_test_entry(deployment_slot: Slot, effective_slot: Slot) -> Arc<ProgramCacheEntry> {
1259 new_test_entry_with_usage(deployment_slot, effective_slot, AtomicU64::default())
1260 }
1261
1262 fn new_loaded_entry(env: ProgramRuntimeEnvironment) -> ProgramCacheEntryType {
1263 let mut elf = Vec::new();
1264 File::open("../runtime/vm/ebpf/bpf_loader/test_elfs/out/noop_aligned.so")
1265 .unwrap()
1266 .read_to_end(&mut elf)
1267 .unwrap();
1268 let executable = Executable::load(&elf, env).unwrap();
1269 ProgramCacheEntryType::Loaded(super::ExecutableKind::Ebpf(executable))
1270 }
1271
1272 fn new_test_entry_with_usage(
1273 deployment_slot: Slot,
1274 effective_slot: Slot,
1275 usage_counter: AtomicU64,
1276 ) -> Arc<ProgramCacheEntry> {
1277 Arc::new(ProgramCacheEntry {
1278 program: new_loaded_entry(get_mock_env()),
1279 account_owner: ProgramCacheEntryOwner::LoaderV2,
1280 account_size: 0,
1281 deployment_slot,
1282 effective_slot,
1283 tx_usage_counter: usage_counter,
1284 ix_usage_counter: AtomicU64::default(),
1285 latest_access_slot: AtomicU64::new(deployment_slot),
1286 })
1287 }
1288
1289 fn new_test_builtin_entry(
1290 deployment_slot: Slot,
1291 effective_slot: Slot,
1292 ) -> Arc<ProgramCacheEntry> {
1293 Arc::new(ProgramCacheEntry {
1294 program: ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1295 account_owner: ProgramCacheEntryOwner::NativeLoader,
1296 account_size: 0,
1297 deployment_slot,
1298 effective_slot,
1299 tx_usage_counter: AtomicU64::default(),
1300 ix_usage_counter: AtomicU64::default(),
1301 latest_access_slot: AtomicU64::default(),
1302 })
1303 }
1304
1305 fn set_tombstone(
1306 cache: &mut ProgramCache,
1307 key: Pubkey,
1308 slot: Slot,
1309 reason: ProgramCacheEntryType,
1310 ) -> Arc<ProgramCacheEntry> {
1311 let program = Arc::new(ProgramCacheEntry::new_tombstone(
1312 slot,
1313 ProgramCacheEntryOwner::LoaderV2,
1314 reason,
1315 ));
1316 cache.assign_program(key, program.clone());
1317 program
1318 }
1319
1320 fn insert_unloaded_entry(
1321 cache: &mut ProgramCache,
1322 key: Pubkey,
1323 slot: Slot,
1324 ) -> Arc<ProgramCacheEntry> {
1325 let loaded = new_test_entry_with_usage(slot, slot.saturating_add(1), AtomicU64::default());
1326 let unloaded = Arc::new(loaded.to_unloaded().expect("Failed to unload the program"));
1327 cache.assign_program(key, unloaded.clone());
1328 unloaded
1329 }
1330
1331 fn num_matching_entries<P>(cache: &ProgramCache, predicate: P) -> usize
1332 where
1333 P: Fn(&ProgramCacheEntryType) -> bool,
1334 {
1335 cache
1336 .get_flattened_entries_for_tests()
1337 .iter()
1338 .filter(|(_key, program)| predicate(&program.program))
1339 .count()
1340 }
1341
1342 #[test]
1343 fn test_usage_counter_decay() {
1344 let _cache = new_mock_cache();
1345 let program = new_test_entry_with_usage(10, 11, AtomicU64::new(32));
1346 program.update_access_slot(15);
1347 assert_eq!(program.decayed_usage_counter(15), 32);
1348 assert_eq!(program.decayed_usage_counter(16), 16);
1349 assert_eq!(program.decayed_usage_counter(17), 8);
1350 assert_eq!(program.decayed_usage_counter(18), 4);
1351 assert_eq!(program.decayed_usage_counter(19), 2);
1352 assert_eq!(program.decayed_usage_counter(20), 1);
1353 assert_eq!(program.decayed_usage_counter(21), 0);
1354 assert_eq!(program.decayed_usage_counter(15), 32);
1355 assert_eq!(program.decayed_usage_counter(14), 32);
1356
1357 program.update_access_slot(18);
1358 assert_eq!(program.decayed_usage_counter(15), 32);
1359 assert_eq!(program.decayed_usage_counter(16), 32);
1360 assert_eq!(program.decayed_usage_counter(17), 32);
1361 assert_eq!(program.decayed_usage_counter(18), 32);
1362 assert_eq!(program.decayed_usage_counter(19), 16);
1363 assert_eq!(program.decayed_usage_counter(20), 8);
1364 assert_eq!(program.decayed_usage_counter(21), 4);
1365
1366 assert_eq!(program.decayed_usage_counter(18 + 63), 0);
1368 assert_eq!(program.decayed_usage_counter(100), 0);
1369 }
1370
1371 fn program_deploy_test_helper(
1372 cache: &mut ProgramCache,
1373 program: Pubkey,
1374 deployment_slots: Vec<Slot>,
1375 usage_counters: Vec<u64>,
1376 programs: &mut Vec<(Pubkey, Slot, u64)>,
1377 ) {
1378 deployment_slots
1380 .iter()
1381 .enumerate()
1382 .for_each(|(i, deployment_slot)| {
1383 let usage_counter = *usage_counters.get(i).unwrap_or(&0);
1384 cache.assign_program(
1385 program,
1386 new_test_entry_with_usage(
1387 *deployment_slot,
1388 (*deployment_slot).saturating_add(2),
1389 AtomicU64::new(usage_counter),
1390 ),
1391 );
1392 programs.push((program, *deployment_slot, usage_counter));
1393 });
1394
1395 let env = Arc::new(BuiltinProgram::new_mock());
1397 for slot in 21..31 {
1398 set_tombstone(
1399 cache,
1400 program,
1401 slot,
1402 ProgramCacheEntryType::FailedVerification(env.clone()),
1403 );
1404 }
1405
1406 for slot in 31..41 {
1408 insert_unloaded_entry(cache, program, slot);
1409 }
1410 }
1411
1412 #[test]
1413 fn test_random_eviction() {
1414 let mut programs = vec![];
1415
1416 let mut cache = new_mock_cache();
1417
1418 program_deploy_test_helper(
1424 &mut cache,
1425 Pubkey::new_unique(),
1426 vec![0, 10, 20],
1427 vec![4, 5, 25],
1428 &mut programs,
1429 );
1430
1431 program_deploy_test_helper(
1433 &mut cache,
1434 Pubkey::new_unique(),
1435 vec![5, 11],
1436 vec![0, 2],
1437 &mut programs,
1438 );
1439
1440 program_deploy_test_helper(
1442 &mut cache,
1443 Pubkey::new_unique(),
1444 vec![0, 5, 15],
1445 vec![100, 3, 20],
1446 &mut programs,
1447 );
1448
1449 let num_loaded_expected = 8;
1451 let num_unloaded_expected = 30;
1453 let num_tombstones_expected = 30;
1455
1456 programs.sort_by_key(|(_id, _slot, usage_count)| *usage_count);
1458 let num_loaded = num_matching_entries(&cache, |program_type| {
1459 matches!(program_type, ProgramCacheEntryType::Loaded(_))
1460 });
1461 let num_unloaded = num_matching_entries(&cache, |program_type| {
1462 matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1463 });
1464 let num_tombstones = num_matching_entries(&cache, |program_type| {
1465 matches!(
1466 program_type,
1467 ProgramCacheEntryType::DelayVisibility
1468 | ProgramCacheEntryType::FailedVerification(_)
1469 | ProgramCacheEntryType::Closed
1470 )
1471 });
1472
1473 assert_eq!(num_loaded, num_loaded_expected);
1475 assert_eq!(num_unloaded, num_unloaded_expected);
1476 assert_eq!(num_tombstones, num_tombstones_expected);
1477
1478 let eviction_pct = 1;
1480
1481 let num_loaded_expected =
1482 Percentage::from(eviction_pct).apply_to(crate::loaded_programs::MAX_LOADED_ENTRY_COUNT);
1483 let num_unloaded_expected = num_unloaded_expected + num_loaded - num_loaded_expected;
1484 cache.evict_using_2s_random_selection(Percentage::from(eviction_pct), 21);
1485
1486 let num_loaded = num_matching_entries(&cache, |program_type| {
1488 matches!(program_type, ProgramCacheEntryType::Loaded(_))
1489 });
1490 let num_unloaded = num_matching_entries(&cache, |program_type| {
1491 matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1492 });
1493 let num_tombstones = num_matching_entries(&cache, |program_type| {
1494 matches!(program_type, ProgramCacheEntryType::FailedVerification(_))
1495 });
1496
1497 assert_eq!(num_loaded, num_loaded_expected);
1499 assert_eq!(num_unloaded, num_unloaded_expected);
1501 assert_eq!(num_tombstones, num_tombstones_expected);
1503 }
1504
1505 #[test]
1506 fn test_eviction() {
1507 let mut programs = vec![];
1508 let mut cache = new_mock_cache();
1509
1510 program_deploy_test_helper(
1512 &mut cache,
1513 Pubkey::new_unique(),
1514 vec![0, 10, 20],
1515 vec![4, 5, 25],
1516 &mut programs,
1517 );
1518
1519 program_deploy_test_helper(
1521 &mut cache,
1522 Pubkey::new_unique(),
1523 vec![5, 11],
1524 vec![0, 2],
1525 &mut programs,
1526 );
1527
1528 program_deploy_test_helper(
1530 &mut cache,
1531 Pubkey::new_unique(),
1532 vec![0, 5, 15],
1533 vec![100, 3, 20],
1534 &mut programs,
1535 );
1536
1537 let num_loaded_expected = 8;
1539 let num_unloaded_expected = 30;
1541 let num_tombstones_expected = 30;
1543
1544 programs.sort_by_key(|(_id, _slot, usage_count)| *usage_count);
1546 let num_loaded = num_matching_entries(&cache, |program_type| {
1547 matches!(program_type, ProgramCacheEntryType::Loaded(_))
1548 });
1549 let num_unloaded = num_matching_entries(&cache, |program_type| {
1550 matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1551 });
1552 let num_tombstones = num_matching_entries(&cache, |program_type| {
1553 matches!(program_type, ProgramCacheEntryType::FailedVerification(_))
1554 });
1555
1556 assert_eq!(num_loaded, num_loaded_expected);
1558 assert_eq!(num_unloaded, num_unloaded_expected);
1559 assert_eq!(num_tombstones, num_tombstones_expected);
1560
1561 let eviction_pct = 1;
1563
1564 let num_loaded_expected =
1565 Percentage::from(eviction_pct).apply_to(crate::loaded_programs::MAX_LOADED_ENTRY_COUNT);
1566 let num_unloaded_expected = num_unloaded_expected + num_loaded - num_loaded_expected;
1567
1568 cache.sort_and_unload(Percentage::from(eviction_pct));
1569
1570 let entries = cache.get_flattened_entries_for_tests();
1572 programs.iter().for_each(|entry| {
1573 assert!(entries.iter().any(|(key, _entry)| key == &entry.0));
1574 });
1575
1576 let unloaded = entries
1577 .iter()
1578 .filter_map(|(key, program)| {
1579 matches!(program.program, ProgramCacheEntryType::Unloaded(_))
1580 .then_some((*key, program.tx_usage_counter.load(Ordering::Relaxed)))
1581 })
1582 .collect::<Vec<(Pubkey, u64)>>();
1583
1584 for index in 0..3 {
1585 let expected = programs.get(index).expect("Missing program");
1586 assert!(unloaded.contains(&(expected.0, expected.2)));
1587 }
1588
1589 let num_loaded = num_matching_entries(&cache, |program_type| {
1591 matches!(program_type, ProgramCacheEntryType::Loaded(_))
1592 });
1593 let num_unloaded = num_matching_entries(&cache, |program_type| {
1594 matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1595 });
1596 let num_tombstones = num_matching_entries(&cache, |program_type| {
1597 matches!(
1598 program_type,
1599 ProgramCacheEntryType::DelayVisibility
1600 | ProgramCacheEntryType::FailedVerification(_)
1601 | ProgramCacheEntryType::Closed
1602 )
1603 });
1604
1605 assert_eq!(num_loaded, num_loaded_expected);
1607 assert_eq!(num_unloaded, num_unloaded_expected);
1609 assert_eq!(num_tombstones, num_tombstones_expected);
1611 }
1612
1613 #[test]
1614 fn test_usage_count_of_unloaded_program() {
1615 let mut cache = new_mock_cache();
1616
1617 let program = Pubkey::new_unique();
1618 let evict_to_pct = 2;
1619 let cache_capacity_after_shrink =
1620 Percentage::from(evict_to_pct).apply_to(crate::loaded_programs::MAX_LOADED_ENTRY_COUNT);
1621 let num_total_programs = (cache_capacity_after_shrink + 1) as u64;
1623 (0..num_total_programs).for_each(|i| {
1624 cache.assign_program(
1625 program,
1626 new_test_entry_with_usage(i, i + 2, AtomicU64::new(i + 10)),
1627 );
1628 });
1629
1630 cache.sort_and_unload(Percentage::from(evict_to_pct));
1631
1632 let num_unloaded = num_matching_entries(&cache, |program_type| {
1633 matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1634 });
1635 assert_eq!(num_unloaded, 1);
1636
1637 cache
1638 .get_flattened_entries_for_tests()
1639 .iter()
1640 .for_each(|(_key, program)| {
1641 if matches!(program.program, ProgramCacheEntryType::Unloaded(_)) {
1642 assert_eq!(program.tx_usage_counter.load(Ordering::Relaxed), 10);
1644 assert_eq!(program.deployment_slot, 0);
1645 assert_eq!(program.effective_slot, 2);
1646 }
1647 });
1648
1649 cache.assign_program(program, new_test_entry_with_usage(0, 2, AtomicU64::new(0)));
1652
1653 cache
1654 .get_flattened_entries_for_tests()
1655 .iter()
1656 .for_each(|(_key, program)| {
1657 if matches!(program.program, ProgramCacheEntryType::Unloaded(_))
1658 && program.deployment_slot == 0
1659 && program.effective_slot == 2
1660 {
1661 assert_eq!(program.tx_usage_counter.load(Ordering::Relaxed), 10);
1663 }
1664 });
1665 }
1666
1667 #[test]
1668 fn test_fuzz_assign_program_order() {
1669 use rand::prelude::SliceRandom;
1670 const EXPECTED_ENTRIES: [(u64, u64); 7] =
1671 [(1, 2), (5, 5), (5, 6), (5, 10), (9, 10), (10, 10), (3, 12)];
1672 let mut rng = rand::thread_rng();
1673 let program_id = Pubkey::new_unique();
1674 for _ in 0..1000 {
1675 let mut entries = EXPECTED_ENTRIES.to_vec();
1676 entries.shuffle(&mut rng);
1677 let mut cache = new_mock_cache();
1678 for (deployment_slot, effective_slot) in entries {
1679 assert!(!cache
1680 .assign_program(program_id, new_test_entry(deployment_slot, effective_slot)));
1681 }
1682 for ((deployment_slot, effective_slot), entry) in EXPECTED_ENTRIES
1683 .iter()
1684 .zip(cache.get_slot_versions_for_tests(&program_id).iter())
1685 {
1686 assert_eq!(entry.deployment_slot, *deployment_slot);
1687 assert_eq!(entry.effective_slot, *effective_slot);
1688 }
1689 }
1690 }
1691
1692 #[test_matrix(
1693 (
1694 ProgramCacheEntryType::Closed,
1695 ProgramCacheEntryType::FailedVerification(get_mock_env()),
1696 new_loaded_entry(get_mock_env()),
1697 ),
1698 (
1699 ProgramCacheEntryType::FailedVerification(get_mock_env()),
1700 ProgramCacheEntryType::Closed,
1701 ProgramCacheEntryType::Unloaded(get_mock_env()),
1702 new_loaded_entry(get_mock_env()),
1703 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1704 )
1705 )]
1706 #[test_matrix(
1707 (
1708 ProgramCacheEntryType::Unloaded(get_mock_env()),
1709 ),
1710 (
1711 ProgramCacheEntryType::FailedVerification(get_mock_env()),
1712 ProgramCacheEntryType::Closed,
1713 ProgramCacheEntryType::Unloaded(get_mock_env()),
1714 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1715 )
1716 )]
1717 #[test_matrix(
1718 (ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),),
1719 (
1720 ProgramCacheEntryType::FailedVerification(get_mock_env()),
1721 ProgramCacheEntryType::Closed,
1722 ProgramCacheEntryType::Unloaded(get_mock_env()),
1723 new_loaded_entry(get_mock_env()),
1724 )
1725 )]
1726 #[should_panic(expected = "Unexpected replacement of an entry")]
1727 fn test_assign_program_failure(old: ProgramCacheEntryType, new: ProgramCacheEntryType) {
1728 let mut cache = new_mock_cache();
1729 let program_id = Pubkey::new_unique();
1730 assert!(!cache.assign_program(
1731 program_id,
1732 Arc::new(ProgramCacheEntry {
1733 program: old,
1734 account_owner: ProgramCacheEntryOwner::LoaderV2,
1735 account_size: 0,
1736 deployment_slot: 10,
1737 effective_slot: 11,
1738 tx_usage_counter: AtomicU64::default(),
1739 ix_usage_counter: AtomicU64::default(),
1740 latest_access_slot: AtomicU64::default(),
1741 }),
1742 ));
1743 cache.assign_program(
1744 program_id,
1745 Arc::new(ProgramCacheEntry {
1746 program: new,
1747 account_owner: ProgramCacheEntryOwner::LoaderV2,
1748 account_size: 0,
1749 deployment_slot: 10,
1750 effective_slot: 11,
1751 tx_usage_counter: AtomicU64::default(),
1752 ix_usage_counter: AtomicU64::default(),
1753 latest_access_slot: AtomicU64::default(),
1754 }),
1755 );
1756 }
1757
1758 #[test_case(
1759 ProgramCacheEntryType::Unloaded(Arc::new(BuiltinProgram::new_mock())),
1760 new_loaded_entry(get_mock_env())
1761 )]
1762 #[test_case(
1763 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1764 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock())
1765 )]
1766 fn test_assign_program_success(old: ProgramCacheEntryType, new: ProgramCacheEntryType) {
1767 let mut cache = new_mock_cache();
1768 let program_id = Pubkey::new_unique();
1769 assert!(!cache.assign_program(
1770 program_id,
1771 Arc::new(ProgramCacheEntry {
1772 program: old,
1773 account_owner: ProgramCacheEntryOwner::LoaderV2,
1774 account_size: 0,
1775 deployment_slot: 10,
1776 effective_slot: 11,
1777 tx_usage_counter: AtomicU64::default(),
1778 ix_usage_counter: AtomicU64::default(),
1779 latest_access_slot: AtomicU64::default(),
1780 }),
1781 ));
1782 assert!(!cache.assign_program(
1783 program_id,
1784 Arc::new(ProgramCacheEntry {
1785 program: new,
1786 account_owner: ProgramCacheEntryOwner::LoaderV2,
1787 account_size: 0,
1788 deployment_slot: 10,
1789 effective_slot: 11,
1790 tx_usage_counter: AtomicU64::default(),
1791 ix_usage_counter: AtomicU64::default(),
1792 latest_access_slot: AtomicU64::default(),
1793 }),
1794 ));
1795 }
1796
1797 #[test]
1798 fn test_tombstone() {
1799 let env = Arc::new(BuiltinProgram::new_mock());
1800 let tombstone = ProgramCacheEntry::new_tombstone(
1801 0,
1802 ProgramCacheEntryOwner::LoaderV2,
1803 ProgramCacheEntryType::FailedVerification(env.clone()),
1804 );
1805 assert_matches!(
1806 tombstone.program,
1807 ProgramCacheEntryType::FailedVerification(_)
1808 );
1809 assert!(tombstone.is_tombstone());
1810 assert_eq!(tombstone.deployment_slot, 0);
1811 assert_eq!(tombstone.effective_slot, 0);
1812
1813 let tombstone = ProgramCacheEntry::new_tombstone(
1814 100,
1815 ProgramCacheEntryOwner::LoaderV2,
1816 ProgramCacheEntryType::Closed,
1817 );
1818 assert_matches!(tombstone.program, ProgramCacheEntryType::Closed);
1819 assert!(tombstone.is_tombstone());
1820 assert_eq!(tombstone.deployment_slot, 100);
1821 assert_eq!(tombstone.effective_slot, 100);
1822
1823 let mut cache = new_mock_cache();
1824 let program1 = Pubkey::new_unique();
1825 let tombstone = set_tombstone(
1826 &mut cache,
1827 program1,
1828 10,
1829 ProgramCacheEntryType::FailedVerification(env.clone()),
1830 );
1831 let slot_versions = cache.get_slot_versions_for_tests(&program1);
1832 assert_eq!(slot_versions.len(), 1);
1833 assert!(slot_versions.first().unwrap().is_tombstone());
1834 assert_eq!(tombstone.deployment_slot, 10);
1835 assert_eq!(tombstone.effective_slot, 10);
1836
1837 let program2 = Pubkey::new_unique();
1839 cache.assign_program(program2, new_test_builtin_entry(50, 51));
1840 let slot_versions = cache.get_slot_versions_for_tests(&program2);
1841 assert_eq!(slot_versions.len(), 1);
1842 assert!(!slot_versions.first().unwrap().is_tombstone());
1843
1844 let tombstone = set_tombstone(
1845 &mut cache,
1846 program2,
1847 60,
1848 ProgramCacheEntryType::FailedVerification(env),
1849 );
1850 let slot_versions = cache.get_slot_versions_for_tests(&program2);
1851 assert_eq!(slot_versions.len(), 2);
1852 assert!(!slot_versions.first().unwrap().is_tombstone());
1853 assert!(slot_versions.get(1).unwrap().is_tombstone());
1854 assert!(tombstone.is_tombstone());
1855 assert_eq!(tombstone.deployment_slot, 60);
1856 assert_eq!(tombstone.effective_slot, 60);
1857 }
1858
1859 #[test]
1860 fn test_prune_empty() {
1861 let mut cache = new_mock_cache();
1862
1863 cache.prune(0, 0);
1864 assert!(cache.get_flattened_entries_for_tests().is_empty());
1865
1866 cache.prune(10, 0);
1867 assert!(cache.get_flattened_entries_for_tests().is_empty());
1868
1869 let mut cache = new_mock_cache();
1870
1871 cache.prune(0, 0);
1872 assert!(cache.get_flattened_entries_for_tests().is_empty());
1873
1874 cache.prune(10, 0);
1875 assert!(cache.get_flattened_entries_for_tests().is_empty());
1876
1877 let mut cache = new_mock_cache();
1878
1879 cache.prune(0, 0);
1880 assert!(cache.get_flattened_entries_for_tests().is_empty());
1881
1882 cache.prune(10, 0);
1883 assert!(cache.get_flattened_entries_for_tests().is_empty());
1884
1885 let mut cache = new_mock_cache();
1886
1887 cache.prune(0, 0);
1888 assert!(cache.get_flattened_entries_for_tests().is_empty());
1889
1890 cache.prune(10, 0);
1891 assert!(cache.get_flattened_entries_for_tests().is_empty());
1892 }
1893
1894 #[test]
1895 fn test_prune_different_env() {
1896 let mut cache = new_mock_cache();
1897
1898 let program1 = Pubkey::new_unique();
1899 cache.assign_program(program1, new_test_entry(10, 10));
1900
1901 let new_env = Arc::new(BuiltinProgram::new_mock());
1902 cache.upcoming_environments = Some(ProgramRuntimeEnvironments {
1903 program_runtime_v1: new_env.clone(),
1904 program_runtime_v2: new_env.clone(),
1905 });
1906 let updated_program = Arc::new(ProgramCacheEntry {
1907 program: new_loaded_entry(new_env.clone()),
1908 account_owner: ProgramCacheEntryOwner::LoaderV2,
1909 account_size: 0,
1910 deployment_slot: 20,
1911 effective_slot: 20,
1912 tx_usage_counter: AtomicU64::default(),
1913 ix_usage_counter: AtomicU64::default(),
1914 latest_access_slot: AtomicU64::default(),
1915 });
1916 cache.assign_program(program1, updated_program.clone());
1917
1918 assert_eq!(cache.get_slot_versions_for_tests(&program1).len(), 2);
1920
1921 cache.prune(21, cache.latest_root_epoch);
1922
1923 assert_eq!(cache.get_slot_versions_for_tests(&program1).len(), 2);
1925
1926 cache.prune(22, cache.latest_root_epoch.saturating_add(1));
1927
1928 assert_eq!(cache.get_slot_versions_for_tests(&program1).len(), 1);
1930
1931 let entry = cache
1932 .get_slot_versions_for_tests(&program1)
1933 .first()
1934 .expect("Failed to get the program")
1935 .clone();
1936 assert_eq!(entry, updated_program);
1938 }
1939
1940 fn get_entries_to_load(
1941 cache: &ProgramCache,
1942 loading_slot: Slot,
1943 keys: &[Pubkey],
1944 ) -> Vec<(Pubkey, (ProgramCacheMatchCriteria, u64))> {
1945 let entries = cache.get_flattened_entries_for_tests();
1946 keys.iter()
1947 .filter_map(|key| {
1948 entries
1949 .iter()
1950 .rev()
1951 .find(|(program_id, entry)| {
1952 program_id == key && entry.deployment_slot <= loading_slot
1953 })
1954 .map(|(program_id, _entry)| {
1955 (*program_id, (ProgramCacheMatchCriteria::NoCriteria, 1))
1956 })
1957 })
1958 .collect()
1959 }
1960
1961 fn match_slot(
1962 extracted: &ProgramCacheForTxBatch,
1963 program: &Pubkey,
1964 deployment_slot: Slot,
1965 working_slot: Slot,
1966 ) -> bool {
1967 assert_eq!(extracted.slot, working_slot);
1968 extracted
1969 .entries
1970 .get(program)
1971 .map(|entry| entry.deployment_slot == deployment_slot)
1972 .unwrap_or(false)
1973 }
1974
1975 fn match_missing(
1976 missing: &[(Pubkey, (ProgramCacheMatchCriteria, u64))],
1977 program: &Pubkey,
1978 expected_result: bool,
1979 ) -> bool {
1980 missing.iter().any(|(key, _)| key == program) == expected_result
1981 }
1982
1983 #[test]
1984 fn test_fork_extract_and_prune() {
1985 let mut cache = new_mock_cache();
1986
1987 let program1 = Pubkey::new_unique();
2002 cache.assign_program(program1, new_test_entry(0, 1));
2003 cache.assign_program(program1, new_test_entry(10, 11));
2004 cache.assign_program(program1, new_test_entry(20, 21));
2005
2006 let program2 = Pubkey::new_unique();
2007 cache.assign_program(program2, new_test_entry(5, 6));
2008 cache.assign_program(
2009 program2,
2010 new_test_entry(11, 11 + DELAY_VISIBILITY_SLOT_OFFSET),
2011 );
2012
2013 let program3 = Pubkey::new_unique();
2014 cache.assign_program(program3, new_test_entry(25, 26));
2015
2016 let program4 = Pubkey::new_unique();
2017 cache.assign_program(program4, new_test_entry(0, 1));
2018 cache.assign_program(program4, new_test_entry(5, 6));
2019 cache.assign_program(
2021 program4,
2022 new_test_entry(15, 15 + DELAY_VISIBILITY_SLOT_OFFSET),
2023 );
2024
2025 let mut missing =
2042 get_entries_to_load(&cache, 22, &[program1, program2, program3, program4]);
2043 assert!(match_missing(&missing, &program2, false));
2044 assert!(match_missing(&missing, &program3, false));
2045 let mut extracted = ProgramCacheForTxBatch::new(22, cache.environments.clone(), None, 0);
2046 cache.extract(&mut missing, &mut extracted, true);
2047 assert!(match_slot(&extracted, &program1, 20, 22));
2048 assert!(match_slot(&extracted, &program4, 0, 22));
2049
2050 let mut missing =
2052 get_entries_to_load(&cache, 15, &[program1, program2, program3, program4]);
2053 assert!(match_missing(&missing, &program3, false));
2054 let mut extracted = ProgramCacheForTxBatch::new(15, cache.environments.clone(), None, 0);
2055 cache.extract(&mut missing, &mut extracted, true);
2056 assert!(match_slot(&extracted, &program1, 0, 15));
2057 assert!(match_slot(&extracted, &program2, 11, 15));
2058 let tombstone = extracted
2061 .find(&program4)
2062 .expect("Failed to find the tombstone");
2063 assert_matches!(tombstone.program, ProgramCacheEntryType::DelayVisibility);
2064 assert_eq!(tombstone.deployment_slot, 15);
2065
2066 let mut missing =
2068 get_entries_to_load(&cache, 18, &[program1, program2, program3, program4]);
2069 assert!(match_missing(&missing, &program3, false));
2070 let mut extracted = ProgramCacheForTxBatch::new(18, cache.environments.clone(), None, 0);
2071 cache.extract(&mut missing, &mut extracted, true);
2072 assert!(match_slot(&extracted, &program1, 0, 18));
2073 assert!(match_slot(&extracted, &program2, 11, 18));
2074 assert!(match_slot(&extracted, &program4, 15, 18));
2076
2077 let mut missing =
2079 get_entries_to_load(&cache, 23, &[program1, program2, program3, program4]);
2080 assert!(match_missing(&missing, &program3, false));
2081 let mut extracted = ProgramCacheForTxBatch::new(23, cache.environments.clone(), None, 0);
2082 cache.extract(&mut missing, &mut extracted, true);
2083 assert!(match_slot(&extracted, &program1, 0, 23));
2084 assert!(match_slot(&extracted, &program2, 11, 23));
2085 assert!(match_slot(&extracted, &program4, 15, 23));
2087
2088 let mut missing =
2090 get_entries_to_load(&cache, 11, &[program1, program2, program3, program4]);
2091 assert!(match_missing(&missing, &program3, false));
2092 let mut extracted = ProgramCacheForTxBatch::new(11, cache.environments.clone(), None, 0);
2093 cache.extract(&mut missing, &mut extracted, true);
2094 assert!(match_slot(&extracted, &program1, 0, 11));
2095 let tombstone = extracted
2097 .find(&program2)
2098 .expect("Failed to find the tombstone");
2099 assert_matches!(tombstone.program, ProgramCacheEntryType::DelayVisibility);
2100 assert_eq!(tombstone.deployment_slot, 11);
2101 assert!(match_slot(&extracted, &program4, 5, 11));
2102
2103 cache.prune(5, 0);
2104
2105 let mut missing =
2122 get_entries_to_load(&cache, 21, &[program1, program2, program3, program4]);
2123 assert!(match_missing(&missing, &program3, false));
2124 let mut extracted = ProgramCacheForTxBatch::new(21, cache.environments.clone(), None, 0);
2125 cache.extract(&mut missing, &mut extracted, true);
2126 assert!(match_slot(&extracted, &program1, 0, 21));
2128 assert!(match_slot(&extracted, &program2, 11, 21));
2129 assert!(match_slot(&extracted, &program4, 15, 21));
2130
2131 let mut missing =
2133 get_entries_to_load(&cache, 27, &[program1, program2, program3, program4]);
2134 let mut extracted = ProgramCacheForTxBatch::new(27, cache.environments.clone(), None, 0);
2135 cache.extract(&mut missing, &mut extracted, true);
2136 assert!(match_slot(&extracted, &program1, 0, 27));
2137 assert!(match_slot(&extracted, &program2, 11, 27));
2138 assert!(match_slot(&extracted, &program3, 25, 27));
2139 assert!(match_slot(&extracted, &program4, 5, 27));
2140
2141 cache.prune(15, 0);
2142
2143 let mut missing =
2160 get_entries_to_load(&cache, 23, &[program1, program2, program3, program4]);
2161 assert!(match_missing(&missing, &program3, false));
2162 let mut extracted = ProgramCacheForTxBatch::new(23, cache.environments.clone(), None, 0);
2163 cache.extract(&mut missing, &mut extracted, true);
2164 assert!(match_slot(&extracted, &program1, 0, 23));
2165 assert!(match_slot(&extracted, &program2, 11, 23));
2166 assert!(match_slot(&extracted, &program4, 15, 23));
2167 }
2168
2169 #[test]
2170 fn test_extract_using_deployment_slot() {
2171 let mut cache = new_mock_cache();
2172
2173 let program1 = Pubkey::new_unique();
2188 cache.assign_program(program1, new_test_entry(0, 1));
2189 cache.assign_program(program1, new_test_entry(20, 21));
2190
2191 let program2 = Pubkey::new_unique();
2192 cache.assign_program(program2, new_test_entry(5, 6));
2193 cache.assign_program(program2, new_test_entry(11, 12));
2194
2195 let program3 = Pubkey::new_unique();
2196 cache.assign_program(program3, new_test_entry(25, 26));
2197
2198 let mut missing = get_entries_to_load(&cache, 12, &[program1, program2, program3]);
2200 assert!(match_missing(&missing, &program3, false));
2201 let mut extracted = ProgramCacheForTxBatch::new(12, cache.environments.clone(), None, 0);
2202 cache.extract(&mut missing, &mut extracted, true);
2203 assert!(match_slot(&extracted, &program1, 0, 12));
2204 assert!(match_slot(&extracted, &program2, 11, 12));
2205
2206 let mut missing = get_entries_to_load(&cache, 12, &[program1, program2, program3]);
2208 missing.get_mut(0).unwrap().1 .0 = ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(5);
2209 missing.get_mut(1).unwrap().1 .0 = ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(5);
2210 assert!(match_missing(&missing, &program3, false));
2211 let mut extracted = ProgramCacheForTxBatch::new(12, cache.environments.clone(), None, 0);
2212 cache.extract(&mut missing, &mut extracted, true);
2213 assert!(match_missing(&missing, &program1, true));
2214 assert!(match_slot(&extracted, &program2, 11, 12));
2215 }
2216
2217 #[test]
2218 fn test_extract_unloaded() {
2219 let mut cache = new_mock_cache();
2220
2221 let program1 = Pubkey::new_unique();
2237 cache.assign_program(program1, new_test_entry(0, 1));
2238 cache.assign_program(program1, new_test_entry(20, 21));
2239
2240 let program2 = Pubkey::new_unique();
2241 cache.assign_program(program2, new_test_entry(5, 6));
2242 cache.assign_program(program2, new_test_entry(11, 12));
2243
2244 let program3 = Pubkey::new_unique();
2245 let _ = insert_unloaded_entry(&mut cache, program3, 25);
2247
2248 cache.assign_program(
2252 program3,
2253 Arc::new(
2254 new_test_entry(20, 21)
2255 .to_unloaded()
2256 .expect("Failed to create unloaded program"),
2257 ),
2258 );
2259
2260 let mut missing = get_entries_to_load(&cache, 19, &[program1, program2, program3]);
2262 assert!(match_missing(&missing, &program3, false));
2263 let mut extracted = ProgramCacheForTxBatch::new(19, cache.environments.clone(), None, 0);
2264 cache.extract(&mut missing, &mut extracted, true);
2265 assert!(match_slot(&extracted, &program1, 0, 19));
2266 assert!(match_slot(&extracted, &program2, 11, 19));
2267
2268 let mut missing = get_entries_to_load(&cache, 27, &[program1, program2, program3]);
2270 let mut extracted = ProgramCacheForTxBatch::new(27, cache.environments.clone(), None, 0);
2271 cache.extract(&mut missing, &mut extracted, true);
2272 assert!(match_slot(&extracted, &program1, 0, 27));
2273 assert!(match_slot(&extracted, &program2, 11, 27));
2274 assert!(match_missing(&missing, &program3, true));
2275
2276 let mut missing = get_entries_to_load(&cache, 22, &[program1, program2, program3]);
2278 assert!(match_missing(&missing, &program2, false));
2279 let mut extracted = ProgramCacheForTxBatch::new(22, cache.environments.clone(), None, 0);
2280 cache.extract(&mut missing, &mut extracted, true);
2281 assert!(match_slot(&extracted, &program1, 20, 22));
2282 assert!(match_missing(&missing, &program3, true));
2283 }
2284
2285 #[test]
2286 fn test_extract_nonexistent() {
2287 let cache = new_mock_cache();
2288
2289 let program1 = Pubkey::new_unique();
2290 let mut missing = vec![(program1, (ProgramCacheMatchCriteria::NoCriteria, 1))];
2291 let mut extracted = ProgramCacheForTxBatch::new(0, cache.environments.clone(), None, 0);
2292 cache.extract(&mut missing, &mut extracted, true);
2293 assert!(match_missing(&missing, &program1, true));
2294 }
2295
2296 #[test]
2297 fn test_unloaded() {
2298 let mut cache = new_mock_cache();
2299 for program_cache_entry_type in [
2300 ProgramCacheEntryType::FailedVerification(
2301 cache.environments.program_runtime_v1.clone(),
2302 ),
2303 ProgramCacheEntryType::Closed,
2304 ProgramCacheEntryType::Unloaded(cache.environments.program_runtime_v1.clone()),
2305 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
2306 ] {
2307 let entry = Arc::new(ProgramCacheEntry {
2308 program: program_cache_entry_type,
2309 account_owner: ProgramCacheEntryOwner::LoaderV2,
2310 account_size: 0,
2311 deployment_slot: 0,
2312 effective_slot: 0,
2313 tx_usage_counter: AtomicU64::default(),
2314 ix_usage_counter: AtomicU64::default(),
2315 latest_access_slot: AtomicU64::default(),
2316 });
2317 assert!(entry.to_unloaded().is_none());
2318
2319 let program_id = Pubkey::new_unique();
2321 cache.assign_program(program_id, entry.clone());
2322 cache.unload_program_entry(&program_id, &entry);
2323 assert_eq!(cache.get_slot_versions_for_tests(&program_id).len(), 1);
2324 assert!(cache.stats.evictions.is_empty());
2325 }
2326
2327 let entry = new_test_entry_with_usage(1, 2, AtomicU64::new(3));
2328 let unloaded_entry = entry.to_unloaded().unwrap();
2329 assert_eq!(unloaded_entry.deployment_slot, 1);
2330 assert_eq!(unloaded_entry.effective_slot, 2);
2331 assert_eq!(unloaded_entry.latest_access_slot.load(Ordering::Relaxed), 1);
2332 assert_eq!(unloaded_entry.tx_usage_counter.load(Ordering::Relaxed), 3);
2333
2334 let program_id = Pubkey::new_unique();
2336 cache.assign_program(program_id, entry.clone());
2337 cache.unload_program_entry(&program_id, &entry);
2338 assert!(cache.stats.evictions.contains_key(&program_id));
2339 }
2340
2341 #[test]
2342 fn test_fork_prune_find_first_ancestor() {
2343 let mut cache = new_mock_cache();
2344
2345 let program1 = Pubkey::new_unique();
2356 cache.assign_program(program1, new_test_entry(0, 1));
2357 cache.assign_program(program1, new_test_entry(5, 6));
2358
2359 cache.prune(10, 0);
2360
2361 let mut missing = get_entries_to_load(&cache, 20, &[program1]);
2362 let mut extracted = ProgramCacheForTxBatch::new(20, cache.environments.clone(), None, 0);
2363 cache.extract(&mut missing, &mut extracted, true);
2364
2365 assert_eq!(
2367 extracted
2368 .find(&program1)
2369 .expect("Did not find the program")
2370 .deployment_slot,
2371 0
2372 );
2373 }
2374
2375 #[test]
2376 fn test_prune_by_deployment_slot() {
2377 let mut cache = new_mock_cache();
2378
2379 let program1 = Pubkey::new_unique();
2390 cache.assign_program(program1, new_test_entry(0, 1));
2391 cache.assign_program(program1, new_test_entry(5, 6));
2392
2393 let program2 = Pubkey::new_unique();
2394 cache.assign_program(program2, new_test_entry(10, 11));
2395
2396 let mut missing = get_entries_to_load(&cache, 20, &[program1, program2]);
2397 let mut extracted = ProgramCacheForTxBatch::new(20, cache.environments.clone(), None, 0);
2398 cache.extract(&mut missing, &mut extracted, true);
2399 assert!(match_slot(&extracted, &program1, 0, 20));
2400 assert!(match_slot(&extracted, &program2, 10, 20));
2401
2402 let mut missing = get_entries_to_load(&cache, 6, &[program1, program2]);
2403 assert!(match_missing(&missing, &program2, false));
2404 let mut extracted = ProgramCacheForTxBatch::new(6, cache.environments.clone(), None, 0);
2405 cache.extract(&mut missing, &mut extracted, true);
2406 assert!(match_slot(&extracted, &program1, 5, 6));
2407
2408 cache.prune_by_deployment_slot(5);
2411
2412 let mut missing = get_entries_to_load(&cache, 20, &[program1, program2]);
2413 let mut extracted = ProgramCacheForTxBatch::new(20, cache.environments.clone(), None, 0);
2414 cache.extract(&mut missing, &mut extracted, true);
2415 assert!(match_slot(&extracted, &program1, 0, 20));
2416 assert!(match_slot(&extracted, &program2, 10, 20));
2417
2418 let mut missing = get_entries_to_load(&cache, 6, &[program1, program2]);
2419 assert!(match_missing(&missing, &program2, false));
2420 let mut extracted = ProgramCacheForTxBatch::new(6, cache.environments.clone(), None, 0);
2421 cache.extract(&mut missing, &mut extracted, true);
2422 assert!(match_slot(&extracted, &program1, 0, 6));
2423
2424 cache.prune_by_deployment_slot(10);
2427
2428 let mut missing = get_entries_to_load(&cache, 20, &[program1, program2]);
2429 assert!(match_missing(&missing, &program2, false));
2430 let mut extracted = ProgramCacheForTxBatch::new(20, cache.environments.clone(), None, 0);
2431 cache.extract(&mut missing, &mut extracted, true);
2432 assert!(match_slot(&extracted, &program1, 0, 20));
2433 }
2434
2435 #[test]
2436 fn test_usable_entries_for_slot() {
2437 new_mock_cache();
2438 let tombstone = Arc::new(ProgramCacheEntry::new_tombstone(
2439 0,
2440 ProgramCacheEntryOwner::LoaderV2,
2441 ProgramCacheEntryType::Closed,
2442 ));
2443
2444 assert!(ProgramCache::matches_criteria(
2445 &tombstone,
2446 &ProgramCacheMatchCriteria::NoCriteria
2447 ));
2448
2449 assert!(ProgramCache::matches_criteria(
2450 &tombstone,
2451 &ProgramCacheMatchCriteria::Tombstone
2452 ));
2453
2454 assert!(ProgramCache::matches_criteria(
2455 &tombstone,
2456 &ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(0)
2457 ));
2458
2459 assert!(!ProgramCache::matches_criteria(
2460 &tombstone,
2461 &ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(1)
2462 ));
2463
2464 let program = new_test_entry(0, 1);
2465
2466 assert!(ProgramCache::matches_criteria(
2467 &program,
2468 &ProgramCacheMatchCriteria::NoCriteria
2469 ));
2470
2471 assert!(!ProgramCache::matches_criteria(
2472 &program,
2473 &ProgramCacheMatchCriteria::Tombstone
2474 ));
2475
2476 assert!(ProgramCache::matches_criteria(
2477 &program,
2478 &ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(0)
2479 ));
2480
2481 assert!(!ProgramCache::matches_criteria(
2482 &program,
2483 &ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(1)
2484 ));
2485
2486 let program = Arc::new(new_test_entry_with_usage(0, 1, AtomicU64::default()));
2487
2488 assert!(ProgramCache::matches_criteria(
2489 &program,
2490 &ProgramCacheMatchCriteria::NoCriteria
2491 ));
2492
2493 assert!(!ProgramCache::matches_criteria(
2494 &program,
2495 &ProgramCacheMatchCriteria::Tombstone
2496 ));
2497
2498 assert!(ProgramCache::matches_criteria(
2499 &program,
2500 &ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(0)
2501 ));
2502
2503 assert!(!ProgramCache::matches_criteria(
2504 &program,
2505 &ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(1)
2506 ));
2507 }
2508}