solana_svm/
transaction_processor.rs

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