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 pub evictions: HashMap<Pubkey, u64>,
188 pub reloads: AtomicU64,
190 pub insertions: AtomicU64,
192 pub lost_insertions: AtomicU64,
194 pub replacements: AtomicU64,
196 pub one_hit_wonders: AtomicU64,
198 pub empty_entries: AtomicU64,
200 pub water_level: AtomicU64,
202}
203
204impl ProgramCacheStats {
205 pub fn reset(&mut self) {
206 *self = ProgramCacheStats::default();
207 }
208 pub fn log(&self) {
209 let hits = self.hits.load(Ordering::Relaxed);
210 let misses = self.misses.load(Ordering::Relaxed);
211 let evictions: u64 = self.evictions.values().sum();
212 let reloads = self.reloads.load(Ordering::Relaxed);
213 let insertions = self.insertions.load(Ordering::Relaxed);
214 let lost_insertions = self.lost_insertions.load(Ordering::Relaxed);
215 let replacements = self.replacements.load(Ordering::Relaxed);
216 let one_hit_wonders = self.one_hit_wonders.load(Ordering::Relaxed);
217 let empty_entries = self.empty_entries.load(Ordering::Relaxed);
218 let water_level = self.water_level.load(Ordering::Relaxed);
219 debug!(
220 "Loaded Programs Cache Stats -- Hits: {}, Misses: {}, Evictions: {}, Reloads: {}, Insertions: {}, Lost-Insertions: {}, Replacements: {}, One-Hit-Wonders: {}, Empty: {}, Water-Level: {}",
221 hits, misses, evictions, reloads, insertions, lost_insertions, replacements, one_hit_wonders, empty_entries, water_level
222 );
223 if log_enabled!(log::Level::Trace) && !self.evictions.is_empty() {
224 let mut evictions = self.evictions.iter().collect::<Vec<_>>();
225 evictions.sort_by_key(|e| e.1);
226 let evictions = evictions
227 .into_iter()
228 .rev()
229 .map(|(program_id, evictions)| {
230 format!(" {:<44} {}", program_id.to_string(), evictions)
231 })
232 .collect::<Vec<_>>();
233 let evictions = evictions.join("\n");
234 trace!(
235 "Eviction Details:\n {:<44} {}\n{}",
236 "Program",
237 "Count",
238 evictions
239 );
240 }
241 }
242}
243
244#[cfg(feature = "metrics")]
245#[derive(Debug, Default)]
247pub struct LoadProgramMetrics {
248 pub program_id: String,
250 pub register_syscalls_us: u64,
252}
253
254#[cfg(feature = "metrics")]
255impl LoadProgramMetrics {
256 pub fn submit_datapoint(&self, timings: &mut ExecuteDetailsTimings) {
257 timings.create_executor_register_syscalls_us += self.register_syscalls_us;
258 datapoint_trace!(
259 "create_executor_trace",
260 ("program_id", self.program_id, String),
261 ("register_syscalls_us", self.register_syscalls_us, i64),
262 );
263 }
264}
265
266impl PartialEq for ProgramCacheEntry {
267 fn eq(&self, other: &Self) -> bool {
268 self.effective_slot == other.effective_slot
269 && self.deployment_slot == other.deployment_slot
270 && self.is_tombstone() == other.is_tombstone()
271 }
272}
273
274impl ProgramCacheEntry {
275 pub fn new_risc_v(
277 loader_key: &Pubkey,
278 deployment_slot: Slot,
279 effective_slot: Slot,
280 elf_bytes: &[u8],
281 account_size: usize,
282 ) -> Result<Self, Box<dyn std::error::Error>> {
283 Self::new_internal_risc_v(
284 loader_key,
285 deployment_slot,
286 effective_slot,
287 elf_bytes,
288 account_size,
289 false, )
291 }
292
293 pub unsafe fn reload_risc_v(
302 loader_key: &Pubkey,
303 deployment_slot: Slot,
304 effective_slot: Slot,
305 elf_bytes: &[u8],
306 account_size: usize,
307 ) -> Result<Self, Box<dyn std::error::Error>> {
308 Self::new_internal_risc_v(
309 loader_key,
310 deployment_slot,
311 effective_slot,
312 elf_bytes,
313 account_size,
314 true, )
316 }
317
318 fn new_internal_risc_v(
319 loader_key: &Pubkey,
320 deployment_slot: Slot,
321 effective_slot: Slot,
322 elf_bytes: &[u8],
323 account_size: usize,
324 reloading: bool,
325 ) -> Result<Self, Box<dyn std::error::Error>> {
326 if !reloading {
334 const MAX_STACK_SIZE: u32 = 262144;
335
336 let program = Into::<ArcBytes>::into(elf_bytes);
337
338 let program_parts = ProgramParts::from_bytes(program.clone())
339 .map_err(|error| format!("failed to parse program: {error:?}"))?;
340
341 if !program_parts.is_64_bit {
342 return Err(String::from("program is not a 64-bit program").into());
343 }
344
345 if program_parts.stack_size > MAX_STACK_SIZE {
346 return Err(String::from("program stack is too large").into());
347 }
348
349 let blob = ProgramBlob::parse(program)
350 .map_err(|error| format!("failed to parse program into program blob: {error:?}"))?;
351
352 if !blob
353 .exports()
354 .any(|entry| entry.symbol().as_bytes() == b"entrypoint")
355 {
356 return Err(String::from("program doesn't have an entrypoint").into());
357 }
358 }
359
360 Ok(Self {
361 deployment_slot,
362 account_owner: ProgramCacheEntryOwner::try_from(loader_key).unwrap(),
363 account_size,
364 effective_slot,
365 tx_usage_counter: AtomicU64::new(0),
366 program: ProgramCacheEntryType::Loaded(ExecutableKind::RiscV(elf_bytes.to_vec())),
367 ix_usage_counter: AtomicU64::new(0),
368 latest_access_slot: AtomicU64::new(0),
369 })
370 }
371
372 pub fn to_unloaded(&self) -> Option<Self> {
373 match &self.program {
374 ProgramCacheEntryType::Loaded(_) => {}
375 ProgramCacheEntryType::FailedVerification
376 | ProgramCacheEntryType::Closed
377 | ProgramCacheEntryType::DelayVisibility
378 | ProgramCacheEntryType::Unloaded
379 | ProgramCacheEntryType::Builtin(_) => {
380 return None;
381 }
382 }
383 Some(Self {
384 program: ProgramCacheEntryType::Unloaded,
385 account_owner: self.account_owner,
386 account_size: self.account_size,
387 deployment_slot: self.deployment_slot,
388 effective_slot: self.effective_slot,
389 tx_usage_counter: AtomicU64::new(self.tx_usage_counter.load(Ordering::Relaxed)),
390 ix_usage_counter: AtomicU64::new(self.ix_usage_counter.load(Ordering::Relaxed)),
391 latest_access_slot: AtomicU64::new(self.latest_access_slot.load(Ordering::Relaxed)),
392 })
393 }
394
395 pub fn new_builtin(
397 deployment_slot: Slot,
398 account_size: usize,
399 builtin_function: BuiltinFunctionWithContext,
400 ) -> Self {
401 Self {
402 deployment_slot,
403 account_owner: ProgramCacheEntryOwner::NativeLoader,
404 account_size,
405 effective_slot: deployment_slot,
406 tx_usage_counter: AtomicU64::new(0),
407 program: ProgramCacheEntryType::Builtin(builtin_function),
408 ix_usage_counter: AtomicU64::new(0),
409 latest_access_slot: AtomicU64::new(0),
410 }
411 }
412
413 pub fn new_tombstone(
414 slot: Slot,
415 account_owner: ProgramCacheEntryOwner,
416 reason: ProgramCacheEntryType,
417 ) -> Self {
418 let tombstone = Self {
419 program: reason,
420 account_owner,
421 account_size: 0,
422 deployment_slot: slot,
423 effective_slot: slot,
424 tx_usage_counter: AtomicU64::default(),
425 ix_usage_counter: AtomicU64::default(),
426 latest_access_slot: AtomicU64::new(0),
427 };
428 debug_assert!(tombstone.is_tombstone());
429 tombstone
430 }
431
432 pub fn is_tombstone(&self) -> bool {
433 matches!(
434 self.program,
435 ProgramCacheEntryType::FailedVerification
436 | ProgramCacheEntryType::Closed
437 | ProgramCacheEntryType::DelayVisibility
438 )
439 }
440
441 fn is_implicit_delay_visibility_tombstone(&self, slot: Slot) -> bool {
442 !matches!(self.program, ProgramCacheEntryType::Builtin(_))
443 && self.effective_slot.saturating_sub(self.deployment_slot)
444 == DELAY_VISIBILITY_SLOT_OFFSET
445 && slot >= self.deployment_slot
446 && slot < self.effective_slot
447 }
448
449 pub fn update_access_slot(&self, slot: Slot) {
450 let _ = self.latest_access_slot.fetch_max(slot, Ordering::Relaxed);
451 }
452
453 pub fn decayed_usage_counter(&self, now: Slot) -> u64 {
454 let last_access = self.latest_access_slot.load(Ordering::Relaxed);
455 let decaying_for = std::cmp::min(63, now.saturating_sub(last_access));
457 self.tx_usage_counter.load(Ordering::Relaxed) >> decaying_for
458 }
459
460 pub fn account_owner(&self) -> Pubkey {
461 self.account_owner.into()
462 }
463}
464
465#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
466pub struct LoadingTaskCookie(u64);
467
468impl LoadingTaskCookie {
469 fn new() -> Self {
470 Self(0)
471 }
472
473 fn update(&mut self) {
474 let LoadingTaskCookie(cookie) = self;
475 *cookie = cookie.wrapping_add(1);
476 }
477}
478
479#[derive(Debug, Default)]
481pub struct LoadingTaskWaiter {
482 cookie: Mutex<LoadingTaskCookie>,
483 cond: Condvar,
484}
485
486impl LoadingTaskWaiter {
487 pub fn new() -> Self {
488 Self {
489 cookie: Mutex::new(LoadingTaskCookie::new()),
490 cond: Condvar::new(),
491 }
492 }
493
494 pub fn cookie(&self) -> LoadingTaskCookie {
495 *self.cookie.lock().unwrap()
496 }
497
498 pub fn notify(&self) {
499 let mut cookie = self.cookie.lock().unwrap();
500 cookie.update();
501 self.cond.notify_all();
502 }
503
504 pub fn wait(&self, cookie: LoadingTaskCookie) -> LoadingTaskCookie {
505 let cookie_guard = self.cookie.lock().unwrap();
506 *self
507 .cond
508 .wait_while(cookie_guard, |current_cookie| *current_cookie == cookie)
509 .unwrap()
510 }
511}
512
513#[derive(Debug)]
514enum IndexImplementation {
515 V1 {
517 entries: HashMap<Pubkey, Vec<Arc<ProgramCacheEntry>>>,
522 loading_entries: Mutex<HashMap<Pubkey, (Slot, thread::ThreadId)>>,
531 },
532}
533
534pub struct ProgramCache {
550 index: IndexImplementation,
552 pub stats: ProgramCacheStats,
554 pub loading_task_waiter: Arc<LoadingTaskWaiter>,
556}
557
558impl Debug for ProgramCache {
559 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
560 f.debug_struct("ProgramCache")
561 .field("stats", &self.stats)
562 .field("index", &self.index)
563 .finish()
564 }
565}
566
567pub struct ProgramCacheForTx<'a> {
573 entries: &'a HashMap<Pubkey, Arc<ProgramCacheEntry>>,
576 modified_entries: HashMap<Pubkey, Arc<ProgramCacheEntry>>,
578 slot: Slot,
579 pub hit_max_limit: bool,
580 pub loaded_missing: bool,
581 pub merged_modified: bool,
582}
583
584impl<'a> ProgramCacheForTx<'a> {
585 pub fn from_cache(cache: &'a ProgramCacheForTxBatch) -> Self {
586 Self {
587 entries: &cache.entries,
588 modified_entries: HashMap::new(),
589 slot: cache.slot,
590 hit_max_limit: cache.hit_max_limit,
591 loaded_missing: cache.loaded_missing,
592 merged_modified: cache.merged_modified,
593 }
594 }
595
596 pub fn store_modified_entry(&mut self, key: Pubkey, entry: Arc<ProgramCacheEntry>) {
599 self.modified_entries.insert(key, entry);
600 }
601
602 pub fn drain_modified_entries(&mut self) -> HashMap<Pubkey, Arc<ProgramCacheEntry>> {
605 std::mem::take(&mut self.modified_entries)
606 }
607
608 pub fn find(&self, key: &Pubkey) -> Option<Arc<ProgramCacheEntry>> {
609 self.modified_entries
613 .get(key)
614 .or(self.entries.get(key))
615 .map(|entry| {
616 if entry.is_implicit_delay_visibility_tombstone(self.slot) {
617 Arc::new(ProgramCacheEntry::new_tombstone(
621 entry.deployment_slot,
622 entry.account_owner,
623 ProgramCacheEntryType::DelayVisibility,
624 ))
625 } else {
626 entry.clone()
627 }
628 })
629 }
630
631 pub fn slot(&self) -> Slot {
632 self.slot
633 }
634
635 pub fn set_slot_for_tests(&mut self, slot: Slot) {
636 self.slot = slot;
637 }
638
639 pub fn is_empty(&self) -> bool {
640 self.entries.is_empty()
641 }
642}
643
644#[derive(Clone, Debug, Default)]
650pub struct ProgramCacheForTxBatch {
651 entries: HashMap<Pubkey, Arc<ProgramCacheEntry>>,
654 modified_entries: HashMap<Pubkey, Arc<ProgramCacheEntry>>,
656 slot: Slot,
657 pub hit_max_limit: bool,
658 pub loaded_missing: bool,
659 pub merged_modified: bool,
660}
661
662impl ProgramCacheForTxBatch {
663 pub fn new(slot: Slot) -> Self {
664 Self {
665 entries: HashMap::new(),
666 modified_entries: HashMap::new(),
667 slot,
668 hit_max_limit: false,
669 loaded_missing: false,
670 merged_modified: false,
671 }
672 }
673
674 pub fn replenish(
680 &mut self,
681 key: Pubkey,
682 entry: Arc<ProgramCacheEntry>,
683 ) -> (bool, Arc<ProgramCacheEntry>) {
684 (self.entries.insert(key, entry.clone()).is_some(), entry)
685 }
686
687 pub fn store_modified_entry(&mut self, key: Pubkey, entry: Arc<ProgramCacheEntry>) {
690 self.modified_entries.insert(key, entry);
691 }
692
693 pub fn drain_modified_entries(&mut self) -> HashMap<Pubkey, Arc<ProgramCacheEntry>> {
696 std::mem::take(&mut self.modified_entries)
697 }
698
699 pub fn find(&self, key: &Pubkey) -> Option<Arc<ProgramCacheEntry>> {
700 self.modified_entries
704 .get(key)
705 .or(self.entries.get(key))
706 .map(|entry| {
707 if entry.is_implicit_delay_visibility_tombstone(self.slot) {
708 Arc::new(ProgramCacheEntry::new_tombstone(
712 entry.deployment_slot,
713 entry.account_owner,
714 ProgramCacheEntryType::DelayVisibility,
715 ))
716 } else {
717 entry.clone()
718 }
719 })
720 }
721
722 pub fn slot(&self) -> Slot {
723 self.slot
724 }
725
726 #[cfg(any(test, feature = "dev-context-only-utils"))]
727 pub fn set_slot_for_tests(&mut self, slot: Slot) {
728 self.slot = slot;
729 }
730
731 pub fn merge(&mut self, modified_entries: &HashMap<Pubkey, Arc<ProgramCacheEntry>>) {
732 modified_entries.iter().for_each(|(key, entry)| {
733 self.merged_modified = true;
734 self.replenish(*key, entry.clone());
735 })
736 }
737
738 pub fn is_empty(&self) -> bool {
739 self.entries.is_empty()
740 }
741}
742
743pub enum ProgramCacheMatchCriteria {
744 DeployedOnOrAfterSlot(Slot),
745 Tombstone,
746 NoCriteria,
747}
748
749impl Default for ProgramCache {
750 fn default() -> Self {
751 Self::new()
752 }
753}
754
755impl ProgramCache {
756 pub fn new() -> Self {
757 Self {
758 index: IndexImplementation::V1 {
759 entries: HashMap::new(),
760 loading_entries: Mutex::new(HashMap::new()),
761 },
762 stats: ProgramCacheStats::default(),
763 loading_task_waiter: Arc::new(LoadingTaskWaiter::default()),
764 }
765 }
766
767 pub fn assign_program(&mut self, key: Pubkey, entry: Arc<ProgramCacheEntry>) -> bool {
770 debug_assert!(!matches!(
771 &entry.program,
772 ProgramCacheEntryType::DelayVisibility
773 ));
774 match &mut self.index {
775 IndexImplementation::V1 { entries, .. } => {
776 let slot_versions = &mut entries.entry(key).or_default();
777 match slot_versions.binary_search_by(|at| {
778 at.effective_slot
779 .cmp(&entry.effective_slot)
780 .then(at.deployment_slot.cmp(&entry.deployment_slot))
781 }) {
782 Ok(index) => {
783 let existing = slot_versions.get_mut(index).unwrap();
784 match (&existing.program, &entry.program) {
785 (
786 ProgramCacheEntryType::Builtin(_),
787 ProgramCacheEntryType::Builtin(_),
788 )
789 | (ProgramCacheEntryType::Unloaded, ProgramCacheEntryType::Loaded(_)) =>
790 {}
791 _ => {
792 error!("ProgramCache::assign_program() failed key={:?} existing={:?} entry={:?}", key, slot_versions, entry);
794 debug_assert!(false, "Unexpected replacement of an entry");
795 self.stats.replacements.fetch_add(1, Ordering::Relaxed);
796 return true;
797 }
798 }
799 entry.tx_usage_counter.fetch_add(
801 existing.tx_usage_counter.load(Ordering::Relaxed),
802 Ordering::Relaxed,
803 );
804 entry.ix_usage_counter.fetch_add(
805 existing.ix_usage_counter.load(Ordering::Relaxed),
806 Ordering::Relaxed,
807 );
808 *existing = Arc::clone(&entry);
809 self.stats.reloads.fetch_add(1, Ordering::Relaxed);
810 }
811 Err(index) => {
812 self.stats.insertions.fetch_add(1, Ordering::Relaxed);
813 slot_versions.insert(index, Arc::clone(&entry));
814 }
815 }
816 }
817 }
818 false
819 }
820
821 fn matches_criteria(
822 program: &Arc<ProgramCacheEntry>,
823 criteria: &ProgramCacheMatchCriteria,
824 ) -> bool {
825 match criteria {
826 ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(slot) => {
827 program.deployment_slot >= *slot
828 }
829 ProgramCacheMatchCriteria::Tombstone => program.is_tombstone(),
830 ProgramCacheMatchCriteria::NoCriteria => true,
831 }
832 }
833
834 pub fn extract(
837 &self,
838 search_for: &mut Vec<(Pubkey, (ProgramCacheMatchCriteria, u64))>,
839 loaded_programs_for_tx_batch: &mut ProgramCacheForTxBatch,
840 is_first_round: bool,
841 ) -> Option<(Pubkey, u64)> {
842 let mut cooperative_loading_task = None;
843 match &self.index {
844 IndexImplementation::V1 {
845 entries,
846 loading_entries,
847 } => {
848 search_for.retain(|(key, (match_criteria, usage_count))| {
849 if let Some(second_level) = entries.get(key) {
850 for entry in second_level.iter().rev() {
851 if entry.deployment_slot <= loaded_programs_for_tx_batch.slot {
852 let entry_to_return = if loaded_programs_for_tx_batch.slot
853 >= entry.effective_slot
854 {
855 if !Self::matches_criteria(entry, match_criteria) {
856 break;
857 }
858 if matches!(&entry.program, ProgramCacheEntryType::Unloaded) {
859 break;
860 }
861 entry.clone()
862 } else if entry.is_implicit_delay_visibility_tombstone(
863 loaded_programs_for_tx_batch.slot,
864 ) {
865 Arc::new(ProgramCacheEntry::new_tombstone(
869 entry.deployment_slot,
870 entry.account_owner,
871 ProgramCacheEntryType::DelayVisibility,
872 ))
873 } else {
874 continue;
875 };
876 entry_to_return
877 .update_access_slot(loaded_programs_for_tx_batch.slot);
878 entry_to_return
879 .tx_usage_counter
880 .fetch_add(*usage_count, Ordering::Relaxed);
881 loaded_programs_for_tx_batch
882 .entries
883 .insert(*key, entry_to_return);
884 return false;
885 }
886 }
887 }
888 if cooperative_loading_task.is_none() {
889 let mut loading_entries = loading_entries.lock().unwrap();
890 let entry = loading_entries.entry(*key);
891 if let Entry::Vacant(entry) = entry {
892 entry.insert((
893 loaded_programs_for_tx_batch.slot,
894 thread::current().id(),
895 ));
896 cooperative_loading_task = Some((*key, *usage_count));
897 }
898 }
899 true
900 });
901 }
902 }
903 if is_first_round {
904 self.stats
905 .misses
906 .fetch_add(search_for.len() as u64, Ordering::Relaxed);
907 self.stats.hits.fetch_add(
908 loaded_programs_for_tx_batch.entries.len() as u64,
909 Ordering::Relaxed,
910 );
911 }
912 cooperative_loading_task
913 }
914
915 pub fn finish_cooperative_loading_task(
917 &mut self,
918 slot: Slot,
919 key: Pubkey,
920 loaded_program: Arc<ProgramCacheEntry>,
921 ) -> bool {
922 match &mut self.index {
923 IndexImplementation::V1 {
924 loading_entries, ..
925 } => {
926 let loading_thread = loading_entries.get_mut().unwrap().remove(&key);
927 debug_assert_eq!(loading_thread, Some((slot, thread::current().id())));
928 if loaded_program.deployment_slot > slot {
930 self.stats.lost_insertions.fetch_add(1, Ordering::Relaxed);
931 }
932 let was_occupied = self.assign_program(key, loaded_program);
933 self.loading_task_waiter.notify();
934 was_occupied
935 }
936 }
937 }
938
939 pub fn merge(&mut self, modified_entries: &HashMap<Pubkey, Arc<ProgramCacheEntry>>) {
940 modified_entries.iter().for_each(|(key, entry)| {
941 self.assign_program(*key, entry.clone());
942 })
943 }
944
945 fn get_flattened_entries(&self) -> Vec<(Pubkey, Arc<ProgramCacheEntry>)> {
947 match &self.index {
948 IndexImplementation::V1 { entries, .. } => entries
949 .iter()
950 .flat_map(|(id, second_level)| {
951 second_level
952 .iter()
953 .filter_map(move |program| match program.program {
954 ProgramCacheEntryType::Loaded(_) => Some((*id, program.clone())),
955 _ => None,
956 })
957 })
958 .collect(),
959 }
960 }
961
962 #[cfg(any(test, feature = "dev-context-only-utils"))]
964 pub fn get_flattened_entries_for_tests(&self) -> Vec<(Pubkey, Arc<ProgramCacheEntry>)> {
965 match &self.index {
966 IndexImplementation::V1 { entries, .. } => entries
967 .iter()
968 .flat_map(|(id, second_level)| {
969 second_level.iter().map(|program| (*id, program.clone()))
970 })
971 .collect(),
972 }
973 }
974
975 #[cfg(any(test, feature = "dev-context-only-utils"))]
977 pub fn get_slot_versions_for_tests(&self, key: &Pubkey) -> &[Arc<ProgramCacheEntry>] {
978 match &self.index {
979 IndexImplementation::V1 { entries, .. } => entries
980 .get(key)
981 .map(|second_level| second_level.as_ref())
982 .unwrap_or(&[]),
983 }
984 }
985
986 pub fn sort_and_unload(&mut self, shrink_to: PercentageInteger) {
988 let mut sorted_candidates = self.get_flattened_entries();
989 sorted_candidates
990 .sort_by_cached_key(|(_id, program)| program.tx_usage_counter.load(Ordering::Relaxed));
991 let num_to_unload = sorted_candidates
992 .len()
993 .saturating_sub(shrink_to.apply_to(MAX_LOADED_ENTRY_COUNT));
994 self.unload_program_entries(sorted_candidates.iter().take(num_to_unload));
995 }
996
997 pub fn evict_using_2s_random_selection(&mut self, shrink_to: PercentageInteger, now: Slot) {
1000 let mut candidates = self.get_flattened_entries();
1001 self.stats
1002 .water_level
1003 .store(candidates.len() as u64, Ordering::Relaxed);
1004 let num_to_unload = candidates
1005 .len()
1006 .saturating_sub(shrink_to.apply_to(MAX_LOADED_ENTRY_COUNT));
1007 fn random_index_and_usage_counter(
1008 candidates: &[(Pubkey, Arc<ProgramCacheEntry>)],
1009 now: Slot,
1010 ) -> (usize, u64) {
1011 let mut rng = thread_rng();
1012 let index = rng.gen_range(0..candidates.len());
1013 let usage_counter = candidates
1014 .get(index)
1015 .expect("Failed to get cached entry")
1016 .1
1017 .decayed_usage_counter(now);
1018 (index, usage_counter)
1019 }
1020
1021 for _ in 0..num_to_unload {
1022 let (index1, usage_counter1) = random_index_and_usage_counter(&candidates, now);
1023 let (index2, usage_counter2) = random_index_and_usage_counter(&candidates, now);
1024
1025 let (program, entry) = if usage_counter1 < usage_counter2 {
1026 candidates.swap_remove(index1)
1027 } else {
1028 candidates.swap_remove(index2)
1029 };
1030 self.unload_program_entry(&program, &entry);
1031 }
1032 }
1033
1034 pub fn remove_programs(&mut self, keys: impl Iterator<Item = Pubkey>) {
1036 match &mut self.index {
1037 IndexImplementation::V1 { entries, .. } => {
1038 for k in keys {
1039 entries.remove(&k);
1040 }
1041 }
1042 }
1043 }
1044
1045 fn unload_program_entry(&mut self, program: &Pubkey, remove_entry: &Arc<ProgramCacheEntry>) {
1048 match &mut self.index {
1049 IndexImplementation::V1 { entries, .. } => {
1050 let second_level = entries.get_mut(program).expect("Cache lookup failed");
1051 let candidate = second_level
1052 .iter_mut()
1053 .find(|entry| entry == &remove_entry)
1054 .expect("Program entry not found");
1055
1056 if let Some(unloaded) = candidate.to_unloaded() {
1060 if candidate.tx_usage_counter.load(Ordering::Relaxed) == 1 {
1061 self.stats.one_hit_wonders.fetch_add(1, Ordering::Relaxed);
1062 }
1063 self.stats
1064 .evictions
1065 .entry(*program)
1066 .and_modify(|c| *c = c.saturating_add(1))
1067 .or_insert(1);
1068 *candidate = Arc::new(unloaded);
1069 }
1070 }
1071 }
1072 }
1073
1074 fn unload_program_entries<'a>(
1075 &mut self,
1076 remove: impl Iterator<Item = &'a (Pubkey, Arc<ProgramCacheEntry>)>,
1077 ) {
1078 for (program, entry) in remove {
1079 self.unload_program_entry(program, entry);
1080 }
1081 }
1082}
1083
1084#[cfg(feature = "frozen-abi")]
1085impl rialo_frozen_abi::abi_example::AbiExample for ProgramCacheEntry {
1086 fn example() -> Self {
1087 Self::default()
1089 }
1090}
1091
1092#[cfg(feature = "frozen-abi")]
1093impl rialo_frozen_abi::abi_example::AbiExample for ProgramCache {
1094 fn example() -> Self {
1095 Self::new()
1097 }
1098}
1099
1100#[cfg(false)] #[cfg(test)]
1102mod tests {
1103 use std::{
1104 fs::File,
1105 io::Read,
1106 sync::{
1107 atomic::{AtomicU64, Ordering},
1108 Arc,
1109 },
1110 };
1111
1112 use assert_matches::assert_matches;
1113 use percentage::Percentage;
1114 use rialo_s_clock::Slot;
1115 use rialo_s_pubkey::Pubkey;
1116 use solana_sbpf::{elf::Executable, program::BuiltinProgram};
1117 use test_case::{test_case, test_matrix};
1118
1119 use crate::loaded_programs::{
1120 ProgramCache, ProgramCacheEntry, ProgramCacheEntryOwner, ProgramCacheEntryType,
1121 ProgramCacheForTxBatch, ProgramCacheMatchCriteria, ProgramRuntimeEnvironment,
1122 ProgramRuntimeEnvironments, DELAY_VISIBILITY_SLOT_OFFSET,
1123 };
1124
1125 static MOCK_ENVIRONMENT: std::sync::OnceLock<ProgramRuntimeEnvironment> =
1126 std::sync::OnceLock::<ProgramRuntimeEnvironment>::new();
1127
1128 fn get_mock_env() -> ProgramRuntimeEnvironment {
1129 MOCK_ENVIRONMENT
1130 .get_or_init(|| Arc::new(BuiltinProgram::new_mock()))
1131 .clone()
1132 }
1133
1134 fn new_mock_cache() -> ProgramCache {
1135 let mut cache = ProgramCache::new(0, 0);
1136 cache.environments.program_runtime_v1 = get_mock_env();
1137 cache
1138 }
1139
1140 fn new_test_entry(deployment_slot: Slot, effective_slot: Slot) -> Arc<ProgramCacheEntry> {
1141 new_test_entry_with_usage(deployment_slot, effective_slot, AtomicU64::default())
1142 }
1143
1144 fn new_loaded_entry(env: ProgramRuntimeEnvironment) -> ProgramCacheEntryType {
1145 let mut elf = Vec::new();
1146 File::open("../runtime/vm/ebpf/bpf_loader/test_elfs/out/noop_aligned.so")
1147 .unwrap()
1148 .read_to_end(&mut elf)
1149 .unwrap();
1150 let executable = Executable::load(&elf, env).unwrap();
1151 ProgramCacheEntryType::Loaded(super::ExecutableKind::Ebpf(executable))
1152 }
1153
1154 fn new_test_entry_with_usage(
1155 deployment_slot: Slot,
1156 effective_slot: Slot,
1157 usage_counter: AtomicU64,
1158 ) -> Arc<ProgramCacheEntry> {
1159 Arc::new(ProgramCacheEntry {
1160 program: new_loaded_entry(get_mock_env()),
1161 account_owner: ProgramCacheEntryOwner::LoaderV2,
1162 account_size: 0,
1163 deployment_slot,
1164 effective_slot,
1165 tx_usage_counter: usage_counter,
1166 ix_usage_counter: AtomicU64::default(),
1167 latest_access_slot: AtomicU64::new(deployment_slot),
1168 })
1169 }
1170
1171 fn new_test_builtin_entry(
1172 deployment_slot: Slot,
1173 effective_slot: Slot,
1174 ) -> Arc<ProgramCacheEntry> {
1175 Arc::new(ProgramCacheEntry {
1176 program: ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1177 account_owner: ProgramCacheEntryOwner::NativeLoader,
1178 account_size: 0,
1179 deployment_slot,
1180 effective_slot,
1181 tx_usage_counter: AtomicU64::default(),
1182 ix_usage_counter: AtomicU64::default(),
1183 latest_access_slot: AtomicU64::default(),
1184 })
1185 }
1186
1187 fn set_tombstone(
1188 cache: &mut ProgramCache,
1189 key: Pubkey,
1190 slot: Slot,
1191 reason: ProgramCacheEntryType,
1192 ) -> Arc<ProgramCacheEntry> {
1193 let program = Arc::new(ProgramCacheEntry::new_tombstone(
1194 slot,
1195 ProgramCacheEntryOwner::LoaderV2,
1196 reason,
1197 ));
1198 cache.assign_program(key, program.clone());
1199 program
1200 }
1201
1202 fn insert_unloaded_entry(
1203 cache: &mut ProgramCache,
1204 key: Pubkey,
1205 slot: Slot,
1206 ) -> Arc<ProgramCacheEntry> {
1207 let loaded = new_test_entry_with_usage(slot, slot.saturating_add(1), AtomicU64::default());
1208 let unloaded = Arc::new(loaded.to_unloaded().expect("Failed to unload the program"));
1209 cache.assign_program(key, unloaded.clone());
1210 unloaded
1211 }
1212
1213 fn num_matching_entries<P>(cache: &ProgramCache, predicate: P) -> usize
1214 where
1215 P: Fn(&ProgramCacheEntryType) -> bool,
1216 {
1217 cache
1218 .get_flattened_entries_for_tests()
1219 .iter()
1220 .filter(|(_key, program)| predicate(&program.program))
1221 .count()
1222 }
1223
1224 #[test]
1225 fn test_usage_counter_decay() {
1226 let _cache = new_mock_cache();
1227 let program = new_test_entry_with_usage(10, 11, AtomicU64::new(32));
1228 program.update_access_slot(15);
1229 assert_eq!(program.decayed_usage_counter(15), 32);
1230 assert_eq!(program.decayed_usage_counter(16), 16);
1231 assert_eq!(program.decayed_usage_counter(17), 8);
1232 assert_eq!(program.decayed_usage_counter(18), 4);
1233 assert_eq!(program.decayed_usage_counter(19), 2);
1234 assert_eq!(program.decayed_usage_counter(20), 1);
1235 assert_eq!(program.decayed_usage_counter(21), 0);
1236 assert_eq!(program.decayed_usage_counter(15), 32);
1237 assert_eq!(program.decayed_usage_counter(14), 32);
1238
1239 program.update_access_slot(18);
1240 assert_eq!(program.decayed_usage_counter(15), 32);
1241 assert_eq!(program.decayed_usage_counter(16), 32);
1242 assert_eq!(program.decayed_usage_counter(17), 32);
1243 assert_eq!(program.decayed_usage_counter(18), 32);
1244 assert_eq!(program.decayed_usage_counter(19), 16);
1245 assert_eq!(program.decayed_usage_counter(20), 8);
1246 assert_eq!(program.decayed_usage_counter(21), 4);
1247
1248 assert_eq!(program.decayed_usage_counter(18 + 63), 0);
1250 assert_eq!(program.decayed_usage_counter(100), 0);
1251 }
1252
1253 fn program_deploy_test_helper(
1254 cache: &mut ProgramCache,
1255 program: Pubkey,
1256 deployment_slots: Vec<Slot>,
1257 usage_counters: Vec<u64>,
1258 programs: &mut Vec<(Pubkey, Slot, u64)>,
1259 ) {
1260 deployment_slots
1262 .iter()
1263 .enumerate()
1264 .for_each(|(i, deployment_slot)| {
1265 let usage_counter = *usage_counters.get(i).unwrap_or(&0);
1266 cache.assign_program(
1267 program,
1268 new_test_entry_with_usage(
1269 *deployment_slot,
1270 (*deployment_slot).saturating_add(2),
1271 AtomicU64::new(usage_counter),
1272 ),
1273 );
1274 programs.push((program, *deployment_slot, usage_counter));
1275 });
1276
1277 let env = Arc::new(BuiltinProgram::new_mock());
1279 for slot in 21..31 {
1280 set_tombstone(
1281 cache,
1282 program,
1283 slot,
1284 ProgramCacheEntryType::FailedVerification(env.clone()),
1285 );
1286 }
1287
1288 for slot in 31..41 {
1290 insert_unloaded_entry(cache, program, slot);
1291 }
1292 }
1293
1294 #[test]
1295 fn test_random_eviction() {
1296 let mut programs = vec![];
1297
1298 let mut cache = new_mock_cache();
1299
1300 program_deploy_test_helper(
1306 &mut cache,
1307 Pubkey::new_unique(),
1308 vec![0, 10, 20],
1309 vec![4, 5, 25],
1310 &mut programs,
1311 );
1312
1313 program_deploy_test_helper(
1315 &mut cache,
1316 Pubkey::new_unique(),
1317 vec![5, 11],
1318 vec![0, 2],
1319 &mut programs,
1320 );
1321
1322 program_deploy_test_helper(
1324 &mut cache,
1325 Pubkey::new_unique(),
1326 vec![0, 5, 15],
1327 vec![100, 3, 20],
1328 &mut programs,
1329 );
1330
1331 let num_loaded_expected = 8;
1333 let num_unloaded_expected = 30;
1335 let num_tombstones_expected = 30;
1337
1338 programs.sort_by_key(|(_id, _slot, usage_count)| *usage_count);
1340 let num_loaded = num_matching_entries(&cache, |program_type| {
1341 matches!(program_type, ProgramCacheEntryType::Loaded(_))
1342 });
1343 let num_unloaded = num_matching_entries(&cache, |program_type| {
1344 matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1345 });
1346 let num_tombstones = num_matching_entries(&cache, |program_type| {
1347 matches!(
1348 program_type,
1349 ProgramCacheEntryType::DelayVisibility
1350 | ProgramCacheEntryType::FailedVerification(_)
1351 | ProgramCacheEntryType::Closed
1352 )
1353 });
1354
1355 assert_eq!(num_loaded, num_loaded_expected);
1357 assert_eq!(num_unloaded, num_unloaded_expected);
1358 assert_eq!(num_tombstones, num_tombstones_expected);
1359
1360 let eviction_pct = 1;
1362
1363 let num_loaded_expected =
1364 Percentage::from(eviction_pct).apply_to(crate::loaded_programs::MAX_LOADED_ENTRY_COUNT);
1365 let num_unloaded_expected = num_unloaded_expected + num_loaded - num_loaded_expected;
1366 cache.evict_using_2s_random_selection(Percentage::from(eviction_pct), 21);
1367
1368 let num_loaded = num_matching_entries(&cache, |program_type| {
1370 matches!(program_type, ProgramCacheEntryType::Loaded(_))
1371 });
1372 let num_unloaded = num_matching_entries(&cache, |program_type| {
1373 matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1374 });
1375 let num_tombstones = num_matching_entries(&cache, |program_type| {
1376 matches!(program_type, ProgramCacheEntryType::FailedVerification(_))
1377 });
1378
1379 assert_eq!(num_loaded, num_loaded_expected);
1381 assert_eq!(num_unloaded, num_unloaded_expected);
1383 assert_eq!(num_tombstones, num_tombstones_expected);
1385 }
1386
1387 #[test]
1388 fn test_eviction() {
1389 let mut programs = vec![];
1390 let mut cache = new_mock_cache();
1391
1392 program_deploy_test_helper(
1394 &mut cache,
1395 Pubkey::new_unique(),
1396 vec![0, 10, 20],
1397 vec![4, 5, 25],
1398 &mut programs,
1399 );
1400
1401 program_deploy_test_helper(
1403 &mut cache,
1404 Pubkey::new_unique(),
1405 vec![5, 11],
1406 vec![0, 2],
1407 &mut programs,
1408 );
1409
1410 program_deploy_test_helper(
1412 &mut cache,
1413 Pubkey::new_unique(),
1414 vec![0, 5, 15],
1415 vec![100, 3, 20],
1416 &mut programs,
1417 );
1418
1419 let num_loaded_expected = 8;
1421 let num_unloaded_expected = 30;
1423 let num_tombstones_expected = 30;
1425
1426 programs.sort_by_key(|(_id, _slot, usage_count)| *usage_count);
1428 let num_loaded = num_matching_entries(&cache, |program_type| {
1429 matches!(program_type, ProgramCacheEntryType::Loaded(_))
1430 });
1431 let num_unloaded = num_matching_entries(&cache, |program_type| {
1432 matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1433 });
1434 let num_tombstones = num_matching_entries(&cache, |program_type| {
1435 matches!(program_type, ProgramCacheEntryType::FailedVerification(_))
1436 });
1437
1438 assert_eq!(num_loaded, num_loaded_expected);
1440 assert_eq!(num_unloaded, num_unloaded_expected);
1441 assert_eq!(num_tombstones, num_tombstones_expected);
1442
1443 let eviction_pct = 1;
1445
1446 let num_loaded_expected =
1447 Percentage::from(eviction_pct).apply_to(crate::loaded_programs::MAX_LOADED_ENTRY_COUNT);
1448 let num_unloaded_expected = num_unloaded_expected + num_loaded - num_loaded_expected;
1449
1450 cache.sort_and_unload(Percentage::from(eviction_pct));
1451
1452 let entries = cache.get_flattened_entries_for_tests();
1454 programs.iter().for_each(|entry| {
1455 assert!(entries.iter().any(|(key, _entry)| key == &entry.0));
1456 });
1457
1458 let unloaded = entries
1459 .iter()
1460 .filter_map(|(key, program)| {
1461 matches!(program.program, ProgramCacheEntryType::Unloaded(_))
1462 .then_some((*key, program.tx_usage_counter.load(Ordering::Relaxed)))
1463 })
1464 .collect::<Vec<(Pubkey, u64)>>();
1465
1466 for index in 0..3 {
1467 let expected = programs.get(index).expect("Missing program");
1468 assert!(unloaded.contains(&(expected.0, expected.2)));
1469 }
1470
1471 let num_loaded = num_matching_entries(&cache, |program_type| {
1473 matches!(program_type, ProgramCacheEntryType::Loaded(_))
1474 });
1475 let num_unloaded = num_matching_entries(&cache, |program_type| {
1476 matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1477 });
1478 let num_tombstones = num_matching_entries(&cache, |program_type| {
1479 matches!(
1480 program_type,
1481 ProgramCacheEntryType::DelayVisibility
1482 | ProgramCacheEntryType::FailedVerification(_)
1483 | ProgramCacheEntryType::Closed
1484 )
1485 });
1486
1487 assert_eq!(num_loaded, num_loaded_expected);
1489 assert_eq!(num_unloaded, num_unloaded_expected);
1491 assert_eq!(num_tombstones, num_tombstones_expected);
1493 }
1494
1495 #[test]
1496 fn test_usage_count_of_unloaded_program() {
1497 let mut cache = new_mock_cache();
1498
1499 let program = Pubkey::new_unique();
1500 let evict_to_pct = 2;
1501 let cache_capacity_after_shrink =
1502 Percentage::from(evict_to_pct).apply_to(crate::loaded_programs::MAX_LOADED_ENTRY_COUNT);
1503 let num_total_programs = (cache_capacity_after_shrink + 1) as u64;
1505 (0..num_total_programs).for_each(|i| {
1506 cache.assign_program(
1507 program,
1508 new_test_entry_with_usage(i, i + 2, AtomicU64::new(i + 10)),
1509 );
1510 });
1511
1512 cache.sort_and_unload(Percentage::from(evict_to_pct));
1513
1514 let num_unloaded = num_matching_entries(&cache, |program_type| {
1515 matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1516 });
1517 assert_eq!(num_unloaded, 1);
1518
1519 cache
1520 .get_flattened_entries_for_tests()
1521 .iter()
1522 .for_each(|(_key, program)| {
1523 if matches!(program.program, ProgramCacheEntryType::Unloaded(_)) {
1524 assert_eq!(program.tx_usage_counter.load(Ordering::Relaxed), 10);
1526 assert_eq!(program.deployment_slot, 0);
1527 assert_eq!(program.effective_slot, 2);
1528 }
1529 });
1530
1531 cache.assign_program(program, new_test_entry_with_usage(0, 2, AtomicU64::new(0)));
1534
1535 cache
1536 .get_flattened_entries_for_tests()
1537 .iter()
1538 .for_each(|(_key, program)| {
1539 if matches!(program.program, ProgramCacheEntryType::Unloaded(_))
1540 && program.deployment_slot == 0
1541 && program.effective_slot == 2
1542 {
1543 assert_eq!(program.tx_usage_counter.load(Ordering::Relaxed), 10);
1545 }
1546 });
1547 }
1548
1549 #[test]
1550 fn test_fuzz_assign_program_order() {
1551 use rand::prelude::SliceRandom;
1552 const EXPECTED_ENTRIES: [(u64, u64); 7] =
1553 [(1, 2), (5, 5), (5, 6), (5, 10), (9, 10), (10, 10), (3, 12)];
1554 let mut rng = rand::thread_rng();
1555 let program_id = Pubkey::new_unique();
1556 for _ in 0..1000 {
1557 let mut entries = EXPECTED_ENTRIES.to_vec();
1558 entries.shuffle(&mut rng);
1559 let mut cache = new_mock_cache();
1560 for (deployment_slot, effective_slot) in entries {
1561 assert!(!cache
1562 .assign_program(program_id, new_test_entry(deployment_slot, effective_slot)));
1563 }
1564 for ((deployment_slot, effective_slot), entry) in EXPECTED_ENTRIES
1565 .iter()
1566 .zip(cache.get_slot_versions_for_tests(&program_id).iter())
1567 {
1568 assert_eq!(entry.deployment_slot, *deployment_slot);
1569 assert_eq!(entry.effective_slot, *effective_slot);
1570 }
1571 }
1572 }
1573
1574 #[test_matrix(
1575 (
1576 ProgramCacheEntryType::Closed,
1577 ProgramCacheEntryType::FailedVerification(get_mock_env()),
1578 new_loaded_entry(get_mock_env()),
1579 ),
1580 (
1581 ProgramCacheEntryType::FailedVerification(get_mock_env()),
1582 ProgramCacheEntryType::Closed,
1583 ProgramCacheEntryType::Unloaded(get_mock_env()),
1584 new_loaded_entry(get_mock_env()),
1585 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1586 )
1587 )]
1588 #[test_matrix(
1589 (
1590 ProgramCacheEntryType::Unloaded(get_mock_env()),
1591 ),
1592 (
1593 ProgramCacheEntryType::FailedVerification(get_mock_env()),
1594 ProgramCacheEntryType::Closed,
1595 ProgramCacheEntryType::Unloaded(get_mock_env()),
1596 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1597 )
1598 )]
1599 #[test_matrix(
1600 (ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),),
1601 (
1602 ProgramCacheEntryType::FailedVerification(get_mock_env()),
1603 ProgramCacheEntryType::Closed,
1604 ProgramCacheEntryType::Unloaded(get_mock_env()),
1605 new_loaded_entry(get_mock_env()),
1606 )
1607 )]
1608 #[should_panic(expected = "Unexpected replacement of an entry")]
1609 fn test_assign_program_failure(old: ProgramCacheEntryType, new: ProgramCacheEntryType) {
1610 let mut cache = new_mock_cache();
1611 let program_id = Pubkey::new_unique();
1612 assert!(!cache.assign_program(
1613 program_id,
1614 Arc::new(ProgramCacheEntry {
1615 program: old,
1616 account_owner: ProgramCacheEntryOwner::LoaderV2,
1617 account_size: 0,
1618 deployment_slot: 10,
1619 effective_slot: 11,
1620 tx_usage_counter: AtomicU64::default(),
1621 ix_usage_counter: AtomicU64::default(),
1622 latest_access_slot: AtomicU64::default(),
1623 }),
1624 ));
1625 cache.assign_program(
1626 program_id,
1627 Arc::new(ProgramCacheEntry {
1628 program: new,
1629 account_owner: ProgramCacheEntryOwner::LoaderV2,
1630 account_size: 0,
1631 deployment_slot: 10,
1632 effective_slot: 11,
1633 tx_usage_counter: AtomicU64::default(),
1634 ix_usage_counter: AtomicU64::default(),
1635 latest_access_slot: AtomicU64::default(),
1636 }),
1637 );
1638 }
1639
1640 #[test_case(
1641 ProgramCacheEntryType::Unloaded(Arc::new(BuiltinProgram::new_mock())),
1642 new_loaded_entry(get_mock_env())
1643 )]
1644 #[test_case(
1645 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1646 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock())
1647 )]
1648 fn test_assign_program_success(old: ProgramCacheEntryType, new: ProgramCacheEntryType) {
1649 let mut cache = new_mock_cache();
1650 let program_id = Pubkey::new_unique();
1651 assert!(!cache.assign_program(
1652 program_id,
1653 Arc::new(ProgramCacheEntry {
1654 program: old,
1655 account_owner: ProgramCacheEntryOwner::LoaderV2,
1656 account_size: 0,
1657 deployment_slot: 10,
1658 effective_slot: 11,
1659 tx_usage_counter: AtomicU64::default(),
1660 ix_usage_counter: AtomicU64::default(),
1661 latest_access_slot: AtomicU64::default(),
1662 }),
1663 ));
1664 assert!(!cache.assign_program(
1665 program_id,
1666 Arc::new(ProgramCacheEntry {
1667 program: new,
1668 account_owner: ProgramCacheEntryOwner::LoaderV2,
1669 account_size: 0,
1670 deployment_slot: 10,
1671 effective_slot: 11,
1672 tx_usage_counter: AtomicU64::default(),
1673 ix_usage_counter: AtomicU64::default(),
1674 latest_access_slot: AtomicU64::default(),
1675 }),
1676 ));
1677 }
1678
1679 #[test]
1680 fn test_tombstone() {
1681 let env = Arc::new(BuiltinProgram::new_mock());
1682 let tombstone = ProgramCacheEntry::new_tombstone(
1683 0,
1684 ProgramCacheEntryOwner::LoaderV2,
1685 ProgramCacheEntryType::FailedVerification(env.clone()),
1686 );
1687 assert_matches!(
1688 tombstone.program,
1689 ProgramCacheEntryType::FailedVerification(_)
1690 );
1691 assert!(tombstone.is_tombstone());
1692 assert_eq!(tombstone.deployment_slot, 0);
1693 assert_eq!(tombstone.effective_slot, 0);
1694
1695 let tombstone = ProgramCacheEntry::new_tombstone(
1696 100,
1697 ProgramCacheEntryOwner::LoaderV2,
1698 ProgramCacheEntryType::Closed,
1699 );
1700 assert_matches!(tombstone.program, ProgramCacheEntryType::Closed);
1701 assert!(tombstone.is_tombstone());
1702 assert_eq!(tombstone.deployment_slot, 100);
1703 assert_eq!(tombstone.effective_slot, 100);
1704
1705 let mut cache = new_mock_cache();
1706 let program1 = Pubkey::new_unique();
1707 let tombstone = set_tombstone(
1708 &mut cache,
1709 program1,
1710 10,
1711 ProgramCacheEntryType::FailedVerification(env.clone()),
1712 );
1713 let slot_versions = cache.get_slot_versions_for_tests(&program1);
1714 assert_eq!(slot_versions.len(), 1);
1715 assert!(slot_versions.first().unwrap().is_tombstone());
1716 assert_eq!(tombstone.deployment_slot, 10);
1717 assert_eq!(tombstone.effective_slot, 10);
1718
1719 let program2 = Pubkey::new_unique();
1721 cache.assign_program(program2, new_test_builtin_entry(50, 51));
1722 let slot_versions = cache.get_slot_versions_for_tests(&program2);
1723 assert_eq!(slot_versions.len(), 1);
1724 assert!(!slot_versions.first().unwrap().is_tombstone());
1725
1726 let tombstone = set_tombstone(
1727 &mut cache,
1728 program2,
1729 60,
1730 ProgramCacheEntryType::FailedVerification(env),
1731 );
1732 let slot_versions = cache.get_slot_versions_for_tests(&program2);
1733 assert_eq!(slot_versions.len(), 2);
1734 assert!(!slot_versions.first().unwrap().is_tombstone());
1735 assert!(slot_versions.get(1).unwrap().is_tombstone());
1736 assert!(tombstone.is_tombstone());
1737 assert_eq!(tombstone.deployment_slot, 60);
1738 assert_eq!(tombstone.effective_slot, 60);
1739 }
1740
1741 #[test]
1742 fn test_prune_empty() {
1743 let mut cache = new_mock_cache();
1744
1745 cache.prune(0, 0);
1746 assert!(cache.get_flattened_entries_for_tests().is_empty());
1747
1748 cache.prune(10, 0);
1749 assert!(cache.get_flattened_entries_for_tests().is_empty());
1750
1751 let mut cache = new_mock_cache();
1752
1753 cache.prune(0, 0);
1754 assert!(cache.get_flattened_entries_for_tests().is_empty());
1755
1756 cache.prune(10, 0);
1757 assert!(cache.get_flattened_entries_for_tests().is_empty());
1758
1759 let mut cache = new_mock_cache();
1760
1761 cache.prune(0, 0);
1762 assert!(cache.get_flattened_entries_for_tests().is_empty());
1763
1764 cache.prune(10, 0);
1765 assert!(cache.get_flattened_entries_for_tests().is_empty());
1766
1767 let mut cache = new_mock_cache();
1768
1769 cache.prune(0, 0);
1770 assert!(cache.get_flattened_entries_for_tests().is_empty());
1771
1772 cache.prune(10, 0);
1773 assert!(cache.get_flattened_entries_for_tests().is_empty());
1774 }
1775
1776 #[test]
1777 fn test_prune_different_env() {
1778 let mut cache = new_mock_cache();
1779
1780 let program1 = Pubkey::new_unique();
1781 cache.assign_program(program1, new_test_entry(10, 10));
1782
1783 let new_env = Arc::new(BuiltinProgram::new_mock());
1784 cache.upcoming_environments = Some(ProgramRuntimeEnvironments {
1785 program_runtime_v1: new_env.clone(),
1786 program_runtime_v2: new_env.clone(),
1787 });
1788 let updated_program = Arc::new(ProgramCacheEntry {
1789 program: new_loaded_entry(new_env.clone()),
1790 account_owner: ProgramCacheEntryOwner::LoaderV2,
1791 account_size: 0,
1792 deployment_slot: 20,
1793 effective_slot: 20,
1794 tx_usage_counter: AtomicU64::default(),
1795 ix_usage_counter: AtomicU64::default(),
1796 latest_access_slot: AtomicU64::default(),
1797 });
1798 cache.assign_program(program1, updated_program.clone());
1799
1800 assert_eq!(cache.get_slot_versions_for_tests(&program1).len(), 2);
1802
1803 cache.prune(21, cache.latest_root_epoch);
1804
1805 assert_eq!(cache.get_slot_versions_for_tests(&program1).len(), 2);
1807
1808 cache.prune(22, cache.latest_root_epoch.saturating_add(1));
1809
1810 assert_eq!(cache.get_slot_versions_for_tests(&program1).len(), 1);
1812
1813 let entry = cache
1814 .get_slot_versions_for_tests(&program1)
1815 .first()
1816 .expect("Failed to get the program")
1817 .clone();
1818 assert_eq!(entry, updated_program);
1820 }
1821
1822 fn get_entries_to_load(
1823 cache: &ProgramCache,
1824 loading_slot: Slot,
1825 keys: &[Pubkey],
1826 ) -> Vec<(Pubkey, (ProgramCacheMatchCriteria, u64))> {
1827 let entries = cache.get_flattened_entries_for_tests();
1828 keys.iter()
1829 .filter_map(|key| {
1830 entries
1831 .iter()
1832 .rev()
1833 .find(|(program_id, entry)| {
1834 program_id == key && entry.deployment_slot <= loading_slot
1835 })
1836 .map(|(program_id, _entry)| {
1837 (*program_id, (ProgramCacheMatchCriteria::NoCriteria, 1))
1838 })
1839 })
1840 .collect()
1841 }
1842
1843 fn match_slot(
1844 extracted: &ProgramCacheForTxBatch,
1845 program: &Pubkey,
1846 deployment_slot: Slot,
1847 working_slot: Slot,
1848 ) -> bool {
1849 assert_eq!(extracted.slot, working_slot);
1850 extracted
1851 .entries
1852 .get(program)
1853 .map(|entry| entry.deployment_slot == deployment_slot)
1854 .unwrap_or(false)
1855 }
1856
1857 fn match_missing(
1858 missing: &[(Pubkey, (ProgramCacheMatchCriteria, u64))],
1859 program: &Pubkey,
1860 expected_result: bool,
1861 ) -> bool {
1862 missing.iter().any(|(key, _)| key == program) == expected_result
1863 }
1864
1865 #[test]
1866 fn test_fork_extract_and_prune() {
1867 let mut cache = new_mock_cache();
1868
1869 let program1 = Pubkey::new_unique();
1884 cache.assign_program(program1, new_test_entry(0, 1));
1885 cache.assign_program(program1, new_test_entry(10, 11));
1886 cache.assign_program(program1, new_test_entry(20, 21));
1887
1888 let program2 = Pubkey::new_unique();
1889 cache.assign_program(program2, new_test_entry(5, 6));
1890 cache.assign_program(
1891 program2,
1892 new_test_entry(11, 11 + DELAY_VISIBILITY_SLOT_OFFSET),
1893 );
1894
1895 let program3 = Pubkey::new_unique();
1896 cache.assign_program(program3, new_test_entry(25, 26));
1897
1898 let program4 = Pubkey::new_unique();
1899 cache.assign_program(program4, new_test_entry(0, 1));
1900 cache.assign_program(program4, new_test_entry(5, 6));
1901 cache.assign_program(
1903 program4,
1904 new_test_entry(15, 15 + DELAY_VISIBILITY_SLOT_OFFSET),
1905 );
1906
1907 let mut missing =
1924 get_entries_to_load(&cache, 22, &[program1, program2, program3, program4]);
1925 assert!(match_missing(&missing, &program2, false));
1926 assert!(match_missing(&missing, &program3, false));
1927 let mut extracted = ProgramCacheForTxBatch::new(22, cache.environments.clone(), None, 0);
1928 cache.extract(&mut missing, &mut extracted, true);
1929 assert!(match_slot(&extracted, &program1, 20, 22));
1930 assert!(match_slot(&extracted, &program4, 0, 22));
1931
1932 let mut missing =
1934 get_entries_to_load(&cache, 15, &[program1, program2, program3, program4]);
1935 assert!(match_missing(&missing, &program3, false));
1936 let mut extracted = ProgramCacheForTxBatch::new(15, cache.environments.clone(), None, 0);
1937 cache.extract(&mut missing, &mut extracted, true);
1938 assert!(match_slot(&extracted, &program1, 0, 15));
1939 assert!(match_slot(&extracted, &program2, 11, 15));
1940 let tombstone = extracted
1943 .find(&program4)
1944 .expect("Failed to find the tombstone");
1945 assert_matches!(tombstone.program, ProgramCacheEntryType::DelayVisibility);
1946 assert_eq!(tombstone.deployment_slot, 15);
1947
1948 let mut missing =
1950 get_entries_to_load(&cache, 18, &[program1, program2, program3, program4]);
1951 assert!(match_missing(&missing, &program3, false));
1952 let mut extracted = ProgramCacheForTxBatch::new(18, cache.environments.clone(), None, 0);
1953 cache.extract(&mut missing, &mut extracted, true);
1954 assert!(match_slot(&extracted, &program1, 0, 18));
1955 assert!(match_slot(&extracted, &program2, 11, 18));
1956 assert!(match_slot(&extracted, &program4, 15, 18));
1958
1959 let mut missing =
1961 get_entries_to_load(&cache, 23, &[program1, program2, program3, program4]);
1962 assert!(match_missing(&missing, &program3, false));
1963 let mut extracted = ProgramCacheForTxBatch::new(23, cache.environments.clone(), None, 0);
1964 cache.extract(&mut missing, &mut extracted, true);
1965 assert!(match_slot(&extracted, &program1, 0, 23));
1966 assert!(match_slot(&extracted, &program2, 11, 23));
1967 assert!(match_slot(&extracted, &program4, 15, 23));
1969
1970 let mut missing =
1972 get_entries_to_load(&cache, 11, &[program1, program2, program3, program4]);
1973 assert!(match_missing(&missing, &program3, false));
1974 let mut extracted = ProgramCacheForTxBatch::new(11, cache.environments.clone(), None, 0);
1975 cache.extract(&mut missing, &mut extracted, true);
1976 assert!(match_slot(&extracted, &program1, 0, 11));
1977 let tombstone = extracted
1979 .find(&program2)
1980 .expect("Failed to find the tombstone");
1981 assert_matches!(tombstone.program, ProgramCacheEntryType::DelayVisibility);
1982 assert_eq!(tombstone.deployment_slot, 11);
1983 assert!(match_slot(&extracted, &program4, 5, 11));
1984
1985 cache.prune(5, 0);
1986
1987 let mut missing =
2004 get_entries_to_load(&cache, 21, &[program1, program2, program3, program4]);
2005 assert!(match_missing(&missing, &program3, false));
2006 let mut extracted = ProgramCacheForTxBatch::new(21, cache.environments.clone(), None, 0);
2007 cache.extract(&mut missing, &mut extracted, true);
2008 assert!(match_slot(&extracted, &program1, 0, 21));
2010 assert!(match_slot(&extracted, &program2, 11, 21));
2011 assert!(match_slot(&extracted, &program4, 15, 21));
2012
2013 let mut missing =
2015 get_entries_to_load(&cache, 27, &[program1, program2, program3, program4]);
2016 let mut extracted = ProgramCacheForTxBatch::new(27, cache.environments.clone(), None, 0);
2017 cache.extract(&mut missing, &mut extracted, true);
2018 assert!(match_slot(&extracted, &program1, 0, 27));
2019 assert!(match_slot(&extracted, &program2, 11, 27));
2020 assert!(match_slot(&extracted, &program3, 25, 27));
2021 assert!(match_slot(&extracted, &program4, 5, 27));
2022
2023 cache.prune(15, 0);
2024
2025 let mut missing =
2042 get_entries_to_load(&cache, 23, &[program1, program2, program3, program4]);
2043 assert!(match_missing(&missing, &program3, false));
2044 let mut extracted = ProgramCacheForTxBatch::new(23, cache.environments.clone(), None, 0);
2045 cache.extract(&mut missing, &mut extracted, true);
2046 assert!(match_slot(&extracted, &program1, 0, 23));
2047 assert!(match_slot(&extracted, &program2, 11, 23));
2048 assert!(match_slot(&extracted, &program4, 15, 23));
2049 }
2050
2051 #[test]
2052 fn test_extract_using_deployment_slot() {
2053 let mut cache = new_mock_cache();
2054
2055 let program1 = Pubkey::new_unique();
2070 cache.assign_program(program1, new_test_entry(0, 1));
2071 cache.assign_program(program1, new_test_entry(20, 21));
2072
2073 let program2 = Pubkey::new_unique();
2074 cache.assign_program(program2, new_test_entry(5, 6));
2075 cache.assign_program(program2, new_test_entry(11, 12));
2076
2077 let program3 = Pubkey::new_unique();
2078 cache.assign_program(program3, new_test_entry(25, 26));
2079
2080 let mut missing = get_entries_to_load(&cache, 12, &[program1, program2, program3]);
2082 assert!(match_missing(&missing, &program3, false));
2083 let mut extracted = ProgramCacheForTxBatch::new(12, cache.environments.clone(), None, 0);
2084 cache.extract(&mut missing, &mut extracted, true);
2085 assert!(match_slot(&extracted, &program1, 0, 12));
2086 assert!(match_slot(&extracted, &program2, 11, 12));
2087
2088 let mut missing = get_entries_to_load(&cache, 12, &[program1, program2, program3]);
2090 missing.get_mut(0).unwrap().1 .0 = ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(5);
2091 missing.get_mut(1).unwrap().1 .0 = ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(5);
2092 assert!(match_missing(&missing, &program3, false));
2093 let mut extracted = ProgramCacheForTxBatch::new(12, cache.environments.clone(), None, 0);
2094 cache.extract(&mut missing, &mut extracted, true);
2095 assert!(match_missing(&missing, &program1, true));
2096 assert!(match_slot(&extracted, &program2, 11, 12));
2097 }
2098
2099 #[test]
2100 fn test_extract_unloaded() {
2101 let mut cache = new_mock_cache();
2102
2103 let program1 = Pubkey::new_unique();
2119 cache.assign_program(program1, new_test_entry(0, 1));
2120 cache.assign_program(program1, new_test_entry(20, 21));
2121
2122 let program2 = Pubkey::new_unique();
2123 cache.assign_program(program2, new_test_entry(5, 6));
2124 cache.assign_program(program2, new_test_entry(11, 12));
2125
2126 let program3 = Pubkey::new_unique();
2127 let _ = insert_unloaded_entry(&mut cache, program3, 25);
2129
2130 cache.assign_program(
2134 program3,
2135 Arc::new(
2136 new_test_entry(20, 21)
2137 .to_unloaded()
2138 .expect("Failed to create unloaded program"),
2139 ),
2140 );
2141
2142 let mut missing = get_entries_to_load(&cache, 19, &[program1, program2, program3]);
2144 assert!(match_missing(&missing, &program3, false));
2145 let mut extracted = ProgramCacheForTxBatch::new(19, cache.environments.clone(), None, 0);
2146 cache.extract(&mut missing, &mut extracted, true);
2147 assert!(match_slot(&extracted, &program1, 0, 19));
2148 assert!(match_slot(&extracted, &program2, 11, 19));
2149
2150 let mut missing = get_entries_to_load(&cache, 27, &[program1, program2, program3]);
2152 let mut extracted = ProgramCacheForTxBatch::new(27, cache.environments.clone(), None, 0);
2153 cache.extract(&mut missing, &mut extracted, true);
2154 assert!(match_slot(&extracted, &program1, 0, 27));
2155 assert!(match_slot(&extracted, &program2, 11, 27));
2156 assert!(match_missing(&missing, &program3, true));
2157
2158 let mut missing = get_entries_to_load(&cache, 22, &[program1, program2, program3]);
2160 assert!(match_missing(&missing, &program2, false));
2161 let mut extracted = ProgramCacheForTxBatch::new(22, cache.environments.clone(), None, 0);
2162 cache.extract(&mut missing, &mut extracted, true);
2163 assert!(match_slot(&extracted, &program1, 20, 22));
2164 assert!(match_missing(&missing, &program3, true));
2165 }
2166
2167 #[test]
2168 fn test_extract_nonexistent() {
2169 let cache = new_mock_cache();
2170
2171 let program1 = Pubkey::new_unique();
2172 let mut missing = vec![(program1, (ProgramCacheMatchCriteria::NoCriteria, 1))];
2173 let mut extracted = ProgramCacheForTxBatch::new(0, cache.environments.clone(), None, 0);
2174 cache.extract(&mut missing, &mut extracted, true);
2175 assert!(match_missing(&missing, &program1, true));
2176 }
2177
2178 #[test]
2179 fn test_unloaded() {
2180 let mut cache = new_mock_cache();
2181 for program_cache_entry_type in [
2182 ProgramCacheEntryType::FailedVerification(
2183 cache.environments.program_runtime_v1.clone(),
2184 ),
2185 ProgramCacheEntryType::Closed,
2186 ProgramCacheEntryType::Unloaded(cache.environments.program_runtime_v1.clone()),
2187 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
2188 ] {
2189 let entry = Arc::new(ProgramCacheEntry {
2190 program: program_cache_entry_type,
2191 account_owner: ProgramCacheEntryOwner::LoaderV2,
2192 account_size: 0,
2193 deployment_slot: 0,
2194 effective_slot: 0,
2195 tx_usage_counter: AtomicU64::default(),
2196 ix_usage_counter: AtomicU64::default(),
2197 latest_access_slot: AtomicU64::default(),
2198 });
2199 assert!(entry.to_unloaded().is_none());
2200
2201 let program_id = Pubkey::new_unique();
2203 cache.assign_program(program_id, entry.clone());
2204 cache.unload_program_entry(&program_id, &entry);
2205 assert_eq!(cache.get_slot_versions_for_tests(&program_id).len(), 1);
2206 assert!(cache.stats.evictions.is_empty());
2207 }
2208
2209 let entry = new_test_entry_with_usage(1, 2, AtomicU64::new(3));
2210 let unloaded_entry = entry.to_unloaded().unwrap();
2211 assert_eq!(unloaded_entry.deployment_slot, 1);
2212 assert_eq!(unloaded_entry.effective_slot, 2);
2213 assert_eq!(unloaded_entry.latest_access_slot.load(Ordering::Relaxed), 1);
2214 assert_eq!(unloaded_entry.tx_usage_counter.load(Ordering::Relaxed), 3);
2215
2216 let program_id = Pubkey::new_unique();
2218 cache.assign_program(program_id, entry.clone());
2219 cache.unload_program_entry(&program_id, &entry);
2220 assert!(cache.stats.evictions.contains_key(&program_id));
2221 }
2222
2223 #[test]
2224 fn test_fork_prune_find_first_ancestor() {
2225 let mut cache = new_mock_cache();
2226
2227 let program1 = Pubkey::new_unique();
2238 cache.assign_program(program1, new_test_entry(0, 1));
2239 cache.assign_program(program1, new_test_entry(5, 6));
2240
2241 cache.prune(10, 0);
2242
2243 let mut missing = get_entries_to_load(&cache, 20, &[program1]);
2244 let mut extracted = ProgramCacheForTxBatch::new(20, cache.environments.clone(), None, 0);
2245 cache.extract(&mut missing, &mut extracted, true);
2246
2247 assert_eq!(
2249 extracted
2250 .find(&program1)
2251 .expect("Did not find the program")
2252 .deployment_slot,
2253 0
2254 );
2255 }
2256
2257 #[test]
2258 fn test_prune_by_deployment_slot() {
2259 let mut cache = new_mock_cache();
2260
2261 let program1 = Pubkey::new_unique();
2272 cache.assign_program(program1, new_test_entry(0, 1));
2273 cache.assign_program(program1, new_test_entry(5, 6));
2274
2275 let program2 = Pubkey::new_unique();
2276 cache.assign_program(program2, new_test_entry(10, 11));
2277
2278 let mut missing = get_entries_to_load(&cache, 20, &[program1, program2]);
2279 let mut extracted = ProgramCacheForTxBatch::new(20, cache.environments.clone(), None, 0);
2280 cache.extract(&mut missing, &mut extracted, true);
2281 assert!(match_slot(&extracted, &program1, 0, 20));
2282 assert!(match_slot(&extracted, &program2, 10, 20));
2283
2284 let mut missing = get_entries_to_load(&cache, 6, &[program1, program2]);
2285 assert!(match_missing(&missing, &program2, false));
2286 let mut extracted = ProgramCacheForTxBatch::new(6, cache.environments.clone(), None, 0);
2287 cache.extract(&mut missing, &mut extracted, true);
2288 assert!(match_slot(&extracted, &program1, 5, 6));
2289
2290 cache.prune_by_deployment_slot(5);
2293
2294 let mut missing = get_entries_to_load(&cache, 20, &[program1, program2]);
2295 let mut extracted = ProgramCacheForTxBatch::new(20, cache.environments.clone(), None, 0);
2296 cache.extract(&mut missing, &mut extracted, true);
2297 assert!(match_slot(&extracted, &program1, 0, 20));
2298 assert!(match_slot(&extracted, &program2, 10, 20));
2299
2300 let mut missing = get_entries_to_load(&cache, 6, &[program1, program2]);
2301 assert!(match_missing(&missing, &program2, false));
2302 let mut extracted = ProgramCacheForTxBatch::new(6, cache.environments.clone(), None, 0);
2303 cache.extract(&mut missing, &mut extracted, true);
2304 assert!(match_slot(&extracted, &program1, 0, 6));
2305
2306 cache.prune_by_deployment_slot(10);
2309
2310 let mut missing = get_entries_to_load(&cache, 20, &[program1, program2]);
2311 assert!(match_missing(&missing, &program2, false));
2312 let mut extracted = ProgramCacheForTxBatch::new(20, cache.environments.clone(), None, 0);
2313 cache.extract(&mut missing, &mut extracted, true);
2314 assert!(match_slot(&extracted, &program1, 0, 20));
2315 }
2316
2317 #[test]
2318 fn test_usable_entries_for_slot() {
2319 new_mock_cache();
2320 let tombstone = Arc::new(ProgramCacheEntry::new_tombstone(
2321 0,
2322 ProgramCacheEntryOwner::LoaderV2,
2323 ProgramCacheEntryType::Closed,
2324 ));
2325
2326 assert!(ProgramCache::matches_criteria(
2327 &tombstone,
2328 &ProgramCacheMatchCriteria::NoCriteria
2329 ));
2330
2331 assert!(ProgramCache::matches_criteria(
2332 &tombstone,
2333 &ProgramCacheMatchCriteria::Tombstone
2334 ));
2335
2336 assert!(ProgramCache::matches_criteria(
2337 &tombstone,
2338 &ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(0)
2339 ));
2340
2341 assert!(!ProgramCache::matches_criteria(
2342 &tombstone,
2343 &ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(1)
2344 ));
2345
2346 let program = new_test_entry(0, 1);
2347
2348 assert!(ProgramCache::matches_criteria(
2349 &program,
2350 &ProgramCacheMatchCriteria::NoCriteria
2351 ));
2352
2353 assert!(!ProgramCache::matches_criteria(
2354 &program,
2355 &ProgramCacheMatchCriteria::Tombstone
2356 ));
2357
2358 assert!(ProgramCache::matches_criteria(
2359 &program,
2360 &ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(0)
2361 ));
2362
2363 assert!(!ProgramCache::matches_criteria(
2364 &program,
2365 &ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(1)
2366 ));
2367
2368 let program = Arc::new(new_test_entry_with_usage(0, 1, AtomicU64::default()));
2369
2370 assert!(ProgramCache::matches_criteria(
2371 &program,
2372 &ProgramCacheMatchCriteria::NoCriteria
2373 ));
2374
2375 assert!(!ProgramCache::matches_criteria(
2376 &program,
2377 &ProgramCacheMatchCriteria::Tombstone
2378 ));
2379
2380 assert!(ProgramCache::matches_criteria(
2381 &program,
2382 &ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(0)
2383 ));
2384
2385 assert!(!ProgramCache::matches_criteria(
2386 &program,
2387 &ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(1)
2388 ));
2389 }
2390}