1use {
2 crate::invoke_context::{BuiltinFunctionWithContext, InvokeContext},
3 log::{debug, error, log_enabled, trace},
4 percentage::PercentageInteger,
5 solana_clock::{Epoch, Slot},
6 solana_pubkey::Pubkey,
7 solana_sbpf::{
8 elf::Executable, program::BuiltinProgram, verifier::RequisiteVerifier, vm::Config,
9 },
10 solana_sdk_ids::{
11 bpf_loader, bpf_loader_deprecated, bpf_loader_upgradeable, loader_v4, native_loader,
12 },
13 solana_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 {solana_svm_measure::measure::Measure, solana_svm_timings::ExecuteDetailsTimings};
29
30pub type ProgramRuntimeEnvironment = Arc<BuiltinProgram<InvokeContext<'static, '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)]
132pub enum ProgramCacheEntryType {
133 FailedVerification(ProgramRuntimeEnvironment),
135 #[default]
139 Closed,
140 DelayVisibility,
142 Unloaded(ProgramRuntimeEnvironment),
146 Loaded(Executable<InvokeContext<'static, 'static>>),
148 Builtin(BuiltinProgram<InvokeContext<'static, 'static>>),
150}
151
152impl Debug for ProgramCacheEntryType {
153 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
154 match self {
155 ProgramCacheEntryType::FailedVerification(_) => {
156 write!(f, "ProgramCacheEntryType::FailedVerification")
157 }
158 ProgramCacheEntryType::Closed => write!(f, "ProgramCacheEntryType::Closed"),
159 ProgramCacheEntryType::DelayVisibility => {
160 write!(f, "ProgramCacheEntryType::DelayVisibility")
161 }
162 ProgramCacheEntryType::Unloaded(_) => write!(f, "ProgramCacheEntryType::Unloaded"),
163 ProgramCacheEntryType::Loaded(_) => write!(f, "ProgramCacheEntryType::Loaded"),
164 ProgramCacheEntryType::Builtin(_) => write!(f, "ProgramCacheEntryType::Builtin"),
165 }
166 }
167}
168
169impl ProgramCacheEntryType {
170 pub fn get_environment(&self) -> Option<&ProgramRuntimeEnvironment> {
172 match self {
173 ProgramCacheEntryType::Loaded(program) => Some(program.get_loader()),
174 ProgramCacheEntryType::FailedVerification(env)
175 | ProgramCacheEntryType::Unloaded(env) => Some(env),
176 _ => None,
177 }
178 }
179}
180
181#[derive(Debug, Default)]
185pub struct ProgramCacheEntry {
186 pub program: ProgramCacheEntryType,
188 pub account_owner: ProgramCacheEntryOwner,
190 pub account_size: usize,
192 pub deployment_slot: Slot,
194 pub effective_slot: Slot,
196 pub tx_usage_counter: Arc<AtomicU64>,
198 pub latest_access_slot: AtomicU64,
200}
201
202#[derive(Debug, Default)]
204pub struct ProgramCacheStats {
205 pub hits: AtomicU64,
207 pub misses: AtomicU64,
209 pub evictions: HashMap<Pubkey, u64>,
211 pub reloads: AtomicU64,
213 pub insertions: AtomicU64,
215 pub lost_insertions: AtomicU64,
217 pub replacements: AtomicU64,
219 pub one_hit_wonders: AtomicU64,
221 pub prunes_orphan: AtomicU64,
223 pub prunes_environment: AtomicU64,
225 pub empty_entries: AtomicU64,
227 pub water_level: AtomicU64,
229}
230
231impl ProgramCacheStats {
232 pub fn reset(&mut self) {
233 *self = ProgramCacheStats::default();
234 }
235 pub fn log(&self) {
236 let hits = self.hits.load(Ordering::Relaxed);
237 let misses = self.misses.load(Ordering::Relaxed);
238 let evictions: u64 = self.evictions.values().sum();
239 let reloads = self.reloads.load(Ordering::Relaxed);
240 let insertions = self.insertions.load(Ordering::Relaxed);
241 let lost_insertions = self.lost_insertions.load(Ordering::Relaxed);
242 let replacements = self.replacements.load(Ordering::Relaxed);
243 let one_hit_wonders = self.one_hit_wonders.load(Ordering::Relaxed);
244 let prunes_orphan = self.prunes_orphan.load(Ordering::Relaxed);
245 let prunes_environment = self.prunes_environment.load(Ordering::Relaxed);
246 let empty_entries = self.empty_entries.load(Ordering::Relaxed);
247 let water_level = self.water_level.load(Ordering::Relaxed);
248 debug!(
249 "Loaded Programs Cache Stats -- Hits: {hits}, Misses: {misses}, Evictions: \
250 {evictions}, Reloads: {reloads}, Insertions: {insertions}, Lost-Insertions: \
251 {lost_insertions}, Replacements: {replacements}, One-Hit-Wonders: {one_hit_wonders}, \
252 Prunes-Orphan: {prunes_orphan}, Prunes-Environment: {prunes_environment}, Empty: \
253 {empty_entries}, Water-Level: {water_level}"
254 );
255 if log_enabled!(log::Level::Trace) && !self.evictions.is_empty() {
256 let mut evictions = self.evictions.iter().collect::<Vec<_>>();
257 evictions.sort_by_key(|e| e.1);
258 let evictions = evictions
259 .into_iter()
260 .rev()
261 .map(|(program_id, evictions)| {
262 format!(" {:<44} {}", program_id.to_string(), evictions)
263 })
264 .collect::<Vec<_>>();
265 let evictions = evictions.join("\n");
266 trace!(
267 "Eviction Details:\n {:<44} {}\n{}",
268 "Program",
269 "Count",
270 evictions
271 );
272 }
273 }
274}
275
276#[cfg(feature = "metrics")]
277#[derive(Debug, Default)]
279pub struct LoadProgramMetrics {
280 pub program_id: String,
282 pub register_syscalls_us: u64,
284 pub load_elf_us: u64,
286 pub verify_code_us: u64,
288 pub jit_compile_us: u64,
290}
291
292#[cfg(feature = "metrics")]
293impl LoadProgramMetrics {
294 pub fn submit_datapoint(&self, timings: &mut ExecuteDetailsTimings) {
295 timings.create_executor_register_syscalls_us += self.register_syscalls_us;
296 timings.create_executor_load_elf_us += self.load_elf_us;
297 timings.create_executor_verify_code_us += self.verify_code_us;
298 timings.create_executor_jit_compile_us += self.jit_compile_us;
299 }
300}
301
302impl PartialEq for ProgramCacheEntry {
303 fn eq(&self, other: &Self) -> bool {
304 self.effective_slot == other.effective_slot
305 && self.deployment_slot == other.deployment_slot
306 && self.is_tombstone() == other.is_tombstone()
307 }
308}
309
310impl ProgramCacheEntry {
311 pub fn new(
313 loader_key: &Pubkey,
314 program_runtime_environment: ProgramRuntimeEnvironment,
315 deployment_slot: Slot,
316 effective_slot: Slot,
317 elf_bytes: &[u8],
318 account_size: usize,
319 #[cfg(feature = "metrics")] metrics: &mut LoadProgramMetrics,
320 ) -> Result<Self, Box<dyn std::error::Error>> {
321 Self::new_internal(
322 loader_key,
323 program_runtime_environment,
324 deployment_slot,
325 effective_slot,
326 elf_bytes,
327 account_size,
328 #[cfg(feature = "metrics")]
329 metrics,
330 false, )
332 }
333
334 pub unsafe fn reload(
343 loader_key: &Pubkey,
344 program_runtime_environment: Arc<BuiltinProgram<InvokeContext<'static, 'static>>>,
345 deployment_slot: Slot,
346 effective_slot: Slot,
347 elf_bytes: &[u8],
348 account_size: usize,
349 #[cfg(feature = "metrics")] metrics: &mut LoadProgramMetrics,
350 ) -> Result<Self, Box<dyn std::error::Error>> {
351 Self::new_internal(
352 loader_key,
353 program_runtime_environment,
354 deployment_slot,
355 effective_slot,
356 elf_bytes,
357 account_size,
358 #[cfg(feature = "metrics")]
359 metrics,
360 true, )
362 }
363
364 fn new_internal(
365 loader_key: &Pubkey,
366 program_runtime_environment: Arc<BuiltinProgram<InvokeContext<'static, 'static>>>,
367 deployment_slot: Slot,
368 effective_slot: Slot,
369 elf_bytes: &[u8],
370 account_size: usize,
371 #[cfg(feature = "metrics")] metrics: &mut LoadProgramMetrics,
372 reloading: bool,
373 ) -> Result<Self, Box<dyn std::error::Error>> {
374 #[cfg(feature = "metrics")]
375 let load_elf_time = Measure::start("load_elf_time");
376 #[allow(unused_mut)]
379 let mut executable = Executable::load(elf_bytes, program_runtime_environment.clone())?;
380 #[cfg(feature = "metrics")]
381 {
382 metrics.load_elf_us = load_elf_time.end_as_us();
383 }
384
385 if !reloading {
386 #[cfg(feature = "metrics")]
387 let verify_code_time = Measure::start("verify_code_time");
388 executable.verify::<RequisiteVerifier>()?;
389 #[cfg(feature = "metrics")]
390 {
391 metrics.verify_code_us = verify_code_time.end_as_us();
392 }
393 }
394
395 #[cfg(all(not(target_os = "windows"), target_arch = "x86_64"))]
396 {
397 #[cfg(feature = "metrics")]
398 let jit_compile_time = Measure::start("jit_compile_time");
399 executable.jit_compile()?;
400 #[cfg(feature = "metrics")]
401 {
402 metrics.jit_compile_us = jit_compile_time.end_as_us();
403 }
404 }
405
406 Ok(Self {
407 deployment_slot,
408 account_owner: ProgramCacheEntryOwner::try_from(loader_key).unwrap(),
409 account_size,
410 effective_slot,
411 tx_usage_counter: Arc::<AtomicU64>::default(),
412 program: ProgramCacheEntryType::Loaded(executable),
413 latest_access_slot: AtomicU64::new(0),
414 })
415 }
416
417 pub fn to_unloaded(&self) -> Option<Self> {
418 match &self.program {
419 ProgramCacheEntryType::Loaded(_) => {}
420 ProgramCacheEntryType::FailedVerification(_)
421 | ProgramCacheEntryType::Closed
422 | ProgramCacheEntryType::DelayVisibility
423 | ProgramCacheEntryType::Unloaded(_)
424 | ProgramCacheEntryType::Builtin(_) => {
425 return None;
426 }
427 }
428 Some(Self {
429 program: ProgramCacheEntryType::Unloaded(self.program.get_environment()?.clone()),
430 account_owner: self.account_owner,
431 account_size: self.account_size,
432 deployment_slot: self.deployment_slot,
433 effective_slot: self.effective_slot,
434 tx_usage_counter: self.tx_usage_counter.clone(),
435 latest_access_slot: AtomicU64::new(self.latest_access_slot.load(Ordering::Relaxed)),
436 })
437 }
438
439 pub fn new_builtin(
441 deployment_slot: Slot,
442 account_size: usize,
443 builtin_function: BuiltinFunctionWithContext,
444 ) -> Self {
445 let mut program = BuiltinProgram::new_builtin();
446 program
447 .register_function("entrypoint", builtin_function)
448 .unwrap();
449 Self {
450 deployment_slot,
451 account_owner: ProgramCacheEntryOwner::NativeLoader,
452 account_size,
453 effective_slot: deployment_slot,
454 tx_usage_counter: Arc::<AtomicU64>::default(),
455 program: ProgramCacheEntryType::Builtin(program),
456 latest_access_slot: AtomicU64::new(0),
457 }
458 }
459
460 pub fn new_tombstone(
461 slot: Slot,
462 account_owner: ProgramCacheEntryOwner,
463 reason: ProgramCacheEntryType,
464 ) -> Self {
465 Self::new_tombstone_with_usage_counter(
466 slot,
467 account_owner,
468 reason,
469 Arc::<AtomicU64>::default(),
470 )
471 }
472
473 pub fn new_tombstone_with_usage_counter(
474 slot: Slot,
475 account_owner: ProgramCacheEntryOwner,
476 reason: ProgramCacheEntryType,
477 tx_usage_counter: Arc<AtomicU64>,
478 ) -> Self {
479 let tombstone = Self {
480 program: reason,
481 account_owner,
482 account_size: 0,
483 deployment_slot: slot,
484 effective_slot: slot,
485 tx_usage_counter,
486 latest_access_slot: AtomicU64::new(0),
487 };
488 debug_assert!(tombstone.is_tombstone());
489 tombstone
490 }
491
492 pub fn is_tombstone(&self) -> bool {
493 matches!(
494 self.program,
495 ProgramCacheEntryType::FailedVerification(_)
496 | ProgramCacheEntryType::Closed
497 | ProgramCacheEntryType::DelayVisibility
498 )
499 }
500
501 fn is_implicit_delay_visibility_tombstone(&self, slot: Slot) -> bool {
502 !matches!(self.program, ProgramCacheEntryType::Builtin(_))
503 && self.effective_slot.saturating_sub(self.deployment_slot)
504 == DELAY_VISIBILITY_SLOT_OFFSET
505 && slot >= self.deployment_slot
506 && slot < self.effective_slot
507 }
508
509 pub fn update_access_slot(&self, slot: Slot) {
510 let _ = self.latest_access_slot.fetch_max(slot, Ordering::Relaxed);
511 }
512
513 pub fn decayed_usage_counter(&self, now: Slot) -> u64 {
514 let last_access = self.latest_access_slot.load(Ordering::Relaxed);
515 let decaying_for = std::cmp::min(63, now.saturating_sub(last_access));
517 self.tx_usage_counter.load(Ordering::Relaxed) >> decaying_for
518 }
519
520 pub fn account_owner(&self) -> Pubkey {
521 self.account_owner.into()
522 }
523}
524
525#[derive(Clone, Debug)]
529pub struct ProgramRuntimeEnvironments {
530 pub program_runtime_v1: ProgramRuntimeEnvironment,
532 pub program_runtime_v2: ProgramRuntimeEnvironment,
534}
535
536impl Default for ProgramRuntimeEnvironments {
537 fn default() -> Self {
538 let empty_loader = Arc::new(BuiltinProgram::new_loader(Config::default()));
539 Self {
540 program_runtime_v1: empty_loader.clone(),
541 program_runtime_v2: empty_loader,
542 }
543 }
544}
545
546#[derive(Debug, Default)]
548pub struct EpochBoundaryPreparation {
549 pub upcoming_epoch: Epoch,
551 pub upcoming_environments: Option<ProgramRuntimeEnvironments>,
557 pub programs_to_recompile: Vec<(Pubkey, Arc<ProgramCacheEntry>)>,
559}
560
561impl EpochBoundaryPreparation {
562 pub fn new(epoch: Epoch) -> Self {
563 Self {
564 upcoming_epoch: epoch,
565 upcoming_environments: None,
566 programs_to_recompile: Vec::default(),
567 }
568 }
569
570 pub fn get_upcoming_environments_for_epoch(
572 &self,
573 epoch: Epoch,
574 ) -> Option<ProgramRuntimeEnvironments> {
575 if epoch == self.upcoming_epoch {
576 return self.upcoming_environments.clone();
577 }
578 None
579 }
580
581 pub fn reroot(&mut self, epoch: Epoch) -> Option<ProgramRuntimeEnvironments> {
583 if epoch == self.upcoming_epoch {
584 if let Some(upcoming_environments) = self.upcoming_environments.take() {
585 self.programs_to_recompile.clear();
586 return Some(upcoming_environments);
587 }
588 }
589 None
590 }
591}
592
593#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
594pub struct LoadingTaskCookie(u64);
595
596impl LoadingTaskCookie {
597 fn new() -> Self {
598 Self(0)
599 }
600
601 fn update(&mut self) {
602 let LoadingTaskCookie(cookie) = self;
603 *cookie = cookie.wrapping_add(1);
604 }
605}
606
607#[derive(Debug, Default)]
609pub struct LoadingTaskWaiter {
610 cookie: Mutex<LoadingTaskCookie>,
611 cond: Condvar,
612}
613
614impl LoadingTaskWaiter {
615 pub fn new() -> Self {
616 Self {
617 cookie: Mutex::new(LoadingTaskCookie::new()),
618 cond: Condvar::new(),
619 }
620 }
621
622 pub fn cookie(&self) -> LoadingTaskCookie {
623 *self.cookie.lock().unwrap()
624 }
625
626 pub fn notify(&self) {
627 let mut cookie = self.cookie.lock().unwrap();
628 cookie.update();
629 self.cond.notify_all();
630 }
631
632 pub fn wait(&self, cookie: LoadingTaskCookie) -> LoadingTaskCookie {
633 let cookie_guard = self.cookie.lock().unwrap();
634 *self
635 .cond
636 .wait_while(cookie_guard, |current_cookie| *current_cookie == cookie)
637 .unwrap()
638 }
639}
640
641#[derive(Debug)]
642enum IndexImplementation {
643 V1 {
645 entries: HashMap<Pubkey, Vec<Arc<ProgramCacheEntry>>>,
650 loading_entries: Mutex<HashMap<Pubkey, (Slot, thread::ThreadId)>>,
659 },
660}
661
662pub struct ProgramCache<FG: ForkGraph> {
678 index: IndexImplementation,
680 pub latest_root_slot: Slot,
682 pub stats: ProgramCacheStats,
684 pub fork_graph: Option<Weak<RwLock<FG>>>,
686 pub loading_task_waiter: Arc<LoadingTaskWaiter>,
688}
689
690impl<FG: ForkGraph> Debug for ProgramCache<FG> {
691 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
692 f.debug_struct("ProgramCache")
693 .field("root slot", &self.latest_root_slot)
694 .field("stats", &self.stats)
695 .field("index", &self.index)
696 .finish()
697 }
698}
699
700#[derive(Clone, Debug, Default)]
706pub struct ProgramCacheForTxBatch {
707 entries: HashMap<Pubkey, Arc<ProgramCacheEntry>>,
710 modified_entries: HashMap<Pubkey, Arc<ProgramCacheEntry>>,
712 slot: Slot,
713 pub hit_max_limit: bool,
714 pub loaded_missing: bool,
715 pub merged_modified: bool,
716}
717
718impl ProgramCacheForTxBatch {
719 pub fn new(slot: Slot) -> Self {
720 Self {
721 entries: HashMap::new(),
722 modified_entries: HashMap::new(),
723 slot,
724 hit_max_limit: false,
725 loaded_missing: false,
726 merged_modified: false,
727 }
728 }
729
730 pub fn replenish(
736 &mut self,
737 key: Pubkey,
738 entry: Arc<ProgramCacheEntry>,
739 ) -> (bool, Arc<ProgramCacheEntry>) {
740 (self.entries.insert(key, entry.clone()).is_some(), entry)
741 }
742
743 pub fn store_modified_entry(&mut self, key: Pubkey, entry: Arc<ProgramCacheEntry>) {
746 self.modified_entries.insert(key, entry);
747 }
748
749 pub fn drain_modified_entries(&mut self) -> HashMap<Pubkey, Arc<ProgramCacheEntry>> {
752 std::mem::take(&mut self.modified_entries)
753 }
754
755 pub fn find(&self, key: &Pubkey) -> Option<Arc<ProgramCacheEntry>> {
756 self.modified_entries
760 .get(key)
761 .or_else(|| self.entries.get(key))
762 .map(|entry| {
763 if entry.is_implicit_delay_visibility_tombstone(self.slot) {
764 Arc::new(ProgramCacheEntry::new_tombstone_with_usage_counter(
768 entry.deployment_slot,
769 entry.account_owner,
770 ProgramCacheEntryType::DelayVisibility,
771 entry.tx_usage_counter.clone(),
772 ))
773 } else {
774 entry.clone()
775 }
776 })
777 }
778
779 pub fn slot(&self) -> Slot {
780 self.slot
781 }
782
783 pub fn set_slot_for_tests(&mut self, slot: Slot) {
784 self.slot = slot;
785 }
786
787 pub fn merge(&mut self, modified_entries: &HashMap<Pubkey, Arc<ProgramCacheEntry>>) {
788 modified_entries.iter().for_each(|(key, entry)| {
789 self.merged_modified = true;
790 self.replenish(*key, entry.clone());
791 })
792 }
793
794 pub fn is_empty(&self) -> bool {
795 self.entries.is_empty()
796 }
797}
798
799pub enum ProgramCacheMatchCriteria {
800 DeployedOnOrAfterSlot(Slot),
801 Tombstone,
802 NoCriteria,
803}
804
805impl<FG: ForkGraph> ProgramCache<FG> {
806 pub fn new(root_slot: Slot) -> Self {
807 Self {
808 index: IndexImplementation::V1 {
809 entries: HashMap::new(),
810 loading_entries: Mutex::new(HashMap::new()),
811 },
812 latest_root_slot: root_slot,
813 stats: ProgramCacheStats::default(),
814 fork_graph: None,
815 loading_task_waiter: Arc::new(LoadingTaskWaiter::default()),
816 }
817 }
818
819 pub fn set_fork_graph(&mut self, fork_graph: Weak<RwLock<FG>>) {
820 self.fork_graph = Some(fork_graph);
821 }
822
823 pub fn assign_program(
826 &mut self,
827 program_runtime_environments: &ProgramRuntimeEnvironments,
828 key: Pubkey,
829 entry: Arc<ProgramCacheEntry>,
830 ) -> bool {
831 debug_assert!(!matches!(
832 &entry.program,
833 ProgramCacheEntryType::DelayVisibility
834 ));
835 fn is_current_env(
839 environments: &ProgramRuntimeEnvironments,
840 env_opt: Option<&ProgramRuntimeEnvironment>,
841 ) -> bool {
842 env_opt
843 .map(|env| {
844 Arc::ptr_eq(env, &environments.program_runtime_v1)
845 || Arc::ptr_eq(env, &environments.program_runtime_v2)
846 })
847 .unwrap_or(true)
848 }
849 match &mut self.index {
850 IndexImplementation::V1 { entries, .. } => {
851 let slot_versions = &mut entries.entry(key).or_default();
852 match slot_versions.binary_search_by(|at| {
853 at.effective_slot
854 .cmp(&entry.effective_slot)
855 .then(at.deployment_slot.cmp(&entry.deployment_slot))
856 .then(
857 is_current_env(
861 program_runtime_environments,
862 at.program.get_environment(),
863 )
864 .cmp(&is_current_env(
865 program_runtime_environments,
866 entry.program.get_environment(),
867 )),
868 )
869 }) {
870 Ok(index) => {
871 let existing = slot_versions.get_mut(index).unwrap();
872 match (&existing.program, &entry.program) {
873 (
874 ProgramCacheEntryType::Builtin(_),
875 ProgramCacheEntryType::Builtin(_),
876 )
877 | (
878 ProgramCacheEntryType::Unloaded(_),
879 ProgramCacheEntryType::Loaded(_),
880 ) => {}
881 (ProgramCacheEntryType::Closed, ProgramCacheEntryType::Closed)
882 if existing.account_owner != entry.account_owner => {}
883 _ => {
884 error!(
886 "ProgramCache::assign_program() failed key={key:?} \
887 existing={slot_versions:?} entry={entry:?}"
888 );
889 debug_assert!(false, "Unexpected replacement of an entry");
890 self.stats.replacements.fetch_add(1, Ordering::Relaxed);
891 return true;
892 }
893 }
894 entry.tx_usage_counter.fetch_add(
896 existing.tx_usage_counter.load(Ordering::Relaxed),
897 Ordering::Relaxed,
898 );
899 *existing = Arc::clone(&entry);
900 self.stats.reloads.fetch_add(1, Ordering::Relaxed);
901 }
902 Err(index) => {
903 self.stats.insertions.fetch_add(1, Ordering::Relaxed);
904 slot_versions.insert(index, Arc::clone(&entry));
905 }
906 }
907 slot_versions.retain(|existing| {
910 existing.deployment_slot != entry.deployment_slot
911 || existing
912 .program
913 .get_environment()
914 .zip(entry.program.get_environment())
915 .map(|(a, b)| !Arc::ptr_eq(a, b))
916 .unwrap_or(false)
917 || existing == &entry
918 });
919 }
920 }
921 false
922 }
923
924 pub fn prune_by_deployment_slot(&mut self, slot: Slot) {
925 match &mut self.index {
926 IndexImplementation::V1 { entries, .. } => {
927 for second_level in entries.values_mut() {
928 second_level.retain(|entry| entry.deployment_slot != slot);
929 }
930 self.remove_programs_with_no_entries();
931 }
932 }
933 }
934
935 pub fn prune(
937 &mut self,
938 new_root_slot: Slot,
939 upcoming_environments: Option<ProgramRuntimeEnvironments>,
940 ) {
941 let Some(fork_graph) = self.fork_graph.clone() else {
942 error!("Program cache doesn't have fork graph.");
943 return;
944 };
945 let fork_graph = fork_graph.upgrade().unwrap();
946 let Ok(fork_graph) = fork_graph.read() else {
947 error!("Failed to lock fork graph for reading.");
948 return;
949 };
950 match &mut self.index {
951 IndexImplementation::V1 { entries, .. } => {
952 for second_level in entries.values_mut() {
953 let mut first_ancestor_found = false;
955 let mut first_ancestor_env = None;
956 *second_level = second_level
957 .iter()
958 .rev()
959 .filter(|entry| {
960 let relation =
961 fork_graph.relationship(entry.deployment_slot, new_root_slot);
962 if entry.deployment_slot >= new_root_slot {
963 matches!(relation, BlockRelation::Equal | BlockRelation::Descendant)
964 } else if matches!(relation, BlockRelation::Ancestor)
965 || entry.deployment_slot <= self.latest_root_slot
966 {
967 if !first_ancestor_found {
968 first_ancestor_found = true;
969 first_ancestor_env = entry.program.get_environment();
970 return true;
971 }
972 if let Some(entry_env) = entry.program.get_environment() {
979 if let Some(env) = first_ancestor_env {
980 if !Arc::ptr_eq(entry_env, env) {
981 return true;
982 }
983 }
984 }
985 self.stats.prunes_orphan.fetch_add(1, Ordering::Relaxed);
986 false
987 } else {
988 self.stats.prunes_orphan.fetch_add(1, Ordering::Relaxed);
989 false
990 }
991 })
992 .filter(|entry| {
993 if let Some(upcoming_environments) = upcoming_environments.as_ref() {
995 if !Self::matches_environment(entry, upcoming_environments) {
996 self.stats
997 .prunes_environment
998 .fetch_add(1, Ordering::Relaxed);
999 return false;
1000 }
1001 }
1002 true
1003 })
1004 .cloned()
1005 .collect();
1006 second_level.reverse();
1007 }
1008 }
1009 }
1010 self.remove_programs_with_no_entries();
1011 debug_assert!(self.latest_root_slot <= new_root_slot);
1012 self.latest_root_slot = new_root_slot;
1013 }
1014
1015 fn matches_environment(
1016 entry: &Arc<ProgramCacheEntry>,
1017 environments: &ProgramRuntimeEnvironments,
1018 ) -> bool {
1019 let Some(environment) = entry.program.get_environment() else {
1020 return true;
1021 };
1022 Arc::ptr_eq(environment, &environments.program_runtime_v1)
1023 || Arc::ptr_eq(environment, &environments.program_runtime_v2)
1024 }
1025
1026 fn matches_criteria(
1027 program: &Arc<ProgramCacheEntry>,
1028 criteria: &ProgramCacheMatchCriteria,
1029 ) -> bool {
1030 match criteria {
1031 ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(slot) => {
1032 program.deployment_slot >= *slot
1033 }
1034 ProgramCacheMatchCriteria::Tombstone => program.is_tombstone(),
1035 ProgramCacheMatchCriteria::NoCriteria => true,
1036 }
1037 }
1038
1039 pub fn extract(
1042 &self,
1043 search_for: &mut Vec<(Pubkey, ProgramCacheMatchCriteria)>,
1044 loaded_programs_for_tx_batch: &mut ProgramCacheForTxBatch,
1045 program_runtime_environments_for_execution: &ProgramRuntimeEnvironments,
1046 increment_usage_counter: bool,
1047 count_hits_and_misses: bool,
1048 ) -> Option<Pubkey> {
1049 debug_assert!(self.fork_graph.is_some());
1050 let fork_graph = self.fork_graph.as_ref().unwrap().upgrade().unwrap();
1051 let locked_fork_graph = fork_graph.read().unwrap();
1052 let mut cooperative_loading_task = None;
1053 match &self.index {
1054 IndexImplementation::V1 {
1055 entries,
1056 loading_entries,
1057 } => {
1058 search_for.retain(|(key, match_criteria)| {
1059 if let Some(second_level) = entries.get(key) {
1060 let mut filter_by_deployment_slot = None;
1061 for entry in second_level.iter().rev() {
1062 if filter_by_deployment_slot
1063 .map(|slot| slot != entry.deployment_slot)
1064 .unwrap_or(false)
1065 {
1066 continue;
1067 }
1068 if entry.deployment_slot <= self.latest_root_slot
1069 || matches!(
1070 locked_fork_graph.relationship(
1071 entry.deployment_slot,
1072 loaded_programs_for_tx_batch.slot
1073 ),
1074 BlockRelation::Equal | BlockRelation::Ancestor
1075 )
1076 {
1077 let entry_to_return = if loaded_programs_for_tx_batch.slot
1078 >= entry.effective_slot
1079 {
1080 if !Self::matches_environment(
1081 entry,
1082 program_runtime_environments_for_execution,
1083 ) {
1084 filter_by_deployment_slot = filter_by_deployment_slot
1085 .or(Some(entry.deployment_slot));
1086 continue;
1087 }
1088 if !Self::matches_criteria(entry, match_criteria) {
1089 break;
1090 }
1091 if let ProgramCacheEntryType::Unloaded(_environment) =
1092 &entry.program
1093 {
1094 break;
1095 }
1096 entry.clone()
1097 } else if entry.is_implicit_delay_visibility_tombstone(
1098 loaded_programs_for_tx_batch.slot,
1099 ) {
1100 Arc::new(ProgramCacheEntry::new_tombstone_with_usage_counter(
1104 entry.deployment_slot,
1105 entry.account_owner,
1106 ProgramCacheEntryType::DelayVisibility,
1107 entry.tx_usage_counter.clone(),
1108 ))
1109 } else {
1110 continue;
1111 };
1112 entry_to_return
1113 .update_access_slot(loaded_programs_for_tx_batch.slot);
1114 if increment_usage_counter {
1115 entry_to_return
1116 .tx_usage_counter
1117 .fetch_add(1, Ordering::Relaxed);
1118 }
1119 loaded_programs_for_tx_batch
1120 .entries
1121 .insert(*key, entry_to_return);
1122 return false;
1123 }
1124 }
1125 }
1126 if cooperative_loading_task.is_none() {
1127 let mut loading_entries = loading_entries.lock().unwrap();
1128 let entry = loading_entries.entry(*key);
1129 if let Entry::Vacant(entry) = entry {
1130 entry.insert((
1131 loaded_programs_for_tx_batch.slot,
1132 thread::current().id(),
1133 ));
1134 cooperative_loading_task = Some(*key);
1135 }
1136 }
1137 true
1138 });
1139 }
1140 }
1141 drop(locked_fork_graph);
1142 if count_hits_and_misses {
1143 self.stats
1144 .misses
1145 .fetch_add(search_for.len() as u64, Ordering::Relaxed);
1146 self.stats.hits.fetch_add(
1147 loaded_programs_for_tx_batch.entries.len() as u64,
1148 Ordering::Relaxed,
1149 );
1150 }
1151 cooperative_loading_task
1152 }
1153
1154 pub fn finish_cooperative_loading_task(
1156 &mut self,
1157 program_runtime_environments: &ProgramRuntimeEnvironments,
1158 slot: Slot,
1159 key: Pubkey,
1160 loaded_program: Arc<ProgramCacheEntry>,
1161 ) -> bool {
1162 match &mut self.index {
1163 IndexImplementation::V1 {
1164 loading_entries, ..
1165 } => {
1166 let loading_thread = loading_entries.get_mut().unwrap().remove(&key);
1167 debug_assert_eq!(loading_thread, Some((slot, thread::current().id())));
1168 if loaded_program.deployment_slot > self.latest_root_slot
1170 && !matches!(
1171 self.fork_graph
1172 .as_ref()
1173 .unwrap()
1174 .upgrade()
1175 .unwrap()
1176 .read()
1177 .unwrap()
1178 .relationship(loaded_program.deployment_slot, slot),
1179 BlockRelation::Equal | BlockRelation::Ancestor
1180 )
1181 {
1182 self.stats.lost_insertions.fetch_add(1, Ordering::Relaxed);
1183 }
1184 let was_occupied =
1185 self.assign_program(program_runtime_environments, key, loaded_program);
1186 self.loading_task_waiter.notify();
1187 was_occupied
1188 }
1189 }
1190 }
1191
1192 pub fn merge(
1193 &mut self,
1194 program_runtime_environments: &ProgramRuntimeEnvironments,
1195 modified_entries: &HashMap<Pubkey, Arc<ProgramCacheEntry>>,
1196 ) {
1197 modified_entries.iter().for_each(|(key, entry)| {
1198 self.assign_program(program_runtime_environments, *key, entry.clone());
1199 })
1200 }
1201
1202 pub fn get_flattened_entries(
1204 &self,
1205 include_program_runtime_v1: bool,
1206 _include_program_runtime_v2: bool,
1207 ) -> Vec<(Pubkey, Arc<ProgramCacheEntry>)> {
1208 match &self.index {
1209 IndexImplementation::V1 { entries, .. } => entries
1210 .iter()
1211 .flat_map(|(id, second_level)| {
1212 second_level
1213 .iter()
1214 .filter_map(move |program| match program.program {
1215 ProgramCacheEntryType::Loaded(_) => {
1216 if include_program_runtime_v1 {
1217 Some((*id, program.clone()))
1218 } else {
1219 None
1220 }
1221 }
1222 _ => None,
1223 })
1224 })
1225 .collect(),
1226 }
1227 }
1228
1229 pub fn get_flattened_entries_for_tests(&self) -> Vec<(Pubkey, Arc<ProgramCacheEntry>)> {
1231 match &self.index {
1232 IndexImplementation::V1 { entries, .. } => entries
1233 .iter()
1234 .flat_map(|(id, second_level)| {
1235 second_level.iter().map(|program| (*id, program.clone()))
1236 })
1237 .collect(),
1238 }
1239 }
1240
1241 pub fn get_slot_versions_for_tests(&self, key: &Pubkey) -> &[Arc<ProgramCacheEntry>] {
1243 match &self.index {
1244 IndexImplementation::V1 { entries, .. } => entries
1245 .get(key)
1246 .map(|second_level| second_level.as_ref())
1247 .unwrap_or(&[]),
1248 }
1249 }
1250
1251 pub fn sort_and_unload(&mut self, shrink_to: PercentageInteger) {
1253 let mut sorted_candidates = self.get_flattened_entries(true, true);
1254 sorted_candidates
1255 .sort_by_cached_key(|(_id, program)| program.tx_usage_counter.load(Ordering::Relaxed));
1256 let num_to_unload = sorted_candidates
1257 .len()
1258 .saturating_sub(shrink_to.apply_to(MAX_LOADED_ENTRY_COUNT));
1259 self.unload_program_entries(sorted_candidates.iter().take(num_to_unload));
1260 }
1261
1262 pub fn evict_using_2s_random_selection(&mut self, shrink_to: PercentageInteger, now: Slot) {
1265 let mut candidates = self.get_flattened_entries(true, true);
1266 self.stats
1267 .water_level
1268 .store(candidates.len() as u64, Ordering::Relaxed);
1269 let num_to_unload = candidates
1270 .len()
1271 .saturating_sub(shrink_to.apply_to(MAX_LOADED_ENTRY_COUNT));
1272 fn random_index_and_usage_counter(
1273 candidates: &[(Pubkey, Arc<ProgramCacheEntry>)],
1274 now: Slot,
1275 ) -> (usize, u64) {
1276 let mut rng = thread_rng();
1277 let index = rng.gen_range(0..candidates.len());
1278 let usage_counter = candidates
1279 .get(index)
1280 .expect("Failed to get cached entry")
1281 .1
1282 .decayed_usage_counter(now);
1283 (index, usage_counter)
1284 }
1285
1286 for _ in 0..num_to_unload {
1287 let (index1, usage_counter1) = random_index_and_usage_counter(&candidates, now);
1288 let (index2, usage_counter2) = random_index_and_usage_counter(&candidates, now);
1289
1290 let (program, entry) = if usage_counter1 < usage_counter2 {
1291 candidates.swap_remove(index1)
1292 } else {
1293 candidates.swap_remove(index2)
1294 };
1295 self.unload_program_entry(&program, &entry);
1296 }
1297 }
1298
1299 pub fn remove_programs(&mut self, keys: impl Iterator<Item = Pubkey>) {
1301 match &mut self.index {
1302 IndexImplementation::V1 { entries, .. } => {
1303 for k in keys {
1304 entries.remove(&k);
1305 }
1306 }
1307 }
1308 }
1309
1310 fn unload_program_entry(&mut self, program: &Pubkey, remove_entry: &Arc<ProgramCacheEntry>) {
1313 match &mut self.index {
1314 IndexImplementation::V1 { entries, .. } => {
1315 let second_level = entries.get_mut(program).expect("Cache lookup failed");
1316 let candidate = second_level
1317 .iter_mut()
1318 .find(|entry| entry == &remove_entry)
1319 .expect("Program entry not found");
1320
1321 if let Some(unloaded) = candidate.to_unloaded() {
1325 if candidate.tx_usage_counter.load(Ordering::Relaxed) == 1 {
1326 self.stats.one_hit_wonders.fetch_add(1, Ordering::Relaxed);
1327 }
1328 self.stats
1329 .evictions
1330 .entry(*program)
1331 .and_modify(|c| *c = c.saturating_add(1))
1332 .or_insert(1);
1333 *candidate = Arc::new(unloaded);
1334 }
1335 }
1336 }
1337 }
1338
1339 fn unload_program_entries<'a>(
1340 &mut self,
1341 remove: impl Iterator<Item = &'a (Pubkey, Arc<ProgramCacheEntry>)>,
1342 ) {
1343 for (program, entry) in remove {
1344 self.unload_program_entry(program, entry);
1345 }
1346 }
1347
1348 fn remove_programs_with_no_entries(&mut self) {
1349 match &mut self.index {
1350 IndexImplementation::V1 { entries, .. } => {
1351 let num_programs_before_removal = entries.len();
1352 entries.retain(|_key, second_level| !second_level.is_empty());
1353 if entries.len() < num_programs_before_removal {
1354 self.stats.empty_entries.fetch_add(
1355 num_programs_before_removal.saturating_sub(entries.len()) as u64,
1356 Ordering::Relaxed,
1357 );
1358 }
1359 }
1360 }
1361 }
1362}
1363
1364#[cfg(feature = "frozen-abi")]
1365impl solana_frozen_abi::abi_example::AbiExample for ProgramCacheEntry {
1366 fn example() -> Self {
1367 Self::default()
1369 }
1370}
1371
1372#[cfg(feature = "frozen-abi")]
1373impl<FG: ForkGraph> solana_frozen_abi::abi_example::AbiExample for ProgramCache<FG> {
1374 fn example() -> Self {
1375 Self::new(Slot::default())
1377 }
1378}
1379
1380#[cfg(test)]
1381mod tests {
1382 use {
1383 crate::loaded_programs::{
1384 BlockRelation, ForkGraph, ProgramCache, ProgramCacheEntry, ProgramCacheEntryOwner,
1385 ProgramCacheEntryType, ProgramCacheForTxBatch, ProgramCacheMatchCriteria,
1386 ProgramRuntimeEnvironment, ProgramRuntimeEnvironments, DELAY_VISIBILITY_SLOT_OFFSET,
1387 },
1388 assert_matches::assert_matches,
1389 percentage::Percentage,
1390 solana_clock::Slot,
1391 solana_pubkey::Pubkey,
1392 solana_sbpf::{elf::Executable, program::BuiltinProgram},
1393 std::{
1394 fs::File,
1395 io::Read,
1396 ops::ControlFlow,
1397 sync::{
1398 atomic::{AtomicU64, Ordering},
1399 Arc, RwLock,
1400 },
1401 },
1402 test_case::{test_case, test_matrix},
1403 };
1404
1405 static MOCK_ENVIRONMENT: std::sync::OnceLock<ProgramRuntimeEnvironment> =
1406 std::sync::OnceLock::<ProgramRuntimeEnvironment>::new();
1407
1408 fn get_mock_env() -> ProgramRuntimeEnvironment {
1409 MOCK_ENVIRONMENT
1410 .get_or_init(|| Arc::new(BuiltinProgram::new_mock()))
1411 .clone()
1412 }
1413
1414 fn get_mock_envs() -> ProgramRuntimeEnvironments {
1415 ProgramRuntimeEnvironments {
1416 program_runtime_v1: get_mock_env(),
1417 program_runtime_v2: get_mock_env(),
1418 }
1419 }
1420
1421 fn new_test_entry(deployment_slot: Slot, effective_slot: Slot) -> Arc<ProgramCacheEntry> {
1422 new_test_entry_with_usage(deployment_slot, effective_slot, AtomicU64::default())
1423 }
1424
1425 fn new_loaded_entry(env: ProgramRuntimeEnvironment) -> ProgramCacheEntryType {
1426 let mut elf = Vec::new();
1427 File::open("../programs/bpf_loader/test_elfs/out/noop_aligned.so")
1428 .unwrap()
1429 .read_to_end(&mut elf)
1430 .unwrap();
1431 let executable = Executable::load(&elf, env).unwrap();
1432 ProgramCacheEntryType::Loaded(executable)
1433 }
1434
1435 fn new_test_entry_with_usage(
1436 deployment_slot: Slot,
1437 effective_slot: Slot,
1438 usage_counter: AtomicU64,
1439 ) -> Arc<ProgramCacheEntry> {
1440 Arc::new(ProgramCacheEntry {
1441 program: new_loaded_entry(get_mock_env()),
1442 account_owner: ProgramCacheEntryOwner::LoaderV2,
1443 account_size: 0,
1444 deployment_slot,
1445 effective_slot,
1446 tx_usage_counter: Arc::new(usage_counter),
1447 latest_access_slot: AtomicU64::new(deployment_slot),
1448 })
1449 }
1450
1451 fn new_test_builtin_entry(
1452 deployment_slot: Slot,
1453 effective_slot: Slot,
1454 ) -> Arc<ProgramCacheEntry> {
1455 Arc::new(ProgramCacheEntry {
1456 program: ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1457 account_owner: ProgramCacheEntryOwner::NativeLoader,
1458 account_size: 0,
1459 deployment_slot,
1460 effective_slot,
1461 tx_usage_counter: Arc::default(),
1462 latest_access_slot: AtomicU64::default(),
1463 })
1464 }
1465
1466 fn set_tombstone<FG: ForkGraph>(
1467 cache: &mut ProgramCache<FG>,
1468 key: Pubkey,
1469 slot: Slot,
1470 reason: ProgramCacheEntryType,
1471 ) -> Arc<ProgramCacheEntry> {
1472 let envs = get_mock_envs();
1473 let program = Arc::new(ProgramCacheEntry::new_tombstone(
1474 slot,
1475 ProgramCacheEntryOwner::LoaderV2,
1476 reason,
1477 ));
1478 cache.assign_program(&envs, key, program.clone());
1479 program
1480 }
1481
1482 fn insert_unloaded_entry<FG: ForkGraph>(
1483 cache: &mut ProgramCache<FG>,
1484 key: Pubkey,
1485 slot: Slot,
1486 ) -> Arc<ProgramCacheEntry> {
1487 let envs = get_mock_envs();
1488 let loaded = new_test_entry_with_usage(slot, slot.saturating_add(1), AtomicU64::default());
1489 let unloaded = Arc::new(loaded.to_unloaded().expect("Failed to unload the program"));
1490 cache.assign_program(&envs, key, unloaded.clone());
1491 unloaded
1492 }
1493
1494 fn num_matching_entries<P, FG>(cache: &ProgramCache<FG>, predicate: P) -> usize
1495 where
1496 P: Fn(&ProgramCacheEntryType) -> bool,
1497 FG: ForkGraph,
1498 {
1499 cache
1500 .get_flattened_entries_for_tests()
1501 .iter()
1502 .filter(|(_key, program)| predicate(&program.program))
1503 .count()
1504 }
1505
1506 #[test]
1507 fn test_usage_counter_decay() {
1508 let program = new_test_entry_with_usage(10, 11, AtomicU64::new(32));
1509 program.update_access_slot(15);
1510 assert_eq!(program.decayed_usage_counter(15), 32);
1511 assert_eq!(program.decayed_usage_counter(16), 16);
1512 assert_eq!(program.decayed_usage_counter(17), 8);
1513 assert_eq!(program.decayed_usage_counter(18), 4);
1514 assert_eq!(program.decayed_usage_counter(19), 2);
1515 assert_eq!(program.decayed_usage_counter(20), 1);
1516 assert_eq!(program.decayed_usage_counter(21), 0);
1517 assert_eq!(program.decayed_usage_counter(15), 32);
1518 assert_eq!(program.decayed_usage_counter(14), 32);
1519
1520 program.update_access_slot(18);
1521 assert_eq!(program.decayed_usage_counter(15), 32);
1522 assert_eq!(program.decayed_usage_counter(16), 32);
1523 assert_eq!(program.decayed_usage_counter(17), 32);
1524 assert_eq!(program.decayed_usage_counter(18), 32);
1525 assert_eq!(program.decayed_usage_counter(19), 16);
1526 assert_eq!(program.decayed_usage_counter(20), 8);
1527 assert_eq!(program.decayed_usage_counter(21), 4);
1528
1529 assert_eq!(program.decayed_usage_counter(18 + 63), 0);
1531 assert_eq!(program.decayed_usage_counter(100), 0);
1532 }
1533
1534 fn program_deploy_test_helper(
1535 cache: &mut ProgramCache<TestForkGraph>,
1536 program: Pubkey,
1537 deployment_slots: Vec<Slot>,
1538 usage_counters: Vec<u64>,
1539 programs: &mut Vec<(Pubkey, Slot, u64)>,
1540 ) {
1541 let envs = get_mock_envs();
1542 deployment_slots
1544 .iter()
1545 .enumerate()
1546 .for_each(|(i, deployment_slot)| {
1547 let usage_counter = *usage_counters.get(i).unwrap_or(&0);
1548 cache.assign_program(
1549 &envs,
1550 program,
1551 new_test_entry_with_usage(
1552 *deployment_slot,
1553 (*deployment_slot).saturating_add(2),
1554 AtomicU64::new(usage_counter),
1555 ),
1556 );
1557 programs.push((program, *deployment_slot, usage_counter));
1558 });
1559
1560 let env = Arc::new(BuiltinProgram::new_mock());
1562 for slot in 21..31 {
1563 set_tombstone(
1564 cache,
1565 program,
1566 slot,
1567 ProgramCacheEntryType::FailedVerification(env.clone()),
1568 );
1569 }
1570
1571 for slot in 31..41 {
1573 insert_unloaded_entry(cache, program, slot);
1574 }
1575 }
1576
1577 #[test]
1578 fn test_random_eviction() {
1579 let mut programs = vec![];
1580 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1581
1582 program_deploy_test_helper(
1588 &mut cache,
1589 Pubkey::new_unique(),
1590 vec![0, 10, 20],
1591 vec![4, 5, 25],
1592 &mut programs,
1593 );
1594
1595 program_deploy_test_helper(
1597 &mut cache,
1598 Pubkey::new_unique(),
1599 vec![5, 11],
1600 vec![0, 2],
1601 &mut programs,
1602 );
1603
1604 program_deploy_test_helper(
1606 &mut cache,
1607 Pubkey::new_unique(),
1608 vec![0, 5, 15],
1609 vec![100, 3, 20],
1610 &mut programs,
1611 );
1612
1613 let num_loaded_expected = 8;
1615 let num_unloaded_expected = 30;
1617 let num_tombstones_expected = 30;
1619
1620 programs.sort_by_key(|(_id, _slot, usage_count)| *usage_count);
1622 let num_loaded = num_matching_entries(&cache, |program_type| {
1623 matches!(program_type, ProgramCacheEntryType::Loaded(_))
1624 });
1625 let num_unloaded = num_matching_entries(&cache, |program_type| {
1626 matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1627 });
1628 let num_tombstones = num_matching_entries(&cache, |program_type| {
1629 matches!(
1630 program_type,
1631 ProgramCacheEntryType::DelayVisibility
1632 | ProgramCacheEntryType::FailedVerification(_)
1633 | ProgramCacheEntryType::Closed
1634 )
1635 });
1636
1637 assert_eq!(num_loaded, num_loaded_expected);
1639 assert_eq!(num_unloaded, num_unloaded_expected);
1640 assert_eq!(num_tombstones, num_tombstones_expected);
1641
1642 let eviction_pct = 1;
1644
1645 let num_loaded_expected =
1646 Percentage::from(eviction_pct).apply_to(crate::loaded_programs::MAX_LOADED_ENTRY_COUNT);
1647 let num_unloaded_expected = num_unloaded_expected + num_loaded - num_loaded_expected;
1648 cache.evict_using_2s_random_selection(Percentage::from(eviction_pct), 21);
1649
1650 let num_loaded = num_matching_entries(&cache, |program_type| {
1652 matches!(program_type, ProgramCacheEntryType::Loaded(_))
1653 });
1654 let num_unloaded = num_matching_entries(&cache, |program_type| {
1655 matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1656 });
1657 let num_tombstones = num_matching_entries(&cache, |program_type| {
1658 matches!(program_type, ProgramCacheEntryType::FailedVerification(_))
1659 });
1660
1661 assert_eq!(num_loaded, num_loaded_expected);
1663 assert_eq!(num_unloaded, num_unloaded_expected);
1665 assert_eq!(num_tombstones, num_tombstones_expected);
1667 }
1668
1669 #[test]
1670 fn test_eviction() {
1671 let mut programs = vec![];
1672 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1673
1674 program_deploy_test_helper(
1676 &mut cache,
1677 Pubkey::new_unique(),
1678 vec![0, 10, 20],
1679 vec![4, 5, 25],
1680 &mut programs,
1681 );
1682
1683 program_deploy_test_helper(
1685 &mut cache,
1686 Pubkey::new_unique(),
1687 vec![5, 11],
1688 vec![0, 2],
1689 &mut programs,
1690 );
1691
1692 program_deploy_test_helper(
1694 &mut cache,
1695 Pubkey::new_unique(),
1696 vec![0, 5, 15],
1697 vec![100, 3, 20],
1698 &mut programs,
1699 );
1700
1701 let num_loaded_expected = 8;
1703 let num_unloaded_expected = 30;
1705 let num_tombstones_expected = 30;
1707
1708 programs.sort_by_key(|(_id, _slot, usage_count)| *usage_count);
1710 let num_loaded = num_matching_entries(&cache, |program_type| {
1711 matches!(program_type, ProgramCacheEntryType::Loaded(_))
1712 });
1713 let num_unloaded = num_matching_entries(&cache, |program_type| {
1714 matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1715 });
1716 let num_tombstones = num_matching_entries(&cache, |program_type| {
1717 matches!(program_type, ProgramCacheEntryType::FailedVerification(_))
1718 });
1719
1720 assert_eq!(num_loaded, num_loaded_expected);
1722 assert_eq!(num_unloaded, num_unloaded_expected);
1723 assert_eq!(num_tombstones, num_tombstones_expected);
1724
1725 let eviction_pct = 1;
1727
1728 let num_loaded_expected =
1729 Percentage::from(eviction_pct).apply_to(crate::loaded_programs::MAX_LOADED_ENTRY_COUNT);
1730 let num_unloaded_expected = num_unloaded_expected + num_loaded - num_loaded_expected;
1731
1732 cache.sort_and_unload(Percentage::from(eviction_pct));
1733
1734 let entries = cache.get_flattened_entries_for_tests();
1736 programs.iter().for_each(|entry| {
1737 assert!(entries.iter().any(|(key, _entry)| key == &entry.0));
1738 });
1739
1740 let unloaded = entries
1741 .iter()
1742 .filter_map(|(key, program)| {
1743 matches!(program.program, ProgramCacheEntryType::Unloaded(_))
1744 .then_some((*key, program.tx_usage_counter.load(Ordering::Relaxed)))
1745 })
1746 .collect::<Vec<(Pubkey, u64)>>();
1747
1748 for index in 0..3 {
1749 let expected = programs.get(index).expect("Missing program");
1750 assert!(unloaded.contains(&(expected.0, expected.2)));
1751 }
1752
1753 let num_loaded = num_matching_entries(&cache, |program_type| {
1755 matches!(program_type, ProgramCacheEntryType::Loaded(_))
1756 });
1757 let num_unloaded = num_matching_entries(&cache, |program_type| {
1758 matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1759 });
1760 let num_tombstones = num_matching_entries(&cache, |program_type| {
1761 matches!(
1762 program_type,
1763 ProgramCacheEntryType::DelayVisibility
1764 | ProgramCacheEntryType::FailedVerification(_)
1765 | ProgramCacheEntryType::Closed
1766 )
1767 });
1768
1769 assert_eq!(num_loaded, num_loaded_expected);
1771 assert_eq!(num_unloaded, num_unloaded_expected);
1773 assert_eq!(num_tombstones, num_tombstones_expected);
1775 }
1776
1777 #[test]
1778 fn test_usage_count_of_unloaded_program() {
1779 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1780 let envs = get_mock_envs();
1781
1782 let program = Pubkey::new_unique();
1783 let evict_to_pct = 2;
1784 let cache_capacity_after_shrink =
1785 Percentage::from(evict_to_pct).apply_to(crate::loaded_programs::MAX_LOADED_ENTRY_COUNT);
1786 let num_total_programs = (cache_capacity_after_shrink + 1) as u64;
1788 (0..num_total_programs).for_each(|i| {
1789 cache.assign_program(
1790 &envs,
1791 program,
1792 new_test_entry_with_usage(i, i + 2, AtomicU64::new(i + 10)),
1793 );
1794 });
1795
1796 cache.sort_and_unload(Percentage::from(evict_to_pct));
1797
1798 let num_unloaded = num_matching_entries(&cache, |program_type| {
1799 matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1800 });
1801 assert_eq!(num_unloaded, 1);
1802
1803 cache
1804 .get_flattened_entries_for_tests()
1805 .iter()
1806 .for_each(|(_key, program)| {
1807 if matches!(program.program, ProgramCacheEntryType::Unloaded(_)) {
1808 assert_eq!(program.tx_usage_counter.load(Ordering::Relaxed), 10);
1810 assert_eq!(program.deployment_slot, 0);
1811 assert_eq!(program.effective_slot, 2);
1812 }
1813 });
1814
1815 cache.assign_program(
1818 &envs,
1819 program,
1820 new_test_entry_with_usage(0, 2, AtomicU64::new(0)),
1821 );
1822
1823 cache
1824 .get_flattened_entries_for_tests()
1825 .iter()
1826 .for_each(|(_key, program)| {
1827 if matches!(program.program, ProgramCacheEntryType::Unloaded(_))
1828 && program.deployment_slot == 0
1829 && program.effective_slot == 2
1830 {
1831 assert_eq!(program.tx_usage_counter.load(Ordering::Relaxed), 10);
1833 }
1834 });
1835 }
1836
1837 #[test]
1838 fn test_fuzz_assign_program_order() {
1839 use rand::prelude::SliceRandom;
1840 const EXPECTED_ENTRIES: [(u64, u64); 7] =
1841 [(1, 2), (5, 5), (5, 6), (5, 10), (9, 10), (10, 10), (3, 12)];
1842 let mut rng = rand::thread_rng();
1843 let program_id = Pubkey::new_unique();
1844 let envs = get_mock_envs();
1845 for _ in 0..1000 {
1846 let mut entries = EXPECTED_ENTRIES.to_vec();
1847 entries.shuffle(&mut rng);
1848 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1849 for (deployment_slot, effective_slot) in entries {
1850 let entry = Arc::new(ProgramCacheEntry {
1851 program: new_loaded_entry(Arc::new(BuiltinProgram::new_mock())), account_owner: ProgramCacheEntryOwner::LoaderV2,
1853 account_size: 0,
1854 deployment_slot,
1855 effective_slot,
1856 tx_usage_counter: Arc::new(AtomicU64::default()),
1857 latest_access_slot: AtomicU64::new(deployment_slot),
1858 });
1859 assert!(!cache.assign_program(&envs, program_id, entry));
1860 }
1861 for ((deployment_slot, effective_slot), entry) in EXPECTED_ENTRIES
1862 .iter()
1863 .zip(cache.get_slot_versions_for_tests(&program_id).iter())
1864 {
1865 assert_eq!(entry.deployment_slot, *deployment_slot);
1866 assert_eq!(entry.effective_slot, *effective_slot);
1867 }
1868 }
1869 }
1870
1871 #[test_matrix(
1872 (
1873 ProgramCacheEntryType::Closed,
1874 ProgramCacheEntryType::FailedVerification(get_mock_env()),
1875 new_loaded_entry(get_mock_env()),
1876 ),
1877 (
1878 ProgramCacheEntryType::FailedVerification(get_mock_env()),
1879 ProgramCacheEntryType::Closed,
1880 ProgramCacheEntryType::Unloaded(get_mock_env()),
1881 new_loaded_entry(get_mock_env()),
1882 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1883 )
1884 )]
1885 #[test_matrix(
1886 (
1887 ProgramCacheEntryType::Unloaded(get_mock_env()),
1888 ),
1889 (
1890 ProgramCacheEntryType::FailedVerification(get_mock_env()),
1891 ProgramCacheEntryType::Closed,
1892 ProgramCacheEntryType::Unloaded(get_mock_env()),
1893 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1894 )
1895 )]
1896 #[test_matrix(
1897 (ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),),
1898 (
1899 ProgramCacheEntryType::FailedVerification(get_mock_env()),
1900 ProgramCacheEntryType::Closed,
1901 ProgramCacheEntryType::Unloaded(get_mock_env()),
1902 new_loaded_entry(get_mock_env()),
1903 )
1904 )]
1905 #[should_panic(expected = "Unexpected replacement of an entry")]
1906 fn test_assign_program_failure(old: ProgramCacheEntryType, new: ProgramCacheEntryType) {
1907 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1908 let envs = get_mock_envs();
1909 let program_id = Pubkey::new_unique();
1910 assert!(!cache.assign_program(
1911 &envs,
1912 program_id,
1913 Arc::new(ProgramCacheEntry {
1914 program: old,
1915 account_owner: ProgramCacheEntryOwner::LoaderV2,
1916 account_size: 0,
1917 deployment_slot: 10,
1918 effective_slot: 11,
1919 tx_usage_counter: Arc::default(),
1920 latest_access_slot: AtomicU64::default(),
1921 }),
1922 ));
1923 cache.assign_program(
1924 &envs,
1925 program_id,
1926 Arc::new(ProgramCacheEntry {
1927 program: new,
1928 account_owner: ProgramCacheEntryOwner::LoaderV2,
1929 account_size: 0,
1930 deployment_slot: 10,
1931 effective_slot: 11,
1932 tx_usage_counter: Arc::default(),
1933 latest_access_slot: AtomicU64::default(),
1934 }),
1935 );
1936 }
1937
1938 #[test_case(
1939 ProgramCacheEntryType::Unloaded(Arc::new(BuiltinProgram::new_mock())),
1940 new_loaded_entry(get_mock_env())
1941 )]
1942 #[test_case(
1943 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1944 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock())
1945 )]
1946 fn test_assign_program_success(old: ProgramCacheEntryType, new: ProgramCacheEntryType) {
1947 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1948 let envs = get_mock_envs();
1949 let program_id = Pubkey::new_unique();
1950 assert!(!cache.assign_program(
1951 &envs,
1952 program_id,
1953 Arc::new(ProgramCacheEntry {
1954 program: old,
1955 account_owner: ProgramCacheEntryOwner::LoaderV2,
1956 account_size: 0,
1957 deployment_slot: 10,
1958 effective_slot: 11,
1959 tx_usage_counter: Arc::default(),
1960 latest_access_slot: AtomicU64::default(),
1961 }),
1962 ));
1963 assert!(!cache.assign_program(
1964 &envs,
1965 program_id,
1966 Arc::new(ProgramCacheEntry {
1967 program: new,
1968 account_owner: ProgramCacheEntryOwner::LoaderV2,
1969 account_size: 0,
1970 deployment_slot: 10,
1971 effective_slot: 11,
1972 tx_usage_counter: Arc::default(),
1973 latest_access_slot: AtomicU64::default(),
1974 }),
1975 ));
1976 }
1977
1978 #[test]
1979 fn test_assign_program_removes_entries_in_same_slot() {
1980 let mut cache = ProgramCache::<TestForkGraph>::new(0);
1981 let envs = get_mock_envs();
1982 let program_id = Pubkey::new_unique();
1983 let closed_other_slot = Arc::new(ProgramCacheEntry {
1984 program: ProgramCacheEntryType::Closed,
1985 account_owner: ProgramCacheEntryOwner::LoaderV2,
1986 account_size: 0,
1987 deployment_slot: 9,
1988 effective_slot: 9,
1989 tx_usage_counter: Arc::default(),
1990 latest_access_slot: AtomicU64::default(),
1991 });
1992 let closed_current_slot = Arc::new(ProgramCacheEntry {
1993 program: ProgramCacheEntryType::Closed,
1994 account_owner: ProgramCacheEntryOwner::LoaderV2,
1995 account_size: 0,
1996 deployment_slot: 10,
1997 effective_slot: 10,
1998 tx_usage_counter: Arc::default(),
1999 latest_access_slot: AtomicU64::default(),
2000 });
2001 let loaded_entry_current_env = Arc::new(ProgramCacheEntry {
2002 program: ProgramCacheEntryType::Unloaded(get_mock_env()),
2003 account_owner: ProgramCacheEntryOwner::LoaderV2,
2004 account_size: 0,
2005 deployment_slot: 10,
2006 effective_slot: 11,
2007 tx_usage_counter: Arc::default(),
2008 latest_access_slot: AtomicU64::default(),
2009 });
2010 let loaded_entry_upcoming_env = Arc::new(ProgramCacheEntry {
2011 program: ProgramCacheEntryType::Unloaded(Arc::new(BuiltinProgram::new_mock())),
2012 account_owner: ProgramCacheEntryOwner::LoaderV2,
2013 account_size: 0,
2014 deployment_slot: 10,
2015 effective_slot: 11,
2016 tx_usage_counter: Arc::default(),
2017 latest_access_slot: AtomicU64::default(),
2018 });
2019 assert!(!cache.assign_program(&envs, program_id, closed_other_slot.clone()));
2020 assert!(!cache.assign_program(&envs, program_id, closed_current_slot));
2021 assert!(!cache.assign_program(&envs, program_id, loaded_entry_upcoming_env.clone()));
2022 assert!(!cache.assign_program(&envs, program_id, loaded_entry_current_env.clone()));
2023 assert_eq!(
2025 cache.get_slot_versions_for_tests(&program_id),
2026 &[
2027 closed_other_slot,
2028 loaded_entry_current_env,
2029 loaded_entry_upcoming_env
2030 ]
2031 );
2032 }
2033
2034 #[test]
2035 fn test_tombstone() {
2036 let env = Arc::new(BuiltinProgram::new_mock());
2037 let envs = get_mock_envs();
2038 let tombstone = ProgramCacheEntry::new_tombstone(
2039 0,
2040 ProgramCacheEntryOwner::LoaderV2,
2041 ProgramCacheEntryType::FailedVerification(env.clone()),
2042 );
2043 assert_matches!(
2044 tombstone.program,
2045 ProgramCacheEntryType::FailedVerification(_)
2046 );
2047 assert!(tombstone.is_tombstone());
2048 assert_eq!(tombstone.deployment_slot, 0);
2049 assert_eq!(tombstone.effective_slot, 0);
2050
2051 let tombstone = ProgramCacheEntry::new_tombstone(
2052 100,
2053 ProgramCacheEntryOwner::LoaderV2,
2054 ProgramCacheEntryType::Closed,
2055 );
2056 assert_matches!(tombstone.program, ProgramCacheEntryType::Closed);
2057 assert!(tombstone.is_tombstone());
2058 assert_eq!(tombstone.deployment_slot, 100);
2059 assert_eq!(tombstone.effective_slot, 100);
2060
2061 let mut cache = ProgramCache::<TestForkGraph>::new(0);
2062 let program1 = Pubkey::new_unique();
2063 let tombstone = set_tombstone(
2064 &mut cache,
2065 program1,
2066 10,
2067 ProgramCacheEntryType::FailedVerification(env.clone()),
2068 );
2069 let slot_versions = cache.get_slot_versions_for_tests(&program1);
2070 assert_eq!(slot_versions.len(), 1);
2071 assert!(slot_versions.first().unwrap().is_tombstone());
2072 assert_eq!(tombstone.deployment_slot, 10);
2073 assert_eq!(tombstone.effective_slot, 10);
2074
2075 let program2 = Pubkey::new_unique();
2077 cache.assign_program(&envs, program2, new_test_builtin_entry(50, 51));
2078 let slot_versions = cache.get_slot_versions_for_tests(&program2);
2079 assert_eq!(slot_versions.len(), 1);
2080 assert!(!slot_versions.first().unwrap().is_tombstone());
2081
2082 let tombstone = set_tombstone(
2083 &mut cache,
2084 program2,
2085 60,
2086 ProgramCacheEntryType::FailedVerification(env),
2087 );
2088 let slot_versions = cache.get_slot_versions_for_tests(&program2);
2089 assert_eq!(slot_versions.len(), 2);
2090 assert!(!slot_versions.first().unwrap().is_tombstone());
2091 assert!(slot_versions.get(1).unwrap().is_tombstone());
2092 assert!(tombstone.is_tombstone());
2093 assert_eq!(tombstone.deployment_slot, 60);
2094 assert_eq!(tombstone.effective_slot, 60);
2095 }
2096
2097 struct TestForkGraph {
2098 relation: BlockRelation,
2099 }
2100 impl ForkGraph for TestForkGraph {
2101 fn relationship(&self, _a: Slot, _b: Slot) -> BlockRelation {
2102 self.relation
2103 }
2104 }
2105
2106 #[test]
2107 fn test_prune_empty() {
2108 let mut cache = ProgramCache::<TestForkGraph>::new(0);
2109 let fork_graph = Arc::new(RwLock::new(TestForkGraph {
2110 relation: BlockRelation::Unrelated,
2111 }));
2112
2113 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2114
2115 cache.prune(0, None);
2116 assert!(cache.get_flattened_entries_for_tests().is_empty());
2117
2118 cache.prune(10, None);
2119 assert!(cache.get_flattened_entries_for_tests().is_empty());
2120
2121 let mut cache = ProgramCache::<TestForkGraph>::new(0);
2122 let fork_graph = Arc::new(RwLock::new(TestForkGraph {
2123 relation: BlockRelation::Ancestor,
2124 }));
2125
2126 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2127
2128 cache.prune(0, None);
2129 assert!(cache.get_flattened_entries_for_tests().is_empty());
2130
2131 cache.prune(10, None);
2132 assert!(cache.get_flattened_entries_for_tests().is_empty());
2133
2134 let mut cache = ProgramCache::<TestForkGraph>::new(0);
2135 let fork_graph = Arc::new(RwLock::new(TestForkGraph {
2136 relation: BlockRelation::Descendant,
2137 }));
2138
2139 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2140
2141 cache.prune(0, None);
2142 assert!(cache.get_flattened_entries_for_tests().is_empty());
2143
2144 cache.prune(10, None);
2145 assert!(cache.get_flattened_entries_for_tests().is_empty());
2146
2147 let mut cache = ProgramCache::<TestForkGraph>::new(0);
2148 let fork_graph = Arc::new(RwLock::new(TestForkGraph {
2149 relation: BlockRelation::Unknown,
2150 }));
2151 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2152
2153 cache.prune(0, None);
2154 assert!(cache.get_flattened_entries_for_tests().is_empty());
2155
2156 cache.prune(10, None);
2157 assert!(cache.get_flattened_entries_for_tests().is_empty());
2158 }
2159
2160 #[test]
2161 fn test_prune_different_env() {
2162 let mut cache = ProgramCache::<TestForkGraph>::new(0);
2163 let envs = get_mock_envs();
2164
2165 let fork_graph = Arc::new(RwLock::new(TestForkGraph {
2166 relation: BlockRelation::Ancestor,
2167 }));
2168
2169 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2170
2171 let program1 = Pubkey::new_unique();
2172 cache.assign_program(&envs, program1, new_test_entry(10, 10));
2173 let new_env = Arc::new(BuiltinProgram::new_mock());
2174 let upcoming_environments = Some(ProgramRuntimeEnvironments {
2175 program_runtime_v1: new_env.clone(),
2176 program_runtime_v2: new_env.clone(),
2177 });
2178 let updated_program = Arc::new(ProgramCacheEntry {
2179 program: new_loaded_entry(new_env.clone()),
2180 account_owner: ProgramCacheEntryOwner::LoaderV2,
2181 account_size: 0,
2182 deployment_slot: 20,
2183 effective_slot: 20,
2184 tx_usage_counter: Arc::default(),
2185 latest_access_slot: AtomicU64::default(),
2186 });
2187 cache.assign_program(&envs, program1, updated_program.clone());
2188
2189 assert_eq!(cache.get_slot_versions_for_tests(&program1).len(), 2);
2191
2192 cache.prune(21, None);
2193
2194 assert_eq!(cache.get_slot_versions_for_tests(&program1).len(), 2);
2196
2197 cache.prune(22, upcoming_environments);
2198
2199 assert_eq!(cache.get_slot_versions_for_tests(&program1).len(), 1);
2201
2202 let entry = cache
2203 .get_slot_versions_for_tests(&program1)
2204 .first()
2205 .expect("Failed to get the program")
2206 .clone();
2207 assert_eq!(entry, updated_program);
2209 }
2210
2211 #[derive(Default)]
2212 struct TestForkGraphSpecific {
2213 forks: Vec<Vec<Slot>>,
2214 }
2215
2216 impl TestForkGraphSpecific {
2217 fn insert_fork(&mut self, fork: &[Slot]) {
2218 let mut fork = fork.to_vec();
2219 fork.sort();
2220 self.forks.push(fork)
2221 }
2222 }
2223
2224 impl ForkGraph for TestForkGraphSpecific {
2225 fn relationship(&self, a: Slot, b: Slot) -> BlockRelation {
2226 match self.forks.iter().try_for_each(|fork| {
2227 let relation = fork
2228 .iter()
2229 .position(|x| *x == a)
2230 .and_then(|a_pos| {
2231 fork.iter().position(|x| *x == b).and_then(|b_pos| {
2232 (a_pos == b_pos)
2233 .then_some(BlockRelation::Equal)
2234 .or_else(|| (a_pos < b_pos).then_some(BlockRelation::Ancestor))
2235 .or(Some(BlockRelation::Descendant))
2236 })
2237 })
2238 .unwrap_or(BlockRelation::Unrelated);
2239
2240 if relation != BlockRelation::Unrelated {
2241 return ControlFlow::Break(relation);
2242 }
2243
2244 ControlFlow::Continue(())
2245 }) {
2246 ControlFlow::Break(relation) => relation,
2247 _ => BlockRelation::Unrelated,
2248 }
2249 }
2250 }
2251
2252 fn get_entries_to_load(
2253 cache: &ProgramCache<TestForkGraphSpecific>,
2254 loading_slot: Slot,
2255 keys: &[Pubkey],
2256 ) -> Vec<(Pubkey, ProgramCacheMatchCriteria)> {
2257 let fork_graph = cache.fork_graph.as_ref().unwrap().upgrade().unwrap();
2258 let locked_fork_graph = fork_graph.read().unwrap();
2259 let entries = cache.get_flattened_entries_for_tests();
2260 keys.iter()
2261 .filter_map(|key| {
2262 entries
2263 .iter()
2264 .rev()
2265 .find(|(program_id, entry)| {
2266 program_id == key
2267 && matches!(
2268 locked_fork_graph.relationship(entry.deployment_slot, loading_slot),
2269 BlockRelation::Equal | BlockRelation::Ancestor,
2270 )
2271 })
2272 .map(|(program_id, _entry)| {
2273 (*program_id, ProgramCacheMatchCriteria::NoCriteria)
2274 })
2275 })
2276 .collect()
2277 }
2278
2279 fn match_slot(
2280 extracted: &ProgramCacheForTxBatch,
2281 program: &Pubkey,
2282 deployment_slot: Slot,
2283 working_slot: Slot,
2284 ) -> bool {
2285 assert_eq!(extracted.slot, working_slot);
2286 extracted
2287 .entries
2288 .get(program)
2289 .map(|entry| entry.deployment_slot == deployment_slot)
2290 .unwrap_or(false)
2291 }
2292
2293 fn match_missing(
2294 missing: &[(Pubkey, ProgramCacheMatchCriteria)],
2295 program: &Pubkey,
2296 expected_result: bool,
2297 ) -> bool {
2298 missing.iter().any(|(key, _)| key == program) == expected_result
2299 }
2300
2301 #[test]
2302 fn test_fork_extract_and_prune() {
2303 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
2304 let envs = get_mock_envs();
2305
2306 let mut fork_graph = TestForkGraphSpecific::default();
2322 fork_graph.insert_fork(&[0, 10, 20, 22]);
2323 fork_graph.insert_fork(&[0, 5, 11, 15, 16, 18, 19, 21, 23]);
2324 fork_graph.insert_fork(&[0, 5, 11, 25, 27]);
2325
2326 let fork_graph = Arc::new(RwLock::new(fork_graph));
2327 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2328
2329 let program1 = Pubkey::new_unique();
2330 cache.assign_program(&envs, program1, new_test_entry(0, 1));
2331 cache.assign_program(&envs, program1, new_test_entry(10, 11));
2332 cache.assign_program(&envs, program1, new_test_entry(20, 21));
2333
2334 let program2 = Pubkey::new_unique();
2335 cache.assign_program(&envs, program2, new_test_entry(5, 6));
2336 cache.assign_program(
2337 &envs,
2338 program2,
2339 new_test_entry(11, 11 + DELAY_VISIBILITY_SLOT_OFFSET),
2340 );
2341
2342 let program3 = Pubkey::new_unique();
2343 cache.assign_program(&envs, program3, new_test_entry(25, 26));
2344
2345 let program4 = Pubkey::new_unique();
2346 cache.assign_program(&envs, program4, new_test_entry(0, 1));
2347 cache.assign_program(&envs, program4, new_test_entry(5, 6));
2348 cache.assign_program(
2350 &envs,
2351 program4,
2352 new_test_entry(15, 15 + DELAY_VISIBILITY_SLOT_OFFSET),
2353 );
2354
2355 let mut missing =
2372 get_entries_to_load(&cache, 22, &[program1, program2, program3, program4]);
2373 assert!(match_missing(&missing, &program2, false));
2374 assert!(match_missing(&missing, &program3, false));
2375 let mut extracted = ProgramCacheForTxBatch::new(22);
2376 cache.extract(&mut missing, &mut extracted, &envs, true, true);
2377 assert!(match_slot(&extracted, &program1, 20, 22));
2378 assert!(match_slot(&extracted, &program4, 0, 22));
2379
2380 let mut missing =
2382 get_entries_to_load(&cache, 15, &[program1, program2, program3, program4]);
2383 assert!(match_missing(&missing, &program3, false));
2384 let mut extracted = ProgramCacheForTxBatch::new(15);
2385 cache.extract(&mut missing, &mut extracted, &envs, true, true);
2386 assert!(match_slot(&extracted, &program1, 0, 15));
2387 assert!(match_slot(&extracted, &program2, 11, 15));
2388 let tombstone = extracted
2391 .find(&program4)
2392 .expect("Failed to find the tombstone");
2393 assert_matches!(tombstone.program, ProgramCacheEntryType::DelayVisibility);
2394 assert_eq!(tombstone.deployment_slot, 15);
2395
2396 let mut missing =
2398 get_entries_to_load(&cache, 18, &[program1, program2, program3, program4]);
2399 assert!(match_missing(&missing, &program3, false));
2400 let mut extracted = ProgramCacheForTxBatch::new(18);
2401 cache.extract(&mut missing, &mut extracted, &envs, true, true);
2402 assert!(match_slot(&extracted, &program1, 0, 18));
2403 assert!(match_slot(&extracted, &program2, 11, 18));
2404 assert!(match_slot(&extracted, &program4, 15, 18));
2406
2407 let mut missing =
2409 get_entries_to_load(&cache, 23, &[program1, program2, program3, program4]);
2410 assert!(match_missing(&missing, &program3, false));
2411 let mut extracted = ProgramCacheForTxBatch::new(23);
2412 cache.extract(&mut missing, &mut extracted, &envs, true, true);
2413 assert!(match_slot(&extracted, &program1, 0, 23));
2414 assert!(match_slot(&extracted, &program2, 11, 23));
2415 assert!(match_slot(&extracted, &program4, 15, 23));
2417
2418 let mut missing =
2420 get_entries_to_load(&cache, 11, &[program1, program2, program3, program4]);
2421 assert!(match_missing(&missing, &program3, false));
2422 let mut extracted = ProgramCacheForTxBatch::new(11);
2423 cache.extract(&mut missing, &mut extracted, &envs, true, true);
2424 assert!(match_slot(&extracted, &program1, 0, 11));
2425 let tombstone = extracted
2427 .find(&program2)
2428 .expect("Failed to find the tombstone");
2429 assert_matches!(tombstone.program, ProgramCacheEntryType::DelayVisibility);
2430 assert_eq!(tombstone.deployment_slot, 11);
2431 assert!(match_slot(&extracted, &program4, 5, 11));
2432
2433 cache.prune(5, None);
2434
2435 let mut missing =
2452 get_entries_to_load(&cache, 21, &[program1, program2, program3, program4]);
2453 assert!(match_missing(&missing, &program3, false));
2454 let mut extracted = ProgramCacheForTxBatch::new(21);
2455 cache.extract(&mut missing, &mut extracted, &envs, true, true);
2456 assert!(match_slot(&extracted, &program1, 0, 21));
2458 assert!(match_slot(&extracted, &program2, 11, 21));
2459 assert!(match_slot(&extracted, &program4, 15, 21));
2460
2461 let mut missing =
2463 get_entries_to_load(&cache, 27, &[program1, program2, program3, program4]);
2464 let mut extracted = ProgramCacheForTxBatch::new(27);
2465 cache.extract(&mut missing, &mut extracted, &envs, true, true);
2466 assert!(match_slot(&extracted, &program1, 0, 27));
2467 assert!(match_slot(&extracted, &program2, 11, 27));
2468 assert!(match_slot(&extracted, &program3, 25, 27));
2469 assert!(match_slot(&extracted, &program4, 5, 27));
2470
2471 cache.prune(15, None);
2472
2473 let mut missing =
2490 get_entries_to_load(&cache, 23, &[program1, program2, program3, program4]);
2491 assert!(match_missing(&missing, &program3, false));
2492 let mut extracted = ProgramCacheForTxBatch::new(23);
2493 cache.extract(&mut missing, &mut extracted, &envs, true, true);
2494 assert!(match_slot(&extracted, &program1, 0, 23));
2495 assert!(match_slot(&extracted, &program2, 11, 23));
2496 assert!(match_slot(&extracted, &program4, 15, 23));
2497 }
2498
2499 #[test]
2500 fn test_extract_using_deployment_slot() {
2501 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
2502 let envs = get_mock_envs();
2503
2504 let mut fork_graph = TestForkGraphSpecific::default();
2520 fork_graph.insert_fork(&[0, 10, 20, 22]);
2521 fork_graph.insert_fork(&[0, 5, 11, 12, 15, 16, 18, 19, 21, 23]);
2522 fork_graph.insert_fork(&[0, 5, 11, 25, 27]);
2523
2524 let fork_graph = Arc::new(RwLock::new(fork_graph));
2525 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2526
2527 let program1 = Pubkey::new_unique();
2528 cache.assign_program(&envs, program1, new_test_entry(0, 1));
2529 cache.assign_program(&envs, program1, new_test_entry(20, 21));
2530
2531 let program2 = Pubkey::new_unique();
2532 cache.assign_program(&envs, program2, new_test_entry(5, 6));
2533 cache.assign_program(&envs, program2, new_test_entry(11, 12));
2534
2535 let program3 = Pubkey::new_unique();
2536 cache.assign_program(&envs, program3, new_test_entry(25, 26));
2537
2538 let mut missing = get_entries_to_load(&cache, 12, &[program1, program2, program3]);
2540 assert!(match_missing(&missing, &program3, false));
2541 let mut extracted = ProgramCacheForTxBatch::new(12);
2542 cache.extract(&mut missing, &mut extracted, &envs, true, true);
2543 assert!(match_slot(&extracted, &program1, 0, 12));
2544 assert!(match_slot(&extracted, &program2, 11, 12));
2545
2546 let mut missing = get_entries_to_load(&cache, 12, &[program1, program2, program3]);
2548 missing.get_mut(0).unwrap().1 = ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(5);
2549 missing.get_mut(1).unwrap().1 = ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(5);
2550 assert!(match_missing(&missing, &program3, false));
2551 let mut extracted = ProgramCacheForTxBatch::new(12);
2552 cache.extract(&mut missing, &mut extracted, &envs, true, true);
2553 assert!(match_missing(&missing, &program1, true));
2554 assert!(match_slot(&extracted, &program2, 11, 12));
2555 }
2556
2557 #[test]
2558 fn test_extract_unloaded() {
2559 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
2560 let envs = get_mock_envs();
2561
2562 let mut fork_graph = TestForkGraphSpecific::default();
2578 fork_graph.insert_fork(&[0, 10, 20, 22]);
2579 fork_graph.insert_fork(&[0, 5, 11, 15, 16, 19, 21, 23]);
2580 fork_graph.insert_fork(&[0, 5, 11, 25, 27]);
2581
2582 let fork_graph = Arc::new(RwLock::new(fork_graph));
2583 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2584
2585 let program1 = Pubkey::new_unique();
2586 cache.assign_program(&envs, program1, new_test_entry(0, 1));
2587 cache.assign_program(&envs, program1, new_test_entry(20, 21));
2588
2589 let program2 = Pubkey::new_unique();
2590 cache.assign_program(&envs, program2, new_test_entry(5, 6));
2591 cache.assign_program(&envs, program2, new_test_entry(11, 12));
2592
2593 let program3 = Pubkey::new_unique();
2594 let _ = insert_unloaded_entry(&mut cache, program3, 25);
2596
2597 cache.assign_program(
2601 &envs,
2602 program3,
2603 Arc::new(
2604 new_test_entry(20, 21)
2605 .to_unloaded()
2606 .expect("Failed to create unloaded program"),
2607 ),
2608 );
2609
2610 let mut missing = get_entries_to_load(&cache, 19, &[program1, program2, program3]);
2612 assert!(match_missing(&missing, &program3, false));
2613 let mut extracted = ProgramCacheForTxBatch::new(19);
2614 cache.extract(&mut missing, &mut extracted, &envs, true, true);
2615 assert!(match_slot(&extracted, &program1, 0, 19));
2616 assert!(match_slot(&extracted, &program2, 11, 19));
2617
2618 let mut missing = get_entries_to_load(&cache, 27, &[program1, program2, program3]);
2620 let mut extracted = ProgramCacheForTxBatch::new(27);
2621 cache.extract(&mut missing, &mut extracted, &envs, true, true);
2622 assert!(match_slot(&extracted, &program1, 0, 27));
2623 assert!(match_slot(&extracted, &program2, 11, 27));
2624 assert!(match_missing(&missing, &program3, true));
2625
2626 let mut missing = get_entries_to_load(&cache, 22, &[program1, program2, program3]);
2628 assert!(match_missing(&missing, &program2, false));
2629 let mut extracted = ProgramCacheForTxBatch::new(22);
2630 cache.extract(&mut missing, &mut extracted, &envs, true, true);
2631 assert!(match_slot(&extracted, &program1, 20, 22));
2632 assert!(match_missing(&missing, &program3, true));
2633 }
2634
2635 #[test]
2636 fn test_extract_different_environment() {
2637 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
2638 let envs = get_mock_envs();
2639 let other_envs = ProgramRuntimeEnvironments {
2640 program_runtime_v1: Arc::new(BuiltinProgram::new_mock()),
2641 program_runtime_v2: Arc::new(BuiltinProgram::new_mock()),
2642 };
2643
2644 let mut fork_graph = TestForkGraphSpecific::default();
2654 fork_graph.insert_fork(&[0, 10, 20, 22]);
2655
2656 let fork_graph = Arc::new(RwLock::new(fork_graph));
2657 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2658
2659 let program1 = Pubkey::new_unique();
2660 cache.assign_program(
2661 &envs,
2662 program1,
2663 Arc::new(ProgramCacheEntry::new_tombstone(
2664 10,
2665 ProgramCacheEntryOwner::LoaderV3,
2666 ProgramCacheEntryType::Closed,
2667 )),
2668 );
2669 cache.assign_program(&envs, program1, new_test_entry(20, 21));
2670
2671 let mut missing = get_entries_to_load(&cache, 22, &[program1]);
2673 let mut extracted = ProgramCacheForTxBatch::new(22);
2674 cache.extract(&mut missing, &mut extracted, &envs, true, true);
2675 assert!(match_slot(&extracted, &program1, 20, 22));
2676
2677 let mut missing = get_entries_to_load(&cache, 22, &[program1]);
2679 let mut extracted = ProgramCacheForTxBatch::new(22);
2680 cache.extract(&mut missing, &mut extracted, &other_envs, true, true);
2681 assert!(match_missing(&missing, &program1, true));
2682 }
2683
2684 #[test]
2685 fn test_extract_nonexistent() {
2686 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
2687 let envs = get_mock_envs();
2688 let fork_graph = TestForkGraphSpecific::default();
2689 let fork_graph = Arc::new(RwLock::new(fork_graph));
2690 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2691
2692 let program1 = Pubkey::new_unique();
2693 let mut missing = vec![(program1, ProgramCacheMatchCriteria::NoCriteria)];
2694 let mut extracted = ProgramCacheForTxBatch::new(0);
2695 cache.extract(&mut missing, &mut extracted, &envs, true, true);
2696 assert!(match_missing(&missing, &program1, true));
2697 }
2698
2699 #[test]
2700 fn test_unloaded() {
2701 let mut cache = ProgramCache::<TestForkGraph>::new(0);
2702 let envs = get_mock_envs();
2703 for program_cache_entry_type in [
2704 ProgramCacheEntryType::FailedVerification(get_mock_env()),
2705 ProgramCacheEntryType::Closed,
2706 ProgramCacheEntryType::Unloaded(get_mock_env()),
2707 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
2708 ] {
2709 let entry = Arc::new(ProgramCacheEntry {
2710 program: program_cache_entry_type,
2711 account_owner: ProgramCacheEntryOwner::LoaderV2,
2712 account_size: 0,
2713 deployment_slot: 0,
2714 effective_slot: 0,
2715 tx_usage_counter: Arc::default(),
2716 latest_access_slot: AtomicU64::default(),
2717 });
2718 assert!(entry.to_unloaded().is_none());
2719
2720 let program_id = Pubkey::new_unique();
2722 cache.assign_program(&envs, program_id, entry.clone());
2723 cache.unload_program_entry(&program_id, &entry);
2724 assert_eq!(cache.get_slot_versions_for_tests(&program_id).len(), 1);
2725 assert!(cache.stats.evictions.is_empty());
2726 }
2727
2728 let entry = new_test_entry_with_usage(1, 2, AtomicU64::new(3));
2729 let unloaded_entry = entry.to_unloaded().unwrap();
2730 assert_eq!(unloaded_entry.deployment_slot, 1);
2731 assert_eq!(unloaded_entry.effective_slot, 2);
2732 assert_eq!(unloaded_entry.latest_access_slot.load(Ordering::Relaxed), 1);
2733 assert_eq!(unloaded_entry.tx_usage_counter.load(Ordering::Relaxed), 3);
2734
2735 let program_id = Pubkey::new_unique();
2737 cache.assign_program(&envs, program_id, entry.clone());
2738 cache.unload_program_entry(&program_id, &entry);
2739 assert!(cache.stats.evictions.contains_key(&program_id));
2740 }
2741
2742 #[test]
2743 fn test_fork_prune_find_first_ancestor() {
2744 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
2745 let envs = get_mock_envs();
2746
2747 let mut fork_graph = TestForkGraphSpecific::default();
2758 fork_graph.insert_fork(&[0, 10, 20]);
2759 fork_graph.insert_fork(&[0, 5]);
2760 let fork_graph = Arc::new(RwLock::new(fork_graph));
2761 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2762
2763 let program1 = Pubkey::new_unique();
2764 cache.assign_program(&envs, program1, new_test_entry(0, 1));
2765 cache.assign_program(&envs, program1, new_test_entry(5, 6));
2766
2767 cache.prune(10, None);
2768
2769 let mut missing = get_entries_to_load(&cache, 20, &[program1]);
2770 let mut extracted = ProgramCacheForTxBatch::new(20);
2771 cache.extract(&mut missing, &mut extracted, &envs, true, true);
2772
2773 assert_eq!(
2775 extracted
2776 .find(&program1)
2777 .expect("Did not find the program")
2778 .deployment_slot,
2779 0
2780 );
2781 }
2782
2783 #[test]
2784 fn test_prune_by_deployment_slot() {
2785 let mut cache = ProgramCache::<TestForkGraphSpecific>::new(0);
2786 let envs = get_mock_envs();
2787
2788 let mut fork_graph = TestForkGraphSpecific::default();
2799 fork_graph.insert_fork(&[0, 10, 20]);
2800 fork_graph.insert_fork(&[0, 5, 6]);
2801 let fork_graph = Arc::new(RwLock::new(fork_graph));
2802 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2803
2804 let program1 = Pubkey::new_unique();
2805 cache.assign_program(&envs, program1, new_test_entry(0, 1));
2806 cache.assign_program(&envs, program1, new_test_entry(5, 6));
2807
2808 let program2 = Pubkey::new_unique();
2809 cache.assign_program(&envs, program2, new_test_entry(10, 11));
2810
2811 let mut missing = get_entries_to_load(&cache, 20, &[program1, program2]);
2812 let mut extracted = ProgramCacheForTxBatch::new(20);
2813 cache.extract(&mut missing, &mut extracted, &envs, true, true);
2814 assert!(match_slot(&extracted, &program1, 0, 20));
2815 assert!(match_slot(&extracted, &program2, 10, 20));
2816
2817 let mut missing = get_entries_to_load(&cache, 6, &[program1, program2]);
2818 assert!(match_missing(&missing, &program2, false));
2819 let mut extracted = ProgramCacheForTxBatch::new(6);
2820 cache.extract(&mut missing, &mut extracted, &envs, true, true);
2821 assert!(match_slot(&extracted, &program1, 5, 6));
2822
2823 cache.prune_by_deployment_slot(5);
2826
2827 let mut missing = get_entries_to_load(&cache, 20, &[program1, program2]);
2828 let mut extracted = ProgramCacheForTxBatch::new(20);
2829 cache.extract(&mut missing, &mut extracted, &envs, true, true);
2830 assert!(match_slot(&extracted, &program1, 0, 20));
2831 assert!(match_slot(&extracted, &program2, 10, 20));
2832
2833 let mut missing = get_entries_to_load(&cache, 6, &[program1, program2]);
2834 assert!(match_missing(&missing, &program2, false));
2835 let mut extracted = ProgramCacheForTxBatch::new(6);
2836 cache.extract(&mut missing, &mut extracted, &envs, true, true);
2837 assert!(match_slot(&extracted, &program1, 0, 6));
2838
2839 cache.prune_by_deployment_slot(10);
2842
2843 let mut missing = get_entries_to_load(&cache, 20, &[program1, program2]);
2844 assert!(match_missing(&missing, &program2, false));
2845 let mut extracted = ProgramCacheForTxBatch::new(20);
2846 cache.extract(&mut missing, &mut extracted, &envs, true, true);
2847 assert!(match_slot(&extracted, &program1, 0, 20));
2848 }
2849
2850 #[test]
2851 fn test_usable_entries_for_slot() {
2852 ProgramCache::<TestForkGraph>::new(0);
2853 let tombstone = Arc::new(ProgramCacheEntry::new_tombstone(
2854 0,
2855 ProgramCacheEntryOwner::LoaderV2,
2856 ProgramCacheEntryType::Closed,
2857 ));
2858
2859 assert!(ProgramCache::<TestForkGraph>::matches_criteria(
2860 &tombstone,
2861 &ProgramCacheMatchCriteria::NoCriteria
2862 ));
2863
2864 assert!(ProgramCache::<TestForkGraph>::matches_criteria(
2865 &tombstone,
2866 &ProgramCacheMatchCriteria::Tombstone
2867 ));
2868
2869 assert!(ProgramCache::<TestForkGraph>::matches_criteria(
2870 &tombstone,
2871 &ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(0)
2872 ));
2873
2874 assert!(!ProgramCache::<TestForkGraph>::matches_criteria(
2875 &tombstone,
2876 &ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(1)
2877 ));
2878
2879 let program = new_test_entry(0, 1);
2880
2881 assert!(ProgramCache::<TestForkGraph>::matches_criteria(
2882 &program,
2883 &ProgramCacheMatchCriteria::NoCriteria
2884 ));
2885
2886 assert!(!ProgramCache::<TestForkGraph>::matches_criteria(
2887 &program,
2888 &ProgramCacheMatchCriteria::Tombstone
2889 ));
2890
2891 assert!(ProgramCache::<TestForkGraph>::matches_criteria(
2892 &program,
2893 &ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(0)
2894 ));
2895
2896 assert!(!ProgramCache::<TestForkGraph>::matches_criteria(
2897 &program,
2898 &ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(1)
2899 ));
2900
2901 let program = Arc::new(new_test_entry_with_usage(0, 1, AtomicU64::default()));
2902
2903 assert!(ProgramCache::<TestForkGraph>::matches_criteria(
2904 &program,
2905 &ProgramCacheMatchCriteria::NoCriteria
2906 ));
2907
2908 assert!(!ProgramCache::<TestForkGraph>::matches_criteria(
2909 &program,
2910 &ProgramCacheMatchCriteria::Tombstone
2911 ));
2912
2913 assert!(ProgramCache::<TestForkGraph>::matches_criteria(
2914 &program,
2915 &ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(0)
2916 ));
2917
2918 assert!(!ProgramCache::<TestForkGraph>::matches_criteria(
2919 &program,
2920 &ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(1)
2921 ));
2922 }
2923}