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