Skip to main content

solana_svm/
transaction_processor.rs

1use {
2    crate::{
3        account_loader::{
4            load_transaction, update_rent_exempt_status_for_account, validate_fee_payer,
5            AccountLoader, CheckedTransactionDetails, LoadedTransaction, TransactionCheckResult,
6            TransactionLoadResult, ValidatedTransactionDetails,
7        },
8        account_overrides::AccountOverrides,
9        message_processor::process_message,
10        nonce_info::NonceInfo,
11        program_loader::{get_program_modification_slot, load_program_with_pubkey},
12        rollback_accounts::RollbackAccounts,
13        transaction_account_state_info::TransactionAccountStateInfo,
14        transaction_balances::{BalanceCollectionRoutines, BalanceCollector},
15        transaction_error_metrics::TransactionErrorMetrics,
16        transaction_execution_result::{ExecutedTransaction, TransactionExecutionDetails},
17        transaction_processing_result::{ProcessedTransaction, TransactionProcessingResult},
18    },
19    log::debug,
20    percentage::Percentage,
21    solana_account::{state_traits::StateMut, AccountSharedData, ReadableAccount, PROGRAM_OWNERS},
22    solana_clock::{Epoch, Slot},
23    solana_hash::Hash,
24    solana_instruction::TRANSACTION_LEVEL_STACK_HEIGHT,
25    solana_message::{
26        compiled_instruction::CompiledInstruction,
27        inner_instruction::{InnerInstruction, InnerInstructionsList},
28    },
29    solana_nonce::{
30        state::{DurableNonce, State as NonceState},
31        versions::Versions as NonceVersions,
32        NONCED_TX_MARKER_IX_INDEX,
33    },
34    solana_nonce_account::verify_nonce_account,
35    solana_program_runtime::{
36        execution_budget::{
37            SVMTransactionExecutionAndFeeBudgetLimits, SVMTransactionExecutionCost,
38        },
39        invoke_context::{EnvironmentConfig, InvokeContext},
40        loaded_programs::{
41            EpochBoundaryPreparation, ForkGraph, ProgramCache, ProgramCacheEntry,
42            ProgramCacheForTxBatch, ProgramCacheMatchCriteria, ProgramRuntimeEnvironments,
43        },
44        sysvar_cache::SysvarCache,
45    },
46    solana_pubkey::Pubkey,
47    solana_rent::Rent,
48    solana_svm_callback::TransactionProcessingCallback,
49    solana_svm_feature_set::SVMFeatureSet,
50    solana_svm_log_collector::LogCollector,
51    solana_svm_measure::{measure::Measure, measure_us},
52    solana_svm_timings::{ExecuteTimingType, ExecuteTimings},
53    solana_svm_transaction::{svm_message::SVMMessage, svm_transaction::SVMTransaction},
54    solana_svm_type_overrides::sync::{atomic::Ordering, Arc, RwLock, RwLockReadGuard},
55    solana_transaction_context::{ExecutionRecord, TransactionContext},
56    solana_transaction_error::{TransactionError, TransactionResult},
57    std::{
58        collections::HashSet,
59        fmt::{Debug, Formatter},
60        rc::Rc,
61    },
62};
63#[cfg(feature = "dev-context-only-utils")]
64use {
65    qualifier_attr::{field_qualifiers, qualifiers},
66    solana_program_runtime::{
67        loaded_programs::ProgramRuntimeEnvironment,
68        solana_sbpf::{program::BuiltinProgram, vm::Config as VmConfig},
69    },
70    std::sync::Weak,
71};
72
73/// A list of log messages emitted during a transaction
74pub type TransactionLogMessages = Vec<String>;
75
76/// The output of the transaction batch processor's
77/// `load_and_execute_sanitized_transactions` method.
78pub struct LoadAndExecuteSanitizedTransactionsOutput {
79    /// Error metrics for transactions that were processed.
80    pub error_metrics: TransactionErrorMetrics,
81    /// Timings for transaction batch execution.
82    pub execute_timings: ExecuteTimings,
83    /// Vector of results indicating whether a transaction was processed or
84    /// could not be processed. Note processed transactions can still have a
85    /// failure result meaning that the transaction will be rolled back.
86    pub processing_results: Vec<TransactionProcessingResult>,
87    /// Balances accumulated for TransactionStatusSender when
88    /// transaction balance recording is enabled.
89    pub balance_collector: Option<BalanceCollector>,
90}
91
92/// Configuration of the recording capabilities for transaction execution
93#[derive(Copy, Clone, Default)]
94pub struct ExecutionRecordingConfig {
95    pub enable_cpi_recording: bool,
96    pub enable_log_recording: bool,
97    pub enable_return_data_recording: bool,
98    pub enable_transaction_balance_recording: bool,
99}
100
101impl ExecutionRecordingConfig {
102    pub fn new_single_setting(option: bool) -> Self {
103        ExecutionRecordingConfig {
104            enable_return_data_recording: option,
105            enable_log_recording: option,
106            enable_cpi_recording: option,
107            enable_transaction_balance_recording: option,
108        }
109    }
110}
111
112/// Configurations for processing transactions.
113#[derive(Default)]
114pub struct TransactionProcessingConfig<'a> {
115    /// Encapsulates overridden accounts, typically used for transaction
116    /// simulation.
117    pub account_overrides: Option<&'a AccountOverrides>,
118    /// Whether or not to check a program's modification slot when replenishing
119    /// a program cache instance.
120    pub check_program_modification_slot: bool,
121    /// The maximum number of bytes that log messages can consume.
122    pub log_messages_bytes_limit: Option<usize>,
123    /// Whether to limit the number of programs loaded for the transaction
124    /// batch.
125    pub limit_to_load_programs: bool,
126    /// Recording capabilities for transaction execution.
127    pub recording_config: ExecutionRecordingConfig,
128}
129
130/// Runtime environment for transaction batch processing.
131#[derive(Default)]
132pub struct TransactionProcessingEnvironment {
133    /// The blockhash to use for the transaction batch.
134    pub blockhash: Hash,
135    /// Lamports per signature that corresponds to this blockhash.
136    ///
137    /// Note: This value is primarily used for nonce accounts. If set to zero,
138    /// it will disable transaction fees. However, any non-zero value will not
139    /// change transaction fees. For this reason, it is recommended to use the
140    /// `fee_per_signature` field to adjust transaction fees.
141    pub blockhash_lamports_per_signature: u64,
142    /// The total stake for the current epoch.
143    pub epoch_total_stake: u64,
144    /// Runtime feature set to use for the transaction batch.
145    pub feature_set: SVMFeatureSet,
146    /// The current ProgramRuntimeEnvironments derived from the SVMFeatureSet.
147    pub program_runtime_environments_for_execution: ProgramRuntimeEnvironments,
148    /// Depending on the next slot this is either the current or the upcoming
149    /// ProgramRuntimeEnvironments.
150    pub program_runtime_environments_for_deployment: ProgramRuntimeEnvironments,
151    /// Rent calculator to use for the transaction batch.
152    pub rent: Rent,
153}
154
155#[cfg_attr(feature = "frozen-abi", derive(AbiExample))]
156#[cfg_attr(
157    feature = "dev-context-only-utils",
158    field_qualifiers(slot(pub), epoch(pub), sysvar_cache(pub))
159)]
160pub struct TransactionBatchProcessor<FG: ForkGraph> {
161    /// Bank slot (i.e. block)
162    slot: Slot,
163
164    /// Bank epoch
165    epoch: Epoch,
166
167    /// SysvarCache is a collection of system variables that are
168    /// accessible from on chain programs. It is passed to SVM from
169    /// client code (e.g. Bank) and forwarded to process_message.
170    sysvar_cache: RwLock<SysvarCache>,
171
172    /// Anticipates the environments of the upcoming epoch
173    pub epoch_boundary_preparation: Arc<RwLock<EpochBoundaryPreparation>>,
174
175    /// Programs required for transaction batch processing
176    pub global_program_cache: Arc<RwLock<ProgramCache<FG>>>,
177
178    /// Environments of the current epoch
179    pub environments: ProgramRuntimeEnvironments,
180
181    /// Builtin program ids
182    pub builtin_program_ids: RwLock<HashSet<Pubkey>>,
183
184    execution_cost: SVMTransactionExecutionCost,
185}
186
187impl<FG: ForkGraph> Debug for TransactionBatchProcessor<FG> {
188    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
189        f.debug_struct("TransactionBatchProcessor")
190            .field("slot", &self.slot)
191            .field("epoch", &self.epoch)
192            .field("sysvar_cache", &self.sysvar_cache)
193            .field("global_program_cache", &self.global_program_cache)
194            .finish()
195    }
196}
197
198impl<FG: ForkGraph> Default for TransactionBatchProcessor<FG> {
199    fn default() -> Self {
200        Self {
201            slot: Slot::default(),
202            epoch: Epoch::default(),
203            sysvar_cache: RwLock::<SysvarCache>::default(),
204            epoch_boundary_preparation: Arc::new(RwLock::new(EpochBoundaryPreparation::default())),
205            global_program_cache: Arc::new(RwLock::new(ProgramCache::new(Slot::default()))),
206            environments: ProgramRuntimeEnvironments::default(),
207            builtin_program_ids: RwLock::new(HashSet::new()),
208            execution_cost: SVMTransactionExecutionCost::default(),
209        }
210    }
211}
212
213impl<FG: ForkGraph> TransactionBatchProcessor<FG> {
214    /// Create a new, uninitialized `TransactionBatchProcessor`.
215    ///
216    /// In this context, uninitialized means that the `TransactionBatchProcessor`
217    /// has been initialized with an empty program cache. The cache contains no
218    /// programs (including builtins) and has not been configured with a valid
219    /// fork graph.
220    ///
221    /// When using this method, it's advisable to call `set_fork_graph_in_program_cache`
222    /// as well as `add_builtin` to configure the cache before using the processor.
223    pub fn new_uninitialized(slot: Slot, epoch: Epoch) -> Self {
224        let epoch_boundary_preparation =
225            Arc::new(RwLock::new(EpochBoundaryPreparation::new(epoch)));
226        Self {
227            slot,
228            epoch,
229            epoch_boundary_preparation,
230            global_program_cache: Arc::new(RwLock::new(ProgramCache::new(slot))),
231            ..Self::default()
232        }
233    }
234
235    /// Create a new `TransactionBatchProcessor`.
236    ///
237    /// The created processor's program cache is initialized with the provided
238    /// fork graph and loaders. If any loaders are omitted, a default "empty"
239    /// loader (no syscalls) will be used.
240    ///
241    /// The cache will still not contain any builtin programs. It's advisable to
242    /// call `add_builtin` to add the required builtins before using the processor.
243    #[cfg(feature = "dev-context-only-utils")]
244    pub fn new(
245        slot: Slot,
246        epoch: Epoch,
247        fork_graph: Weak<RwLock<FG>>,
248        program_runtime_environment_v1: Option<ProgramRuntimeEnvironment>,
249        program_runtime_environment_v2: Option<ProgramRuntimeEnvironment>,
250    ) -> Self {
251        let mut processor = Self::new_uninitialized(slot, epoch);
252        processor
253            .global_program_cache
254            .write()
255            .unwrap()
256            .set_fork_graph(fork_graph);
257        let empty_loader = || Arc::new(BuiltinProgram::new_loader(VmConfig::default()));
258        processor
259            .global_program_cache
260            .write()
261            .unwrap()
262            .latest_root_slot = processor.slot;
263        processor
264            .epoch_boundary_preparation
265            .write()
266            .unwrap()
267            .upcoming_epoch = processor.epoch;
268        processor.environments.program_runtime_v1 =
269            program_runtime_environment_v1.unwrap_or(empty_loader());
270        processor.environments.program_runtime_v2 =
271            program_runtime_environment_v2.unwrap_or(empty_loader());
272        processor
273    }
274
275    /// Create a new `TransactionBatchProcessor` from the current instance, but
276    /// with the provided slot and epoch.
277    ///
278    /// * Inherits the program cache and builtin program ids from the current
279    ///   instance.
280    /// * Resets the sysvar cache.
281    pub fn new_from(&self, slot: Slot, epoch: Epoch) -> Self {
282        Self {
283            slot,
284            epoch,
285            sysvar_cache: RwLock::<SysvarCache>::default(),
286            epoch_boundary_preparation: self.epoch_boundary_preparation.clone(),
287            global_program_cache: self.global_program_cache.clone(),
288            environments: self.environments.clone(),
289            builtin_program_ids: RwLock::new(self.builtin_program_ids.read().unwrap().clone()),
290            execution_cost: self.execution_cost,
291        }
292    }
293
294    /// Sets the base execution cost for the transactions that this instance of transaction processor
295    /// will execute.
296    pub fn set_execution_cost(&mut self, cost: SVMTransactionExecutionCost) {
297        self.execution_cost = cost;
298    }
299
300    /// Updates the environments when entering a new Epoch.
301    pub fn set_environments(&mut self, new_environments: ProgramRuntimeEnvironments) {
302        // First update the parts of the environments which changed
303        if self.environments.program_runtime_v1 != new_environments.program_runtime_v1 {
304            self.environments.program_runtime_v1 = new_environments.program_runtime_v1;
305        }
306        if self.environments.program_runtime_v2 != new_environments.program_runtime_v2 {
307            self.environments.program_runtime_v2 = new_environments.program_runtime_v2;
308        }
309        // Then try to consolidate with the upcoming environments (to reuse their address)
310        if let Some(upcoming_environments) = &self
311            .epoch_boundary_preparation
312            .read()
313            .unwrap()
314            .upcoming_environments
315        {
316            if self.environments.program_runtime_v1 == upcoming_environments.program_runtime_v1
317                && !Arc::ptr_eq(
318                    &self.environments.program_runtime_v1,
319                    &upcoming_environments.program_runtime_v1,
320                )
321            {
322                self.environments.program_runtime_v1 =
323                    upcoming_environments.program_runtime_v1.clone();
324            }
325            if self.environments.program_runtime_v2 == upcoming_environments.program_runtime_v2
326                && !Arc::ptr_eq(
327                    &self.environments.program_runtime_v2,
328                    &upcoming_environments.program_runtime_v2,
329                )
330            {
331                self.environments.program_runtime_v2 =
332                    upcoming_environments.program_runtime_v2.clone();
333            }
334        }
335    }
336
337    /// Returns the current environments depending on the given epoch
338    /// Returns None if the call could result in a deadlock
339    pub fn get_environments_for_epoch(&self, epoch: Epoch) -> ProgramRuntimeEnvironments {
340        self.epoch_boundary_preparation
341            .read()
342            .unwrap()
343            .get_upcoming_environments_for_epoch(epoch)
344            .unwrap_or_else(|| self.environments.clone())
345    }
346
347    pub fn sysvar_cache(&self) -> RwLockReadGuard<'_, SysvarCache> {
348        self.sysvar_cache.read().unwrap()
349    }
350
351    /// Main entrypoint to the SVM.
352    pub fn load_and_execute_sanitized_transactions<CB: TransactionProcessingCallback>(
353        &self,
354        callbacks: &CB,
355        sanitized_txs: &[impl SVMTransaction],
356        check_results: Vec<TransactionCheckResult>,
357        environment: &TransactionProcessingEnvironment,
358        config: &TransactionProcessingConfig,
359    ) -> LoadAndExecuteSanitizedTransactionsOutput {
360        // If `check_results` does not have the same length as `sanitized_txs`,
361        // transactions could be truncated as a result of `.iter().zip()` in
362        // many of the below methods.
363        // See <https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.zip>.
364        debug_assert_eq!(
365            sanitized_txs.len(),
366            check_results.len(),
367            "Length of check_results does not match length of sanitized_txs"
368        );
369
370        // Initialize metrics.
371        let mut error_metrics = TransactionErrorMetrics::default();
372        let mut execute_timings = ExecuteTimings::default();
373        let mut processing_results = Vec::with_capacity(sanitized_txs.len());
374
375        // Determine a capacity for the internal account cache. This
376        // over-allocates but avoids ever reallocating, and spares us from
377        // deduplicating the account keys lists.
378        let account_keys_in_batch = sanitized_txs.iter().map(|tx| tx.account_keys().len()).sum();
379
380        // Create the account loader, which wraps all external account fetching.
381        let mut account_loader = AccountLoader::new_with_loaded_accounts_capacity(
382            config.account_overrides,
383            callbacks,
384            &environment.feature_set,
385            account_keys_in_batch,
386        );
387
388        // Create the transaction balance collector if recording is enabled.
389        let mut balance_collector = config
390            .recording_config
391            .enable_transaction_balance_recording
392            .then(|| BalanceCollector::new_with_transaction_count(sanitized_txs.len()));
393
394        // Create the batch-local program cache.
395        let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::new(self.slot);
396        let builtins = self.builtin_program_ids.read().unwrap().clone();
397        let ((), program_cache_us) = measure_us!({
398            self.replenish_program_cache(
399                &account_loader,
400                &builtins,
401                &environment.program_runtime_environments_for_execution,
402                &mut program_cache_for_tx_batch,
403                &mut execute_timings,
404                config.check_program_modification_slot,
405                config.limit_to_load_programs,
406                false, // increment_usage_counter
407            );
408        });
409        execute_timings
410            .saturating_add_in_place(ExecuteTimingType::ProgramCacheUs, program_cache_us);
411
412        if program_cache_for_tx_batch.hit_max_limit {
413            return LoadAndExecuteSanitizedTransactionsOutput {
414                error_metrics,
415                execute_timings,
416                processing_results: (0..sanitized_txs.len())
417                    .map(|_| Err(TransactionError::ProgramCacheHitMaxLimit))
418                    .collect(),
419                // If we abort the batch and balance recording is enabled, no balances should be
420                // collected. If this is a leader thread, no batch will be committed.
421                balance_collector: None,
422            };
423        }
424
425        let (mut load_us, mut execution_us): (u64, u64) = (0, 0);
426
427        // Validate, execute, and collect results from each transaction in order.
428        // With SIMD83, transactions must be executed in order, because transactions
429        // in the same batch may modify the same accounts. Transaction order is
430        // preserved within entries written to the ledger.
431        for (tx, check_result) in sanitized_txs.iter().zip(check_results) {
432            let (validate_result, validate_fees_us) =
433                measure_us!(check_result.and_then(|tx_details| {
434                    Self::validate_transaction_nonce_and_fee_payer(
435                        &mut account_loader,
436                        tx,
437                        tx_details,
438                        &environment.blockhash,
439                        environment.blockhash_lamports_per_signature,
440                        &environment.rent,
441                        &mut error_metrics,
442                    )
443                }));
444            execute_timings
445                .saturating_add_in_place(ExecuteTimingType::ValidateFeesUs, validate_fees_us);
446
447            let (load_result, single_load_us) = measure_us!(load_transaction(
448                &mut account_loader,
449                tx,
450                validate_result,
451                &mut error_metrics,
452                &environment.rent,
453            ));
454            load_us = load_us.saturating_add(single_load_us);
455
456            let ((), collect_balances_us) =
457                measure_us!(balance_collector.collect_pre_balances(&mut account_loader, tx));
458            execute_timings
459                .saturating_add_in_place(ExecuteTimingType::CollectBalancesUs, collect_balances_us);
460
461            let (processing_result, single_execution_us) = measure_us!(match load_result {
462                TransactionLoadResult::NotLoaded(err) => Err(err),
463                TransactionLoadResult::FeesOnly(fees_only_tx) => {
464                    // Update loaded accounts cache with nonce and fee-payer
465                    account_loader.update_accounts_for_failed_tx(&fees_only_tx.rollback_accounts);
466
467                    Ok(ProcessedTransaction::FeesOnly(Box::new(fees_only_tx)))
468                }
469                TransactionLoadResult::Loaded(loaded_transaction) => {
470                    let (program_accounts_set, filter_executable_us) = measure_us!(self
471                        .filter_executable_program_accounts(
472                            &account_loader,
473                            &mut program_cache_for_tx_batch,
474                            tx,
475                        ));
476                    execute_timings.saturating_add_in_place(
477                        ExecuteTimingType::FilterExecutableUs,
478                        filter_executable_us,
479                    );
480
481                    let ((), program_cache_us) = measure_us!({
482                        self.replenish_program_cache(
483                            &account_loader,
484                            &program_accounts_set,
485                            &environment.program_runtime_environments_for_execution,
486                            &mut program_cache_for_tx_batch,
487                            &mut execute_timings,
488                            config.check_program_modification_slot,
489                            config.limit_to_load_programs,
490                            true, // increment_usage_counter
491                        );
492                    });
493                    execute_timings.saturating_add_in_place(
494                        ExecuteTimingType::ProgramCacheUs,
495                        program_cache_us,
496                    );
497
498                    if program_cache_for_tx_batch.hit_max_limit {
499                        return LoadAndExecuteSanitizedTransactionsOutput {
500                            error_metrics,
501                            execute_timings,
502                            processing_results: (0..sanitized_txs.len())
503                                .map(|_| Err(TransactionError::ProgramCacheHitMaxLimit))
504                                .collect(),
505                            // If we abort the batch and balance recording is enabled, no balances should be
506                            // collected. If this is a leader thread, no batch will be committed.
507                            balance_collector: None,
508                        };
509                    }
510
511                    let executed_tx = self.execute_loaded_transaction(
512                        callbacks,
513                        tx,
514                        loaded_transaction,
515                        &mut execute_timings,
516                        &mut error_metrics,
517                        &mut program_cache_for_tx_batch,
518                        environment,
519                        config,
520                    );
521
522                    // Update loaded accounts cache with account states which might have changed.
523                    // Also update local program cache with modifications made by the transaction,
524                    // if it executed successfully.
525                    account_loader.update_accounts_for_executed_tx(tx, &executed_tx);
526
527                    if executed_tx.was_successful() {
528                        program_cache_for_tx_batch.merge(&executed_tx.programs_modified_by_tx);
529                    }
530
531                    Ok(ProcessedTransaction::Executed(Box::new(executed_tx)))
532                }
533            });
534            execution_us = execution_us.saturating_add(single_execution_us);
535
536            let ((), collect_balances_us) =
537                measure_us!(balance_collector.collect_post_balances(&mut account_loader, tx));
538            execute_timings
539                .saturating_add_in_place(ExecuteTimingType::CollectBalancesUs, collect_balances_us);
540
541            processing_results.push(processing_result);
542        }
543
544        // Skip eviction when there's no chance this particular tx batch has increased the size of
545        // ProgramCache entries. Note that loaded_missing is deliberately defined, so that there's
546        // still at least one other batch, which will evict the program cache, even after the
547        // occurrences of cooperative loading.
548        if program_cache_for_tx_batch.loaded_missing || program_cache_for_tx_batch.merged_modified {
549            const SHRINK_LOADED_PROGRAMS_TO_PERCENTAGE: u8 = 90;
550            self.global_program_cache
551                .write()
552                .unwrap()
553                .evict_using_2s_random_selection(
554                    Percentage::from(SHRINK_LOADED_PROGRAMS_TO_PERCENTAGE),
555                    self.slot,
556                );
557        }
558
559        debug!(
560            "load: {}us execute: {}us txs_len={}",
561            load_us,
562            execution_us,
563            sanitized_txs.len(),
564        );
565        execute_timings.saturating_add_in_place(ExecuteTimingType::LoadUs, load_us);
566        execute_timings.saturating_add_in_place(ExecuteTimingType::ExecuteUs, execution_us);
567
568        if let Some(ref balance_collector) = balance_collector {
569            debug_assert!(balance_collector.lengths_match_expected(sanitized_txs.len()));
570        }
571
572        LoadAndExecuteSanitizedTransactionsOutput {
573            error_metrics,
574            execute_timings,
575            processing_results,
576            balance_collector,
577        }
578    }
579
580    fn validate_transaction_nonce_and_fee_payer<CB: TransactionProcessingCallback>(
581        account_loader: &mut AccountLoader<CB>,
582        message: &impl SVMMessage,
583        checked_details: CheckedTransactionDetails,
584        environment_blockhash: &Hash,
585        next_lamports_per_signature: u64,
586        rent: &Rent,
587        error_counters: &mut TransactionErrorMetrics,
588    ) -> TransactionResult<ValidatedTransactionDetails> {
589        let CheckedTransactionDetails {
590            nonce_address,
591            compute_budget_and_limits,
592        } = checked_details;
593
594        // If this is a nonce transaction, validate the nonce info.
595        // This must be done for every transaction to support SIMD83 because
596        // it may have changed due to use, authorization, or deallocation.
597        let nonce_info = if let Some(ref nonce_address) = nonce_address {
598            let next_durable_nonce = DurableNonce::from_blockhash(environment_blockhash);
599            Some(Self::validate_transaction_nonce(
600                account_loader,
601                message,
602                nonce_address,
603                &next_durable_nonce,
604                next_lamports_per_signature,
605                error_counters,
606            )?)
607        } else {
608            None
609        };
610
611        // Now validate the fee-payer for the transaction unconditionally.
612        Self::validate_transaction_fee_payer(
613            account_loader,
614            message,
615            nonce_info,
616            compute_budget_and_limits,
617            rent,
618            error_counters,
619        )
620    }
621
622    // Loads transaction fee payer, collects rent if necessary, then calculates
623    // transaction fees, and deducts them from the fee payer balance. If the
624    // account is not found or has insufficient funds, an error is returned.
625    fn validate_transaction_fee_payer<CB: TransactionProcessingCallback>(
626        account_loader: &mut AccountLoader<CB>,
627        message: &impl SVMMessage,
628        nonce_info: Option<NonceInfo>,
629        compute_budget_and_limits: SVMTransactionExecutionAndFeeBudgetLimits,
630        rent: &Rent,
631        error_counters: &mut TransactionErrorMetrics,
632    ) -> TransactionResult<ValidatedTransactionDetails> {
633        let fee_payer_address = message.fee_payer();
634
635        // We *must* use load_transaction_account() here because *this* is when the fee-payer
636        // is loaded for the transaction. Transaction loading skips the first account and
637        // loads (and thus inspects) all others normally.
638        let Some(mut loaded_fee_payer) =
639            account_loader.load_transaction_account(fee_payer_address, true)
640        else {
641            error_counters.account_not_found += 1;
642            return Err(TransactionError::AccountNotFound);
643        };
644
645        let fee_payer_loaded_rent_epoch = loaded_fee_payer.account.rent_epoch();
646        update_rent_exempt_status_for_account(rent, &mut loaded_fee_payer.account);
647
648        let fee_payer_index = 0;
649        validate_fee_payer(
650            fee_payer_address,
651            &mut loaded_fee_payer.account,
652            fee_payer_index,
653            error_counters,
654            rent,
655            compute_budget_and_limits.fee_details.total_fee(),
656        )?;
657
658        // Capture fee-subtracted fee payer account and next nonce account state
659        // to commit if transaction execution fails.
660        let rollback_accounts = RollbackAccounts::new(
661            nonce_info,
662            *fee_payer_address,
663            loaded_fee_payer.account.clone(),
664            fee_payer_loaded_rent_epoch,
665        );
666
667        Ok(ValidatedTransactionDetails {
668            fee_details: compute_budget_and_limits.fee_details,
669            rollback_accounts,
670            loaded_accounts_bytes_limit: compute_budget_and_limits.loaded_accounts_data_size_limit,
671            compute_budget: compute_budget_and_limits.budget,
672            loaded_fee_payer_account: loaded_fee_payer,
673        })
674    }
675
676    fn validate_transaction_nonce<CB: TransactionProcessingCallback>(
677        account_loader: &mut AccountLoader<CB>,
678        message: &impl SVMMessage,
679        nonce_address: &Pubkey,
680        next_durable_nonce: &DurableNonce,
681        next_lamports_per_signature: u64,
682        error_counters: &mut TransactionErrorMetrics,
683    ) -> TransactionResult<NonceInfo> {
684        // When SIMD83 is enabled, if the nonce has been used in this batch already, we must drop
685        // the transaction. This is the same as if it was used in different batches in the same slot.
686        // It is possible that the nonce account was used, closed, closed and reopened, closed and
687        // spoofed by a non-system program, or had its authority changed. Such a transaction cannot
688        // be processed, even as fee-only.
689
690        let Some(mut nonce_account) = account_loader
691            .load_transaction_account(nonce_address, true)
692            .map(|loaded| loaded.account)
693        else {
694            error_counters.account_not_found += 1;
695            return Err(TransactionError::AccountNotFound);
696        };
697
698        // This function verifies:
699        // * Nonce account owner is SystemProgram
700        // * Nonce account parses as State::Initialized
701        // * Stored durable nonce matches the message blockhash
702        let Some(nonce_data) = verify_nonce_account(&nonce_account, message.recent_blockhash())
703        else {
704            error_counters.blockhash_not_found += 1;
705            return Err(TransactionError::BlockhashNotFound);
706        };
707
708        // We must still check that the nonce account is usable and that its authority has signed.
709        let nonce_can_be_advanced = &nonce_data.durable_nonce != next_durable_nonce;
710        let nonce_authority_is_valid = message
711            .get_ix_signers(NONCED_TX_MARKER_IX_INDEX as usize)
712            .any(|signer| signer == &nonce_data.authority);
713
714        if nonce_can_be_advanced && nonce_authority_is_valid {
715            let next_nonce_state = NonceState::new_initialized(
716                &nonce_data.authority,
717                *next_durable_nonce,
718                next_lamports_per_signature,
719            );
720            nonce_account
721                .set_state(&NonceVersions::new(next_nonce_state))
722                .expect("Serializing into a validated nonce account cannot fail");
723
724            Ok(NonceInfo::new(*nonce_address, nonce_account))
725        } else {
726            error_counters.blockhash_not_found += 1;
727            Err(TransactionError::BlockhashNotFound)
728        }
729    }
730
731    /// Appends to a set of executable program accounts (all accounts owned by any loader)
732    /// for transactions with a valid blockhash or nonce.
733    fn filter_executable_program_accounts<CB: TransactionProcessingCallback>(
734        &self,
735        account_loader: &AccountLoader<CB>,
736        program_cache_for_tx_batch: &mut ProgramCacheForTxBatch,
737        tx: &impl SVMMessage,
738    ) -> HashSet<Pubkey> {
739        let mut program_accounts_set = HashSet::default();
740        for account_key in tx.account_keys().iter() {
741            if let Some(cache_entry) = program_cache_for_tx_batch.find(account_key) {
742                cache_entry.tx_usage_counter.fetch_add(1, Ordering::Relaxed);
743            } else if account_loader
744                .get_account_shared_data(account_key)
745                .map(|(account, _slot)| PROGRAM_OWNERS.contains(account.owner()))
746                .unwrap_or(false)
747            {
748                program_accounts_set.insert(*account_key);
749            }
750        }
751        program_accounts_set
752    }
753
754    #[cfg_attr(feature = "dev-context-only-utils", qualifiers(pub))]
755    fn replenish_program_cache<CB: TransactionProcessingCallback>(
756        &self,
757        account_loader: &AccountLoader<CB>,
758        program_accounts_set: &HashSet<Pubkey>,
759        program_runtime_environments_for_execution: &ProgramRuntimeEnvironments,
760        program_cache_for_tx_batch: &mut ProgramCacheForTxBatch,
761        execute_timings: &mut ExecuteTimings,
762        check_program_modification_slot: bool,
763        limit_to_load_programs: bool,
764        increment_usage_counter: bool,
765    ) {
766        let mut missing_programs: Vec<(Pubkey, ProgramCacheMatchCriteria)> = program_accounts_set
767            .iter()
768            .map(|pubkey| {
769                let match_criteria = if check_program_modification_slot {
770                    get_program_modification_slot(account_loader, pubkey)
771                        .map_or(ProgramCacheMatchCriteria::Tombstone, |slot| {
772                            ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(slot)
773                        })
774                } else {
775                    ProgramCacheMatchCriteria::NoCriteria
776                };
777                (*pubkey, match_criteria)
778            })
779            .collect();
780
781        let mut count_hits_and_misses = true;
782        loop {
783            let (program_to_store, task_cookie, task_waiter) = {
784                // Lock the global cache.
785                let global_program_cache = self.global_program_cache.read().unwrap();
786                // Figure out which program needs to be loaded next.
787                let program_to_load = global_program_cache.extract(
788                    &mut missing_programs,
789                    program_cache_for_tx_batch,
790                    program_runtime_environments_for_execution,
791                    increment_usage_counter,
792                    count_hits_and_misses,
793                );
794                count_hits_and_misses = false;
795
796                let program_to_store = program_to_load.map(|key| {
797                    // Load, verify and compile one program.
798                    let program = load_program_with_pubkey(
799                        account_loader,
800                        program_runtime_environments_for_execution,
801                        &key,
802                        self.slot,
803                        execute_timings,
804                        false,
805                    )
806                    .expect("called load_program_with_pubkey() with nonexistent account");
807                    (key, program)
808                });
809
810                let task_waiter = Arc::clone(&global_program_cache.loading_task_waiter);
811                (program_to_store, task_waiter.cookie(), task_waiter)
812                // Unlock the global cache again.
813            };
814
815            if let Some((key, program)) = program_to_store {
816                program_cache_for_tx_batch.loaded_missing = true;
817                let mut global_program_cache = self.global_program_cache.write().unwrap();
818                // Submit our last completed loading task.
819                if global_program_cache.finish_cooperative_loading_task(
820                    program_runtime_environments_for_execution,
821                    self.slot,
822                    key,
823                    program,
824                ) && limit_to_load_programs
825                {
826                    // This branch is taken when there is an error in assigning a program to a
827                    // cache slot. It is not possible to mock this error for SVM unit
828                    // tests purposes.
829                    *program_cache_for_tx_batch = ProgramCacheForTxBatch::new(self.slot);
830                    program_cache_for_tx_batch.hit_max_limit = true;
831                    return;
832                }
833            } else if missing_programs.is_empty() {
834                break;
835            } else {
836                // Sleep until the next finish_cooperative_loading_task() call.
837                // Once a task completes we'll wake up and try to load the
838                // missing programs inside the tx batch again.
839                let _new_cookie = task_waiter.wait(task_cookie);
840            }
841        }
842    }
843
844    /// Execute a transaction using the provided loaded accounts and update
845    /// the executors cache if the transaction was successful.
846    #[allow(clippy::too_many_arguments)]
847    fn execute_loaded_transaction<CB: TransactionProcessingCallback>(
848        &self,
849        callback: &CB,
850        tx: &impl SVMTransaction,
851        mut loaded_transaction: LoadedTransaction,
852        execute_timings: &mut ExecuteTimings,
853        error_metrics: &mut TransactionErrorMetrics,
854        program_cache_for_tx_batch: &mut ProgramCacheForTxBatch,
855        environment: &TransactionProcessingEnvironment,
856        config: &TransactionProcessingConfig,
857    ) -> ExecutedTransaction {
858        let transaction_accounts = std::mem::take(&mut loaded_transaction.accounts);
859
860        // Ensure the length of accounts matches the expected length from tx.account_keys().
861        // This is a sanity check in case that someone starts adding some additional accounts
862        // since this has been done before. See discussion in PR #4497 for details
863        debug_assert!(transaction_accounts.len() == tx.account_keys().len());
864
865        fn transaction_accounts_lamports_sum(
866            accounts: &[(Pubkey, AccountSharedData)],
867        ) -> Option<u128> {
868            accounts.iter().try_fold(0u128, |sum, (_, account)| {
869                sum.checked_add(u128::from(account.lamports()))
870            })
871        }
872
873        let lamports_before_tx =
874            transaction_accounts_lamports_sum(&transaction_accounts).unwrap_or(0);
875
876        let compute_budget = loaded_transaction.compute_budget;
877
878        let mut transaction_context = TransactionContext::new(
879            transaction_accounts,
880            environment.rent.clone(),
881            compute_budget.max_instruction_stack_depth,
882            compute_budget.max_instruction_trace_length,
883        );
884
885        let pre_account_state_info =
886            TransactionAccountStateInfo::new(&transaction_context, tx, &environment.rent);
887
888        let log_collector = if config.recording_config.enable_log_recording {
889            match config.log_messages_bytes_limit {
890                None => Some(LogCollector::new_ref()),
891                Some(log_messages_bytes_limit) => Some(LogCollector::new_ref_with_limit(Some(
892                    log_messages_bytes_limit,
893                ))),
894            }
895        } else {
896            None
897        };
898
899        let mut executed_units = 0u64;
900        let sysvar_cache = &self.sysvar_cache.read().unwrap();
901
902        let mut invoke_context = InvokeContext::new(
903            &mut transaction_context,
904            program_cache_for_tx_batch,
905            EnvironmentConfig::new(
906                environment.blockhash,
907                environment.blockhash_lamports_per_signature,
908                callback,
909                &environment.feature_set,
910                &environment.program_runtime_environments_for_execution,
911                &environment.program_runtime_environments_for_deployment,
912                sysvar_cache,
913            ),
914            log_collector.clone(),
915            compute_budget,
916            self.execution_cost,
917        );
918
919        let mut process_message_time = Measure::start("process_message_time");
920        let process_result = process_message(
921            tx,
922            &loaded_transaction.program_indices,
923            &mut invoke_context,
924            execute_timings,
925            &mut executed_units,
926        );
927        process_message_time.stop();
928
929        drop(invoke_context);
930
931        execute_timings.execute_accessories.process_message_us += process_message_time.as_us();
932
933        let mut status = process_result
934            .and_then(|info| {
935                let post_account_state_info =
936                    TransactionAccountStateInfo::new(&transaction_context, tx, &environment.rent);
937                TransactionAccountStateInfo::verify_changes(
938                    &pre_account_state_info,
939                    &post_account_state_info,
940                    &transaction_context,
941                )
942                .map(|_| info)
943            })
944            .map_err(|err| {
945                match err {
946                    TransactionError::InvalidRentPayingAccount
947                    | TransactionError::InsufficientFundsForRent { .. } => {
948                        error_metrics.invalid_rent_paying_account += 1;
949                    }
950                    TransactionError::InvalidAccountIndex => {
951                        error_metrics.invalid_account_index += 1;
952                    }
953                    _ => {
954                        error_metrics.instruction_error += 1;
955                    }
956                }
957                err
958            });
959
960        let log_messages: Option<TransactionLogMessages> =
961            log_collector.and_then(|log_collector| {
962                Rc::try_unwrap(log_collector)
963                    .map(|log_collector| log_collector.into_inner().into_messages())
964                    .ok()
965            });
966
967        let (execution_record, inner_instructions) = Self::deconstruct_transaction(
968            transaction_context,
969            config.recording_config.enable_cpi_recording,
970        );
971
972        let ExecutionRecord {
973            accounts,
974            return_data,
975            touched_account_count,
976            accounts_resize_delta: accounts_data_len_delta,
977        } = execution_record;
978
979        if status.is_ok()
980            && transaction_accounts_lamports_sum(&accounts)
981                .filter(|lamports_after_tx| lamports_before_tx == *lamports_after_tx)
982                .is_none()
983        {
984            status = Err(TransactionError::UnbalancedTransaction);
985        }
986        let status = status.map(|_| ());
987
988        loaded_transaction.accounts = accounts;
989        execute_timings.details.total_account_count += loaded_transaction.accounts.len() as u64;
990        execute_timings.details.changed_account_count += touched_account_count;
991
992        let return_data = if config.recording_config.enable_return_data_recording
993            && !return_data.data.is_empty()
994        {
995            Some(return_data)
996        } else {
997            None
998        };
999
1000        ExecutedTransaction {
1001            execution_details: TransactionExecutionDetails {
1002                status,
1003                log_messages,
1004                inner_instructions,
1005                return_data,
1006                executed_units,
1007                accounts_data_len_delta,
1008            },
1009            loaded_transaction,
1010            programs_modified_by_tx: program_cache_for_tx_batch.drain_modified_entries(),
1011        }
1012    }
1013
1014    /// Extract an ExecutionRecord and an InnerInstructionsList from a TransactionContext
1015    fn deconstruct_transaction(
1016        mut transaction_context: TransactionContext,
1017        record_inner_instructions: bool,
1018    ) -> (ExecutionRecord, Option<InnerInstructionsList>) {
1019        let inner_ix = if record_inner_instructions {
1020            debug_assert!(transaction_context
1021                .get_instruction_context_at_index_in_trace(0)
1022                .map(|instruction_context| instruction_context.get_stack_height()
1023                    == TRANSACTION_LEVEL_STACK_HEIGHT)
1024                .unwrap_or(true));
1025
1026            let ix_trace = transaction_context.take_instruction_trace();
1027            let mut outer_instructions = Vec::new();
1028            for ix_in_trace in ix_trace.into_iter() {
1029                let stack_height = ix_in_trace.nesting_level.saturating_add(1);
1030                if stack_height == TRANSACTION_LEVEL_STACK_HEIGHT {
1031                    outer_instructions.push(Vec::new());
1032                } else if let Some(inner_instructions) = outer_instructions.last_mut() {
1033                    let stack_height = u8::try_from(stack_height).unwrap_or(u8::MAX);
1034                    inner_instructions.push(InnerInstruction {
1035                        instruction: CompiledInstruction::new_from_raw_parts(
1036                            ix_in_trace.program_account_index_in_tx as u8,
1037                            ix_in_trace.instruction_data.into_owned(),
1038                            ix_in_trace
1039                                .instruction_accounts
1040                                .iter()
1041                                .map(|acc| acc.index_in_transaction as u8)
1042                                .collect(),
1043                        ),
1044                        stack_height,
1045                    });
1046                } else {
1047                    debug_assert!(false);
1048                }
1049            }
1050
1051            Some(outer_instructions)
1052        } else {
1053            None
1054        };
1055
1056        let record: ExecutionRecord = transaction_context.into();
1057
1058        (record, inner_ix)
1059    }
1060
1061    pub fn fill_missing_sysvar_cache_entries<CB: TransactionProcessingCallback>(
1062        &self,
1063        callbacks: &CB,
1064    ) {
1065        let mut sysvar_cache = self.sysvar_cache.write().unwrap();
1066        sysvar_cache.fill_missing_entries(|pubkey, set_sysvar| {
1067            if let Some((account, _slot)) = callbacks.get_account_shared_data(pubkey) {
1068                set_sysvar(account.data());
1069            }
1070        });
1071    }
1072
1073    pub fn reset_sysvar_cache(&self) {
1074        let mut sysvar_cache = self.sysvar_cache.write().unwrap();
1075        sysvar_cache.reset();
1076    }
1077
1078    pub fn get_sysvar_cache_for_tests(&self) -> SysvarCache {
1079        self.sysvar_cache.read().unwrap().clone()
1080    }
1081
1082    /// Add a built-in program
1083    pub fn add_builtin(&self, program_id: Pubkey, builtin: ProgramCacheEntry) {
1084        self.builtin_program_ids.write().unwrap().insert(program_id);
1085        self.global_program_cache.write().unwrap().assign_program(
1086            &self.environments,
1087            program_id,
1088            Arc::new(builtin),
1089        );
1090    }
1091
1092    #[cfg(feature = "dev-context-only-utils")]
1093    #[cfg_attr(feature = "dev-context-only-utils", qualifiers(pub))]
1094    fn writable_sysvar_cache(&self) -> &RwLock<SysvarCache> {
1095        &self.sysvar_cache
1096    }
1097}
1098
1099#[cfg(test)]
1100mod tests {
1101    #[allow(deprecated)]
1102    use solana_sysvar::fees::Fees;
1103    use {
1104        super::*,
1105        crate::{
1106            account_loader::{
1107                LoadedTransactionAccount, ValidatedTransactionDetails,
1108                TRANSACTION_ACCOUNT_BASE_SIZE,
1109            },
1110            nonce_info::NonceInfo,
1111            rent_calculator::RENT_EXEMPT_RENT_EPOCH,
1112            rollback_accounts::RollbackAccounts,
1113        },
1114        solana_account::{create_account_shared_data_for_test, WritableAccount},
1115        solana_clock::Clock,
1116        solana_compute_budget_interface::ComputeBudgetInstruction,
1117        solana_epoch_schedule::EpochSchedule,
1118        solana_fee_calculator::FeeCalculator,
1119        solana_fee_structure::FeeDetails,
1120        solana_hash::Hash,
1121        solana_keypair::Keypair,
1122        solana_message::{LegacyMessage, Message, MessageHeader, SanitizedMessage},
1123        solana_nonce as nonce,
1124        solana_program_runtime::{
1125            execution_budget::{
1126                SVMTransactionExecutionAndFeeBudgetLimits, SVMTransactionExecutionBudget,
1127            },
1128            loaded_programs::{BlockRelation, ProgramCacheEntryType},
1129        },
1130        solana_rent::Rent,
1131        solana_sdk_ids::{bpf_loader, loader_v4, system_program, sysvar},
1132        solana_signature::Signature,
1133        solana_svm_callback::{AccountState, InvokeContextCallback},
1134        solana_system_interface::instruction as system_instruction,
1135        solana_transaction::{sanitized::SanitizedTransaction, Transaction},
1136        solana_transaction_context::TransactionContext,
1137        solana_transaction_error::TransactionError,
1138        std::collections::HashMap,
1139        test_case::test_case,
1140    };
1141
1142    fn new_unchecked_sanitized_message(message: Message) -> SanitizedMessage {
1143        SanitizedMessage::Legacy(LegacyMessage::new(message, &HashSet::new()))
1144    }
1145
1146    struct TestForkGraph {}
1147
1148    impl ForkGraph for TestForkGraph {
1149        fn relationship(&self, _a: Slot, _b: Slot) -> BlockRelation {
1150            BlockRelation::Unknown
1151        }
1152    }
1153
1154    #[derive(Clone)]
1155    struct MockBankCallback {
1156        account_shared_data: Arc<RwLock<HashMap<Pubkey, AccountSharedData>>>,
1157        #[allow(clippy::type_complexity)]
1158        inspected_accounts:
1159            Arc<RwLock<HashMap<Pubkey, Vec<(Option<AccountSharedData>, /* is_writable */ bool)>>>>,
1160        feature_set: SVMFeatureSet,
1161    }
1162
1163    impl Default for MockBankCallback {
1164        fn default() -> Self {
1165            Self {
1166                account_shared_data: Arc::default(),
1167                inspected_accounts: Arc::default(),
1168                feature_set: SVMFeatureSet::all_enabled(),
1169            }
1170        }
1171    }
1172
1173    impl InvokeContextCallback for MockBankCallback {}
1174
1175    impl TransactionProcessingCallback for MockBankCallback {
1176        fn get_account_shared_data(&self, pubkey: &Pubkey) -> Option<(AccountSharedData, Slot)> {
1177            self.account_shared_data
1178                .read()
1179                .unwrap()
1180                .get(pubkey)
1181                .map(|account| (account.clone(), 0))
1182        }
1183
1184        fn inspect_account(
1185            &self,
1186            address: &Pubkey,
1187            account_state: AccountState,
1188            is_writable: bool,
1189        ) {
1190            let account = match account_state {
1191                AccountState::Dead => None,
1192                AccountState::Alive(account) => Some(account.clone()),
1193            };
1194            self.inspected_accounts
1195                .write()
1196                .unwrap()
1197                .entry(*address)
1198                .or_default()
1199                .push((account, is_writable));
1200        }
1201    }
1202
1203    impl MockBankCallback {
1204        pub fn calculate_fee_details(
1205            message: &impl SVMMessage,
1206            lamports_per_signature: u64,
1207            prioritization_fee: u64,
1208        ) -> FeeDetails {
1209            let signature_count = message
1210                .num_transaction_signatures()
1211                .saturating_add(message.num_ed25519_signatures())
1212                .saturating_add(message.num_secp256k1_signatures())
1213                .saturating_add(message.num_secp256r1_signatures());
1214
1215            FeeDetails::new(
1216                signature_count.saturating_mul(lamports_per_signature),
1217                prioritization_fee,
1218            )
1219        }
1220    }
1221
1222    impl<'a> From<&'a MockBankCallback> for AccountLoader<'a, MockBankCallback> {
1223        fn from(callbacks: &'a MockBankCallback) -> AccountLoader<'a, MockBankCallback> {
1224            AccountLoader::new_with_loaded_accounts_capacity(
1225                None,
1226                callbacks,
1227                &callbacks.feature_set,
1228                0,
1229            )
1230        }
1231    }
1232
1233    #[test_case(1; "Check results too small")]
1234    #[test_case(3; "Check results too large")]
1235    #[should_panic(expected = "Length of check_results does not match length of sanitized_txs")]
1236    fn test_check_results_txs_length_mismatch(check_results_len: usize) {
1237        let sanitized_message = new_unchecked_sanitized_message(Message {
1238            account_keys: vec![Pubkey::new_from_array([0; 32])],
1239            header: MessageHeader::default(),
1240            instructions: vec![CompiledInstruction {
1241                program_id_index: 0,
1242                accounts: vec![],
1243                data: vec![],
1244            }],
1245            recent_blockhash: Hash::default(),
1246        });
1247
1248        // Transactions, length 2.
1249        let sanitized_txs = vec![
1250            SanitizedTransaction::new_for_tests(
1251                sanitized_message,
1252                vec![Signature::new_unique()],
1253                false,
1254            );
1255            2
1256        ];
1257
1258        let check_results = vec![
1259            TransactionCheckResult::Ok(CheckedTransactionDetails::default());
1260            check_results_len
1261        ];
1262
1263        let batch_processor = TransactionBatchProcessor::<TestForkGraph>::default();
1264        let callback = MockBankCallback::default();
1265
1266        batch_processor.load_and_execute_sanitized_transactions(
1267            &callback,
1268            &sanitized_txs,
1269            check_results,
1270            &TransactionProcessingEnvironment::default(),
1271            &TransactionProcessingConfig::default(),
1272        );
1273    }
1274
1275    #[test]
1276    fn test_inner_instructions_list_from_instruction_trace() {
1277        let instruction_trace = [1, 2, 1, 1, 2, 3, 2];
1278        let mut transaction_context = TransactionContext::new(
1279            vec![(
1280                Pubkey::new_unique(),
1281                AccountSharedData::new(1, 1, &bpf_loader::ID),
1282            )],
1283            Rent::default(),
1284            3,
1285            instruction_trace.len(),
1286        );
1287        for (index_in_trace, stack_height) in instruction_trace.into_iter().enumerate() {
1288            while stack_height <= transaction_context.get_instruction_stack_height() {
1289                transaction_context.pop().unwrap();
1290            }
1291            if stack_height > transaction_context.get_instruction_stack_height() {
1292                transaction_context
1293                    .configure_next_instruction_for_tests(0, vec![], vec![index_in_trace as u8])
1294                    .unwrap();
1295                transaction_context.push().unwrap();
1296            }
1297        }
1298        let inner_instructions =
1299            TransactionBatchProcessor::<TestForkGraph>::deconstruct_transaction(
1300                transaction_context,
1301                true,
1302            )
1303            .1
1304            .unwrap();
1305
1306        assert_eq!(
1307            inner_instructions,
1308            vec![
1309                vec![InnerInstruction {
1310                    instruction: CompiledInstruction::new_from_raw_parts(0, vec![1], vec![]),
1311                    stack_height: 2,
1312                }],
1313                vec![],
1314                vec![
1315                    InnerInstruction {
1316                        instruction: CompiledInstruction::new_from_raw_parts(0, vec![4], vec![]),
1317                        stack_height: 2,
1318                    },
1319                    InnerInstruction {
1320                        instruction: CompiledInstruction::new_from_raw_parts(0, vec![5], vec![]),
1321                        stack_height: 3,
1322                    },
1323                    InnerInstruction {
1324                        instruction: CompiledInstruction::new_from_raw_parts(0, vec![6], vec![]),
1325                        stack_height: 2,
1326                    },
1327                ]
1328            ]
1329        );
1330    }
1331
1332    #[test]
1333    fn test_execute_loaded_transaction_recordings() {
1334        // Setting all the arguments correctly is too burdensome for testing
1335        // execute_loaded_transaction separately.This function will be tested in an integration
1336        // test with load_and_execute_sanitized_transactions
1337        let message = Message {
1338            account_keys: vec![Pubkey::new_from_array([0; 32])],
1339            header: MessageHeader::default(),
1340            instructions: vec![CompiledInstruction {
1341                program_id_index: 0,
1342                accounts: vec![],
1343                data: vec![],
1344            }],
1345            recent_blockhash: Hash::default(),
1346        };
1347
1348        let sanitized_message = new_unchecked_sanitized_message(message);
1349        let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
1350        let batch_processor = TransactionBatchProcessor::<TestForkGraph>::default();
1351
1352        let sanitized_transaction = SanitizedTransaction::new_for_tests(
1353            sanitized_message,
1354            vec![Signature::new_unique()],
1355            false,
1356        );
1357
1358        let loaded_transaction = LoadedTransaction {
1359            accounts: vec![(Pubkey::new_unique(), AccountSharedData::default())],
1360            program_indices: vec![0],
1361            fee_details: FeeDetails::default(),
1362            rollback_accounts: RollbackAccounts::default(),
1363            compute_budget: SVMTransactionExecutionBudget::default(),
1364            loaded_accounts_data_size: 32,
1365        };
1366
1367        let processing_environment = TransactionProcessingEnvironment::default();
1368
1369        let mut processing_config = TransactionProcessingConfig::default();
1370        processing_config.recording_config.enable_log_recording = true;
1371
1372        let mock_bank = MockBankCallback::default();
1373
1374        let executed_tx = batch_processor.execute_loaded_transaction(
1375            &mock_bank,
1376            &sanitized_transaction,
1377            loaded_transaction.clone(),
1378            &mut ExecuteTimings::default(),
1379            &mut TransactionErrorMetrics::default(),
1380            &mut program_cache_for_tx_batch,
1381            &processing_environment,
1382            &processing_config,
1383        );
1384        assert!(executed_tx.execution_details.log_messages.is_some());
1385
1386        processing_config.log_messages_bytes_limit = Some(2);
1387
1388        let executed_tx = batch_processor.execute_loaded_transaction(
1389            &mock_bank,
1390            &sanitized_transaction,
1391            loaded_transaction.clone(),
1392            &mut ExecuteTimings::default(),
1393            &mut TransactionErrorMetrics::default(),
1394            &mut program_cache_for_tx_batch,
1395            &processing_environment,
1396            &processing_config,
1397        );
1398        assert!(executed_tx.execution_details.log_messages.is_some());
1399        assert!(executed_tx.execution_details.inner_instructions.is_none());
1400
1401        processing_config.recording_config.enable_log_recording = false;
1402        processing_config.recording_config.enable_cpi_recording = true;
1403        processing_config.log_messages_bytes_limit = None;
1404
1405        let executed_tx = batch_processor.execute_loaded_transaction(
1406            &mock_bank,
1407            &sanitized_transaction,
1408            loaded_transaction,
1409            &mut ExecuteTimings::default(),
1410            &mut TransactionErrorMetrics::default(),
1411            &mut program_cache_for_tx_batch,
1412            &processing_environment,
1413            &processing_config,
1414        );
1415
1416        assert!(executed_tx.execution_details.log_messages.is_none());
1417        assert!(executed_tx.execution_details.inner_instructions.is_some());
1418    }
1419
1420    #[test]
1421    fn test_execute_loaded_transaction_error_metrics() {
1422        // Setting all the arguments correctly is too burdensome for testing
1423        // execute_loaded_transaction separately.This function will be tested in an integration
1424        // test with load_and_execute_sanitized_transactions
1425        let key1 = Pubkey::new_unique();
1426        let key2 = Pubkey::new_unique();
1427        let message = Message {
1428            account_keys: vec![key1, key2],
1429            header: MessageHeader::default(),
1430            instructions: vec![CompiledInstruction {
1431                program_id_index: 0,
1432                accounts: vec![2],
1433                data: vec![],
1434            }],
1435            recent_blockhash: Hash::default(),
1436        };
1437
1438        let sanitized_message = new_unchecked_sanitized_message(message);
1439        let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
1440        let batch_processor = TransactionBatchProcessor::<TestForkGraph>::default();
1441
1442        let sanitized_transaction = SanitizedTransaction::new_for_tests(
1443            sanitized_message,
1444            vec![Signature::new_unique()],
1445            false,
1446        );
1447
1448        let mut account_data = AccountSharedData::default();
1449        account_data.set_owner(bpf_loader::id());
1450        let loaded_transaction = LoadedTransaction {
1451            accounts: vec![
1452                (key1, AccountSharedData::default()),
1453                (key2, AccountSharedData::default()),
1454            ],
1455            program_indices: vec![0],
1456            fee_details: FeeDetails::default(),
1457            rollback_accounts: RollbackAccounts::default(),
1458            compute_budget: SVMTransactionExecutionBudget::default(),
1459            loaded_accounts_data_size: 0,
1460        };
1461
1462        let processing_config = TransactionProcessingConfig {
1463            recording_config: ExecutionRecordingConfig::new_single_setting(false),
1464            ..Default::default()
1465        };
1466        let mut error_metrics = TransactionErrorMetrics::new();
1467        let mock_bank = MockBankCallback::default();
1468
1469        let _ = batch_processor.execute_loaded_transaction(
1470            &mock_bank,
1471            &sanitized_transaction,
1472            loaded_transaction,
1473            &mut ExecuteTimings::default(),
1474            &mut error_metrics,
1475            &mut program_cache_for_tx_batch,
1476            &TransactionProcessingEnvironment::default(),
1477            &processing_config,
1478        );
1479
1480        assert_eq!(error_metrics.instruction_error.0, 1);
1481    }
1482
1483    #[test]
1484    #[should_panic = "called load_program_with_pubkey() with nonexistent account"]
1485    fn test_replenish_program_cache_with_nonexistent_accounts() {
1486        let mock_bank = MockBankCallback::default();
1487        let account_loader = (&mock_bank).into();
1488        let fork_graph = Arc::new(RwLock::new(TestForkGraph {}));
1489        let batch_processor =
1490            TransactionBatchProcessor::new(0, 0, Arc::downgrade(&fork_graph), None, None);
1491        let key = Pubkey::new_unique();
1492
1493        let mut account_set = HashSet::new();
1494        account_set.insert(key);
1495
1496        let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::new(batch_processor.slot);
1497
1498        batch_processor.replenish_program_cache(
1499            &account_loader,
1500            &account_set,
1501            &ProgramRuntimeEnvironments::default(),
1502            &mut program_cache_for_tx_batch,
1503            &mut ExecuteTimings::default(),
1504            false,
1505            true,
1506            true,
1507        );
1508    }
1509
1510    #[test]
1511    fn test_replenish_program_cache() {
1512        let mock_bank = MockBankCallback::default();
1513        let fork_graph = Arc::new(RwLock::new(TestForkGraph {}));
1514        let batch_processor =
1515            TransactionBatchProcessor::new(0, 0, Arc::downgrade(&fork_graph), None, None);
1516        let program_runtime_environments_for_execution =
1517            batch_processor.get_environments_for_epoch(0);
1518        let key = Pubkey::new_unique();
1519
1520        let mut account_data = AccountSharedData::default();
1521        account_data.set_owner(bpf_loader::id());
1522        mock_bank
1523            .account_shared_data
1524            .write()
1525            .unwrap()
1526            .insert(key, account_data);
1527        let account_loader = (&mock_bank).into();
1528
1529        let mut account_set = HashSet::new();
1530        account_set.insert(key);
1531        let mut loaded_missing = 0;
1532
1533        for limit_to_load_programs in [false, true] {
1534            let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::new(batch_processor.slot);
1535
1536            batch_processor.replenish_program_cache(
1537                &account_loader,
1538                &account_set,
1539                &program_runtime_environments_for_execution,
1540                &mut program_cache_for_tx_batch,
1541                &mut ExecuteTimings::default(),
1542                false,
1543                limit_to_load_programs,
1544                true,
1545            );
1546            assert!(!program_cache_for_tx_batch.hit_max_limit);
1547            if program_cache_for_tx_batch.loaded_missing {
1548                loaded_missing += 1;
1549            }
1550
1551            let program = program_cache_for_tx_batch.find(&key).unwrap();
1552            assert!(matches!(
1553                program.program,
1554                ProgramCacheEntryType::FailedVerification(_)
1555            ));
1556        }
1557        assert!(loaded_missing > 0);
1558    }
1559
1560    #[test]
1561    fn test_filter_executable_program_accounts() {
1562        let mock_bank = MockBankCallback::default();
1563        let key1 = Pubkey::new_unique();
1564        let owner1 = bpf_loader::id();
1565        let key2 = Pubkey::new_unique();
1566        let owner2 = loader_v4::id();
1567
1568        let mut data1 = AccountSharedData::default();
1569        data1.set_owner(owner1);
1570        data1.set_lamports(93);
1571        mock_bank
1572            .account_shared_data
1573            .write()
1574            .unwrap()
1575            .insert(key1, data1);
1576
1577        let mut data2 = AccountSharedData::default();
1578        data2.set_owner(owner2);
1579        data2.set_lamports(90);
1580        mock_bank
1581            .account_shared_data
1582            .write()
1583            .unwrap()
1584            .insert(key2, data2);
1585        let account_loader = (&mock_bank).into();
1586
1587        let message = Message {
1588            account_keys: vec![key1, key2],
1589            header: MessageHeader::default(),
1590            instructions: vec![CompiledInstruction {
1591                program_id_index: 0,
1592                accounts: vec![],
1593                data: vec![],
1594            }],
1595            recent_blockhash: Hash::default(),
1596        };
1597
1598        let sanitized_message = new_unchecked_sanitized_message(message);
1599
1600        let sanitized_transaction = SanitizedTransaction::new_for_tests(
1601            sanitized_message,
1602            vec![Signature::new_unique()],
1603            false,
1604        );
1605
1606        let batch_processor = TransactionBatchProcessor::<TestForkGraph>::default();
1607        let program_accounts_set = batch_processor.filter_executable_program_accounts(
1608            &account_loader,
1609            &mut ProgramCacheForTxBatch::default(),
1610            &sanitized_transaction,
1611        );
1612
1613        assert_eq!(program_accounts_set.len(), 2);
1614        assert!(program_accounts_set.contains(&key1));
1615        assert!(program_accounts_set.contains(&key2));
1616    }
1617
1618    #[test]
1619    fn test_filter_executable_program_accounts_no_errors() {
1620        let keypair1 = Keypair::new();
1621        let keypair2 = Keypair::new();
1622
1623        let non_program_pubkey1 = Pubkey::new_unique();
1624        let non_program_pubkey2 = Pubkey::new_unique();
1625        let program1_pubkey = bpf_loader::id();
1626        let program2_pubkey = loader_v4::id();
1627        let account1_pubkey = Pubkey::new_unique();
1628        let account2_pubkey = Pubkey::new_unique();
1629        let account3_pubkey = Pubkey::new_unique();
1630        let account4_pubkey = Pubkey::new_unique();
1631
1632        let account5_pubkey = Pubkey::new_unique();
1633
1634        let bank = MockBankCallback::default();
1635        bank.account_shared_data.write().unwrap().insert(
1636            non_program_pubkey1,
1637            AccountSharedData::new(1, 10, &account5_pubkey),
1638        );
1639        bank.account_shared_data.write().unwrap().insert(
1640            non_program_pubkey2,
1641            AccountSharedData::new(1, 10, &account5_pubkey),
1642        );
1643        bank.account_shared_data.write().unwrap().insert(
1644            program1_pubkey,
1645            AccountSharedData::new(40, 1, &account5_pubkey),
1646        );
1647        bank.account_shared_data.write().unwrap().insert(
1648            program2_pubkey,
1649            AccountSharedData::new(40, 1, &account5_pubkey),
1650        );
1651        bank.account_shared_data.write().unwrap().insert(
1652            account1_pubkey,
1653            AccountSharedData::new(1, 10, &non_program_pubkey1),
1654        );
1655        bank.account_shared_data.write().unwrap().insert(
1656            account2_pubkey,
1657            AccountSharedData::new(1, 10, &non_program_pubkey2),
1658        );
1659        bank.account_shared_data.write().unwrap().insert(
1660            account3_pubkey,
1661            AccountSharedData::new(40, 1, &program1_pubkey),
1662        );
1663        bank.account_shared_data.write().unwrap().insert(
1664            account4_pubkey,
1665            AccountSharedData::new(40, 1, &program2_pubkey),
1666        );
1667        let account_loader = (&bank).into();
1668
1669        let tx1 = Transaction::new_with_compiled_instructions(
1670            &[&keypair1],
1671            &[non_program_pubkey1],
1672            Hash::new_unique(),
1673            vec![account1_pubkey, account2_pubkey, account3_pubkey],
1674            vec![CompiledInstruction::new(1, &(), vec![0])],
1675        );
1676        let sanitized_tx1 = SanitizedTransaction::from_transaction_for_tests(tx1);
1677
1678        let tx2 = Transaction::new_with_compiled_instructions(
1679            &[&keypair2],
1680            &[non_program_pubkey2],
1681            Hash::new_unique(),
1682            vec![account4_pubkey, account3_pubkey, account2_pubkey],
1683            vec![CompiledInstruction::new(1, &(), vec![0])],
1684        );
1685        let sanitized_tx2 = SanitizedTransaction::from_transaction_for_tests(tx2);
1686
1687        let batch_processor = TransactionBatchProcessor::<TestForkGraph>::default();
1688
1689        let tx1_programs = batch_processor.filter_executable_program_accounts(
1690            &account_loader,
1691            &mut ProgramCacheForTxBatch::default(),
1692            &sanitized_tx1,
1693        );
1694
1695        assert_eq!(tx1_programs.len(), 1);
1696        assert!(
1697            tx1_programs.contains(&account3_pubkey),
1698            "failed to find the program account",
1699        );
1700
1701        let tx2_programs = batch_processor.filter_executable_program_accounts(
1702            &account_loader,
1703            &mut ProgramCacheForTxBatch::default(),
1704            &sanitized_tx2,
1705        );
1706
1707        assert_eq!(tx2_programs.len(), 2);
1708        assert!(
1709            tx2_programs.contains(&account3_pubkey),
1710            "failed to find the program account",
1711        );
1712        assert!(
1713            tx2_programs.contains(&account4_pubkey),
1714            "failed to find the program account",
1715        );
1716    }
1717
1718    #[test]
1719    #[allow(deprecated)]
1720    fn test_sysvar_cache_initialization1() {
1721        let mock_bank = MockBankCallback::default();
1722
1723        let clock = Clock {
1724            slot: 1,
1725            epoch_start_timestamp: 2,
1726            epoch: 3,
1727            leader_schedule_epoch: 4,
1728            unix_timestamp: 5,
1729        };
1730        let clock_account = create_account_shared_data_for_test(&clock);
1731        mock_bank
1732            .account_shared_data
1733            .write()
1734            .unwrap()
1735            .insert(sysvar::clock::id(), clock_account);
1736
1737        let epoch_schedule = EpochSchedule::custom(64, 2, true);
1738        let epoch_schedule_account = create_account_shared_data_for_test(&epoch_schedule);
1739        mock_bank
1740            .account_shared_data
1741            .write()
1742            .unwrap()
1743            .insert(sysvar::epoch_schedule::id(), epoch_schedule_account);
1744
1745        let fees = Fees {
1746            fee_calculator: FeeCalculator {
1747                lamports_per_signature: 123,
1748            },
1749        };
1750        let fees_account = create_account_shared_data_for_test(&fees);
1751        mock_bank
1752            .account_shared_data
1753            .write()
1754            .unwrap()
1755            .insert(sysvar::fees::id(), fees_account);
1756
1757        let rent = Rent::with_slots_per_epoch(2048);
1758        let rent_account = create_account_shared_data_for_test(&rent);
1759        mock_bank
1760            .account_shared_data
1761            .write()
1762            .unwrap()
1763            .insert(sysvar::rent::id(), rent_account);
1764
1765        let transaction_processor = TransactionBatchProcessor::<TestForkGraph>::default();
1766        transaction_processor.fill_missing_sysvar_cache_entries(&mock_bank);
1767
1768        let sysvar_cache = transaction_processor.sysvar_cache.read().unwrap();
1769        let cached_clock = sysvar_cache.get_clock();
1770        let cached_epoch_schedule = sysvar_cache.get_epoch_schedule();
1771        let cached_fees = sysvar_cache.get_fees();
1772        let cached_rent = sysvar_cache.get_rent();
1773
1774        assert_eq!(
1775            cached_clock.expect("clock sysvar missing in cache"),
1776            clock.into()
1777        );
1778        assert_eq!(
1779            cached_epoch_schedule.expect("epoch_schedule sysvar missing in cache"),
1780            epoch_schedule.into()
1781        );
1782        assert_eq!(
1783            cached_fees.expect("fees sysvar missing in cache"),
1784            fees.into()
1785        );
1786        assert_eq!(
1787            cached_rent.expect("rent sysvar missing in cache"),
1788            rent.into()
1789        );
1790        assert!(sysvar_cache.get_slot_hashes().is_err());
1791        assert!(sysvar_cache.get_epoch_rewards().is_err());
1792    }
1793
1794    #[test]
1795    #[allow(deprecated)]
1796    fn test_reset_and_fill_sysvar_cache() {
1797        let mock_bank = MockBankCallback::default();
1798
1799        let clock = Clock {
1800            slot: 1,
1801            epoch_start_timestamp: 2,
1802            epoch: 3,
1803            leader_schedule_epoch: 4,
1804            unix_timestamp: 5,
1805        };
1806        let clock_account = create_account_shared_data_for_test(&clock);
1807        mock_bank
1808            .account_shared_data
1809            .write()
1810            .unwrap()
1811            .insert(sysvar::clock::id(), clock_account);
1812
1813        let epoch_schedule = EpochSchedule::custom(64, 2, true);
1814        let epoch_schedule_account = create_account_shared_data_for_test(&epoch_schedule);
1815        mock_bank
1816            .account_shared_data
1817            .write()
1818            .unwrap()
1819            .insert(sysvar::epoch_schedule::id(), epoch_schedule_account);
1820
1821        let fees = Fees {
1822            fee_calculator: FeeCalculator {
1823                lamports_per_signature: 123,
1824            },
1825        };
1826        let fees_account = create_account_shared_data_for_test(&fees);
1827        mock_bank
1828            .account_shared_data
1829            .write()
1830            .unwrap()
1831            .insert(sysvar::fees::id(), fees_account);
1832
1833        let rent = Rent::with_slots_per_epoch(2048);
1834        let rent_account = create_account_shared_data_for_test(&rent);
1835        mock_bank
1836            .account_shared_data
1837            .write()
1838            .unwrap()
1839            .insert(sysvar::rent::id(), rent_account);
1840
1841        let transaction_processor = TransactionBatchProcessor::<TestForkGraph>::default();
1842        // Fill the sysvar cache
1843        transaction_processor.fill_missing_sysvar_cache_entries(&mock_bank);
1844        // Reset the sysvar cache
1845        transaction_processor.reset_sysvar_cache();
1846
1847        {
1848            let sysvar_cache = transaction_processor.sysvar_cache.read().unwrap();
1849            // Test that sysvar cache is empty and none of the values are found
1850            assert!(sysvar_cache.get_clock().is_err());
1851            assert!(sysvar_cache.get_epoch_schedule().is_err());
1852            assert!(sysvar_cache.get_fees().is_err());
1853            assert!(sysvar_cache.get_epoch_rewards().is_err());
1854            assert!(sysvar_cache.get_rent().is_err());
1855            assert!(sysvar_cache.get_epoch_rewards().is_err());
1856        }
1857
1858        // Refill the cache and test the values are available.
1859        transaction_processor.fill_missing_sysvar_cache_entries(&mock_bank);
1860
1861        let sysvar_cache = transaction_processor.sysvar_cache.read().unwrap();
1862        let cached_clock = sysvar_cache.get_clock();
1863        let cached_epoch_schedule = sysvar_cache.get_epoch_schedule();
1864        let cached_fees = sysvar_cache.get_fees();
1865        let cached_rent = sysvar_cache.get_rent();
1866
1867        assert_eq!(
1868            cached_clock.expect("clock sysvar missing in cache"),
1869            clock.into()
1870        );
1871        assert_eq!(
1872            cached_epoch_schedule.expect("epoch_schedule sysvar missing in cache"),
1873            epoch_schedule.into()
1874        );
1875        assert_eq!(
1876            cached_fees.expect("fees sysvar missing in cache"),
1877            fees.into()
1878        );
1879        assert_eq!(
1880            cached_rent.expect("rent sysvar missing in cache"),
1881            rent.into()
1882        );
1883        assert!(sysvar_cache.get_slot_hashes().is_err());
1884        assert!(sysvar_cache.get_epoch_rewards().is_err());
1885    }
1886
1887    #[test]
1888    fn test_add_builtin() {
1889        let fork_graph = Arc::new(RwLock::new(TestForkGraph {}));
1890        let batch_processor =
1891            TransactionBatchProcessor::new(0, 0, Arc::downgrade(&fork_graph), None, None);
1892
1893        let key = Pubkey::new_unique();
1894        let name = "a_builtin_name";
1895        let program = ProgramCacheEntry::new_builtin(
1896            0,
1897            name.len(),
1898            |_invoke_context, _param0, _param1, _param2, _param3, _param4| {},
1899        );
1900
1901        batch_processor.add_builtin(key, program);
1902
1903        let mut loaded_programs_for_tx_batch = ProgramCacheForTxBatch::new(0);
1904        let program_runtime_environments =
1905            batch_processor.get_environments_for_epoch(batch_processor.epoch);
1906        batch_processor
1907            .global_program_cache
1908            .write()
1909            .unwrap()
1910            .extract(
1911                &mut vec![(key, ProgramCacheMatchCriteria::NoCriteria)],
1912                &mut loaded_programs_for_tx_batch,
1913                &program_runtime_environments,
1914                true,
1915                true,
1916            );
1917        let entry = loaded_programs_for_tx_batch.find(&key).unwrap();
1918
1919        // Repeating code because ProgramCacheEntry does not implement clone.
1920        let program = ProgramCacheEntry::new_builtin(
1921            0,
1922            name.len(),
1923            |_invoke_context, _param0, _param1, _param2, _param3, _param4| {},
1924        );
1925        assert_eq!(entry, Arc::new(program));
1926    }
1927
1928    #[test_case(false; "informal_loaded_size")]
1929    #[test_case(true; "simd186_loaded_size")]
1930    fn test_validate_transaction_fee_payer_exact_balance(
1931        formalize_loaded_transaction_data_size: bool,
1932    ) {
1933        let lamports_per_signature = 5000;
1934        let message = new_unchecked_sanitized_message(Message::new_with_blockhash(
1935            &[
1936                ComputeBudgetInstruction::set_compute_unit_limit(2000u32),
1937                ComputeBudgetInstruction::set_compute_unit_price(1_000_000_000),
1938            ],
1939            Some(&Pubkey::new_unique()),
1940            &Hash::new_unique(),
1941        ));
1942        let fee_payer_address = message.fee_payer();
1943        let current_epoch = 42;
1944        let rent = Rent::default();
1945        let min_balance = rent.minimum_balance(nonce::state::State::size());
1946        let transaction_fee = lamports_per_signature;
1947        let priority_fee = 2_000_000u64;
1948        let starting_balance = transaction_fee + priority_fee;
1949        assert!(
1950            starting_balance > min_balance,
1951            "we're testing that a rent exempt fee payer can be fully drained, so ensure that the \
1952             starting balance is more than the min balance"
1953        );
1954
1955        let fee_payer_rent_epoch = current_epoch;
1956        let fee_payer_account = AccountSharedData::new_rent_epoch(
1957            starting_balance,
1958            0,
1959            &Pubkey::default(),
1960            fee_payer_rent_epoch,
1961        );
1962        let mut mock_accounts = HashMap::new();
1963        mock_accounts.insert(*fee_payer_address, fee_payer_account.clone());
1964        let mut mock_bank = MockBankCallback {
1965            account_shared_data: Arc::new(RwLock::new(mock_accounts)),
1966            ..Default::default()
1967        };
1968        mock_bank.feature_set.formalize_loaded_transaction_data_size =
1969            formalize_loaded_transaction_data_size;
1970        let mut account_loader = (&mock_bank).into();
1971
1972        let mut error_counters = TransactionErrorMetrics::default();
1973        let compute_budget_and_limits = SVMTransactionExecutionAndFeeBudgetLimits {
1974            budget: SVMTransactionExecutionBudget {
1975                compute_unit_limit: 2000,
1976                ..SVMTransactionExecutionBudget::default()
1977            },
1978            fee_details: FeeDetails::new(transaction_fee, priority_fee),
1979            ..SVMTransactionExecutionAndFeeBudgetLimits::default()
1980        };
1981        let result =
1982            TransactionBatchProcessor::<TestForkGraph>::validate_transaction_nonce_and_fee_payer(
1983                &mut account_loader,
1984                &message,
1985                CheckedTransactionDetails::new(None, compute_budget_and_limits),
1986                &Hash::default(),
1987                lamports_per_signature,
1988                &rent,
1989                &mut error_counters,
1990            );
1991
1992        let post_validation_fee_payer_account = {
1993            let mut account = fee_payer_account.clone();
1994            account.set_rent_epoch(RENT_EXEMPT_RENT_EPOCH);
1995            account.set_lamports(0);
1996            account
1997        };
1998
1999        let base_account_size = if formalize_loaded_transaction_data_size {
2000            TRANSACTION_ACCOUNT_BASE_SIZE
2001        } else {
2002            0
2003        };
2004
2005        assert_eq!(
2006            result,
2007            Ok(ValidatedTransactionDetails {
2008                rollback_accounts: RollbackAccounts::new(
2009                    None, // nonce
2010                    *fee_payer_address,
2011                    post_validation_fee_payer_account.clone(),
2012                    fee_payer_rent_epoch
2013                ),
2014                compute_budget: compute_budget_and_limits.budget,
2015                loaded_accounts_bytes_limit: compute_budget_and_limits
2016                    .loaded_accounts_data_size_limit,
2017                fee_details: FeeDetails::new(transaction_fee, priority_fee),
2018                loaded_fee_payer_account: LoadedTransactionAccount {
2019                    loaded_size: base_account_size + fee_payer_account.data().len(),
2020                    account: post_validation_fee_payer_account,
2021                },
2022            })
2023        );
2024    }
2025
2026    #[test_case(false; "informal_loaded_size")]
2027    #[test_case(true; "simd186_loaded_size")]
2028    fn test_validate_transaction_fee_payer_rent_paying(
2029        formalize_loaded_transaction_data_size: bool,
2030    ) {
2031        let lamports_per_signature = 5000;
2032        let message = new_unchecked_sanitized_message(Message::new_with_blockhash(
2033            &[],
2034            Some(&Pubkey::new_unique()),
2035            &Hash::new_unique(),
2036        ));
2037        let fee_payer_address = message.fee_payer();
2038        let rent = Rent {
2039            lamports_per_byte_year: 1_000_000,
2040            ..Default::default()
2041        };
2042        let min_balance = rent.minimum_balance(0);
2043        let transaction_fee = lamports_per_signature;
2044        let starting_balance = min_balance - 1;
2045        let fee_payer_account = AccountSharedData::new(starting_balance, 0, &Pubkey::default());
2046
2047        let mut mock_accounts = HashMap::new();
2048        mock_accounts.insert(*fee_payer_address, fee_payer_account.clone());
2049        let mut mock_bank = MockBankCallback {
2050            account_shared_data: Arc::new(RwLock::new(mock_accounts)),
2051            ..Default::default()
2052        };
2053        mock_bank.feature_set.formalize_loaded_transaction_data_size =
2054            formalize_loaded_transaction_data_size;
2055        let mut account_loader = (&mock_bank).into();
2056
2057        let mut error_counters = TransactionErrorMetrics::default();
2058        let compute_budget_and_limits = SVMTransactionExecutionAndFeeBudgetLimits::with_fee(
2059            MockBankCallback::calculate_fee_details(&message, lamports_per_signature, 0),
2060        );
2061        let result =
2062            TransactionBatchProcessor::<TestForkGraph>::validate_transaction_nonce_and_fee_payer(
2063                &mut account_loader,
2064                &message,
2065                CheckedTransactionDetails::new(None, compute_budget_and_limits),
2066                &Hash::default(),
2067                lamports_per_signature,
2068                &rent,
2069                &mut error_counters,
2070            );
2071
2072        let post_validation_fee_payer_account = {
2073            let mut account = fee_payer_account.clone();
2074            account.set_lamports(starting_balance - transaction_fee);
2075            account
2076        };
2077
2078        let base_account_size = if formalize_loaded_transaction_data_size {
2079            TRANSACTION_ACCOUNT_BASE_SIZE
2080        } else {
2081            0
2082        };
2083
2084        assert_eq!(
2085            result,
2086            Ok(ValidatedTransactionDetails {
2087                rollback_accounts: RollbackAccounts::new(
2088                    None, // nonce
2089                    *fee_payer_address,
2090                    post_validation_fee_payer_account.clone(),
2091                    0, // rent epoch
2092                ),
2093                compute_budget: compute_budget_and_limits.budget,
2094                loaded_accounts_bytes_limit: compute_budget_and_limits
2095                    .loaded_accounts_data_size_limit,
2096                fee_details: FeeDetails::new(transaction_fee, 0),
2097                loaded_fee_payer_account: LoadedTransactionAccount {
2098                    loaded_size: base_account_size + fee_payer_account.data().len(),
2099                    account: post_validation_fee_payer_account,
2100                }
2101            })
2102        );
2103    }
2104
2105    #[test]
2106    fn test_validate_transaction_fee_payer_not_found() {
2107        let lamports_per_signature = 5000;
2108        let message =
2109            new_unchecked_sanitized_message(Message::new(&[], Some(&Pubkey::new_unique())));
2110
2111        let mock_bank = MockBankCallback::default();
2112        let mut account_loader = (&mock_bank).into();
2113        let mut error_counters = TransactionErrorMetrics::default();
2114        let result =
2115            TransactionBatchProcessor::<TestForkGraph>::validate_transaction_nonce_and_fee_payer(
2116                &mut account_loader,
2117                &message,
2118                CheckedTransactionDetails::new(
2119                    None,
2120                    SVMTransactionExecutionAndFeeBudgetLimits::default(),
2121                ),
2122                &Hash::default(),
2123                lamports_per_signature,
2124                &Rent::default(),
2125                &mut error_counters,
2126            );
2127
2128        assert_eq!(error_counters.account_not_found.0, 1);
2129        assert_eq!(result, Err(TransactionError::AccountNotFound));
2130    }
2131
2132    #[test]
2133    fn test_validate_transaction_fee_payer_insufficient_funds() {
2134        let lamports_per_signature = 5000;
2135        let message =
2136            new_unchecked_sanitized_message(Message::new(&[], Some(&Pubkey::new_unique())));
2137        let fee_payer_address = message.fee_payer();
2138        let fee_payer_account = AccountSharedData::new(1, 0, &Pubkey::default());
2139        let mut mock_accounts = HashMap::new();
2140        mock_accounts.insert(*fee_payer_address, fee_payer_account.clone());
2141        let mock_bank = MockBankCallback {
2142            account_shared_data: Arc::new(RwLock::new(mock_accounts)),
2143            ..Default::default()
2144        };
2145        let mut account_loader = (&mock_bank).into();
2146
2147        let mut error_counters = TransactionErrorMetrics::default();
2148        let result =
2149            TransactionBatchProcessor::<TestForkGraph>::validate_transaction_nonce_and_fee_payer(
2150                &mut account_loader,
2151                &message,
2152                CheckedTransactionDetails::new(
2153                    None,
2154                    SVMTransactionExecutionAndFeeBudgetLimits::with_fee(
2155                        MockBankCallback::calculate_fee_details(
2156                            &message,
2157                            lamports_per_signature,
2158                            0,
2159                        ),
2160                    ),
2161                ),
2162                &Hash::default(),
2163                lamports_per_signature,
2164                &Rent::default(),
2165                &mut error_counters,
2166            );
2167
2168        assert_eq!(error_counters.insufficient_funds.0, 1);
2169        assert_eq!(result, Err(TransactionError::InsufficientFundsForFee));
2170    }
2171
2172    #[test]
2173    fn test_validate_transaction_fee_payer_insufficient_rent() {
2174        let lamports_per_signature = 5000;
2175        let message =
2176            new_unchecked_sanitized_message(Message::new(&[], Some(&Pubkey::new_unique())));
2177        let fee_payer_address = message.fee_payer();
2178        let transaction_fee = lamports_per_signature;
2179        let rent = Rent::default();
2180        let min_balance = rent.minimum_balance(0);
2181        let starting_balance = min_balance + transaction_fee - 1;
2182        let fee_payer_account = AccountSharedData::new(starting_balance, 0, &Pubkey::default());
2183        let mut mock_accounts = HashMap::new();
2184        mock_accounts.insert(*fee_payer_address, fee_payer_account.clone());
2185        let mock_bank = MockBankCallback {
2186            account_shared_data: Arc::new(RwLock::new(mock_accounts)),
2187            ..Default::default()
2188        };
2189        let mut account_loader = (&mock_bank).into();
2190
2191        let mut error_counters = TransactionErrorMetrics::default();
2192        let result =
2193            TransactionBatchProcessor::<TestForkGraph>::validate_transaction_nonce_and_fee_payer(
2194                &mut account_loader,
2195                &message,
2196                CheckedTransactionDetails::new(
2197                    None,
2198                    SVMTransactionExecutionAndFeeBudgetLimits::with_fee(
2199                        MockBankCallback::calculate_fee_details(
2200                            &message,
2201                            lamports_per_signature,
2202                            0,
2203                        ),
2204                    ),
2205                ),
2206                &Hash::default(),
2207                lamports_per_signature,
2208                &rent,
2209                &mut error_counters,
2210            );
2211
2212        assert_eq!(
2213            result,
2214            Err(TransactionError::InsufficientFundsForRent { account_index: 0 })
2215        );
2216    }
2217
2218    #[test]
2219    fn test_validate_transaction_fee_payer_invalid() {
2220        let lamports_per_signature = 5000;
2221        let message =
2222            new_unchecked_sanitized_message(Message::new(&[], Some(&Pubkey::new_unique())));
2223        let fee_payer_address = message.fee_payer();
2224        let fee_payer_account = AccountSharedData::new(1_000_000, 0, &Pubkey::new_unique());
2225        let mut mock_accounts = HashMap::new();
2226        mock_accounts.insert(*fee_payer_address, fee_payer_account.clone());
2227        let mock_bank = MockBankCallback {
2228            account_shared_data: Arc::new(RwLock::new(mock_accounts)),
2229            ..Default::default()
2230        };
2231        let mut account_loader = (&mock_bank).into();
2232
2233        let mut error_counters = TransactionErrorMetrics::default();
2234        let result =
2235            TransactionBatchProcessor::<TestForkGraph>::validate_transaction_nonce_and_fee_payer(
2236                &mut account_loader,
2237                &message,
2238                CheckedTransactionDetails::new(
2239                    None,
2240                    SVMTransactionExecutionAndFeeBudgetLimits::with_fee(
2241                        MockBankCallback::calculate_fee_details(
2242                            &message,
2243                            lamports_per_signature,
2244                            0,
2245                        ),
2246                    ),
2247                ),
2248                &Hash::default(),
2249                lamports_per_signature,
2250                &Rent::default(),
2251                &mut error_counters,
2252            );
2253
2254        assert_eq!(error_counters.invalid_account_for_fee.0, 1);
2255        assert_eq!(result, Err(TransactionError::InvalidAccountForFee));
2256    }
2257
2258    #[derive(Debug, PartialEq, Eq)]
2259    enum ValidateNonce {
2260        Success,
2261        NoAccount,
2262        BadOwner,
2263        BlockhashMismatch,
2264        AlreadyUsed,
2265        BadSigner,
2266    }
2267
2268    #[test_case(ValidateNonce::Success)]
2269    #[test_case(ValidateNonce::NoAccount)]
2270    #[test_case(ValidateNonce::BadOwner)]
2271    #[test_case(ValidateNonce::BlockhashMismatch)]
2272    #[test_case(ValidateNonce::AlreadyUsed)]
2273    #[test_case(ValidateNonce::BadSigner)]
2274    fn test_validate_transaction_nonce(case: ValidateNonce) {
2275        let lamports_per_signature = 5000;
2276        let previous_durable_nonce = DurableNonce::from_blockhash(&Hash::new_unique());
2277        let nonce_address = Pubkey::new_unique();
2278        let authority_address = Pubkey::new_unique();
2279
2280        let message_blockhash = if case == ValidateNonce::BlockhashMismatch {
2281            Hash::new_unique()
2282        } else {
2283            *previous_durable_nonce.as_hash()
2284        };
2285
2286        let message_authority = if case == ValidateNonce::BadSigner {
2287            Pubkey::new_unique()
2288        } else {
2289            authority_address
2290        };
2291
2292        let message = new_unchecked_sanitized_message(Message::new_with_blockhash(
2293            &[system_instruction::advance_nonce_account(
2294                &nonce_address,
2295                &message_authority,
2296            )],
2297            Some(&Pubkey::new_unique()),
2298            &message_blockhash,
2299        ));
2300
2301        let environment_blockhash = Hash::new_unique();
2302        let next_durable_nonce = DurableNonce::from_blockhash(&environment_blockhash);
2303
2304        let stored_durable_nonce = if case == ValidateNonce::AlreadyUsed {
2305            next_durable_nonce
2306        } else {
2307            previous_durable_nonce
2308        };
2309
2310        let nonce_versions = nonce::versions::Versions::new(nonce::state::State::Initialized(
2311            nonce::state::Data::new(
2312                authority_address,
2313                stored_durable_nonce,
2314                lamports_per_signature,
2315            ),
2316        ));
2317
2318        let nonce_owner = if case == ValidateNonce::BadOwner {
2319            Pubkey::new_unique()
2320        } else {
2321            system_program::id()
2322        };
2323
2324        let nonce_account = AccountSharedData::new_data(1, &nonce_versions, &nonce_owner).unwrap();
2325
2326        let mut mock_accounts = HashMap::new();
2327
2328        if case != ValidateNonce::NoAccount {
2329            mock_accounts.insert(nonce_address, nonce_account.clone());
2330        }
2331
2332        let mock_bank = MockBankCallback {
2333            account_shared_data: Arc::new(RwLock::new(mock_accounts)),
2334            ..Default::default()
2335        };
2336        let mut account_loader = (&mock_bank).into();
2337
2338        let mut error_counters = TransactionErrorMetrics::default();
2339        let result = TransactionBatchProcessor::<TestForkGraph>::validate_transaction_nonce(
2340            &mut account_loader,
2341            &message,
2342            &nonce_address,
2343            &next_durable_nonce,
2344            lamports_per_signature,
2345            &mut error_counters,
2346        );
2347
2348        match case {
2349            ValidateNonce::Success => {
2350                let mut future_nonce_info = NonceInfo::new(nonce_address, nonce_account);
2351                future_nonce_info
2352                    .try_advance_nonce(next_durable_nonce, lamports_per_signature)
2353                    .unwrap();
2354
2355                assert_eq!(result, Ok(future_nonce_info));
2356            }
2357            ValidateNonce::NoAccount => {
2358                assert_eq!(error_counters.account_not_found.0, 1);
2359                assert_eq!(result, Err(TransactionError::AccountNotFound));
2360            }
2361            _ => {
2362                assert_eq!(error_counters.blockhash_not_found.0, 1);
2363                assert_eq!(result, Err(TransactionError::BlockhashNotFound));
2364            }
2365        }
2366    }
2367
2368    #[test_case(false; "informal_loaded_size")]
2369    #[test_case(true; "simd186_loaded_size")]
2370    fn test_validate_transaction_fee_payer_is_nonce(formalize_loaded_transaction_data_size: bool) {
2371        let lamports_per_signature = 5000;
2372        let rent = Rent::default();
2373        let compute_unit_limit = 1000u64;
2374        let previous_durable_nonce = DurableNonce::from_blockhash(&Hash::new_unique());
2375        let fee_payer_address = &Pubkey::new_unique();
2376        let message = new_unchecked_sanitized_message(Message::new_with_blockhash(
2377            &[
2378                system_instruction::advance_nonce_account(fee_payer_address, fee_payer_address),
2379                ComputeBudgetInstruction::set_compute_unit_limit(compute_unit_limit as u32),
2380                ComputeBudgetInstruction::set_compute_unit_price(1_000_000),
2381            ],
2382            Some(fee_payer_address),
2383            previous_durable_nonce.as_hash(),
2384        ));
2385        let transaction_fee = lamports_per_signature;
2386        let compute_budget_and_limits = SVMTransactionExecutionAndFeeBudgetLimits {
2387            fee_details: FeeDetails::new(transaction_fee, compute_unit_limit),
2388            ..SVMTransactionExecutionAndFeeBudgetLimits::default()
2389        };
2390        let min_balance = Rent::default().minimum_balance(nonce::state::State::size());
2391        let priority_fee = compute_unit_limit;
2392
2393        let nonce_versions = nonce::versions::Versions::new(nonce::state::State::Initialized(
2394            nonce::state::Data::new(
2395                *fee_payer_address,
2396                previous_durable_nonce,
2397                lamports_per_signature,
2398            ),
2399        ));
2400
2401        let environment_blockhash = Hash::new_unique();
2402        let next_durable_nonce = DurableNonce::from_blockhash(&environment_blockhash);
2403
2404        // Sufficient Fees
2405        {
2406            let fee_payer_account = AccountSharedData::new_data(
2407                min_balance + transaction_fee + priority_fee,
2408                &nonce_versions,
2409                &system_program::id(),
2410            )
2411            .unwrap();
2412
2413            let mut future_nonce = NonceInfo::new(*fee_payer_address, fee_payer_account.clone());
2414            future_nonce
2415                .try_advance_nonce(next_durable_nonce, lamports_per_signature)
2416                .unwrap();
2417
2418            let mut mock_accounts = HashMap::new();
2419            mock_accounts.insert(*fee_payer_address, fee_payer_account.clone());
2420            let mut mock_bank = MockBankCallback {
2421                account_shared_data: Arc::new(RwLock::new(mock_accounts)),
2422                ..Default::default()
2423            };
2424            mock_bank.feature_set.formalize_loaded_transaction_data_size =
2425                formalize_loaded_transaction_data_size;
2426            let mut account_loader = (&mock_bank).into();
2427
2428            let mut error_counters = TransactionErrorMetrics::default();
2429
2430            let tx_details =
2431                CheckedTransactionDetails::new(Some(*fee_payer_address), compute_budget_and_limits);
2432
2433            let result = TransactionBatchProcessor::<TestForkGraph>::validate_transaction_nonce_and_fee_payer(
2434                &mut account_loader,
2435                &message,
2436                tx_details,
2437                &environment_blockhash,
2438                lamports_per_signature,
2439                &rent,
2440                &mut error_counters,
2441            );
2442
2443            let post_validation_fee_payer_account = {
2444                let mut account = fee_payer_account.clone();
2445                account.set_rent_epoch(RENT_EXEMPT_RENT_EPOCH);
2446                account.set_lamports(min_balance);
2447                account
2448            };
2449
2450            let base_account_size = if formalize_loaded_transaction_data_size {
2451                TRANSACTION_ACCOUNT_BASE_SIZE
2452            } else {
2453                0
2454            };
2455
2456            assert_eq!(
2457                result,
2458                Ok(ValidatedTransactionDetails {
2459                    rollback_accounts: RollbackAccounts::new(
2460                        Some(future_nonce),
2461                        *fee_payer_address,
2462                        post_validation_fee_payer_account.clone(),
2463                        0, // fee_payer_rent_epoch
2464                    ),
2465                    compute_budget: compute_budget_and_limits.budget,
2466                    loaded_accounts_bytes_limit: compute_budget_and_limits
2467                        .loaded_accounts_data_size_limit,
2468                    fee_details: FeeDetails::new(transaction_fee, priority_fee),
2469                    loaded_fee_payer_account: LoadedTransactionAccount {
2470                        loaded_size: base_account_size + fee_payer_account.data().len(),
2471                        account: post_validation_fee_payer_account,
2472                    }
2473                })
2474            );
2475        }
2476
2477        // Insufficient Fees
2478        {
2479            let fee_payer_account = AccountSharedData::new_data(
2480                transaction_fee + priority_fee, // no min_balance this time
2481                &nonce_versions,
2482                &system_program::id(),
2483            )
2484            .unwrap();
2485
2486            let mut mock_accounts = HashMap::new();
2487            mock_accounts.insert(*fee_payer_address, fee_payer_account.clone());
2488            let mock_bank = MockBankCallback {
2489                account_shared_data: Arc::new(RwLock::new(mock_accounts)),
2490                ..Default::default()
2491            };
2492            let mut account_loader = (&mock_bank).into();
2493
2494            let mut error_counters = TransactionErrorMetrics::default();
2495
2496            let tx_details =
2497                CheckedTransactionDetails::new(Some(*fee_payer_address), compute_budget_and_limits);
2498
2499            let result = TransactionBatchProcessor::<TestForkGraph>::validate_transaction_nonce_and_fee_payer(
2500                &mut account_loader,
2501                &message,
2502                tx_details,
2503                &environment_blockhash,
2504                lamports_per_signature,
2505                &rent,
2506                &mut error_counters,
2507            );
2508
2509            assert_eq!(error_counters.insufficient_funds.0, 1);
2510            assert_eq!(result, Err(TransactionError::InsufficientFundsForFee));
2511        }
2512    }
2513
2514    // Ensure `TransactionProcessingCallback::inspect_account()` is called when
2515    // validating the fee payer, since that's when the fee payer account is loaded.
2516    #[test]
2517    fn test_inspect_account_fee_payer() {
2518        let lamports_per_signature = 5000;
2519        let fee_payer_address = Pubkey::new_unique();
2520        let fee_payer_account = AccountSharedData::new_rent_epoch(
2521            123_000_000_000,
2522            0,
2523            &Pubkey::default(),
2524            RENT_EXEMPT_RENT_EPOCH,
2525        );
2526        let mock_bank = MockBankCallback::default();
2527        mock_bank
2528            .account_shared_data
2529            .write()
2530            .unwrap()
2531            .insert(fee_payer_address, fee_payer_account.clone());
2532        let mut account_loader = (&mock_bank).into();
2533
2534        let message = new_unchecked_sanitized_message(Message::new_with_blockhash(
2535            &[
2536                ComputeBudgetInstruction::set_compute_unit_limit(2000u32),
2537                ComputeBudgetInstruction::set_compute_unit_price(1_000_000_000),
2538            ],
2539            Some(&fee_payer_address),
2540            &Hash::new_unique(),
2541        ));
2542        TransactionBatchProcessor::<TestForkGraph>::validate_transaction_nonce_and_fee_payer(
2543            &mut account_loader,
2544            &message,
2545            CheckedTransactionDetails::new(
2546                None,
2547                SVMTransactionExecutionAndFeeBudgetLimits::with_fee(
2548                    MockBankCallback::calculate_fee_details(&message, 5000, 0),
2549                ),
2550            ),
2551            &Hash::default(),
2552            lamports_per_signature,
2553            &Rent::default(),
2554            &mut TransactionErrorMetrics::default(),
2555        )
2556        .unwrap();
2557
2558        // ensure the fee payer is an inspected account
2559        let actual_inspected_accounts: Vec<_> = mock_bank
2560            .inspected_accounts
2561            .read()
2562            .unwrap()
2563            .iter()
2564            .map(|(k, v)| (*k, v.clone()))
2565            .collect();
2566        assert_eq!(
2567            actual_inspected_accounts.as_slice(),
2568            &[(fee_payer_address, vec![(Some(fee_payer_account), true)])],
2569        );
2570    }
2571}