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>>>;
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 }
295}
296
297impl PartialEq for ProgramCacheEntry {
298 fn eq(&self, other: &Self) -> bool {
299 self.effective_slot == other.effective_slot
300 && self.deployment_slot == other.deployment_slot
301 && self.is_tombstone() == other.is_tombstone()
302 }
303}
304
305impl ProgramCacheEntry {
306 pub fn new(
308 loader_key: &Pubkey,
309 program_runtime_environment: ProgramRuntimeEnvironment,
310 deployment_slot: Slot,
311 effective_slot: Slot,
312 elf_bytes: &[u8],
313 account_size: usize,
314 #[cfg(feature = "metrics")] metrics: &mut LoadProgramMetrics,
315 ) -> Result<Self, Box<dyn std::error::Error>> {
316 Self::new_internal(
317 loader_key,
318 program_runtime_environment,
319 deployment_slot,
320 effective_slot,
321 elf_bytes,
322 account_size,
323 #[cfg(feature = "metrics")]
324 metrics,
325 false, )
327 }
328
329 pub unsafe fn reload(
338 loader_key: &Pubkey,
339 program_runtime_environment: Arc<BuiltinProgram<InvokeContext<'static>>>,
340 deployment_slot: Slot,
341 effective_slot: Slot,
342 elf_bytes: &[u8],
343 account_size: usize,
344 #[cfg(feature = "metrics")] metrics: &mut LoadProgramMetrics,
345 ) -> Result<Self, Box<dyn std::error::Error>> {
346 Self::new_internal(
347 loader_key,
348 program_runtime_environment,
349 deployment_slot,
350 effective_slot,
351 elf_bytes,
352 account_size,
353 #[cfg(feature = "metrics")]
354 metrics,
355 true, )
357 }
358
359 fn new_internal(
360 loader_key: &Pubkey,
361 program_runtime_environment: Arc<BuiltinProgram<InvokeContext<'static>>>,
362 deployment_slot: Slot,
363 effective_slot: Slot,
364 elf_bytes: &[u8],
365 account_size: usize,
366 #[cfg(feature = "metrics")] metrics: &mut LoadProgramMetrics,
367 reloading: bool,
368 ) -> Result<Self, Box<dyn std::error::Error>> {
369 #[cfg(feature = "metrics")]
370 let load_elf_time = Measure::start("load_elf_time");
371 #[allow(unused_mut)]
374 let mut executable = Executable::load(elf_bytes, program_runtime_environment.clone())?;
375 #[cfg(feature = "metrics")]
376 {
377 metrics.load_elf_us = load_elf_time.end_as_us();
378 }
379
380 if !reloading {
381 #[cfg(feature = "metrics")]
382 let verify_code_time = Measure::start("verify_code_time");
383 executable.verify::<RequisiteVerifier>()?;
384 #[cfg(feature = "metrics")]
385 {
386 metrics.verify_code_us = verify_code_time.end_as_us();
387 }
388 }
389
390 #[cfg(all(not(target_os = "windows"), target_arch = "x86_64"))]
391 {
392 #[cfg(feature = "metrics")]
393 let jit_compile_time = Measure::start("jit_compile_time");
394 executable.jit_compile()?;
395 #[cfg(feature = "metrics")]
396 {
397 metrics.jit_compile_us = jit_compile_time.end_as_us();
398 }
399 }
400
401 Ok(Self {
402 deployment_slot,
403 account_owner: ProgramCacheEntryOwner::try_from(loader_key).unwrap(),
404 account_size,
405 effective_slot,
406 tx_usage_counter: Arc::<AtomicU64>::default(),
407 program: ProgramCacheEntryType::Loaded(executable),
408 latest_access_slot: AtomicU64::new(0),
409 })
410 }
411
412 pub fn to_unloaded(&self) -> Option<Self> {
413 match &self.program {
414 ProgramCacheEntryType::Loaded(_) => {}
415 ProgramCacheEntryType::FailedVerification(_)
416 | ProgramCacheEntryType::Closed
417 | ProgramCacheEntryType::DelayVisibility
418 | ProgramCacheEntryType::Unloaded(_)
419 | ProgramCacheEntryType::Builtin(_) => {
420 return None;
421 }
422 }
423 Some(Self {
424 program: ProgramCacheEntryType::Unloaded(self.program.get_environment()?.clone()),
425 account_owner: self.account_owner,
426 account_size: self.account_size,
427 deployment_slot: self.deployment_slot,
428 effective_slot: self.effective_slot,
429 tx_usage_counter: self.tx_usage_counter.clone(),
430 latest_access_slot: AtomicU64::new(self.latest_access_slot.load(Ordering::Relaxed)),
431 })
432 }
433
434 pub fn new_builtin(
436 deployment_slot: Slot,
437 account_size: usize,
438 builtin_function: BuiltinFunctionWithContext,
439 ) -> Self {
440 let mut program = BuiltinProgram::new_builtin();
441 program
442 .register_function("entrypoint", builtin_function)
443 .unwrap();
444 Self {
445 deployment_slot,
446 account_owner: ProgramCacheEntryOwner::NativeLoader,
447 account_size,
448 effective_slot: deployment_slot,
449 tx_usage_counter: Arc::<AtomicU64>::default(),
450 program: ProgramCacheEntryType::Builtin(program),
451 latest_access_slot: AtomicU64::new(0),
452 }
453 }
454
455 pub fn new_tombstone(
456 slot: Slot,
457 account_owner: ProgramCacheEntryOwner,
458 reason: ProgramCacheEntryType,
459 ) -> Self {
460 Self::new_tombstone_with_usage_counter(
461 slot,
462 account_owner,
463 reason,
464 Arc::<AtomicU64>::default(),
465 )
466 }
467
468 pub fn new_tombstone_with_usage_counter(
469 slot: Slot,
470 account_owner: ProgramCacheEntryOwner,
471 reason: ProgramCacheEntryType,
472 tx_usage_counter: Arc<AtomicU64>,
473 ) -> Self {
474 let tombstone = Self {
475 program: reason,
476 account_owner,
477 account_size: 0,
478 deployment_slot: slot,
479 effective_slot: slot,
480 tx_usage_counter,
481 latest_access_slot: AtomicU64::new(0),
482 };
483 debug_assert!(tombstone.is_tombstone());
484 tombstone
485 }
486
487 pub fn is_tombstone(&self) -> bool {
488 matches!(
489 self.program,
490 ProgramCacheEntryType::FailedVerification(_)
491 | ProgramCacheEntryType::Closed
492 | ProgramCacheEntryType::DelayVisibility
493 )
494 }
495
496 fn is_implicit_delay_visibility_tombstone(&self, slot: Slot) -> bool {
497 !matches!(self.program, ProgramCacheEntryType::Builtin(_))
498 && self.effective_slot.saturating_sub(self.deployment_slot)
499 == DELAY_VISIBILITY_SLOT_OFFSET
500 && slot >= self.deployment_slot
501 && slot < self.effective_slot
502 }
503
504 pub fn update_access_slot(&self, slot: Slot) {
505 let _ = self.latest_access_slot.fetch_max(slot, Ordering::Relaxed);
506 }
507
508 pub fn decayed_usage_counter(&self, now: Slot) -> u64 {
509 let last_access = self.latest_access_slot.load(Ordering::Relaxed);
510 let decaying_for = std::cmp::min(63, now.saturating_sub(last_access));
512 self.tx_usage_counter.load(Ordering::Relaxed) >> decaying_for
513 }
514
515 pub fn account_owner(&self) -> Pubkey {
516 self.account_owner.into()
517 }
518}
519
520#[derive(Clone, Debug)]
524pub struct ProgramRuntimeEnvironments {
525 pub program_runtime_v1: ProgramRuntimeEnvironment,
527 pub program_runtime_v2: ProgramRuntimeEnvironment,
529}
530
531impl Default for ProgramRuntimeEnvironments {
532 fn default() -> Self {
533 let empty_loader = Arc::new(BuiltinProgram::new_loader(Config::default()));
534 Self {
535 program_runtime_v1: empty_loader.clone(),
536 program_runtime_v2: empty_loader,
537 }
538 }
539}
540
541#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
542pub struct LoadingTaskCookie(u64);
543
544impl LoadingTaskCookie {
545 fn new() -> Self {
546 Self(0)
547 }
548
549 fn update(&mut self) {
550 let LoadingTaskCookie(cookie) = self;
551 *cookie = cookie.wrapping_add(1);
552 }
553}
554
555#[derive(Debug, Default)]
557pub struct LoadingTaskWaiter {
558 cookie: Mutex<LoadingTaskCookie>,
559 cond: Condvar,
560}
561
562impl LoadingTaskWaiter {
563 pub fn new() -> Self {
564 Self {
565 cookie: Mutex::new(LoadingTaskCookie::new()),
566 cond: Condvar::new(),
567 }
568 }
569
570 pub fn cookie(&self) -> LoadingTaskCookie {
571 *self.cookie.lock().unwrap()
572 }
573
574 pub fn notify(&self) {
575 let mut cookie = self.cookie.lock().unwrap();
576 cookie.update();
577 self.cond.notify_all();
578 }
579
580 pub fn wait(&self, cookie: LoadingTaskCookie) -> LoadingTaskCookie {
581 let cookie_guard = self.cookie.lock().unwrap();
582 *self
583 .cond
584 .wait_while(cookie_guard, |current_cookie| *current_cookie == cookie)
585 .unwrap()
586 }
587}
588
589#[derive(Debug)]
590enum IndexImplementation {
591 V1 {
593 entries: HashMap<Pubkey, Vec<Arc<ProgramCacheEntry>>>,
598 loading_entries: Mutex<HashMap<Pubkey, (Slot, thread::ThreadId)>>,
607 },
608}
609
610pub struct ProgramCache<FG: ForkGraph> {
626 index: IndexImplementation,
628 pub latest_root_slot: Slot,
630 pub latest_root_epoch: Epoch,
632 pub environments: ProgramRuntimeEnvironments,
634 pub upcoming_environments: Option<ProgramRuntimeEnvironments>,
640 pub programs_to_recompile: Vec<(Pubkey, Arc<ProgramCacheEntry>)>,
642 pub stats: ProgramCacheStats,
644 pub fork_graph: Option<Weak<RwLock<FG>>>,
646 pub loading_task_waiter: Arc<LoadingTaskWaiter>,
648}
649
650impl<FG: ForkGraph> Debug for ProgramCache<FG> {
651 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
652 f.debug_struct("ProgramCache")
653 .field("root slot", &self.latest_root_slot)
654 .field("root epoch", &self.latest_root_epoch)
655 .field("stats", &self.stats)
656 .field("index", &self.index)
657 .finish()
658 }
659}
660
661#[derive(Clone, Debug, Default)]
667pub struct ProgramCacheForTxBatch {
668 entries: HashMap<Pubkey, Arc<ProgramCacheEntry>>,
671 modified_entries: HashMap<Pubkey, Arc<ProgramCacheEntry>>,
673 slot: Slot,
674 pub environments: ProgramRuntimeEnvironments,
675 pub upcoming_environments: Option<ProgramRuntimeEnvironments>,
683 pub latest_root_epoch: Epoch,
685 pub hit_max_limit: bool,
686 pub loaded_missing: bool,
687 pub merged_modified: bool,
688}
689
690impl ProgramCacheForTxBatch {
691 pub fn new(
692 slot: Slot,
693 environments: ProgramRuntimeEnvironments,
694 upcoming_environments: Option<ProgramRuntimeEnvironments>,
695 latest_root_epoch: Epoch,
696 ) -> Self {
697 Self {
698 entries: HashMap::new(),
699 modified_entries: HashMap::new(),
700 slot,
701 environments,
702 upcoming_environments,
703 latest_root_epoch,
704 hit_max_limit: false,
705 loaded_missing: false,
706 merged_modified: false,
707 }
708 }
709
710 pub fn new_from_cache<FG: ForkGraph>(
711 slot: Slot,
712 epoch: Epoch,
713 cache: &ProgramCache<FG>,
714 ) -> Self {
715 Self {
716 entries: HashMap::new(),
717 modified_entries: HashMap::new(),
718 slot,
719 environments: cache.get_environments_for_epoch(epoch),
720 upcoming_environments: cache.get_upcoming_environments_for_epoch(epoch),
721 latest_root_epoch: cache.latest_root_epoch,
722 hit_max_limit: false,
723 loaded_missing: false,
724 merged_modified: false,
725 }
726 }
727
728 pub fn get_environments_for_epoch(&self, epoch: Epoch) -> &ProgramRuntimeEnvironments {
730 if epoch != self.latest_root_epoch {
731 if let Some(upcoming_environments) = self.upcoming_environments.as_ref() {
732 return upcoming_environments;
733 }
734 }
735 &self.environments
736 }
737
738 pub fn replenish(
744 &mut self,
745 key: Pubkey,
746 entry: Arc<ProgramCacheEntry>,
747 ) -> (bool, Arc<ProgramCacheEntry>) {
748 (self.entries.insert(key, entry.clone()).is_some(), entry)
749 }
750
751 pub fn store_modified_entry(&mut self, key: Pubkey, entry: Arc<ProgramCacheEntry>) {
754 self.modified_entries.insert(key, entry);
755 }
756
757 pub fn drain_modified_entries(&mut self) -> HashMap<Pubkey, Arc<ProgramCacheEntry>> {
760 std::mem::take(&mut self.modified_entries)
761 }
762
763 pub fn find(&self, key: &Pubkey) -> Option<Arc<ProgramCacheEntry>> {
764 self.modified_entries
768 .get(key)
769 .or_else(|| self.entries.get(key))
770 .map(|entry| {
771 if entry.is_implicit_delay_visibility_tombstone(self.slot) {
772 Arc::new(ProgramCacheEntry::new_tombstone_with_usage_counter(
776 entry.deployment_slot,
777 entry.account_owner,
778 ProgramCacheEntryType::DelayVisibility,
779 entry.tx_usage_counter.clone(),
780 ))
781 } else {
782 entry.clone()
783 }
784 })
785 }
786
787 pub fn slot(&self) -> Slot {
788 self.slot
789 }
790
791 pub fn set_slot_for_tests(&mut self, slot: Slot) {
792 self.slot = slot;
793 }
794
795 pub fn merge(&mut self, modified_entries: &HashMap<Pubkey, Arc<ProgramCacheEntry>>) {
796 modified_entries.iter().for_each(|(key, entry)| {
797 self.merged_modified = true;
798 self.replenish(*key, entry.clone());
799 })
800 }
801
802 pub fn is_empty(&self) -> bool {
803 self.entries.is_empty()
804 }
805}
806
807pub enum ProgramCacheMatchCriteria {
808 DeployedOnOrAfterSlot(Slot),
809 Tombstone,
810 NoCriteria,
811}
812
813impl<FG: ForkGraph> ProgramCache<FG> {
814 pub fn new(root_slot: Slot, root_epoch: Epoch) -> Self {
815 Self {
816 index: IndexImplementation::V1 {
817 entries: HashMap::new(),
818 loading_entries: Mutex::new(HashMap::new()),
819 },
820 latest_root_slot: root_slot,
821 latest_root_epoch: root_epoch,
822 environments: ProgramRuntimeEnvironments::default(),
823 upcoming_environments: None,
824 programs_to_recompile: Vec::default(),
825 stats: ProgramCacheStats::default(),
826 fork_graph: None,
827 loading_task_waiter: Arc::new(LoadingTaskWaiter::default()),
828 }
829 }
830
831 pub fn set_fork_graph(&mut self, fork_graph: Weak<RwLock<FG>>) {
832 self.fork_graph = Some(fork_graph);
833 }
834
835 pub fn get_environments_for_epoch(&self, epoch: Epoch) -> ProgramRuntimeEnvironments {
837 if epoch != self.latest_root_epoch {
838 if let Some(upcoming_environments) = self.upcoming_environments.as_ref() {
839 return upcoming_environments.clone();
840 }
841 }
842 self.environments.clone()
843 }
844
845 pub fn get_upcoming_environments_for_epoch(
847 &self,
848 epoch: Epoch,
849 ) -> Option<ProgramRuntimeEnvironments> {
850 if epoch == self.latest_root_epoch {
851 return self.upcoming_environments.clone();
852 }
853 None
854 }
855
856 pub fn assign_program(&mut self, key: Pubkey, entry: Arc<ProgramCacheEntry>) -> bool {
859 debug_assert!(!matches!(
860 &entry.program,
861 ProgramCacheEntryType::DelayVisibility
862 ));
863 fn is_current_env(
867 environments: &ProgramRuntimeEnvironments,
868 env_opt: Option<&ProgramRuntimeEnvironment>,
869 ) -> bool {
870 env_opt
871 .map(|env| {
872 Arc::ptr_eq(env, &environments.program_runtime_v1)
873 || Arc::ptr_eq(env, &environments.program_runtime_v2)
874 })
875 .unwrap_or(true)
876 }
877 match &mut self.index {
878 IndexImplementation::V1 { entries, .. } => {
879 let slot_versions = &mut entries.entry(key).or_default();
880 match slot_versions.binary_search_by(|at| {
881 at.effective_slot
882 .cmp(&entry.effective_slot)
883 .then(at.deployment_slot.cmp(&entry.deployment_slot))
884 .then(
885 is_current_env(&self.environments, at.program.get_environment()).cmp(
889 &is_current_env(
890 &self.environments,
891 entry.program.get_environment(),
892 ),
893 ),
894 )
895 }) {
896 Ok(index) => {
897 let existing = slot_versions.get_mut(index).unwrap();
898 match (&existing.program, &entry.program) {
899 (
900 ProgramCacheEntryType::Builtin(_),
901 ProgramCacheEntryType::Builtin(_),
902 )
903 | (
904 ProgramCacheEntryType::Unloaded(_),
905 ProgramCacheEntryType::Loaded(_),
906 ) => {}
907 _ => {
908 error!(
910 "ProgramCache::assign_program() failed key={key:?} \
911 existing={slot_versions:?} entry={entry:?}"
912 );
913 debug_assert!(false, "Unexpected replacement of an entry");
914 self.stats.replacements.fetch_add(1, Ordering::Relaxed);
915 return true;
916 }
917 }
918 entry.tx_usage_counter.fetch_add(
920 existing.tx_usage_counter.load(Ordering::Relaxed),
921 Ordering::Relaxed,
922 );
923 *existing = Arc::clone(&entry);
924 self.stats.reloads.fetch_add(1, Ordering::Relaxed);
925 }
926 Err(index) => {
927 self.stats.insertions.fetch_add(1, Ordering::Relaxed);
928 slot_versions.insert(index, Arc::clone(&entry));
929 }
930 }
931 }
932 }
933 false
934 }
935
936 pub fn prune_by_deployment_slot(&mut self, slot: Slot) {
937 match &mut self.index {
938 IndexImplementation::V1 { entries, .. } => {
939 for second_level in entries.values_mut() {
940 second_level.retain(|entry| entry.deployment_slot != slot);
941 }
942 self.remove_programs_with_no_entries();
943 }
944 }
945 }
946
947 pub fn prune(&mut self, new_root_slot: Slot, new_root_epoch: Epoch) {
949 let Some(fork_graph) = self.fork_graph.clone() else {
950 error!("Program cache doesn't have fork graph.");
951 return;
952 };
953 let fork_graph = fork_graph.upgrade().unwrap();
954 let Ok(fork_graph) = fork_graph.read() else {
955 error!("Failed to lock fork graph for reading.");
956 return;
957 };
958 let mut preparation_phase_ends = false;
959 if self.latest_root_epoch != new_root_epoch {
960 self.latest_root_epoch = new_root_epoch;
961 if let Some(upcoming_environments) = self.upcoming_environments.take() {
962 preparation_phase_ends = true;
963 self.environments = upcoming_environments;
964 self.programs_to_recompile.clear();
965 }
966 }
967 match &mut self.index {
968 IndexImplementation::V1 { entries, .. } => {
969 for second_level in entries.values_mut() {
970 let mut first_ancestor_found = false;
972 let mut first_ancestor_env = None;
973 *second_level = second_level
974 .iter()
975 .rev()
976 .filter(|entry| {
977 let relation =
978 fork_graph.relationship(entry.deployment_slot, new_root_slot);
979 if entry.deployment_slot >= new_root_slot {
980 matches!(relation, BlockRelation::Equal | BlockRelation::Descendant)
981 } else if matches!(relation, BlockRelation::Ancestor)
982 || entry.deployment_slot <= self.latest_root_slot
983 {
984 if !first_ancestor_found {
985 first_ancestor_found = true;
986 first_ancestor_env = entry.program.get_environment();
987 return true;
988 }
989 if let Some(entry_env) = entry.program.get_environment() {
996 if let Some(env) = first_ancestor_env {
997 if !Arc::ptr_eq(entry_env, env) {
998 return true;
999 }
1000 }
1001 }
1002 self.stats.prunes_orphan.fetch_add(1, Ordering::Relaxed);
1003 false
1004 } else {
1005 self.stats.prunes_orphan.fetch_add(1, Ordering::Relaxed);
1006 false
1007 }
1008 })
1009 .filter(|entry| {
1010 if preparation_phase_ends
1012 && !Self::matches_environment(entry, &self.environments)
1013 {
1014 self.stats
1015 .prunes_environment
1016 .fetch_add(1, Ordering::Relaxed);
1017 return false;
1018 }
1019 true
1020 })
1021 .cloned()
1022 .collect();
1023 second_level.reverse();
1024 }
1025 }
1026 }
1027 self.remove_programs_with_no_entries();
1028 debug_assert!(self.latest_root_slot <= new_root_slot);
1029 self.latest_root_slot = new_root_slot;
1030 }
1031
1032 fn matches_environment(
1033 entry: &Arc<ProgramCacheEntry>,
1034 environments: &ProgramRuntimeEnvironments,
1035 ) -> bool {
1036 let Some(environment) = entry.program.get_environment() else {
1037 return true;
1038 };
1039 Arc::ptr_eq(environment, &environments.program_runtime_v1)
1040 || Arc::ptr_eq(environment, &environments.program_runtime_v2)
1041 }
1042
1043 fn matches_criteria(
1044 program: &Arc<ProgramCacheEntry>,
1045 criteria: &ProgramCacheMatchCriteria,
1046 ) -> bool {
1047 match criteria {
1048 ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(slot) => {
1049 program.deployment_slot >= *slot
1050 }
1051 ProgramCacheMatchCriteria::Tombstone => program.is_tombstone(),
1052 ProgramCacheMatchCriteria::NoCriteria => true,
1053 }
1054 }
1055
1056 pub fn extract(
1059 &self,
1060 search_for: &mut Vec<(Pubkey, ProgramCacheMatchCriteria)>,
1061 loaded_programs_for_tx_batch: &mut ProgramCacheForTxBatch,
1062 increment_usage_counter: bool,
1063 count_hits_and_misses: bool,
1064 ) -> Option<Pubkey> {
1065 debug_assert!(self.fork_graph.is_some());
1066 let fork_graph = self.fork_graph.as_ref().unwrap().upgrade().unwrap();
1067 let locked_fork_graph = fork_graph.read().unwrap();
1068 let mut cooperative_loading_task = None;
1069 match &self.index {
1070 IndexImplementation::V1 {
1071 entries,
1072 loading_entries,
1073 } => {
1074 search_for.retain(|(key, match_criteria)| {
1075 if let Some(second_level) = entries.get(key) {
1076 for entry in second_level.iter().rev() {
1077 if entry.deployment_slot <= self.latest_root_slot
1078 || matches!(
1079 locked_fork_graph.relationship(
1080 entry.deployment_slot,
1081 loaded_programs_for_tx_batch.slot
1082 ),
1083 BlockRelation::Equal | BlockRelation::Ancestor
1084 )
1085 {
1086 let entry_to_return = if loaded_programs_for_tx_batch.slot
1087 >= entry.effective_slot
1088 && Self::matches_environment(
1089 entry,
1090 &loaded_programs_for_tx_batch.environments,
1091 ) {
1092 if !Self::matches_criteria(entry, match_criteria) {
1093 break;
1094 }
1095 if let ProgramCacheEntryType::Unloaded(_environment) =
1096 &entry.program
1097 {
1098 break;
1099 }
1100 entry.clone()
1101 } else if entry.is_implicit_delay_visibility_tombstone(
1102 loaded_programs_for_tx_batch.slot,
1103 ) {
1104 Arc::new(ProgramCacheEntry::new_tombstone_with_usage_counter(
1108 entry.deployment_slot,
1109 entry.account_owner,
1110 ProgramCacheEntryType::DelayVisibility,
1111 entry.tx_usage_counter.clone(),
1112 ))
1113 } else {
1114 continue;
1115 };
1116 entry_to_return
1117 .update_access_slot(loaded_programs_for_tx_batch.slot);
1118 if increment_usage_counter {
1119 entry_to_return
1120 .tx_usage_counter
1121 .fetch_add(1, Ordering::Relaxed);
1122 }
1123 loaded_programs_for_tx_batch
1124 .entries
1125 .insert(*key, entry_to_return);
1126 return false;
1127 }
1128 }
1129 }
1130 if cooperative_loading_task.is_none() {
1131 let mut loading_entries = loading_entries.lock().unwrap();
1132 let entry = loading_entries.entry(*key);
1133 if let Entry::Vacant(entry) = entry {
1134 entry.insert((
1135 loaded_programs_for_tx_batch.slot,
1136 thread::current().id(),
1137 ));
1138 cooperative_loading_task = Some(*key);
1139 }
1140 }
1141 true
1142 });
1143 }
1144 }
1145 drop(locked_fork_graph);
1146 if count_hits_and_misses {
1147 self.stats
1148 .misses
1149 .fetch_add(search_for.len() as u64, Ordering::Relaxed);
1150 self.stats.hits.fetch_add(
1151 loaded_programs_for_tx_batch.entries.len() as u64,
1152 Ordering::Relaxed,
1153 );
1154 }
1155 cooperative_loading_task
1156 }
1157
1158 pub fn finish_cooperative_loading_task(
1160 &mut self,
1161 slot: Slot,
1162 key: Pubkey,
1163 loaded_program: Arc<ProgramCacheEntry>,
1164 ) -> bool {
1165 match &mut self.index {
1166 IndexImplementation::V1 {
1167 loading_entries, ..
1168 } => {
1169 let loading_thread = loading_entries.get_mut().unwrap().remove(&key);
1170 debug_assert_eq!(loading_thread, Some((slot, thread::current().id())));
1171 if loaded_program.deployment_slot > self.latest_root_slot
1173 && !matches!(
1174 self.fork_graph
1175 .as_ref()
1176 .unwrap()
1177 .upgrade()
1178 .unwrap()
1179 .read()
1180 .unwrap()
1181 .relationship(loaded_program.deployment_slot, slot),
1182 BlockRelation::Equal | BlockRelation::Ancestor
1183 )
1184 {
1185 self.stats.lost_insertions.fetch_add(1, Ordering::Relaxed);
1186 }
1187 let was_occupied = self.assign_program(key, loaded_program);
1188 self.loading_task_waiter.notify();
1189 was_occupied
1190 }
1191 }
1192 }
1193
1194 pub fn merge(&mut self, modified_entries: &HashMap<Pubkey, Arc<ProgramCacheEntry>>) {
1195 modified_entries.iter().for_each(|(key, entry)| {
1196 self.assign_program(*key, entry.clone());
1197 })
1198 }
1199
1200 pub fn get_flattened_entries(
1202 &self,
1203 include_program_runtime_v1: bool,
1204 _include_program_runtime_v2: bool,
1205 ) -> Vec<(Pubkey, Arc<ProgramCacheEntry>)> {
1206 match &self.index {
1207 IndexImplementation::V1 { entries, .. } => entries
1208 .iter()
1209 .flat_map(|(id, second_level)| {
1210 second_level
1211 .iter()
1212 .filter_map(move |program| match program.program {
1213 ProgramCacheEntryType::Loaded(_) => {
1214 if include_program_runtime_v1 {
1215 Some((*id, program.clone()))
1216 } else {
1217 None
1218 }
1219 }
1220 _ => None,
1221 })
1222 })
1223 .collect(),
1224 }
1225 }
1226
1227 pub fn get_flattened_entries_for_tests(&self) -> Vec<(Pubkey, Arc<ProgramCacheEntry>)> {
1229 match &self.index {
1230 IndexImplementation::V1 { entries, .. } => entries
1231 .iter()
1232 .flat_map(|(id, second_level)| {
1233 second_level.iter().map(|program| (*id, program.clone()))
1234 })
1235 .collect(),
1236 }
1237 }
1238
1239 pub fn get_slot_versions_for_tests(&self, key: &Pubkey) -> &[Arc<ProgramCacheEntry>] {
1241 match &self.index {
1242 IndexImplementation::V1 { entries, .. } => entries
1243 .get(key)
1244 .map(|second_level| second_level.as_ref())
1245 .unwrap_or(&[]),
1246 }
1247 }
1248
1249 pub fn sort_and_unload(&mut self, shrink_to: PercentageInteger) {
1251 let mut sorted_candidates = self.get_flattened_entries(true, true);
1252 sorted_candidates
1253 .sort_by_cached_key(|(_id, program)| program.tx_usage_counter.load(Ordering::Relaxed));
1254 let num_to_unload = sorted_candidates
1255 .len()
1256 .saturating_sub(shrink_to.apply_to(MAX_LOADED_ENTRY_COUNT));
1257 self.unload_program_entries(sorted_candidates.iter().take(num_to_unload));
1258 }
1259
1260 pub fn evict_using_2s_random_selection(&mut self, shrink_to: PercentageInteger, now: Slot) {
1263 let mut candidates = self.get_flattened_entries(true, true);
1264 self.stats
1265 .water_level
1266 .store(candidates.len() as u64, Ordering::Relaxed);
1267 let num_to_unload = candidates
1268 .len()
1269 .saturating_sub(shrink_to.apply_to(MAX_LOADED_ENTRY_COUNT));
1270 fn random_index_and_usage_counter(
1271 candidates: &[(Pubkey, Arc<ProgramCacheEntry>)],
1272 now: Slot,
1273 ) -> (usize, u64) {
1274 let mut rng = thread_rng();
1275 let index = rng.gen_range(0..candidates.len());
1276 let usage_counter = candidates
1277 .get(index)
1278 .expect("Failed to get cached entry")
1279 .1
1280 .decayed_usage_counter(now);
1281 (index, usage_counter)
1282 }
1283
1284 for _ in 0..num_to_unload {
1285 let (index1, usage_counter1) = random_index_and_usage_counter(&candidates, now);
1286 let (index2, usage_counter2) = random_index_and_usage_counter(&candidates, now);
1287
1288 let (program, entry) = if usage_counter1 < usage_counter2 {
1289 candidates.swap_remove(index1)
1290 } else {
1291 candidates.swap_remove(index2)
1292 };
1293 self.unload_program_entry(&program, &entry);
1294 }
1295 }
1296
1297 pub fn remove_programs(&mut self, keys: impl Iterator<Item = Pubkey>) {
1299 match &mut self.index {
1300 IndexImplementation::V1 { entries, .. } => {
1301 for k in keys {
1302 entries.remove(&k);
1303 }
1304 }
1305 }
1306 }
1307
1308 fn unload_program_entry(&mut self, program: &Pubkey, remove_entry: &Arc<ProgramCacheEntry>) {
1311 match &mut self.index {
1312 IndexImplementation::V1 { entries, .. } => {
1313 let second_level = entries.get_mut(program).expect("Cache lookup failed");
1314 let candidate = second_level
1315 .iter_mut()
1316 .find(|entry| entry == &remove_entry)
1317 .expect("Program entry not found");
1318
1319 if let Some(unloaded) = candidate.to_unloaded() {
1323 if candidate.tx_usage_counter.load(Ordering::Relaxed) == 1 {
1324 self.stats.one_hit_wonders.fetch_add(1, Ordering::Relaxed);
1325 }
1326 self.stats
1327 .evictions
1328 .entry(*program)
1329 .and_modify(|c| *c = c.saturating_add(1))
1330 .or_insert(1);
1331 *candidate = Arc::new(unloaded);
1332 }
1333 }
1334 }
1335 }
1336
1337 fn unload_program_entries<'a>(
1338 &mut self,
1339 remove: impl Iterator<Item = &'a (Pubkey, Arc<ProgramCacheEntry>)>,
1340 ) {
1341 for (program, entry) in remove {
1342 self.unload_program_entry(program, entry);
1343 }
1344 }
1345
1346 fn remove_programs_with_no_entries(&mut self) {
1347 match &mut self.index {
1348 IndexImplementation::V1 { entries, .. } => {
1349 let num_programs_before_removal = entries.len();
1350 entries.retain(|_key, second_level| !second_level.is_empty());
1351 if entries.len() < num_programs_before_removal {
1352 self.stats.empty_entries.fetch_add(
1353 num_programs_before_removal.saturating_sub(entries.len()) as u64,
1354 Ordering::Relaxed,
1355 );
1356 }
1357 }
1358 }
1359 }
1360}
1361
1362#[cfg(feature = "frozen-abi")]
1363impl solana_frozen_abi::abi_example::AbiExample for ProgramCacheEntry {
1364 fn example() -> Self {
1365 Self::default()
1367 }
1368}
1369
1370#[cfg(feature = "frozen-abi")]
1371impl<FG: ForkGraph> solana_frozen_abi::abi_example::AbiExample for ProgramCache<FG> {
1372 fn example() -> Self {
1373 Self::new(Slot::default(), Epoch::default())
1375 }
1376}
1377
1378#[cfg(test)]
1379mod tests {
1380 use {
1381 crate::loaded_programs::{
1382 BlockRelation, ForkGraph, ProgramCache, ProgramCacheEntry, ProgramCacheEntryOwner,
1383 ProgramCacheEntryType, ProgramCacheForTxBatch, ProgramCacheMatchCriteria,
1384 ProgramRuntimeEnvironment, ProgramRuntimeEnvironments, DELAY_VISIBILITY_SLOT_OFFSET,
1385 },
1386 assert_matches::assert_matches,
1387 percentage::Percentage,
1388 solana_clock::Slot,
1389 solana_pubkey::Pubkey,
1390 solana_sbpf::{elf::Executable, program::BuiltinProgram},
1391 std::{
1392 fs::File,
1393 io::Read,
1394 ops::ControlFlow,
1395 sync::{
1396 atomic::{AtomicU64, Ordering},
1397 Arc, RwLock,
1398 },
1399 },
1400 test_case::{test_case, test_matrix},
1401 };
1402
1403 static MOCK_ENVIRONMENT: std::sync::OnceLock<ProgramRuntimeEnvironment> =
1404 std::sync::OnceLock::<ProgramRuntimeEnvironment>::new();
1405
1406 fn get_mock_env() -> ProgramRuntimeEnvironment {
1407 MOCK_ENVIRONMENT
1408 .get_or_init(|| Arc::new(BuiltinProgram::new_mock()))
1409 .clone()
1410 }
1411
1412 fn new_mock_cache<FG: ForkGraph>() -> ProgramCache<FG> {
1413 let mut cache = ProgramCache::new(0, 0);
1414 cache.environments.program_runtime_v1 = get_mock_env();
1415 cache
1416 }
1417
1418 fn new_test_entry(deployment_slot: Slot, effective_slot: Slot) -> Arc<ProgramCacheEntry> {
1419 new_test_entry_with_usage(deployment_slot, effective_slot, AtomicU64::default())
1420 }
1421
1422 fn new_loaded_entry(env: ProgramRuntimeEnvironment) -> ProgramCacheEntryType {
1423 let mut elf = Vec::new();
1424 File::open("../programs/bpf_loader/test_elfs/out/noop_aligned.so")
1425 .unwrap()
1426 .read_to_end(&mut elf)
1427 .unwrap();
1428 let executable = Executable::load(&elf, env).unwrap();
1429 ProgramCacheEntryType::Loaded(executable)
1430 }
1431
1432 fn new_test_entry_with_usage(
1433 deployment_slot: Slot,
1434 effective_slot: Slot,
1435 usage_counter: AtomicU64,
1436 ) -> Arc<ProgramCacheEntry> {
1437 Arc::new(ProgramCacheEntry {
1438 program: new_loaded_entry(get_mock_env()),
1439 account_owner: ProgramCacheEntryOwner::LoaderV2,
1440 account_size: 0,
1441 deployment_slot,
1442 effective_slot,
1443 tx_usage_counter: Arc::new(usage_counter),
1444 latest_access_slot: AtomicU64::new(deployment_slot),
1445 })
1446 }
1447
1448 fn new_test_builtin_entry(
1449 deployment_slot: Slot,
1450 effective_slot: Slot,
1451 ) -> Arc<ProgramCacheEntry> {
1452 Arc::new(ProgramCacheEntry {
1453 program: ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1454 account_owner: ProgramCacheEntryOwner::NativeLoader,
1455 account_size: 0,
1456 deployment_slot,
1457 effective_slot,
1458 tx_usage_counter: Arc::default(),
1459 latest_access_slot: AtomicU64::default(),
1460 })
1461 }
1462
1463 fn set_tombstone<FG: ForkGraph>(
1464 cache: &mut ProgramCache<FG>,
1465 key: Pubkey,
1466 slot: Slot,
1467 reason: ProgramCacheEntryType,
1468 ) -> Arc<ProgramCacheEntry> {
1469 let program = Arc::new(ProgramCacheEntry::new_tombstone(
1470 slot,
1471 ProgramCacheEntryOwner::LoaderV2,
1472 reason,
1473 ));
1474 cache.assign_program(key, program.clone());
1475 program
1476 }
1477
1478 fn insert_unloaded_entry<FG: ForkGraph>(
1479 cache: &mut ProgramCache<FG>,
1480 key: Pubkey,
1481 slot: Slot,
1482 ) -> Arc<ProgramCacheEntry> {
1483 let loaded = new_test_entry_with_usage(slot, slot.saturating_add(1), AtomicU64::default());
1484 let unloaded = Arc::new(loaded.to_unloaded().expect("Failed to unload the program"));
1485 cache.assign_program(key, unloaded.clone());
1486 unloaded
1487 }
1488
1489 fn num_matching_entries<P, FG>(cache: &ProgramCache<FG>, predicate: P) -> usize
1490 where
1491 P: Fn(&ProgramCacheEntryType) -> bool,
1492 FG: ForkGraph,
1493 {
1494 cache
1495 .get_flattened_entries_for_tests()
1496 .iter()
1497 .filter(|(_key, program)| predicate(&program.program))
1498 .count()
1499 }
1500
1501 #[test]
1502 fn test_usage_counter_decay() {
1503 let _cache = new_mock_cache::<TestForkGraph>();
1504 let program = new_test_entry_with_usage(10, 11, AtomicU64::new(32));
1505 program.update_access_slot(15);
1506 assert_eq!(program.decayed_usage_counter(15), 32);
1507 assert_eq!(program.decayed_usage_counter(16), 16);
1508 assert_eq!(program.decayed_usage_counter(17), 8);
1509 assert_eq!(program.decayed_usage_counter(18), 4);
1510 assert_eq!(program.decayed_usage_counter(19), 2);
1511 assert_eq!(program.decayed_usage_counter(20), 1);
1512 assert_eq!(program.decayed_usage_counter(21), 0);
1513 assert_eq!(program.decayed_usage_counter(15), 32);
1514 assert_eq!(program.decayed_usage_counter(14), 32);
1515
1516 program.update_access_slot(18);
1517 assert_eq!(program.decayed_usage_counter(15), 32);
1518 assert_eq!(program.decayed_usage_counter(16), 32);
1519 assert_eq!(program.decayed_usage_counter(17), 32);
1520 assert_eq!(program.decayed_usage_counter(18), 32);
1521 assert_eq!(program.decayed_usage_counter(19), 16);
1522 assert_eq!(program.decayed_usage_counter(20), 8);
1523 assert_eq!(program.decayed_usage_counter(21), 4);
1524
1525 assert_eq!(program.decayed_usage_counter(18 + 63), 0);
1527 assert_eq!(program.decayed_usage_counter(100), 0);
1528 }
1529
1530 fn program_deploy_test_helper(
1531 cache: &mut ProgramCache<TestForkGraph>,
1532 program: Pubkey,
1533 deployment_slots: Vec<Slot>,
1534 usage_counters: Vec<u64>,
1535 programs: &mut Vec<(Pubkey, Slot, u64)>,
1536 ) {
1537 deployment_slots
1539 .iter()
1540 .enumerate()
1541 .for_each(|(i, deployment_slot)| {
1542 let usage_counter = *usage_counters.get(i).unwrap_or(&0);
1543 cache.assign_program(
1544 program,
1545 new_test_entry_with_usage(
1546 *deployment_slot,
1547 (*deployment_slot).saturating_add(2),
1548 AtomicU64::new(usage_counter),
1549 ),
1550 );
1551 programs.push((program, *deployment_slot, usage_counter));
1552 });
1553
1554 let env = Arc::new(BuiltinProgram::new_mock());
1556 for slot in 21..31 {
1557 set_tombstone(
1558 cache,
1559 program,
1560 slot,
1561 ProgramCacheEntryType::FailedVerification(env.clone()),
1562 );
1563 }
1564
1565 for slot in 31..41 {
1567 insert_unloaded_entry(cache, program, slot);
1568 }
1569 }
1570
1571 #[test]
1572 fn test_random_eviction() {
1573 let mut programs = vec![];
1574
1575 let mut cache = new_mock_cache::<TestForkGraph>();
1576
1577 program_deploy_test_helper(
1583 &mut cache,
1584 Pubkey::new_unique(),
1585 vec![0, 10, 20],
1586 vec![4, 5, 25],
1587 &mut programs,
1588 );
1589
1590 program_deploy_test_helper(
1592 &mut cache,
1593 Pubkey::new_unique(),
1594 vec![5, 11],
1595 vec![0, 2],
1596 &mut programs,
1597 );
1598
1599 program_deploy_test_helper(
1601 &mut cache,
1602 Pubkey::new_unique(),
1603 vec![0, 5, 15],
1604 vec![100, 3, 20],
1605 &mut programs,
1606 );
1607
1608 let num_loaded_expected = 8;
1610 let num_unloaded_expected = 30;
1612 let num_tombstones_expected = 30;
1614
1615 programs.sort_by_key(|(_id, _slot, usage_count)| *usage_count);
1617 let num_loaded = num_matching_entries(&cache, |program_type| {
1618 matches!(program_type, ProgramCacheEntryType::Loaded(_))
1619 });
1620 let num_unloaded = num_matching_entries(&cache, |program_type| {
1621 matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1622 });
1623 let num_tombstones = num_matching_entries(&cache, |program_type| {
1624 matches!(
1625 program_type,
1626 ProgramCacheEntryType::DelayVisibility
1627 | ProgramCacheEntryType::FailedVerification(_)
1628 | ProgramCacheEntryType::Closed
1629 )
1630 });
1631
1632 assert_eq!(num_loaded, num_loaded_expected);
1634 assert_eq!(num_unloaded, num_unloaded_expected);
1635 assert_eq!(num_tombstones, num_tombstones_expected);
1636
1637 let eviction_pct = 1;
1639
1640 let num_loaded_expected =
1641 Percentage::from(eviction_pct).apply_to(crate::loaded_programs::MAX_LOADED_ENTRY_COUNT);
1642 let num_unloaded_expected = num_unloaded_expected + num_loaded - num_loaded_expected;
1643 cache.evict_using_2s_random_selection(Percentage::from(eviction_pct), 21);
1644
1645 let num_loaded = num_matching_entries(&cache, |program_type| {
1647 matches!(program_type, ProgramCacheEntryType::Loaded(_))
1648 });
1649 let num_unloaded = num_matching_entries(&cache, |program_type| {
1650 matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1651 });
1652 let num_tombstones = num_matching_entries(&cache, |program_type| {
1653 matches!(program_type, ProgramCacheEntryType::FailedVerification(_))
1654 });
1655
1656 assert_eq!(num_loaded, num_loaded_expected);
1658 assert_eq!(num_unloaded, num_unloaded_expected);
1660 assert_eq!(num_tombstones, num_tombstones_expected);
1662 }
1663
1664 #[test]
1665 fn test_eviction() {
1666 let mut programs = vec![];
1667 let mut cache = new_mock_cache::<TestForkGraph>();
1668
1669 program_deploy_test_helper(
1671 &mut cache,
1672 Pubkey::new_unique(),
1673 vec![0, 10, 20],
1674 vec![4, 5, 25],
1675 &mut programs,
1676 );
1677
1678 program_deploy_test_helper(
1680 &mut cache,
1681 Pubkey::new_unique(),
1682 vec![5, 11],
1683 vec![0, 2],
1684 &mut programs,
1685 );
1686
1687 program_deploy_test_helper(
1689 &mut cache,
1690 Pubkey::new_unique(),
1691 vec![0, 5, 15],
1692 vec![100, 3, 20],
1693 &mut programs,
1694 );
1695
1696 let num_loaded_expected = 8;
1698 let num_unloaded_expected = 30;
1700 let num_tombstones_expected = 30;
1702
1703 programs.sort_by_key(|(_id, _slot, usage_count)| *usage_count);
1705 let num_loaded = num_matching_entries(&cache, |program_type| {
1706 matches!(program_type, ProgramCacheEntryType::Loaded(_))
1707 });
1708 let num_unloaded = num_matching_entries(&cache, |program_type| {
1709 matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1710 });
1711 let num_tombstones = num_matching_entries(&cache, |program_type| {
1712 matches!(program_type, ProgramCacheEntryType::FailedVerification(_))
1713 });
1714
1715 assert_eq!(num_loaded, num_loaded_expected);
1717 assert_eq!(num_unloaded, num_unloaded_expected);
1718 assert_eq!(num_tombstones, num_tombstones_expected);
1719
1720 let eviction_pct = 1;
1722
1723 let num_loaded_expected =
1724 Percentage::from(eviction_pct).apply_to(crate::loaded_programs::MAX_LOADED_ENTRY_COUNT);
1725 let num_unloaded_expected = num_unloaded_expected + num_loaded - num_loaded_expected;
1726
1727 cache.sort_and_unload(Percentage::from(eviction_pct));
1728
1729 let entries = cache.get_flattened_entries_for_tests();
1731 programs.iter().for_each(|entry| {
1732 assert!(entries.iter().any(|(key, _entry)| key == &entry.0));
1733 });
1734
1735 let unloaded = entries
1736 .iter()
1737 .filter_map(|(key, program)| {
1738 matches!(program.program, ProgramCacheEntryType::Unloaded(_))
1739 .then_some((*key, program.tx_usage_counter.load(Ordering::Relaxed)))
1740 })
1741 .collect::<Vec<(Pubkey, u64)>>();
1742
1743 for index in 0..3 {
1744 let expected = programs.get(index).expect("Missing program");
1745 assert!(unloaded.contains(&(expected.0, expected.2)));
1746 }
1747
1748 let num_loaded = num_matching_entries(&cache, |program_type| {
1750 matches!(program_type, ProgramCacheEntryType::Loaded(_))
1751 });
1752 let num_unloaded = num_matching_entries(&cache, |program_type| {
1753 matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1754 });
1755 let num_tombstones = num_matching_entries(&cache, |program_type| {
1756 matches!(
1757 program_type,
1758 ProgramCacheEntryType::DelayVisibility
1759 | ProgramCacheEntryType::FailedVerification(_)
1760 | ProgramCacheEntryType::Closed
1761 )
1762 });
1763
1764 assert_eq!(num_loaded, num_loaded_expected);
1766 assert_eq!(num_unloaded, num_unloaded_expected);
1768 assert_eq!(num_tombstones, num_tombstones_expected);
1770 }
1771
1772 #[test]
1773 fn test_usage_count_of_unloaded_program() {
1774 let mut cache = new_mock_cache::<TestForkGraph>();
1775
1776 let program = Pubkey::new_unique();
1777 let evict_to_pct = 2;
1778 let cache_capacity_after_shrink =
1779 Percentage::from(evict_to_pct).apply_to(crate::loaded_programs::MAX_LOADED_ENTRY_COUNT);
1780 let num_total_programs = (cache_capacity_after_shrink + 1) as u64;
1782 (0..num_total_programs).for_each(|i| {
1783 cache.assign_program(
1784 program,
1785 new_test_entry_with_usage(i, i + 2, AtomicU64::new(i + 10)),
1786 );
1787 });
1788
1789 cache.sort_and_unload(Percentage::from(evict_to_pct));
1790
1791 let num_unloaded = num_matching_entries(&cache, |program_type| {
1792 matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1793 });
1794 assert_eq!(num_unloaded, 1);
1795
1796 cache
1797 .get_flattened_entries_for_tests()
1798 .iter()
1799 .for_each(|(_key, program)| {
1800 if matches!(program.program, ProgramCacheEntryType::Unloaded(_)) {
1801 assert_eq!(program.tx_usage_counter.load(Ordering::Relaxed), 10);
1803 assert_eq!(program.deployment_slot, 0);
1804 assert_eq!(program.effective_slot, 2);
1805 }
1806 });
1807
1808 cache.assign_program(program, new_test_entry_with_usage(0, 2, AtomicU64::new(0)));
1811
1812 cache
1813 .get_flattened_entries_for_tests()
1814 .iter()
1815 .for_each(|(_key, program)| {
1816 if matches!(program.program, ProgramCacheEntryType::Unloaded(_))
1817 && program.deployment_slot == 0
1818 && program.effective_slot == 2
1819 {
1820 assert_eq!(program.tx_usage_counter.load(Ordering::Relaxed), 10);
1822 }
1823 });
1824 }
1825
1826 #[test]
1827 fn test_fuzz_assign_program_order() {
1828 use rand::prelude::SliceRandom;
1829 const EXPECTED_ENTRIES: [(u64, u64); 7] =
1830 [(1, 2), (5, 5), (5, 6), (5, 10), (9, 10), (10, 10), (3, 12)];
1831 let mut rng = rand::thread_rng();
1832 let program_id = Pubkey::new_unique();
1833 for _ in 0..1000 {
1834 let mut entries = EXPECTED_ENTRIES.to_vec();
1835 entries.shuffle(&mut rng);
1836 let mut cache = new_mock_cache::<TestForkGraph>();
1837 for (deployment_slot, effective_slot) in entries {
1838 assert!(!cache
1839 .assign_program(program_id, new_test_entry(deployment_slot, effective_slot)));
1840 }
1841 for ((deployment_slot, effective_slot), entry) in EXPECTED_ENTRIES
1842 .iter()
1843 .zip(cache.get_slot_versions_for_tests(&program_id).iter())
1844 {
1845 assert_eq!(entry.deployment_slot, *deployment_slot);
1846 assert_eq!(entry.effective_slot, *effective_slot);
1847 }
1848 }
1849 }
1850
1851 #[test_matrix(
1852 (
1853 ProgramCacheEntryType::Closed,
1854 ProgramCacheEntryType::FailedVerification(get_mock_env()),
1855 new_loaded_entry(get_mock_env()),
1856 ),
1857 (
1858 ProgramCacheEntryType::FailedVerification(get_mock_env()),
1859 ProgramCacheEntryType::Closed,
1860 ProgramCacheEntryType::Unloaded(get_mock_env()),
1861 new_loaded_entry(get_mock_env()),
1862 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1863 )
1864 )]
1865 #[test_matrix(
1866 (
1867 ProgramCacheEntryType::Unloaded(get_mock_env()),
1868 ),
1869 (
1870 ProgramCacheEntryType::FailedVerification(get_mock_env()),
1871 ProgramCacheEntryType::Closed,
1872 ProgramCacheEntryType::Unloaded(get_mock_env()),
1873 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1874 )
1875 )]
1876 #[test_matrix(
1877 (ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),),
1878 (
1879 ProgramCacheEntryType::FailedVerification(get_mock_env()),
1880 ProgramCacheEntryType::Closed,
1881 ProgramCacheEntryType::Unloaded(get_mock_env()),
1882 new_loaded_entry(get_mock_env()),
1883 )
1884 )]
1885 #[should_panic(expected = "Unexpected replacement of an entry")]
1886 fn test_assign_program_failure(old: ProgramCacheEntryType, new: ProgramCacheEntryType) {
1887 let mut cache = new_mock_cache::<TestForkGraph>();
1888 let program_id = Pubkey::new_unique();
1889 assert!(!cache.assign_program(
1890 program_id,
1891 Arc::new(ProgramCacheEntry {
1892 program: old,
1893 account_owner: ProgramCacheEntryOwner::LoaderV2,
1894 account_size: 0,
1895 deployment_slot: 10,
1896 effective_slot: 11,
1897 tx_usage_counter: Arc::default(),
1898 latest_access_slot: AtomicU64::default(),
1899 }),
1900 ));
1901 cache.assign_program(
1902 program_id,
1903 Arc::new(ProgramCacheEntry {
1904 program: new,
1905 account_owner: ProgramCacheEntryOwner::LoaderV2,
1906 account_size: 0,
1907 deployment_slot: 10,
1908 effective_slot: 11,
1909 tx_usage_counter: Arc::default(),
1910 latest_access_slot: AtomicU64::default(),
1911 }),
1912 );
1913 }
1914
1915 #[test_case(
1916 ProgramCacheEntryType::Unloaded(Arc::new(BuiltinProgram::new_mock())),
1917 new_loaded_entry(get_mock_env())
1918 )]
1919 #[test_case(
1920 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1921 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock())
1922 )]
1923 fn test_assign_program_success(old: ProgramCacheEntryType, new: ProgramCacheEntryType) {
1924 let mut cache = new_mock_cache::<TestForkGraph>();
1925 let program_id = Pubkey::new_unique();
1926 assert!(!cache.assign_program(
1927 program_id,
1928 Arc::new(ProgramCacheEntry {
1929 program: old,
1930 account_owner: ProgramCacheEntryOwner::LoaderV2,
1931 account_size: 0,
1932 deployment_slot: 10,
1933 effective_slot: 11,
1934 tx_usage_counter: Arc::default(),
1935 latest_access_slot: AtomicU64::default(),
1936 }),
1937 ));
1938 assert!(!cache.assign_program(
1939 program_id,
1940 Arc::new(ProgramCacheEntry {
1941 program: new,
1942 account_owner: ProgramCacheEntryOwner::LoaderV2,
1943 account_size: 0,
1944 deployment_slot: 10,
1945 effective_slot: 11,
1946 tx_usage_counter: Arc::default(),
1947 latest_access_slot: AtomicU64::default(),
1948 }),
1949 ));
1950 }
1951
1952 #[test]
1953 fn test_tombstone() {
1954 let env = Arc::new(BuiltinProgram::new_mock());
1955 let tombstone = ProgramCacheEntry::new_tombstone(
1956 0,
1957 ProgramCacheEntryOwner::LoaderV2,
1958 ProgramCacheEntryType::FailedVerification(env.clone()),
1959 );
1960 assert_matches!(
1961 tombstone.program,
1962 ProgramCacheEntryType::FailedVerification(_)
1963 );
1964 assert!(tombstone.is_tombstone());
1965 assert_eq!(tombstone.deployment_slot, 0);
1966 assert_eq!(tombstone.effective_slot, 0);
1967
1968 let tombstone = ProgramCacheEntry::new_tombstone(
1969 100,
1970 ProgramCacheEntryOwner::LoaderV2,
1971 ProgramCacheEntryType::Closed,
1972 );
1973 assert_matches!(tombstone.program, ProgramCacheEntryType::Closed);
1974 assert!(tombstone.is_tombstone());
1975 assert_eq!(tombstone.deployment_slot, 100);
1976 assert_eq!(tombstone.effective_slot, 100);
1977
1978 let mut cache = new_mock_cache::<TestForkGraph>();
1979 let program1 = Pubkey::new_unique();
1980 let tombstone = set_tombstone(
1981 &mut cache,
1982 program1,
1983 10,
1984 ProgramCacheEntryType::FailedVerification(env.clone()),
1985 );
1986 let slot_versions = cache.get_slot_versions_for_tests(&program1);
1987 assert_eq!(slot_versions.len(), 1);
1988 assert!(slot_versions.first().unwrap().is_tombstone());
1989 assert_eq!(tombstone.deployment_slot, 10);
1990 assert_eq!(tombstone.effective_slot, 10);
1991
1992 let program2 = Pubkey::new_unique();
1994 cache.assign_program(program2, new_test_builtin_entry(50, 51));
1995 let slot_versions = cache.get_slot_versions_for_tests(&program2);
1996 assert_eq!(slot_versions.len(), 1);
1997 assert!(!slot_versions.first().unwrap().is_tombstone());
1998
1999 let tombstone = set_tombstone(
2000 &mut cache,
2001 program2,
2002 60,
2003 ProgramCacheEntryType::FailedVerification(env),
2004 );
2005 let slot_versions = cache.get_slot_versions_for_tests(&program2);
2006 assert_eq!(slot_versions.len(), 2);
2007 assert!(!slot_versions.first().unwrap().is_tombstone());
2008 assert!(slot_versions.get(1).unwrap().is_tombstone());
2009 assert!(tombstone.is_tombstone());
2010 assert_eq!(tombstone.deployment_slot, 60);
2011 assert_eq!(tombstone.effective_slot, 60);
2012 }
2013
2014 struct TestForkGraph {
2015 relation: BlockRelation,
2016 }
2017 impl ForkGraph for TestForkGraph {
2018 fn relationship(&self, _a: Slot, _b: Slot) -> BlockRelation {
2019 self.relation
2020 }
2021 }
2022
2023 #[test]
2024 fn test_prune_empty() {
2025 let mut cache = new_mock_cache::<TestForkGraph>();
2026 let fork_graph = Arc::new(RwLock::new(TestForkGraph {
2027 relation: BlockRelation::Unrelated,
2028 }));
2029
2030 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2031
2032 cache.prune(0, 0);
2033 assert!(cache.get_flattened_entries_for_tests().is_empty());
2034
2035 cache.prune(10, 0);
2036 assert!(cache.get_flattened_entries_for_tests().is_empty());
2037
2038 let mut cache = new_mock_cache::<TestForkGraph>();
2039 let fork_graph = Arc::new(RwLock::new(TestForkGraph {
2040 relation: BlockRelation::Ancestor,
2041 }));
2042
2043 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2044
2045 cache.prune(0, 0);
2046 assert!(cache.get_flattened_entries_for_tests().is_empty());
2047
2048 cache.prune(10, 0);
2049 assert!(cache.get_flattened_entries_for_tests().is_empty());
2050
2051 let mut cache = new_mock_cache::<TestForkGraph>();
2052 let fork_graph = Arc::new(RwLock::new(TestForkGraph {
2053 relation: BlockRelation::Descendant,
2054 }));
2055
2056 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2057
2058 cache.prune(0, 0);
2059 assert!(cache.get_flattened_entries_for_tests().is_empty());
2060
2061 cache.prune(10, 0);
2062 assert!(cache.get_flattened_entries_for_tests().is_empty());
2063
2064 let mut cache = new_mock_cache::<TestForkGraph>();
2065 let fork_graph = Arc::new(RwLock::new(TestForkGraph {
2066 relation: BlockRelation::Unknown,
2067 }));
2068 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2069
2070 cache.prune(0, 0);
2071 assert!(cache.get_flattened_entries_for_tests().is_empty());
2072
2073 cache.prune(10, 0);
2074 assert!(cache.get_flattened_entries_for_tests().is_empty());
2075 }
2076
2077 #[test]
2078 fn test_prune_different_env() {
2079 let mut cache = new_mock_cache::<TestForkGraph>();
2080
2081 let fork_graph = Arc::new(RwLock::new(TestForkGraph {
2082 relation: BlockRelation::Ancestor,
2083 }));
2084
2085 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2086
2087 let program1 = Pubkey::new_unique();
2088 cache.assign_program(program1, new_test_entry(10, 10));
2089
2090 let new_env = Arc::new(BuiltinProgram::new_mock());
2091 cache.upcoming_environments = Some(ProgramRuntimeEnvironments {
2092 program_runtime_v1: new_env.clone(),
2093 program_runtime_v2: new_env.clone(),
2094 });
2095 let updated_program = Arc::new(ProgramCacheEntry {
2096 program: new_loaded_entry(new_env.clone()),
2097 account_owner: ProgramCacheEntryOwner::LoaderV2,
2098 account_size: 0,
2099 deployment_slot: 20,
2100 effective_slot: 20,
2101 tx_usage_counter: Arc::default(),
2102 latest_access_slot: AtomicU64::default(),
2103 });
2104 cache.assign_program(program1, updated_program.clone());
2105
2106 assert_eq!(cache.get_slot_versions_for_tests(&program1).len(), 2);
2108
2109 cache.prune(21, cache.latest_root_epoch);
2110
2111 assert_eq!(cache.get_slot_versions_for_tests(&program1).len(), 2);
2113
2114 cache.prune(22, cache.latest_root_epoch.saturating_add(1));
2115
2116 assert_eq!(cache.get_slot_versions_for_tests(&program1).len(), 1);
2118
2119 let entry = cache
2120 .get_slot_versions_for_tests(&program1)
2121 .first()
2122 .expect("Failed to get the program")
2123 .clone();
2124 assert_eq!(entry, updated_program);
2126 }
2127
2128 #[derive(Default)]
2129 struct TestForkGraphSpecific {
2130 forks: Vec<Vec<Slot>>,
2131 }
2132
2133 impl TestForkGraphSpecific {
2134 fn insert_fork(&mut self, fork: &[Slot]) {
2135 let mut fork = fork.to_vec();
2136 fork.sort();
2137 self.forks.push(fork)
2138 }
2139 }
2140
2141 impl ForkGraph for TestForkGraphSpecific {
2142 fn relationship(&self, a: Slot, b: Slot) -> BlockRelation {
2143 match self.forks.iter().try_for_each(|fork| {
2144 let relation = fork
2145 .iter()
2146 .position(|x| *x == a)
2147 .and_then(|a_pos| {
2148 fork.iter().position(|x| *x == b).and_then(|b_pos| {
2149 (a_pos == b_pos)
2150 .then_some(BlockRelation::Equal)
2151 .or_else(|| (a_pos < b_pos).then_some(BlockRelation::Ancestor))
2152 .or(Some(BlockRelation::Descendant))
2153 })
2154 })
2155 .unwrap_or(BlockRelation::Unrelated);
2156
2157 if relation != BlockRelation::Unrelated {
2158 return ControlFlow::Break(relation);
2159 }
2160
2161 ControlFlow::Continue(())
2162 }) {
2163 ControlFlow::Break(relation) => relation,
2164 _ => BlockRelation::Unrelated,
2165 }
2166 }
2167 }
2168
2169 fn get_entries_to_load(
2170 cache: &ProgramCache<TestForkGraphSpecific>,
2171 loading_slot: Slot,
2172 keys: &[Pubkey],
2173 ) -> Vec<(Pubkey, ProgramCacheMatchCriteria)> {
2174 let fork_graph = cache.fork_graph.as_ref().unwrap().upgrade().unwrap();
2175 let locked_fork_graph = fork_graph.read().unwrap();
2176 let entries = cache.get_flattened_entries_for_tests();
2177 keys.iter()
2178 .filter_map(|key| {
2179 entries
2180 .iter()
2181 .rev()
2182 .find(|(program_id, entry)| {
2183 program_id == key
2184 && matches!(
2185 locked_fork_graph.relationship(entry.deployment_slot, loading_slot),
2186 BlockRelation::Equal | BlockRelation::Ancestor,
2187 )
2188 })
2189 .map(|(program_id, _entry)| {
2190 (*program_id, ProgramCacheMatchCriteria::NoCriteria)
2191 })
2192 })
2193 .collect()
2194 }
2195
2196 fn match_slot(
2197 extracted: &ProgramCacheForTxBatch,
2198 program: &Pubkey,
2199 deployment_slot: Slot,
2200 working_slot: Slot,
2201 ) -> bool {
2202 assert_eq!(extracted.slot, working_slot);
2203 extracted
2204 .entries
2205 .get(program)
2206 .map(|entry| entry.deployment_slot == deployment_slot)
2207 .unwrap_or(false)
2208 }
2209
2210 fn match_missing(
2211 missing: &[(Pubkey, ProgramCacheMatchCriteria)],
2212 program: &Pubkey,
2213 expected_result: bool,
2214 ) -> bool {
2215 missing.iter().any(|(key, _)| key == program) == expected_result
2216 }
2217
2218 #[test]
2219 fn test_fork_extract_and_prune() {
2220 let mut cache = new_mock_cache::<TestForkGraphSpecific>();
2221
2222 let mut fork_graph = TestForkGraphSpecific::default();
2238 fork_graph.insert_fork(&[0, 10, 20, 22]);
2239 fork_graph.insert_fork(&[0, 5, 11, 15, 16, 18, 19, 21, 23]);
2240 fork_graph.insert_fork(&[0, 5, 11, 25, 27]);
2241
2242 let fork_graph = Arc::new(RwLock::new(fork_graph));
2243 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2244
2245 let program1 = Pubkey::new_unique();
2246 cache.assign_program(program1, new_test_entry(0, 1));
2247 cache.assign_program(program1, new_test_entry(10, 11));
2248 cache.assign_program(program1, new_test_entry(20, 21));
2249
2250 let program2 = Pubkey::new_unique();
2251 cache.assign_program(program2, new_test_entry(5, 6));
2252 cache.assign_program(
2253 program2,
2254 new_test_entry(11, 11 + DELAY_VISIBILITY_SLOT_OFFSET),
2255 );
2256
2257 let program3 = Pubkey::new_unique();
2258 cache.assign_program(program3, new_test_entry(25, 26));
2259
2260 let program4 = Pubkey::new_unique();
2261 cache.assign_program(program4, new_test_entry(0, 1));
2262 cache.assign_program(program4, new_test_entry(5, 6));
2263 cache.assign_program(
2265 program4,
2266 new_test_entry(15, 15 + DELAY_VISIBILITY_SLOT_OFFSET),
2267 );
2268
2269 let mut missing =
2286 get_entries_to_load(&cache, 22, &[program1, program2, program3, program4]);
2287 assert!(match_missing(&missing, &program2, false));
2288 assert!(match_missing(&missing, &program3, false));
2289 let mut extracted = ProgramCacheForTxBatch::new(22, cache.environments.clone(), None, 0);
2290 cache.extract(&mut missing, &mut extracted, true, true);
2291 assert!(match_slot(&extracted, &program1, 20, 22));
2292 assert!(match_slot(&extracted, &program4, 0, 22));
2293
2294 let mut missing =
2296 get_entries_to_load(&cache, 15, &[program1, program2, program3, program4]);
2297 assert!(match_missing(&missing, &program3, false));
2298 let mut extracted = ProgramCacheForTxBatch::new(15, cache.environments.clone(), None, 0);
2299 cache.extract(&mut missing, &mut extracted, true, true);
2300 assert!(match_slot(&extracted, &program1, 0, 15));
2301 assert!(match_slot(&extracted, &program2, 11, 15));
2302 let tombstone = extracted
2305 .find(&program4)
2306 .expect("Failed to find the tombstone");
2307 assert_matches!(tombstone.program, ProgramCacheEntryType::DelayVisibility);
2308 assert_eq!(tombstone.deployment_slot, 15);
2309
2310 let mut missing =
2312 get_entries_to_load(&cache, 18, &[program1, program2, program3, program4]);
2313 assert!(match_missing(&missing, &program3, false));
2314 let mut extracted = ProgramCacheForTxBatch::new(18, cache.environments.clone(), None, 0);
2315 cache.extract(&mut missing, &mut extracted, true, true);
2316 assert!(match_slot(&extracted, &program1, 0, 18));
2317 assert!(match_slot(&extracted, &program2, 11, 18));
2318 assert!(match_slot(&extracted, &program4, 15, 18));
2320
2321 let mut missing =
2323 get_entries_to_load(&cache, 23, &[program1, program2, program3, program4]);
2324 assert!(match_missing(&missing, &program3, false));
2325 let mut extracted = ProgramCacheForTxBatch::new(23, cache.environments.clone(), None, 0);
2326 cache.extract(&mut missing, &mut extracted, true, true);
2327 assert!(match_slot(&extracted, &program1, 0, 23));
2328 assert!(match_slot(&extracted, &program2, 11, 23));
2329 assert!(match_slot(&extracted, &program4, 15, 23));
2331
2332 let mut missing =
2334 get_entries_to_load(&cache, 11, &[program1, program2, program3, program4]);
2335 assert!(match_missing(&missing, &program3, false));
2336 let mut extracted = ProgramCacheForTxBatch::new(11, cache.environments.clone(), None, 0);
2337 cache.extract(&mut missing, &mut extracted, true, true);
2338 assert!(match_slot(&extracted, &program1, 0, 11));
2339 let tombstone = extracted
2341 .find(&program2)
2342 .expect("Failed to find the tombstone");
2343 assert_matches!(tombstone.program, ProgramCacheEntryType::DelayVisibility);
2344 assert_eq!(tombstone.deployment_slot, 11);
2345 assert!(match_slot(&extracted, &program4, 5, 11));
2346
2347 cache.prune(5, 0);
2348
2349 let mut missing =
2366 get_entries_to_load(&cache, 21, &[program1, program2, program3, program4]);
2367 assert!(match_missing(&missing, &program3, false));
2368 let mut extracted = ProgramCacheForTxBatch::new(21, cache.environments.clone(), None, 0);
2369 cache.extract(&mut missing, &mut extracted, true, true);
2370 assert!(match_slot(&extracted, &program1, 0, 21));
2372 assert!(match_slot(&extracted, &program2, 11, 21));
2373 assert!(match_slot(&extracted, &program4, 15, 21));
2374
2375 let mut missing =
2377 get_entries_to_load(&cache, 27, &[program1, program2, program3, program4]);
2378 let mut extracted = ProgramCacheForTxBatch::new(27, cache.environments.clone(), None, 0);
2379 cache.extract(&mut missing, &mut extracted, true, true);
2380 assert!(match_slot(&extracted, &program1, 0, 27));
2381 assert!(match_slot(&extracted, &program2, 11, 27));
2382 assert!(match_slot(&extracted, &program3, 25, 27));
2383 assert!(match_slot(&extracted, &program4, 5, 27));
2384
2385 cache.prune(15, 0);
2386
2387 let mut missing =
2404 get_entries_to_load(&cache, 23, &[program1, program2, program3, program4]);
2405 assert!(match_missing(&missing, &program3, false));
2406 let mut extracted = ProgramCacheForTxBatch::new(23, cache.environments.clone(), None, 0);
2407 cache.extract(&mut missing, &mut extracted, true, true);
2408 assert!(match_slot(&extracted, &program1, 0, 23));
2409 assert!(match_slot(&extracted, &program2, 11, 23));
2410 assert!(match_slot(&extracted, &program4, 15, 23));
2411 }
2412
2413 #[test]
2414 fn test_extract_using_deployment_slot() {
2415 let mut cache = new_mock_cache::<TestForkGraphSpecific>();
2416
2417 let mut fork_graph = TestForkGraphSpecific::default();
2433 fork_graph.insert_fork(&[0, 10, 20, 22]);
2434 fork_graph.insert_fork(&[0, 5, 11, 12, 15, 16, 18, 19, 21, 23]);
2435 fork_graph.insert_fork(&[0, 5, 11, 25, 27]);
2436
2437 let fork_graph = Arc::new(RwLock::new(fork_graph));
2438 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2439
2440 let program1 = Pubkey::new_unique();
2441 cache.assign_program(program1, new_test_entry(0, 1));
2442 cache.assign_program(program1, new_test_entry(20, 21));
2443
2444 let program2 = Pubkey::new_unique();
2445 cache.assign_program(program2, new_test_entry(5, 6));
2446 cache.assign_program(program2, new_test_entry(11, 12));
2447
2448 let program3 = Pubkey::new_unique();
2449 cache.assign_program(program3, new_test_entry(25, 26));
2450
2451 let mut missing = get_entries_to_load(&cache, 12, &[program1, program2, program3]);
2453 assert!(match_missing(&missing, &program3, false));
2454 let mut extracted = ProgramCacheForTxBatch::new(12, cache.environments.clone(), None, 0);
2455 cache.extract(&mut missing, &mut extracted, true, true);
2456 assert!(match_slot(&extracted, &program1, 0, 12));
2457 assert!(match_slot(&extracted, &program2, 11, 12));
2458
2459 let mut missing = get_entries_to_load(&cache, 12, &[program1, program2, program3]);
2461 missing.get_mut(0).unwrap().1 = ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(5);
2462 missing.get_mut(1).unwrap().1 = ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(5);
2463 assert!(match_missing(&missing, &program3, false));
2464 let mut extracted = ProgramCacheForTxBatch::new(12, cache.environments.clone(), None, 0);
2465 cache.extract(&mut missing, &mut extracted, true, true);
2466 assert!(match_missing(&missing, &program1, true));
2467 assert!(match_slot(&extracted, &program2, 11, 12));
2468 }
2469
2470 #[test]
2471 fn test_extract_unloaded() {
2472 let mut cache = new_mock_cache::<TestForkGraphSpecific>();
2473
2474 let mut fork_graph = TestForkGraphSpecific::default();
2490 fork_graph.insert_fork(&[0, 10, 20, 22]);
2491 fork_graph.insert_fork(&[0, 5, 11, 15, 16, 19, 21, 23]);
2492 fork_graph.insert_fork(&[0, 5, 11, 25, 27]);
2493
2494 let fork_graph = Arc::new(RwLock::new(fork_graph));
2495 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2496
2497 let program1 = Pubkey::new_unique();
2498 cache.assign_program(program1, new_test_entry(0, 1));
2499 cache.assign_program(program1, new_test_entry(20, 21));
2500
2501 let program2 = Pubkey::new_unique();
2502 cache.assign_program(program2, new_test_entry(5, 6));
2503 cache.assign_program(program2, new_test_entry(11, 12));
2504
2505 let program3 = Pubkey::new_unique();
2506 let _ = insert_unloaded_entry(&mut cache, program3, 25);
2508
2509 cache.assign_program(
2513 program3,
2514 Arc::new(
2515 new_test_entry(20, 21)
2516 .to_unloaded()
2517 .expect("Failed to create unloaded program"),
2518 ),
2519 );
2520
2521 let mut missing = get_entries_to_load(&cache, 19, &[program1, program2, program3]);
2523 assert!(match_missing(&missing, &program3, false));
2524 let mut extracted = ProgramCacheForTxBatch::new(19, cache.environments.clone(), None, 0);
2525 cache.extract(&mut missing, &mut extracted, true, true);
2526 assert!(match_slot(&extracted, &program1, 0, 19));
2527 assert!(match_slot(&extracted, &program2, 11, 19));
2528
2529 let mut missing = get_entries_to_load(&cache, 27, &[program1, program2, program3]);
2531 let mut extracted = ProgramCacheForTxBatch::new(27, cache.environments.clone(), None, 0);
2532 cache.extract(&mut missing, &mut extracted, true, true);
2533 assert!(match_slot(&extracted, &program1, 0, 27));
2534 assert!(match_slot(&extracted, &program2, 11, 27));
2535 assert!(match_missing(&missing, &program3, true));
2536
2537 let mut missing = get_entries_to_load(&cache, 22, &[program1, program2, program3]);
2539 assert!(match_missing(&missing, &program2, false));
2540 let mut extracted = ProgramCacheForTxBatch::new(22, cache.environments.clone(), None, 0);
2541 cache.extract(&mut missing, &mut extracted, true, true);
2542 assert!(match_slot(&extracted, &program1, 20, 22));
2543 assert!(match_missing(&missing, &program3, true));
2544 }
2545
2546 #[test]
2547 fn test_extract_nonexistent() {
2548 let mut cache = new_mock_cache::<TestForkGraphSpecific>();
2549 let fork_graph = TestForkGraphSpecific::default();
2550 let fork_graph = Arc::new(RwLock::new(fork_graph));
2551 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2552
2553 let program1 = Pubkey::new_unique();
2554 let mut missing = vec![(program1, ProgramCacheMatchCriteria::NoCriteria)];
2555 let mut extracted = ProgramCacheForTxBatch::new(0, cache.environments.clone(), None, 0);
2556 cache.extract(&mut missing, &mut extracted, true, true);
2557 assert!(match_missing(&missing, &program1, true));
2558 }
2559
2560 #[test]
2561 fn test_unloaded() {
2562 let mut cache = new_mock_cache::<TestForkGraph>();
2563 for program_cache_entry_type in [
2564 ProgramCacheEntryType::FailedVerification(
2565 cache.environments.program_runtime_v1.clone(),
2566 ),
2567 ProgramCacheEntryType::Closed,
2568 ProgramCacheEntryType::Unloaded(cache.environments.program_runtime_v1.clone()),
2569 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
2570 ] {
2571 let entry = Arc::new(ProgramCacheEntry {
2572 program: program_cache_entry_type,
2573 account_owner: ProgramCacheEntryOwner::LoaderV2,
2574 account_size: 0,
2575 deployment_slot: 0,
2576 effective_slot: 0,
2577 tx_usage_counter: Arc::default(),
2578 latest_access_slot: AtomicU64::default(),
2579 });
2580 assert!(entry.to_unloaded().is_none());
2581
2582 let program_id = Pubkey::new_unique();
2584 cache.assign_program(program_id, entry.clone());
2585 cache.unload_program_entry(&program_id, &entry);
2586 assert_eq!(cache.get_slot_versions_for_tests(&program_id).len(), 1);
2587 assert!(cache.stats.evictions.is_empty());
2588 }
2589
2590 let entry = new_test_entry_with_usage(1, 2, AtomicU64::new(3));
2591 let unloaded_entry = entry.to_unloaded().unwrap();
2592 assert_eq!(unloaded_entry.deployment_slot, 1);
2593 assert_eq!(unloaded_entry.effective_slot, 2);
2594 assert_eq!(unloaded_entry.latest_access_slot.load(Ordering::Relaxed), 1);
2595 assert_eq!(unloaded_entry.tx_usage_counter.load(Ordering::Relaxed), 3);
2596
2597 let program_id = Pubkey::new_unique();
2599 cache.assign_program(program_id, entry.clone());
2600 cache.unload_program_entry(&program_id, &entry);
2601 assert!(cache.stats.evictions.contains_key(&program_id));
2602 }
2603
2604 #[test]
2605 fn test_fork_prune_find_first_ancestor() {
2606 let mut cache = new_mock_cache::<TestForkGraphSpecific>();
2607
2608 let mut fork_graph = TestForkGraphSpecific::default();
2619 fork_graph.insert_fork(&[0, 10, 20]);
2620 fork_graph.insert_fork(&[0, 5]);
2621 let fork_graph = Arc::new(RwLock::new(fork_graph));
2622 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2623
2624 let program1 = Pubkey::new_unique();
2625 cache.assign_program(program1, new_test_entry(0, 1));
2626 cache.assign_program(program1, new_test_entry(5, 6));
2627
2628 cache.prune(10, 0);
2629
2630 let mut missing = get_entries_to_load(&cache, 20, &[program1]);
2631 let mut extracted = ProgramCacheForTxBatch::new(20, cache.environments.clone(), None, 0);
2632 cache.extract(&mut missing, &mut extracted, true, true);
2633
2634 assert_eq!(
2636 extracted
2637 .find(&program1)
2638 .expect("Did not find the program")
2639 .deployment_slot,
2640 0
2641 );
2642 }
2643
2644 #[test]
2645 fn test_prune_by_deployment_slot() {
2646 let mut cache = new_mock_cache::<TestForkGraphSpecific>();
2647
2648 let mut fork_graph = TestForkGraphSpecific::default();
2659 fork_graph.insert_fork(&[0, 10, 20]);
2660 fork_graph.insert_fork(&[0, 5, 6]);
2661 let fork_graph = Arc::new(RwLock::new(fork_graph));
2662 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2663
2664 let program1 = Pubkey::new_unique();
2665 cache.assign_program(program1, new_test_entry(0, 1));
2666 cache.assign_program(program1, new_test_entry(5, 6));
2667
2668 let program2 = Pubkey::new_unique();
2669 cache.assign_program(program2, new_test_entry(10, 11));
2670
2671 let mut missing = get_entries_to_load(&cache, 20, &[program1, program2]);
2672 let mut extracted = ProgramCacheForTxBatch::new(20, cache.environments.clone(), None, 0);
2673 cache.extract(&mut missing, &mut extracted, true, true);
2674 assert!(match_slot(&extracted, &program1, 0, 20));
2675 assert!(match_slot(&extracted, &program2, 10, 20));
2676
2677 let mut missing = get_entries_to_load(&cache, 6, &[program1, program2]);
2678 assert!(match_missing(&missing, &program2, false));
2679 let mut extracted = ProgramCacheForTxBatch::new(6, cache.environments.clone(), None, 0);
2680 cache.extract(&mut missing, &mut extracted, true, true);
2681 assert!(match_slot(&extracted, &program1, 5, 6));
2682
2683 cache.prune_by_deployment_slot(5);
2686
2687 let mut missing = get_entries_to_load(&cache, 20, &[program1, program2]);
2688 let mut extracted = ProgramCacheForTxBatch::new(20, cache.environments.clone(), None, 0);
2689 cache.extract(&mut missing, &mut extracted, true, true);
2690 assert!(match_slot(&extracted, &program1, 0, 20));
2691 assert!(match_slot(&extracted, &program2, 10, 20));
2692
2693 let mut missing = get_entries_to_load(&cache, 6, &[program1, program2]);
2694 assert!(match_missing(&missing, &program2, false));
2695 let mut extracted = ProgramCacheForTxBatch::new(6, cache.environments.clone(), None, 0);
2696 cache.extract(&mut missing, &mut extracted, true, true);
2697 assert!(match_slot(&extracted, &program1, 0, 6));
2698
2699 cache.prune_by_deployment_slot(10);
2702
2703 let mut missing = get_entries_to_load(&cache, 20, &[program1, program2]);
2704 assert!(match_missing(&missing, &program2, false));
2705 let mut extracted = ProgramCacheForTxBatch::new(20, cache.environments.clone(), None, 0);
2706 cache.extract(&mut missing, &mut extracted, true, true);
2707 assert!(match_slot(&extracted, &program1, 0, 20));
2708 }
2709
2710 #[test]
2711 fn test_usable_entries_for_slot() {
2712 new_mock_cache::<TestForkGraph>();
2713 let tombstone = Arc::new(ProgramCacheEntry::new_tombstone(
2714 0,
2715 ProgramCacheEntryOwner::LoaderV2,
2716 ProgramCacheEntryType::Closed,
2717 ));
2718
2719 assert!(ProgramCache::<TestForkGraph>::matches_criteria(
2720 &tombstone,
2721 &ProgramCacheMatchCriteria::NoCriteria
2722 ));
2723
2724 assert!(ProgramCache::<TestForkGraph>::matches_criteria(
2725 &tombstone,
2726 &ProgramCacheMatchCriteria::Tombstone
2727 ));
2728
2729 assert!(ProgramCache::<TestForkGraph>::matches_criteria(
2730 &tombstone,
2731 &ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(0)
2732 ));
2733
2734 assert!(!ProgramCache::<TestForkGraph>::matches_criteria(
2735 &tombstone,
2736 &ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(1)
2737 ));
2738
2739 let program = new_test_entry(0, 1);
2740
2741 assert!(ProgramCache::<TestForkGraph>::matches_criteria(
2742 &program,
2743 &ProgramCacheMatchCriteria::NoCriteria
2744 ));
2745
2746 assert!(!ProgramCache::<TestForkGraph>::matches_criteria(
2747 &program,
2748 &ProgramCacheMatchCriteria::Tombstone
2749 ));
2750
2751 assert!(ProgramCache::<TestForkGraph>::matches_criteria(
2752 &program,
2753 &ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(0)
2754 ));
2755
2756 assert!(!ProgramCache::<TestForkGraph>::matches_criteria(
2757 &program,
2758 &ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(1)
2759 ));
2760
2761 let program = Arc::new(new_test_entry_with_usage(0, 1, AtomicU64::default()));
2762
2763 assert!(ProgramCache::<TestForkGraph>::matches_criteria(
2764 &program,
2765 &ProgramCacheMatchCriteria::NoCriteria
2766 ));
2767
2768 assert!(!ProgramCache::<TestForkGraph>::matches_criteria(
2769 &program,
2770 &ProgramCacheMatchCriteria::Tombstone
2771 ));
2772
2773 assert!(ProgramCache::<TestForkGraph>::matches_criteria(
2774 &program,
2775 &ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(0)
2776 ));
2777
2778 assert!(!ProgramCache::<TestForkGraph>::matches_criteria(
2779 &program,
2780 &ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(1)
2781 ));
2782 }
2783}