Skip to main content

solana_svm/
transaction_processor.rs

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