1use {
2 crate::invoke_context::{BuiltinFunctionWithContext, InvokeContext},
3 log::{debug, error, log_enabled, trace},
4 percentage::PercentageInteger,
5 atlas_clock::{Epoch, Slot},
6 atlas_pubkey::Pubkey,
7 atlas_sbpf::{
8 elf::Executable, program::BuiltinProgram, verifier::RequisiteVerifier, vm::Config,
9 },
10 atlas_sdk_ids::{
11 bpf_loader, bpf_loader_deprecated, bpf_loader_upgradeable, loader_v4, native_loader,
12 },
13 atlas_svm_type_overrides::{
14 rand::{thread_rng, Rng},
15 sync::{
16 atomic::{AtomicU64, Ordering},
17 Arc, Condvar, Mutex, RwLock,
18 },
19 thread,
20 },
21 std::{
22 collections::{hash_map::Entry, HashMap},
23 fmt::{Debug, Formatter},
24 sync::Weak,
25 },
26};
27#[cfg(feature = "metrics")]
28use {atlas_svm_measure::measure::Measure, atlas_svm_timings::ExecuteDetailsTimings};
29
30pub type ProgramRuntimeEnvironment = Arc<BuiltinProgram<InvokeContext<'static>>>;
31pub const MAX_LOADED_ENTRY_COUNT: usize = 512;
32pub const DELAY_VISIBILITY_SLOT_OFFSET: Slot = 1;
33
34#[derive(Copy, Clone, Debug, PartialEq)]
36pub enum BlockRelation {
37 Ancestor,
39 Equal,
41 Descendant,
43 Unrelated,
45 Unknown,
47}
48
49pub trait ForkGraph {
51 fn relationship(&self, a: Slot, b: Slot) -> BlockRelation;
53}
54
55#[derive(Default, Clone, Copy, PartialEq, Eq, Debug)]
57pub enum ProgramCacheEntryOwner {
58 #[default]
59 NativeLoader,
60 LoaderV1,
61 LoaderV2,
62 LoaderV3,
63 LoaderV4,
64}
65
66impl TryFrom<&Pubkey> for ProgramCacheEntryOwner {
67 type Error = ();
68 fn try_from(loader_key: &Pubkey) -> Result<Self, ()> {
69 if native_loader::check_id(loader_key) {
70 Ok(ProgramCacheEntryOwner::NativeLoader)
71 } else if bpf_loader_deprecated::check_id(loader_key) {
72 Ok(ProgramCacheEntryOwner::LoaderV1)
73 } else if bpf_loader::check_id(loader_key) {
74 Ok(ProgramCacheEntryOwner::LoaderV2)
75 } else if bpf_loader_upgradeable::check_id(loader_key) {
76 Ok(ProgramCacheEntryOwner::LoaderV3)
77 } else if loader_v4::check_id(loader_key) {
78 Ok(ProgramCacheEntryOwner::LoaderV4)
79 } else {
80 Err(())
81 }
82 }
83}
84
85impl From<ProgramCacheEntryOwner> for Pubkey {
86 fn from(program_cache_entry_owner: ProgramCacheEntryOwner) -> Self {
87 match program_cache_entry_owner {
88 ProgramCacheEntryOwner::NativeLoader => native_loader::id(),
89 ProgramCacheEntryOwner::LoaderV1 => bpf_loader_deprecated::id(),
90 ProgramCacheEntryOwner::LoaderV2 => bpf_loader::id(),
91 ProgramCacheEntryOwner::LoaderV3 => bpf_loader_upgradeable::id(),
92 ProgramCacheEntryOwner::LoaderV4 => loader_v4::id(),
93 }
94 }
95}
96
97#[derive(Default)]
127pub enum ProgramCacheEntryType {
128 FailedVerification(ProgramRuntimeEnvironment),
130 #[default]
134 Closed,
135 DelayVisibility,
137 Unloaded(ProgramRuntimeEnvironment),
141 Loaded(Executable<InvokeContext<'static>>),
143 Builtin(BuiltinProgram<InvokeContext<'static>>),
145}
146
147impl Debug for ProgramCacheEntryType {
148 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
149 match self {
150 ProgramCacheEntryType::FailedVerification(_) => {
151 write!(f, "ProgramCacheEntryType::FailedVerification")
152 }
153 ProgramCacheEntryType::Closed => write!(f, "ProgramCacheEntryType::Closed"),
154 ProgramCacheEntryType::DelayVisibility => {
155 write!(f, "ProgramCacheEntryType::DelayVisibility")
156 }
157 ProgramCacheEntryType::Unloaded(_) => write!(f, "ProgramCacheEntryType::Unloaded"),
158 ProgramCacheEntryType::Loaded(_) => write!(f, "ProgramCacheEntryType::Loaded"),
159 ProgramCacheEntryType::Builtin(_) => write!(f, "ProgramCacheEntryType::Builtin"),
160 }
161 }
162}
163
164impl ProgramCacheEntryType {
165 pub fn get_environment(&self) -> Option<&ProgramRuntimeEnvironment> {
167 match self {
168 ProgramCacheEntryType::Loaded(program) => Some(program.get_loader()),
169 ProgramCacheEntryType::FailedVerification(env)
170 | ProgramCacheEntryType::Unloaded(env) => Some(env),
171 _ => None,
172 }
173 }
174}
175
176#[derive(Debug, Default)]
180pub struct ProgramCacheEntry {
181 pub program: ProgramCacheEntryType,
183 pub account_owner: ProgramCacheEntryOwner,
185 pub account_size: usize,
187 pub deployment_slot: Slot,
189 pub effective_slot: Slot,
191 pub tx_usage_counter: Arc<AtomicU64>,
193 pub latest_access_slot: AtomicU64,
195}
196
197#[derive(Debug, Default)]
199pub struct ProgramCacheStats {
200 pub hits: AtomicU64,
202 pub misses: AtomicU64,
204 pub evictions: HashMap<Pubkey, u64>,
206 pub reloads: AtomicU64,
208 pub insertions: AtomicU64,
210 pub lost_insertions: AtomicU64,
212 pub replacements: AtomicU64,
214 pub one_hit_wonders: AtomicU64,
216 pub prunes_orphan: AtomicU64,
218 pub prunes_environment: AtomicU64,
220 pub empty_entries: AtomicU64,
222 pub water_level: AtomicU64,
224}
225
226impl ProgramCacheStats {
227 pub fn reset(&mut self) {
228 *self = ProgramCacheStats::default();
229 }
230 pub fn log(&self) {
231 let hits = self.hits.load(Ordering::Relaxed);
232 let misses = self.misses.load(Ordering::Relaxed);
233 let evictions: u64 = self.evictions.values().sum();
234 let reloads = self.reloads.load(Ordering::Relaxed);
235 let insertions = self.insertions.load(Ordering::Relaxed);
236 let lost_insertions = self.lost_insertions.load(Ordering::Relaxed);
237 let replacements = self.replacements.load(Ordering::Relaxed);
238 let one_hit_wonders = self.one_hit_wonders.load(Ordering::Relaxed);
239 let prunes_orphan = self.prunes_orphan.load(Ordering::Relaxed);
240 let prunes_environment = self.prunes_environment.load(Ordering::Relaxed);
241 let empty_entries = self.empty_entries.load(Ordering::Relaxed);
242 let water_level = self.water_level.load(Ordering::Relaxed);
243 debug!(
244 "Loaded Programs Cache Stats -- Hits: {hits}, Misses: {misses}, Evictions: \
245 {evictions}, Reloads: {reloads}, Insertions: {insertions}, Lost-Insertions: \
246 {lost_insertions}, Replacements: {replacements}, One-Hit-Wonders: {one_hit_wonders}, \
247 Prunes-Orphan: {prunes_orphan}, Prunes-Environment: {prunes_environment}, Empty: \
248 {empty_entries}, Water-Level: {water_level}"
249 );
250 if log_enabled!(log::Level::Trace) && !self.evictions.is_empty() {
251 let mut evictions = self.evictions.iter().collect::<Vec<_>>();
252 evictions.sort_by_key(|e| e.1);
253 let evictions = evictions
254 .into_iter()
255 .rev()
256 .map(|(program_id, evictions)| {
257 format!(" {:<44} {}", program_id.to_string(), evictions)
258 })
259 .collect::<Vec<_>>();
260 let evictions = evictions.join("\n");
261 trace!(
262 "Eviction Details:\n {:<44} {}\n{}",
263 "Program",
264 "Count",
265 evictions
266 );
267 }
268 }
269}
270
271#[cfg(feature = "metrics")]
272#[derive(Debug, Default)]
274pub struct LoadProgramMetrics {
275 pub program_id: String,
277 pub register_syscalls_us: u64,
279 pub load_elf_us: u64,
281 pub verify_code_us: u64,
283 pub jit_compile_us: u64,
285}
286
287#[cfg(feature = "metrics")]
288impl LoadProgramMetrics {
289 pub fn submit_datapoint(&self, timings: &mut ExecuteDetailsTimings) {
290 timings.create_executor_register_syscalls_us += self.register_syscalls_us;
291 timings.create_executor_load_elf_us += self.load_elf_us;
292 timings.create_executor_verify_code_us += self.verify_code_us;
293 timings.create_executor_jit_compile_us += self.jit_compile_us;
294 datapoint_trace!(
295 "create_executor_trace",
296 ("program_id", self.program_id, String),
297 ("register_syscalls_us", self.register_syscalls_us, i64),
298 ("load_elf_us", self.load_elf_us, i64),
299 ("verify_code_us", self.verify_code_us, i64),
300 ("jit_compile_us", self.jit_compile_us, i64),
301 );
302 }
303}
304
305impl PartialEq for ProgramCacheEntry {
306 fn eq(&self, other: &Self) -> bool {
307 self.effective_slot == other.effective_slot
308 && self.deployment_slot == other.deployment_slot
309 && self.is_tombstone() == other.is_tombstone()
310 }
311}
312
313impl ProgramCacheEntry {
314 pub fn new(
316 loader_key: &Pubkey,
317 program_runtime_environment: ProgramRuntimeEnvironment,
318 deployment_slot: Slot,
319 effective_slot: Slot,
320 elf_bytes: &[u8],
321 account_size: usize,
322 #[cfg(feature = "metrics")] metrics: &mut LoadProgramMetrics,
323 ) -> Result<Self, Box<dyn std::error::Error>> {
324 Self::new_internal(
325 loader_key,
326 program_runtime_environment,
327 deployment_slot,
328 effective_slot,
329 elf_bytes,
330 account_size,
331 #[cfg(feature = "metrics")]
332 metrics,
333 false, )
335 }
336
337 pub unsafe fn reload(
346 loader_key: &Pubkey,
347 program_runtime_environment: Arc<BuiltinProgram<InvokeContext<'static>>>,
348 deployment_slot: Slot,
349 effective_slot: Slot,
350 elf_bytes: &[u8],
351 account_size: usize,
352 #[cfg(feature = "metrics")] metrics: &mut LoadProgramMetrics,
353 ) -> Result<Self, Box<dyn std::error::Error>> {
354 Self::new_internal(
355 loader_key,
356 program_runtime_environment,
357 deployment_slot,
358 effective_slot,
359 elf_bytes,
360 account_size,
361 #[cfg(feature = "metrics")]
362 metrics,
363 true, )
365 }
366
367 fn new_internal(
368 loader_key: &Pubkey,
369 program_runtime_environment: Arc<BuiltinProgram<InvokeContext<'static>>>,
370 deployment_slot: Slot,
371 effective_slot: Slot,
372 elf_bytes: &[u8],
373 account_size: usize,
374 #[cfg(feature = "metrics")] metrics: &mut LoadProgramMetrics,
375 reloading: bool,
376 ) -> Result<Self, Box<dyn std::error::Error>> {
377 #[cfg(feature = "metrics")]
378 let load_elf_time = Measure::start("load_elf_time");
379 #[allow(unused_mut)]
382 let mut executable = Executable::load(elf_bytes, program_runtime_environment.clone())?;
383 #[cfg(feature = "metrics")]
384 {
385 metrics.load_elf_us = load_elf_time.end_as_us();
386 }
387
388 if !reloading {
389 #[cfg(feature = "metrics")]
390 let verify_code_time = Measure::start("verify_code_time");
391 executable.verify::<RequisiteVerifier>()?;
392 #[cfg(feature = "metrics")]
393 {
394 metrics.verify_code_us = verify_code_time.end_as_us();
395 }
396 }
397
398 #[cfg(all(not(target_os = "windows"), target_arch = "x86_64"))]
399 {
400 #[cfg(feature = "metrics")]
401 let jit_compile_time = Measure::start("jit_compile_time");
402 executable.jit_compile()?;
403 #[cfg(feature = "metrics")]
404 {
405 metrics.jit_compile_us = jit_compile_time.end_as_us();
406 }
407 }
408
409 Ok(Self {
410 deployment_slot,
411 account_owner: ProgramCacheEntryOwner::try_from(loader_key).unwrap(),
412 account_size,
413 effective_slot,
414 tx_usage_counter: Arc::<AtomicU64>::default(),
415 program: ProgramCacheEntryType::Loaded(executable),
416 latest_access_slot: AtomicU64::new(0),
417 })
418 }
419
420 pub fn to_unloaded(&self) -> Option<Self> {
421 match &self.program {
422 ProgramCacheEntryType::Loaded(_) => {}
423 ProgramCacheEntryType::FailedVerification(_)
424 | ProgramCacheEntryType::Closed
425 | ProgramCacheEntryType::DelayVisibility
426 | ProgramCacheEntryType::Unloaded(_)
427 | ProgramCacheEntryType::Builtin(_) => {
428 return None;
429 }
430 }
431 Some(Self {
432 program: ProgramCacheEntryType::Unloaded(self.program.get_environment()?.clone()),
433 account_owner: self.account_owner,
434 account_size: self.account_size,
435 deployment_slot: self.deployment_slot,
436 effective_slot: self.effective_slot,
437 tx_usage_counter: self.tx_usage_counter.clone(),
438 latest_access_slot: AtomicU64::new(self.latest_access_slot.load(Ordering::Relaxed)),
439 })
440 }
441
442 pub fn new_builtin(
444 deployment_slot: Slot,
445 account_size: usize,
446 builtin_function: BuiltinFunctionWithContext,
447 ) -> Self {
448 let mut program = BuiltinProgram::new_builtin();
449 program
450 .register_function("entrypoint", builtin_function)
451 .unwrap();
452 Self {
453 deployment_slot,
454 account_owner: ProgramCacheEntryOwner::NativeLoader,
455 account_size,
456 effective_slot: deployment_slot,
457 tx_usage_counter: Arc::<AtomicU64>::default(),
458 program: ProgramCacheEntryType::Builtin(program),
459 latest_access_slot: AtomicU64::new(0),
460 }
461 }
462
463 pub fn new_tombstone(
464 slot: Slot,
465 account_owner: ProgramCacheEntryOwner,
466 reason: ProgramCacheEntryType,
467 ) -> Self {
468 Self::new_tombstone_with_usage_counter(
469 slot,
470 account_owner,
471 reason,
472 Arc::<AtomicU64>::default(),
473 )
474 }
475
476 pub fn new_tombstone_with_usage_counter(
477 slot: Slot,
478 account_owner: ProgramCacheEntryOwner,
479 reason: ProgramCacheEntryType,
480 tx_usage_counter: Arc<AtomicU64>,
481 ) -> Self {
482 let tombstone = Self {
483 program: reason,
484 account_owner,
485 account_size: 0,
486 deployment_slot: slot,
487 effective_slot: slot,
488 tx_usage_counter,
489 latest_access_slot: AtomicU64::new(0),
490 };
491 debug_assert!(tombstone.is_tombstone());
492 tombstone
493 }
494
495 pub fn is_tombstone(&self) -> bool {
496 matches!(
497 self.program,
498 ProgramCacheEntryType::FailedVerification(_)
499 | ProgramCacheEntryType::Closed
500 | ProgramCacheEntryType::DelayVisibility
501 )
502 }
503
504 fn is_implicit_delay_visibility_tombstone(&self, slot: Slot) -> bool {
505 !matches!(self.program, ProgramCacheEntryType::Builtin(_))
506 && self.effective_slot.saturating_sub(self.deployment_slot)
507 == DELAY_VISIBILITY_SLOT_OFFSET
508 && slot >= self.deployment_slot
509 && slot < self.effective_slot
510 }
511
512 pub fn update_access_slot(&self, slot: Slot) {
513 let _ = self.latest_access_slot.fetch_max(slot, Ordering::Relaxed);
514 }
515
516 pub fn decayed_usage_counter(&self, now: Slot) -> u64 {
517 let last_access = self.latest_access_slot.load(Ordering::Relaxed);
518 let decaying_for = std::cmp::min(63, now.saturating_sub(last_access));
520 self.tx_usage_counter.load(Ordering::Relaxed) >> decaying_for
521 }
522
523 pub fn account_owner(&self) -> Pubkey {
524 self.account_owner.into()
525 }
526}
527
528#[derive(Clone, Debug)]
532pub struct ProgramRuntimeEnvironments {
533 pub program_runtime_v1: ProgramRuntimeEnvironment,
535 pub program_runtime_v2: ProgramRuntimeEnvironment,
537}
538
539impl Default for ProgramRuntimeEnvironments {
540 fn default() -> Self {
541 let empty_loader = Arc::new(BuiltinProgram::new_loader(Config::default()));
542 Self {
543 program_runtime_v1: empty_loader.clone(),
544 program_runtime_v2: empty_loader,
545 }
546 }
547}
548
549#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
550pub struct LoadingTaskCookie(u64);
551
552impl LoadingTaskCookie {
553 fn new() -> Self {
554 Self(0)
555 }
556
557 fn update(&mut self) {
558 let LoadingTaskCookie(cookie) = self;
559 *cookie = cookie.wrapping_add(1);
560 }
561}
562
563#[derive(Debug, Default)]
565pub struct LoadingTaskWaiter {
566 cookie: Mutex<LoadingTaskCookie>,
567 cond: Condvar,
568}
569
570impl LoadingTaskWaiter {
571 pub fn new() -> Self {
572 Self {
573 cookie: Mutex::new(LoadingTaskCookie::new()),
574 cond: Condvar::new(),
575 }
576 }
577
578 pub fn cookie(&self) -> LoadingTaskCookie {
579 *self.cookie.lock().unwrap()
580 }
581
582 pub fn notify(&self) {
583 let mut cookie = self.cookie.lock().unwrap();
584 cookie.update();
585 self.cond.notify_all();
586 }
587
588 pub fn wait(&self, cookie: LoadingTaskCookie) -> LoadingTaskCookie {
589 let cookie_guard = self.cookie.lock().unwrap();
590 *self
591 .cond
592 .wait_while(cookie_guard, |current_cookie| *current_cookie == cookie)
593 .unwrap()
594 }
595}
596
597#[derive(Debug)]
598enum IndexImplementation {
599 V1 {
601 entries: HashMap<Pubkey, Vec<Arc<ProgramCacheEntry>>>,
606 loading_entries: Mutex<HashMap<Pubkey, (Slot, thread::ThreadId)>>,
615 },
616}
617
618pub struct ProgramCache<FG: ForkGraph> {
634 index: IndexImplementation,
636 pub latest_root_slot: Slot,
638 pub latest_root_epoch: Epoch,
640 pub environments: ProgramRuntimeEnvironments,
642 pub upcoming_environments: Option<ProgramRuntimeEnvironments>,
648 pub programs_to_recompile: Vec<(Pubkey, Arc<ProgramCacheEntry>)>,
650 pub stats: ProgramCacheStats,
652 pub fork_graph: Option<Weak<RwLock<FG>>>,
654 pub loading_task_waiter: Arc<LoadingTaskWaiter>,
656}
657
658impl<FG: ForkGraph> Debug for ProgramCache<FG> {
659 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
660 f.debug_struct("ProgramCache")
661 .field("root slot", &self.latest_root_slot)
662 .field("root epoch", &self.latest_root_epoch)
663 .field("stats", &self.stats)
664 .field("index", &self.index)
665 .finish()
666 }
667}
668
669#[derive(Clone, Debug, Default)]
675pub struct ProgramCacheForTxBatch {
676 entries: HashMap<Pubkey, Arc<ProgramCacheEntry>>,
679 modified_entries: HashMap<Pubkey, Arc<ProgramCacheEntry>>,
681 slot: Slot,
682 pub environments: ProgramRuntimeEnvironments,
683 pub upcoming_environments: Option<ProgramRuntimeEnvironments>,
691 pub latest_root_epoch: Epoch,
693 pub hit_max_limit: bool,
694 pub loaded_missing: bool,
695 pub merged_modified: bool,
696}
697
698impl ProgramCacheForTxBatch {
699 pub fn new(
700 slot: Slot,
701 environments: ProgramRuntimeEnvironments,
702 upcoming_environments: Option<ProgramRuntimeEnvironments>,
703 latest_root_epoch: Epoch,
704 ) -> Self {
705 Self {
706 entries: HashMap::new(),
707 modified_entries: HashMap::new(),
708 slot,
709 environments,
710 upcoming_environments,
711 latest_root_epoch,
712 hit_max_limit: false,
713 loaded_missing: false,
714 merged_modified: false,
715 }
716 }
717
718 pub fn new_from_cache<FG: ForkGraph>(
719 slot: Slot,
720 epoch: Epoch,
721 cache: &ProgramCache<FG>,
722 ) -> Self {
723 Self {
724 entries: HashMap::new(),
725 modified_entries: HashMap::new(),
726 slot,
727 environments: cache.get_environments_for_epoch(epoch),
728 upcoming_environments: cache.get_upcoming_environments_for_epoch(epoch),
729 latest_root_epoch: cache.latest_root_epoch,
730 hit_max_limit: false,
731 loaded_missing: false,
732 merged_modified: false,
733 }
734 }
735
736 pub fn get_environments_for_epoch(&self, epoch: Epoch) -> &ProgramRuntimeEnvironments {
738 if epoch != self.latest_root_epoch {
739 if let Some(upcoming_environments) = self.upcoming_environments.as_ref() {
740 return upcoming_environments;
741 }
742 }
743 &self.environments
744 }
745
746 pub fn replenish(
752 &mut self,
753 key: Pubkey,
754 entry: Arc<ProgramCacheEntry>,
755 ) -> (bool, Arc<ProgramCacheEntry>) {
756 (self.entries.insert(key, entry.clone()).is_some(), entry)
757 }
758
759 pub fn store_modified_entry(&mut self, key: Pubkey, entry: Arc<ProgramCacheEntry>) {
762 self.modified_entries.insert(key, entry);
763 }
764
765 pub fn drain_modified_entries(&mut self) -> HashMap<Pubkey, Arc<ProgramCacheEntry>> {
768 std::mem::take(&mut self.modified_entries)
769 }
770
771 pub fn find(&self, key: &Pubkey) -> Option<Arc<ProgramCacheEntry>> {
772 self.modified_entries
776 .get(key)
777 .or_else(|| self.entries.get(key))
778 .map(|entry| {
779 if entry.is_implicit_delay_visibility_tombstone(self.slot) {
780 Arc::new(ProgramCacheEntry::new_tombstone_with_usage_counter(
784 entry.deployment_slot,
785 entry.account_owner,
786 ProgramCacheEntryType::DelayVisibility,
787 entry.tx_usage_counter.clone(),
788 ))
789 } else {
790 entry.clone()
791 }
792 })
793 }
794
795 pub fn slot(&self) -> Slot {
796 self.slot
797 }
798
799 pub fn set_slot_for_tests(&mut self, slot: Slot) {
800 self.slot = slot;
801 }
802
803 pub fn merge(&mut self, modified_entries: &HashMap<Pubkey, Arc<ProgramCacheEntry>>) {
804 modified_entries.iter().for_each(|(key, entry)| {
805 self.merged_modified = true;
806 self.replenish(*key, entry.clone());
807 })
808 }
809
810 pub fn is_empty(&self) -> bool {
811 self.entries.is_empty()
812 }
813}
814
815pub enum ProgramCacheMatchCriteria {
816 DeployedOnOrAfterSlot(Slot),
817 Tombstone,
818 NoCriteria,
819}
820
821impl<FG: ForkGraph> ProgramCache<FG> {
822 pub fn new(root_slot: Slot, root_epoch: Epoch) -> Self {
823 Self {
824 index: IndexImplementation::V1 {
825 entries: HashMap::new(),
826 loading_entries: Mutex::new(HashMap::new()),
827 },
828 latest_root_slot: root_slot,
829 latest_root_epoch: root_epoch,
830 environments: ProgramRuntimeEnvironments::default(),
831 upcoming_environments: None,
832 programs_to_recompile: Vec::default(),
833 stats: ProgramCacheStats::default(),
834 fork_graph: None,
835 loading_task_waiter: Arc::new(LoadingTaskWaiter::default()),
836 }
837 }
838
839 pub fn set_fork_graph(&mut self, fork_graph: Weak<RwLock<FG>>) {
840 self.fork_graph = Some(fork_graph);
841 }
842
843 pub fn get_environments_for_epoch(&self, epoch: Epoch) -> ProgramRuntimeEnvironments {
845 if epoch != self.latest_root_epoch {
846 if let Some(upcoming_environments) = self.upcoming_environments.as_ref() {
847 return upcoming_environments.clone();
848 }
849 }
850 self.environments.clone()
851 }
852
853 pub fn get_upcoming_environments_for_epoch(
855 &self,
856 epoch: Epoch,
857 ) -> Option<ProgramRuntimeEnvironments> {
858 if epoch == self.latest_root_epoch {
859 return self.upcoming_environments.clone();
860 }
861 None
862 }
863
864 pub fn assign_program(&mut self, key: Pubkey, entry: Arc<ProgramCacheEntry>) -> bool {
867 debug_assert!(!matches!(
868 &entry.program,
869 ProgramCacheEntryType::DelayVisibility
870 ));
871 fn is_current_env(
875 environments: &ProgramRuntimeEnvironments,
876 env_opt: Option<&ProgramRuntimeEnvironment>,
877 ) -> bool {
878 env_opt
879 .map(|env| {
880 Arc::ptr_eq(env, &environments.program_runtime_v1)
881 || Arc::ptr_eq(env, &environments.program_runtime_v2)
882 })
883 .unwrap_or(true)
884 }
885 match &mut self.index {
886 IndexImplementation::V1 { entries, .. } => {
887 let slot_versions = &mut entries.entry(key).or_default();
888 match slot_versions.binary_search_by(|at| {
889 at.effective_slot
890 .cmp(&entry.effective_slot)
891 .then(at.deployment_slot.cmp(&entry.deployment_slot))
892 .then(
893 is_current_env(&self.environments, at.program.get_environment()).cmp(
897 &is_current_env(
898 &self.environments,
899 entry.program.get_environment(),
900 ),
901 ),
902 )
903 }) {
904 Ok(index) => {
905 let existing = slot_versions.get_mut(index).unwrap();
906 match (&existing.program, &entry.program) {
907 (
908 ProgramCacheEntryType::Builtin(_),
909 ProgramCacheEntryType::Builtin(_),
910 )
911 | (
912 ProgramCacheEntryType::Unloaded(_),
913 ProgramCacheEntryType::Loaded(_),
914 ) => {}
915 _ => {
916 error!(
918 "ProgramCache::assign_program() failed key={key:?} \
919 existing={slot_versions:?} entry={entry:?}"
920 );
921 debug_assert!(false, "Unexpected replacement of an entry");
922 self.stats.replacements.fetch_add(1, Ordering::Relaxed);
923 return true;
924 }
925 }
926 entry.tx_usage_counter.fetch_add(
928 existing.tx_usage_counter.load(Ordering::Relaxed),
929 Ordering::Relaxed,
930 );
931 *existing = Arc::clone(&entry);
932 self.stats.reloads.fetch_add(1, Ordering::Relaxed);
933 }
934 Err(index) => {
935 self.stats.insertions.fetch_add(1, Ordering::Relaxed);
936 slot_versions.insert(index, Arc::clone(&entry));
937 }
938 }
939 }
940 }
941 false
942 }
943
944 pub fn prune_by_deployment_slot(&mut self, slot: Slot) {
945 match &mut self.index {
946 IndexImplementation::V1 { entries, .. } => {
947 for second_level in entries.values_mut() {
948 second_level.retain(|entry| entry.deployment_slot != slot);
949 }
950 self.remove_programs_with_no_entries();
951 }
952 }
953 }
954
955 pub fn prune(&mut self, new_root_slot: Slot, new_root_epoch: Epoch) {
957 let Some(fork_graph) = self.fork_graph.clone() else {
958 error!("Program cache doesn't have fork graph.");
959 return;
960 };
961 let fork_graph = fork_graph.upgrade().unwrap();
962 let Ok(fork_graph) = fork_graph.read() else {
963 error!("Failed to lock fork graph for reading.");
964 return;
965 };
966 let mut preparation_phase_ends = false;
967 if self.latest_root_epoch != new_root_epoch {
968 self.latest_root_epoch = new_root_epoch;
969 if let Some(upcoming_environments) = self.upcoming_environments.take() {
970 preparation_phase_ends = true;
971 self.environments = upcoming_environments;
972 self.programs_to_recompile.clear();
973 }
974 }
975 match &mut self.index {
976 IndexImplementation::V1 { entries, .. } => {
977 for second_level in entries.values_mut() {
978 let mut first_ancestor_found = false;
980 let mut first_ancestor_env = None;
981 *second_level = second_level
982 .iter()
983 .rev()
984 .filter(|entry| {
985 let relation =
986 fork_graph.relationship(entry.deployment_slot, new_root_slot);
987 if entry.deployment_slot >= new_root_slot {
988 matches!(relation, BlockRelation::Equal | BlockRelation::Descendant)
989 } else if matches!(relation, BlockRelation::Ancestor)
990 || entry.deployment_slot <= self.latest_root_slot
991 {
992 if !first_ancestor_found {
993 first_ancestor_found = true;
994 first_ancestor_env = entry.program.get_environment();
995 return true;
996 }
997 if let Some(entry_env) = entry.program.get_environment() {
1004 if let Some(env) = first_ancestor_env {
1005 if !Arc::ptr_eq(entry_env, env) {
1006 return true;
1007 }
1008 }
1009 }
1010 self.stats.prunes_orphan.fetch_add(1, Ordering::Relaxed);
1011 false
1012 } else {
1013 self.stats.prunes_orphan.fetch_add(1, Ordering::Relaxed);
1014 false
1015 }
1016 })
1017 .filter(|entry| {
1018 if preparation_phase_ends
1020 && !Self::matches_environment(entry, &self.environments)
1021 {
1022 self.stats
1023 .prunes_environment
1024 .fetch_add(1, Ordering::Relaxed);
1025 return false;
1026 }
1027 true
1028 })
1029 .cloned()
1030 .collect();
1031 second_level.reverse();
1032 }
1033 }
1034 }
1035 self.remove_programs_with_no_entries();
1036 debug_assert!(self.latest_root_slot <= new_root_slot);
1037 self.latest_root_slot = new_root_slot;
1038 }
1039
1040 fn matches_environment(
1041 entry: &Arc<ProgramCacheEntry>,
1042 environments: &ProgramRuntimeEnvironments,
1043 ) -> bool {
1044 let Some(environment) = entry.program.get_environment() else {
1045 return true;
1046 };
1047 Arc::ptr_eq(environment, &environments.program_runtime_v1)
1048 || Arc::ptr_eq(environment, &environments.program_runtime_v2)
1049 }
1050
1051 fn matches_criteria(
1052 program: &Arc<ProgramCacheEntry>,
1053 criteria: &ProgramCacheMatchCriteria,
1054 ) -> bool {
1055 match criteria {
1056 ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(slot) => {
1057 program.deployment_slot >= *slot
1058 }
1059 ProgramCacheMatchCriteria::Tombstone => program.is_tombstone(),
1060 ProgramCacheMatchCriteria::NoCriteria => true,
1061 }
1062 }
1063
1064 pub fn extract(
1067 &self,
1068 search_for: &mut Vec<(Pubkey, ProgramCacheMatchCriteria)>,
1069 loaded_programs_for_tx_batch: &mut ProgramCacheForTxBatch,
1070 increment_usage_counter: bool,
1071 count_hits_and_misses: bool,
1072 ) -> Option<Pubkey> {
1073 debug_assert!(self.fork_graph.is_some());
1074 let fork_graph = self.fork_graph.as_ref().unwrap().upgrade().unwrap();
1075 let locked_fork_graph = fork_graph.read().unwrap();
1076 let mut cooperative_loading_task = None;
1077 match &self.index {
1078 IndexImplementation::V1 {
1079 entries,
1080 loading_entries,
1081 } => {
1082 search_for.retain(|(key, match_criteria)| {
1083 if let Some(second_level) = entries.get(key) {
1084 for entry in second_level.iter().rev() {
1085 if entry.deployment_slot <= self.latest_root_slot
1086 || matches!(
1087 locked_fork_graph.relationship(
1088 entry.deployment_slot,
1089 loaded_programs_for_tx_batch.slot
1090 ),
1091 BlockRelation::Equal | BlockRelation::Ancestor
1092 )
1093 {
1094 let entry_to_return = if loaded_programs_for_tx_batch.slot
1095 >= entry.effective_slot
1096 && Self::matches_environment(
1097 entry,
1098 &loaded_programs_for_tx_batch.environments,
1099 ) {
1100 if !Self::matches_criteria(entry, match_criteria) {
1101 break;
1102 }
1103 if let ProgramCacheEntryType::Unloaded(_environment) =
1104 &entry.program
1105 {
1106 break;
1107 }
1108 entry.clone()
1109 } else if entry.is_implicit_delay_visibility_tombstone(
1110 loaded_programs_for_tx_batch.slot,
1111 ) {
1112 Arc::new(ProgramCacheEntry::new_tombstone_with_usage_counter(
1116 entry.deployment_slot,
1117 entry.account_owner,
1118 ProgramCacheEntryType::DelayVisibility,
1119 entry.tx_usage_counter.clone(),
1120 ))
1121 } else {
1122 continue;
1123 };
1124 entry_to_return
1125 .update_access_slot(loaded_programs_for_tx_batch.slot);
1126 if increment_usage_counter {
1127 entry_to_return
1128 .tx_usage_counter
1129 .fetch_add(1, Ordering::Relaxed);
1130 }
1131 loaded_programs_for_tx_batch
1132 .entries
1133 .insert(*key, entry_to_return);
1134 return false;
1135 }
1136 }
1137 }
1138 if cooperative_loading_task.is_none() {
1139 let mut loading_entries = loading_entries.lock().unwrap();
1140 let entry = loading_entries.entry(*key);
1141 if let Entry::Vacant(entry) = entry {
1142 entry.insert((
1143 loaded_programs_for_tx_batch.slot,
1144 thread::current().id(),
1145 ));
1146 cooperative_loading_task = Some(*key);
1147 }
1148 }
1149 true
1150 });
1151 }
1152 }
1153 drop(locked_fork_graph);
1154 if count_hits_and_misses {
1155 self.stats
1156 .misses
1157 .fetch_add(search_for.len() as u64, Ordering::Relaxed);
1158 self.stats.hits.fetch_add(
1159 loaded_programs_for_tx_batch.entries.len() as u64,
1160 Ordering::Relaxed,
1161 );
1162 }
1163 cooperative_loading_task
1164 }
1165
1166 pub fn finish_cooperative_loading_task(
1168 &mut self,
1169 slot: Slot,
1170 key: Pubkey,
1171 loaded_program: Arc<ProgramCacheEntry>,
1172 ) -> bool {
1173 match &mut self.index {
1174 IndexImplementation::V1 {
1175 loading_entries, ..
1176 } => {
1177 let loading_thread = loading_entries.get_mut().unwrap().remove(&key);
1178 debug_assert_eq!(loading_thread, Some((slot, thread::current().id())));
1179 if loaded_program.deployment_slot > self.latest_root_slot
1181 && !matches!(
1182 self.fork_graph
1183 .as_ref()
1184 .unwrap()
1185 .upgrade()
1186 .unwrap()
1187 .read()
1188 .unwrap()
1189 .relationship(loaded_program.deployment_slot, slot),
1190 BlockRelation::Equal | BlockRelation::Ancestor
1191 )
1192 {
1193 self.stats.lost_insertions.fetch_add(1, Ordering::Relaxed);
1194 }
1195 let was_occupied = self.assign_program(key, loaded_program);
1196 self.loading_task_waiter.notify();
1197 was_occupied
1198 }
1199 }
1200 }
1201
1202 pub fn merge(&mut self, modified_entries: &HashMap<Pubkey, Arc<ProgramCacheEntry>>) {
1203 modified_entries.iter().for_each(|(key, entry)| {
1204 self.assign_program(*key, entry.clone());
1205 })
1206 }
1207
1208 pub fn get_flattened_entries(
1210 &self,
1211 include_program_runtime_v1: bool,
1212 _include_program_runtime_v2: bool,
1213 ) -> Vec<(Pubkey, Arc<ProgramCacheEntry>)> {
1214 match &self.index {
1215 IndexImplementation::V1 { entries, .. } => entries
1216 .iter()
1217 .flat_map(|(id, second_level)| {
1218 second_level
1219 .iter()
1220 .filter_map(move |program| match program.program {
1221 ProgramCacheEntryType::Loaded(_) => {
1222 if include_program_runtime_v1 {
1223 Some((*id, program.clone()))
1224 } else {
1225 None
1226 }
1227 }
1228 _ => None,
1229 })
1230 })
1231 .collect(),
1232 }
1233 }
1234
1235 pub fn get_flattened_entries_for_tests(&self) -> Vec<(Pubkey, Arc<ProgramCacheEntry>)> {
1237 match &self.index {
1238 IndexImplementation::V1 { entries, .. } => entries
1239 .iter()
1240 .flat_map(|(id, second_level)| {
1241 second_level.iter().map(|program| (*id, program.clone()))
1242 })
1243 .collect(),
1244 }
1245 }
1246
1247 pub fn get_slot_versions_for_tests(&self, key: &Pubkey) -> &[Arc<ProgramCacheEntry>] {
1249 match &self.index {
1250 IndexImplementation::V1 { entries, .. } => entries
1251 .get(key)
1252 .map(|second_level| second_level.as_ref())
1253 .unwrap_or(&[]),
1254 }
1255 }
1256
1257 pub fn sort_and_unload(&mut self, shrink_to: PercentageInteger) {
1259 let mut sorted_candidates = self.get_flattened_entries(true, true);
1260 sorted_candidates
1261 .sort_by_cached_key(|(_id, program)| program.tx_usage_counter.load(Ordering::Relaxed));
1262 let num_to_unload = sorted_candidates
1263 .len()
1264 .saturating_sub(shrink_to.apply_to(MAX_LOADED_ENTRY_COUNT));
1265 self.unload_program_entries(sorted_candidates.iter().take(num_to_unload));
1266 }
1267
1268 pub fn evict_using_2s_random_selection(&mut self, shrink_to: PercentageInteger, now: Slot) {
1271 let mut candidates = self.get_flattened_entries(true, true);
1272 self.stats
1273 .water_level
1274 .store(candidates.len() as u64, Ordering::Relaxed);
1275 let num_to_unload = candidates
1276 .len()
1277 .saturating_sub(shrink_to.apply_to(MAX_LOADED_ENTRY_COUNT));
1278 fn random_index_and_usage_counter(
1279 candidates: &[(Pubkey, Arc<ProgramCacheEntry>)],
1280 now: Slot,
1281 ) -> (usize, u64) {
1282 let mut rng = thread_rng();
1283 let index = rng.gen_range(0..candidates.len());
1284 let usage_counter = candidates
1285 .get(index)
1286 .expect("Failed to get cached entry")
1287 .1
1288 .decayed_usage_counter(now);
1289 (index, usage_counter)
1290 }
1291
1292 for _ in 0..num_to_unload {
1293 let (index1, usage_counter1) = random_index_and_usage_counter(&candidates, now);
1294 let (index2, usage_counter2) = random_index_and_usage_counter(&candidates, now);
1295
1296 let (program, entry) = if usage_counter1 < usage_counter2 {
1297 candidates.swap_remove(index1)
1298 } else {
1299 candidates.swap_remove(index2)
1300 };
1301 self.unload_program_entry(&program, &entry);
1302 }
1303 }
1304
1305 pub fn remove_programs(&mut self, keys: impl Iterator<Item = Pubkey>) {
1307 match &mut self.index {
1308 IndexImplementation::V1 { entries, .. } => {
1309 for k in keys {
1310 entries.remove(&k);
1311 }
1312 }
1313 }
1314 }
1315
1316 fn unload_program_entry(&mut self, program: &Pubkey, remove_entry: &Arc<ProgramCacheEntry>) {
1319 match &mut self.index {
1320 IndexImplementation::V1 { entries, .. } => {
1321 let second_level = entries.get_mut(program).expect("Cache lookup failed");
1322 let candidate = second_level
1323 .iter_mut()
1324 .find(|entry| entry == &remove_entry)
1325 .expect("Program entry not found");
1326
1327 if let Some(unloaded) = candidate.to_unloaded() {
1331 if candidate.tx_usage_counter.load(Ordering::Relaxed) == 1 {
1332 self.stats.one_hit_wonders.fetch_add(1, Ordering::Relaxed);
1333 }
1334 self.stats
1335 .evictions
1336 .entry(*program)
1337 .and_modify(|c| *c = c.saturating_add(1))
1338 .or_insert(1);
1339 *candidate = Arc::new(unloaded);
1340 }
1341 }
1342 }
1343 }
1344
1345 fn unload_program_entries<'a>(
1346 &mut self,
1347 remove: impl Iterator<Item = &'a (Pubkey, Arc<ProgramCacheEntry>)>,
1348 ) {
1349 for (program, entry) in remove {
1350 self.unload_program_entry(program, entry);
1351 }
1352 }
1353
1354 fn remove_programs_with_no_entries(&mut self) {
1355 match &mut self.index {
1356 IndexImplementation::V1 { entries, .. } => {
1357 let num_programs_before_removal = entries.len();
1358 entries.retain(|_key, second_level| !second_level.is_empty());
1359 if entries.len() < num_programs_before_removal {
1360 self.stats.empty_entries.fetch_add(
1361 num_programs_before_removal.saturating_sub(entries.len()) as u64,
1362 Ordering::Relaxed,
1363 );
1364 }
1365 }
1366 }
1367 }
1368}
1369
1370#[cfg(feature = "frozen-abi")]
1371impl atlas_frozen_abi::abi_example::AbiExample for ProgramCacheEntry {
1372 fn example() -> Self {
1373 Self::default()
1375 }
1376}
1377
1378#[cfg(feature = "frozen-abi")]
1379impl<FG: ForkGraph> atlas_frozen_abi::abi_example::AbiExample for ProgramCache<FG> {
1380 fn example() -> Self {
1381 Self::new(Slot::default(), Epoch::default())
1383 }
1384}
1385
1386#[cfg(test)]
1387mod tests {
1388 use {
1389 crate::loaded_programs::{
1390 BlockRelation, ForkGraph, ProgramCache, ProgramCacheEntry, ProgramCacheEntryOwner,
1391 ProgramCacheEntryType, ProgramCacheForTxBatch, ProgramCacheMatchCriteria,
1392 ProgramRuntimeEnvironment, ProgramRuntimeEnvironments, DELAY_VISIBILITY_SLOT_OFFSET,
1393 },
1394 assert_matches::assert_matches,
1395 percentage::Percentage,
1396 atlas_clock::Slot,
1397 atlas_pubkey::Pubkey,
1398 atlas_sbpf::{elf::Executable, program::BuiltinProgram},
1399 std::{
1400 fs::File,
1401 io::Read,
1402 ops::ControlFlow,
1403 sync::{
1404 atomic::{AtomicU64, Ordering},
1405 Arc, RwLock,
1406 },
1407 },
1408 test_case::{test_case, test_matrix},
1409 };
1410
1411 static MOCK_ENVIRONMENT: std::sync::OnceLock<ProgramRuntimeEnvironment> =
1412 std::sync::OnceLock::<ProgramRuntimeEnvironment>::new();
1413
1414 fn get_mock_env() -> ProgramRuntimeEnvironment {
1415 MOCK_ENVIRONMENT
1416 .get_or_init(|| Arc::new(BuiltinProgram::new_mock()))
1417 .clone()
1418 }
1419
1420 fn new_mock_cache<FG: ForkGraph>() -> ProgramCache<FG> {
1421 let mut cache = ProgramCache::new(0, 0);
1422 cache.environments.program_runtime_v1 = get_mock_env();
1423 cache
1424 }
1425
1426 fn new_test_entry(deployment_slot: Slot, effective_slot: Slot) -> Arc<ProgramCacheEntry> {
1427 new_test_entry_with_usage(deployment_slot, effective_slot, AtomicU64::default())
1428 }
1429
1430 fn new_loaded_entry(env: ProgramRuntimeEnvironment) -> ProgramCacheEntryType {
1431 let mut elf = Vec::new();
1432 File::open("../programs/bpf_loader/test_elfs/out/noop_aligned.so")
1433 .unwrap()
1434 .read_to_end(&mut elf)
1435 .unwrap();
1436 let executable = Executable::load(&elf, env).unwrap();
1437 ProgramCacheEntryType::Loaded(executable)
1438 }
1439
1440 fn new_test_entry_with_usage(
1441 deployment_slot: Slot,
1442 effective_slot: Slot,
1443 usage_counter: AtomicU64,
1444 ) -> Arc<ProgramCacheEntry> {
1445 Arc::new(ProgramCacheEntry {
1446 program: new_loaded_entry(get_mock_env()),
1447 account_owner: ProgramCacheEntryOwner::LoaderV2,
1448 account_size: 0,
1449 deployment_slot,
1450 effective_slot,
1451 tx_usage_counter: Arc::new(usage_counter),
1452 latest_access_slot: AtomicU64::new(deployment_slot),
1453 })
1454 }
1455
1456 fn new_test_builtin_entry(
1457 deployment_slot: Slot,
1458 effective_slot: Slot,
1459 ) -> Arc<ProgramCacheEntry> {
1460 Arc::new(ProgramCacheEntry {
1461 program: ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1462 account_owner: ProgramCacheEntryOwner::NativeLoader,
1463 account_size: 0,
1464 deployment_slot,
1465 effective_slot,
1466 tx_usage_counter: Arc::default(),
1467 latest_access_slot: AtomicU64::default(),
1468 })
1469 }
1470
1471 fn set_tombstone<FG: ForkGraph>(
1472 cache: &mut ProgramCache<FG>,
1473 key: Pubkey,
1474 slot: Slot,
1475 reason: ProgramCacheEntryType,
1476 ) -> Arc<ProgramCacheEntry> {
1477 let program = Arc::new(ProgramCacheEntry::new_tombstone(
1478 slot,
1479 ProgramCacheEntryOwner::LoaderV2,
1480 reason,
1481 ));
1482 cache.assign_program(key, program.clone());
1483 program
1484 }
1485
1486 fn insert_unloaded_entry<FG: ForkGraph>(
1487 cache: &mut ProgramCache<FG>,
1488 key: Pubkey,
1489 slot: Slot,
1490 ) -> Arc<ProgramCacheEntry> {
1491 let loaded = new_test_entry_with_usage(slot, slot.saturating_add(1), AtomicU64::default());
1492 let unloaded = Arc::new(loaded.to_unloaded().expect("Failed to unload the program"));
1493 cache.assign_program(key, unloaded.clone());
1494 unloaded
1495 }
1496
1497 fn num_matching_entries<P, FG>(cache: &ProgramCache<FG>, predicate: P) -> usize
1498 where
1499 P: Fn(&ProgramCacheEntryType) -> bool,
1500 FG: ForkGraph,
1501 {
1502 cache
1503 .get_flattened_entries_for_tests()
1504 .iter()
1505 .filter(|(_key, program)| predicate(&program.program))
1506 .count()
1507 }
1508
1509 #[test]
1510 fn test_usage_counter_decay() {
1511 let _cache = new_mock_cache::<TestForkGraph>();
1512 let program = new_test_entry_with_usage(10, 11, AtomicU64::new(32));
1513 program.update_access_slot(15);
1514 assert_eq!(program.decayed_usage_counter(15), 32);
1515 assert_eq!(program.decayed_usage_counter(16), 16);
1516 assert_eq!(program.decayed_usage_counter(17), 8);
1517 assert_eq!(program.decayed_usage_counter(18), 4);
1518 assert_eq!(program.decayed_usage_counter(19), 2);
1519 assert_eq!(program.decayed_usage_counter(20), 1);
1520 assert_eq!(program.decayed_usage_counter(21), 0);
1521 assert_eq!(program.decayed_usage_counter(15), 32);
1522 assert_eq!(program.decayed_usage_counter(14), 32);
1523
1524 program.update_access_slot(18);
1525 assert_eq!(program.decayed_usage_counter(15), 32);
1526 assert_eq!(program.decayed_usage_counter(16), 32);
1527 assert_eq!(program.decayed_usage_counter(17), 32);
1528 assert_eq!(program.decayed_usage_counter(18), 32);
1529 assert_eq!(program.decayed_usage_counter(19), 16);
1530 assert_eq!(program.decayed_usage_counter(20), 8);
1531 assert_eq!(program.decayed_usage_counter(21), 4);
1532
1533 assert_eq!(program.decayed_usage_counter(18 + 63), 0);
1535 assert_eq!(program.decayed_usage_counter(100), 0);
1536 }
1537
1538 fn program_deploy_test_helper(
1539 cache: &mut ProgramCache<TestForkGraph>,
1540 program: Pubkey,
1541 deployment_slots: Vec<Slot>,
1542 usage_counters: Vec<u64>,
1543 programs: &mut Vec<(Pubkey, Slot, u64)>,
1544 ) {
1545 deployment_slots
1547 .iter()
1548 .enumerate()
1549 .for_each(|(i, deployment_slot)| {
1550 let usage_counter = *usage_counters.get(i).unwrap_or(&0);
1551 cache.assign_program(
1552 program,
1553 new_test_entry_with_usage(
1554 *deployment_slot,
1555 (*deployment_slot).saturating_add(2),
1556 AtomicU64::new(usage_counter),
1557 ),
1558 );
1559 programs.push((program, *deployment_slot, usage_counter));
1560 });
1561
1562 let env = Arc::new(BuiltinProgram::new_mock());
1564 for slot in 21..31 {
1565 set_tombstone(
1566 cache,
1567 program,
1568 slot,
1569 ProgramCacheEntryType::FailedVerification(env.clone()),
1570 );
1571 }
1572
1573 for slot in 31..41 {
1575 insert_unloaded_entry(cache, program, slot);
1576 }
1577 }
1578
1579 #[test]
1580 fn test_random_eviction() {
1581 let mut programs = vec![];
1582
1583 let mut cache = new_mock_cache::<TestForkGraph>();
1584
1585 program_deploy_test_helper(
1591 &mut cache,
1592 Pubkey::new_unique(),
1593 vec![0, 10, 20],
1594 vec![4, 5, 25],
1595 &mut programs,
1596 );
1597
1598 program_deploy_test_helper(
1600 &mut cache,
1601 Pubkey::new_unique(),
1602 vec![5, 11],
1603 vec![0, 2],
1604 &mut programs,
1605 );
1606
1607 program_deploy_test_helper(
1609 &mut cache,
1610 Pubkey::new_unique(),
1611 vec![0, 5, 15],
1612 vec![100, 3, 20],
1613 &mut programs,
1614 );
1615
1616 let num_loaded_expected = 8;
1618 let num_unloaded_expected = 30;
1620 let num_tombstones_expected = 30;
1622
1623 programs.sort_by_key(|(_id, _slot, usage_count)| *usage_count);
1625 let num_loaded = num_matching_entries(&cache, |program_type| {
1626 matches!(program_type, ProgramCacheEntryType::Loaded(_))
1627 });
1628 let num_unloaded = num_matching_entries(&cache, |program_type| {
1629 matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1630 });
1631 let num_tombstones = num_matching_entries(&cache, |program_type| {
1632 matches!(
1633 program_type,
1634 ProgramCacheEntryType::DelayVisibility
1635 | ProgramCacheEntryType::FailedVerification(_)
1636 | ProgramCacheEntryType::Closed
1637 )
1638 });
1639
1640 assert_eq!(num_loaded, num_loaded_expected);
1642 assert_eq!(num_unloaded, num_unloaded_expected);
1643 assert_eq!(num_tombstones, num_tombstones_expected);
1644
1645 let eviction_pct = 1;
1647
1648 let num_loaded_expected =
1649 Percentage::from(eviction_pct).apply_to(crate::loaded_programs::MAX_LOADED_ENTRY_COUNT);
1650 let num_unloaded_expected = num_unloaded_expected + num_loaded - num_loaded_expected;
1651 cache.evict_using_2s_random_selection(Percentage::from(eviction_pct), 21);
1652
1653 let num_loaded = num_matching_entries(&cache, |program_type| {
1655 matches!(program_type, ProgramCacheEntryType::Loaded(_))
1656 });
1657 let num_unloaded = num_matching_entries(&cache, |program_type| {
1658 matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1659 });
1660 let num_tombstones = num_matching_entries(&cache, |program_type| {
1661 matches!(program_type, ProgramCacheEntryType::FailedVerification(_))
1662 });
1663
1664 assert_eq!(num_loaded, num_loaded_expected);
1666 assert_eq!(num_unloaded, num_unloaded_expected);
1668 assert_eq!(num_tombstones, num_tombstones_expected);
1670 }
1671
1672 #[test]
1673 fn test_eviction() {
1674 let mut programs = vec![];
1675 let mut cache = new_mock_cache::<TestForkGraph>();
1676
1677 program_deploy_test_helper(
1679 &mut cache,
1680 Pubkey::new_unique(),
1681 vec![0, 10, 20],
1682 vec![4, 5, 25],
1683 &mut programs,
1684 );
1685
1686 program_deploy_test_helper(
1688 &mut cache,
1689 Pubkey::new_unique(),
1690 vec![5, 11],
1691 vec![0, 2],
1692 &mut programs,
1693 );
1694
1695 program_deploy_test_helper(
1697 &mut cache,
1698 Pubkey::new_unique(),
1699 vec![0, 5, 15],
1700 vec![100, 3, 20],
1701 &mut programs,
1702 );
1703
1704 let num_loaded_expected = 8;
1706 let num_unloaded_expected = 30;
1708 let num_tombstones_expected = 30;
1710
1711 programs.sort_by_key(|(_id, _slot, usage_count)| *usage_count);
1713 let num_loaded = num_matching_entries(&cache, |program_type| {
1714 matches!(program_type, ProgramCacheEntryType::Loaded(_))
1715 });
1716 let num_unloaded = num_matching_entries(&cache, |program_type| {
1717 matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1718 });
1719 let num_tombstones = num_matching_entries(&cache, |program_type| {
1720 matches!(program_type, ProgramCacheEntryType::FailedVerification(_))
1721 });
1722
1723 assert_eq!(num_loaded, num_loaded_expected);
1725 assert_eq!(num_unloaded, num_unloaded_expected);
1726 assert_eq!(num_tombstones, num_tombstones_expected);
1727
1728 let eviction_pct = 1;
1730
1731 let num_loaded_expected =
1732 Percentage::from(eviction_pct).apply_to(crate::loaded_programs::MAX_LOADED_ENTRY_COUNT);
1733 let num_unloaded_expected = num_unloaded_expected + num_loaded - num_loaded_expected;
1734
1735 cache.sort_and_unload(Percentage::from(eviction_pct));
1736
1737 let entries = cache.get_flattened_entries_for_tests();
1739 programs.iter().for_each(|entry| {
1740 assert!(entries.iter().any(|(key, _entry)| key == &entry.0));
1741 });
1742
1743 let unloaded = entries
1744 .iter()
1745 .filter_map(|(key, program)| {
1746 matches!(program.program, ProgramCacheEntryType::Unloaded(_))
1747 .then_some((*key, program.tx_usage_counter.load(Ordering::Relaxed)))
1748 })
1749 .collect::<Vec<(Pubkey, u64)>>();
1750
1751 for index in 0..3 {
1752 let expected = programs.get(index).expect("Missing program");
1753 assert!(unloaded.contains(&(expected.0, expected.2)));
1754 }
1755
1756 let num_loaded = num_matching_entries(&cache, |program_type| {
1758 matches!(program_type, ProgramCacheEntryType::Loaded(_))
1759 });
1760 let num_unloaded = num_matching_entries(&cache, |program_type| {
1761 matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1762 });
1763 let num_tombstones = num_matching_entries(&cache, |program_type| {
1764 matches!(
1765 program_type,
1766 ProgramCacheEntryType::DelayVisibility
1767 | ProgramCacheEntryType::FailedVerification(_)
1768 | ProgramCacheEntryType::Closed
1769 )
1770 });
1771
1772 assert_eq!(num_loaded, num_loaded_expected);
1774 assert_eq!(num_unloaded, num_unloaded_expected);
1776 assert_eq!(num_tombstones, num_tombstones_expected);
1778 }
1779
1780 #[test]
1781 fn test_usage_count_of_unloaded_program() {
1782 let mut cache = new_mock_cache::<TestForkGraph>();
1783
1784 let program = Pubkey::new_unique();
1785 let evict_to_pct = 2;
1786 let cache_capacity_after_shrink =
1787 Percentage::from(evict_to_pct).apply_to(crate::loaded_programs::MAX_LOADED_ENTRY_COUNT);
1788 let num_total_programs = (cache_capacity_after_shrink + 1) as u64;
1790 (0..num_total_programs).for_each(|i| {
1791 cache.assign_program(
1792 program,
1793 new_test_entry_with_usage(i, i + 2, AtomicU64::new(i + 10)),
1794 );
1795 });
1796
1797 cache.sort_and_unload(Percentage::from(evict_to_pct));
1798
1799 let num_unloaded = num_matching_entries(&cache, |program_type| {
1800 matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1801 });
1802 assert_eq!(num_unloaded, 1);
1803
1804 cache
1805 .get_flattened_entries_for_tests()
1806 .iter()
1807 .for_each(|(_key, program)| {
1808 if matches!(program.program, ProgramCacheEntryType::Unloaded(_)) {
1809 assert_eq!(program.tx_usage_counter.load(Ordering::Relaxed), 10);
1811 assert_eq!(program.deployment_slot, 0);
1812 assert_eq!(program.effective_slot, 2);
1813 }
1814 });
1815
1816 cache.assign_program(program, new_test_entry_with_usage(0, 2, AtomicU64::new(0)));
1819
1820 cache
1821 .get_flattened_entries_for_tests()
1822 .iter()
1823 .for_each(|(_key, program)| {
1824 if matches!(program.program, ProgramCacheEntryType::Unloaded(_))
1825 && program.deployment_slot == 0
1826 && program.effective_slot == 2
1827 {
1828 assert_eq!(program.tx_usage_counter.load(Ordering::Relaxed), 10);
1830 }
1831 });
1832 }
1833
1834 #[test]
1835 fn test_fuzz_assign_program_order() {
1836 use rand::prelude::SliceRandom;
1837 const EXPECTED_ENTRIES: [(u64, u64); 7] =
1838 [(1, 2), (5, 5), (5, 6), (5, 10), (9, 10), (10, 10), (3, 12)];
1839 let mut rng = rand::thread_rng();
1840 let program_id = Pubkey::new_unique();
1841 for _ in 0..1000 {
1842 let mut entries = EXPECTED_ENTRIES.to_vec();
1843 entries.shuffle(&mut rng);
1844 let mut cache = new_mock_cache::<TestForkGraph>();
1845 for (deployment_slot, effective_slot) in entries {
1846 assert!(!cache
1847 .assign_program(program_id, new_test_entry(deployment_slot, effective_slot)));
1848 }
1849 for ((deployment_slot, effective_slot), entry) in EXPECTED_ENTRIES
1850 .iter()
1851 .zip(cache.get_slot_versions_for_tests(&program_id).iter())
1852 {
1853 assert_eq!(entry.deployment_slot, *deployment_slot);
1854 assert_eq!(entry.effective_slot, *effective_slot);
1855 }
1856 }
1857 }
1858
1859 #[test_matrix(
1860 (
1861 ProgramCacheEntryType::Closed,
1862 ProgramCacheEntryType::FailedVerification(get_mock_env()),
1863 new_loaded_entry(get_mock_env()),
1864 ),
1865 (
1866 ProgramCacheEntryType::FailedVerification(get_mock_env()),
1867 ProgramCacheEntryType::Closed,
1868 ProgramCacheEntryType::Unloaded(get_mock_env()),
1869 new_loaded_entry(get_mock_env()),
1870 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1871 )
1872 )]
1873 #[test_matrix(
1874 (
1875 ProgramCacheEntryType::Unloaded(get_mock_env()),
1876 ),
1877 (
1878 ProgramCacheEntryType::FailedVerification(get_mock_env()),
1879 ProgramCacheEntryType::Closed,
1880 ProgramCacheEntryType::Unloaded(get_mock_env()),
1881 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1882 )
1883 )]
1884 #[test_matrix(
1885 (ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),),
1886 (
1887 ProgramCacheEntryType::FailedVerification(get_mock_env()),
1888 ProgramCacheEntryType::Closed,
1889 ProgramCacheEntryType::Unloaded(get_mock_env()),
1890 new_loaded_entry(get_mock_env()),
1891 )
1892 )]
1893 #[should_panic(expected = "Unexpected replacement of an entry")]
1894 fn test_assign_program_failure(old: ProgramCacheEntryType, new: ProgramCacheEntryType) {
1895 let mut cache = new_mock_cache::<TestForkGraph>();
1896 let program_id = Pubkey::new_unique();
1897 assert!(!cache.assign_program(
1898 program_id,
1899 Arc::new(ProgramCacheEntry {
1900 program: old,
1901 account_owner: ProgramCacheEntryOwner::LoaderV2,
1902 account_size: 0,
1903 deployment_slot: 10,
1904 effective_slot: 11,
1905 tx_usage_counter: Arc::default(),
1906 latest_access_slot: AtomicU64::default(),
1907 }),
1908 ));
1909 cache.assign_program(
1910 program_id,
1911 Arc::new(ProgramCacheEntry {
1912 program: new,
1913 account_owner: ProgramCacheEntryOwner::LoaderV2,
1914 account_size: 0,
1915 deployment_slot: 10,
1916 effective_slot: 11,
1917 tx_usage_counter: Arc::default(),
1918 latest_access_slot: AtomicU64::default(),
1919 }),
1920 );
1921 }
1922
1923 #[test_case(
1924 ProgramCacheEntryType::Unloaded(Arc::new(BuiltinProgram::new_mock())),
1925 new_loaded_entry(get_mock_env())
1926 )]
1927 #[test_case(
1928 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1929 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock())
1930 )]
1931 fn test_assign_program_success(old: ProgramCacheEntryType, new: ProgramCacheEntryType) {
1932 let mut cache = new_mock_cache::<TestForkGraph>();
1933 let program_id = Pubkey::new_unique();
1934 assert!(!cache.assign_program(
1935 program_id,
1936 Arc::new(ProgramCacheEntry {
1937 program: old,
1938 account_owner: ProgramCacheEntryOwner::LoaderV2,
1939 account_size: 0,
1940 deployment_slot: 10,
1941 effective_slot: 11,
1942 tx_usage_counter: Arc::default(),
1943 latest_access_slot: AtomicU64::default(),
1944 }),
1945 ));
1946 assert!(!cache.assign_program(
1947 program_id,
1948 Arc::new(ProgramCacheEntry {
1949 program: new,
1950 account_owner: ProgramCacheEntryOwner::LoaderV2,
1951 account_size: 0,
1952 deployment_slot: 10,
1953 effective_slot: 11,
1954 tx_usage_counter: Arc::default(),
1955 latest_access_slot: AtomicU64::default(),
1956 }),
1957 ));
1958 }
1959
1960 #[test]
1961 fn test_tombstone() {
1962 let env = Arc::new(BuiltinProgram::new_mock());
1963 let tombstone = ProgramCacheEntry::new_tombstone(
1964 0,
1965 ProgramCacheEntryOwner::LoaderV2,
1966 ProgramCacheEntryType::FailedVerification(env.clone()),
1967 );
1968 assert_matches!(
1969 tombstone.program,
1970 ProgramCacheEntryType::FailedVerification(_)
1971 );
1972 assert!(tombstone.is_tombstone());
1973 assert_eq!(tombstone.deployment_slot, 0);
1974 assert_eq!(tombstone.effective_slot, 0);
1975
1976 let tombstone = ProgramCacheEntry::new_tombstone(
1977 100,
1978 ProgramCacheEntryOwner::LoaderV2,
1979 ProgramCacheEntryType::Closed,
1980 );
1981 assert_matches!(tombstone.program, ProgramCacheEntryType::Closed);
1982 assert!(tombstone.is_tombstone());
1983 assert_eq!(tombstone.deployment_slot, 100);
1984 assert_eq!(tombstone.effective_slot, 100);
1985
1986 let mut cache = new_mock_cache::<TestForkGraph>();
1987 let program1 = Pubkey::new_unique();
1988 let tombstone = set_tombstone(
1989 &mut cache,
1990 program1,
1991 10,
1992 ProgramCacheEntryType::FailedVerification(env.clone()),
1993 );
1994 let slot_versions = cache.get_slot_versions_for_tests(&program1);
1995 assert_eq!(slot_versions.len(), 1);
1996 assert!(slot_versions.first().unwrap().is_tombstone());
1997 assert_eq!(tombstone.deployment_slot, 10);
1998 assert_eq!(tombstone.effective_slot, 10);
1999
2000 let program2 = Pubkey::new_unique();
2002 cache.assign_program(program2, new_test_builtin_entry(50, 51));
2003 let slot_versions = cache.get_slot_versions_for_tests(&program2);
2004 assert_eq!(slot_versions.len(), 1);
2005 assert!(!slot_versions.first().unwrap().is_tombstone());
2006
2007 let tombstone = set_tombstone(
2008 &mut cache,
2009 program2,
2010 60,
2011 ProgramCacheEntryType::FailedVerification(env),
2012 );
2013 let slot_versions = cache.get_slot_versions_for_tests(&program2);
2014 assert_eq!(slot_versions.len(), 2);
2015 assert!(!slot_versions.first().unwrap().is_tombstone());
2016 assert!(slot_versions.get(1).unwrap().is_tombstone());
2017 assert!(tombstone.is_tombstone());
2018 assert_eq!(tombstone.deployment_slot, 60);
2019 assert_eq!(tombstone.effective_slot, 60);
2020 }
2021
2022 struct TestForkGraph {
2023 relation: BlockRelation,
2024 }
2025 impl ForkGraph for TestForkGraph {
2026 fn relationship(&self, _a: Slot, _b: Slot) -> BlockRelation {
2027 self.relation
2028 }
2029 }
2030
2031 #[test]
2032 fn test_prune_empty() {
2033 let mut cache = new_mock_cache::<TestForkGraph>();
2034 let fork_graph = Arc::new(RwLock::new(TestForkGraph {
2035 relation: BlockRelation::Unrelated,
2036 }));
2037
2038 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2039
2040 cache.prune(0, 0);
2041 assert!(cache.get_flattened_entries_for_tests().is_empty());
2042
2043 cache.prune(10, 0);
2044 assert!(cache.get_flattened_entries_for_tests().is_empty());
2045
2046 let mut cache = new_mock_cache::<TestForkGraph>();
2047 let fork_graph = Arc::new(RwLock::new(TestForkGraph {
2048 relation: BlockRelation::Ancestor,
2049 }));
2050
2051 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2052
2053 cache.prune(0, 0);
2054 assert!(cache.get_flattened_entries_for_tests().is_empty());
2055
2056 cache.prune(10, 0);
2057 assert!(cache.get_flattened_entries_for_tests().is_empty());
2058
2059 let mut cache = new_mock_cache::<TestForkGraph>();
2060 let fork_graph = Arc::new(RwLock::new(TestForkGraph {
2061 relation: BlockRelation::Descendant,
2062 }));
2063
2064 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2065
2066 cache.prune(0, 0);
2067 assert!(cache.get_flattened_entries_for_tests().is_empty());
2068
2069 cache.prune(10, 0);
2070 assert!(cache.get_flattened_entries_for_tests().is_empty());
2071
2072 let mut cache = new_mock_cache::<TestForkGraph>();
2073 let fork_graph = Arc::new(RwLock::new(TestForkGraph {
2074 relation: BlockRelation::Unknown,
2075 }));
2076 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2077
2078 cache.prune(0, 0);
2079 assert!(cache.get_flattened_entries_for_tests().is_empty());
2080
2081 cache.prune(10, 0);
2082 assert!(cache.get_flattened_entries_for_tests().is_empty());
2083 }
2084
2085 #[test]
2086 fn test_prune_different_env() {
2087 let mut cache = new_mock_cache::<TestForkGraph>();
2088
2089 let fork_graph = Arc::new(RwLock::new(TestForkGraph {
2090 relation: BlockRelation::Ancestor,
2091 }));
2092
2093 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2094
2095 let program1 = Pubkey::new_unique();
2096 cache.assign_program(program1, new_test_entry(10, 10));
2097
2098 let new_env = Arc::new(BuiltinProgram::new_mock());
2099 cache.upcoming_environments = Some(ProgramRuntimeEnvironments {
2100 program_runtime_v1: new_env.clone(),
2101 program_runtime_v2: new_env.clone(),
2102 });
2103 let updated_program = Arc::new(ProgramCacheEntry {
2104 program: new_loaded_entry(new_env.clone()),
2105 account_owner: ProgramCacheEntryOwner::LoaderV2,
2106 account_size: 0,
2107 deployment_slot: 20,
2108 effective_slot: 20,
2109 tx_usage_counter: Arc::default(),
2110 latest_access_slot: AtomicU64::default(),
2111 });
2112 cache.assign_program(program1, updated_program.clone());
2113
2114 assert_eq!(cache.get_slot_versions_for_tests(&program1).len(), 2);
2116
2117 cache.prune(21, cache.latest_root_epoch);
2118
2119 assert_eq!(cache.get_slot_versions_for_tests(&program1).len(), 2);
2121
2122 cache.prune(22, cache.latest_root_epoch.saturating_add(1));
2123
2124 assert_eq!(cache.get_slot_versions_for_tests(&program1).len(), 1);
2126
2127 let entry = cache
2128 .get_slot_versions_for_tests(&program1)
2129 .first()
2130 .expect("Failed to get the program")
2131 .clone();
2132 assert_eq!(entry, updated_program);
2134 }
2135
2136 #[derive(Default)]
2137 struct TestForkGraphSpecific {
2138 forks: Vec<Vec<Slot>>,
2139 }
2140
2141 impl TestForkGraphSpecific {
2142 fn insert_fork(&mut self, fork: &[Slot]) {
2143 let mut fork = fork.to_vec();
2144 fork.sort();
2145 self.forks.push(fork)
2146 }
2147 }
2148
2149 impl ForkGraph for TestForkGraphSpecific {
2150 fn relationship(&self, a: Slot, b: Slot) -> BlockRelation {
2151 match self.forks.iter().try_for_each(|fork| {
2152 let relation = fork
2153 .iter()
2154 .position(|x| *x == a)
2155 .and_then(|a_pos| {
2156 fork.iter().position(|x| *x == b).and_then(|b_pos| {
2157 (a_pos == b_pos)
2158 .then_some(BlockRelation::Equal)
2159 .or_else(|| (a_pos < b_pos).then_some(BlockRelation::Ancestor))
2160 .or(Some(BlockRelation::Descendant))
2161 })
2162 })
2163 .unwrap_or(BlockRelation::Unrelated);
2164
2165 if relation != BlockRelation::Unrelated {
2166 return ControlFlow::Break(relation);
2167 }
2168
2169 ControlFlow::Continue(())
2170 }) {
2171 ControlFlow::Break(relation) => relation,
2172 _ => BlockRelation::Unrelated,
2173 }
2174 }
2175 }
2176
2177 fn get_entries_to_load(
2178 cache: &ProgramCache<TestForkGraphSpecific>,
2179 loading_slot: Slot,
2180 keys: &[Pubkey],
2181 ) -> Vec<(Pubkey, ProgramCacheMatchCriteria)> {
2182 let fork_graph = cache.fork_graph.as_ref().unwrap().upgrade().unwrap();
2183 let locked_fork_graph = fork_graph.read().unwrap();
2184 let entries = cache.get_flattened_entries_for_tests();
2185 keys.iter()
2186 .filter_map(|key| {
2187 entries
2188 .iter()
2189 .rev()
2190 .find(|(program_id, entry)| {
2191 program_id == key
2192 && matches!(
2193 locked_fork_graph.relationship(entry.deployment_slot, loading_slot),
2194 BlockRelation::Equal | BlockRelation::Ancestor,
2195 )
2196 })
2197 .map(|(program_id, _entry)| {
2198 (*program_id, ProgramCacheMatchCriteria::NoCriteria)
2199 })
2200 })
2201 .collect()
2202 }
2203
2204 fn match_slot(
2205 extracted: &ProgramCacheForTxBatch,
2206 program: &Pubkey,
2207 deployment_slot: Slot,
2208 working_slot: Slot,
2209 ) -> bool {
2210 assert_eq!(extracted.slot, working_slot);
2211 extracted
2212 .entries
2213 .get(program)
2214 .map(|entry| entry.deployment_slot == deployment_slot)
2215 .unwrap_or(false)
2216 }
2217
2218 fn match_missing(
2219 missing: &[(Pubkey, ProgramCacheMatchCriteria)],
2220 program: &Pubkey,
2221 expected_result: bool,
2222 ) -> bool {
2223 missing.iter().any(|(key, _)| key == program) == expected_result
2224 }
2225
2226 #[test]
2227 fn test_fork_extract_and_prune() {
2228 let mut cache = new_mock_cache::<TestForkGraphSpecific>();
2229
2230 let mut fork_graph = TestForkGraphSpecific::default();
2246 fork_graph.insert_fork(&[0, 10, 20, 22]);
2247 fork_graph.insert_fork(&[0, 5, 11, 15, 16, 18, 19, 21, 23]);
2248 fork_graph.insert_fork(&[0, 5, 11, 25, 27]);
2249
2250 let fork_graph = Arc::new(RwLock::new(fork_graph));
2251 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2252
2253 let program1 = Pubkey::new_unique();
2254 cache.assign_program(program1, new_test_entry(0, 1));
2255 cache.assign_program(program1, new_test_entry(10, 11));
2256 cache.assign_program(program1, new_test_entry(20, 21));
2257
2258 let program2 = Pubkey::new_unique();
2259 cache.assign_program(program2, new_test_entry(5, 6));
2260 cache.assign_program(
2261 program2,
2262 new_test_entry(11, 11 + DELAY_VISIBILITY_SLOT_OFFSET),
2263 );
2264
2265 let program3 = Pubkey::new_unique();
2266 cache.assign_program(program3, new_test_entry(25, 26));
2267
2268 let program4 = Pubkey::new_unique();
2269 cache.assign_program(program4, new_test_entry(0, 1));
2270 cache.assign_program(program4, new_test_entry(5, 6));
2271 cache.assign_program(
2273 program4,
2274 new_test_entry(15, 15 + DELAY_VISIBILITY_SLOT_OFFSET),
2275 );
2276
2277 let mut missing =
2294 get_entries_to_load(&cache, 22, &[program1, program2, program3, program4]);
2295 assert!(match_missing(&missing, &program2, false));
2296 assert!(match_missing(&missing, &program3, false));
2297 let mut extracted = ProgramCacheForTxBatch::new(22, cache.environments.clone(), None, 0);
2298 cache.extract(&mut missing, &mut extracted, true, true);
2299 assert!(match_slot(&extracted, &program1, 20, 22));
2300 assert!(match_slot(&extracted, &program4, 0, 22));
2301
2302 let mut missing =
2304 get_entries_to_load(&cache, 15, &[program1, program2, program3, program4]);
2305 assert!(match_missing(&missing, &program3, false));
2306 let mut extracted = ProgramCacheForTxBatch::new(15, cache.environments.clone(), None, 0);
2307 cache.extract(&mut missing, &mut extracted, true, true);
2308 assert!(match_slot(&extracted, &program1, 0, 15));
2309 assert!(match_slot(&extracted, &program2, 11, 15));
2310 let tombstone = extracted
2313 .find(&program4)
2314 .expect("Failed to find the tombstone");
2315 assert_matches!(tombstone.program, ProgramCacheEntryType::DelayVisibility);
2316 assert_eq!(tombstone.deployment_slot, 15);
2317
2318 let mut missing =
2320 get_entries_to_load(&cache, 18, &[program1, program2, program3, program4]);
2321 assert!(match_missing(&missing, &program3, false));
2322 let mut extracted = ProgramCacheForTxBatch::new(18, cache.environments.clone(), None, 0);
2323 cache.extract(&mut missing, &mut extracted, true, true);
2324 assert!(match_slot(&extracted, &program1, 0, 18));
2325 assert!(match_slot(&extracted, &program2, 11, 18));
2326 assert!(match_slot(&extracted, &program4, 15, 18));
2328
2329 let mut missing =
2331 get_entries_to_load(&cache, 23, &[program1, program2, program3, program4]);
2332 assert!(match_missing(&missing, &program3, false));
2333 let mut extracted = ProgramCacheForTxBatch::new(23, cache.environments.clone(), None, 0);
2334 cache.extract(&mut missing, &mut extracted, true, true);
2335 assert!(match_slot(&extracted, &program1, 0, 23));
2336 assert!(match_slot(&extracted, &program2, 11, 23));
2337 assert!(match_slot(&extracted, &program4, 15, 23));
2339
2340 let mut missing =
2342 get_entries_to_load(&cache, 11, &[program1, program2, program3, program4]);
2343 assert!(match_missing(&missing, &program3, false));
2344 let mut extracted = ProgramCacheForTxBatch::new(11, cache.environments.clone(), None, 0);
2345 cache.extract(&mut missing, &mut extracted, true, true);
2346 assert!(match_slot(&extracted, &program1, 0, 11));
2347 let tombstone = extracted
2349 .find(&program2)
2350 .expect("Failed to find the tombstone");
2351 assert_matches!(tombstone.program, ProgramCacheEntryType::DelayVisibility);
2352 assert_eq!(tombstone.deployment_slot, 11);
2353 assert!(match_slot(&extracted, &program4, 5, 11));
2354
2355 cache.prune(5, 0);
2356
2357 let mut missing =
2374 get_entries_to_load(&cache, 21, &[program1, program2, program3, program4]);
2375 assert!(match_missing(&missing, &program3, false));
2376 let mut extracted = ProgramCacheForTxBatch::new(21, cache.environments.clone(), None, 0);
2377 cache.extract(&mut missing, &mut extracted, true, true);
2378 assert!(match_slot(&extracted, &program1, 0, 21));
2380 assert!(match_slot(&extracted, &program2, 11, 21));
2381 assert!(match_slot(&extracted, &program4, 15, 21));
2382
2383 let mut missing =
2385 get_entries_to_load(&cache, 27, &[program1, program2, program3, program4]);
2386 let mut extracted = ProgramCacheForTxBatch::new(27, cache.environments.clone(), None, 0);
2387 cache.extract(&mut missing, &mut extracted, true, true);
2388 assert!(match_slot(&extracted, &program1, 0, 27));
2389 assert!(match_slot(&extracted, &program2, 11, 27));
2390 assert!(match_slot(&extracted, &program3, 25, 27));
2391 assert!(match_slot(&extracted, &program4, 5, 27));
2392
2393 cache.prune(15, 0);
2394
2395 let mut missing =
2412 get_entries_to_load(&cache, 23, &[program1, program2, program3, program4]);
2413 assert!(match_missing(&missing, &program3, false));
2414 let mut extracted = ProgramCacheForTxBatch::new(23, cache.environments.clone(), None, 0);
2415 cache.extract(&mut missing, &mut extracted, true, true);
2416 assert!(match_slot(&extracted, &program1, 0, 23));
2417 assert!(match_slot(&extracted, &program2, 11, 23));
2418 assert!(match_slot(&extracted, &program4, 15, 23));
2419 }
2420
2421 #[test]
2422 fn test_extract_using_deployment_slot() {
2423 let mut cache = new_mock_cache::<TestForkGraphSpecific>();
2424
2425 let mut fork_graph = TestForkGraphSpecific::default();
2441 fork_graph.insert_fork(&[0, 10, 20, 22]);
2442 fork_graph.insert_fork(&[0, 5, 11, 12, 15, 16, 18, 19, 21, 23]);
2443 fork_graph.insert_fork(&[0, 5, 11, 25, 27]);
2444
2445 let fork_graph = Arc::new(RwLock::new(fork_graph));
2446 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2447
2448 let program1 = Pubkey::new_unique();
2449 cache.assign_program(program1, new_test_entry(0, 1));
2450 cache.assign_program(program1, new_test_entry(20, 21));
2451
2452 let program2 = Pubkey::new_unique();
2453 cache.assign_program(program2, new_test_entry(5, 6));
2454 cache.assign_program(program2, new_test_entry(11, 12));
2455
2456 let program3 = Pubkey::new_unique();
2457 cache.assign_program(program3, new_test_entry(25, 26));
2458
2459 let mut missing = get_entries_to_load(&cache, 12, &[program1, program2, program3]);
2461 assert!(match_missing(&missing, &program3, false));
2462 let mut extracted = ProgramCacheForTxBatch::new(12, cache.environments.clone(), None, 0);
2463 cache.extract(&mut missing, &mut extracted, true, true);
2464 assert!(match_slot(&extracted, &program1, 0, 12));
2465 assert!(match_slot(&extracted, &program2, 11, 12));
2466
2467 let mut missing = get_entries_to_load(&cache, 12, &[program1, program2, program3]);
2469 missing.get_mut(0).unwrap().1 = ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(5);
2470 missing.get_mut(1).unwrap().1 = ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(5);
2471 assert!(match_missing(&missing, &program3, false));
2472 let mut extracted = ProgramCacheForTxBatch::new(12, cache.environments.clone(), None, 0);
2473 cache.extract(&mut missing, &mut extracted, true, true);
2474 assert!(match_missing(&missing, &program1, true));
2475 assert!(match_slot(&extracted, &program2, 11, 12));
2476 }
2477
2478 #[test]
2479 fn test_extract_unloaded() {
2480 let mut cache = new_mock_cache::<TestForkGraphSpecific>();
2481
2482 let mut fork_graph = TestForkGraphSpecific::default();
2498 fork_graph.insert_fork(&[0, 10, 20, 22]);
2499 fork_graph.insert_fork(&[0, 5, 11, 15, 16, 19, 21, 23]);
2500 fork_graph.insert_fork(&[0, 5, 11, 25, 27]);
2501
2502 let fork_graph = Arc::new(RwLock::new(fork_graph));
2503 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2504
2505 let program1 = Pubkey::new_unique();
2506 cache.assign_program(program1, new_test_entry(0, 1));
2507 cache.assign_program(program1, new_test_entry(20, 21));
2508
2509 let program2 = Pubkey::new_unique();
2510 cache.assign_program(program2, new_test_entry(5, 6));
2511 cache.assign_program(program2, new_test_entry(11, 12));
2512
2513 let program3 = Pubkey::new_unique();
2514 let _ = insert_unloaded_entry(&mut cache, program3, 25);
2516
2517 cache.assign_program(
2521 program3,
2522 Arc::new(
2523 new_test_entry(20, 21)
2524 .to_unloaded()
2525 .expect("Failed to create unloaded program"),
2526 ),
2527 );
2528
2529 let mut missing = get_entries_to_load(&cache, 19, &[program1, program2, program3]);
2531 assert!(match_missing(&missing, &program3, false));
2532 let mut extracted = ProgramCacheForTxBatch::new(19, cache.environments.clone(), None, 0);
2533 cache.extract(&mut missing, &mut extracted, true, true);
2534 assert!(match_slot(&extracted, &program1, 0, 19));
2535 assert!(match_slot(&extracted, &program2, 11, 19));
2536
2537 let mut missing = get_entries_to_load(&cache, 27, &[program1, program2, program3]);
2539 let mut extracted = ProgramCacheForTxBatch::new(27, cache.environments.clone(), None, 0);
2540 cache.extract(&mut missing, &mut extracted, true, true);
2541 assert!(match_slot(&extracted, &program1, 0, 27));
2542 assert!(match_slot(&extracted, &program2, 11, 27));
2543 assert!(match_missing(&missing, &program3, true));
2544
2545 let mut missing = get_entries_to_load(&cache, 22, &[program1, program2, program3]);
2547 assert!(match_missing(&missing, &program2, false));
2548 let mut extracted = ProgramCacheForTxBatch::new(22, cache.environments.clone(), None, 0);
2549 cache.extract(&mut missing, &mut extracted, true, true);
2550 assert!(match_slot(&extracted, &program1, 20, 22));
2551 assert!(match_missing(&missing, &program3, true));
2552 }
2553
2554 #[test]
2555 fn test_extract_nonexistent() {
2556 let mut cache = new_mock_cache::<TestForkGraphSpecific>();
2557 let fork_graph = TestForkGraphSpecific::default();
2558 let fork_graph = Arc::new(RwLock::new(fork_graph));
2559 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2560
2561 let program1 = Pubkey::new_unique();
2562 let mut missing = vec![(program1, ProgramCacheMatchCriteria::NoCriteria)];
2563 let mut extracted = ProgramCacheForTxBatch::new(0, cache.environments.clone(), None, 0);
2564 cache.extract(&mut missing, &mut extracted, true, true);
2565 assert!(match_missing(&missing, &program1, true));
2566 }
2567
2568 #[test]
2569 fn test_unloaded() {
2570 let mut cache = new_mock_cache::<TestForkGraph>();
2571 for program_cache_entry_type in [
2572 ProgramCacheEntryType::FailedVerification(
2573 cache.environments.program_runtime_v1.clone(),
2574 ),
2575 ProgramCacheEntryType::Closed,
2576 ProgramCacheEntryType::Unloaded(cache.environments.program_runtime_v1.clone()),
2577 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
2578 ] {
2579 let entry = Arc::new(ProgramCacheEntry {
2580 program: program_cache_entry_type,
2581 account_owner: ProgramCacheEntryOwner::LoaderV2,
2582 account_size: 0,
2583 deployment_slot: 0,
2584 effective_slot: 0,
2585 tx_usage_counter: Arc::default(),
2586 latest_access_slot: AtomicU64::default(),
2587 });
2588 assert!(entry.to_unloaded().is_none());
2589
2590 let program_id = Pubkey::new_unique();
2592 cache.assign_program(program_id, entry.clone());
2593 cache.unload_program_entry(&program_id, &entry);
2594 assert_eq!(cache.get_slot_versions_for_tests(&program_id).len(), 1);
2595 assert!(cache.stats.evictions.is_empty());
2596 }
2597
2598 let entry = new_test_entry_with_usage(1, 2, AtomicU64::new(3));
2599 let unloaded_entry = entry.to_unloaded().unwrap();
2600 assert_eq!(unloaded_entry.deployment_slot, 1);
2601 assert_eq!(unloaded_entry.effective_slot, 2);
2602 assert_eq!(unloaded_entry.latest_access_slot.load(Ordering::Relaxed), 1);
2603 assert_eq!(unloaded_entry.tx_usage_counter.load(Ordering::Relaxed), 3);
2604
2605 let program_id = Pubkey::new_unique();
2607 cache.assign_program(program_id, entry.clone());
2608 cache.unload_program_entry(&program_id, &entry);
2609 assert!(cache.stats.evictions.contains_key(&program_id));
2610 }
2611
2612 #[test]
2613 fn test_fork_prune_find_first_ancestor() {
2614 let mut cache = new_mock_cache::<TestForkGraphSpecific>();
2615
2616 let mut fork_graph = TestForkGraphSpecific::default();
2627 fork_graph.insert_fork(&[0, 10, 20]);
2628 fork_graph.insert_fork(&[0, 5]);
2629 let fork_graph = Arc::new(RwLock::new(fork_graph));
2630 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2631
2632 let program1 = Pubkey::new_unique();
2633 cache.assign_program(program1, new_test_entry(0, 1));
2634 cache.assign_program(program1, new_test_entry(5, 6));
2635
2636 cache.prune(10, 0);
2637
2638 let mut missing = get_entries_to_load(&cache, 20, &[program1]);
2639 let mut extracted = ProgramCacheForTxBatch::new(20, cache.environments.clone(), None, 0);
2640 cache.extract(&mut missing, &mut extracted, true, true);
2641
2642 assert_eq!(
2644 extracted
2645 .find(&program1)
2646 .expect("Did not find the program")
2647 .deployment_slot,
2648 0
2649 );
2650 }
2651
2652 #[test]
2653 fn test_prune_by_deployment_slot() {
2654 let mut cache = new_mock_cache::<TestForkGraphSpecific>();
2655
2656 let mut fork_graph = TestForkGraphSpecific::default();
2667 fork_graph.insert_fork(&[0, 10, 20]);
2668 fork_graph.insert_fork(&[0, 5, 6]);
2669 let fork_graph = Arc::new(RwLock::new(fork_graph));
2670 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2671
2672 let program1 = Pubkey::new_unique();
2673 cache.assign_program(program1, new_test_entry(0, 1));
2674 cache.assign_program(program1, new_test_entry(5, 6));
2675
2676 let program2 = Pubkey::new_unique();
2677 cache.assign_program(program2, new_test_entry(10, 11));
2678
2679 let mut missing = get_entries_to_load(&cache, 20, &[program1, program2]);
2680 let mut extracted = ProgramCacheForTxBatch::new(20, cache.environments.clone(), None, 0);
2681 cache.extract(&mut missing, &mut extracted, true, true);
2682 assert!(match_slot(&extracted, &program1, 0, 20));
2683 assert!(match_slot(&extracted, &program2, 10, 20));
2684
2685 let mut missing = get_entries_to_load(&cache, 6, &[program1, program2]);
2686 assert!(match_missing(&missing, &program2, false));
2687 let mut extracted = ProgramCacheForTxBatch::new(6, cache.environments.clone(), None, 0);
2688 cache.extract(&mut missing, &mut extracted, true, true);
2689 assert!(match_slot(&extracted, &program1, 5, 6));
2690
2691 cache.prune_by_deployment_slot(5);
2694
2695 let mut missing = get_entries_to_load(&cache, 20, &[program1, program2]);
2696 let mut extracted = ProgramCacheForTxBatch::new(20, cache.environments.clone(), None, 0);
2697 cache.extract(&mut missing, &mut extracted, true, true);
2698 assert!(match_slot(&extracted, &program1, 0, 20));
2699 assert!(match_slot(&extracted, &program2, 10, 20));
2700
2701 let mut missing = get_entries_to_load(&cache, 6, &[program1, program2]);
2702 assert!(match_missing(&missing, &program2, false));
2703 let mut extracted = ProgramCacheForTxBatch::new(6, cache.environments.clone(), None, 0);
2704 cache.extract(&mut missing, &mut extracted, true, true);
2705 assert!(match_slot(&extracted, &program1, 0, 6));
2706
2707 cache.prune_by_deployment_slot(10);
2710
2711 let mut missing = get_entries_to_load(&cache, 20, &[program1, program2]);
2712 assert!(match_missing(&missing, &program2, false));
2713 let mut extracted = ProgramCacheForTxBatch::new(20, cache.environments.clone(), None, 0);
2714 cache.extract(&mut missing, &mut extracted, true, true);
2715 assert!(match_slot(&extracted, &program1, 0, 20));
2716 }
2717
2718 #[test]
2719 fn test_usable_entries_for_slot() {
2720 new_mock_cache::<TestForkGraph>();
2721 let tombstone = Arc::new(ProgramCacheEntry::new_tombstone(
2722 0,
2723 ProgramCacheEntryOwner::LoaderV2,
2724 ProgramCacheEntryType::Closed,
2725 ));
2726
2727 assert!(ProgramCache::<TestForkGraph>::matches_criteria(
2728 &tombstone,
2729 &ProgramCacheMatchCriteria::NoCriteria
2730 ));
2731
2732 assert!(ProgramCache::<TestForkGraph>::matches_criteria(
2733 &tombstone,
2734 &ProgramCacheMatchCriteria::Tombstone
2735 ));
2736
2737 assert!(ProgramCache::<TestForkGraph>::matches_criteria(
2738 &tombstone,
2739 &ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(0)
2740 ));
2741
2742 assert!(!ProgramCache::<TestForkGraph>::matches_criteria(
2743 &tombstone,
2744 &ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(1)
2745 ));
2746
2747 let program = new_test_entry(0, 1);
2748
2749 assert!(ProgramCache::<TestForkGraph>::matches_criteria(
2750 &program,
2751 &ProgramCacheMatchCriteria::NoCriteria
2752 ));
2753
2754 assert!(!ProgramCache::<TestForkGraph>::matches_criteria(
2755 &program,
2756 &ProgramCacheMatchCriteria::Tombstone
2757 ));
2758
2759 assert!(ProgramCache::<TestForkGraph>::matches_criteria(
2760 &program,
2761 &ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(0)
2762 ));
2763
2764 assert!(!ProgramCache::<TestForkGraph>::matches_criteria(
2765 &program,
2766 &ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(1)
2767 ));
2768
2769 let program = Arc::new(new_test_entry_with_usage(0, 1, AtomicU64::default()));
2770
2771 assert!(ProgramCache::<TestForkGraph>::matches_criteria(
2772 &program,
2773 &ProgramCacheMatchCriteria::NoCriteria
2774 ));
2775
2776 assert!(!ProgramCache::<TestForkGraph>::matches_criteria(
2777 &program,
2778 &ProgramCacheMatchCriteria::Tombstone
2779 ));
2780
2781 assert!(ProgramCache::<TestForkGraph>::matches_criteria(
2782 &program,
2783 &ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(0)
2784 ));
2785
2786 assert!(!ProgramCache::<TestForkGraph>::matches_criteria(
2787 &program,
2788 &ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(1)
2789 ));
2790 }
2791}