1use std::{
6 collections::{hash_map::Entry, HashMap},
7 fmt::{Debug, Formatter},
8};
9
10use log::{debug, error, log_enabled, trace};
11use percentage::PercentageInteger;
12use polkavm::{ArcBytes, ProgramBlob, ProgramParts};
13use rialo_s_clock::{Epoch, Slot};
14use rialo_s_pubkey::Pubkey;
15use rialo_s_sdk_ids::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 prunes_orphan: AtomicU64,
200 pub prunes_environment: AtomicU64,
202 pub empty_entries: AtomicU64,
204 pub water_level: AtomicU64,
206}
207
208impl ProgramCacheStats {
209 pub fn reset(&mut self) {
210 *self = ProgramCacheStats::default();
211 }
212 pub fn log(&self) {
213 let hits = self.hits.load(Ordering::Relaxed);
214 let misses = self.misses.load(Ordering::Relaxed);
215 let evictions: u64 = self.evictions.values().sum();
216 let reloads = self.reloads.load(Ordering::Relaxed);
217 let insertions = self.insertions.load(Ordering::Relaxed);
218 let lost_insertions = self.lost_insertions.load(Ordering::Relaxed);
219 let replacements = self.replacements.load(Ordering::Relaxed);
220 let one_hit_wonders = self.one_hit_wonders.load(Ordering::Relaxed);
221 let prunes_orphan = self.prunes_orphan.load(Ordering::Relaxed);
222 let prunes_environment = self.prunes_environment.load(Ordering::Relaxed);
223 let empty_entries = self.empty_entries.load(Ordering::Relaxed);
224 let water_level = self.water_level.load(Ordering::Relaxed);
225 debug!(
226 "Loaded Programs Cache Stats -- Hits: {}, Misses: {}, Evictions: {}, Reloads: {}, Insertions: {}, Lost-Insertions: {}, Replacements: {}, One-Hit-Wonders: {}, Prunes-Orphan: {}, Prunes-Environment: {}, Empty: {}, Water-Level: {}",
227 hits, misses, evictions, reloads, insertions, lost_insertions, replacements, one_hit_wonders, prunes_orphan, prunes_environment, empty_entries, water_level
228 );
229 if log_enabled!(log::Level::Trace) && !self.evictions.is_empty() {
230 let mut evictions = self.evictions.iter().collect::<Vec<_>>();
231 evictions.sort_by_key(|e| e.1);
232 let evictions = evictions
233 .into_iter()
234 .rev()
235 .map(|(program_id, evictions)| {
236 format!(" {:<44} {}", program_id.to_string(), evictions)
237 })
238 .collect::<Vec<_>>();
239 let evictions = evictions.join("\n");
240 trace!(
241 "Eviction Details:\n {:<44} {}\n{}",
242 "Program",
243 "Count",
244 evictions
245 );
246 }
247 }
248}
249
250#[cfg(feature = "metrics")]
251#[derive(Debug, Default)]
253pub struct LoadProgramMetrics {
254 pub program_id: String,
256 pub register_syscalls_us: u64,
258}
259
260#[cfg(feature = "metrics")]
261impl LoadProgramMetrics {
262 pub fn submit_datapoint(&self, timings: &mut ExecuteDetailsTimings) {
263 timings.create_executor_register_syscalls_us += self.register_syscalls_us;
264 datapoint_trace!(
265 "create_executor_trace",
266 ("program_id", self.program_id, String),
267 ("register_syscalls_us", self.register_syscalls_us, i64),
268 );
269 }
270}
271
272impl PartialEq for ProgramCacheEntry {
273 fn eq(&self, other: &Self) -> bool {
274 self.effective_slot == other.effective_slot
275 && self.deployment_slot == other.deployment_slot
276 && self.is_tombstone() == other.is_tombstone()
277 }
278}
279
280impl ProgramCacheEntry {
281 pub fn new_risc_v(
283 loader_key: &Pubkey,
284 deployment_slot: Slot,
285 effective_slot: Slot,
286 elf_bytes: &[u8],
287 account_size: usize,
288 ) -> Result<Self, Box<dyn std::error::Error>> {
289 Self::new_internal_risc_v(
290 loader_key,
291 deployment_slot,
292 effective_slot,
293 elf_bytes,
294 account_size,
295 false, )
297 }
298
299 pub unsafe fn reload_risc_v(
308 loader_key: &Pubkey,
309 deployment_slot: Slot,
310 effective_slot: Slot,
311 elf_bytes: &[u8],
312 account_size: usize,
313 ) -> Result<Self, Box<dyn std::error::Error>> {
314 Self::new_internal_risc_v(
315 loader_key,
316 deployment_slot,
317 effective_slot,
318 elf_bytes,
319 account_size,
320 true, )
322 }
323
324 fn new_internal_risc_v(
325 loader_key: &Pubkey,
326 deployment_slot: Slot,
327 effective_slot: Slot,
328 elf_bytes: &[u8],
329 account_size: usize,
330 reloading: bool,
331 ) -> Result<Self, Box<dyn std::error::Error>> {
332 if !reloading {
340 const MAX_STACK_SIZE: u32 = 262144;
341
342 let program = Into::<ArcBytes>::into(elf_bytes);
343
344 let program_parts = ProgramParts::from_bytes(program.clone())
345 .map_err(|error| format!("failed to parse program: {error:?}"))?;
346
347 if !program_parts.is_64_bit {
348 return Err(String::from("program is not a 64-bit program").into());
349 }
350
351 if program_parts.stack_size > MAX_STACK_SIZE {
352 return Err(String::from("program stack is too large").into());
353 }
354
355 let blob = ProgramBlob::parse(program)
356 .map_err(|error| format!("failed to parse program into program blob: {error:?}"))?;
357
358 if !blob
359 .exports()
360 .any(|entry| entry.symbol().as_bytes() == b"entrypoint")
361 {
362 return Err(String::from("program doesn't have an entrypoint").into());
363 }
364 }
365
366 Ok(Self {
367 deployment_slot,
368 account_owner: ProgramCacheEntryOwner::try_from(loader_key).unwrap(),
369 account_size,
370 effective_slot,
371 tx_usage_counter: AtomicU64::new(0),
372 program: ProgramCacheEntryType::Loaded(ExecutableKind::RiscV(elf_bytes.to_vec())),
373 ix_usage_counter: AtomicU64::new(0),
374 latest_access_slot: AtomicU64::new(0),
375 })
376 }
377
378 pub fn to_unloaded(&self) -> Option<Self> {
379 match &self.program {
380 ProgramCacheEntryType::Loaded(_) => {}
381 ProgramCacheEntryType::FailedVerification
382 | ProgramCacheEntryType::Closed
383 | ProgramCacheEntryType::DelayVisibility
384 | ProgramCacheEntryType::Unloaded
385 | ProgramCacheEntryType::Builtin(_) => {
386 return None;
387 }
388 }
389 Some(Self {
390 program: ProgramCacheEntryType::Unloaded,
391 account_owner: self.account_owner,
392 account_size: self.account_size,
393 deployment_slot: self.deployment_slot,
394 effective_slot: self.effective_slot,
395 tx_usage_counter: AtomicU64::new(self.tx_usage_counter.load(Ordering::Relaxed)),
396 ix_usage_counter: AtomicU64::new(self.ix_usage_counter.load(Ordering::Relaxed)),
397 latest_access_slot: AtomicU64::new(self.latest_access_slot.load(Ordering::Relaxed)),
398 })
399 }
400
401 pub fn new_builtin(
403 deployment_slot: Slot,
404 account_size: usize,
405 builtin_function: BuiltinFunctionWithContext,
406 ) -> Self {
407 Self {
408 deployment_slot,
409 account_owner: ProgramCacheEntryOwner::NativeLoader,
410 account_size,
411 effective_slot: deployment_slot,
412 tx_usage_counter: AtomicU64::new(0),
413 program: ProgramCacheEntryType::Builtin(builtin_function),
414 ix_usage_counter: AtomicU64::new(0),
415 latest_access_slot: AtomicU64::new(0),
416 }
417 }
418
419 pub fn new_tombstone(
420 slot: Slot,
421 account_owner: ProgramCacheEntryOwner,
422 reason: ProgramCacheEntryType,
423 ) -> Self {
424 let tombstone = Self {
425 program: reason,
426 account_owner,
427 account_size: 0,
428 deployment_slot: slot,
429 effective_slot: slot,
430 tx_usage_counter: AtomicU64::default(),
431 ix_usage_counter: AtomicU64::default(),
432 latest_access_slot: AtomicU64::new(0),
433 };
434 debug_assert!(tombstone.is_tombstone());
435 tombstone
436 }
437
438 pub fn is_tombstone(&self) -> bool {
439 matches!(
440 self.program,
441 ProgramCacheEntryType::FailedVerification
442 | ProgramCacheEntryType::Closed
443 | ProgramCacheEntryType::DelayVisibility
444 )
445 }
446
447 fn is_implicit_delay_visibility_tombstone(&self, slot: Slot) -> bool {
448 !matches!(self.program, ProgramCacheEntryType::Builtin(_))
449 && self.effective_slot.saturating_sub(self.deployment_slot)
450 == DELAY_VISIBILITY_SLOT_OFFSET
451 && slot >= self.deployment_slot
452 && slot < self.effective_slot
453 }
454
455 pub fn update_access_slot(&self, slot: Slot) {
456 let _ = self.latest_access_slot.fetch_max(slot, Ordering::Relaxed);
457 }
458
459 pub fn decayed_usage_counter(&self, now: Slot) -> u64 {
460 let last_access = self.latest_access_slot.load(Ordering::Relaxed);
461 let decaying_for = std::cmp::min(63, now.saturating_sub(last_access));
463 self.tx_usage_counter.load(Ordering::Relaxed) >> decaying_for
464 }
465
466 pub fn account_owner(&self) -> Pubkey {
467 self.account_owner.into()
468 }
469}
470
471#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
472pub struct LoadingTaskCookie(u64);
473
474impl LoadingTaskCookie {
475 fn new() -> Self {
476 Self(0)
477 }
478
479 fn update(&mut self) {
480 let LoadingTaskCookie(cookie) = self;
481 *cookie = cookie.wrapping_add(1);
482 }
483}
484
485#[derive(Debug, Default)]
487pub struct LoadingTaskWaiter {
488 cookie: Mutex<LoadingTaskCookie>,
489 cond: Condvar,
490}
491
492impl LoadingTaskWaiter {
493 pub fn new() -> Self {
494 Self {
495 cookie: Mutex::new(LoadingTaskCookie::new()),
496 cond: Condvar::new(),
497 }
498 }
499
500 pub fn cookie(&self) -> LoadingTaskCookie {
501 *self.cookie.lock().unwrap()
502 }
503
504 pub fn notify(&self) {
505 let mut cookie = self.cookie.lock().unwrap();
506 cookie.update();
507 self.cond.notify_all();
508 }
509
510 pub fn wait(&self, cookie: LoadingTaskCookie) -> LoadingTaskCookie {
511 let cookie_guard = self.cookie.lock().unwrap();
512 *self
513 .cond
514 .wait_while(cookie_guard, |current_cookie| *current_cookie == cookie)
515 .unwrap()
516 }
517}
518
519#[derive(Debug)]
520enum IndexImplementation {
521 V1 {
523 entries: HashMap<Pubkey, Vec<Arc<ProgramCacheEntry>>>,
528 loading_entries: Mutex<HashMap<Pubkey, (Slot, thread::ThreadId)>>,
537 },
538}
539
540pub struct ProgramCache {
556 index: IndexImplementation,
558 pub latest_root_slot: Slot,
560 pub latest_root_epoch: Epoch,
562 pub stats: ProgramCacheStats,
564 pub loading_task_waiter: Arc<LoadingTaskWaiter>,
566}
567
568impl Debug for ProgramCache {
569 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
570 f.debug_struct("ProgramCache")
571 .field("root slot", &self.latest_root_slot)
572 .field("root epoch", &self.latest_root_epoch)
573 .field("stats", &self.stats)
574 .field("index", &self.index)
575 .finish()
576 }
577}
578
579pub struct ProgramCacheForTx<'a> {
585 entries: &'a HashMap<Pubkey, Arc<ProgramCacheEntry>>,
588 modified_entries: HashMap<Pubkey, Arc<ProgramCacheEntry>>,
590 slot: Slot,
591 pub hit_max_limit: bool,
592 pub loaded_missing: bool,
593 pub merged_modified: bool,
594}
595
596impl<'a> ProgramCacheForTx<'a> {
597 pub fn from_cache(cache: &'a ProgramCacheForTxBatch) -> Self {
598 Self {
599 entries: &cache.entries,
600 modified_entries: HashMap::new(),
601 slot: cache.slot,
602 hit_max_limit: cache.hit_max_limit,
603 loaded_missing: cache.loaded_missing,
604 merged_modified: cache.merged_modified,
605 }
606 }
607
608 pub fn store_modified_entry(&mut self, key: Pubkey, entry: Arc<ProgramCacheEntry>) {
611 self.modified_entries.insert(key, entry);
612 }
613
614 pub fn drain_modified_entries(&mut self) -> HashMap<Pubkey, Arc<ProgramCacheEntry>> {
617 std::mem::take(&mut self.modified_entries)
618 }
619
620 pub fn find(&self, key: &Pubkey) -> Option<Arc<ProgramCacheEntry>> {
621 self.modified_entries
625 .get(key)
626 .or(self.entries.get(key))
627 .map(|entry| {
628 if entry.is_implicit_delay_visibility_tombstone(self.slot) {
629 Arc::new(ProgramCacheEntry::new_tombstone(
633 entry.deployment_slot,
634 entry.account_owner,
635 ProgramCacheEntryType::DelayVisibility,
636 ))
637 } else {
638 entry.clone()
639 }
640 })
641 }
642
643 pub fn slot(&self) -> Slot {
644 self.slot
645 }
646
647 pub fn set_slot_for_tests(&mut self, slot: Slot) {
648 self.slot = slot;
649 }
650
651 pub fn is_empty(&self) -> bool {
652 self.entries.is_empty()
653 }
654}
655
656#[derive(Clone, Debug, Default)]
662pub struct ProgramCacheForTxBatch {
663 entries: HashMap<Pubkey, Arc<ProgramCacheEntry>>,
666 modified_entries: HashMap<Pubkey, Arc<ProgramCacheEntry>>,
668 slot: Slot,
669 pub hit_max_limit: bool,
670 pub loaded_missing: bool,
671 pub merged_modified: bool,
672}
673
674impl ProgramCacheForTxBatch {
675 pub fn new(slot: Slot) -> Self {
676 Self {
677 entries: HashMap::new(),
678 modified_entries: HashMap::new(),
679 slot,
680 hit_max_limit: false,
681 loaded_missing: false,
682 merged_modified: false,
683 }
684 }
685
686 pub fn replenish(
692 &mut self,
693 key: Pubkey,
694 entry: Arc<ProgramCacheEntry>,
695 ) -> (bool, Arc<ProgramCacheEntry>) {
696 (self.entries.insert(key, entry.clone()).is_some(), entry)
697 }
698
699 pub fn store_modified_entry(&mut self, key: Pubkey, entry: Arc<ProgramCacheEntry>) {
702 self.modified_entries.insert(key, entry);
703 }
704
705 pub fn drain_modified_entries(&mut self) -> HashMap<Pubkey, Arc<ProgramCacheEntry>> {
708 std::mem::take(&mut self.modified_entries)
709 }
710
711 pub fn find(&self, key: &Pubkey) -> Option<Arc<ProgramCacheEntry>> {
712 self.modified_entries
716 .get(key)
717 .or(self.entries.get(key))
718 .map(|entry| {
719 if entry.is_implicit_delay_visibility_tombstone(self.slot) {
720 Arc::new(ProgramCacheEntry::new_tombstone(
724 entry.deployment_slot,
725 entry.account_owner,
726 ProgramCacheEntryType::DelayVisibility,
727 ))
728 } else {
729 entry.clone()
730 }
731 })
732 }
733
734 pub fn slot(&self) -> Slot {
735 self.slot
736 }
737
738 #[cfg(any(test, feature = "dev-context-only-utils"))]
739 pub fn set_slot_for_tests(&mut self, slot: Slot) {
740 self.slot = slot;
741 }
742
743 pub fn merge(&mut self, modified_entries: &HashMap<Pubkey, Arc<ProgramCacheEntry>>) {
744 modified_entries.iter().for_each(|(key, entry)| {
745 self.merged_modified = true;
746 self.replenish(*key, entry.clone());
747 })
748 }
749
750 pub fn is_empty(&self) -> bool {
751 self.entries.is_empty()
752 }
753}
754
755pub enum ProgramCacheMatchCriteria {
756 DeployedOnOrAfterSlot(Slot),
757 Tombstone,
758 NoCriteria,
759}
760
761impl ProgramCache {
762 pub fn new(root_slot: Slot, root_epoch: Epoch) -> Self {
763 Self {
764 index: IndexImplementation::V1 {
765 entries: HashMap::new(),
766 loading_entries: Mutex::new(HashMap::new()),
767 },
768 latest_root_slot: root_slot,
769 latest_root_epoch: root_epoch,
770 stats: ProgramCacheStats::default(),
771 loading_task_waiter: Arc::new(LoadingTaskWaiter::default()),
772 }
773 }
774
775 pub fn assign_program(&mut self, key: Pubkey, entry: Arc<ProgramCacheEntry>) -> bool {
778 debug_assert!(!matches!(
779 &entry.program,
780 ProgramCacheEntryType::DelayVisibility
781 ));
782 match &mut self.index {
783 IndexImplementation::V1 { entries, .. } => {
784 let slot_versions = &mut entries.entry(key).or_default();
785 match slot_versions.binary_search_by(|at| {
786 at.effective_slot
787 .cmp(&entry.effective_slot)
788 .then(at.deployment_slot.cmp(&entry.deployment_slot))
789 }) {
790 Ok(index) => {
791 let existing = slot_versions.get_mut(index).unwrap();
792 match (&existing.program, &entry.program) {
793 (
794 ProgramCacheEntryType::Builtin(_),
795 ProgramCacheEntryType::Builtin(_),
796 )
797 | (ProgramCacheEntryType::Unloaded, ProgramCacheEntryType::Loaded(_)) =>
798 {}
799 _ => {
800 error!("ProgramCache::assign_program() failed key={:?} existing={:?} entry={:?}", key, slot_versions, entry);
802 debug_assert!(false, "Unexpected replacement of an entry");
803 self.stats.replacements.fetch_add(1, Ordering::Relaxed);
804 return true;
805 }
806 }
807 entry.tx_usage_counter.fetch_add(
809 existing.tx_usage_counter.load(Ordering::Relaxed),
810 Ordering::Relaxed,
811 );
812 entry.ix_usage_counter.fetch_add(
813 existing.ix_usage_counter.load(Ordering::Relaxed),
814 Ordering::Relaxed,
815 );
816 *existing = Arc::clone(&entry);
817 self.stats.reloads.fetch_add(1, Ordering::Relaxed);
818 }
819 Err(index) => {
820 self.stats.insertions.fetch_add(1, Ordering::Relaxed);
821 slot_versions.insert(index, Arc::clone(&entry));
822 }
823 }
824 }
825 }
826 false
827 }
828
829 pub fn prune_by_deployment_slot(&mut self, slot: Slot) {
830 match &mut self.index {
831 IndexImplementation::V1 { entries, .. } => {
832 for second_level in entries.values_mut() {
833 second_level.retain(|entry| entry.deployment_slot != slot);
834 }
835 self.remove_programs_with_no_entries();
836 }
837 }
838 }
839
840 pub fn prune(&mut self, new_root_slot: Slot, new_root_epoch: Epoch) {
842 if self.latest_root_epoch != new_root_epoch {
843 self.latest_root_epoch = new_root_epoch;
844 }
845 match &mut self.index {
846 IndexImplementation::V1 { entries, .. } => {
847 for second_level in entries.values_mut() {
848 let mut first_ancestor_found = false;
850 *second_level = second_level
851 .iter()
852 .rev()
853 .filter(|entry| {
854 if entry.deployment_slot >= new_root_slot {
855 true
856 } else if entry.deployment_slot < new_root_slot
857 || entry.deployment_slot <= self.latest_root_slot
858 {
859 if !first_ancestor_found {
860 first_ancestor_found = true;
861 return true;
862 }
863 self.stats.prunes_orphan.fetch_add(1, Ordering::Relaxed);
864 false
865 } else {
866 self.stats.prunes_orphan.fetch_add(1, Ordering::Relaxed);
867 false
868 }
869 })
870 .cloned()
871 .collect();
872 second_level.reverse();
873 }
874 }
875 }
876 self.remove_programs_with_no_entries();
877 debug_assert!(self.latest_root_slot <= new_root_slot);
878 self.latest_root_slot = new_root_slot;
879 }
880
881 fn matches_criteria(
882 program: &Arc<ProgramCacheEntry>,
883 criteria: &ProgramCacheMatchCriteria,
884 ) -> bool {
885 match criteria {
886 ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(slot) => {
887 program.deployment_slot >= *slot
888 }
889 ProgramCacheMatchCriteria::Tombstone => program.is_tombstone(),
890 ProgramCacheMatchCriteria::NoCriteria => true,
891 }
892 }
893
894 pub fn extract(
897 &self,
898 search_for: &mut Vec<(Pubkey, (ProgramCacheMatchCriteria, u64))>,
899 loaded_programs_for_tx_batch: &mut ProgramCacheForTxBatch,
900 is_first_round: bool,
901 ) -> Option<(Pubkey, u64)> {
902 let mut cooperative_loading_task = None;
903 match &self.index {
904 IndexImplementation::V1 {
905 entries,
906 loading_entries,
907 } => {
908 search_for.retain(|(key, (match_criteria, usage_count))| {
909 if let Some(second_level) = entries.get(key) {
910 for entry in second_level.iter().rev() {
911 if entry.deployment_slot <= self.latest_root_slot
912 || entry.deployment_slot <= loaded_programs_for_tx_batch.slot
913 {
914 let entry_to_return = if loaded_programs_for_tx_batch.slot
915 >= entry.effective_slot
916 {
917 if !Self::matches_criteria(entry, match_criteria) {
918 break;
919 }
920 if matches!(&entry.program, ProgramCacheEntryType::Unloaded) {
921 break;
922 }
923 entry.clone()
924 } else if entry.is_implicit_delay_visibility_tombstone(
925 loaded_programs_for_tx_batch.slot,
926 ) {
927 Arc::new(ProgramCacheEntry::new_tombstone(
931 entry.deployment_slot,
932 entry.account_owner,
933 ProgramCacheEntryType::DelayVisibility,
934 ))
935 } else {
936 continue;
937 };
938 entry_to_return
939 .update_access_slot(loaded_programs_for_tx_batch.slot);
940 entry_to_return
941 .tx_usage_counter
942 .fetch_add(*usage_count, Ordering::Relaxed);
943 loaded_programs_for_tx_batch
944 .entries
945 .insert(*key, entry_to_return);
946 return false;
947 }
948 }
949 }
950 if cooperative_loading_task.is_none() {
951 let mut loading_entries = loading_entries.lock().unwrap();
952 let entry = loading_entries.entry(*key);
953 if let Entry::Vacant(entry) = entry {
954 entry.insert((
955 loaded_programs_for_tx_batch.slot,
956 thread::current().id(),
957 ));
958 cooperative_loading_task = Some((*key, *usage_count));
959 }
960 }
961 true
962 });
963 }
964 }
965 if is_first_round {
966 self.stats
967 .misses
968 .fetch_add(search_for.len() as u64, Ordering::Relaxed);
969 self.stats.hits.fetch_add(
970 loaded_programs_for_tx_batch.entries.len() as u64,
971 Ordering::Relaxed,
972 );
973 }
974 cooperative_loading_task
975 }
976
977 pub fn finish_cooperative_loading_task(
979 &mut self,
980 slot: Slot,
981 key: Pubkey,
982 loaded_program: Arc<ProgramCacheEntry>,
983 ) -> bool {
984 match &mut self.index {
985 IndexImplementation::V1 {
986 loading_entries, ..
987 } => {
988 let loading_thread = loading_entries.get_mut().unwrap().remove(&key);
989 debug_assert_eq!(loading_thread, Some((slot, thread::current().id())));
990 if loaded_program.deployment_slot > self.latest_root_slot
992 && loaded_program.deployment_slot > slot
993 {
994 self.stats.lost_insertions.fetch_add(1, Ordering::Relaxed);
995 }
996 let was_occupied = self.assign_program(key, loaded_program);
997 self.loading_task_waiter.notify();
998 was_occupied
999 }
1000 }
1001 }
1002
1003 pub fn merge(&mut self, modified_entries: &HashMap<Pubkey, Arc<ProgramCacheEntry>>) {
1004 modified_entries.iter().for_each(|(key, entry)| {
1005 self.assign_program(*key, entry.clone());
1006 })
1007 }
1008
1009 fn get_flattened_entries(&self) -> Vec<(Pubkey, Arc<ProgramCacheEntry>)> {
1011 match &self.index {
1012 IndexImplementation::V1 { entries, .. } => entries
1013 .iter()
1014 .flat_map(|(id, second_level)| {
1015 second_level
1016 .iter()
1017 .filter_map(move |program| match program.program {
1018 ProgramCacheEntryType::Loaded(_) => Some((*id, program.clone())),
1019 _ => None,
1020 })
1021 })
1022 .collect(),
1023 }
1024 }
1025
1026 #[cfg(any(test, feature = "dev-context-only-utils"))]
1028 pub fn get_flattened_entries_for_tests(&self) -> Vec<(Pubkey, Arc<ProgramCacheEntry>)> {
1029 match &self.index {
1030 IndexImplementation::V1 { entries, .. } => entries
1031 .iter()
1032 .flat_map(|(id, second_level)| {
1033 second_level.iter().map(|program| (*id, program.clone()))
1034 })
1035 .collect(),
1036 }
1037 }
1038
1039 #[cfg(any(test, feature = "dev-context-only-utils"))]
1041 pub fn get_slot_versions_for_tests(&self, key: &Pubkey) -> &[Arc<ProgramCacheEntry>] {
1042 match &self.index {
1043 IndexImplementation::V1 { entries, .. } => entries
1044 .get(key)
1045 .map(|second_level| second_level.as_ref())
1046 .unwrap_or(&[]),
1047 }
1048 }
1049
1050 pub fn sort_and_unload(&mut self, shrink_to: PercentageInteger) {
1052 let mut sorted_candidates = self.get_flattened_entries();
1053 sorted_candidates
1054 .sort_by_cached_key(|(_id, program)| program.tx_usage_counter.load(Ordering::Relaxed));
1055 let num_to_unload = sorted_candidates
1056 .len()
1057 .saturating_sub(shrink_to.apply_to(MAX_LOADED_ENTRY_COUNT));
1058 self.unload_program_entries(sorted_candidates.iter().take(num_to_unload));
1059 }
1060
1061 pub fn evict_using_2s_random_selection(&mut self, shrink_to: PercentageInteger, now: Slot) {
1064 let mut candidates = self.get_flattened_entries();
1065 self.stats
1066 .water_level
1067 .store(candidates.len() as u64, Ordering::Relaxed);
1068 let num_to_unload = candidates
1069 .len()
1070 .saturating_sub(shrink_to.apply_to(MAX_LOADED_ENTRY_COUNT));
1071 fn random_index_and_usage_counter(
1072 candidates: &[(Pubkey, Arc<ProgramCacheEntry>)],
1073 now: Slot,
1074 ) -> (usize, u64) {
1075 let mut rng = thread_rng();
1076 let index = rng.gen_range(0..candidates.len());
1077 let usage_counter = candidates
1078 .get(index)
1079 .expect("Failed to get cached entry")
1080 .1
1081 .decayed_usage_counter(now);
1082 (index, usage_counter)
1083 }
1084
1085 for _ in 0..num_to_unload {
1086 let (index1, usage_counter1) = random_index_and_usage_counter(&candidates, now);
1087 let (index2, usage_counter2) = random_index_and_usage_counter(&candidates, now);
1088
1089 let (program, entry) = if usage_counter1 < usage_counter2 {
1090 candidates.swap_remove(index1)
1091 } else {
1092 candidates.swap_remove(index2)
1093 };
1094 self.unload_program_entry(&program, &entry);
1095 }
1096 }
1097
1098 pub fn remove_programs(&mut self, keys: impl Iterator<Item = Pubkey>) {
1100 match &mut self.index {
1101 IndexImplementation::V1 { entries, .. } => {
1102 for k in keys {
1103 entries.remove(&k);
1104 }
1105 }
1106 }
1107 }
1108
1109 fn unload_program_entry(&mut self, program: &Pubkey, remove_entry: &Arc<ProgramCacheEntry>) {
1112 match &mut self.index {
1113 IndexImplementation::V1 { entries, .. } => {
1114 let second_level = entries.get_mut(program).expect("Cache lookup failed");
1115 let candidate = second_level
1116 .iter_mut()
1117 .find(|entry| entry == &remove_entry)
1118 .expect("Program entry not found");
1119
1120 if let Some(unloaded) = candidate.to_unloaded() {
1124 if candidate.tx_usage_counter.load(Ordering::Relaxed) == 1 {
1125 self.stats.one_hit_wonders.fetch_add(1, Ordering::Relaxed);
1126 }
1127 self.stats
1128 .evictions
1129 .entry(*program)
1130 .and_modify(|c| *c = c.saturating_add(1))
1131 .or_insert(1);
1132 *candidate = Arc::new(unloaded);
1133 }
1134 }
1135 }
1136 }
1137
1138 fn unload_program_entries<'a>(
1139 &mut self,
1140 remove: impl Iterator<Item = &'a (Pubkey, Arc<ProgramCacheEntry>)>,
1141 ) {
1142 for (program, entry) in remove {
1143 self.unload_program_entry(program, entry);
1144 }
1145 }
1146
1147 fn remove_programs_with_no_entries(&mut self) {
1148 match &mut self.index {
1149 IndexImplementation::V1 { entries, .. } => {
1150 let num_programs_before_removal = entries.len();
1151 entries.retain(|_key, second_level| !second_level.is_empty());
1152 if entries.len() < num_programs_before_removal {
1153 self.stats.empty_entries.fetch_add(
1154 num_programs_before_removal.saturating_sub(entries.len()) as u64,
1155 Ordering::Relaxed,
1156 );
1157 }
1158 }
1159 }
1160 }
1161}
1162
1163#[cfg(feature = "frozen-abi")]
1164impl rialo_frozen_abi::abi_example::AbiExample for ProgramCacheEntry {
1165 fn example() -> Self {
1166 Self::default()
1168 }
1169}
1170
1171#[cfg(feature = "frozen-abi")]
1172impl rialo_frozen_abi::abi_example::AbiExample for ProgramCache {
1173 fn example() -> Self {
1174 Self::new(Slot::default(), Epoch::default())
1176 }
1177}
1178
1179#[cfg(false)] #[cfg(test)]
1181mod tests {
1182 use std::{
1183 fs::File,
1184 io::Read,
1185 sync::{
1186 atomic::{AtomicU64, Ordering},
1187 Arc,
1188 },
1189 };
1190
1191 use assert_matches::assert_matches;
1192 use percentage::Percentage;
1193 use rialo_s_clock::Slot;
1194 use rialo_s_pubkey::Pubkey;
1195 use solana_sbpf::{elf::Executable, program::BuiltinProgram};
1196 use test_case::{test_case, test_matrix};
1197
1198 use crate::loaded_programs::{
1199 ProgramCache, ProgramCacheEntry, ProgramCacheEntryOwner, ProgramCacheEntryType,
1200 ProgramCacheForTxBatch, ProgramCacheMatchCriteria, ProgramRuntimeEnvironment,
1201 ProgramRuntimeEnvironments, DELAY_VISIBILITY_SLOT_OFFSET,
1202 };
1203
1204 static MOCK_ENVIRONMENT: std::sync::OnceLock<ProgramRuntimeEnvironment> =
1205 std::sync::OnceLock::<ProgramRuntimeEnvironment>::new();
1206
1207 fn get_mock_env() -> ProgramRuntimeEnvironment {
1208 MOCK_ENVIRONMENT
1209 .get_or_init(|| Arc::new(BuiltinProgram::new_mock()))
1210 .clone()
1211 }
1212
1213 fn new_mock_cache() -> ProgramCache {
1214 let mut cache = ProgramCache::new(0, 0);
1215 cache.environments.program_runtime_v1 = get_mock_env();
1216 cache
1217 }
1218
1219 fn new_test_entry(deployment_slot: Slot, effective_slot: Slot) -> Arc<ProgramCacheEntry> {
1220 new_test_entry_with_usage(deployment_slot, effective_slot, AtomicU64::default())
1221 }
1222
1223 fn new_loaded_entry(env: ProgramRuntimeEnvironment) -> ProgramCacheEntryType {
1224 let mut elf = Vec::new();
1225 File::open("../runtime/vm/ebpf/bpf_loader/test_elfs/out/noop_aligned.so")
1226 .unwrap()
1227 .read_to_end(&mut elf)
1228 .unwrap();
1229 let executable = Executable::load(&elf, env).unwrap();
1230 ProgramCacheEntryType::Loaded(super::ExecutableKind::Ebpf(executable))
1231 }
1232
1233 fn new_test_entry_with_usage(
1234 deployment_slot: Slot,
1235 effective_slot: Slot,
1236 usage_counter: AtomicU64,
1237 ) -> Arc<ProgramCacheEntry> {
1238 Arc::new(ProgramCacheEntry {
1239 program: new_loaded_entry(get_mock_env()),
1240 account_owner: ProgramCacheEntryOwner::LoaderV2,
1241 account_size: 0,
1242 deployment_slot,
1243 effective_slot,
1244 tx_usage_counter: usage_counter,
1245 ix_usage_counter: AtomicU64::default(),
1246 latest_access_slot: AtomicU64::new(deployment_slot),
1247 })
1248 }
1249
1250 fn new_test_builtin_entry(
1251 deployment_slot: Slot,
1252 effective_slot: Slot,
1253 ) -> Arc<ProgramCacheEntry> {
1254 Arc::new(ProgramCacheEntry {
1255 program: ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1256 account_owner: ProgramCacheEntryOwner::NativeLoader,
1257 account_size: 0,
1258 deployment_slot,
1259 effective_slot,
1260 tx_usage_counter: AtomicU64::default(),
1261 ix_usage_counter: AtomicU64::default(),
1262 latest_access_slot: AtomicU64::default(),
1263 })
1264 }
1265
1266 fn set_tombstone(
1267 cache: &mut ProgramCache,
1268 key: Pubkey,
1269 slot: Slot,
1270 reason: ProgramCacheEntryType,
1271 ) -> Arc<ProgramCacheEntry> {
1272 let program = Arc::new(ProgramCacheEntry::new_tombstone(
1273 slot,
1274 ProgramCacheEntryOwner::LoaderV2,
1275 reason,
1276 ));
1277 cache.assign_program(key, program.clone());
1278 program
1279 }
1280
1281 fn insert_unloaded_entry(
1282 cache: &mut ProgramCache,
1283 key: Pubkey,
1284 slot: Slot,
1285 ) -> Arc<ProgramCacheEntry> {
1286 let loaded = new_test_entry_with_usage(slot, slot.saturating_add(1), AtomicU64::default());
1287 let unloaded = Arc::new(loaded.to_unloaded().expect("Failed to unload the program"));
1288 cache.assign_program(key, unloaded.clone());
1289 unloaded
1290 }
1291
1292 fn num_matching_entries<P>(cache: &ProgramCache, predicate: P) -> usize
1293 where
1294 P: Fn(&ProgramCacheEntryType) -> bool,
1295 {
1296 cache
1297 .get_flattened_entries_for_tests()
1298 .iter()
1299 .filter(|(_key, program)| predicate(&program.program))
1300 .count()
1301 }
1302
1303 #[test]
1304 fn test_usage_counter_decay() {
1305 let _cache = new_mock_cache();
1306 let program = new_test_entry_with_usage(10, 11, AtomicU64::new(32));
1307 program.update_access_slot(15);
1308 assert_eq!(program.decayed_usage_counter(15), 32);
1309 assert_eq!(program.decayed_usage_counter(16), 16);
1310 assert_eq!(program.decayed_usage_counter(17), 8);
1311 assert_eq!(program.decayed_usage_counter(18), 4);
1312 assert_eq!(program.decayed_usage_counter(19), 2);
1313 assert_eq!(program.decayed_usage_counter(20), 1);
1314 assert_eq!(program.decayed_usage_counter(21), 0);
1315 assert_eq!(program.decayed_usage_counter(15), 32);
1316 assert_eq!(program.decayed_usage_counter(14), 32);
1317
1318 program.update_access_slot(18);
1319 assert_eq!(program.decayed_usage_counter(15), 32);
1320 assert_eq!(program.decayed_usage_counter(16), 32);
1321 assert_eq!(program.decayed_usage_counter(17), 32);
1322 assert_eq!(program.decayed_usage_counter(18), 32);
1323 assert_eq!(program.decayed_usage_counter(19), 16);
1324 assert_eq!(program.decayed_usage_counter(20), 8);
1325 assert_eq!(program.decayed_usage_counter(21), 4);
1326
1327 assert_eq!(program.decayed_usage_counter(18 + 63), 0);
1329 assert_eq!(program.decayed_usage_counter(100), 0);
1330 }
1331
1332 fn program_deploy_test_helper(
1333 cache: &mut ProgramCache,
1334 program: Pubkey,
1335 deployment_slots: Vec<Slot>,
1336 usage_counters: Vec<u64>,
1337 programs: &mut Vec<(Pubkey, Slot, u64)>,
1338 ) {
1339 deployment_slots
1341 .iter()
1342 .enumerate()
1343 .for_each(|(i, deployment_slot)| {
1344 let usage_counter = *usage_counters.get(i).unwrap_or(&0);
1345 cache.assign_program(
1346 program,
1347 new_test_entry_with_usage(
1348 *deployment_slot,
1349 (*deployment_slot).saturating_add(2),
1350 AtomicU64::new(usage_counter),
1351 ),
1352 );
1353 programs.push((program, *deployment_slot, usage_counter));
1354 });
1355
1356 let env = Arc::new(BuiltinProgram::new_mock());
1358 for slot in 21..31 {
1359 set_tombstone(
1360 cache,
1361 program,
1362 slot,
1363 ProgramCacheEntryType::FailedVerification(env.clone()),
1364 );
1365 }
1366
1367 for slot in 31..41 {
1369 insert_unloaded_entry(cache, program, slot);
1370 }
1371 }
1372
1373 #[test]
1374 fn test_random_eviction() {
1375 let mut programs = vec![];
1376
1377 let mut cache = new_mock_cache();
1378
1379 program_deploy_test_helper(
1385 &mut cache,
1386 Pubkey::new_unique(),
1387 vec![0, 10, 20],
1388 vec![4, 5, 25],
1389 &mut programs,
1390 );
1391
1392 program_deploy_test_helper(
1394 &mut cache,
1395 Pubkey::new_unique(),
1396 vec![5, 11],
1397 vec![0, 2],
1398 &mut programs,
1399 );
1400
1401 program_deploy_test_helper(
1403 &mut cache,
1404 Pubkey::new_unique(),
1405 vec![0, 5, 15],
1406 vec![100, 3, 20],
1407 &mut programs,
1408 );
1409
1410 let num_loaded_expected = 8;
1412 let num_unloaded_expected = 30;
1414 let num_tombstones_expected = 30;
1416
1417 programs.sort_by_key(|(_id, _slot, usage_count)| *usage_count);
1419 let num_loaded = num_matching_entries(&cache, |program_type| {
1420 matches!(program_type, ProgramCacheEntryType::Loaded(_))
1421 });
1422 let num_unloaded = num_matching_entries(&cache, |program_type| {
1423 matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1424 });
1425 let num_tombstones = num_matching_entries(&cache, |program_type| {
1426 matches!(
1427 program_type,
1428 ProgramCacheEntryType::DelayVisibility
1429 | ProgramCacheEntryType::FailedVerification(_)
1430 | ProgramCacheEntryType::Closed
1431 )
1432 });
1433
1434 assert_eq!(num_loaded, num_loaded_expected);
1436 assert_eq!(num_unloaded, num_unloaded_expected);
1437 assert_eq!(num_tombstones, num_tombstones_expected);
1438
1439 let eviction_pct = 1;
1441
1442 let num_loaded_expected =
1443 Percentage::from(eviction_pct).apply_to(crate::loaded_programs::MAX_LOADED_ENTRY_COUNT);
1444 let num_unloaded_expected = num_unloaded_expected + num_loaded - num_loaded_expected;
1445 cache.evict_using_2s_random_selection(Percentage::from(eviction_pct), 21);
1446
1447 let num_loaded = num_matching_entries(&cache, |program_type| {
1449 matches!(program_type, ProgramCacheEntryType::Loaded(_))
1450 });
1451 let num_unloaded = num_matching_entries(&cache, |program_type| {
1452 matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1453 });
1454 let num_tombstones = num_matching_entries(&cache, |program_type| {
1455 matches!(program_type, ProgramCacheEntryType::FailedVerification(_))
1456 });
1457
1458 assert_eq!(num_loaded, num_loaded_expected);
1460 assert_eq!(num_unloaded, num_unloaded_expected);
1462 assert_eq!(num_tombstones, num_tombstones_expected);
1464 }
1465
1466 #[test]
1467 fn test_eviction() {
1468 let mut programs = vec![];
1469 let mut cache = new_mock_cache();
1470
1471 program_deploy_test_helper(
1473 &mut cache,
1474 Pubkey::new_unique(),
1475 vec![0, 10, 20],
1476 vec![4, 5, 25],
1477 &mut programs,
1478 );
1479
1480 program_deploy_test_helper(
1482 &mut cache,
1483 Pubkey::new_unique(),
1484 vec![5, 11],
1485 vec![0, 2],
1486 &mut programs,
1487 );
1488
1489 program_deploy_test_helper(
1491 &mut cache,
1492 Pubkey::new_unique(),
1493 vec![0, 5, 15],
1494 vec![100, 3, 20],
1495 &mut programs,
1496 );
1497
1498 let num_loaded_expected = 8;
1500 let num_unloaded_expected = 30;
1502 let num_tombstones_expected = 30;
1504
1505 programs.sort_by_key(|(_id, _slot, usage_count)| *usage_count);
1507 let num_loaded = num_matching_entries(&cache, |program_type| {
1508 matches!(program_type, ProgramCacheEntryType::Loaded(_))
1509 });
1510 let num_unloaded = num_matching_entries(&cache, |program_type| {
1511 matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1512 });
1513 let num_tombstones = num_matching_entries(&cache, |program_type| {
1514 matches!(program_type, ProgramCacheEntryType::FailedVerification(_))
1515 });
1516
1517 assert_eq!(num_loaded, num_loaded_expected);
1519 assert_eq!(num_unloaded, num_unloaded_expected);
1520 assert_eq!(num_tombstones, num_tombstones_expected);
1521
1522 let eviction_pct = 1;
1524
1525 let num_loaded_expected =
1526 Percentage::from(eviction_pct).apply_to(crate::loaded_programs::MAX_LOADED_ENTRY_COUNT);
1527 let num_unloaded_expected = num_unloaded_expected + num_loaded - num_loaded_expected;
1528
1529 cache.sort_and_unload(Percentage::from(eviction_pct));
1530
1531 let entries = cache.get_flattened_entries_for_tests();
1533 programs.iter().for_each(|entry| {
1534 assert!(entries.iter().any(|(key, _entry)| key == &entry.0));
1535 });
1536
1537 let unloaded = entries
1538 .iter()
1539 .filter_map(|(key, program)| {
1540 matches!(program.program, ProgramCacheEntryType::Unloaded(_))
1541 .then_some((*key, program.tx_usage_counter.load(Ordering::Relaxed)))
1542 })
1543 .collect::<Vec<(Pubkey, u64)>>();
1544
1545 for index in 0..3 {
1546 let expected = programs.get(index).expect("Missing program");
1547 assert!(unloaded.contains(&(expected.0, expected.2)));
1548 }
1549
1550 let num_loaded = num_matching_entries(&cache, |program_type| {
1552 matches!(program_type, ProgramCacheEntryType::Loaded(_))
1553 });
1554 let num_unloaded = num_matching_entries(&cache, |program_type| {
1555 matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1556 });
1557 let num_tombstones = num_matching_entries(&cache, |program_type| {
1558 matches!(
1559 program_type,
1560 ProgramCacheEntryType::DelayVisibility
1561 | ProgramCacheEntryType::FailedVerification(_)
1562 | ProgramCacheEntryType::Closed
1563 )
1564 });
1565
1566 assert_eq!(num_loaded, num_loaded_expected);
1568 assert_eq!(num_unloaded, num_unloaded_expected);
1570 assert_eq!(num_tombstones, num_tombstones_expected);
1572 }
1573
1574 #[test]
1575 fn test_usage_count_of_unloaded_program() {
1576 let mut cache = new_mock_cache();
1577
1578 let program = Pubkey::new_unique();
1579 let evict_to_pct = 2;
1580 let cache_capacity_after_shrink =
1581 Percentage::from(evict_to_pct).apply_to(crate::loaded_programs::MAX_LOADED_ENTRY_COUNT);
1582 let num_total_programs = (cache_capacity_after_shrink + 1) as u64;
1584 (0..num_total_programs).for_each(|i| {
1585 cache.assign_program(
1586 program,
1587 new_test_entry_with_usage(i, i + 2, AtomicU64::new(i + 10)),
1588 );
1589 });
1590
1591 cache.sort_and_unload(Percentage::from(evict_to_pct));
1592
1593 let num_unloaded = num_matching_entries(&cache, |program_type| {
1594 matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1595 });
1596 assert_eq!(num_unloaded, 1);
1597
1598 cache
1599 .get_flattened_entries_for_tests()
1600 .iter()
1601 .for_each(|(_key, program)| {
1602 if matches!(program.program, ProgramCacheEntryType::Unloaded(_)) {
1603 assert_eq!(program.tx_usage_counter.load(Ordering::Relaxed), 10);
1605 assert_eq!(program.deployment_slot, 0);
1606 assert_eq!(program.effective_slot, 2);
1607 }
1608 });
1609
1610 cache.assign_program(program, new_test_entry_with_usage(0, 2, AtomicU64::new(0)));
1613
1614 cache
1615 .get_flattened_entries_for_tests()
1616 .iter()
1617 .for_each(|(_key, program)| {
1618 if matches!(program.program, ProgramCacheEntryType::Unloaded(_))
1619 && program.deployment_slot == 0
1620 && program.effective_slot == 2
1621 {
1622 assert_eq!(program.tx_usage_counter.load(Ordering::Relaxed), 10);
1624 }
1625 });
1626 }
1627
1628 #[test]
1629 fn test_fuzz_assign_program_order() {
1630 use rand::prelude::SliceRandom;
1631 const EXPECTED_ENTRIES: [(u64, u64); 7] =
1632 [(1, 2), (5, 5), (5, 6), (5, 10), (9, 10), (10, 10), (3, 12)];
1633 let mut rng = rand::thread_rng();
1634 let program_id = Pubkey::new_unique();
1635 for _ in 0..1000 {
1636 let mut entries = EXPECTED_ENTRIES.to_vec();
1637 entries.shuffle(&mut rng);
1638 let mut cache = new_mock_cache();
1639 for (deployment_slot, effective_slot) in entries {
1640 assert!(!cache
1641 .assign_program(program_id, new_test_entry(deployment_slot, effective_slot)));
1642 }
1643 for ((deployment_slot, effective_slot), entry) in EXPECTED_ENTRIES
1644 .iter()
1645 .zip(cache.get_slot_versions_for_tests(&program_id).iter())
1646 {
1647 assert_eq!(entry.deployment_slot, *deployment_slot);
1648 assert_eq!(entry.effective_slot, *effective_slot);
1649 }
1650 }
1651 }
1652
1653 #[test_matrix(
1654 (
1655 ProgramCacheEntryType::Closed,
1656 ProgramCacheEntryType::FailedVerification(get_mock_env()),
1657 new_loaded_entry(get_mock_env()),
1658 ),
1659 (
1660 ProgramCacheEntryType::FailedVerification(get_mock_env()),
1661 ProgramCacheEntryType::Closed,
1662 ProgramCacheEntryType::Unloaded(get_mock_env()),
1663 new_loaded_entry(get_mock_env()),
1664 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1665 )
1666 )]
1667 #[test_matrix(
1668 (
1669 ProgramCacheEntryType::Unloaded(get_mock_env()),
1670 ),
1671 (
1672 ProgramCacheEntryType::FailedVerification(get_mock_env()),
1673 ProgramCacheEntryType::Closed,
1674 ProgramCacheEntryType::Unloaded(get_mock_env()),
1675 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1676 )
1677 )]
1678 #[test_matrix(
1679 (ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),),
1680 (
1681 ProgramCacheEntryType::FailedVerification(get_mock_env()),
1682 ProgramCacheEntryType::Closed,
1683 ProgramCacheEntryType::Unloaded(get_mock_env()),
1684 new_loaded_entry(get_mock_env()),
1685 )
1686 )]
1687 #[should_panic(expected = "Unexpected replacement of an entry")]
1688 fn test_assign_program_failure(old: ProgramCacheEntryType, new: ProgramCacheEntryType) {
1689 let mut cache = new_mock_cache();
1690 let program_id = Pubkey::new_unique();
1691 assert!(!cache.assign_program(
1692 program_id,
1693 Arc::new(ProgramCacheEntry {
1694 program: old,
1695 account_owner: ProgramCacheEntryOwner::LoaderV2,
1696 account_size: 0,
1697 deployment_slot: 10,
1698 effective_slot: 11,
1699 tx_usage_counter: AtomicU64::default(),
1700 ix_usage_counter: AtomicU64::default(),
1701 latest_access_slot: AtomicU64::default(),
1702 }),
1703 ));
1704 cache.assign_program(
1705 program_id,
1706 Arc::new(ProgramCacheEntry {
1707 program: new,
1708 account_owner: ProgramCacheEntryOwner::LoaderV2,
1709 account_size: 0,
1710 deployment_slot: 10,
1711 effective_slot: 11,
1712 tx_usage_counter: AtomicU64::default(),
1713 ix_usage_counter: AtomicU64::default(),
1714 latest_access_slot: AtomicU64::default(),
1715 }),
1716 );
1717 }
1718
1719 #[test_case(
1720 ProgramCacheEntryType::Unloaded(Arc::new(BuiltinProgram::new_mock())),
1721 new_loaded_entry(get_mock_env())
1722 )]
1723 #[test_case(
1724 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1725 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock())
1726 )]
1727 fn test_assign_program_success(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 assert!(!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]
1759 fn test_tombstone() {
1760 let env = Arc::new(BuiltinProgram::new_mock());
1761 let tombstone = ProgramCacheEntry::new_tombstone(
1762 0,
1763 ProgramCacheEntryOwner::LoaderV2,
1764 ProgramCacheEntryType::FailedVerification(env.clone()),
1765 );
1766 assert_matches!(
1767 tombstone.program,
1768 ProgramCacheEntryType::FailedVerification(_)
1769 );
1770 assert!(tombstone.is_tombstone());
1771 assert_eq!(tombstone.deployment_slot, 0);
1772 assert_eq!(tombstone.effective_slot, 0);
1773
1774 let tombstone = ProgramCacheEntry::new_tombstone(
1775 100,
1776 ProgramCacheEntryOwner::LoaderV2,
1777 ProgramCacheEntryType::Closed,
1778 );
1779 assert_matches!(tombstone.program, ProgramCacheEntryType::Closed);
1780 assert!(tombstone.is_tombstone());
1781 assert_eq!(tombstone.deployment_slot, 100);
1782 assert_eq!(tombstone.effective_slot, 100);
1783
1784 let mut cache = new_mock_cache();
1785 let program1 = Pubkey::new_unique();
1786 let tombstone = set_tombstone(
1787 &mut cache,
1788 program1,
1789 10,
1790 ProgramCacheEntryType::FailedVerification(env.clone()),
1791 );
1792 let slot_versions = cache.get_slot_versions_for_tests(&program1);
1793 assert_eq!(slot_versions.len(), 1);
1794 assert!(slot_versions.first().unwrap().is_tombstone());
1795 assert_eq!(tombstone.deployment_slot, 10);
1796 assert_eq!(tombstone.effective_slot, 10);
1797
1798 let program2 = Pubkey::new_unique();
1800 cache.assign_program(program2, new_test_builtin_entry(50, 51));
1801 let slot_versions = cache.get_slot_versions_for_tests(&program2);
1802 assert_eq!(slot_versions.len(), 1);
1803 assert!(!slot_versions.first().unwrap().is_tombstone());
1804
1805 let tombstone = set_tombstone(
1806 &mut cache,
1807 program2,
1808 60,
1809 ProgramCacheEntryType::FailedVerification(env),
1810 );
1811 let slot_versions = cache.get_slot_versions_for_tests(&program2);
1812 assert_eq!(slot_versions.len(), 2);
1813 assert!(!slot_versions.first().unwrap().is_tombstone());
1814 assert!(slot_versions.get(1).unwrap().is_tombstone());
1815 assert!(tombstone.is_tombstone());
1816 assert_eq!(tombstone.deployment_slot, 60);
1817 assert_eq!(tombstone.effective_slot, 60);
1818 }
1819
1820 #[test]
1821 fn test_prune_empty() {
1822 let mut cache = new_mock_cache();
1823
1824 cache.prune(0, 0);
1825 assert!(cache.get_flattened_entries_for_tests().is_empty());
1826
1827 cache.prune(10, 0);
1828 assert!(cache.get_flattened_entries_for_tests().is_empty());
1829
1830 let mut cache = new_mock_cache();
1831
1832 cache.prune(0, 0);
1833 assert!(cache.get_flattened_entries_for_tests().is_empty());
1834
1835 cache.prune(10, 0);
1836 assert!(cache.get_flattened_entries_for_tests().is_empty());
1837
1838 let mut cache = new_mock_cache();
1839
1840 cache.prune(0, 0);
1841 assert!(cache.get_flattened_entries_for_tests().is_empty());
1842
1843 cache.prune(10, 0);
1844 assert!(cache.get_flattened_entries_for_tests().is_empty());
1845
1846 let mut cache = new_mock_cache();
1847
1848 cache.prune(0, 0);
1849 assert!(cache.get_flattened_entries_for_tests().is_empty());
1850
1851 cache.prune(10, 0);
1852 assert!(cache.get_flattened_entries_for_tests().is_empty());
1853 }
1854
1855 #[test]
1856 fn test_prune_different_env() {
1857 let mut cache = new_mock_cache();
1858
1859 let program1 = Pubkey::new_unique();
1860 cache.assign_program(program1, new_test_entry(10, 10));
1861
1862 let new_env = Arc::new(BuiltinProgram::new_mock());
1863 cache.upcoming_environments = Some(ProgramRuntimeEnvironments {
1864 program_runtime_v1: new_env.clone(),
1865 program_runtime_v2: new_env.clone(),
1866 });
1867 let updated_program = Arc::new(ProgramCacheEntry {
1868 program: new_loaded_entry(new_env.clone()),
1869 account_owner: ProgramCacheEntryOwner::LoaderV2,
1870 account_size: 0,
1871 deployment_slot: 20,
1872 effective_slot: 20,
1873 tx_usage_counter: AtomicU64::default(),
1874 ix_usage_counter: AtomicU64::default(),
1875 latest_access_slot: AtomicU64::default(),
1876 });
1877 cache.assign_program(program1, updated_program.clone());
1878
1879 assert_eq!(cache.get_slot_versions_for_tests(&program1).len(), 2);
1881
1882 cache.prune(21, cache.latest_root_epoch);
1883
1884 assert_eq!(cache.get_slot_versions_for_tests(&program1).len(), 2);
1886
1887 cache.prune(22, cache.latest_root_epoch.saturating_add(1));
1888
1889 assert_eq!(cache.get_slot_versions_for_tests(&program1).len(), 1);
1891
1892 let entry = cache
1893 .get_slot_versions_for_tests(&program1)
1894 .first()
1895 .expect("Failed to get the program")
1896 .clone();
1897 assert_eq!(entry, updated_program);
1899 }
1900
1901 fn get_entries_to_load(
1902 cache: &ProgramCache,
1903 loading_slot: Slot,
1904 keys: &[Pubkey],
1905 ) -> Vec<(Pubkey, (ProgramCacheMatchCriteria, u64))> {
1906 let entries = cache.get_flattened_entries_for_tests();
1907 keys.iter()
1908 .filter_map(|key| {
1909 entries
1910 .iter()
1911 .rev()
1912 .find(|(program_id, entry)| {
1913 program_id == key && entry.deployment_slot <= loading_slot
1914 })
1915 .map(|(program_id, _entry)| {
1916 (*program_id, (ProgramCacheMatchCriteria::NoCriteria, 1))
1917 })
1918 })
1919 .collect()
1920 }
1921
1922 fn match_slot(
1923 extracted: &ProgramCacheForTxBatch,
1924 program: &Pubkey,
1925 deployment_slot: Slot,
1926 working_slot: Slot,
1927 ) -> bool {
1928 assert_eq!(extracted.slot, working_slot);
1929 extracted
1930 .entries
1931 .get(program)
1932 .map(|entry| entry.deployment_slot == deployment_slot)
1933 .unwrap_or(false)
1934 }
1935
1936 fn match_missing(
1937 missing: &[(Pubkey, (ProgramCacheMatchCriteria, u64))],
1938 program: &Pubkey,
1939 expected_result: bool,
1940 ) -> bool {
1941 missing.iter().any(|(key, _)| key == program) == expected_result
1942 }
1943
1944 #[test]
1945 fn test_fork_extract_and_prune() {
1946 let mut cache = new_mock_cache();
1947
1948 let program1 = Pubkey::new_unique();
1963 cache.assign_program(program1, new_test_entry(0, 1));
1964 cache.assign_program(program1, new_test_entry(10, 11));
1965 cache.assign_program(program1, new_test_entry(20, 21));
1966
1967 let program2 = Pubkey::new_unique();
1968 cache.assign_program(program2, new_test_entry(5, 6));
1969 cache.assign_program(
1970 program2,
1971 new_test_entry(11, 11 + DELAY_VISIBILITY_SLOT_OFFSET),
1972 );
1973
1974 let program3 = Pubkey::new_unique();
1975 cache.assign_program(program3, new_test_entry(25, 26));
1976
1977 let program4 = Pubkey::new_unique();
1978 cache.assign_program(program4, new_test_entry(0, 1));
1979 cache.assign_program(program4, new_test_entry(5, 6));
1980 cache.assign_program(
1982 program4,
1983 new_test_entry(15, 15 + DELAY_VISIBILITY_SLOT_OFFSET),
1984 );
1985
1986 let mut missing =
2003 get_entries_to_load(&cache, 22, &[program1, program2, program3, program4]);
2004 assert!(match_missing(&missing, &program2, false));
2005 assert!(match_missing(&missing, &program3, false));
2006 let mut extracted = ProgramCacheForTxBatch::new(22, cache.environments.clone(), None, 0);
2007 cache.extract(&mut missing, &mut extracted, true);
2008 assert!(match_slot(&extracted, &program1, 20, 22));
2009 assert!(match_slot(&extracted, &program4, 0, 22));
2010
2011 let mut missing =
2013 get_entries_to_load(&cache, 15, &[program1, program2, program3, program4]);
2014 assert!(match_missing(&missing, &program3, false));
2015 let mut extracted = ProgramCacheForTxBatch::new(15, cache.environments.clone(), None, 0);
2016 cache.extract(&mut missing, &mut extracted, true);
2017 assert!(match_slot(&extracted, &program1, 0, 15));
2018 assert!(match_slot(&extracted, &program2, 11, 15));
2019 let tombstone = extracted
2022 .find(&program4)
2023 .expect("Failed to find the tombstone");
2024 assert_matches!(tombstone.program, ProgramCacheEntryType::DelayVisibility);
2025 assert_eq!(tombstone.deployment_slot, 15);
2026
2027 let mut missing =
2029 get_entries_to_load(&cache, 18, &[program1, program2, program3, program4]);
2030 assert!(match_missing(&missing, &program3, false));
2031 let mut extracted = ProgramCacheForTxBatch::new(18, cache.environments.clone(), None, 0);
2032 cache.extract(&mut missing, &mut extracted, true);
2033 assert!(match_slot(&extracted, &program1, 0, 18));
2034 assert!(match_slot(&extracted, &program2, 11, 18));
2035 assert!(match_slot(&extracted, &program4, 15, 18));
2037
2038 let mut missing =
2040 get_entries_to_load(&cache, 23, &[program1, program2, program3, program4]);
2041 assert!(match_missing(&missing, &program3, false));
2042 let mut extracted = ProgramCacheForTxBatch::new(23, cache.environments.clone(), None, 0);
2043 cache.extract(&mut missing, &mut extracted, true);
2044 assert!(match_slot(&extracted, &program1, 0, 23));
2045 assert!(match_slot(&extracted, &program2, 11, 23));
2046 assert!(match_slot(&extracted, &program4, 15, 23));
2048
2049 let mut missing =
2051 get_entries_to_load(&cache, 11, &[program1, program2, program3, program4]);
2052 assert!(match_missing(&missing, &program3, false));
2053 let mut extracted = ProgramCacheForTxBatch::new(11, cache.environments.clone(), None, 0);
2054 cache.extract(&mut missing, &mut extracted, true);
2055 assert!(match_slot(&extracted, &program1, 0, 11));
2056 let tombstone = extracted
2058 .find(&program2)
2059 .expect("Failed to find the tombstone");
2060 assert_matches!(tombstone.program, ProgramCacheEntryType::DelayVisibility);
2061 assert_eq!(tombstone.deployment_slot, 11);
2062 assert!(match_slot(&extracted, &program4, 5, 11));
2063
2064 cache.prune(5, 0);
2065
2066 let mut missing =
2083 get_entries_to_load(&cache, 21, &[program1, program2, program3, program4]);
2084 assert!(match_missing(&missing, &program3, false));
2085 let mut extracted = ProgramCacheForTxBatch::new(21, cache.environments.clone(), None, 0);
2086 cache.extract(&mut missing, &mut extracted, true);
2087 assert!(match_slot(&extracted, &program1, 0, 21));
2089 assert!(match_slot(&extracted, &program2, 11, 21));
2090 assert!(match_slot(&extracted, &program4, 15, 21));
2091
2092 let mut missing =
2094 get_entries_to_load(&cache, 27, &[program1, program2, program3, program4]);
2095 let mut extracted = ProgramCacheForTxBatch::new(27, cache.environments.clone(), None, 0);
2096 cache.extract(&mut missing, &mut extracted, true);
2097 assert!(match_slot(&extracted, &program1, 0, 27));
2098 assert!(match_slot(&extracted, &program2, 11, 27));
2099 assert!(match_slot(&extracted, &program3, 25, 27));
2100 assert!(match_slot(&extracted, &program4, 5, 27));
2101
2102 cache.prune(15, 0);
2103
2104 let mut missing =
2121 get_entries_to_load(&cache, 23, &[program1, program2, program3, program4]);
2122 assert!(match_missing(&missing, &program3, false));
2123 let mut extracted = ProgramCacheForTxBatch::new(23, cache.environments.clone(), None, 0);
2124 cache.extract(&mut missing, &mut extracted, true);
2125 assert!(match_slot(&extracted, &program1, 0, 23));
2126 assert!(match_slot(&extracted, &program2, 11, 23));
2127 assert!(match_slot(&extracted, &program4, 15, 23));
2128 }
2129
2130 #[test]
2131 fn test_extract_using_deployment_slot() {
2132 let mut cache = new_mock_cache();
2133
2134 let program1 = Pubkey::new_unique();
2149 cache.assign_program(program1, new_test_entry(0, 1));
2150 cache.assign_program(program1, new_test_entry(20, 21));
2151
2152 let program2 = Pubkey::new_unique();
2153 cache.assign_program(program2, new_test_entry(5, 6));
2154 cache.assign_program(program2, new_test_entry(11, 12));
2155
2156 let program3 = Pubkey::new_unique();
2157 cache.assign_program(program3, new_test_entry(25, 26));
2158
2159 let mut missing = get_entries_to_load(&cache, 12, &[program1, program2, program3]);
2161 assert!(match_missing(&missing, &program3, false));
2162 let mut extracted = ProgramCacheForTxBatch::new(12, cache.environments.clone(), None, 0);
2163 cache.extract(&mut missing, &mut extracted, true);
2164 assert!(match_slot(&extracted, &program1, 0, 12));
2165 assert!(match_slot(&extracted, &program2, 11, 12));
2166
2167 let mut missing = get_entries_to_load(&cache, 12, &[program1, program2, program3]);
2169 missing.get_mut(0).unwrap().1 .0 = ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(5);
2170 missing.get_mut(1).unwrap().1 .0 = ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(5);
2171 assert!(match_missing(&missing, &program3, false));
2172 let mut extracted = ProgramCacheForTxBatch::new(12, cache.environments.clone(), None, 0);
2173 cache.extract(&mut missing, &mut extracted, true);
2174 assert!(match_missing(&missing, &program1, true));
2175 assert!(match_slot(&extracted, &program2, 11, 12));
2176 }
2177
2178 #[test]
2179 fn test_extract_unloaded() {
2180 let mut cache = new_mock_cache();
2181
2182 let program1 = Pubkey::new_unique();
2198 cache.assign_program(program1, new_test_entry(0, 1));
2199 cache.assign_program(program1, new_test_entry(20, 21));
2200
2201 let program2 = Pubkey::new_unique();
2202 cache.assign_program(program2, new_test_entry(5, 6));
2203 cache.assign_program(program2, new_test_entry(11, 12));
2204
2205 let program3 = Pubkey::new_unique();
2206 let _ = insert_unloaded_entry(&mut cache, program3, 25);
2208
2209 cache.assign_program(
2213 program3,
2214 Arc::new(
2215 new_test_entry(20, 21)
2216 .to_unloaded()
2217 .expect("Failed to create unloaded program"),
2218 ),
2219 );
2220
2221 let mut missing = get_entries_to_load(&cache, 19, &[program1, program2, program3]);
2223 assert!(match_missing(&missing, &program3, false));
2224 let mut extracted = ProgramCacheForTxBatch::new(19, cache.environments.clone(), None, 0);
2225 cache.extract(&mut missing, &mut extracted, true);
2226 assert!(match_slot(&extracted, &program1, 0, 19));
2227 assert!(match_slot(&extracted, &program2, 11, 19));
2228
2229 let mut missing = get_entries_to_load(&cache, 27, &[program1, program2, program3]);
2231 let mut extracted = ProgramCacheForTxBatch::new(27, cache.environments.clone(), None, 0);
2232 cache.extract(&mut missing, &mut extracted, true);
2233 assert!(match_slot(&extracted, &program1, 0, 27));
2234 assert!(match_slot(&extracted, &program2, 11, 27));
2235 assert!(match_missing(&missing, &program3, true));
2236
2237 let mut missing = get_entries_to_load(&cache, 22, &[program1, program2, program3]);
2239 assert!(match_missing(&missing, &program2, false));
2240 let mut extracted = ProgramCacheForTxBatch::new(22, cache.environments.clone(), None, 0);
2241 cache.extract(&mut missing, &mut extracted, true);
2242 assert!(match_slot(&extracted, &program1, 20, 22));
2243 assert!(match_missing(&missing, &program3, true));
2244 }
2245
2246 #[test]
2247 fn test_extract_nonexistent() {
2248 let cache = new_mock_cache();
2249
2250 let program1 = Pubkey::new_unique();
2251 let mut missing = vec![(program1, (ProgramCacheMatchCriteria::NoCriteria, 1))];
2252 let mut extracted = ProgramCacheForTxBatch::new(0, cache.environments.clone(), None, 0);
2253 cache.extract(&mut missing, &mut extracted, true);
2254 assert!(match_missing(&missing, &program1, true));
2255 }
2256
2257 #[test]
2258 fn test_unloaded() {
2259 let mut cache = new_mock_cache();
2260 for program_cache_entry_type in [
2261 ProgramCacheEntryType::FailedVerification(
2262 cache.environments.program_runtime_v1.clone(),
2263 ),
2264 ProgramCacheEntryType::Closed,
2265 ProgramCacheEntryType::Unloaded(cache.environments.program_runtime_v1.clone()),
2266 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
2267 ] {
2268 let entry = Arc::new(ProgramCacheEntry {
2269 program: program_cache_entry_type,
2270 account_owner: ProgramCacheEntryOwner::LoaderV2,
2271 account_size: 0,
2272 deployment_slot: 0,
2273 effective_slot: 0,
2274 tx_usage_counter: AtomicU64::default(),
2275 ix_usage_counter: AtomicU64::default(),
2276 latest_access_slot: AtomicU64::default(),
2277 });
2278 assert!(entry.to_unloaded().is_none());
2279
2280 let program_id = Pubkey::new_unique();
2282 cache.assign_program(program_id, entry.clone());
2283 cache.unload_program_entry(&program_id, &entry);
2284 assert_eq!(cache.get_slot_versions_for_tests(&program_id).len(), 1);
2285 assert!(cache.stats.evictions.is_empty());
2286 }
2287
2288 let entry = new_test_entry_with_usage(1, 2, AtomicU64::new(3));
2289 let unloaded_entry = entry.to_unloaded().unwrap();
2290 assert_eq!(unloaded_entry.deployment_slot, 1);
2291 assert_eq!(unloaded_entry.effective_slot, 2);
2292 assert_eq!(unloaded_entry.latest_access_slot.load(Ordering::Relaxed), 1);
2293 assert_eq!(unloaded_entry.tx_usage_counter.load(Ordering::Relaxed), 3);
2294
2295 let program_id = Pubkey::new_unique();
2297 cache.assign_program(program_id, entry.clone());
2298 cache.unload_program_entry(&program_id, &entry);
2299 assert!(cache.stats.evictions.contains_key(&program_id));
2300 }
2301
2302 #[test]
2303 fn test_fork_prune_find_first_ancestor() {
2304 let mut cache = new_mock_cache();
2305
2306 let program1 = Pubkey::new_unique();
2317 cache.assign_program(program1, new_test_entry(0, 1));
2318 cache.assign_program(program1, new_test_entry(5, 6));
2319
2320 cache.prune(10, 0);
2321
2322 let mut missing = get_entries_to_load(&cache, 20, &[program1]);
2323 let mut extracted = ProgramCacheForTxBatch::new(20, cache.environments.clone(), None, 0);
2324 cache.extract(&mut missing, &mut extracted, true);
2325
2326 assert_eq!(
2328 extracted
2329 .find(&program1)
2330 .expect("Did not find the program")
2331 .deployment_slot,
2332 0
2333 );
2334 }
2335
2336 #[test]
2337 fn test_prune_by_deployment_slot() {
2338 let mut cache = new_mock_cache();
2339
2340 let program1 = Pubkey::new_unique();
2351 cache.assign_program(program1, new_test_entry(0, 1));
2352 cache.assign_program(program1, new_test_entry(5, 6));
2353
2354 let program2 = Pubkey::new_unique();
2355 cache.assign_program(program2, new_test_entry(10, 11));
2356
2357 let mut missing = get_entries_to_load(&cache, 20, &[program1, program2]);
2358 let mut extracted = ProgramCacheForTxBatch::new(20, cache.environments.clone(), None, 0);
2359 cache.extract(&mut missing, &mut extracted, true);
2360 assert!(match_slot(&extracted, &program1, 0, 20));
2361 assert!(match_slot(&extracted, &program2, 10, 20));
2362
2363 let mut missing = get_entries_to_load(&cache, 6, &[program1, program2]);
2364 assert!(match_missing(&missing, &program2, false));
2365 let mut extracted = ProgramCacheForTxBatch::new(6, cache.environments.clone(), None, 0);
2366 cache.extract(&mut missing, &mut extracted, true);
2367 assert!(match_slot(&extracted, &program1, 5, 6));
2368
2369 cache.prune_by_deployment_slot(5);
2372
2373 let mut missing = get_entries_to_load(&cache, 20, &[program1, program2]);
2374 let mut extracted = ProgramCacheForTxBatch::new(20, cache.environments.clone(), None, 0);
2375 cache.extract(&mut missing, &mut extracted, true);
2376 assert!(match_slot(&extracted, &program1, 0, 20));
2377 assert!(match_slot(&extracted, &program2, 10, 20));
2378
2379 let mut missing = get_entries_to_load(&cache, 6, &[program1, program2]);
2380 assert!(match_missing(&missing, &program2, false));
2381 let mut extracted = ProgramCacheForTxBatch::new(6, cache.environments.clone(), None, 0);
2382 cache.extract(&mut missing, &mut extracted, true);
2383 assert!(match_slot(&extracted, &program1, 0, 6));
2384
2385 cache.prune_by_deployment_slot(10);
2388
2389 let mut missing = get_entries_to_load(&cache, 20, &[program1, program2]);
2390 assert!(match_missing(&missing, &program2, false));
2391 let mut extracted = ProgramCacheForTxBatch::new(20, cache.environments.clone(), None, 0);
2392 cache.extract(&mut missing, &mut extracted, true);
2393 assert!(match_slot(&extracted, &program1, 0, 20));
2394 }
2395
2396 #[test]
2397 fn test_usable_entries_for_slot() {
2398 new_mock_cache();
2399 let tombstone = Arc::new(ProgramCacheEntry::new_tombstone(
2400 0,
2401 ProgramCacheEntryOwner::LoaderV2,
2402 ProgramCacheEntryType::Closed,
2403 ));
2404
2405 assert!(ProgramCache::matches_criteria(
2406 &tombstone,
2407 &ProgramCacheMatchCriteria::NoCriteria
2408 ));
2409
2410 assert!(ProgramCache::matches_criteria(
2411 &tombstone,
2412 &ProgramCacheMatchCriteria::Tombstone
2413 ));
2414
2415 assert!(ProgramCache::matches_criteria(
2416 &tombstone,
2417 &ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(0)
2418 ));
2419
2420 assert!(!ProgramCache::matches_criteria(
2421 &tombstone,
2422 &ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(1)
2423 ));
2424
2425 let program = new_test_entry(0, 1);
2426
2427 assert!(ProgramCache::matches_criteria(
2428 &program,
2429 &ProgramCacheMatchCriteria::NoCriteria
2430 ));
2431
2432 assert!(!ProgramCache::matches_criteria(
2433 &program,
2434 &ProgramCacheMatchCriteria::Tombstone
2435 ));
2436
2437 assert!(ProgramCache::matches_criteria(
2438 &program,
2439 &ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(0)
2440 ));
2441
2442 assert!(!ProgramCache::matches_criteria(
2443 &program,
2444 &ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(1)
2445 ));
2446
2447 let program = Arc::new(new_test_entry_with_usage(0, 1, AtomicU64::default()));
2448
2449 assert!(ProgramCache::matches_criteria(
2450 &program,
2451 &ProgramCacheMatchCriteria::NoCriteria
2452 ));
2453
2454 assert!(!ProgramCache::matches_criteria(
2455 &program,
2456 &ProgramCacheMatchCriteria::Tombstone
2457 ));
2458
2459 assert!(ProgramCache::matches_criteria(
2460 &program,
2461 &ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(0)
2462 ));
2463
2464 assert!(!ProgramCache::matches_criteria(
2465 &program,
2466 &ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(1)
2467 ));
2468 }
2469}