1use {
2 crate::{
3 account_loader::{
4 load_accounts, LoadedTransaction, TransactionCheckResult, TransactionLoadResult,
5 },
6 account_overrides::AccountOverrides,
7 runtime_config::RuntimeConfig,
8 transaction_account_state_info::TransactionAccountStateInfo,
9 transaction_error_metrics::TransactionErrorMetrics,
10 transaction_results::{
11 DurableNonceFee, TransactionExecutionDetails, TransactionExecutionResult,
12 },
13 },
14 log::debug,
15 miraland_measure::measure::Measure,
16 percentage::Percentage,
17 miraland_program_runtime::{
18 compute_budget::ComputeBudget,
19 loaded_programs::{
20 ForkGraph, LoadProgramMetrics, LoadedProgram, LoadedProgramMatchCriteria,
21 LoadedProgramType, LoadedPrograms, LoadedProgramsForTxBatch, ProgramRuntimeEnvironment,
22 ProgramRuntimeEnvironments, DELAY_VISIBILITY_SLOT_OFFSET,
23 },
24 log_collector::LogCollector,
25 message_processor::MessageProcessor,
26 sysvar_cache::SysvarCache,
27 timings::{ExecuteDetailsTimings, ExecuteTimingType, ExecuteTimings},
28 },
29 miraland_sdk::{
30 account::{AccountSharedData, ReadableAccount, PROGRAM_OWNERS},
31 account_utils::StateMut,
32 bpf_loader_upgradeable::{self, UpgradeableLoaderState},
33 clock::{Epoch, Slot},
34 epoch_schedule::EpochSchedule,
35 feature_set::FeatureSet,
36 fee::FeeStructure,
37 hash::Hash,
38 inner_instruction::{InnerInstruction, InnerInstructionsList},
39 instruction::{CompiledInstruction, InstructionError, TRANSACTION_LEVEL_STACK_HEIGHT},
40 loader_v4::{self, LoaderV4State, LoaderV4Status},
41 message::SanitizedMessage,
42 native_loader,
43 pubkey::Pubkey,
44 rent_collector::RentCollector,
45 saturating_add_assign,
46 transaction::{self, SanitizedTransaction, TransactionError},
47 transaction_context::{ExecutionRecord, TransactionContext},
48 },
49 std::{
50 cell::RefCell,
51 collections::{hash_map::Entry, HashMap},
52 fmt::{Debug, Formatter},
53 rc::Rc,
54 sync::{atomic::Ordering, Arc, RwLock},
55 },
56};
57
58pub type TransactionLogMessages = Vec<String>;
60
61pub struct LoadAndExecuteSanitizedTransactionsOutput {
62 pub loaded_transactions: Vec<TransactionLoadResult>,
63 pub execution_results: Vec<TransactionExecutionResult>,
66}
67
68pub trait TransactionProcessingCallback {
69 fn account_matches_owners(&self, account: &Pubkey, owners: &[Pubkey]) -> Option<usize>;
70
71 fn get_account_shared_data(&self, pubkey: &Pubkey) -> Option<AccountSharedData>;
72
73 fn get_last_blockhash_and_lamports_per_signature(&self) -> (Hash, u64);
74
75 fn get_rent_collector(&self) -> &RentCollector;
76
77 fn get_feature_set(&self) -> Arc<FeatureSet>;
78
79 fn check_account_access(
80 &self,
81 _tx: &SanitizedTransaction,
82 _account_index: usize,
83 _account: &AccountSharedData,
84 _error_counters: &mut TransactionErrorMetrics,
85 ) -> transaction::Result<()> {
86 Ok(())
87 }
88}
89
90enum ProgramAccountLoadResult {
91 AccountNotFound,
92 InvalidAccountData(ProgramRuntimeEnvironment),
93 ProgramOfLoaderV1orV2(AccountSharedData),
94 ProgramOfLoaderV3(AccountSharedData, AccountSharedData, Slot),
95 ProgramOfLoaderV4(AccountSharedData, Slot),
96}
97
98#[derive(AbiExample)]
99pub struct TransactionBatchProcessor<FG: ForkGraph> {
100 slot: Slot,
102
103 epoch: Epoch,
105
106 epoch_schedule: EpochSchedule,
108
109 fee_structure: FeeStructure,
111
112 pub check_program_modification_slot: bool,
113
114 runtime_config: Arc<RuntimeConfig>,
116
117 pub sysvar_cache: RwLock<SysvarCache>,
118
119 pub loaded_programs_cache: Arc<RwLock<LoadedPrograms<FG>>>,
120}
121
122impl<FG: ForkGraph> Debug for TransactionBatchProcessor<FG> {
123 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
124 f.debug_struct("TransactionBatchProcessor")
125 .field("slot", &self.slot)
126 .field("epoch", &self.epoch)
127 .field("epoch_schedule", &self.epoch_schedule)
128 .field("fee_structure", &self.fee_structure)
129 .field(
130 "check_program_modification_slot",
131 &self.check_program_modification_slot,
132 )
133 .field("runtime_config", &self.runtime_config)
134 .field("sysvar_cache", &self.sysvar_cache)
135 .field("loaded_programs_cache", &self.loaded_programs_cache)
136 .finish()
137 }
138}
139
140impl<FG: ForkGraph> Default for TransactionBatchProcessor<FG> {
141 fn default() -> Self {
142 Self {
143 slot: Slot::default(),
144 epoch: Epoch::default(),
145 epoch_schedule: EpochSchedule::default(),
146 fee_structure: FeeStructure::default(),
147 check_program_modification_slot: false,
148 runtime_config: Arc::<RuntimeConfig>::default(),
149 sysvar_cache: RwLock::<SysvarCache>::default(),
150 loaded_programs_cache: Arc::new(RwLock::new(LoadedPrograms::new(
151 Slot::default(),
152 Epoch::default(),
153 ))),
154 }
155 }
156}
157
158impl<FG: ForkGraph> TransactionBatchProcessor<FG> {
159 pub fn new(
160 slot: Slot,
161 epoch: Epoch,
162 epoch_schedule: EpochSchedule,
163 fee_structure: FeeStructure,
164 runtime_config: Arc<RuntimeConfig>,
165 loaded_programs_cache: Arc<RwLock<LoadedPrograms<FG>>>,
166 ) -> Self {
167 Self {
168 slot,
169 epoch,
170 epoch_schedule,
171 fee_structure,
172 check_program_modification_slot: false,
173 runtime_config,
174 sysvar_cache: RwLock::<SysvarCache>::default(),
175 loaded_programs_cache,
176 }
177 }
178
179 #[allow(clippy::too_many_arguments)]
180 pub fn load_and_execute_sanitized_transactions<'a, CB: TransactionProcessingCallback>(
181 &self,
182 callbacks: &CB,
183 sanitized_txs: &[SanitizedTransaction],
184 check_results: &mut [TransactionCheckResult],
185 error_counters: &mut TransactionErrorMetrics,
186 enable_cpi_recording: bool,
187 enable_log_recording: bool,
188 enable_return_data_recording: bool,
189 timings: &mut ExecuteTimings,
190 account_overrides: Option<&AccountOverrides>,
191 builtin_programs: impl Iterator<Item = &'a Pubkey>,
192 log_messages_bytes_limit: Option<usize>,
193 ) -> LoadAndExecuteSanitizedTransactionsOutput {
194 let mut program_accounts_map = Self::filter_executable_program_accounts(
195 callbacks,
196 sanitized_txs,
197 check_results,
198 PROGRAM_OWNERS,
199 );
200 let native_loader = native_loader::id();
201 for builtin_program in builtin_programs {
202 program_accounts_map.insert(*builtin_program, (&native_loader, 0));
203 }
204
205 let programs_loaded_for_tx_batch = Rc::new(RefCell::new(
206 self.replenish_program_cache(callbacks, &program_accounts_map),
207 ));
208
209 let mut load_time = Measure::start("accounts_load");
210 let mut loaded_transactions = load_accounts(
211 callbacks,
212 sanitized_txs,
213 check_results,
214 error_counters,
215 &self.fee_structure,
216 account_overrides,
217 &program_accounts_map,
218 &programs_loaded_for_tx_batch.borrow(),
219 );
220 load_time.stop();
221
222 let mut execution_time = Measure::start("execution_time");
223
224 let execution_results: Vec<TransactionExecutionResult> = loaded_transactions
225 .iter_mut()
226 .zip(sanitized_txs.iter())
227 .map(|(accs, tx)| match accs {
228 (Err(e), _nonce) => TransactionExecutionResult::NotExecuted(e.clone()),
229 (Ok(loaded_transaction), nonce) => {
230 let compute_budget =
231 if let Some(compute_budget) = self.runtime_config.compute_budget {
232 compute_budget
233 } else {
234 let mut compute_budget_process_transaction_time =
235 Measure::start("compute_budget_process_transaction_time");
236 let maybe_compute_budget = ComputeBudget::try_from_instructions(
237 tx.message().program_instructions_iter(),
238 );
239 compute_budget_process_transaction_time.stop();
240 saturating_add_assign!(
241 timings
242 .execute_accessories
243 .compute_budget_process_transaction_us,
244 compute_budget_process_transaction_time.as_us()
245 );
246 if let Err(err) = maybe_compute_budget {
247 return TransactionExecutionResult::NotExecuted(err);
248 }
249 maybe_compute_budget.unwrap()
250 };
251
252 let result = self.execute_loaded_transaction(
253 callbacks,
254 tx,
255 loaded_transaction,
256 compute_budget,
257 nonce.as_ref().map(DurableNonceFee::from),
258 enable_cpi_recording,
259 enable_log_recording,
260 enable_return_data_recording,
261 timings,
262 error_counters,
263 log_messages_bytes_limit,
264 &programs_loaded_for_tx_batch.borrow(),
265 );
266
267 if let TransactionExecutionResult::Executed {
268 details,
269 programs_modified_by_tx,
270 } = &result
271 {
272 if details.status.is_ok() {
275 programs_loaded_for_tx_batch
276 .borrow_mut()
277 .merge(programs_modified_by_tx);
278 }
279 }
280
281 result
282 }
283 })
284 .collect();
285
286 execution_time.stop();
287
288 const SHRINK_LOADED_PROGRAMS_TO_PERCENTAGE: u8 = 90;
289 self.loaded_programs_cache
290 .write()
291 .unwrap()
292 .evict_using_2s_random_selection(
293 Percentage::from(SHRINK_LOADED_PROGRAMS_TO_PERCENTAGE),
294 self.slot,
295 );
296
297 debug!(
298 "load: {}us execute: {}us txs_len={}",
299 load_time.as_us(),
300 execution_time.as_us(),
301 sanitized_txs.len(),
302 );
303
304 timings.saturating_add_in_place(ExecuteTimingType::LoadUs, load_time.as_us());
305 timings.saturating_add_in_place(ExecuteTimingType::ExecuteUs, execution_time.as_us());
306
307 LoadAndExecuteSanitizedTransactionsOutput {
308 loaded_transactions,
309 execution_results,
310 }
311 }
312
313 pub fn filter_executable_program_accounts<'a, CB: TransactionProcessingCallback>(
317 callbacks: &CB,
318 txs: &[SanitizedTransaction],
319 lock_results: &mut [TransactionCheckResult],
320 program_owners: &'a [Pubkey],
321 ) -> HashMap<Pubkey, (&'a Pubkey, u64)> {
322 let mut result: HashMap<Pubkey, (&'a Pubkey, u64)> = HashMap::new();
323 lock_results.iter_mut().zip(txs).for_each(|etx| {
324 if let ((Ok(()), _nonce, lamports_per_signature), tx) = etx {
325 if lamports_per_signature.is_some() {
326 tx.message()
327 .account_keys()
328 .iter()
329 .for_each(|key| match result.entry(*key) {
330 Entry::Occupied(mut entry) => {
331 let (_, count) = entry.get_mut();
332 saturating_add_assign!(*count, 1);
333 }
334 Entry::Vacant(entry) => {
335 if let Some(index) =
336 callbacks.account_matches_owners(key, program_owners)
337 {
338 program_owners
339 .get(index)
340 .map(|owner| entry.insert((owner, 1)));
341 }
342 }
343 });
344 } else {
345 *etx.0 = (Err(TransactionError::BlockhashNotFound), None, None);
349 }
350 }
351 });
352 result
353 }
354
355 fn replenish_program_cache<CB: TransactionProcessingCallback>(
356 &self,
357 callback: &CB,
358 program_accounts_map: &HashMap<Pubkey, (&Pubkey, u64)>,
359 ) -> LoadedProgramsForTxBatch {
360 let mut missing_programs: Vec<(Pubkey, (LoadedProgramMatchCriteria, u64))> =
361 if self.check_program_modification_slot {
362 program_accounts_map
363 .iter()
364 .map(|(pubkey, (_, count))| {
365 (
366 *pubkey,
367 (
368 self.program_modification_slot(callback, pubkey)
369 .map_or(LoadedProgramMatchCriteria::Tombstone, |slot| {
370 LoadedProgramMatchCriteria::DeployedOnOrAfterSlot(slot)
371 }),
372 *count,
373 ),
374 )
375 })
376 .collect()
377 } else {
378 program_accounts_map
379 .iter()
380 .map(|(pubkey, (_, count))| {
381 (*pubkey, (LoadedProgramMatchCriteria::NoCriteria, *count))
382 })
383 .collect()
384 };
385
386 let mut loaded_programs_for_txs = None;
387 let mut program_to_store = None;
388 loop {
389 let (program_to_load, task_cookie, task_waiter) = {
390 let mut loaded_programs_cache = self.loaded_programs_cache.write().unwrap();
392 let is_first_round = loaded_programs_for_txs.is_none();
394 if is_first_round {
395 loaded_programs_for_txs = Some(LoadedProgramsForTxBatch::new(
396 self.slot,
397 loaded_programs_cache
398 .get_environments_for_epoch(self.epoch)
399 .clone(),
400 ));
401 }
402 if let Some((key, program)) = program_to_store.take() {
404 loaded_programs_cache.finish_cooperative_loading_task(self.slot, key, program);
405 }
406 let program_to_load = loaded_programs_cache.extract(
408 &mut missing_programs,
409 loaded_programs_for_txs.as_mut().unwrap(),
410 is_first_round,
411 );
412 let task_waiter = Arc::clone(&loaded_programs_cache.loading_task_waiter);
413 (program_to_load, task_waiter.cookie(), task_waiter)
414 };
416
417 if let Some((key, count)) = program_to_load {
418 let program = self.load_program(callback, &key, false, self.epoch);
420 program.tx_usage_counter.store(count, Ordering::Relaxed);
421 program_to_store = Some((key, program));
422 } else if missing_programs.is_empty() {
423 break;
424 } else {
425 let _new_cookie = task_waiter.wait(task_cookie);
429 }
430 }
431
432 loaded_programs_for_txs.unwrap()
433 }
434
435 #[allow(clippy::too_many_arguments)]
438 fn execute_loaded_transaction<CB: TransactionProcessingCallback>(
439 &self,
440 callback: &CB,
441 tx: &SanitizedTransaction,
442 loaded_transaction: &mut LoadedTransaction,
443 compute_budget: ComputeBudget,
444 durable_nonce_fee: Option<DurableNonceFee>,
445 enable_cpi_recording: bool,
446 enable_log_recording: bool,
447 enable_return_data_recording: bool,
448 timings: &mut ExecuteTimings,
449 error_counters: &mut TransactionErrorMetrics,
450 log_messages_bytes_limit: Option<usize>,
451 programs_loaded_for_tx_batch: &LoadedProgramsForTxBatch,
452 ) -> TransactionExecutionResult {
453 let transaction_accounts = std::mem::take(&mut loaded_transaction.accounts);
454
455 fn transaction_accounts_lamports_sum(
456 accounts: &[(Pubkey, AccountSharedData)],
457 message: &SanitizedMessage,
458 ) -> Option<u128> {
459 let mut lamports_sum = 0u128;
460 for i in 0..message.account_keys().len() {
461 let (_, account) = accounts.get(i)?;
462 lamports_sum = lamports_sum.checked_add(u128::from(account.lamports()))?;
463 }
464 Some(lamports_sum)
465 }
466
467 let lamports_before_tx =
468 transaction_accounts_lamports_sum(&transaction_accounts, tx.message()).unwrap_or(0);
469
470 let mut transaction_context = TransactionContext::new(
471 transaction_accounts,
472 callback.get_rent_collector().rent.clone(),
473 compute_budget.max_invoke_stack_height,
474 compute_budget.max_instruction_trace_length,
475 );
476 #[cfg(debug_assertions)]
477 transaction_context.set_signature(tx.signature());
478
479 let pre_account_state_info = TransactionAccountStateInfo::new(
480 &callback.get_rent_collector().rent,
481 &transaction_context,
482 tx.message(),
483 );
484
485 let log_collector = if enable_log_recording {
486 match log_messages_bytes_limit {
487 None => Some(LogCollector::new_ref()),
488 Some(log_messages_bytes_limit) => Some(LogCollector::new_ref_with_limit(Some(
489 log_messages_bytes_limit,
490 ))),
491 }
492 } else {
493 None
494 };
495
496 let (blockhash, lamports_per_signature) =
497 callback.get_last_blockhash_and_lamports_per_signature();
498
499 let mut executed_units = 0u64;
500 let mut programs_modified_by_tx = LoadedProgramsForTxBatch::new(
501 self.slot,
502 programs_loaded_for_tx_batch.environments.clone(),
503 );
504 let mut process_message_time = Measure::start("process_message_time");
505 let process_result = MessageProcessor::process_message(
506 tx.message(),
507 &loaded_transaction.program_indices,
508 &mut transaction_context,
509 log_collector.clone(),
510 programs_loaded_for_tx_batch,
511 &mut programs_modified_by_tx,
512 callback.get_feature_set(),
513 compute_budget,
514 timings,
515 &self.sysvar_cache.read().unwrap(),
516 blockhash,
517 lamports_per_signature,
518 &mut executed_units,
519 );
520 process_message_time.stop();
521
522 saturating_add_assign!(
523 timings.execute_accessories.process_message_us,
524 process_message_time.as_us()
525 );
526
527 let mut status = process_result
528 .and_then(|info| {
529 let post_account_state_info = TransactionAccountStateInfo::new(
530 &callback.get_rent_collector().rent,
531 &transaction_context,
532 tx.message(),
533 );
534 TransactionAccountStateInfo::verify_changes(
535 &pre_account_state_info,
536 &post_account_state_info,
537 &transaction_context,
538 )
539 .map(|_| info)
540 })
541 .map_err(|err| {
542 match err {
543 TransactionError::InvalidRentPayingAccount
544 | TransactionError::InsufficientFundsForRent { .. } => {
545 error_counters.invalid_rent_paying_account += 1;
546 }
547 TransactionError::InvalidAccountIndex => {
548 error_counters.invalid_account_index += 1;
549 }
550 _ => {
551 error_counters.instruction_error += 1;
552 }
553 }
554 err
555 });
556
557 let log_messages: Option<TransactionLogMessages> =
558 log_collector.and_then(|log_collector| {
559 Rc::try_unwrap(log_collector)
560 .map(|log_collector| log_collector.into_inner().into_messages())
561 .ok()
562 });
563
564 let inner_instructions = if enable_cpi_recording {
565 Some(Self::inner_instructions_list_from_instruction_trace(
566 &transaction_context,
567 ))
568 } else {
569 None
570 };
571
572 let ExecutionRecord {
573 accounts,
574 return_data,
575 touched_account_count,
576 accounts_resize_delta: accounts_data_len_delta,
577 } = transaction_context.into();
578
579 if status.is_ok()
580 && transaction_accounts_lamports_sum(&accounts, tx.message())
581 .filter(|lamports_after_tx| lamports_before_tx == *lamports_after_tx)
582 .is_none()
583 {
584 status = Err(TransactionError::UnbalancedTransaction);
585 }
586 let status = status.map(|_| ());
587
588 loaded_transaction.accounts = accounts;
589 saturating_add_assign!(
590 timings.details.total_account_count,
591 loaded_transaction.accounts.len() as u64
592 );
593 saturating_add_assign!(timings.details.changed_account_count, touched_account_count);
594
595 let return_data = if enable_return_data_recording && !return_data.data.is_empty() {
596 Some(return_data)
597 } else {
598 None
599 };
600
601 TransactionExecutionResult::Executed {
602 details: TransactionExecutionDetails {
603 status,
604 log_messages,
605 inner_instructions,
606 durable_nonce_fee,
607 return_data,
608 executed_units,
609 accounts_data_len_delta,
610 },
611 programs_modified_by_tx: Box::new(programs_modified_by_tx),
612 }
613 }
614
615 fn program_modification_slot<CB: TransactionProcessingCallback>(
616 &self,
617 callbacks: &CB,
618 pubkey: &Pubkey,
619 ) -> transaction::Result<Slot> {
620 let program = callbacks
621 .get_account_shared_data(pubkey)
622 .ok_or(TransactionError::ProgramAccountNotFound)?;
623 if bpf_loader_upgradeable::check_id(program.owner()) {
624 if let Ok(UpgradeableLoaderState::Program {
625 programdata_address,
626 }) = program.state()
627 {
628 let programdata = callbacks
629 .get_account_shared_data(&programdata_address)
630 .ok_or(TransactionError::ProgramAccountNotFound)?;
631 if let Ok(UpgradeableLoaderState::ProgramData {
632 slot,
633 upgrade_authority_address: _,
634 }) = programdata.state()
635 {
636 return Ok(slot);
637 }
638 }
639 Err(TransactionError::ProgramAccountNotFound)
640 } else if loader_v4::check_id(program.owner()) {
641 let state = miraland_loader_v4_program::get_state(program.data())
642 .map_err(|_| TransactionError::ProgramAccountNotFound)?;
643 Ok(state.slot)
644 } else {
645 Ok(0)
646 }
647 }
648
649 pub fn load_program<CB: TransactionProcessingCallback>(
650 &self,
651 callbacks: &CB,
652 pubkey: &Pubkey,
653 reload: bool,
654 effective_epoch: Epoch,
655 ) -> Arc<LoadedProgram> {
656 let loaded_programs_cache = self.loaded_programs_cache.read().unwrap();
657 let environments = loaded_programs_cache.get_environments_for_epoch(effective_epoch);
658 let mut load_program_metrics = LoadProgramMetrics {
659 program_id: pubkey.to_string(),
660 ..LoadProgramMetrics::default()
661 };
662
663 let mut loaded_program =
664 match self.load_program_accounts(callbacks, pubkey, environments) {
665 ProgramAccountLoadResult::AccountNotFound => Ok(LoadedProgram::new_tombstone(
666 self.slot,
667 LoadedProgramType::Closed,
668 )),
669
670 ProgramAccountLoadResult::InvalidAccountData(env) => Err((self.slot, env)),
671
672 ProgramAccountLoadResult::ProgramOfLoaderV1orV2(program_account) => {
673 Self::load_program_from_bytes(
674 &mut load_program_metrics,
675 program_account.data(),
676 program_account.owner(),
677 program_account.data().len(),
678 0,
679 environments.program_runtime_v1.clone(),
680 reload,
681 )
682 .map_err(|_| (0, environments.program_runtime_v1.clone()))
683 }
684
685 ProgramAccountLoadResult::ProgramOfLoaderV3(
686 program_account,
687 programdata_account,
688 slot,
689 ) => programdata_account
690 .data()
691 .get(UpgradeableLoaderState::size_of_programdata_metadata()..)
692 .ok_or(Box::new(InstructionError::InvalidAccountData).into())
693 .and_then(|programdata| {
694 Self::load_program_from_bytes(
695 &mut load_program_metrics,
696 programdata,
697 program_account.owner(),
698 program_account
699 .data()
700 .len()
701 .saturating_add(programdata_account.data().len()),
702 slot,
703 environments.program_runtime_v1.clone(),
704 reload,
705 )
706 })
707 .map_err(|_| (slot, environments.program_runtime_v1.clone())),
708
709 ProgramAccountLoadResult::ProgramOfLoaderV4(program_account, slot) => {
710 program_account
711 .data()
712 .get(LoaderV4State::program_data_offset()..)
713 .ok_or(Box::new(InstructionError::InvalidAccountData).into())
714 .and_then(|elf_bytes| {
715 Self::load_program_from_bytes(
716 &mut load_program_metrics,
717 elf_bytes,
718 &loader_v4::id(),
719 program_account.data().len(),
720 slot,
721 environments.program_runtime_v2.clone(),
722 reload,
723 )
724 })
725 .map_err(|_| (slot, environments.program_runtime_v2.clone()))
726 }
727 }
728 .unwrap_or_else(|(slot, env)| {
729 LoadedProgram::new_tombstone(slot, LoadedProgramType::FailedVerification(env))
730 });
731
732 let mut timings = ExecuteDetailsTimings::default();
733 load_program_metrics.submit_datapoint(&mut timings);
734 if !Arc::ptr_eq(
735 &environments.program_runtime_v1,
736 &loaded_programs_cache.environments.program_runtime_v1,
737 ) || !Arc::ptr_eq(
738 &environments.program_runtime_v2,
739 &loaded_programs_cache.environments.program_runtime_v2,
740 ) {
741 loaded_program.effective_slot = loaded_program
746 .effective_slot
747 .max(self.epoch_schedule.get_first_slot_in_epoch(effective_epoch));
748 }
749 loaded_program.update_access_slot(self.slot);
750 Arc::new(loaded_program)
751 }
752
753 fn load_program_from_bytes(
754 load_program_metrics: &mut LoadProgramMetrics,
755 programdata: &[u8],
756 loader_key: &Pubkey,
757 account_size: usize,
758 deployment_slot: Slot,
759 program_runtime_environment: ProgramRuntimeEnvironment,
760 reloading: bool,
761 ) -> std::result::Result<LoadedProgram, Box<dyn std::error::Error>> {
762 if reloading {
763 unsafe {
765 LoadedProgram::reload(
766 loader_key,
767 program_runtime_environment.clone(),
768 deployment_slot,
769 deployment_slot.saturating_add(DELAY_VISIBILITY_SLOT_OFFSET),
770 programdata,
771 account_size,
772 load_program_metrics,
773 )
774 }
775 } else {
776 LoadedProgram::new(
777 loader_key,
778 program_runtime_environment.clone(),
779 deployment_slot,
780 deployment_slot.saturating_add(DELAY_VISIBILITY_SLOT_OFFSET),
781 programdata,
782 account_size,
783 load_program_metrics,
784 )
785 }
786 }
787
788 fn load_program_accounts<CB: TransactionProcessingCallback>(
789 &self,
790 callbacks: &CB,
791 pubkey: &Pubkey,
792 environments: &ProgramRuntimeEnvironments,
793 ) -> ProgramAccountLoadResult {
794 let program_account = match callbacks.get_account_shared_data(pubkey) {
795 None => return ProgramAccountLoadResult::AccountNotFound,
796 Some(account) => account,
797 };
798
799 debug_assert!(miraland_bpf_loader_program::check_loader_id(
800 program_account.owner()
801 ));
802
803 if loader_v4::check_id(program_account.owner()) {
804 return miraland_loader_v4_program::get_state(program_account.data())
805 .ok()
806 .and_then(|state| {
807 (!matches!(state.status, LoaderV4Status::Retracted)).then_some(state.slot)
808 })
809 .map(|slot| ProgramAccountLoadResult::ProgramOfLoaderV4(program_account, slot))
810 .unwrap_or(ProgramAccountLoadResult::InvalidAccountData(
811 environments.program_runtime_v2.clone(),
812 ));
813 }
814
815 if !bpf_loader_upgradeable::check_id(program_account.owner()) {
816 return ProgramAccountLoadResult::ProgramOfLoaderV1orV2(program_account);
817 }
818
819 if let Ok(UpgradeableLoaderState::Program {
820 programdata_address,
821 }) = program_account.state()
822 {
823 let programdata_account = match callbacks.get_account_shared_data(&programdata_address)
824 {
825 None => return ProgramAccountLoadResult::AccountNotFound,
826 Some(account) => account,
827 };
828
829 if let Ok(UpgradeableLoaderState::ProgramData {
830 slot,
831 upgrade_authority_address: _,
832 }) = programdata_account.state()
833 {
834 return ProgramAccountLoadResult::ProgramOfLoaderV3(
835 program_account,
836 programdata_account,
837 slot,
838 );
839 }
840 }
841 ProgramAccountLoadResult::InvalidAccountData(environments.program_runtime_v1.clone())
842 }
843
844 fn inner_instructions_list_from_instruction_trace(
846 transaction_context: &TransactionContext,
847 ) -> InnerInstructionsList {
848 debug_assert!(transaction_context
849 .get_instruction_context_at_index_in_trace(0)
850 .map(|instruction_context| instruction_context.get_stack_height()
851 == TRANSACTION_LEVEL_STACK_HEIGHT)
852 .unwrap_or(true));
853 let mut outer_instructions = Vec::new();
854 for index_in_trace in 0..transaction_context.get_instruction_trace_length() {
855 if let Ok(instruction_context) =
856 transaction_context.get_instruction_context_at_index_in_trace(index_in_trace)
857 {
858 let stack_height = instruction_context.get_stack_height();
859 if stack_height == TRANSACTION_LEVEL_STACK_HEIGHT {
860 outer_instructions.push(Vec::new());
861 } else if let Some(inner_instructions) = outer_instructions.last_mut() {
862 let stack_height = u8::try_from(stack_height).unwrap_or(u8::MAX);
863 let instruction = CompiledInstruction::new_from_raw_parts(
864 instruction_context
865 .get_index_of_program_account_in_transaction(
866 instruction_context
867 .get_number_of_program_accounts()
868 .saturating_sub(1),
869 )
870 .unwrap_or_default() as u8,
871 instruction_context.get_instruction_data().to_vec(),
872 (0..instruction_context.get_number_of_instruction_accounts())
873 .map(|instruction_account_index| {
874 instruction_context
875 .get_index_of_instruction_account_in_transaction(
876 instruction_account_index,
877 )
878 .unwrap_or_default() as u8
879 })
880 .collect(),
881 );
882 inner_instructions.push(InnerInstruction {
883 instruction,
884 stack_height,
885 });
886 } else {
887 debug_assert!(false);
888 }
889 } else {
890 debug_assert!(false);
891 }
892 }
893 outer_instructions
894 }
895}
896
897#[cfg(test)]
898mod tests {
899 use {
900 super::*,
901 miraland_program_runtime::loaded_programs::BlockRelation,
902 miraland_sdk::{sysvar::rent::Rent, transaction_context::TransactionContext},
903 };
904
905 struct TestForkGraph {}
906
907 impl ForkGraph for TestForkGraph {
908 fn relationship(&self, _a: Slot, _b: Slot) -> BlockRelation {
909 BlockRelation::Unknown
910 }
911 }
912
913 #[test]
914 fn test_inner_instructions_list_from_instruction_trace() {
915 let instruction_trace = [1, 2, 1, 1, 2, 3, 2];
916 let mut transaction_context =
917 TransactionContext::new(vec![], Rent::default(), 3, instruction_trace.len());
918 for (index_in_trace, stack_height) in instruction_trace.into_iter().enumerate() {
919 while stack_height <= transaction_context.get_instruction_context_stack_height() {
920 transaction_context.pop().unwrap();
921 }
922 if stack_height > transaction_context.get_instruction_context_stack_height() {
923 transaction_context
924 .get_next_instruction_context()
925 .unwrap()
926 .configure(&[], &[], &[index_in_trace as u8]);
927 transaction_context.push().unwrap();
928 }
929 }
930 let inner_instructions =
931 TransactionBatchProcessor::<TestForkGraph>::inner_instructions_list_from_instruction_trace(
932 &transaction_context,
933 );
934
935 assert_eq!(
936 inner_instructions,
937 vec![
938 vec![InnerInstruction {
939 instruction: CompiledInstruction::new_from_raw_parts(0, vec![1], vec![]),
940 stack_height: 2,
941 }],
942 vec![],
943 vec![
944 InnerInstruction {
945 instruction: CompiledInstruction::new_from_raw_parts(0, vec![4], vec![]),
946 stack_height: 2,
947 },
948 InnerInstruction {
949 instruction: CompiledInstruction::new_from_raw_parts(0, vec![5], vec![]),
950 stack_height: 3,
951 },
952 InnerInstruction {
953 instruction: CompiledInstruction::new_from_raw_parts(0, vec![6], vec![]),
954 stack_height: 2,
955 },
956 ]
957 ]
958 );
959 }
960}