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_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_measure::measure::Measure, solana_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: AtomicU64,
193 pub ix_usage_counter: AtomicU64,
195 pub latest_access_slot: AtomicU64,
197}
198
199#[derive(Debug, Default)]
201pub struct ProgramCacheStats {
202 pub hits: AtomicU64,
204 pub misses: AtomicU64,
206 pub evictions: HashMap<Pubkey, u64>,
208 pub reloads: AtomicU64,
210 pub insertions: AtomicU64,
212 pub lost_insertions: AtomicU64,
214 pub replacements: AtomicU64,
216 pub one_hit_wonders: AtomicU64,
218 pub prunes_orphan: AtomicU64,
220 pub prunes_environment: AtomicU64,
222 pub empty_entries: AtomicU64,
224 pub water_level: AtomicU64,
226}
227
228impl ProgramCacheStats {
229 pub fn reset(&mut self) {
230 *self = ProgramCacheStats::default();
231 }
232 pub fn log(&self) {
233 let hits = self.hits.load(Ordering::Relaxed);
234 let misses = self.misses.load(Ordering::Relaxed);
235 let evictions: u64 = self.evictions.values().sum();
236 let reloads = self.reloads.load(Ordering::Relaxed);
237 let insertions = self.insertions.load(Ordering::Relaxed);
238 let lost_insertions = self.lost_insertions.load(Ordering::Relaxed);
239 let replacements = self.replacements.load(Ordering::Relaxed);
240 let one_hit_wonders = self.one_hit_wonders.load(Ordering::Relaxed);
241 let prunes_orphan = self.prunes_orphan.load(Ordering::Relaxed);
242 let prunes_environment = self.prunes_environment.load(Ordering::Relaxed);
243 let empty_entries = self.empty_entries.load(Ordering::Relaxed);
244 let water_level = self.water_level.load(Ordering::Relaxed);
245 debug!(
246 "Loaded Programs Cache Stats -- Hits: {}, Misses: {}, Evictions: {}, Reloads: {}, Insertions: {}, Lost-Insertions: {}, Replacements: {}, One-Hit-Wonders: {}, Prunes-Orphan: {}, Prunes-Environment: {}, Empty: {}, Water-Level: {}",
247 hits, misses, evictions, reloads, insertions, lost_insertions, replacements, one_hit_wonders, prunes_orphan, prunes_environment, empty_entries, water_level
248 );
249 if log_enabled!(log::Level::Trace) && !self.evictions.is_empty() {
250 let mut evictions = self.evictions.iter().collect::<Vec<_>>();
251 evictions.sort_by_key(|e| e.1);
252 let evictions = evictions
253 .into_iter()
254 .rev()
255 .map(|(program_id, evictions)| {
256 format!(" {:<44} {}", program_id.to_string(), evictions)
257 })
258 .collect::<Vec<_>>();
259 let evictions = evictions.join("\n");
260 trace!(
261 "Eviction Details:\n {:<44} {}\n{}",
262 "Program",
263 "Count",
264 evictions
265 );
266 }
267 }
268}
269
270#[cfg(feature = "metrics")]
271#[derive(Debug, Default)]
273pub struct LoadProgramMetrics {
274 pub program_id: String,
276 pub register_syscalls_us: u64,
278 pub load_elf_us: u64,
280 pub verify_code_us: u64,
282 pub jit_compile_us: u64,
284}
285
286#[cfg(feature = "metrics")]
287impl LoadProgramMetrics {
288 pub fn submit_datapoint(&self, timings: &mut ExecuteDetailsTimings) {
289 timings.create_executor_register_syscalls_us += self.register_syscalls_us;
290 timings.create_executor_load_elf_us += self.load_elf_us;
291 timings.create_executor_verify_code_us += self.verify_code_us;
292 timings.create_executor_jit_compile_us += self.jit_compile_us;
293 datapoint_trace!(
294 "create_executor_trace",
295 ("program_id", self.program_id, String),
296 ("register_syscalls_us", self.register_syscalls_us, i64),
297 ("load_elf_us", self.load_elf_us, i64),
298 ("verify_code_us", self.verify_code_us, i64),
299 ("jit_compile_us", self.jit_compile_us, i64),
300 );
301 }
302}
303
304impl PartialEq for ProgramCacheEntry {
305 fn eq(&self, other: &Self) -> bool {
306 self.effective_slot == other.effective_slot
307 && self.deployment_slot == other.deployment_slot
308 && self.is_tombstone() == other.is_tombstone()
309 }
310}
311
312impl ProgramCacheEntry {
313 pub fn new(
315 loader_key: &Pubkey,
316 program_runtime_environment: ProgramRuntimeEnvironment,
317 deployment_slot: Slot,
318 effective_slot: Slot,
319 elf_bytes: &[u8],
320 account_size: usize,
321 #[cfg(feature = "metrics")] metrics: &mut LoadProgramMetrics,
322 ) -> Result<Self, Box<dyn std::error::Error>> {
323 Self::new_internal(
324 loader_key,
325 program_runtime_environment,
326 deployment_slot,
327 effective_slot,
328 elf_bytes,
329 account_size,
330 #[cfg(feature = "metrics")]
331 metrics,
332 false, )
334 }
335
336 pub unsafe fn reload(
345 loader_key: &Pubkey,
346 program_runtime_environment: Arc<BuiltinProgram<InvokeContext<'static>>>,
347 deployment_slot: Slot,
348 effective_slot: Slot,
349 elf_bytes: &[u8],
350 account_size: usize,
351 #[cfg(feature = "metrics")] metrics: &mut LoadProgramMetrics,
352 ) -> Result<Self, Box<dyn std::error::Error>> {
353 Self::new_internal(
354 loader_key,
355 program_runtime_environment,
356 deployment_slot,
357 effective_slot,
358 elf_bytes,
359 account_size,
360 #[cfg(feature = "metrics")]
361 metrics,
362 true, )
364 }
365
366 fn new_internal(
367 loader_key: &Pubkey,
368 program_runtime_environment: Arc<BuiltinProgram<InvokeContext<'static>>>,
369 deployment_slot: Slot,
370 effective_slot: Slot,
371 elf_bytes: &[u8],
372 account_size: usize,
373 #[cfg(feature = "metrics")] metrics: &mut LoadProgramMetrics,
374 reloading: bool,
375 ) -> Result<Self, Box<dyn std::error::Error>> {
376 #[cfg(feature = "metrics")]
377 let load_elf_time = Measure::start("load_elf_time");
378 #[allow(unused_mut)]
381 let mut executable = Executable::load(elf_bytes, program_runtime_environment.clone())?;
382 #[cfg(feature = "metrics")]
383 {
384 metrics.load_elf_us = load_elf_time.end_as_us();
385 }
386
387 if !reloading {
388 #[cfg(feature = "metrics")]
389 let verify_code_time = Measure::start("verify_code_time");
390 executable.verify::<RequisiteVerifier>()?;
391 #[cfg(feature = "metrics")]
392 {
393 metrics.verify_code_us = verify_code_time.end_as_us();
394 }
395 }
396
397 #[cfg(all(not(target_os = "windows"), target_arch = "x86_64"))]
398 {
399 #[cfg(feature = "metrics")]
400 let jit_compile_time = Measure::start("jit_compile_time");
401 executable.jit_compile()?;
402 #[cfg(feature = "metrics")]
403 {
404 metrics.jit_compile_us = jit_compile_time.end_as_us();
405 }
406 }
407
408 Ok(Self {
409 deployment_slot,
410 account_owner: ProgramCacheEntryOwner::try_from(loader_key).unwrap(),
411 account_size,
412 effective_slot,
413 tx_usage_counter: AtomicU64::new(0),
414 program: ProgramCacheEntryType::Loaded(executable),
415 ix_usage_counter: AtomicU64::new(0),
416 latest_access_slot: AtomicU64::new(0),
417 })
418 }
419
420 pub fn to_unloaded(&self) -> Option<Self> {
421 match &self.program {
422 ProgramCacheEntryType::Loaded(_) => {}
423 ProgramCacheEntryType::FailedVerification(_)
424 | ProgramCacheEntryType::Closed
425 | ProgramCacheEntryType::DelayVisibility
426 | ProgramCacheEntryType::Unloaded(_)
427 | ProgramCacheEntryType::Builtin(_) => {
428 return None;
429 }
430 }
431 Some(Self {
432 program: ProgramCacheEntryType::Unloaded(self.program.get_environment()?.clone()),
433 account_owner: self.account_owner,
434 account_size: self.account_size,
435 deployment_slot: self.deployment_slot,
436 effective_slot: self.effective_slot,
437 tx_usage_counter: AtomicU64::new(self.tx_usage_counter.load(Ordering::Relaxed)),
438 ix_usage_counter: AtomicU64::new(self.ix_usage_counter.load(Ordering::Relaxed)),
439 latest_access_slot: AtomicU64::new(self.latest_access_slot.load(Ordering::Relaxed)),
440 })
441 }
442
443 pub fn new_builtin(
445 deployment_slot: Slot,
446 account_size: usize,
447 builtin_function: BuiltinFunctionWithContext,
448 ) -> Self {
449 let mut program = BuiltinProgram::new_builtin();
450 program
451 .register_function("entrypoint", builtin_function)
452 .unwrap();
453 Self {
454 deployment_slot,
455 account_owner: ProgramCacheEntryOwner::NativeLoader,
456 account_size,
457 effective_slot: deployment_slot,
458 tx_usage_counter: AtomicU64::new(0),
459 program: ProgramCacheEntryType::Builtin(program),
460 ix_usage_counter: AtomicU64::new(0),
461 latest_access_slot: AtomicU64::new(0),
462 }
463 }
464
465 pub fn new_tombstone(
466 slot: Slot,
467 account_owner: ProgramCacheEntryOwner,
468 reason: ProgramCacheEntryType,
469 ) -> Self {
470 let tombstone = Self {
471 program: reason,
472 account_owner,
473 account_size: 0,
474 deployment_slot: slot,
475 effective_slot: slot,
476 tx_usage_counter: AtomicU64::default(),
477 ix_usage_counter: AtomicU64::default(),
478 latest_access_slot: AtomicU64::new(0),
479 };
480 debug_assert!(tombstone.is_tombstone());
481 tombstone
482 }
483
484 pub fn is_tombstone(&self) -> bool {
485 matches!(
486 self.program,
487 ProgramCacheEntryType::FailedVerification(_)
488 | ProgramCacheEntryType::Closed
489 | ProgramCacheEntryType::DelayVisibility
490 )
491 }
492
493 fn is_implicit_delay_visibility_tombstone(&self, slot: Slot) -> bool {
494 !matches!(self.program, ProgramCacheEntryType::Builtin(_))
495 && self.effective_slot.saturating_sub(self.deployment_slot)
496 == DELAY_VISIBILITY_SLOT_OFFSET
497 && slot >= self.deployment_slot
498 && slot < self.effective_slot
499 }
500
501 pub fn update_access_slot(&self, slot: Slot) {
502 let _ = self.latest_access_slot.fetch_max(slot, Ordering::Relaxed);
503 }
504
505 pub fn decayed_usage_counter(&self, now: Slot) -> u64 {
506 let last_access = self.latest_access_slot.load(Ordering::Relaxed);
507 let decaying_for = std::cmp::min(63, now.saturating_sub(last_access));
509 self.tx_usage_counter.load(Ordering::Relaxed) >> decaying_for
510 }
511
512 pub fn account_owner(&self) -> Pubkey {
513 self.account_owner.into()
514 }
515}
516
517#[derive(Clone, Debug)]
521pub struct ProgramRuntimeEnvironments {
522 pub program_runtime_v1: ProgramRuntimeEnvironment,
524 pub program_runtime_v2: ProgramRuntimeEnvironment,
526}
527
528impl Default for ProgramRuntimeEnvironments {
529 fn default() -> Self {
530 let empty_loader = Arc::new(BuiltinProgram::new_loader(Config::default()));
531 Self {
532 program_runtime_v1: empty_loader.clone(),
533 program_runtime_v2: empty_loader,
534 }
535 }
536}
537
538#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
539pub struct LoadingTaskCookie(u64);
540
541impl LoadingTaskCookie {
542 fn new() -> Self {
543 Self(0)
544 }
545
546 fn update(&mut self) {
547 let LoadingTaskCookie(cookie) = self;
548 *cookie = cookie.wrapping_add(1);
549 }
550}
551
552#[derive(Debug, Default)]
554pub struct LoadingTaskWaiter {
555 cookie: Mutex<LoadingTaskCookie>,
556 cond: Condvar,
557}
558
559impl LoadingTaskWaiter {
560 pub fn new() -> Self {
561 Self {
562 cookie: Mutex::new(LoadingTaskCookie::new()),
563 cond: Condvar::new(),
564 }
565 }
566
567 pub fn cookie(&self) -> LoadingTaskCookie {
568 *self.cookie.lock().unwrap()
569 }
570
571 pub fn notify(&self) {
572 let mut cookie = self.cookie.lock().unwrap();
573 cookie.update();
574 self.cond.notify_all();
575 }
576
577 pub fn wait(&self, cookie: LoadingTaskCookie) -> LoadingTaskCookie {
578 let cookie_guard = self.cookie.lock().unwrap();
579 *self
580 .cond
581 .wait_while(cookie_guard, |current_cookie| *current_cookie == cookie)
582 .unwrap()
583 }
584}
585
586#[derive(Debug)]
587enum IndexImplementation {
588 V1 {
590 entries: HashMap<Pubkey, Vec<Arc<ProgramCacheEntry>>>,
595 loading_entries: Mutex<HashMap<Pubkey, (Slot, thread::ThreadId)>>,
604 },
605}
606
607pub struct ProgramCache<FG: ForkGraph> {
623 index: IndexImplementation,
625 pub latest_root_slot: Slot,
627 pub latest_root_epoch: Epoch,
629 pub environments: ProgramRuntimeEnvironments,
631 pub upcoming_environments: Option<ProgramRuntimeEnvironments>,
637 pub programs_to_recompile: Vec<(Pubkey, Arc<ProgramCacheEntry>)>,
639 pub stats: ProgramCacheStats,
641 pub fork_graph: Option<Weak<RwLock<FG>>>,
643 pub loading_task_waiter: Arc<LoadingTaskWaiter>,
645}
646
647impl<FG: ForkGraph> Debug for ProgramCache<FG> {
648 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
649 f.debug_struct("ProgramCache")
650 .field("root slot", &self.latest_root_slot)
651 .field("root epoch", &self.latest_root_epoch)
652 .field("stats", &self.stats)
653 .field("index", &self.index)
654 .finish()
655 }
656}
657
658#[derive(Clone, Debug, Default)]
664pub struct ProgramCacheForTxBatch {
665 entries: HashMap<Pubkey, Arc<ProgramCacheEntry>>,
668 modified_entries: HashMap<Pubkey, Arc<ProgramCacheEntry>>,
670 slot: Slot,
671 pub environments: ProgramRuntimeEnvironments,
672 pub upcoming_environments: Option<ProgramRuntimeEnvironments>,
680 pub latest_root_epoch: Epoch,
682 pub hit_max_limit: bool,
683 pub loaded_missing: bool,
684 pub merged_modified: bool,
685}
686
687impl ProgramCacheForTxBatch {
688 pub fn new(
689 slot: Slot,
690 environments: ProgramRuntimeEnvironments,
691 upcoming_environments: Option<ProgramRuntimeEnvironments>,
692 latest_root_epoch: Epoch,
693 ) -> Self {
694 Self {
695 entries: HashMap::new(),
696 modified_entries: HashMap::new(),
697 slot,
698 environments,
699 upcoming_environments,
700 latest_root_epoch,
701 hit_max_limit: false,
702 loaded_missing: false,
703 merged_modified: false,
704 }
705 }
706
707 pub fn new_from_cache<FG: ForkGraph>(
708 slot: Slot,
709 epoch: Epoch,
710 cache: &ProgramCache<FG>,
711 ) -> Self {
712 Self {
713 entries: HashMap::new(),
714 modified_entries: HashMap::new(),
715 slot,
716 environments: cache.get_environments_for_epoch(epoch),
717 upcoming_environments: cache.get_upcoming_environments_for_epoch(epoch),
718 latest_root_epoch: cache.latest_root_epoch,
719 hit_max_limit: false,
720 loaded_missing: false,
721 merged_modified: false,
722 }
723 }
724
725 pub fn get_environments_for_epoch(&self, epoch: Epoch) -> &ProgramRuntimeEnvironments {
727 if epoch != self.latest_root_epoch {
728 if let Some(upcoming_environments) = self.upcoming_environments.as_ref() {
729 return upcoming_environments;
730 }
731 }
732 &self.environments
733 }
734
735 pub fn replenish(
741 &mut self,
742 key: Pubkey,
743 entry: Arc<ProgramCacheEntry>,
744 ) -> (bool, Arc<ProgramCacheEntry>) {
745 (self.entries.insert(key, entry.clone()).is_some(), entry)
746 }
747
748 pub fn store_modified_entry(&mut self, key: Pubkey, entry: Arc<ProgramCacheEntry>) {
751 self.modified_entries.insert(key, entry);
752 }
753
754 pub fn drain_modified_entries(&mut self) -> HashMap<Pubkey, Arc<ProgramCacheEntry>> {
757 std::mem::take(&mut self.modified_entries)
758 }
759
760 pub fn find(&self, key: &Pubkey) -> Option<Arc<ProgramCacheEntry>> {
761 self.modified_entries
765 .get(key)
766 .or(self.entries.get(key))
767 .map(|entry| {
768 if entry.is_implicit_delay_visibility_tombstone(self.slot) {
769 Arc::new(ProgramCacheEntry::new_tombstone(
773 entry.deployment_slot,
774 entry.account_owner,
775 ProgramCacheEntryType::DelayVisibility,
776 ))
777 } else {
778 entry.clone()
779 }
780 })
781 }
782
783 pub fn slot(&self) -> Slot {
784 self.slot
785 }
786
787 pub fn set_slot_for_tests(&mut self, slot: Slot) {
788 self.slot = slot;
789 }
790
791 pub fn merge(&mut self, modified_entries: &HashMap<Pubkey, Arc<ProgramCacheEntry>>) {
792 modified_entries.iter().for_each(|(key, entry)| {
793 self.merged_modified = true;
794 self.replenish(*key, entry.clone());
795 })
796 }
797
798 pub fn is_empty(&self) -> bool {
799 self.entries.is_empty()
800 }
801}
802
803pub enum ProgramCacheMatchCriteria {
804 DeployedOnOrAfterSlot(Slot),
805 Tombstone,
806 NoCriteria,
807}
808
809impl<FG: ForkGraph> ProgramCache<FG> {
810 pub fn new(root_slot: Slot, root_epoch: Epoch) -> Self {
811 Self {
812 index: IndexImplementation::V1 {
813 entries: HashMap::new(),
814 loading_entries: Mutex::new(HashMap::new()),
815 },
816 latest_root_slot: root_slot,
817 latest_root_epoch: root_epoch,
818 environments: ProgramRuntimeEnvironments::default(),
819 upcoming_environments: None,
820 programs_to_recompile: Vec::default(),
821 stats: ProgramCacheStats::default(),
822 fork_graph: None,
823 loading_task_waiter: Arc::new(LoadingTaskWaiter::default()),
824 }
825 }
826
827 pub fn set_fork_graph(&mut self, fork_graph: Weak<RwLock<FG>>) {
828 self.fork_graph = Some(fork_graph);
829 }
830
831 pub fn get_environments_for_epoch(&self, epoch: Epoch) -> ProgramRuntimeEnvironments {
833 if epoch != self.latest_root_epoch {
834 if let Some(upcoming_environments) = self.upcoming_environments.as_ref() {
835 return upcoming_environments.clone();
836 }
837 }
838 self.environments.clone()
839 }
840
841 pub fn get_upcoming_environments_for_epoch(
843 &self,
844 epoch: Epoch,
845 ) -> Option<ProgramRuntimeEnvironments> {
846 if epoch == self.latest_root_epoch {
847 return self.upcoming_environments.clone();
848 }
849 None
850 }
851
852 pub fn assign_program(&mut self, key: Pubkey, entry: Arc<ProgramCacheEntry>) -> bool {
855 debug_assert!(!matches!(
856 &entry.program,
857 ProgramCacheEntryType::DelayVisibility
858 ));
859 fn is_current_env(
863 environments: &ProgramRuntimeEnvironments,
864 env_opt: Option<&ProgramRuntimeEnvironment>,
865 ) -> bool {
866 env_opt
867 .map(|env| {
868 Arc::ptr_eq(env, &environments.program_runtime_v1)
869 || Arc::ptr_eq(env, &environments.program_runtime_v2)
870 })
871 .unwrap_or(true)
872 }
873 match &mut self.index {
874 IndexImplementation::V1 { entries, .. } => {
875 let slot_versions = &mut entries.entry(key).or_default();
876 match slot_versions.binary_search_by(|at| {
877 at.effective_slot
878 .cmp(&entry.effective_slot)
879 .then(at.deployment_slot.cmp(&entry.deployment_slot))
880 .then(
881 is_current_env(&self.environments, at.program.get_environment()).cmp(
885 &is_current_env(
886 &self.environments,
887 entry.program.get_environment(),
888 ),
889 ),
890 )
891 }) {
892 Ok(index) => {
893 let existing = slot_versions.get_mut(index).unwrap();
894 match (&existing.program, &entry.program) {
895 (
896 ProgramCacheEntryType::Builtin(_),
897 ProgramCacheEntryType::Builtin(_),
898 )
899 | (
900 ProgramCacheEntryType::Unloaded(_),
901 ProgramCacheEntryType::Loaded(_),
902 ) => {}
903 _ => {
904 error!("ProgramCache::assign_program() failed key={:?} existing={:?} entry={:?}", key, slot_versions, entry);
906 debug_assert!(false, "Unexpected replacement of an entry");
907 self.stats.replacements.fetch_add(1, Ordering::Relaxed);
908 return true;
909 }
910 }
911 entry.tx_usage_counter.fetch_add(
913 existing.tx_usage_counter.load(Ordering::Relaxed),
914 Ordering::Relaxed,
915 );
916 entry.ix_usage_counter.fetch_add(
917 existing.ix_usage_counter.load(Ordering::Relaxed),
918 Ordering::Relaxed,
919 );
920 *existing = Arc::clone(&entry);
921 self.stats.reloads.fetch_add(1, Ordering::Relaxed);
922 }
923 Err(index) => {
924 self.stats.insertions.fetch_add(1, Ordering::Relaxed);
925 slot_versions.insert(index, Arc::clone(&entry));
926 }
927 }
928 }
929 }
930 false
931 }
932
933 pub fn prune_by_deployment_slot(&mut self, slot: Slot) {
934 match &mut self.index {
935 IndexImplementation::V1 { entries, .. } => {
936 for second_level in entries.values_mut() {
937 second_level.retain(|entry| entry.deployment_slot != slot);
938 }
939 self.remove_programs_with_no_entries();
940 }
941 }
942 }
943
944 pub fn prune(&mut self, new_root_slot: Slot, new_root_epoch: Epoch) {
946 let Some(fork_graph) = self.fork_graph.clone() else {
947 error!("Program cache doesn't have fork graph.");
948 return;
949 };
950 let fork_graph = fork_graph.upgrade().unwrap();
951 let Ok(fork_graph) = fork_graph.read() else {
952 error!("Failed to lock fork graph for reading.");
953 return;
954 };
955 let mut preparation_phase_ends = false;
956 if self.latest_root_epoch != new_root_epoch {
957 self.latest_root_epoch = new_root_epoch;
958 if let Some(upcoming_environments) = self.upcoming_environments.take() {
959 preparation_phase_ends = true;
960 self.environments = upcoming_environments;
961 self.programs_to_recompile.clear();
962 }
963 }
964 match &mut self.index {
965 IndexImplementation::V1 { entries, .. } => {
966 for second_level in entries.values_mut() {
967 let mut first_ancestor_found = false;
969 let mut first_ancestor_env = None;
970 *second_level = second_level
971 .iter()
972 .rev()
973 .filter(|entry| {
974 let relation =
975 fork_graph.relationship(entry.deployment_slot, new_root_slot);
976 if entry.deployment_slot >= new_root_slot {
977 matches!(relation, BlockRelation::Equal | BlockRelation::Descendant)
978 } else if matches!(relation, BlockRelation::Ancestor)
979 || entry.deployment_slot <= self.latest_root_slot
980 {
981 if !first_ancestor_found {
982 first_ancestor_found = true;
983 first_ancestor_env = entry.program.get_environment();
984 return true;
985 }
986 if let Some(entry_env) = entry.program.get_environment() {
993 if let Some(env) = first_ancestor_env {
994 if !Arc::ptr_eq(entry_env, env) {
995 return true;
996 }
997 }
998 }
999 self.stats.prunes_orphan.fetch_add(1, Ordering::Relaxed);
1000 false
1001 } else {
1002 self.stats.prunes_orphan.fetch_add(1, Ordering::Relaxed);
1003 false
1004 }
1005 })
1006 .filter(|entry| {
1007 if preparation_phase_ends
1009 && !Self::matches_environment(entry, &self.environments)
1010 {
1011 self.stats
1012 .prunes_environment
1013 .fetch_add(1, Ordering::Relaxed);
1014 return false;
1015 }
1016 true
1017 })
1018 .cloned()
1019 .collect();
1020 second_level.reverse();
1021 }
1022 }
1023 }
1024 self.remove_programs_with_no_entries();
1025 debug_assert!(self.latest_root_slot <= new_root_slot);
1026 self.latest_root_slot = new_root_slot;
1027 }
1028
1029 fn matches_environment(
1030 entry: &Arc<ProgramCacheEntry>,
1031 environments: &ProgramRuntimeEnvironments,
1032 ) -> bool {
1033 let Some(environment) = entry.program.get_environment() else {
1034 return true;
1035 };
1036 Arc::ptr_eq(environment, &environments.program_runtime_v1)
1037 || Arc::ptr_eq(environment, &environments.program_runtime_v2)
1038 }
1039
1040 fn matches_criteria(
1041 program: &Arc<ProgramCacheEntry>,
1042 criteria: &ProgramCacheMatchCriteria,
1043 ) -> bool {
1044 match criteria {
1045 ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(slot) => {
1046 program.deployment_slot >= *slot
1047 }
1048 ProgramCacheMatchCriteria::Tombstone => program.is_tombstone(),
1049 ProgramCacheMatchCriteria::NoCriteria => true,
1050 }
1051 }
1052
1053 pub fn extract(
1056 &self,
1057 search_for: &mut Vec<(Pubkey, (ProgramCacheMatchCriteria, u64))>,
1058 loaded_programs_for_tx_batch: &mut ProgramCacheForTxBatch,
1059 is_first_round: bool,
1060 ) -> Option<(Pubkey, u64)> {
1061 debug_assert!(self.fork_graph.is_some());
1062 let fork_graph = self.fork_graph.as_ref().unwrap().upgrade().unwrap();
1063 let locked_fork_graph = fork_graph.read().unwrap();
1064 let mut cooperative_loading_task = None;
1065 match &self.index {
1066 IndexImplementation::V1 {
1067 entries,
1068 loading_entries,
1069 } => {
1070 search_for.retain(|(key, (match_criteria, usage_count))| {
1071 if let Some(second_level) = entries.get(key) {
1072 for entry in second_level.iter().rev() {
1073 if entry.deployment_slot <= self.latest_root_slot
1074 || matches!(
1075 locked_fork_graph.relationship(
1076 entry.deployment_slot,
1077 loaded_programs_for_tx_batch.slot
1078 ),
1079 BlockRelation::Equal | BlockRelation::Ancestor
1080 )
1081 {
1082 let entry_to_return = if loaded_programs_for_tx_batch.slot
1083 >= entry.effective_slot
1084 && Self::matches_environment(
1085 entry,
1086 &loaded_programs_for_tx_batch.environments,
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(
1104 entry.deployment_slot,
1105 entry.account_owner,
1106 ProgramCacheEntryType::DelayVisibility,
1107 ))
1108 } else {
1109 continue;
1110 };
1111 entry_to_return
1112 .update_access_slot(loaded_programs_for_tx_batch.slot);
1113 entry_to_return
1114 .tx_usage_counter
1115 .fetch_add(*usage_count, Ordering::Relaxed);
1116 loaded_programs_for_tx_batch
1117 .entries
1118 .insert(*key, entry_to_return);
1119 return false;
1120 }
1121 }
1122 }
1123 if cooperative_loading_task.is_none() {
1124 let mut loading_entries = loading_entries.lock().unwrap();
1125 let entry = loading_entries.entry(*key);
1126 if let Entry::Vacant(entry) = entry {
1127 entry.insert((
1128 loaded_programs_for_tx_batch.slot,
1129 thread::current().id(),
1130 ));
1131 cooperative_loading_task = Some((*key, *usage_count));
1132 }
1133 }
1134 true
1135 });
1136 }
1137 }
1138 drop(locked_fork_graph);
1139 if is_first_round {
1140 self.stats
1141 .misses
1142 .fetch_add(search_for.len() as u64, Ordering::Relaxed);
1143 self.stats.hits.fetch_add(
1144 loaded_programs_for_tx_batch.entries.len() as u64,
1145 Ordering::Relaxed,
1146 );
1147 }
1148 cooperative_loading_task
1149 }
1150
1151 pub fn finish_cooperative_loading_task(
1153 &mut self,
1154 slot: Slot,
1155 key: Pubkey,
1156 loaded_program: Arc<ProgramCacheEntry>,
1157 ) -> bool {
1158 match &mut self.index {
1159 IndexImplementation::V1 {
1160 loading_entries, ..
1161 } => {
1162 let loading_thread = loading_entries.get_mut().unwrap().remove(&key);
1163 debug_assert_eq!(loading_thread, Some((slot, thread::current().id())));
1164 if loaded_program.deployment_slot > self.latest_root_slot
1166 && !matches!(
1167 self.fork_graph
1168 .as_ref()
1169 .unwrap()
1170 .upgrade()
1171 .unwrap()
1172 .read()
1173 .unwrap()
1174 .relationship(loaded_program.deployment_slot, slot),
1175 BlockRelation::Equal | BlockRelation::Ancestor
1176 )
1177 {
1178 self.stats.lost_insertions.fetch_add(1, Ordering::Relaxed);
1179 }
1180 let was_occupied = self.assign_program(key, loaded_program);
1181 self.loading_task_waiter.notify();
1182 was_occupied
1183 }
1184 }
1185 }
1186
1187 pub fn merge(&mut self, modified_entries: &HashMap<Pubkey, Arc<ProgramCacheEntry>>) {
1188 modified_entries.iter().for_each(|(key, entry)| {
1189 self.assign_program(*key, entry.clone());
1190 })
1191 }
1192
1193 pub fn get_flattened_entries(
1195 &self,
1196 include_program_runtime_v1: bool,
1197 _include_program_runtime_v2: bool,
1198 ) -> Vec<(Pubkey, Arc<ProgramCacheEntry>)> {
1199 match &self.index {
1200 IndexImplementation::V1 { entries, .. } => entries
1201 .iter()
1202 .flat_map(|(id, second_level)| {
1203 second_level
1204 .iter()
1205 .filter_map(move |program| match program.program {
1206 ProgramCacheEntryType::Loaded(_) => {
1207 if include_program_runtime_v1 {
1208 Some((*id, program.clone()))
1209 } else {
1210 None
1211 }
1212 }
1213 _ => None,
1214 })
1215 })
1216 .collect(),
1217 }
1218 }
1219
1220 pub fn get_flattened_entries_for_tests(&self) -> Vec<(Pubkey, Arc<ProgramCacheEntry>)> {
1222 match &self.index {
1223 IndexImplementation::V1 { entries, .. } => entries
1224 .iter()
1225 .flat_map(|(id, second_level)| {
1226 second_level.iter().map(|program| (*id, program.clone()))
1227 })
1228 .collect(),
1229 }
1230 }
1231
1232 pub fn get_slot_versions_for_tests(&self, key: &Pubkey) -> &[Arc<ProgramCacheEntry>] {
1234 match &self.index {
1235 IndexImplementation::V1 { entries, .. } => entries
1236 .get(key)
1237 .map(|second_level| second_level.as_ref())
1238 .unwrap_or(&[]),
1239 }
1240 }
1241
1242 pub fn sort_and_unload(&mut self, shrink_to: PercentageInteger) {
1244 let mut sorted_candidates = self.get_flattened_entries(true, true);
1245 sorted_candidates
1246 .sort_by_cached_key(|(_id, program)| program.tx_usage_counter.load(Ordering::Relaxed));
1247 let num_to_unload = sorted_candidates
1248 .len()
1249 .saturating_sub(shrink_to.apply_to(MAX_LOADED_ENTRY_COUNT));
1250 self.unload_program_entries(sorted_candidates.iter().take(num_to_unload));
1251 }
1252
1253 pub fn evict_using_2s_random_selection(&mut self, shrink_to: PercentageInteger, now: Slot) {
1256 let mut candidates = self.get_flattened_entries(true, true);
1257 self.stats
1258 .water_level
1259 .store(candidates.len() as u64, Ordering::Relaxed);
1260 let num_to_unload = candidates
1261 .len()
1262 .saturating_sub(shrink_to.apply_to(MAX_LOADED_ENTRY_COUNT));
1263 fn random_index_and_usage_counter(
1264 candidates: &[(Pubkey, Arc<ProgramCacheEntry>)],
1265 now: Slot,
1266 ) -> (usize, u64) {
1267 let mut rng = thread_rng();
1268 let index = rng.gen_range(0..candidates.len());
1269 let usage_counter = candidates
1270 .get(index)
1271 .expect("Failed to get cached entry")
1272 .1
1273 .decayed_usage_counter(now);
1274 (index, usage_counter)
1275 }
1276
1277 for _ in 0..num_to_unload {
1278 let (index1, usage_counter1) = random_index_and_usage_counter(&candidates, now);
1279 let (index2, usage_counter2) = random_index_and_usage_counter(&candidates, now);
1280
1281 let (program, entry) = if usage_counter1 < usage_counter2 {
1282 candidates.swap_remove(index1)
1283 } else {
1284 candidates.swap_remove(index2)
1285 };
1286 self.unload_program_entry(&program, &entry);
1287 }
1288 }
1289
1290 pub fn remove_programs(&mut self, keys: impl Iterator<Item = Pubkey>) {
1292 match &mut self.index {
1293 IndexImplementation::V1 { entries, .. } => {
1294 for k in keys {
1295 entries.remove(&k);
1296 }
1297 }
1298 }
1299 }
1300
1301 fn unload_program_entry(&mut self, program: &Pubkey, remove_entry: &Arc<ProgramCacheEntry>) {
1304 match &mut self.index {
1305 IndexImplementation::V1 { entries, .. } => {
1306 let second_level = entries.get_mut(program).expect("Cache lookup failed");
1307 let candidate = second_level
1308 .iter_mut()
1309 .find(|entry| entry == &remove_entry)
1310 .expect("Program entry not found");
1311
1312 if let Some(unloaded) = candidate.to_unloaded() {
1316 if candidate.tx_usage_counter.load(Ordering::Relaxed) == 1 {
1317 self.stats.one_hit_wonders.fetch_add(1, Ordering::Relaxed);
1318 }
1319 self.stats
1320 .evictions
1321 .entry(*program)
1322 .and_modify(|c| *c = c.saturating_add(1))
1323 .or_insert(1);
1324 *candidate = Arc::new(unloaded);
1325 }
1326 }
1327 }
1328 }
1329
1330 fn unload_program_entries<'a>(
1331 &mut self,
1332 remove: impl Iterator<Item = &'a (Pubkey, Arc<ProgramCacheEntry>)>,
1333 ) {
1334 for (program, entry) in remove {
1335 self.unload_program_entry(program, entry);
1336 }
1337 }
1338
1339 fn remove_programs_with_no_entries(&mut self) {
1340 match &mut self.index {
1341 IndexImplementation::V1 { entries, .. } => {
1342 let num_programs_before_removal = entries.len();
1343 entries.retain(|_key, second_level| !second_level.is_empty());
1344 if entries.len() < num_programs_before_removal {
1345 self.stats.empty_entries.fetch_add(
1346 num_programs_before_removal.saturating_sub(entries.len()) as u64,
1347 Ordering::Relaxed,
1348 );
1349 }
1350 }
1351 }
1352 }
1353}
1354
1355#[cfg(feature = "frozen-abi")]
1356impl solana_frozen_abi::abi_example::AbiExample for ProgramCacheEntry {
1357 fn example() -> Self {
1358 Self::default()
1360 }
1361}
1362
1363#[cfg(feature = "frozen-abi")]
1364impl<FG: ForkGraph> solana_frozen_abi::abi_example::AbiExample for ProgramCache<FG> {
1365 fn example() -> Self {
1366 Self::new(Slot::default(), Epoch::default())
1368 }
1369}
1370
1371#[cfg(test)]
1372mod tests {
1373 use {
1374 crate::loaded_programs::{
1375 BlockRelation, ForkGraph, ProgramCache, ProgramCacheEntry, ProgramCacheEntryOwner,
1376 ProgramCacheEntryType, ProgramCacheForTxBatch, ProgramCacheMatchCriteria,
1377 ProgramRuntimeEnvironment, ProgramRuntimeEnvironments, DELAY_VISIBILITY_SLOT_OFFSET,
1378 },
1379 assert_matches::assert_matches,
1380 percentage::Percentage,
1381 solana_clock::Slot,
1382 solana_pubkey::Pubkey,
1383 solana_sbpf::{elf::Executable, program::BuiltinProgram},
1384 std::{
1385 fs::File,
1386 io::Read,
1387 ops::ControlFlow,
1388 sync::{
1389 atomic::{AtomicU64, Ordering},
1390 Arc, RwLock,
1391 },
1392 },
1393 test_case::{test_case, test_matrix},
1394 };
1395
1396 static MOCK_ENVIRONMENT: std::sync::OnceLock<ProgramRuntimeEnvironment> =
1397 std::sync::OnceLock::<ProgramRuntimeEnvironment>::new();
1398
1399 fn get_mock_env() -> ProgramRuntimeEnvironment {
1400 MOCK_ENVIRONMENT
1401 .get_or_init(|| Arc::new(BuiltinProgram::new_mock()))
1402 .clone()
1403 }
1404
1405 fn new_mock_cache<FG: ForkGraph>() -> ProgramCache<FG> {
1406 let mut cache = ProgramCache::new(0, 0);
1407 cache.environments.program_runtime_v1 = get_mock_env();
1408 cache
1409 }
1410
1411 fn new_test_entry(deployment_slot: Slot, effective_slot: Slot) -> Arc<ProgramCacheEntry> {
1412 new_test_entry_with_usage(deployment_slot, effective_slot, AtomicU64::default())
1413 }
1414
1415 fn new_loaded_entry(env: ProgramRuntimeEnvironment) -> ProgramCacheEntryType {
1416 let mut elf = Vec::new();
1417 File::open("../programs/bpf_loader/test_elfs/out/noop_aligned.so")
1418 .unwrap()
1419 .read_to_end(&mut elf)
1420 .unwrap();
1421 let executable = Executable::load(&elf, env).unwrap();
1422 ProgramCacheEntryType::Loaded(executable)
1423 }
1424
1425 fn new_test_entry_with_usage(
1426 deployment_slot: Slot,
1427 effective_slot: Slot,
1428 usage_counter: AtomicU64,
1429 ) -> Arc<ProgramCacheEntry> {
1430 Arc::new(ProgramCacheEntry {
1431 program: new_loaded_entry(get_mock_env()),
1432 account_owner: ProgramCacheEntryOwner::LoaderV2,
1433 account_size: 0,
1434 deployment_slot,
1435 effective_slot,
1436 tx_usage_counter: usage_counter,
1437 ix_usage_counter: AtomicU64::default(),
1438 latest_access_slot: AtomicU64::new(deployment_slot),
1439 })
1440 }
1441
1442 fn new_test_builtin_entry(
1443 deployment_slot: Slot,
1444 effective_slot: Slot,
1445 ) -> Arc<ProgramCacheEntry> {
1446 Arc::new(ProgramCacheEntry {
1447 program: ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1448 account_owner: ProgramCacheEntryOwner::NativeLoader,
1449 account_size: 0,
1450 deployment_slot,
1451 effective_slot,
1452 tx_usage_counter: AtomicU64::default(),
1453 ix_usage_counter: AtomicU64::default(),
1454 latest_access_slot: AtomicU64::default(),
1455 })
1456 }
1457
1458 fn set_tombstone<FG: ForkGraph>(
1459 cache: &mut ProgramCache<FG>,
1460 key: Pubkey,
1461 slot: Slot,
1462 reason: ProgramCacheEntryType,
1463 ) -> Arc<ProgramCacheEntry> {
1464 let program = Arc::new(ProgramCacheEntry::new_tombstone(
1465 slot,
1466 ProgramCacheEntryOwner::LoaderV2,
1467 reason,
1468 ));
1469 cache.assign_program(key, program.clone());
1470 program
1471 }
1472
1473 fn insert_unloaded_entry<FG: ForkGraph>(
1474 cache: &mut ProgramCache<FG>,
1475 key: Pubkey,
1476 slot: Slot,
1477 ) -> Arc<ProgramCacheEntry> {
1478 let loaded = new_test_entry_with_usage(slot, slot.saturating_add(1), AtomicU64::default());
1479 let unloaded = Arc::new(loaded.to_unloaded().expect("Failed to unload the program"));
1480 cache.assign_program(key, unloaded.clone());
1481 unloaded
1482 }
1483
1484 fn num_matching_entries<P, FG>(cache: &ProgramCache<FG>, predicate: P) -> usize
1485 where
1486 P: Fn(&ProgramCacheEntryType) -> bool,
1487 FG: ForkGraph,
1488 {
1489 cache
1490 .get_flattened_entries_for_tests()
1491 .iter()
1492 .filter(|(_key, program)| predicate(&program.program))
1493 .count()
1494 }
1495
1496 #[test]
1497 fn test_usage_counter_decay() {
1498 let _cache = new_mock_cache::<TestForkGraph>();
1499 let program = new_test_entry_with_usage(10, 11, AtomicU64::new(32));
1500 program.update_access_slot(15);
1501 assert_eq!(program.decayed_usage_counter(15), 32);
1502 assert_eq!(program.decayed_usage_counter(16), 16);
1503 assert_eq!(program.decayed_usage_counter(17), 8);
1504 assert_eq!(program.decayed_usage_counter(18), 4);
1505 assert_eq!(program.decayed_usage_counter(19), 2);
1506 assert_eq!(program.decayed_usage_counter(20), 1);
1507 assert_eq!(program.decayed_usage_counter(21), 0);
1508 assert_eq!(program.decayed_usage_counter(15), 32);
1509 assert_eq!(program.decayed_usage_counter(14), 32);
1510
1511 program.update_access_slot(18);
1512 assert_eq!(program.decayed_usage_counter(15), 32);
1513 assert_eq!(program.decayed_usage_counter(16), 32);
1514 assert_eq!(program.decayed_usage_counter(17), 32);
1515 assert_eq!(program.decayed_usage_counter(18), 32);
1516 assert_eq!(program.decayed_usage_counter(19), 16);
1517 assert_eq!(program.decayed_usage_counter(20), 8);
1518 assert_eq!(program.decayed_usage_counter(21), 4);
1519
1520 assert_eq!(program.decayed_usage_counter(18 + 63), 0);
1522 assert_eq!(program.decayed_usage_counter(100), 0);
1523 }
1524
1525 fn program_deploy_test_helper(
1526 cache: &mut ProgramCache<TestForkGraph>,
1527 program: Pubkey,
1528 deployment_slots: Vec<Slot>,
1529 usage_counters: Vec<u64>,
1530 programs: &mut Vec<(Pubkey, Slot, u64)>,
1531 ) {
1532 deployment_slots
1534 .iter()
1535 .enumerate()
1536 .for_each(|(i, deployment_slot)| {
1537 let usage_counter = *usage_counters.get(i).unwrap_or(&0);
1538 cache.assign_program(
1539 program,
1540 new_test_entry_with_usage(
1541 *deployment_slot,
1542 (*deployment_slot).saturating_add(2),
1543 AtomicU64::new(usage_counter),
1544 ),
1545 );
1546 programs.push((program, *deployment_slot, usage_counter));
1547 });
1548
1549 let env = Arc::new(BuiltinProgram::new_mock());
1551 for slot in 21..31 {
1552 set_tombstone(
1553 cache,
1554 program,
1555 slot,
1556 ProgramCacheEntryType::FailedVerification(env.clone()),
1557 );
1558 }
1559
1560 for slot in 31..41 {
1562 insert_unloaded_entry(cache, program, slot);
1563 }
1564 }
1565
1566 #[test]
1567 fn test_random_eviction() {
1568 let mut programs = vec![];
1569
1570 let mut cache = new_mock_cache::<TestForkGraph>();
1571
1572 program_deploy_test_helper(
1578 &mut cache,
1579 Pubkey::new_unique(),
1580 vec![0, 10, 20],
1581 vec![4, 5, 25],
1582 &mut programs,
1583 );
1584
1585 program_deploy_test_helper(
1587 &mut cache,
1588 Pubkey::new_unique(),
1589 vec![5, 11],
1590 vec![0, 2],
1591 &mut programs,
1592 );
1593
1594 program_deploy_test_helper(
1596 &mut cache,
1597 Pubkey::new_unique(),
1598 vec![0, 5, 15],
1599 vec![100, 3, 20],
1600 &mut programs,
1601 );
1602
1603 let num_loaded_expected = 8;
1605 let num_unloaded_expected = 30;
1607 let num_tombstones_expected = 30;
1609
1610 programs.sort_by_key(|(_id, _slot, usage_count)| *usage_count);
1612 let num_loaded = num_matching_entries(&cache, |program_type| {
1613 matches!(program_type, ProgramCacheEntryType::Loaded(_))
1614 });
1615 let num_unloaded = num_matching_entries(&cache, |program_type| {
1616 matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1617 });
1618 let num_tombstones = num_matching_entries(&cache, |program_type| {
1619 matches!(
1620 program_type,
1621 ProgramCacheEntryType::DelayVisibility
1622 | ProgramCacheEntryType::FailedVerification(_)
1623 | ProgramCacheEntryType::Closed
1624 )
1625 });
1626
1627 assert_eq!(num_loaded, num_loaded_expected);
1629 assert_eq!(num_unloaded, num_unloaded_expected);
1630 assert_eq!(num_tombstones, num_tombstones_expected);
1631
1632 let eviction_pct = 1;
1634
1635 let num_loaded_expected =
1636 Percentage::from(eviction_pct).apply_to(crate::loaded_programs::MAX_LOADED_ENTRY_COUNT);
1637 let num_unloaded_expected = num_unloaded_expected + num_loaded - num_loaded_expected;
1638 cache.evict_using_2s_random_selection(Percentage::from(eviction_pct), 21);
1639
1640 let num_loaded = num_matching_entries(&cache, |program_type| {
1642 matches!(program_type, ProgramCacheEntryType::Loaded(_))
1643 });
1644 let num_unloaded = num_matching_entries(&cache, |program_type| {
1645 matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1646 });
1647 let num_tombstones = num_matching_entries(&cache, |program_type| {
1648 matches!(program_type, ProgramCacheEntryType::FailedVerification(_))
1649 });
1650
1651 assert_eq!(num_loaded, num_loaded_expected);
1653 assert_eq!(num_unloaded, num_unloaded_expected);
1655 assert_eq!(num_tombstones, num_tombstones_expected);
1657 }
1658
1659 #[test]
1660 fn test_eviction() {
1661 let mut programs = vec![];
1662 let mut cache = new_mock_cache::<TestForkGraph>();
1663
1664 program_deploy_test_helper(
1666 &mut cache,
1667 Pubkey::new_unique(),
1668 vec![0, 10, 20],
1669 vec![4, 5, 25],
1670 &mut programs,
1671 );
1672
1673 program_deploy_test_helper(
1675 &mut cache,
1676 Pubkey::new_unique(),
1677 vec![5, 11],
1678 vec![0, 2],
1679 &mut programs,
1680 );
1681
1682 program_deploy_test_helper(
1684 &mut cache,
1685 Pubkey::new_unique(),
1686 vec![0, 5, 15],
1687 vec![100, 3, 20],
1688 &mut programs,
1689 );
1690
1691 let num_loaded_expected = 8;
1693 let num_unloaded_expected = 30;
1695 let num_tombstones_expected = 30;
1697
1698 programs.sort_by_key(|(_id, _slot, usage_count)| *usage_count);
1700 let num_loaded = num_matching_entries(&cache, |program_type| {
1701 matches!(program_type, ProgramCacheEntryType::Loaded(_))
1702 });
1703 let num_unloaded = num_matching_entries(&cache, |program_type| {
1704 matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1705 });
1706 let num_tombstones = num_matching_entries(&cache, |program_type| {
1707 matches!(program_type, ProgramCacheEntryType::FailedVerification(_))
1708 });
1709
1710 assert_eq!(num_loaded, num_loaded_expected);
1712 assert_eq!(num_unloaded, num_unloaded_expected);
1713 assert_eq!(num_tombstones, num_tombstones_expected);
1714
1715 let eviction_pct = 1;
1717
1718 let num_loaded_expected =
1719 Percentage::from(eviction_pct).apply_to(crate::loaded_programs::MAX_LOADED_ENTRY_COUNT);
1720 let num_unloaded_expected = num_unloaded_expected + num_loaded - num_loaded_expected;
1721
1722 cache.sort_and_unload(Percentage::from(eviction_pct));
1723
1724 let entries = cache.get_flattened_entries_for_tests();
1726 programs.iter().for_each(|entry| {
1727 assert!(entries.iter().any(|(key, _entry)| key == &entry.0));
1728 });
1729
1730 let unloaded = entries
1731 .iter()
1732 .filter_map(|(key, program)| {
1733 matches!(program.program, ProgramCacheEntryType::Unloaded(_))
1734 .then_some((*key, program.tx_usage_counter.load(Ordering::Relaxed)))
1735 })
1736 .collect::<Vec<(Pubkey, u64)>>();
1737
1738 for index in 0..3 {
1739 let expected = programs.get(index).expect("Missing program");
1740 assert!(unloaded.contains(&(expected.0, expected.2)));
1741 }
1742
1743 let num_loaded = num_matching_entries(&cache, |program_type| {
1745 matches!(program_type, ProgramCacheEntryType::Loaded(_))
1746 });
1747 let num_unloaded = num_matching_entries(&cache, |program_type| {
1748 matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1749 });
1750 let num_tombstones = num_matching_entries(&cache, |program_type| {
1751 matches!(
1752 program_type,
1753 ProgramCacheEntryType::DelayVisibility
1754 | ProgramCacheEntryType::FailedVerification(_)
1755 | ProgramCacheEntryType::Closed
1756 )
1757 });
1758
1759 assert_eq!(num_loaded, num_loaded_expected);
1761 assert_eq!(num_unloaded, num_unloaded_expected);
1763 assert_eq!(num_tombstones, num_tombstones_expected);
1765 }
1766
1767 #[test]
1768 fn test_usage_count_of_unloaded_program() {
1769 let mut cache = new_mock_cache::<TestForkGraph>();
1770
1771 let program = Pubkey::new_unique();
1772 let evict_to_pct = 2;
1773 let cache_capacity_after_shrink =
1774 Percentage::from(evict_to_pct).apply_to(crate::loaded_programs::MAX_LOADED_ENTRY_COUNT);
1775 let num_total_programs = (cache_capacity_after_shrink + 1) as u64;
1777 (0..num_total_programs).for_each(|i| {
1778 cache.assign_program(
1779 program,
1780 new_test_entry_with_usage(i, i + 2, AtomicU64::new(i + 10)),
1781 );
1782 });
1783
1784 cache.sort_and_unload(Percentage::from(evict_to_pct));
1785
1786 let num_unloaded = num_matching_entries(&cache, |program_type| {
1787 matches!(program_type, ProgramCacheEntryType::Unloaded(_))
1788 });
1789 assert_eq!(num_unloaded, 1);
1790
1791 cache
1792 .get_flattened_entries_for_tests()
1793 .iter()
1794 .for_each(|(_key, program)| {
1795 if matches!(program.program, ProgramCacheEntryType::Unloaded(_)) {
1796 assert_eq!(program.tx_usage_counter.load(Ordering::Relaxed), 10);
1798 assert_eq!(program.deployment_slot, 0);
1799 assert_eq!(program.effective_slot, 2);
1800 }
1801 });
1802
1803 cache.assign_program(program, new_test_entry_with_usage(0, 2, AtomicU64::new(0)));
1806
1807 cache
1808 .get_flattened_entries_for_tests()
1809 .iter()
1810 .for_each(|(_key, program)| {
1811 if matches!(program.program, ProgramCacheEntryType::Unloaded(_))
1812 && program.deployment_slot == 0
1813 && program.effective_slot == 2
1814 {
1815 assert_eq!(program.tx_usage_counter.load(Ordering::Relaxed), 10);
1817 }
1818 });
1819 }
1820
1821 #[test]
1822 fn test_fuzz_assign_program_order() {
1823 use rand::prelude::SliceRandom;
1824 const EXPECTED_ENTRIES: [(u64, u64); 7] =
1825 [(1, 2), (5, 5), (5, 6), (5, 10), (9, 10), (10, 10), (3, 12)];
1826 let mut rng = rand::thread_rng();
1827 let program_id = Pubkey::new_unique();
1828 for _ in 0..1000 {
1829 let mut entries = EXPECTED_ENTRIES.to_vec();
1830 entries.shuffle(&mut rng);
1831 let mut cache = new_mock_cache::<TestForkGraph>();
1832 for (deployment_slot, effective_slot) in entries {
1833 assert!(!cache
1834 .assign_program(program_id, new_test_entry(deployment_slot, effective_slot)));
1835 }
1836 for ((deployment_slot, effective_slot), entry) in EXPECTED_ENTRIES
1837 .iter()
1838 .zip(cache.get_slot_versions_for_tests(&program_id).iter())
1839 {
1840 assert_eq!(entry.deployment_slot, *deployment_slot);
1841 assert_eq!(entry.effective_slot, *effective_slot);
1842 }
1843 }
1844 }
1845
1846 #[test_matrix(
1847 (
1848 ProgramCacheEntryType::Closed,
1849 ProgramCacheEntryType::FailedVerification(get_mock_env()),
1850 new_loaded_entry(get_mock_env()),
1851 ),
1852 (
1853 ProgramCacheEntryType::FailedVerification(get_mock_env()),
1854 ProgramCacheEntryType::Closed,
1855 ProgramCacheEntryType::Unloaded(get_mock_env()),
1856 new_loaded_entry(get_mock_env()),
1857 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1858 )
1859 )]
1860 #[test_matrix(
1861 (
1862 ProgramCacheEntryType::Unloaded(get_mock_env()),
1863 ),
1864 (
1865 ProgramCacheEntryType::FailedVerification(get_mock_env()),
1866 ProgramCacheEntryType::Closed,
1867 ProgramCacheEntryType::Unloaded(get_mock_env()),
1868 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1869 )
1870 )]
1871 #[test_matrix(
1872 (ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),),
1873 (
1874 ProgramCacheEntryType::FailedVerification(get_mock_env()),
1875 ProgramCacheEntryType::Closed,
1876 ProgramCacheEntryType::Unloaded(get_mock_env()),
1877 new_loaded_entry(get_mock_env()),
1878 )
1879 )]
1880 #[should_panic(expected = "Unexpected replacement of an entry")]
1881 fn test_assign_program_failure(old: ProgramCacheEntryType, new: ProgramCacheEntryType) {
1882 let mut cache = new_mock_cache::<TestForkGraph>();
1883 let program_id = Pubkey::new_unique();
1884 assert!(!cache.assign_program(
1885 program_id,
1886 Arc::new(ProgramCacheEntry {
1887 program: old,
1888 account_owner: ProgramCacheEntryOwner::LoaderV2,
1889 account_size: 0,
1890 deployment_slot: 10,
1891 effective_slot: 11,
1892 tx_usage_counter: AtomicU64::default(),
1893 ix_usage_counter: AtomicU64::default(),
1894 latest_access_slot: AtomicU64::default(),
1895 }),
1896 ));
1897 cache.assign_program(
1898 program_id,
1899 Arc::new(ProgramCacheEntry {
1900 program: new,
1901 account_owner: ProgramCacheEntryOwner::LoaderV2,
1902 account_size: 0,
1903 deployment_slot: 10,
1904 effective_slot: 11,
1905 tx_usage_counter: AtomicU64::default(),
1906 ix_usage_counter: AtomicU64::default(),
1907 latest_access_slot: AtomicU64::default(),
1908 }),
1909 );
1910 }
1911
1912 #[test_case(
1913 ProgramCacheEntryType::Unloaded(Arc::new(BuiltinProgram::new_mock())),
1914 new_loaded_entry(get_mock_env())
1915 )]
1916 #[test_case(
1917 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock()),
1918 ProgramCacheEntryType::Builtin(BuiltinProgram::new_mock())
1919 )]
1920 fn test_assign_program_success(old: ProgramCacheEntryType, new: ProgramCacheEntryType) {
1921 let mut cache = new_mock_cache::<TestForkGraph>();
1922 let program_id = Pubkey::new_unique();
1923 assert!(!cache.assign_program(
1924 program_id,
1925 Arc::new(ProgramCacheEntry {
1926 program: old,
1927 account_owner: ProgramCacheEntryOwner::LoaderV2,
1928 account_size: 0,
1929 deployment_slot: 10,
1930 effective_slot: 11,
1931 tx_usage_counter: AtomicU64::default(),
1932 ix_usage_counter: AtomicU64::default(),
1933 latest_access_slot: AtomicU64::default(),
1934 }),
1935 ));
1936 assert!(!cache.assign_program(
1937 program_id,
1938 Arc::new(ProgramCacheEntry {
1939 program: new,
1940 account_owner: ProgramCacheEntryOwner::LoaderV2,
1941 account_size: 0,
1942 deployment_slot: 10,
1943 effective_slot: 11,
1944 tx_usage_counter: AtomicU64::default(),
1945 ix_usage_counter: AtomicU64::default(),
1946 latest_access_slot: AtomicU64::default(),
1947 }),
1948 ));
1949 }
1950
1951 #[test]
1952 fn test_tombstone() {
1953 let env = Arc::new(BuiltinProgram::new_mock());
1954 let tombstone = ProgramCacheEntry::new_tombstone(
1955 0,
1956 ProgramCacheEntryOwner::LoaderV2,
1957 ProgramCacheEntryType::FailedVerification(env.clone()),
1958 );
1959 assert_matches!(
1960 tombstone.program,
1961 ProgramCacheEntryType::FailedVerification(_)
1962 );
1963 assert!(tombstone.is_tombstone());
1964 assert_eq!(tombstone.deployment_slot, 0);
1965 assert_eq!(tombstone.effective_slot, 0);
1966
1967 let tombstone = ProgramCacheEntry::new_tombstone(
1968 100,
1969 ProgramCacheEntryOwner::LoaderV2,
1970 ProgramCacheEntryType::Closed,
1971 );
1972 assert_matches!(tombstone.program, ProgramCacheEntryType::Closed);
1973 assert!(tombstone.is_tombstone());
1974 assert_eq!(tombstone.deployment_slot, 100);
1975 assert_eq!(tombstone.effective_slot, 100);
1976
1977 let mut cache = new_mock_cache::<TestForkGraph>();
1978 let program1 = Pubkey::new_unique();
1979 let tombstone = set_tombstone(
1980 &mut cache,
1981 program1,
1982 10,
1983 ProgramCacheEntryType::FailedVerification(env.clone()),
1984 );
1985 let slot_versions = cache.get_slot_versions_for_tests(&program1);
1986 assert_eq!(slot_versions.len(), 1);
1987 assert!(slot_versions.first().unwrap().is_tombstone());
1988 assert_eq!(tombstone.deployment_slot, 10);
1989 assert_eq!(tombstone.effective_slot, 10);
1990
1991 let program2 = Pubkey::new_unique();
1993 cache.assign_program(program2, new_test_builtin_entry(50, 51));
1994 let slot_versions = cache.get_slot_versions_for_tests(&program2);
1995 assert_eq!(slot_versions.len(), 1);
1996 assert!(!slot_versions.first().unwrap().is_tombstone());
1997
1998 let tombstone = set_tombstone(
1999 &mut cache,
2000 program2,
2001 60,
2002 ProgramCacheEntryType::FailedVerification(env),
2003 );
2004 let slot_versions = cache.get_slot_versions_for_tests(&program2);
2005 assert_eq!(slot_versions.len(), 2);
2006 assert!(!slot_versions.first().unwrap().is_tombstone());
2007 assert!(slot_versions.get(1).unwrap().is_tombstone());
2008 assert!(tombstone.is_tombstone());
2009 assert_eq!(tombstone.deployment_slot, 60);
2010 assert_eq!(tombstone.effective_slot, 60);
2011 }
2012
2013 struct TestForkGraph {
2014 relation: BlockRelation,
2015 }
2016 impl ForkGraph for TestForkGraph {
2017 fn relationship(&self, _a: Slot, _b: Slot) -> BlockRelation {
2018 self.relation
2019 }
2020 }
2021
2022 #[test]
2023 fn test_prune_empty() {
2024 let mut cache = new_mock_cache::<TestForkGraph>();
2025 let fork_graph = Arc::new(RwLock::new(TestForkGraph {
2026 relation: BlockRelation::Unrelated,
2027 }));
2028
2029 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2030
2031 cache.prune(0, 0);
2032 assert!(cache.get_flattened_entries_for_tests().is_empty());
2033
2034 cache.prune(10, 0);
2035 assert!(cache.get_flattened_entries_for_tests().is_empty());
2036
2037 let mut cache = new_mock_cache::<TestForkGraph>();
2038 let fork_graph = Arc::new(RwLock::new(TestForkGraph {
2039 relation: BlockRelation::Ancestor,
2040 }));
2041
2042 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2043
2044 cache.prune(0, 0);
2045 assert!(cache.get_flattened_entries_for_tests().is_empty());
2046
2047 cache.prune(10, 0);
2048 assert!(cache.get_flattened_entries_for_tests().is_empty());
2049
2050 let mut cache = new_mock_cache::<TestForkGraph>();
2051 let fork_graph = Arc::new(RwLock::new(TestForkGraph {
2052 relation: BlockRelation::Descendant,
2053 }));
2054
2055 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2056
2057 cache.prune(0, 0);
2058 assert!(cache.get_flattened_entries_for_tests().is_empty());
2059
2060 cache.prune(10, 0);
2061 assert!(cache.get_flattened_entries_for_tests().is_empty());
2062
2063 let mut cache = new_mock_cache::<TestForkGraph>();
2064 let fork_graph = Arc::new(RwLock::new(TestForkGraph {
2065 relation: BlockRelation::Unknown,
2066 }));
2067 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2068
2069 cache.prune(0, 0);
2070 assert!(cache.get_flattened_entries_for_tests().is_empty());
2071
2072 cache.prune(10, 0);
2073 assert!(cache.get_flattened_entries_for_tests().is_empty());
2074 }
2075
2076 #[test]
2077 fn test_prune_different_env() {
2078 let mut cache = new_mock_cache::<TestForkGraph>();
2079
2080 let fork_graph = Arc::new(RwLock::new(TestForkGraph {
2081 relation: BlockRelation::Ancestor,
2082 }));
2083
2084 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2085
2086 let program1 = Pubkey::new_unique();
2087 cache.assign_program(program1, new_test_entry(10, 10));
2088
2089 let new_env = Arc::new(BuiltinProgram::new_mock());
2090 cache.upcoming_environments = Some(ProgramRuntimeEnvironments {
2091 program_runtime_v1: new_env.clone(),
2092 program_runtime_v2: new_env.clone(),
2093 });
2094 let updated_program = Arc::new(ProgramCacheEntry {
2095 program: new_loaded_entry(new_env.clone()),
2096 account_owner: ProgramCacheEntryOwner::LoaderV2,
2097 account_size: 0,
2098 deployment_slot: 20,
2099 effective_slot: 20,
2100 tx_usage_counter: AtomicU64::default(),
2101 ix_usage_counter: AtomicU64::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, u64))> {
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, 1))
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, u64))],
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);
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);
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);
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);
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);
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);
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);
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);
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);
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 .0 = ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(5);
2462 missing.get_mut(1).unwrap().1 .0 = 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);
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);
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);
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);
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, 1))];
2555 let mut extracted = ProgramCacheForTxBatch::new(0, cache.environments.clone(), None, 0);
2556 cache.extract(&mut missing, &mut extracted, 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: AtomicU64::default(),
2578 ix_usage_counter: AtomicU64::default(),
2579 latest_access_slot: AtomicU64::default(),
2580 });
2581 assert!(entry.to_unloaded().is_none());
2582
2583 let program_id = Pubkey::new_unique();
2585 cache.assign_program(program_id, entry.clone());
2586 cache.unload_program_entry(&program_id, &entry);
2587 assert_eq!(cache.get_slot_versions_for_tests(&program_id).len(), 1);
2588 assert!(cache.stats.evictions.is_empty());
2589 }
2590
2591 let entry = new_test_entry_with_usage(1, 2, AtomicU64::new(3));
2592 let unloaded_entry = entry.to_unloaded().unwrap();
2593 assert_eq!(unloaded_entry.deployment_slot, 1);
2594 assert_eq!(unloaded_entry.effective_slot, 2);
2595 assert_eq!(unloaded_entry.latest_access_slot.load(Ordering::Relaxed), 1);
2596 assert_eq!(unloaded_entry.tx_usage_counter.load(Ordering::Relaxed), 3);
2597
2598 let program_id = Pubkey::new_unique();
2600 cache.assign_program(program_id, entry.clone());
2601 cache.unload_program_entry(&program_id, &entry);
2602 assert!(cache.stats.evictions.contains_key(&program_id));
2603 }
2604
2605 #[test]
2606 fn test_fork_prune_find_first_ancestor() {
2607 let mut cache = new_mock_cache::<TestForkGraphSpecific>();
2608
2609 let mut fork_graph = TestForkGraphSpecific::default();
2620 fork_graph.insert_fork(&[0, 10, 20]);
2621 fork_graph.insert_fork(&[0, 5]);
2622 let fork_graph = Arc::new(RwLock::new(fork_graph));
2623 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2624
2625 let program1 = Pubkey::new_unique();
2626 cache.assign_program(program1, new_test_entry(0, 1));
2627 cache.assign_program(program1, new_test_entry(5, 6));
2628
2629 cache.prune(10, 0);
2630
2631 let mut missing = get_entries_to_load(&cache, 20, &[program1]);
2632 let mut extracted = ProgramCacheForTxBatch::new(20, cache.environments.clone(), None, 0);
2633 cache.extract(&mut missing, &mut extracted, true);
2634
2635 assert_eq!(
2637 extracted
2638 .find(&program1)
2639 .expect("Did not find the program")
2640 .deployment_slot,
2641 0
2642 );
2643 }
2644
2645 #[test]
2646 fn test_prune_by_deployment_slot() {
2647 let mut cache = new_mock_cache::<TestForkGraphSpecific>();
2648
2649 let mut fork_graph = TestForkGraphSpecific::default();
2660 fork_graph.insert_fork(&[0, 10, 20]);
2661 fork_graph.insert_fork(&[0, 5, 6]);
2662 let fork_graph = Arc::new(RwLock::new(fork_graph));
2663 cache.set_fork_graph(Arc::downgrade(&fork_graph));
2664
2665 let program1 = Pubkey::new_unique();
2666 cache.assign_program(program1, new_test_entry(0, 1));
2667 cache.assign_program(program1, new_test_entry(5, 6));
2668
2669 let program2 = Pubkey::new_unique();
2670 cache.assign_program(program2, new_test_entry(10, 11));
2671
2672 let mut missing = get_entries_to_load(&cache, 20, &[program1, program2]);
2673 let mut extracted = ProgramCacheForTxBatch::new(20, cache.environments.clone(), None, 0);
2674 cache.extract(&mut missing, &mut extracted, true);
2675 assert!(match_slot(&extracted, &program1, 0, 20));
2676 assert!(match_slot(&extracted, &program2, 10, 20));
2677
2678 let mut missing = get_entries_to_load(&cache, 6, &[program1, program2]);
2679 assert!(match_missing(&missing, &program2, false));
2680 let mut extracted = ProgramCacheForTxBatch::new(6, cache.environments.clone(), None, 0);
2681 cache.extract(&mut missing, &mut extracted, true);
2682 assert!(match_slot(&extracted, &program1, 5, 6));
2683
2684 cache.prune_by_deployment_slot(5);
2687
2688 let mut missing = get_entries_to_load(&cache, 20, &[program1, program2]);
2689 let mut extracted = ProgramCacheForTxBatch::new(20, cache.environments.clone(), None, 0);
2690 cache.extract(&mut missing, &mut extracted, true);
2691 assert!(match_slot(&extracted, &program1, 0, 20));
2692 assert!(match_slot(&extracted, &program2, 10, 20));
2693
2694 let mut missing = get_entries_to_load(&cache, 6, &[program1, program2]);
2695 assert!(match_missing(&missing, &program2, false));
2696 let mut extracted = ProgramCacheForTxBatch::new(6, cache.environments.clone(), None, 0);
2697 cache.extract(&mut missing, &mut extracted, true);
2698 assert!(match_slot(&extracted, &program1, 0, 6));
2699
2700 cache.prune_by_deployment_slot(10);
2703
2704 let mut missing = get_entries_to_load(&cache, 20, &[program1, program2]);
2705 assert!(match_missing(&missing, &program2, false));
2706 let mut extracted = ProgramCacheForTxBatch::new(20, cache.environments.clone(), None, 0);
2707 cache.extract(&mut missing, &mut extracted, true);
2708 assert!(match_slot(&extracted, &program1, 0, 20));
2709 }
2710
2711 #[test]
2712 fn test_usable_entries_for_slot() {
2713 new_mock_cache::<TestForkGraph>();
2714 let tombstone = Arc::new(ProgramCacheEntry::new_tombstone(
2715 0,
2716 ProgramCacheEntryOwner::LoaderV2,
2717 ProgramCacheEntryType::Closed,
2718 ));
2719
2720 assert!(ProgramCache::<TestForkGraph>::matches_criteria(
2721 &tombstone,
2722 &ProgramCacheMatchCriteria::NoCriteria
2723 ));
2724
2725 assert!(ProgramCache::<TestForkGraph>::matches_criteria(
2726 &tombstone,
2727 &ProgramCacheMatchCriteria::Tombstone
2728 ));
2729
2730 assert!(ProgramCache::<TestForkGraph>::matches_criteria(
2731 &tombstone,
2732 &ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(0)
2733 ));
2734
2735 assert!(!ProgramCache::<TestForkGraph>::matches_criteria(
2736 &tombstone,
2737 &ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(1)
2738 ));
2739
2740 let program = new_test_entry(0, 1);
2741
2742 assert!(ProgramCache::<TestForkGraph>::matches_criteria(
2743 &program,
2744 &ProgramCacheMatchCriteria::NoCriteria
2745 ));
2746
2747 assert!(!ProgramCache::<TestForkGraph>::matches_criteria(
2748 &program,
2749 &ProgramCacheMatchCriteria::Tombstone
2750 ));
2751
2752 assert!(ProgramCache::<TestForkGraph>::matches_criteria(
2753 &program,
2754 &ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(0)
2755 ));
2756
2757 assert!(!ProgramCache::<TestForkGraph>::matches_criteria(
2758 &program,
2759 &ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(1)
2760 ));
2761
2762 let program = Arc::new(new_test_entry_with_usage(0, 1, AtomicU64::default()));
2763
2764 assert!(ProgramCache::<TestForkGraph>::matches_criteria(
2765 &program,
2766 &ProgramCacheMatchCriteria::NoCriteria
2767 ));
2768
2769 assert!(!ProgramCache::<TestForkGraph>::matches_criteria(
2770 &program,
2771 &ProgramCacheMatchCriteria::Tombstone
2772 ));
2773
2774 assert!(ProgramCache::<TestForkGraph>::matches_criteria(
2775 &program,
2776 &ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(0)
2777 ));
2778
2779 assert!(!ProgramCache::<TestForkGraph>::matches_criteria(
2780 &program,
2781 &ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(1)
2782 ));
2783 }
2784}