1use {
2 crate::{invoke_context::InvokeContext, timings::ExecuteDetailsTimings},
3 solana_measure::measure::Measure,
4 solana_rbpf::{
5 elf::Executable,
6 error::EbpfError,
7 verifier::RequisiteVerifier,
8 vm::{BuiltInProgram, VerifiedExecutable},
9 },
10 solana_sdk::{
11 bpf_loader, bpf_loader_deprecated, bpf_loader_upgradeable, clock::Slot, pubkey::Pubkey,
12 saturating_add_assign,
13 },
14 std::{
15 collections::HashMap,
16 fmt::{Debug, Formatter},
17 sync::{atomic::AtomicU64, Arc},
18 },
19};
20
21#[derive(Copy, Clone, PartialEq)]
23pub enum BlockRelation {
24 Ancestor,
26 Equal,
28 Descendant,
30 Unrelated,
32 Unknown,
34}
35
36pub trait ForkGraph {
38 fn relationship(&self, a: Slot, b: Slot) -> BlockRelation;
40}
41
42pub trait WorkingSlot {
44 fn current_slot(&self) -> Slot;
46
47 fn is_ancestor(&self, other: Slot) -> bool;
49}
50
51#[derive(Default)]
52pub enum LoadedProgramType {
53 #[default]
55 Invalid,
56 LegacyV0(VerifiedExecutable<RequisiteVerifier, InvokeContext<'static>>),
57 LegacyV1(VerifiedExecutable<RequisiteVerifier, InvokeContext<'static>>),
58 BuiltIn(BuiltInProgram<InvokeContext<'static>>),
60}
61
62impl Debug for LoadedProgramType {
63 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
64 match self {
65 LoadedProgramType::Invalid => write!(f, "LoadedProgramType::Invalid"),
66 LoadedProgramType::LegacyV0(_) => write!(f, "LoadedProgramType::LegacyV0"),
67 LoadedProgramType::LegacyV1(_) => write!(f, "LoadedProgramType::LegacyV1"),
68 LoadedProgramType::BuiltIn(_) => write!(f, "LoadedProgramType::BuiltIn"),
69 }
70 }
71}
72
73#[derive(Debug, Default)]
74pub struct LoadedProgram {
75 pub program: LoadedProgramType,
77 pub account_size: usize,
79 pub deployment_slot: Slot,
81 pub effective_slot: Slot,
83 pub usage_counter: AtomicU64,
85}
86
87#[derive(Debug, Default)]
88pub struct LoadProgramMetrics {
89 pub program_id: String,
90 pub register_syscalls_us: u64,
91 pub load_elf_us: u64,
92 pub verify_code_us: u64,
93 pub jit_compile_us: u64,
94}
95
96impl LoadProgramMetrics {
97 pub fn submit_datapoint(&self, timings: &mut ExecuteDetailsTimings) {
98 saturating_add_assign!(
99 timings.create_executor_register_syscalls_us,
100 self.register_syscalls_us
101 );
102 saturating_add_assign!(timings.create_executor_load_elf_us, self.load_elf_us);
103 saturating_add_assign!(timings.create_executor_verify_code_us, self.verify_code_us);
104 saturating_add_assign!(timings.create_executor_jit_compile_us, self.jit_compile_us);
105 datapoint_trace!(
106 "create_executor_trace",
107 ("program_id", self.program_id, String),
108 ("register_syscalls_us", self.register_syscalls_us, i64),
109 ("load_elf_us", self.load_elf_us, i64),
110 ("verify_code_us", self.verify_code_us, i64),
111 ("jit_compile_us", self.jit_compile_us, i64),
112 );
113 }
114}
115
116impl LoadedProgram {
117 pub fn new(
119 loader_key: &Pubkey,
120 loader: Arc<BuiltInProgram<InvokeContext<'static>>>,
121 deployment_slot: Slot,
122 elf_bytes: &[u8],
123 account_size: usize,
124 use_jit: bool,
125 metrics: &mut LoadProgramMetrics,
126 ) -> Result<Self, EbpfError> {
127 let mut load_elf_time = Measure::start("load_elf_time");
128 let executable = Executable::load(elf_bytes, loader.clone())?;
129 load_elf_time.stop();
130 metrics.load_elf_us = load_elf_time.as_us();
131
132 let mut verify_code_time = Measure::start("verify_code_time");
133
134 #[allow(unused_mut)]
136 let mut program = if bpf_loader_deprecated::check_id(loader_key) {
137 LoadedProgramType::LegacyV0(VerifiedExecutable::from_executable(executable)?)
138 } else if bpf_loader::check_id(loader_key) || bpf_loader_upgradeable::check_id(loader_key) {
139 LoadedProgramType::LegacyV1(VerifiedExecutable::from_executable(executable)?)
140 } else {
141 panic!();
142 };
143 verify_code_time.stop();
144 metrics.verify_code_us = verify_code_time.as_us();
145
146 if use_jit {
147 #[cfg(all(not(target_os = "windows"), target_arch = "x86_64"))]
148 {
149 let mut jit_compile_time = Measure::start("jit_compile_time");
150 match &mut program {
151 LoadedProgramType::LegacyV0(executable) => executable.jit_compile(),
152 LoadedProgramType::LegacyV1(executable) => executable.jit_compile(),
153 _ => Err(EbpfError::JitNotCompiled),
154 }?;
155 jit_compile_time.stop();
156 metrics.jit_compile_us = jit_compile_time.as_us();
157 }
158 }
159
160 Ok(Self {
161 deployment_slot,
162 account_size,
163 effective_slot: deployment_slot.saturating_add(1),
164 usage_counter: AtomicU64::new(0),
165 program,
166 })
167 }
168
169 pub fn new_built_in(
171 deployment_slot: Slot,
172 program: BuiltInProgram<InvokeContext<'static>>,
173 ) -> Self {
174 Self {
175 deployment_slot,
176 account_size: 0,
177 effective_slot: deployment_slot.saturating_add(1),
178 usage_counter: AtomicU64::new(0),
179 program: LoadedProgramType::BuiltIn(program),
180 }
181 }
182
183 pub fn new_tombstone() -> Self {
184 Self {
185 program: LoadedProgramType::Invalid,
186 account_size: 0,
187 deployment_slot: 0,
188 effective_slot: 0,
189 usage_counter: AtomicU64::default(),
190 }
191 }
192
193 pub fn is_tombstone(&self) -> bool {
194 matches!(self.program, LoadedProgramType::Invalid)
195 }
196}
197
198#[derive(Debug, Default)]
199pub struct LoadedPrograms {
200 entries: HashMap<Pubkey, Vec<Arc<LoadedProgram>>>,
204}
205
206#[cfg(RUSTC_WITH_SPECIALIZATION)]
207impl solana_frozen_abi::abi_example::AbiExample for LoadedPrograms {
208 fn example() -> Self {
209 Self::default()
213 }
214}
215
216pub enum LoadedProgramEntry {
217 WasOccupied(Arc<LoadedProgram>),
218 WasVacant(Arc<LoadedProgram>),
219}
220
221impl LoadedPrograms {
222 pub fn insert_entry(&mut self, key: Pubkey, entry: LoadedProgram) -> LoadedProgramEntry {
224 let second_level = self.entries.entry(key).or_insert_with(Vec::new);
225 let index = second_level
226 .iter()
227 .position(|at| at.effective_slot >= entry.effective_slot);
228 if let Some(index) = index {
229 let existing = second_level
230 .get(index)
231 .expect("Missing entry, even though position was found");
232 if existing.deployment_slot == entry.deployment_slot
233 && existing.effective_slot == entry.effective_slot
234 {
235 return LoadedProgramEntry::WasOccupied(existing.clone());
236 }
237 }
238 let new_entry = Arc::new(entry);
239 second_level.insert(index.unwrap_or(second_level.len()), new_entry.clone());
240 LoadedProgramEntry::WasVacant(new_entry)
241 }
242
243 pub fn prune<F: ForkGraph>(&mut self, fork_graph: &F, new_root: Slot) {
245 self.entries.retain(|_key, second_level| {
246 let mut first_ancestor = true;
247 *second_level = second_level
248 .iter()
249 .rev()
250 .filter(|entry| {
251 let relation = fork_graph.relationship(entry.deployment_slot, new_root);
252 if entry.deployment_slot >= new_root {
253 matches!(relation, BlockRelation::Equal | BlockRelation::Descendant)
254 } else if first_ancestor {
255 first_ancestor = false;
256 matches!(relation, BlockRelation::Ancestor)
257 } else {
258 false
259 }
260 })
261 .cloned()
262 .collect();
263 second_level.reverse();
264 !second_level.is_empty()
265 });
266 }
267
268 pub fn extract<S: WorkingSlot>(
271 &self,
272 working_slot: &S,
273 keys: impl Iterator<Item = Pubkey>,
274 ) -> (HashMap<Pubkey, Arc<LoadedProgram>>, Vec<Pubkey>) {
275 let mut missing = Vec::new();
276 let found = keys
277 .filter_map(|key| {
278 if let Some(second_level) = self.entries.get(&key) {
279 for entry in second_level.iter().rev() {
280 if working_slot.current_slot() >= entry.effective_slot
281 && working_slot.is_ancestor(entry.deployment_slot)
282 {
283 return Some((key, entry.clone()));
284 }
285 }
286 }
287 missing.push(key);
288 None
289 })
290 .collect();
291 (found, missing)
292 }
293
294 pub fn sort_and_evict(&mut self) {
296 }
299
300 pub fn remove_entries(&mut self, _key: impl Iterator<Item = Pubkey>) {
302 }
304}
305
306#[cfg(test)]
307mod tests {
308 use {
309 crate::loaded_programs::{
310 BlockRelation, ForkGraph, LoadedProgram, LoadedProgramEntry, LoadedProgramType,
311 LoadedPrograms, WorkingSlot,
312 },
313 solana_sdk::{clock::Slot, pubkey::Pubkey},
314 std::{
315 collections::HashMap,
316 ops::ControlFlow,
317 sync::{atomic::AtomicU64, Arc},
318 },
319 };
320
321 #[test]
322 fn test_tombstone() {
323 let tombstone = LoadedProgram::new_tombstone();
324 assert!(matches!(tombstone.program, LoadedProgramType::Invalid));
325 assert!(tombstone.is_tombstone());
326 }
327
328 struct TestForkGraph {
329 relation: BlockRelation,
330 }
331 impl ForkGraph for TestForkGraph {
332 fn relationship(&self, _a: Slot, _b: Slot) -> BlockRelation {
333 self.relation
334 }
335 }
336
337 #[test]
338 fn test_prune_empty() {
339 let mut cache = LoadedPrograms::default();
340 let fork_graph = TestForkGraph {
341 relation: BlockRelation::Unrelated,
342 };
343
344 cache.prune(&fork_graph, 0);
345 assert!(cache.entries.is_empty());
346
347 cache.prune(&fork_graph, 10);
348 assert!(cache.entries.is_empty());
349
350 let fork_graph = TestForkGraph {
351 relation: BlockRelation::Ancestor,
352 };
353
354 cache.prune(&fork_graph, 0);
355 assert!(cache.entries.is_empty());
356
357 cache.prune(&fork_graph, 10);
358 assert!(cache.entries.is_empty());
359
360 let fork_graph = TestForkGraph {
361 relation: BlockRelation::Descendant,
362 };
363
364 cache.prune(&fork_graph, 0);
365 assert!(cache.entries.is_empty());
366
367 cache.prune(&fork_graph, 10);
368 assert!(cache.entries.is_empty());
369
370 let fork_graph = TestForkGraph {
371 relation: BlockRelation::Unknown,
372 };
373
374 cache.prune(&fork_graph, 0);
375 assert!(cache.entries.is_empty());
376
377 cache.prune(&fork_graph, 10);
378 assert!(cache.entries.is_empty());
379 }
380
381 #[derive(Default)]
382 struct TestForkGraphSpecific {
383 forks: Vec<Vec<Slot>>,
384 }
385
386 impl TestForkGraphSpecific {
387 fn insert_fork(&mut self, fork: &[Slot]) {
388 let mut fork = fork.to_vec();
389 fork.sort();
390 self.forks.push(fork)
391 }
392 }
393
394 impl ForkGraph for TestForkGraphSpecific {
395 fn relationship(&self, a: Slot, b: Slot) -> BlockRelation {
396 match self.forks.iter().try_for_each(|fork| {
397 let relation = fork
398 .iter()
399 .position(|x| *x == a)
400 .and_then(|a_pos| {
401 fork.iter().position(|x| *x == b).and_then(|b_pos| {
402 (a_pos == b_pos)
403 .then_some(BlockRelation::Equal)
404 .or_else(|| (a_pos < b_pos).then_some(BlockRelation::Ancestor))
405 .or(Some(BlockRelation::Descendant))
406 })
407 })
408 .unwrap_or(BlockRelation::Unrelated);
409
410 if relation != BlockRelation::Unrelated {
411 return ControlFlow::Break(relation);
412 }
413
414 ControlFlow::Continue(())
415 }) {
416 ControlFlow::Break(relation) => relation,
417 _ => BlockRelation::Unrelated,
418 }
419 }
420 }
421
422 struct TestWorkingSlot {
423 slot: Slot,
424 fork: Vec<Slot>,
425 slot_pos: usize,
426 }
427
428 impl TestWorkingSlot {
429 fn new(slot: Slot, fork: &[Slot]) -> Self {
430 let mut fork = fork.to_vec();
431 fork.sort();
432 let slot_pos = fork
433 .iter()
434 .position(|current| *current == slot)
435 .expect("The fork didn't have the slot in it");
436 TestWorkingSlot {
437 slot,
438 fork,
439 slot_pos,
440 }
441 }
442
443 fn update_slot(&mut self, slot: Slot) {
444 self.slot = slot;
445 self.slot_pos = self
446 .fork
447 .iter()
448 .position(|current| *current == slot)
449 .expect("The fork didn't have the slot in it");
450 }
451 }
452
453 impl WorkingSlot for TestWorkingSlot {
454 fn current_slot(&self) -> Slot {
455 self.slot
456 }
457
458 fn is_ancestor(&self, other: Slot) -> bool {
459 self.fork
460 .iter()
461 .position(|current| *current == other)
462 .map(|other_pos| other_pos < self.slot_pos)
463 .unwrap_or(false)
464 }
465 }
466
467 fn new_test_loaded_program(deployment_slot: Slot, effective_slot: Slot) -> LoadedProgram {
468 LoadedProgram {
469 program: LoadedProgramType::Invalid,
470 account_size: 0,
471 deployment_slot,
472 effective_slot,
473 usage_counter: AtomicU64::default(),
474 }
475 }
476
477 fn match_slot(
478 table: &HashMap<Pubkey, Arc<LoadedProgram>>,
479 program: &Pubkey,
480 deployment_slot: Slot,
481 ) -> bool {
482 table
483 .get(program)
484 .map(|entry| entry.deployment_slot == deployment_slot)
485 .unwrap_or(false)
486 }
487
488 #[test]
489 fn test_fork_extract_and_prune() {
490 let mut cache = LoadedPrograms::default();
491
492 let mut fork_graph = TestForkGraphSpecific::default();
508 fork_graph.insert_fork(&[0, 10, 20, 22]);
509 fork_graph.insert_fork(&[0, 5, 11, 15, 16]);
510 fork_graph.insert_fork(&[0, 5, 11, 25, 27]);
511
512 let program1 = Pubkey::new_unique();
513 assert!(matches!(
514 cache.insert_entry(program1, new_test_loaded_program(0, 1)),
515 LoadedProgramEntry::WasVacant(_)
516 ));
517 assert!(matches!(
518 cache.insert_entry(program1, new_test_loaded_program(10, 11)),
519 LoadedProgramEntry::WasVacant(_)
520 ));
521 assert!(matches!(
522 cache.insert_entry(program1, new_test_loaded_program(20, 21)),
523 LoadedProgramEntry::WasVacant(_)
524 ));
525
526 assert!(matches!(
528 cache.insert_entry(program1, new_test_loaded_program(20, 21)),
529 LoadedProgramEntry::WasOccupied(_)
530 ));
531
532 let program2 = Pubkey::new_unique();
533 assert!(matches!(
534 cache.insert_entry(program2, new_test_loaded_program(5, 6)),
535 LoadedProgramEntry::WasVacant(_)
536 ));
537 assert!(matches!(
538 cache.insert_entry(program2, new_test_loaded_program(11, 12)),
539 LoadedProgramEntry::WasVacant(_)
540 ));
541
542 let program3 = Pubkey::new_unique();
543 assert!(matches!(
544 cache.insert_entry(program3, new_test_loaded_program(25, 26)),
545 LoadedProgramEntry::WasVacant(_)
546 ));
547
548 let program4 = Pubkey::new_unique();
549 assert!(matches!(
550 cache.insert_entry(program4, new_test_loaded_program(0, 1)),
551 LoadedProgramEntry::WasVacant(_)
552 ));
553 assert!(matches!(
554 cache.insert_entry(program4, new_test_loaded_program(5, 6)),
555 LoadedProgramEntry::WasVacant(_)
556 ));
557 assert!(matches!(
559 cache.insert_entry(program4, new_test_loaded_program(15, 19)),
560 LoadedProgramEntry::WasVacant(_)
561 ));
562
563 let working_slot = TestWorkingSlot::new(22, &[0, 10, 20, 22]);
580 let (found, missing) = cache.extract(
581 &working_slot,
582 vec![program1, program2, program3, program4].into_iter(),
583 );
584
585 assert!(match_slot(&found, &program1, 20));
586 assert!(match_slot(&found, &program4, 0));
587
588 assert!(missing.contains(&program2));
589 assert!(missing.contains(&program3));
590
591 let mut working_slot = TestWorkingSlot::new(16, &[0, 5, 11, 15, 16, 19, 23]);
593 let (found, missing) = cache.extract(
594 &working_slot,
595 vec![program1, program2, program3, program4].into_iter(),
596 );
597
598 assert!(match_slot(&found, &program1, 0));
599 assert!(match_slot(&found, &program2, 11));
600
601 assert!(match_slot(&found, &program4, 5));
603
604 assert!(missing.contains(&program3));
605
606 working_slot.update_slot(19);
608 let (found, missing) = cache.extract(
609 &working_slot,
610 vec![program1, program2, program3, program4].into_iter(),
611 );
612
613 assert!(match_slot(&found, &program1, 0));
614 assert!(match_slot(&found, &program2, 11));
615
616 assert!(match_slot(&found, &program4, 15));
618
619 assert!(missing.contains(&program3));
620
621 working_slot.update_slot(23);
623 let (found, missing) = cache.extract(
624 &working_slot,
625 vec![program1, program2, program3, program4].into_iter(),
626 );
627
628 assert!(match_slot(&found, &program1, 0));
629 assert!(match_slot(&found, &program2, 11));
630
631 assert!(match_slot(&found, &program4, 15));
633
634 assert!(missing.contains(&program3));
635
636 let working_slot = TestWorkingSlot::new(11, &[0, 5, 11, 15, 16]);
638 let (found, missing) = cache.extract(
639 &working_slot,
640 vec![program1, program2, program3, program4].into_iter(),
641 );
642
643 assert!(match_slot(&found, &program1, 0));
644 assert!(match_slot(&found, &program2, 5));
645 assert!(match_slot(&found, &program4, 5));
646
647 assert!(missing.contains(&program3));
648
649 cache.prune(&fork_graph, 5);
650
651 let working_slot = TestWorkingSlot::new(22, &[0, 10, 20, 22]);
668 let (found, missing) = cache.extract(
669 &working_slot,
670 vec![program1, program2, program3, program4].into_iter(),
671 );
672
673 assert!(match_slot(&found, &program1, 0));
675 assert!(match_slot(&found, &program4, 0));
676
677 assert!(missing.contains(&program2));
678 assert!(missing.contains(&program3));
679
680 let working_slot = TestWorkingSlot::new(27, &[0, 5, 11, 25, 27]);
682 let (found, _missing) = cache.extract(
683 &working_slot,
684 vec![program1, program2, program3, program4].into_iter(),
685 );
686
687 assert!(match_slot(&found, &program1, 0));
688 assert!(match_slot(&found, &program2, 11));
689 assert!(match_slot(&found, &program3, 25));
690 assert!(match_slot(&found, &program4, 5));
691
692 cache.prune(&fork_graph, 15);
693
694 let working_slot = TestWorkingSlot::new(27, &[0, 5, 11, 25, 27]);
711 let (found, missing) = cache.extract(
712 &working_slot,
713 vec![program1, program2, program3, program4].into_iter(),
714 );
715
716 assert!(match_slot(&found, &program1, 0));
717 assert!(match_slot(&found, &program2, 11));
718 assert!(match_slot(&found, &program4, 5));
719
720 assert!(missing.contains(&program3));
722 }
723}