Skip to main content

solana_svm/
transaction_processor.rs

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