miraland_svm/
transaction_processor.rs

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
58/// A list of log messages emitted during a transaction
59pub type TransactionLogMessages = Vec<String>;
60
61pub struct LoadAndExecuteSanitizedTransactionsOutput {
62    pub loaded_transactions: Vec<TransactionLoadResult>,
63    // Vector of results indicating whether a transaction was executed or could not
64    // be executed. Note executed transactions can still have failed!
65    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    /// Bank slot (i.e. block)
101    slot: Slot,
102
103    /// Bank epoch
104    epoch: Epoch,
105
106    /// initialized from genesis
107    epoch_schedule: EpochSchedule,
108
109    /// Transaction fee structure
110    fee_structure: FeeStructure,
111
112    pub check_program_modification_slot: bool,
113
114    /// Optional config parameters that can override runtime behavior
115    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                        // Update batch specific cache of the loaded programs with the modifications
273                        // made by the transaction, if it executed successfully.
274                        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    /// Returns a hash map of executable program accounts (program accounts that are not writable
314    /// in the given transactions), and their owners, for the transactions with a valid
315    /// blockhash or nonce.
316    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                    // If the transaction's nonce account was not valid, and blockhash is not found,
346                    // the transaction will fail to process. Let's not load any programs from the
347                    // transaction, and update the status of the transaction.
348                    *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                // Lock the global cache.
391                let mut loaded_programs_cache = self.loaded_programs_cache.write().unwrap();
392                // Initialize our local cache.
393                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                // Submit our last completed loading task.
403                if let Some((key, program)) = program_to_store.take() {
404                    loaded_programs_cache.finish_cooperative_loading_task(self.slot, key, program);
405                }
406                // Figure out which program needs to be loaded next.
407                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                // Unlock the global cache again.
415            };
416
417            if let Some((key, count)) = program_to_load {
418                // Load, verify and compile one program.
419                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                // Sleep until the next finish_cooperative_loading_task() call.
426                // Once a task completes we'll wake up and try to load the
427                // missing programs inside the tx batch again.
428                let _new_cookie = task_waiter.wait(task_cookie);
429            }
430        }
431
432        loaded_programs_for_txs.unwrap()
433    }
434
435    /// Execute a transaction using the provided loaded accounts and update
436    /// the executors cache if the transaction was successful.
437    #[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            // There can be two entries per program when the environment changes.
742            // One for the old environment before the epoch boundary and one for the new environment after the epoch boundary.
743            // These two entries have the same deployment slot, so they must differ in their effective slot instead.
744            // This is done by setting the effective slot of the entry for the new environment to the epoch boundary.
745            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            // Safety: this is safe because the program is being reloaded in the cache.
764            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    /// Extract the InnerInstructionsList from a TransactionContext
845    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}