1use {
2 crate::{
3 invoke_context::{BuiltinFunctionWithContext, InvokeContext},
4 timings::ExecuteDetailsTimings,
5 },
6 log::{debug, error, log_enabled, trace},
7 miraland_measure::measure::Measure,
8 percentage::PercentageInteger,
9 rand::{thread_rng, Rng},
10 solana_rbpf::{
11 elf::Executable,
12 program::{BuiltinProgram, FunctionRegistry},
13 verifier::RequisiteVerifier,
14 vm::Config,
15 },
16 miraland_sdk::{
17 bpf_loader, bpf_loader_deprecated, bpf_loader_upgradeable,
18 clock::{Epoch, Slot},
19 loader_v4,
20 pubkey::Pubkey,
21 saturating_add_assign,
22 },
23 std::{
24 collections::HashMap,
25 fmt::{Debug, Formatter},
26 sync::{
27 atomic::{AtomicU64, Ordering},
28 Arc, Condvar, Mutex, RwLock,
29 },
30 },
31};
32
33pub type ProgramRuntimeEnvironment = Arc<BuiltinProgram<InvokeContext<'static>>>;
34pub const MAX_LOADED_ENTRY_COUNT: usize = 256;
35pub const DELAY_VISIBILITY_SLOT_OFFSET: Slot = 1;
36
37#[derive(Copy, Clone, Debug, PartialEq)]
39pub enum BlockRelation {
40 Ancestor,
42 Equal,
44 Descendant,
46 Unrelated,
48 Unknown,
50}
51
52pub trait ForkGraph {
54 fn relationship(&self, a: Slot, b: Slot) -> BlockRelation;
56
57 fn slot_epoch(&self, _slot: Slot) -> Option<Epoch> {
59 Some(0)
60 }
61}
62
63#[derive(Default)]
65pub enum LoadedProgramType {
66 FailedVerification(ProgramRuntimeEnvironment),
70 #[default]
72 Closed,
73 DelayVisibility,
75 Unloaded(ProgramRuntimeEnvironment),
79 LegacyV0(Executable<InvokeContext<'static>>),
81 LegacyV1(Executable<InvokeContext<'static>>),
83 Typed(Executable<InvokeContext<'static>>),
85 #[cfg(test)]
86 TestLoaded(ProgramRuntimeEnvironment),
87 Builtin(BuiltinProgram<InvokeContext<'static>>),
89}
90
91impl Debug for LoadedProgramType {
92 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
93 match self {
94 LoadedProgramType::FailedVerification(_) => {
95 write!(f, "LoadedProgramType::FailedVerification")
96 }
97 LoadedProgramType::Closed => write!(f, "LoadedProgramType::Closed"),
98 LoadedProgramType::DelayVisibility => write!(f, "LoadedProgramType::DelayVisibility"),
99 LoadedProgramType::Unloaded(_) => write!(f, "LoadedProgramType::Unloaded"),
100 LoadedProgramType::LegacyV0(_) => write!(f, "LoadedProgramType::LegacyV0"),
101 LoadedProgramType::LegacyV1(_) => write!(f, "LoadedProgramType::LegacyV1"),
102 LoadedProgramType::Typed(_) => write!(f, "LoadedProgramType::Typed"),
103 #[cfg(test)]
104 LoadedProgramType::TestLoaded(_) => write!(f, "LoadedProgramType::TestLoaded"),
105 LoadedProgramType::Builtin(_) => write!(f, "LoadedProgramType::Builtin"),
106 }
107 }
108}
109
110impl LoadedProgramType {
111 pub fn get_environment(&self) -> Option<&ProgramRuntimeEnvironment> {
113 match self {
114 LoadedProgramType::LegacyV0(program)
115 | LoadedProgramType::LegacyV1(program)
116 | LoadedProgramType::Typed(program) => Some(program.get_loader()),
117 LoadedProgramType::FailedVerification(env) | LoadedProgramType::Unloaded(env) => {
118 Some(env)
119 }
120 #[cfg(test)]
121 LoadedProgramType::TestLoaded(environment) => Some(environment),
122 _ => None,
123 }
124 }
125}
126
127#[derive(Debug, Default)]
131pub struct LoadedProgram {
132 pub program: LoadedProgramType,
134 pub account_size: usize,
136 pub deployment_slot: Slot,
138 pub effective_slot: Slot,
140 pub tx_usage_counter: AtomicU64,
142 pub ix_usage_counter: AtomicU64,
144 pub latest_access_slot: AtomicU64,
146}
147
148#[derive(Debug, Default)]
150pub struct Stats {
151 pub hits: AtomicU64,
153 pub misses: AtomicU64,
155 pub evictions: HashMap<Pubkey, u64>,
157 pub reloads: AtomicU64,
159 pub insertions: AtomicU64,
161 pub lost_insertions: AtomicU64,
163 pub replacements: AtomicU64,
165 pub one_hit_wonders: AtomicU64,
167 pub prunes_orphan: AtomicU64,
169 pub prunes_environment: AtomicU64,
171 pub empty_entries: AtomicU64,
173}
174
175impl Stats {
176 pub fn submit(&self, slot: Slot) {
178 let hits = self.hits.load(Ordering::Relaxed);
179 let misses = self.misses.load(Ordering::Relaxed);
180 let evictions: u64 = self.evictions.values().sum();
181 let reloads = self.reloads.load(Ordering::Relaxed);
182 let insertions = self.insertions.load(Ordering::Relaxed);
183 let lost_insertions = self.lost_insertions.load(Ordering::Relaxed);
184 let replacements = self.replacements.load(Ordering::Relaxed);
185 let one_hit_wonders = self.one_hit_wonders.load(Ordering::Relaxed);
186 let prunes_orphan = self.prunes_orphan.load(Ordering::Relaxed);
187 let prunes_environment = self.prunes_environment.load(Ordering::Relaxed);
188 let empty_entries = self.empty_entries.load(Ordering::Relaxed);
189 datapoint_info!(
190 "loaded-programs-cache-stats",
191 ("slot", slot, i64),
192 ("hits", hits, i64),
193 ("misses", misses, i64),
194 ("evictions", evictions, i64),
195 ("reloads", reloads, i64),
196 ("insertions", insertions, i64),
197 ("lost_insertions", lost_insertions, i64),
198 ("replacements", replacements, i64),
199 ("one_hit_wonders", one_hit_wonders, i64),
200 ("prunes_orphan", prunes_orphan, i64),
201 ("prunes_environment", prunes_environment, i64),
202 ("empty_entries", empty_entries, i64),
203 );
204 debug!(
205 "Loaded Programs Cache Stats -- Hits: {}, Misses: {}, Evictions: {}, Reloads: {}, Insertions: {} Lost-Insertions: {}, Replacements: {}, One-Hit-Wonders: {}, Prunes-Orphan: {}, Prunes-Environment: {}, Empty: {}",
206 hits, misses, evictions, reloads, insertions, lost_insertions, replacements, one_hit_wonders, prunes_orphan, prunes_environment, empty_entries
207 );
208 if log_enabled!(log::Level::Trace) && !self.evictions.is_empty() {
209 let mut evictions = self.evictions.iter().collect::<Vec<_>>();
210 evictions.sort_by_key(|e| e.1);
211 let evictions = evictions
212 .into_iter()
213 .rev()
214 .map(|(program_id, evictions)| {
215 format!(" {:<44} {}", program_id.to_string(), evictions)
216 })
217 .collect::<Vec<_>>();
218 let evictions = evictions.join("\n");
219 trace!(
220 "Eviction Details:\n {:<44} {}\n{}",
221 "Program",
222 "Count",
223 evictions
224 );
225 }
226 }
227
228 pub fn reset(&mut self) {
229 *self = Stats::default();
230 }
231}
232
233#[derive(Debug, Default)]
235pub struct LoadProgramMetrics {
236 pub program_id: String,
238 pub register_syscalls_us: u64,
240 pub load_elf_us: u64,
242 pub verify_code_us: u64,
244 pub jit_compile_us: u64,
246}
247
248impl LoadProgramMetrics {
249 pub fn submit_datapoint(&self, timings: &mut ExecuteDetailsTimings) {
250 saturating_add_assign!(
251 timings.create_executor_register_syscalls_us,
252 self.register_syscalls_us
253 );
254 saturating_add_assign!(timings.create_executor_load_elf_us, self.load_elf_us);
255 saturating_add_assign!(timings.create_executor_verify_code_us, self.verify_code_us);
256 saturating_add_assign!(timings.create_executor_jit_compile_us, self.jit_compile_us);
257 datapoint_trace!(
258 "create_executor_trace",
259 ("program_id", self.program_id, String),
260 ("register_syscalls_us", self.register_syscalls_us, i64),
261 ("load_elf_us", self.load_elf_us, i64),
262 ("verify_code_us", self.verify_code_us, i64),
263 ("jit_compile_us", self.jit_compile_us, i64),
264 );
265 }
266}
267
268impl PartialEq for LoadedProgram {
269 fn eq(&self, other: &Self) -> bool {
270 self.effective_slot == other.effective_slot
271 && self.deployment_slot == other.deployment_slot
272 && self.is_tombstone() == other.is_tombstone()
273 }
274}
275
276impl LoadedProgram {
277 pub fn new(
279 loader_key: &Pubkey,
280 program_runtime_environment: ProgramRuntimeEnvironment,
281 deployment_slot: Slot,
282 effective_slot: Slot,
283 elf_bytes: &[u8],
284 account_size: usize,
285 metrics: &mut LoadProgramMetrics,
286 ) -> Result<Self, Box<dyn std::error::Error>> {
287 Self::new_internal(
288 loader_key,
289 program_runtime_environment,
290 deployment_slot,
291 effective_slot,
292 elf_bytes,
293 account_size,
294 metrics,
295 false, )
297 }
298
299 pub unsafe fn reload(
308 loader_key: &Pubkey,
309 program_runtime_environment: Arc<BuiltinProgram<InvokeContext<'static>>>,
310 deployment_slot: Slot,
311 effective_slot: Slot,
312 elf_bytes: &[u8],
313 account_size: usize,
314 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 metrics,
324 true, )
326 }
327
328 fn new_internal(
329 loader_key: &Pubkey,
330 program_runtime_environment: Arc<BuiltinProgram<InvokeContext<'static>>>,
331 deployment_slot: Slot,
332 effective_slot: Slot,
333 elf_bytes: &[u8],
334 account_size: usize,
335 metrics: &mut LoadProgramMetrics,
336 reloading: bool,
337 ) -> Result<Self, Box<dyn std::error::Error>> {
338 let mut load_elf_time = Measure::start("load_elf_time");
339 #[allow(unused_mut)]
342 let mut executable = Executable::load(elf_bytes, program_runtime_environment.clone())?;
343 load_elf_time.stop();
344 metrics.load_elf_us = load_elf_time.as_us();
345
346 if !reloading {
347 let mut verify_code_time = Measure::start("verify_code_time");
348 executable.verify::<RequisiteVerifier>()?;
349 verify_code_time.stop();
350 metrics.verify_code_us = verify_code_time.as_us();
351 }
352
353 #[cfg(all(not(target_os = "windows"), target_arch = "x86_64"))]
354 {
355 let mut jit_compile_time = Measure::start("jit_compile_time");
356 executable.jit_compile()?;
357 jit_compile_time.stop();
358 metrics.jit_compile_us = jit_compile_time.as_us();
359 }
360
361 let program = if bpf_loader_deprecated::check_id(loader_key) {
362 LoadedProgramType::LegacyV0(executable)
363 } else if bpf_loader::check_id(loader_key) || bpf_loader_upgradeable::check_id(loader_key) {
364 LoadedProgramType::LegacyV1(executable)
365 } else if loader_v4::check_id(loader_key) {
366 LoadedProgramType::Typed(executable)
367 } else {
368 panic!();
369 };
370
371 Ok(Self {
372 deployment_slot,
373 account_size,
374 effective_slot,
375 tx_usage_counter: AtomicU64::new(0),
376 program,
377 ix_usage_counter: AtomicU64::new(0),
378 latest_access_slot: AtomicU64::new(0),
379 })
380 }
381
382 pub fn to_unloaded(&self) -> Option<Self> {
383 match &self.program {
384 LoadedProgramType::LegacyV0(_)
385 | LoadedProgramType::LegacyV1(_)
386 | LoadedProgramType::Typed(_) => {}
387 #[cfg(test)]
388 LoadedProgramType::TestLoaded(_) => {}
389 LoadedProgramType::FailedVerification(_)
390 | LoadedProgramType::Closed
391 | LoadedProgramType::DelayVisibility
392 | LoadedProgramType::Unloaded(_)
393 | LoadedProgramType::Builtin(_) => {
394 return None;
395 }
396 }
397 Some(Self {
398 program: LoadedProgramType::Unloaded(self.program.get_environment()?.clone()),
399 account_size: self.account_size,
400 deployment_slot: self.deployment_slot,
401 effective_slot: self.effective_slot,
402 tx_usage_counter: AtomicU64::new(self.tx_usage_counter.load(Ordering::Relaxed)),
403 ix_usage_counter: AtomicU64::new(self.ix_usage_counter.load(Ordering::Relaxed)),
404 latest_access_slot: AtomicU64::new(self.latest_access_slot.load(Ordering::Relaxed)),
405 })
406 }
407
408 pub fn new_builtin(
410 deployment_slot: Slot,
411 account_size: usize,
412 builtin_function: BuiltinFunctionWithContext,
413 ) -> Self {
414 let mut function_registry = FunctionRegistry::default();
415 function_registry
416 .register_function_hashed(*b"entrypoint", builtin_function)
417 .unwrap();
418 Self {
419 deployment_slot,
420 account_size,
421 effective_slot: deployment_slot,
422 tx_usage_counter: AtomicU64::new(0),
423 program: LoadedProgramType::Builtin(BuiltinProgram::new_builtin(function_registry)),
424 ix_usage_counter: AtomicU64::new(0),
425 latest_access_slot: AtomicU64::new(0),
426 }
427 }
428
429 pub fn new_tombstone(slot: Slot, reason: LoadedProgramType) -> Self {
430 let tombstone = Self {
431 program: reason,
432 account_size: 0,
433 deployment_slot: slot,
434 effective_slot: slot,
435 tx_usage_counter: AtomicU64::default(),
436 ix_usage_counter: AtomicU64::default(),
437 latest_access_slot: AtomicU64::new(0),
438 };
439 debug_assert!(tombstone.is_tombstone());
440 tombstone
441 }
442
443 pub fn is_tombstone(&self) -> bool {
444 matches!(
445 self.program,
446 LoadedProgramType::FailedVerification(_)
447 | LoadedProgramType::Closed
448 | LoadedProgramType::DelayVisibility
449 )
450 }
451
452 fn is_implicit_delay_visibility_tombstone(&self, slot: Slot) -> bool {
453 !matches!(self.program, LoadedProgramType::Builtin(_))
454 && self.effective_slot.saturating_sub(self.deployment_slot)
455 == DELAY_VISIBILITY_SLOT_OFFSET
456 && slot >= self.deployment_slot
457 && slot < self.effective_slot
458 }
459
460 pub fn update_access_slot(&self, slot: Slot) {
461 let _ = self.latest_access_slot.fetch_max(slot, Ordering::Relaxed);
462 }
463
464 pub fn decayed_usage_counter(&self, now: Slot) -> u64 {
465 let last_access = self.latest_access_slot.load(Ordering::Relaxed);
466 let decaying_for = std::cmp::min(63, now.saturating_sub(last_access));
468 self.tx_usage_counter.load(Ordering::Relaxed) >> decaying_for
469 }
470}
471
472#[derive(Clone, Debug)]
476pub struct ProgramRuntimeEnvironments {
477 pub program_runtime_v1: ProgramRuntimeEnvironment,
479 pub program_runtime_v2: ProgramRuntimeEnvironment,
481}
482
483impl Default for ProgramRuntimeEnvironments {
484 fn default() -> Self {
485 let empty_loader = Arc::new(BuiltinProgram::new_loader(
486 Config::default(),
487 FunctionRegistry::default(),
488 ));
489 Self {
490 program_runtime_v1: empty_loader.clone(),
491 program_runtime_v2: empty_loader,
492 }
493 }
494}
495
496#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
497pub struct LoadingTaskCookie(u64);
498
499impl LoadingTaskCookie {
500 fn new() -> Self {
501 Self(0)
502 }
503
504 fn update(&mut self) {
505 let LoadingTaskCookie(cookie) = self;
506 *cookie = cookie.wrapping_add(1);
507 }
508}
509
510#[derive(Debug, Default)]
512pub struct LoadingTaskWaiter {
513 cookie: Mutex<LoadingTaskCookie>,
514 cond: Condvar,
515}
516
517impl LoadingTaskWaiter {
518 pub fn new() -> Self {
519 Self {
520 cookie: Mutex::new(LoadingTaskCookie::new()),
521 cond: Condvar::new(),
522 }
523 }
524
525 pub fn cookie(&self) -> LoadingTaskCookie {
526 *self.cookie.lock().unwrap()
527 }
528
529 pub fn notify(&self) {
530 let mut cookie = self.cookie.lock().unwrap();
531 cookie.update();
532 self.cond.notify_all();
533 }
534
535 pub fn wait(&self, cookie: LoadingTaskCookie) -> LoadingTaskCookie {
536 let cookie_guard = self.cookie.lock().unwrap();
537 *self
538 .cond
539 .wait_while(cookie_guard, |current_cookie| *current_cookie == cookie)
540 .unwrap()
541 }
542}
543
544#[derive(Debug, Default)]
546struct SecondLevel {
547 slot_versions: Vec<Arc<LoadedProgram>>,
549 cooperative_loading_lock: Option<(Slot, std::thread::ThreadId)>,
554}
555
556pub struct LoadedPrograms<FG: ForkGraph> {
572 entries: HashMap<Pubkey, SecondLevel>,
576 pub latest_root_slot: Slot,
578 pub latest_root_epoch: Epoch,
580 pub environments: ProgramRuntimeEnvironments,
582 pub upcoming_environments: Option<ProgramRuntimeEnvironments>,
588 pub programs_to_recompile: Vec<(Pubkey, Arc<LoadedProgram>)>,
590 pub stats: Stats,
592 pub fork_graph: Option<Arc<RwLock<FG>>>,
594 pub loading_task_waiter: Arc<LoadingTaskWaiter>,
596}
597
598impl<FG: ForkGraph> Debug for LoadedPrograms<FG> {
599 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
600 f.debug_struct("LoadedPrograms")
601 .field("root slot", &self.latest_root_slot)
602 .field("root epoch", &self.latest_root_epoch)
603 .field("stats", &self.stats)
604 .field("cache", &self.entries)
605 .finish()
606 }
607}
608
609#[derive(Clone, Debug, Default)]
615pub struct LoadedProgramsForTxBatch {
616 entries: HashMap<Pubkey, Arc<LoadedProgram>>,
619 slot: Slot,
620 pub environments: ProgramRuntimeEnvironments,
621}
622
623impl LoadedProgramsForTxBatch {
624 pub fn new(slot: Slot, environments: ProgramRuntimeEnvironments) -> Self {
625 Self {
626 entries: HashMap::new(),
627 slot,
628 environments,
629 }
630 }
631
632 pub fn replenish(
638 &mut self,
639 key: Pubkey,
640 entry: Arc<LoadedProgram>,
641 ) -> (bool, Arc<LoadedProgram>) {
642 (self.entries.insert(key, entry.clone()).is_some(), entry)
643 }
644
645 pub fn find(&self, key: &Pubkey) -> Option<Arc<LoadedProgram>> {
646 self.entries.get(key).map(|entry| {
647 if entry.is_implicit_delay_visibility_tombstone(self.slot) {
648 Arc::new(LoadedProgram::new_tombstone(
652 entry.deployment_slot,
653 LoadedProgramType::DelayVisibility,
654 ))
655 } else {
656 entry.clone()
657 }
658 })
659 }
660
661 pub fn slot(&self) -> Slot {
662 self.slot
663 }
664
665 pub fn set_slot_for_tests(&mut self, slot: Slot) {
666 self.slot = slot;
667 }
668
669 pub fn merge(&mut self, other: &Self) {
670 other.entries.iter().for_each(|(key, entry)| {
671 self.replenish(*key, entry.clone());
672 })
673 }
674}
675
676pub enum LoadedProgramMatchCriteria {
677 DeployedOnOrAfterSlot(Slot),
678 Tombstone,
679 NoCriteria,
680}
681
682impl<FG: ForkGraph> LoadedPrograms<FG> {
683 pub fn new(root_slot: Slot, root_epoch: Epoch) -> Self {
684 Self {
685 entries: HashMap::new(),
686 latest_root_slot: root_slot,
687 latest_root_epoch: root_epoch,
688 environments: ProgramRuntimeEnvironments::default(),
689 upcoming_environments: None,
690 programs_to_recompile: Vec::default(),
691 stats: Stats::default(),
692 fork_graph: None,
693 loading_task_waiter: Arc::new(LoadingTaskWaiter::default()),
694 }
695 }
696
697 pub fn set_fork_graph(&mut self, fork_graph: Arc<RwLock<FG>>) {
698 self.fork_graph = Some(fork_graph);
699 }
700
701 pub fn get_environments_for_epoch(&self, epoch: Epoch) -> &ProgramRuntimeEnvironments {
703 if epoch != self.latest_root_epoch {
704 if let Some(upcoming_environments) = self.upcoming_environments.as_ref() {
705 return upcoming_environments;
706 }
707 }
708 &self.environments
709 }
710
711 pub fn assign_program(&mut self, key: Pubkey, entry: Arc<LoadedProgram>) -> bool {
714 let slot_versions = &mut self.entries.entry(key).or_default().slot_versions;
715 match slot_versions.binary_search_by(|at| {
716 at.effective_slot
717 .cmp(&entry.effective_slot)
718 .then(at.deployment_slot.cmp(&entry.deployment_slot))
719 }) {
720 Ok(index) => {
721 let existing = slot_versions.get_mut(index).unwrap();
722 if std::mem::discriminant(&existing.program)
723 != std::mem::discriminant(&entry.program)
724 {
725 entry.tx_usage_counter.fetch_add(
727 existing.tx_usage_counter.load(Ordering::Relaxed),
728 Ordering::Relaxed,
729 );
730 entry.ix_usage_counter.fetch_add(
731 existing.ix_usage_counter.load(Ordering::Relaxed),
732 Ordering::Relaxed,
733 );
734 *existing = entry.clone();
735 self.stats.reloads.fetch_add(1, Ordering::Relaxed);
736 false
737 } else {
738 self.stats.replacements.fetch_add(1, Ordering::Relaxed);
740 true
741 }
742 }
743 Err(index) => {
744 self.stats.insertions.fetch_add(1, Ordering::Relaxed);
745 slot_versions.insert(index, entry.clone());
746 false
747 }
748 }
749 }
750
751 pub fn prune_by_deployment_slot(&mut self, slot: Slot) {
752 for second_level in self.entries.values_mut() {
753 second_level
754 .slot_versions
755 .retain(|entry| entry.deployment_slot != slot);
756 }
757 self.remove_programs_with_no_entries();
758 }
759
760 pub fn prune(&mut self, new_root_slot: Slot, new_root_epoch: Epoch) {
762 let Some(fork_graph) = self.fork_graph.clone() else {
763 error!("Program cache doesn't have fork graph.");
764 return;
765 };
766 let Ok(fork_graph) = fork_graph.read() else {
767 error!("Failed to lock fork graph for reading.");
768 return;
769 };
770 let mut recompilation_phase_ends = false;
771 if self.latest_root_epoch != new_root_epoch {
772 self.latest_root_epoch = new_root_epoch;
773 if let Some(upcoming_environments) = self.upcoming_environments.take() {
774 recompilation_phase_ends = true;
775 self.environments = upcoming_environments;
776 self.programs_to_recompile.clear();
777 }
778 }
779 for second_level in self.entries.values_mut() {
780 let mut first_ancestor_found = false;
782 let mut first_ancestor_env = None;
783 second_level.slot_versions = second_level
784 .slot_versions
785 .iter()
786 .rev()
787 .filter(|entry| {
788 let relation = fork_graph.relationship(entry.deployment_slot, new_root_slot);
789 if entry.deployment_slot >= new_root_slot {
790 matches!(relation, BlockRelation::Equal | BlockRelation::Descendant)
791 } else if matches!(relation, BlockRelation::Ancestor)
792 || entry.deployment_slot <= self.latest_root_slot
793 {
794 if !first_ancestor_found {
795 first_ancestor_found = true;
796 first_ancestor_env = entry.program.get_environment();
797 return true;
798 }
799 if let Some(entry_env) = entry.program.get_environment() {
806 if let Some(env) = first_ancestor_env {
807 if !Arc::ptr_eq(entry_env, env) {
808 return true;
809 }
810 }
811 }
812 self.stats.prunes_orphan.fetch_add(1, Ordering::Relaxed);
813 false
814 } else {
815 self.stats.prunes_orphan.fetch_add(1, Ordering::Relaxed);
816 false
817 }
818 })
819 .filter(|entry| {
820 if recompilation_phase_ends
822 && !Self::matches_environment(entry, &self.environments)
823 {
824 self.stats
825 .prunes_environment
826 .fetch_add(1, Ordering::Relaxed);
827 return false;
828 }
829 true
830 })
831 .cloned()
832 .collect();
833 second_level.slot_versions.reverse();
834 }
835 self.remove_programs_with_no_entries();
836 debug_assert!(self.latest_root_slot <= new_root_slot);
837 self.latest_root_slot = new_root_slot;
838 }
839
840 fn matches_environment(
841 entry: &Arc<LoadedProgram>,
842 environments: &ProgramRuntimeEnvironments,
843 ) -> bool {
844 let Some(environment) = entry.program.get_environment() else {
845 return true;
846 };
847 Arc::ptr_eq(environment, &environments.program_runtime_v1)
848 || Arc::ptr_eq(environment, &environments.program_runtime_v2)
849 }
850
851 fn matches_loaded_program_criteria(
852 program: &Arc<LoadedProgram>,
853 criteria: &LoadedProgramMatchCriteria,
854 ) -> bool {
855 match criteria {
856 LoadedProgramMatchCriteria::DeployedOnOrAfterSlot(slot) => {
857 program.deployment_slot >= *slot
858 }
859 LoadedProgramMatchCriteria::Tombstone => program.is_tombstone(),
860 LoadedProgramMatchCriteria::NoCriteria => true,
861 }
862 }
863
864 pub fn extract(
867 &mut self,
868 search_for: &mut Vec<(Pubkey, (LoadedProgramMatchCriteria, u64))>,
869 loaded_programs_for_tx_batch: &mut LoadedProgramsForTxBatch,
870 is_first_round: bool,
871 ) -> Option<(Pubkey, u64)> {
872 debug_assert!(self.fork_graph.is_some());
873 let locked_fork_graph = self.fork_graph.as_ref().unwrap().read().unwrap();
874 let mut cooperative_loading_task = None;
875 search_for.retain(|(key, (match_criteria, usage_count))| {
876 if let Some(second_level) = self.entries.get_mut(key) {
877 for entry in second_level.slot_versions.iter().rev() {
878 if entry.deployment_slot <= self.latest_root_slot
879 || matches!(
880 locked_fork_graph.relationship(
881 entry.deployment_slot,
882 loaded_programs_for_tx_batch.slot
883 ),
884 BlockRelation::Equal | BlockRelation::Ancestor
885 )
886 {
887 let entry_to_return = if loaded_programs_for_tx_batch.slot
888 >= entry.effective_slot
889 && Self::matches_environment(
890 entry,
891 &loaded_programs_for_tx_batch.environments,
892 ) {
893 if !Self::matches_loaded_program_criteria(entry, match_criteria) {
894 break;
895 }
896 if let LoadedProgramType::Unloaded(_environment) = &entry.program {
897 break;
898 }
899 entry.clone()
900 } else if entry.is_implicit_delay_visibility_tombstone(
901 loaded_programs_for_tx_batch.slot,
902 ) {
903 Arc::new(LoadedProgram::new_tombstone(
907 entry.deployment_slot,
908 LoadedProgramType::DelayVisibility,
909 ))
910 } else {
911 continue;
912 };
913 entry_to_return.update_access_slot(loaded_programs_for_tx_batch.slot);
914 entry_to_return
915 .tx_usage_counter
916 .fetch_add(*usage_count, Ordering::Relaxed);
917 loaded_programs_for_tx_batch
918 .entries
919 .insert(*key, entry_to_return);
920 return false;
921 }
922 }
923 }
924 if cooperative_loading_task.is_none() {
925 let second_level = self.entries.entry(*key).or_default();
927 if second_level.cooperative_loading_lock.is_none() {
928 cooperative_loading_task = Some((*key, *usage_count));
930 second_level.cooperative_loading_lock = Some((
931 loaded_programs_for_tx_batch.slot,
932 std::thread::current().id(),
933 ));
934 }
935 }
936 true
937 });
938 drop(locked_fork_graph);
939 if is_first_round {
940 self.stats
941 .misses
942 .fetch_add(search_for.len() as u64, Ordering::Relaxed);
943 self.stats.hits.fetch_add(
944 loaded_programs_for_tx_batch.entries.len() as u64,
945 Ordering::Relaxed,
946 );
947 }
948 cooperative_loading_task
949 }
950
951 pub fn finish_cooperative_loading_task(
953 &mut self,
954 slot: Slot,
955 key: Pubkey,
956 loaded_program: Arc<LoadedProgram>,
957 ) {
958 let second_level = self.entries.entry(key).or_default();
959 debug_assert_eq!(
960 second_level.cooperative_loading_lock,
961 Some((slot, std::thread::current().id()))
962 );
963 second_level.cooperative_loading_lock = None;
964 if loaded_program.deployment_slot > self.latest_root_slot
966 && !matches!(
967 self.fork_graph
968 .as_ref()
969 .unwrap()
970 .read()
971 .unwrap()
972 .relationship(loaded_program.deployment_slot, slot),
973 BlockRelation::Equal | BlockRelation::Ancestor
974 )
975 {
976 self.stats.lost_insertions.fetch_add(1, Ordering::Relaxed);
977 }
978 self.assign_program(key, loaded_program);
979 self.loading_task_waiter.notify();
980 }
981
982 pub fn merge(&mut self, tx_batch_cache: &LoadedProgramsForTxBatch) {
983 tx_batch_cache.entries.iter().for_each(|(key, entry)| {
984 self.assign_program(*key, entry.clone());
985 })
986 }
987
988 pub fn get_flattened_entries(
990 &self,
991 include_program_runtime_v1: bool,
992 include_program_runtime_v2: bool,
993 ) -> Vec<(Pubkey, Arc<LoadedProgram>)> {
994 self.entries
995 .iter()
996 .flat_map(|(id, second_level)| {
997 second_level
998 .slot_versions
999 .iter()
1000 .filter_map(move |program| match program.program {
1001 LoadedProgramType::LegacyV0(_) | LoadedProgramType::LegacyV1(_)
1002 if include_program_runtime_v1 =>
1003 {
1004 Some((*id, program.clone()))
1005 }
1006 LoadedProgramType::Typed(_) if include_program_runtime_v2 => {
1007 Some((*id, program.clone()))
1008 }
1009 #[cfg(test)]
1010 LoadedProgramType::TestLoaded(_) => Some((*id, program.clone())),
1011 _ => None,
1012 })
1013 })
1014 .collect()
1015 }
1016
1017 pub fn sort_and_unload(&mut self, shrink_to: PercentageInteger) {
1019 let mut sorted_candidates = self.get_flattened_entries(true, true);
1020 sorted_candidates
1021 .sort_by_cached_key(|(_id, program)| program.tx_usage_counter.load(Ordering::Relaxed));
1022 let num_to_unload = sorted_candidates
1023 .len()
1024 .saturating_sub(shrink_to.apply_to(MAX_LOADED_ENTRY_COUNT));
1025 self.unload_program_entries(sorted_candidates.iter().take(num_to_unload));
1026 }
1027
1028 pub fn evict_using_2s_random_selection(&mut self, shrink_to: PercentageInteger, now: Slot) {
1031 let mut candidates = self.get_flattened_entries(true, true);
1032 let num_to_unload = candidates
1033 .len()
1034 .saturating_sub(shrink_to.apply_to(MAX_LOADED_ENTRY_COUNT));
1035 fn random_index_and_usage_counter(
1036 candidates: &[(Pubkey, Arc<LoadedProgram>)],
1037 now: Slot,
1038 ) -> (usize, u64) {
1039 let mut rng = thread_rng();
1040 let index = rng.gen_range(0..candidates.len());
1041 let usage_counter = candidates
1042 .get(index)
1043 .expect("Failed to get cached entry")
1044 .1
1045 .decayed_usage_counter(now);
1046 (index, usage_counter)
1047 }
1048
1049 for _ in 0..num_to_unload {
1050 let (index1, usage_counter1) = random_index_and_usage_counter(&candidates, now);
1051 let (index2, usage_counter2) = random_index_and_usage_counter(&candidates, now);
1052
1053 let (program, entry) = if usage_counter1 < usage_counter2 {
1054 candidates.swap_remove(index1)
1055 } else {
1056 candidates.swap_remove(index2)
1057 };
1058 self.unload_program_entry(&program, &entry);
1059 }
1060 }
1061
1062 pub fn remove_programs(&mut self, keys: impl Iterator<Item = Pubkey>) {
1064 for k in keys {
1065 self.entries.remove(&k);
1066 }
1067 }
1068
1069 fn unload_program_entry(&mut self, program: &Pubkey, remove_entry: &Arc<LoadedProgram>) {
1072 let second_level = self.entries.get_mut(program).expect("Cache lookup failed");
1073 let candidate = second_level
1074 .slot_versions
1075 .iter_mut()
1076 .find(|entry| entry == &remove_entry)
1077 .expect("Program entry not found");
1078
1079 if let Some(unloaded) = candidate.to_unloaded() {
1083 if candidate.tx_usage_counter.load(Ordering::Relaxed) == 1 {
1084 self.stats.one_hit_wonders.fetch_add(1, Ordering::Relaxed);
1085 }
1086 self.stats
1087 .evictions
1088 .entry(*program)
1089 .and_modify(|c| saturating_add_assign!(*c, 1))
1090 .or_insert(1);
1091 *candidate = Arc::new(unloaded);
1092 }
1093 }
1094
1095 fn unload_program_entries<'a>(
1096 &mut self,
1097 remove: impl Iterator<Item = &'a (Pubkey, Arc<LoadedProgram>)>,
1098 ) {
1099 for (program, entry) in remove {
1100 self.unload_program_entry(program, entry);
1101 }
1102 }
1103
1104 fn remove_programs_with_no_entries(&mut self) {
1105 let num_programs_before_removal = self.entries.len();
1106 self.entries.retain(|_, second_level| {
1107 !second_level.slot_versions.is_empty()
1108 || second_level.cooperative_loading_lock.is_some()
1109 });
1110 if self.entries.len() < num_programs_before_removal {
1111 self.stats.empty_entries.fetch_add(
1112 num_programs_before_removal.saturating_sub(self.entries.len()) as u64,
1113 Ordering::Relaxed,
1114 );
1115 }
1116 }
1117}
1118
1119#[cfg(RUSTC_WITH_SPECIALIZATION)]
1120impl miraland_frozen_abi::abi_example::AbiExample for LoadedProgram {
1121 fn example() -> Self {
1122 Self::default()
1124 }
1125}
1126
1127#[cfg(RUSTC_WITH_SPECIALIZATION)]
1128impl<FG: ForkGraph> miraland_frozen_abi::abi_example::AbiExample for LoadedPrograms<FG> {
1129 fn example() -> Self {
1130 Self::new(Slot::default(), Epoch::default())
1132 }
1133}
1134
1135#[cfg(test)]
1136mod tests {
1137 use {
1138 crate::loaded_programs::{
1139 BlockRelation, ForkGraph, LoadedProgram, LoadedProgramMatchCriteria, LoadedProgramType,
1140 LoadedPrograms, LoadedProgramsForTxBatch, ProgramRuntimeEnvironment,
1141 ProgramRuntimeEnvironments, DELAY_VISIBILITY_SLOT_OFFSET,
1142 },
1143 assert_matches::assert_matches,
1144 percentage::Percentage,
1145 solana_rbpf::program::BuiltinProgram,
1146 miraland_sdk::{clock::Slot, pubkey::Pubkey},
1147 std::{
1148 ops::ControlFlow,
1149 sync::{
1150 atomic::{AtomicU64, Ordering},
1151 Arc, RwLock,
1152 },
1153 },
1154 };
1155
1156 static MOCK_ENVIRONMENT: std::sync::OnceLock<ProgramRuntimeEnvironment> =
1157 std::sync::OnceLock::<ProgramRuntimeEnvironment>::new();
1158
1159 fn new_mock_cache<FG: ForkGraph>() -> LoadedPrograms<FG> {
1160 let mut cache = LoadedPrograms::new(0, 0);
1161
1162 cache.environments.program_runtime_v1 = MOCK_ENVIRONMENT
1163 .get_or_init(|| Arc::new(BuiltinProgram::new_mock()))
1164 .clone();
1165 cache
1166 }
1167
1168 fn new_test_loaded_program(deployment_slot: Slot, effective_slot: Slot) -> Arc<LoadedProgram> {
1169 new_test_loaded_program_with_usage(deployment_slot, effective_slot, AtomicU64::default())
1170 }
1171
1172 fn new_test_loaded_program_with_usage(
1173 deployment_slot: Slot,
1174 effective_slot: Slot,
1175 usage_counter: AtomicU64,
1176 ) -> Arc<LoadedProgram> {
1177 Arc::new(LoadedProgram {
1178 program: LoadedProgramType::TestLoaded(MOCK_ENVIRONMENT.get().unwrap().clone()),
1179 account_size: 0,
1180 deployment_slot,
1181 effective_slot,
1182 tx_usage_counter: usage_counter,
1183 ix_usage_counter: AtomicU64::default(),
1184 latest_access_slot: AtomicU64::new(deployment_slot),
1185 })
1186 }
1187
1188 fn new_test_builtin_program(deployment_slot: Slot, effective_slot: Slot) -> Arc<LoadedProgram> {
1189 Arc::new(LoadedProgram {
1190 program: LoadedProgramType::Builtin(BuiltinProgram::new_mock()),
1191 account_size: 0,
1192 deployment_slot,
1193 effective_slot,
1194 tx_usage_counter: AtomicU64::default(),
1195 ix_usage_counter: AtomicU64::default(),
1196 latest_access_slot: AtomicU64::default(),
1197 })
1198 }
1199
1200 fn set_tombstone<FG: ForkGraph>(
1201 cache: &mut LoadedPrograms<FG>,
1202 key: Pubkey,
1203 slot: Slot,
1204 reason: LoadedProgramType,
1205 ) -> Arc<LoadedProgram> {
1206 let program = Arc::new(LoadedProgram::new_tombstone(slot, reason));
1207 cache.assign_program(key, program.clone());
1208 program
1209 }
1210
1211 fn insert_unloaded_program<FG: ForkGraph>(
1212 cache: &mut LoadedPrograms<FG>,
1213 key: Pubkey,
1214 slot: Slot,
1215 ) -> Arc<LoadedProgram> {
1216 let unloaded = Arc::new(
1217 LoadedProgram {
1218 program: LoadedProgramType::TestLoaded(
1219 cache.environments.program_runtime_v1.clone(),
1220 ),
1221 account_size: 0,
1222 deployment_slot: slot,
1223 effective_slot: slot.saturating_add(1),
1224 tx_usage_counter: AtomicU64::default(),
1225 ix_usage_counter: AtomicU64::default(),
1226 latest_access_slot: AtomicU64::default(),
1227 }
1228 .to_unloaded()
1229 .expect("Failed to unload the program"),
1230 );
1231 cache.assign_program(key, unloaded.clone());
1232 unloaded
1233 }
1234
1235 fn num_matching_entries<P, FG>(cache: &LoadedPrograms<FG>, predicate: P) -> usize
1236 where
1237 P: Fn(&LoadedProgramType) -> bool,
1238 FG: ForkGraph,
1239 {
1240 cache
1241 .entries
1242 .values()
1243 .map(|second_level| {
1244 second_level
1245 .slot_versions
1246 .iter()
1247 .filter(|program| predicate(&program.program))
1248 .count()
1249 })
1250 .sum()
1251 }
1252
1253 #[test]
1254 fn test_usage_counter_decay() {
1255 let _cache = new_mock_cache::<TestForkGraph>();
1256 let program = new_test_loaded_program_with_usage(10, 11, AtomicU64::new(32));
1257 program.update_access_slot(15);
1258 assert_eq!(program.decayed_usage_counter(15), 32);
1259 assert_eq!(program.decayed_usage_counter(16), 16);
1260 assert_eq!(program.decayed_usage_counter(17), 8);
1261 assert_eq!(program.decayed_usage_counter(18), 4);
1262 assert_eq!(program.decayed_usage_counter(19), 2);
1263 assert_eq!(program.decayed_usage_counter(20), 1);
1264 assert_eq!(program.decayed_usage_counter(21), 0);
1265 assert_eq!(program.decayed_usage_counter(15), 32);
1266 assert_eq!(program.decayed_usage_counter(14), 32);
1267
1268 program.update_access_slot(18);
1269 assert_eq!(program.decayed_usage_counter(15), 32);
1270 assert_eq!(program.decayed_usage_counter(16), 32);
1271 assert_eq!(program.decayed_usage_counter(17), 32);
1272 assert_eq!(program.decayed_usage_counter(18), 32);
1273 assert_eq!(program.decayed_usage_counter(19), 16);
1274 assert_eq!(program.decayed_usage_counter(20), 8);
1275 assert_eq!(program.decayed_usage_counter(21), 4);
1276
1277 assert_eq!(program.decayed_usage_counter(18 + 63), 0);
1279 assert_eq!(program.decayed_usage_counter(100), 0);
1280 }
1281
1282 #[test]
1283 fn test_random_eviction() {
1284 let mut programs = vec![];
1285
1286 let mut cache = new_mock_cache::<TestForkGraph>();
1287
1288 let program1 = Pubkey::new_unique();
1292 let program1_deployment_slots = [0, 10, 20];
1293 let program1_usage_counters = [4, 5, 25];
1294 program1_deployment_slots
1295 .iter()
1296 .enumerate()
1297 .for_each(|(i, deployment_slot)| {
1298 let usage_counter = *program1_usage_counters.get(i).unwrap_or(&0);
1299 cache.assign_program(
1300 program1,
1301 new_test_loaded_program_with_usage(
1302 *deployment_slot,
1303 (*deployment_slot) + 2,
1304 AtomicU64::new(usage_counter),
1305 ),
1306 );
1307 programs.push((program1, *deployment_slot, usage_counter));
1308 });
1309
1310 let env = Arc::new(BuiltinProgram::new_mock());
1311 for slot in 21..31 {
1312 set_tombstone(
1313 &mut cache,
1314 program1,
1315 slot,
1316 LoadedProgramType::FailedVerification(env.clone()),
1317 );
1318 }
1319
1320 for slot in 31..41 {
1321 insert_unloaded_program(&mut cache, program1, slot);
1322 }
1323
1324 let program2 = Pubkey::new_unique();
1325 let program2_deployment_slots = [5, 11];
1326 let program2_usage_counters = [0, 2];
1327 program2_deployment_slots
1328 .iter()
1329 .enumerate()
1330 .for_each(|(i, deployment_slot)| {
1331 let usage_counter = *program2_usage_counters.get(i).unwrap_or(&0);
1332 cache.assign_program(
1333 program2,
1334 new_test_loaded_program_with_usage(
1335 *deployment_slot,
1336 (*deployment_slot) + 2,
1337 AtomicU64::new(usage_counter),
1338 ),
1339 );
1340 programs.push((program2, *deployment_slot, usage_counter));
1341 });
1342
1343 for slot in 21..31 {
1344 set_tombstone(
1345 &mut cache,
1346 program2,
1347 slot,
1348 LoadedProgramType::DelayVisibility,
1349 );
1350 }
1351
1352 for slot in 31..41 {
1353 insert_unloaded_program(&mut cache, program2, slot);
1354 }
1355
1356 let program3 = Pubkey::new_unique();
1357 let program3_deployment_slots = [0, 5, 15];
1358 let program3_usage_counters = [100, 3, 20];
1359 program3_deployment_slots
1360 .iter()
1361 .enumerate()
1362 .for_each(|(i, deployment_slot)| {
1363 let usage_counter = *program3_usage_counters.get(i).unwrap_or(&0);
1364 cache.assign_program(
1365 program3,
1366 new_test_loaded_program_with_usage(
1367 *deployment_slot,
1368 (*deployment_slot) + 2,
1369 AtomicU64::new(usage_counter),
1370 ),
1371 );
1372 programs.push((program3, *deployment_slot, usage_counter));
1373 });
1374
1375 for slot in 21..31 {
1376 set_tombstone(&mut cache, program3, slot, LoadedProgramType::Closed);
1377 }
1378
1379 for slot in 31..41 {
1380 insert_unloaded_program(&mut cache, program3, slot);
1381 }
1382
1383 programs.sort_by_key(|(_id, _slot, usage_count)| *usage_count);
1384
1385 let num_loaded = num_matching_entries(&cache, |program_type| {
1386 matches!(program_type, LoadedProgramType::TestLoaded(_))
1387 });
1388 let num_unloaded = num_matching_entries(&cache, |program_type| {
1389 matches!(program_type, LoadedProgramType::Unloaded(_))
1390 });
1391 let num_tombstones = num_matching_entries(&cache, |program_type| {
1392 matches!(
1393 program_type,
1394 LoadedProgramType::DelayVisibility
1395 | LoadedProgramType::FailedVerification(_)
1396 | LoadedProgramType::Closed
1397 )
1398 });
1399
1400 assert_eq!(num_loaded, 8);
1402 assert_eq!(num_unloaded, 30);
1403 assert_eq!(num_tombstones, 30);
1404
1405 cache.evict_using_2s_random_selection(Percentage::from(2), 21);
1410
1411 let num_loaded = num_matching_entries(&cache, |program_type| {
1412 matches!(program_type, LoadedProgramType::TestLoaded(_))
1413 });
1414 let num_unloaded = num_matching_entries(&cache, |program_type| {
1415 matches!(program_type, LoadedProgramType::Unloaded(_))
1416 });
1417 let num_tombstones = num_matching_entries(&cache, |program_type| {
1418 matches!(
1419 program_type,
1420 LoadedProgramType::DelayVisibility
1421 | LoadedProgramType::FailedVerification(_)
1422 | LoadedProgramType::Closed
1423 )
1424 });
1425
1426 assert_eq!(num_loaded, 5);
1428 assert_eq!(num_unloaded, 33);
1429 assert_eq!(num_tombstones, 30);
1430 }
1431
1432 #[test]
1433 fn test_eviction() {
1434 let mut programs = vec![];
1435
1436 let mut cache = new_mock_cache::<TestForkGraph>();
1437
1438 let program1 = Pubkey::new_unique();
1439 let program1_deployment_slots = [0, 10, 20];
1440 let program1_usage_counters = [4, 5, 25];
1441 program1_deployment_slots
1442 .iter()
1443 .enumerate()
1444 .for_each(|(i, deployment_slot)| {
1445 let usage_counter = *program1_usage_counters.get(i).unwrap_or(&0);
1446 cache.assign_program(
1447 program1,
1448 new_test_loaded_program_with_usage(
1449 *deployment_slot,
1450 (*deployment_slot) + 2,
1451 AtomicU64::new(usage_counter),
1452 ),
1453 );
1454 programs.push((program1, *deployment_slot, usage_counter));
1455 });
1456
1457 let env = Arc::new(BuiltinProgram::new_mock());
1458 for slot in 21..31 {
1459 set_tombstone(
1460 &mut cache,
1461 program1,
1462 slot,
1463 LoadedProgramType::FailedVerification(env.clone()),
1464 );
1465 }
1466
1467 for slot in 31..41 {
1468 insert_unloaded_program(&mut cache, program1, slot);
1469 }
1470
1471 let program2 = Pubkey::new_unique();
1472 let program2_deployment_slots = [5, 11];
1473 let program2_usage_counters = [0, 2];
1474 program2_deployment_slots
1475 .iter()
1476 .enumerate()
1477 .for_each(|(i, deployment_slot)| {
1478 let usage_counter = *program2_usage_counters.get(i).unwrap_or(&0);
1479 cache.assign_program(
1480 program2,
1481 new_test_loaded_program_with_usage(
1482 *deployment_slot,
1483 (*deployment_slot) + 2,
1484 AtomicU64::new(usage_counter),
1485 ),
1486 );
1487 programs.push((program2, *deployment_slot, usage_counter));
1488 });
1489
1490 for slot in 21..31 {
1491 set_tombstone(
1492 &mut cache,
1493 program2,
1494 slot,
1495 LoadedProgramType::DelayVisibility,
1496 );
1497 }
1498
1499 for slot in 31..41 {
1500 insert_unloaded_program(&mut cache, program2, slot);
1501 }
1502
1503 let program3 = Pubkey::new_unique();
1504 let program3_deployment_slots = [0, 5, 15];
1505 let program3_usage_counters = [100, 3, 20];
1506 program3_deployment_slots
1507 .iter()
1508 .enumerate()
1509 .for_each(|(i, deployment_slot)| {
1510 let usage_counter = *program3_usage_counters.get(i).unwrap_or(&0);
1511 cache.assign_program(
1512 program3,
1513 new_test_loaded_program_with_usage(
1514 *deployment_slot,
1515 (*deployment_slot) + 2,
1516 AtomicU64::new(usage_counter),
1517 ),
1518 );
1519 programs.push((program3, *deployment_slot, usage_counter));
1520 });
1521
1522 for slot in 21..31 {
1523 set_tombstone(&mut cache, program3, slot, LoadedProgramType::Closed);
1524 }
1525
1526 for slot in 31..41 {
1527 insert_unloaded_program(&mut cache, program3, slot);
1528 }
1529
1530 programs.sort_by_key(|(_id, _slot, usage_count)| *usage_count);
1531
1532 let num_loaded = num_matching_entries(&cache, |program_type| {
1533 matches!(program_type, LoadedProgramType::TestLoaded(_))
1534 });
1535 let num_unloaded = num_matching_entries(&cache, |program_type| {
1536 matches!(program_type, LoadedProgramType::Unloaded(_))
1537 });
1538 let num_tombstones = num_matching_entries(&cache, |program_type| {
1539 matches!(
1540 program_type,
1541 LoadedProgramType::DelayVisibility
1542 | LoadedProgramType::FailedVerification(_)
1543 | LoadedProgramType::Closed
1544 )
1545 });
1546
1547 assert_eq!(num_loaded, 8);
1548 assert_eq!(num_unloaded, 30);
1549 assert_eq!(num_tombstones, 30);
1550
1551 cache.sort_and_unload(Percentage::from(2));
1556 programs.iter().for_each(|entry| {
1558 assert!(cache.entries.get(&entry.0).is_some());
1559 });
1560
1561 let unloaded = cache
1562 .entries
1563 .iter()
1564 .flat_map(|(id, second_level)| {
1565 second_level.slot_versions.iter().filter_map(|program| {
1566 matches!(program.program, LoadedProgramType::Unloaded(_))
1567 .then_some((*id, program.tx_usage_counter.load(Ordering::Relaxed)))
1568 })
1569 })
1570 .collect::<Vec<(Pubkey, u64)>>();
1571
1572 for index in 0..3 {
1573 let expected = programs.get(index).expect("Missing program");
1574 assert!(unloaded.contains(&(expected.0, expected.2)));
1575 }
1576
1577 let num_loaded = num_matching_entries(&cache, |program_type| {
1578 matches!(program_type, LoadedProgramType::TestLoaded(_))
1579 });
1580 let num_unloaded = num_matching_entries(&cache, |program_type| {
1581 matches!(program_type, LoadedProgramType::Unloaded(_))
1582 });
1583 let num_tombstones = num_matching_entries(&cache, |program_type| {
1584 matches!(
1585 program_type,
1586 LoadedProgramType::DelayVisibility
1587 | LoadedProgramType::FailedVerification(_)
1588 | LoadedProgramType::Closed
1589 )
1590 });
1591
1592 assert_eq!(num_loaded, 5);
1593 assert_eq!(num_unloaded, 33);
1594 assert_eq!(num_tombstones, 30);
1595 }
1596
1597 #[test]
1598 fn test_usage_count_of_unloaded_program() {
1599 let mut cache = new_mock_cache::<TestForkGraph>();
1600
1601 let program = Pubkey::new_unique();
1602 let num_total_programs = 6;
1603 (0..num_total_programs).for_each(|i| {
1604 cache.assign_program(
1605 program,
1606 new_test_loaded_program_with_usage(i, i + 2, AtomicU64::new(i + 10)),
1607 );
1608 });
1609
1610 cache.sort_and_unload(Percentage::from(2));
1612
1613 let num_unloaded = num_matching_entries(&cache, |program_type| {
1614 matches!(program_type, LoadedProgramType::Unloaded(_))
1615 });
1616 assert_eq!(num_unloaded, 1);
1617
1618 cache.entries.values().for_each(|second_level| {
1619 second_level.slot_versions.iter().for_each(|program| {
1620 if matches!(program.program, LoadedProgramType::Unloaded(_)) {
1621 assert_eq!(program.tx_usage_counter.load(Ordering::Relaxed), 10);
1623 assert_eq!(program.deployment_slot, 0);
1624 assert_eq!(program.effective_slot, 2);
1625 }
1626 })
1627 });
1628
1629 cache.assign_program(
1632 program,
1633 new_test_loaded_program_with_usage(0, 2, AtomicU64::new(0)),
1634 );
1635
1636 cache.entries.values().for_each(|second_level| {
1637 second_level.slot_versions.iter().for_each(|program| {
1638 if matches!(program.program, LoadedProgramType::Unloaded(_))
1639 && program.deployment_slot == 0
1640 && program.effective_slot == 2
1641 {
1642 assert_eq!(program.tx_usage_counter.load(Ordering::Relaxed), 10);
1644 }
1645 })
1646 });
1647 }
1648
1649 #[test]
1650 fn test_fuzz_assign_program_order() {
1651 use rand::prelude::SliceRandom;
1652 const EXPECTED_ENTRIES: [(u64, u64); 7] =
1653 [(1, 2), (5, 5), (5, 6), (5, 10), (9, 10), (10, 10), (3, 12)];
1654 let mut rng = rand::thread_rng();
1655 let program_id = Pubkey::new_unique();
1656 for _ in 0..1000 {
1657 let mut entries = EXPECTED_ENTRIES.to_vec();
1658 entries.shuffle(&mut rng);
1659 let mut cache = new_mock_cache::<TestForkGraph>();
1660 for (deployment_slot, effective_slot) in entries {
1661 assert!(!cache.assign_program(
1662 program_id,
1663 new_test_loaded_program(deployment_slot, effective_slot)
1664 ));
1665 }
1666 for ((deployment_slot, effective_slot), entry) in EXPECTED_ENTRIES
1667 .iter()
1668 .zip(cache.entries.get(&program_id).unwrap().slot_versions.iter())
1669 {
1670 assert_eq!(entry.deployment_slot, *deployment_slot);
1671 assert_eq!(entry.effective_slot, *effective_slot);
1672 }
1673 }
1674 }
1675
1676 #[test]
1677 fn test_assign_program_tombstones() {
1678 let mut cache = new_mock_cache::<TestForkGraph>();
1679 let program1 = Pubkey::new_unique();
1680 let env = cache.environments.program_runtime_v1.clone();
1681
1682 set_tombstone(
1683 &mut cache,
1684 program1,
1685 10,
1686 LoadedProgramType::FailedVerification(env.clone()),
1687 );
1688 assert_eq!(cache.entries.get(&program1).unwrap().slot_versions.len(), 1);
1689 set_tombstone(&mut cache, program1, 10, LoadedProgramType::Closed);
1690 assert_eq!(cache.entries.get(&program1).unwrap().slot_versions.len(), 1);
1691 set_tombstone(
1692 &mut cache,
1693 program1,
1694 10,
1695 LoadedProgramType::FailedVerification(env.clone()),
1696 );
1697 assert_eq!(cache.entries.get(&program1).unwrap().slot_versions.len(), 1);
1698
1699 assert!(cache.assign_program(
1701 program1,
1702 Arc::new(LoadedProgram::new_tombstone(
1703 10,
1704 LoadedProgramType::FailedVerification(env)
1705 ))
1706 ));
1707 }
1708
1709 #[test]
1710 fn test_tombstone() {
1711 let env = Arc::new(BuiltinProgram::new_mock());
1712 let tombstone =
1713 LoadedProgram::new_tombstone(0, LoadedProgramType::FailedVerification(env.clone()));
1714 assert_matches!(tombstone.program, LoadedProgramType::FailedVerification(_));
1715 assert!(tombstone.is_tombstone());
1716 assert_eq!(tombstone.deployment_slot, 0);
1717 assert_eq!(tombstone.effective_slot, 0);
1718
1719 let tombstone = LoadedProgram::new_tombstone(100, LoadedProgramType::Closed);
1720 assert_matches!(tombstone.program, LoadedProgramType::Closed);
1721 assert!(tombstone.is_tombstone());
1722 assert_eq!(tombstone.deployment_slot, 100);
1723 assert_eq!(tombstone.effective_slot, 100);
1724
1725 let mut cache = new_mock_cache::<TestForkGraph>();
1726 let program1 = Pubkey::new_unique();
1727 let tombstone = set_tombstone(
1728 &mut cache,
1729 program1,
1730 10,
1731 LoadedProgramType::FailedVerification(env.clone()),
1732 );
1733 let second_level = &cache
1734 .entries
1735 .get(&program1)
1736 .expect("Failed to find the entry");
1737 assert_eq!(second_level.slot_versions.len(), 1);
1738 assert!(second_level.slot_versions.first().unwrap().is_tombstone());
1739 assert_eq!(tombstone.deployment_slot, 10);
1740 assert_eq!(tombstone.effective_slot, 10);
1741
1742 let program2 = Pubkey::new_unique();
1744 cache.assign_program(program2, new_test_builtin_program(50, 51));
1745 let second_level = &cache
1746 .entries
1747 .get(&program2)
1748 .expect("Failed to find the entry");
1749 assert_eq!(second_level.slot_versions.len(), 1);
1750 assert!(!second_level.slot_versions.first().unwrap().is_tombstone());
1751
1752 let tombstone = set_tombstone(
1753 &mut cache,
1754 program2,
1755 60,
1756 LoadedProgramType::FailedVerification(env),
1757 );
1758 let second_level = &cache
1759 .entries
1760 .get(&program2)
1761 .expect("Failed to find the entry");
1762 assert_eq!(second_level.slot_versions.len(), 2);
1763 assert!(!second_level.slot_versions.first().unwrap().is_tombstone());
1764 assert!(second_level.slot_versions.get(1).unwrap().is_tombstone());
1765 assert!(tombstone.is_tombstone());
1766 assert_eq!(tombstone.deployment_slot, 60);
1767 assert_eq!(tombstone.effective_slot, 60);
1768 }
1769
1770 struct TestForkGraph {
1771 relation: BlockRelation,
1772 }
1773 impl ForkGraph for TestForkGraph {
1774 fn relationship(&self, _a: Slot, _b: Slot) -> BlockRelation {
1775 self.relation
1776 }
1777 }
1778
1779 #[test]
1780 fn test_prune_empty() {
1781 let mut cache = new_mock_cache::<TestForkGraph>();
1782 let fork_graph = Arc::new(RwLock::new(TestForkGraph {
1783 relation: BlockRelation::Unrelated,
1784 }));
1785
1786 cache.set_fork_graph(fork_graph);
1787
1788 cache.prune(0, 0);
1789 assert!(cache.entries.is_empty());
1790
1791 cache.prune(10, 0);
1792 assert!(cache.entries.is_empty());
1793
1794 let mut cache = new_mock_cache::<TestForkGraph>();
1795 let fork_graph = Arc::new(RwLock::new(TestForkGraph {
1796 relation: BlockRelation::Ancestor,
1797 }));
1798
1799 cache.set_fork_graph(fork_graph);
1800
1801 cache.prune(0, 0);
1802 assert!(cache.entries.is_empty());
1803
1804 cache.prune(10, 0);
1805 assert!(cache.entries.is_empty());
1806
1807 let mut cache = new_mock_cache::<TestForkGraph>();
1808 let fork_graph = Arc::new(RwLock::new(TestForkGraph {
1809 relation: BlockRelation::Descendant,
1810 }));
1811
1812 cache.set_fork_graph(fork_graph);
1813
1814 cache.prune(0, 0);
1815 assert!(cache.entries.is_empty());
1816
1817 cache.prune(10, 0);
1818 assert!(cache.entries.is_empty());
1819
1820 let mut cache = new_mock_cache::<TestForkGraph>();
1821 let fork_graph = Arc::new(RwLock::new(TestForkGraph {
1822 relation: BlockRelation::Unknown,
1823 }));
1824 cache.set_fork_graph(fork_graph);
1825
1826 cache.prune(0, 0);
1827 assert!(cache.entries.is_empty());
1828
1829 cache.prune(10, 0);
1830 assert!(cache.entries.is_empty());
1831 }
1832
1833 #[test]
1834 fn test_prune_different_env() {
1835 let mut cache = new_mock_cache::<TestForkGraph>();
1836
1837 let fork_graph = Arc::new(RwLock::new(TestForkGraph {
1838 relation: BlockRelation::Ancestor,
1839 }));
1840
1841 cache.set_fork_graph(fork_graph);
1842
1843 let program1 = Pubkey::new_unique();
1844 cache.assign_program(program1, new_test_loaded_program(10, 10));
1845
1846 let new_env = Arc::new(BuiltinProgram::new_mock());
1847 cache.upcoming_environments = Some(ProgramRuntimeEnvironments {
1848 program_runtime_v1: new_env.clone(),
1849 program_runtime_v2: new_env.clone(),
1850 });
1851 let updated_program = Arc::new(LoadedProgram {
1852 program: LoadedProgramType::TestLoaded(new_env.clone()),
1853 account_size: 0,
1854 deployment_slot: 20,
1855 effective_slot: 20,
1856 tx_usage_counter: AtomicU64::default(),
1857 ix_usage_counter: AtomicU64::default(),
1858 latest_access_slot: AtomicU64::default(),
1859 });
1860 cache.assign_program(program1, updated_program.clone());
1861
1862 assert_eq!(
1864 cache
1865 .entries
1866 .get(&program1)
1867 .expect("failed to find the program")
1868 .slot_versions
1869 .len(),
1870 2
1871 );
1872
1873 cache.prune(21, cache.latest_root_epoch);
1874
1875 assert_eq!(
1877 cache
1878 .entries
1879 .get(&program1)
1880 .expect("failed to find the program")
1881 .slot_versions
1882 .len(),
1883 2
1884 );
1885
1886 cache.prune(22, cache.latest_root_epoch.saturating_add(1));
1887
1888 let second_level = cache
1889 .entries
1890 .get(&program1)
1891 .expect("failed to find the program");
1892 assert_eq!(second_level.slot_versions.len(), 1);
1894
1895 let entry = second_level
1896 .slot_versions
1897 .first()
1898 .expect("Failed to get the program")
1899 .clone();
1900 assert_eq!(entry, updated_program);
1902 }
1903
1904 #[derive(Default)]
1905 struct TestForkGraphSpecific {
1906 forks: Vec<Vec<Slot>>,
1907 }
1908
1909 impl TestForkGraphSpecific {
1910 fn insert_fork(&mut self, fork: &[Slot]) {
1911 let mut fork = fork.to_vec();
1912 fork.sort();
1913 self.forks.push(fork)
1914 }
1915 }
1916
1917 impl ForkGraph for TestForkGraphSpecific {
1918 fn relationship(&self, a: Slot, b: Slot) -> BlockRelation {
1919 match self.forks.iter().try_for_each(|fork| {
1920 let relation = fork
1921 .iter()
1922 .position(|x| *x == a)
1923 .and_then(|a_pos| {
1924 fork.iter().position(|x| *x == b).and_then(|b_pos| {
1925 (a_pos == b_pos)
1926 .then_some(BlockRelation::Equal)
1927 .or_else(|| (a_pos < b_pos).then_some(BlockRelation::Ancestor))
1928 .or(Some(BlockRelation::Descendant))
1929 })
1930 })
1931 .unwrap_or(BlockRelation::Unrelated);
1932
1933 if relation != BlockRelation::Unrelated {
1934 return ControlFlow::Break(relation);
1935 }
1936
1937 ControlFlow::Continue(())
1938 }) {
1939 ControlFlow::Break(relation) => relation,
1940 _ => BlockRelation::Unrelated,
1941 }
1942 }
1943 }
1944
1945 fn match_slot(
1946 extracted: &LoadedProgramsForTxBatch,
1947 program: &Pubkey,
1948 deployment_slot: Slot,
1949 working_slot: Slot,
1950 ) -> bool {
1951 assert_eq!(extracted.slot, working_slot);
1952 extracted
1953 .entries
1954 .get(program)
1955 .map(|entry| entry.deployment_slot == deployment_slot)
1956 .unwrap_or(false)
1957 }
1958
1959 fn match_missing(
1960 missing: &[(Pubkey, (LoadedProgramMatchCriteria, u64))],
1961 program: &Pubkey,
1962 _reload: bool,
1963 ) -> bool {
1964 missing.iter().any(|(key, _)| key == program)
1965 }
1966
1967 #[test]
1968 fn test_fork_extract_and_prune() {
1969 let mut cache = new_mock_cache::<TestForkGraphSpecific>();
1970
1971 let mut fork_graph = TestForkGraphSpecific::default();
1987 fork_graph.insert_fork(&[0, 10, 20, 22]);
1988 fork_graph.insert_fork(&[0, 5, 11, 15, 16, 18, 19, 21, 23]);
1989 fork_graph.insert_fork(&[0, 5, 11, 25, 27]);
1990
1991 let fork_graph = Arc::new(RwLock::new(fork_graph));
1992 cache.set_fork_graph(fork_graph);
1993
1994 let program1 = Pubkey::new_unique();
1995 cache.assign_program(program1, new_test_loaded_program(0, 1));
1996 cache.assign_program(program1, new_test_loaded_program(10, 11));
1997 cache.assign_program(program1, new_test_loaded_program(20, 21));
1998
1999 let program2 = Pubkey::new_unique();
2000 cache.assign_program(program2, new_test_loaded_program(5, 6));
2001 cache.assign_program(
2002 program2,
2003 new_test_loaded_program(11, 11 + DELAY_VISIBILITY_SLOT_OFFSET),
2004 );
2005
2006 let program3 = Pubkey::new_unique();
2007 cache.assign_program(program3, new_test_loaded_program(25, 26));
2008
2009 let program4 = Pubkey::new_unique();
2010 cache.assign_program(program4, new_test_loaded_program(0, 1));
2011 cache.assign_program(program4, new_test_loaded_program(5, 6));
2012 cache.assign_program(
2014 program4,
2015 new_test_loaded_program(15, 15 + DELAY_VISIBILITY_SLOT_OFFSET),
2016 );
2017
2018 let mut missing = vec![
2035 (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)),
2036 (program2, (LoadedProgramMatchCriteria::NoCriteria, 2)),
2037 (program3, (LoadedProgramMatchCriteria::NoCriteria, 3)),
2038 (program4, (LoadedProgramMatchCriteria::NoCriteria, 4)),
2039 ];
2040 let mut extracted = LoadedProgramsForTxBatch::new(22, cache.environments.clone());
2041 cache.extract(&mut missing, &mut extracted, true);
2042
2043 assert!(match_slot(&extracted, &program1, 20, 22));
2044 assert!(match_slot(&extracted, &program4, 0, 22));
2045
2046 assert!(match_missing(&missing, &program2, false));
2047 assert!(match_missing(&missing, &program3, false));
2048
2049 let mut missing = vec![
2051 (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)),
2052 (program2, (LoadedProgramMatchCriteria::NoCriteria, 1)),
2053 (program3, (LoadedProgramMatchCriteria::NoCriteria, 1)),
2054 (program4, (LoadedProgramMatchCriteria::NoCriteria, 1)),
2055 ];
2056 let mut extracted = LoadedProgramsForTxBatch::new(15, cache.environments.clone());
2057 cache.extract(&mut missing, &mut extracted, true);
2058
2059 assert!(match_slot(&extracted, &program1, 0, 15));
2060 assert!(match_slot(&extracted, &program2, 11, 15));
2061
2062 let tombstone = extracted
2065 .find(&program4)
2066 .expect("Failed to find the tombstone");
2067 assert_matches!(tombstone.program, LoadedProgramType::DelayVisibility);
2068 assert_eq!(tombstone.deployment_slot, 15);
2069
2070 assert!(match_missing(&missing, &program3, false));
2071
2072 let mut missing = vec![
2074 (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)),
2075 (program2, (LoadedProgramMatchCriteria::NoCriteria, 1)),
2076 (program3, (LoadedProgramMatchCriteria::NoCriteria, 1)),
2077 (program4, (LoadedProgramMatchCriteria::NoCriteria, 1)),
2078 ];
2079 let mut extracted = LoadedProgramsForTxBatch::new(18, cache.environments.clone());
2080 cache.extract(&mut missing, &mut extracted, true);
2081
2082 assert!(match_slot(&extracted, &program1, 0, 18));
2083 assert!(match_slot(&extracted, &program2, 11, 18));
2084
2085 assert!(match_slot(&extracted, &program4, 15, 18));
2087
2088 assert!(match_missing(&missing, &program3, false));
2089
2090 let mut missing = vec![
2092 (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)),
2093 (program2, (LoadedProgramMatchCriteria::NoCriteria, 1)),
2094 (program3, (LoadedProgramMatchCriteria::NoCriteria, 1)),
2095 (program4, (LoadedProgramMatchCriteria::NoCriteria, 1)),
2096 ];
2097 let mut extracted = LoadedProgramsForTxBatch::new(23, cache.environments.clone());
2098 cache.extract(&mut missing, &mut extracted, true);
2099
2100 assert!(match_slot(&extracted, &program1, 0, 23));
2101 assert!(match_slot(&extracted, &program2, 11, 23));
2102
2103 assert!(match_slot(&extracted, &program4, 15, 23));
2105
2106 assert!(match_missing(&missing, &program3, false));
2107
2108 let mut missing = vec![
2110 (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)),
2111 (program2, (LoadedProgramMatchCriteria::NoCriteria, 1)),
2112 (program3, (LoadedProgramMatchCriteria::NoCriteria, 1)),
2113 (program4, (LoadedProgramMatchCriteria::NoCriteria, 1)),
2114 ];
2115 let mut extracted = LoadedProgramsForTxBatch::new(11, cache.environments.clone());
2116 cache.extract(&mut missing, &mut extracted, true);
2117
2118 assert!(match_slot(&extracted, &program1, 0, 11));
2119 let tombstone = extracted
2121 .find(&program2)
2122 .expect("Failed to find the tombstone");
2123 assert_matches!(tombstone.program, LoadedProgramType::DelayVisibility);
2124 assert_eq!(tombstone.deployment_slot, 11);
2125 assert!(match_slot(&extracted, &program4, 5, 11));
2126
2127 assert!(match_missing(&missing, &program3, false));
2128
2129 cache.prune(5, 0);
2130
2131 let mut missing = vec![
2148 (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)),
2149 (program2, (LoadedProgramMatchCriteria::NoCriteria, 1)),
2150 (program3, (LoadedProgramMatchCriteria::NoCriteria, 1)),
2151 (program4, (LoadedProgramMatchCriteria::NoCriteria, 1)),
2152 ];
2153 let mut extracted = LoadedProgramsForTxBatch::new(21, cache.environments.clone());
2154 cache.extract(&mut missing, &mut extracted, true);
2155
2156 assert!(match_slot(&extracted, &program1, 0, 21));
2158 assert!(match_slot(&extracted, &program2, 11, 21));
2159 assert!(match_slot(&extracted, &program4, 15, 21));
2160
2161 assert!(match_missing(&missing, &program3, false));
2162
2163 let mut missing = vec![
2165 (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)),
2166 (program2, (LoadedProgramMatchCriteria::NoCriteria, 1)),
2167 (program3, (LoadedProgramMatchCriteria::NoCriteria, 1)),
2168 (program4, (LoadedProgramMatchCriteria::NoCriteria, 1)),
2169 ];
2170 let mut extracted = LoadedProgramsForTxBatch::new(27, cache.environments.clone());
2171 cache.extract(&mut missing, &mut extracted, true);
2172
2173 assert!(match_slot(&extracted, &program1, 0, 27));
2174 assert!(match_slot(&extracted, &program2, 11, 27));
2175 assert!(match_slot(&extracted, &program3, 25, 27));
2176 assert!(match_slot(&extracted, &program4, 5, 27));
2177
2178 cache.prune(15, 0);
2179
2180 let mut missing = vec![
2197 (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)),
2198 (program2, (LoadedProgramMatchCriteria::NoCriteria, 1)),
2199 (program3, (LoadedProgramMatchCriteria::NoCriteria, 1)),
2200 (program4, (LoadedProgramMatchCriteria::NoCriteria, 1)),
2201 ];
2202 let mut extracted = LoadedProgramsForTxBatch::new(23, cache.environments.clone());
2203 cache.extract(&mut missing, &mut extracted, true);
2204
2205 assert!(match_slot(&extracted, &program1, 0, 23));
2206 assert!(match_slot(&extracted, &program2, 11, 23));
2207 assert!(match_slot(&extracted, &program4, 15, 23));
2208
2209 assert!(match_missing(&missing, &program3, false));
2211 }
2212
2213 #[test]
2214 fn test_extract_using_deployment_slot() {
2215 let mut cache = new_mock_cache::<TestForkGraphSpecific>();
2216
2217 let mut fork_graph = TestForkGraphSpecific::default();
2233 fork_graph.insert_fork(&[0, 10, 20, 22]);
2234 fork_graph.insert_fork(&[0, 5, 11, 12, 15, 16, 18, 19, 21, 23]);
2235 fork_graph.insert_fork(&[0, 5, 11, 25, 27]);
2236
2237 let fork_graph = Arc::new(RwLock::new(fork_graph));
2238 cache.set_fork_graph(fork_graph);
2239
2240 let program1 = Pubkey::new_unique();
2241 cache.assign_program(program1, new_test_loaded_program(0, 1));
2242 cache.assign_program(program1, new_test_loaded_program(20, 21));
2243
2244 let program2 = Pubkey::new_unique();
2245 cache.assign_program(program2, new_test_loaded_program(5, 6));
2246 cache.assign_program(program2, new_test_loaded_program(11, 12));
2247
2248 let program3 = Pubkey::new_unique();
2249 cache.assign_program(program3, new_test_loaded_program(25, 26));
2250
2251 let mut missing = vec![
2253 (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)),
2254 (program2, (LoadedProgramMatchCriteria::NoCriteria, 1)),
2255 (program3, (LoadedProgramMatchCriteria::NoCriteria, 1)),
2256 ];
2257 let mut extracted = LoadedProgramsForTxBatch::new(12, cache.environments.clone());
2258 cache.extract(&mut missing, &mut extracted, true);
2259
2260 assert!(match_slot(&extracted, &program1, 0, 12));
2261 assert!(match_slot(&extracted, &program2, 11, 12));
2262
2263 assert!(match_missing(&missing, &program3, false));
2264
2265 let mut missing = vec![
2267 (
2268 program1,
2269 (LoadedProgramMatchCriteria::DeployedOnOrAfterSlot(5), 1),
2270 ),
2271 (
2272 program2,
2273 (LoadedProgramMatchCriteria::DeployedOnOrAfterSlot(5), 1),
2274 ),
2275 (program3, (LoadedProgramMatchCriteria::NoCriteria, 1)),
2276 ];
2277 let mut extracted = LoadedProgramsForTxBatch::new(12, cache.environments.clone());
2278 cache.extract(&mut missing, &mut extracted, true);
2279
2280 assert!(match_slot(&extracted, &program2, 11, 12));
2281
2282 assert!(match_missing(&missing, &program1, false));
2283 assert!(match_missing(&missing, &program3, false));
2284 }
2285
2286 #[test]
2287 fn test_extract_unloaded() {
2288 let mut cache = new_mock_cache::<TestForkGraphSpecific>();
2289
2290 let mut fork_graph = TestForkGraphSpecific::default();
2306 fork_graph.insert_fork(&[0, 10, 20, 22]);
2307 fork_graph.insert_fork(&[0, 5, 11, 15, 16, 19, 21, 23]);
2308 fork_graph.insert_fork(&[0, 5, 11, 25, 27]);
2309
2310 let fork_graph = Arc::new(RwLock::new(fork_graph));
2311 cache.set_fork_graph(fork_graph);
2312
2313 let program1 = Pubkey::new_unique();
2314 cache.assign_program(program1, new_test_loaded_program(0, 1));
2315 cache.assign_program(program1, new_test_loaded_program(20, 21));
2316
2317 let program2 = Pubkey::new_unique();
2318 cache.assign_program(program2, new_test_loaded_program(5, 6));
2319 cache.assign_program(program2, new_test_loaded_program(11, 12));
2320
2321 let program3 = Pubkey::new_unique();
2322 let _ = insert_unloaded_program(&mut cache, program3, 25);
2324
2325 cache.assign_program(
2329 program3,
2330 Arc::new(
2331 new_test_loaded_program(20, 21)
2332 .to_unloaded()
2333 .expect("Failed to create unloaded program"),
2334 ),
2335 );
2336
2337 let mut missing = vec![
2339 (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)),
2340 (program2, (LoadedProgramMatchCriteria::NoCriteria, 1)),
2341 (program3, (LoadedProgramMatchCriteria::NoCriteria, 1)),
2342 ];
2343 let mut extracted = LoadedProgramsForTxBatch::new(19, cache.environments.clone());
2344 cache.extract(&mut missing, &mut extracted, true);
2345
2346 assert!(match_slot(&extracted, &program1, 0, 19));
2347 assert!(match_slot(&extracted, &program2, 11, 19));
2348
2349 assert!(match_missing(&missing, &program3, false));
2350
2351 let mut missing = vec![
2353 (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)),
2354 (program2, (LoadedProgramMatchCriteria::NoCriteria, 1)),
2355 (program3, (LoadedProgramMatchCriteria::NoCriteria, 1)),
2356 ];
2357 let mut extracted = LoadedProgramsForTxBatch::new(27, cache.environments.clone());
2358 cache.extract(&mut missing, &mut extracted, true);
2359
2360 assert!(match_slot(&extracted, &program1, 0, 27));
2361 assert!(match_slot(&extracted, &program2, 11, 27));
2362
2363 assert!(match_missing(&missing, &program3, true));
2364
2365 let mut missing = vec![
2367 (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)),
2368 (program2, (LoadedProgramMatchCriteria::NoCriteria, 1)),
2369 (program3, (LoadedProgramMatchCriteria::NoCriteria, 1)),
2370 ];
2371 let mut extracted = LoadedProgramsForTxBatch::new(22, cache.environments.clone());
2372 cache.extract(&mut missing, &mut extracted, true);
2373
2374 assert!(match_slot(&extracted, &program1, 20, 22));
2375
2376 assert!(match_missing(&missing, &program2, false));
2377 assert!(match_missing(&missing, &program3, true));
2378 }
2379
2380 #[test]
2381 fn test_unloaded() {
2382 let mut cache = new_mock_cache::<TestForkGraph>();
2383 for loaded_program_type in [
2384 LoadedProgramType::FailedVerification(cache.environments.program_runtime_v1.clone()),
2385 LoadedProgramType::Closed,
2386 LoadedProgramType::DelayVisibility, LoadedProgramType::Unloaded(cache.environments.program_runtime_v1.clone()),
2388 LoadedProgramType::Builtin(BuiltinProgram::new_mock()),
2389 ] {
2390 let entry = Arc::new(LoadedProgram {
2391 program: loaded_program_type,
2392 account_size: 0,
2393 deployment_slot: 0,
2394 effective_slot: 0,
2395 tx_usage_counter: AtomicU64::default(),
2396 ix_usage_counter: AtomicU64::default(),
2397 latest_access_slot: AtomicU64::default(),
2398 });
2399 assert!(entry.to_unloaded().is_none());
2400
2401 let program_id = Pubkey::new_unique();
2403 cache.assign_program(program_id, entry.clone());
2404 cache.unload_program_entry(&program_id, &entry);
2405 assert_eq!(
2406 cache.entries.get(&program_id).unwrap().slot_versions.len(),
2407 1
2408 );
2409 assert!(cache.stats.evictions.is_empty());
2410 }
2411
2412 let entry = new_test_loaded_program_with_usage(1, 2, AtomicU64::new(3));
2413 let unloaded_entry = entry.to_unloaded().unwrap();
2414 assert_eq!(unloaded_entry.deployment_slot, 1);
2415 assert_eq!(unloaded_entry.effective_slot, 2);
2416 assert_eq!(unloaded_entry.latest_access_slot.load(Ordering::Relaxed), 1);
2417 assert_eq!(unloaded_entry.tx_usage_counter.load(Ordering::Relaxed), 3);
2418
2419 let program_id = Pubkey::new_unique();
2421 cache.assign_program(program_id, entry.clone());
2422 cache.unload_program_entry(&program_id, &entry);
2423 assert!(cache.stats.evictions.get(&program_id).is_some());
2424 }
2425
2426 #[test]
2427 fn test_fork_prune_find_first_ancestor() {
2428 let mut cache = new_mock_cache::<TestForkGraphSpecific>();
2429
2430 let mut fork_graph = TestForkGraphSpecific::default();
2441 fork_graph.insert_fork(&[0, 10, 20]);
2442 fork_graph.insert_fork(&[0, 5]);
2443 let fork_graph = Arc::new(RwLock::new(fork_graph));
2444 cache.set_fork_graph(fork_graph);
2445
2446 let program1 = Pubkey::new_unique();
2447 cache.assign_program(program1, new_test_loaded_program(0, 1));
2448 cache.assign_program(program1, new_test_loaded_program(5, 6));
2449
2450 cache.prune(10, 0);
2451
2452 let mut missing = vec![(program1, (LoadedProgramMatchCriteria::NoCriteria, 1))];
2453 let mut extracted = LoadedProgramsForTxBatch::new(20, cache.environments.clone());
2454 cache.extract(&mut missing, &mut extracted, true);
2455
2456 assert_eq!(
2458 extracted
2459 .find(&program1)
2460 .expect("Did not find the program")
2461 .deployment_slot,
2462 0
2463 );
2464 }
2465
2466 #[test]
2467 fn test_prune_by_deployment_slot() {
2468 let mut cache = new_mock_cache::<TestForkGraphSpecific>();
2469
2470 let mut fork_graph = TestForkGraphSpecific::default();
2481 fork_graph.insert_fork(&[0, 10, 20]);
2482 fork_graph.insert_fork(&[0, 5, 6]);
2483 let fork_graph = Arc::new(RwLock::new(fork_graph));
2484 cache.set_fork_graph(fork_graph);
2485
2486 let program1 = Pubkey::new_unique();
2487 cache.assign_program(program1, new_test_loaded_program(0, 1));
2488 cache.assign_program(program1, new_test_loaded_program(5, 6));
2489
2490 let program2 = Pubkey::new_unique();
2491 cache.assign_program(program2, new_test_loaded_program(10, 11));
2492
2493 let mut missing = vec![
2494 (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)),
2495 (program2, (LoadedProgramMatchCriteria::NoCriteria, 1)),
2496 ];
2497 let mut extracted = LoadedProgramsForTxBatch::new(20, cache.environments.clone());
2498 cache.extract(&mut missing, &mut extracted, true);
2499
2500 assert!(match_slot(&extracted, &program1, 0, 20));
2501 assert!(match_slot(&extracted, &program2, 10, 20));
2502
2503 let mut missing = vec![
2504 (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)),
2505 (program2, (LoadedProgramMatchCriteria::NoCriteria, 1)),
2506 ];
2507 let mut extracted = LoadedProgramsForTxBatch::new(6, cache.environments.clone());
2508 cache.extract(&mut missing, &mut extracted, true);
2509
2510 assert!(match_slot(&extracted, &program1, 5, 6));
2511 assert!(match_missing(&missing, &program2, false));
2512
2513 cache.prune_by_deployment_slot(5);
2516
2517 let mut missing = vec![
2518 (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)),
2519 (program2, (LoadedProgramMatchCriteria::NoCriteria, 1)),
2520 ];
2521 let mut extracted = LoadedProgramsForTxBatch::new(20, cache.environments.clone());
2522 cache.extract(&mut missing, &mut extracted, true);
2523
2524 assert!(match_slot(&extracted, &program1, 0, 20));
2525 assert!(match_slot(&extracted, &program2, 10, 20));
2526
2527 let mut missing = vec![
2528 (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)),
2529 (program2, (LoadedProgramMatchCriteria::NoCriteria, 1)),
2530 ];
2531 let mut extracted = LoadedProgramsForTxBatch::new(6, cache.environments.clone());
2532 cache.extract(&mut missing, &mut extracted, true);
2533
2534 assert!(match_slot(&extracted, &program1, 0, 6));
2535 assert!(match_missing(&missing, &program2, false));
2536
2537 cache.prune_by_deployment_slot(10);
2540
2541 let mut missing = vec![
2542 (program1, (LoadedProgramMatchCriteria::NoCriteria, 1)),
2543 (program2, (LoadedProgramMatchCriteria::NoCriteria, 1)),
2544 ];
2545 let mut extracted = LoadedProgramsForTxBatch::new(20, cache.environments.clone());
2546 cache.extract(&mut missing, &mut extracted, true);
2547
2548 assert!(match_slot(&extracted, &program1, 0, 20));
2549 assert!(match_missing(&missing, &program2, false));
2550 }
2551
2552 #[test]
2553 fn test_usable_entries_for_slot() {
2554 new_mock_cache::<TestForkGraph>();
2555 let tombstone = Arc::new(LoadedProgram::new_tombstone(0, LoadedProgramType::Closed));
2556
2557 assert!(
2558 LoadedPrograms::<TestForkGraph>::matches_loaded_program_criteria(
2559 &tombstone,
2560 &LoadedProgramMatchCriteria::NoCriteria
2561 )
2562 );
2563
2564 assert!(
2565 LoadedPrograms::<TestForkGraph>::matches_loaded_program_criteria(
2566 &tombstone,
2567 &LoadedProgramMatchCriteria::Tombstone
2568 )
2569 );
2570
2571 assert!(
2572 LoadedPrograms::<TestForkGraph>::matches_loaded_program_criteria(
2573 &tombstone,
2574 &LoadedProgramMatchCriteria::DeployedOnOrAfterSlot(0)
2575 )
2576 );
2577
2578 assert!(
2579 !LoadedPrograms::<TestForkGraph>::matches_loaded_program_criteria(
2580 &tombstone,
2581 &LoadedProgramMatchCriteria::DeployedOnOrAfterSlot(1)
2582 )
2583 );
2584
2585 let program = new_test_loaded_program(0, 1);
2586
2587 assert!(
2588 LoadedPrograms::<TestForkGraph>::matches_loaded_program_criteria(
2589 &program,
2590 &LoadedProgramMatchCriteria::NoCriteria
2591 )
2592 );
2593
2594 assert!(
2595 !LoadedPrograms::<TestForkGraph>::matches_loaded_program_criteria(
2596 &program,
2597 &LoadedProgramMatchCriteria::Tombstone
2598 )
2599 );
2600
2601 assert!(
2602 LoadedPrograms::<TestForkGraph>::matches_loaded_program_criteria(
2603 &program,
2604 &LoadedProgramMatchCriteria::DeployedOnOrAfterSlot(0)
2605 )
2606 );
2607
2608 assert!(
2609 !LoadedPrograms::<TestForkGraph>::matches_loaded_program_criteria(
2610 &program,
2611 &LoadedProgramMatchCriteria::DeployedOnOrAfterSlot(1)
2612 )
2613 );
2614
2615 let program = Arc::new(new_test_loaded_program_with_usage(
2616 0,
2617 1,
2618 AtomicU64::default(),
2619 ));
2620
2621 assert!(
2622 LoadedPrograms::<TestForkGraph>::matches_loaded_program_criteria(
2623 &program,
2624 &LoadedProgramMatchCriteria::NoCriteria
2625 )
2626 );
2627
2628 assert!(
2629 !LoadedPrograms::<TestForkGraph>::matches_loaded_program_criteria(
2630 &program,
2631 &LoadedProgramMatchCriteria::Tombstone
2632 )
2633 );
2634
2635 assert!(
2636 LoadedPrograms::<TestForkGraph>::matches_loaded_program_criteria(
2637 &program,
2638 &LoadedProgramMatchCriteria::DeployedOnOrAfterSlot(0)
2639 )
2640 );
2641
2642 assert!(
2643 !LoadedPrograms::<TestForkGraph>::matches_loaded_program_criteria(
2644 &program,
2645 &LoadedProgramMatchCriteria::DeployedOnOrAfterSlot(1)
2646 )
2647 );
2648 }
2649}