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_instruction_error::InstructionError,
29    solana_message::{
30        compiled_instruction::CompiledInstruction,
31        inner_instruction::{InnerInstruction, InnerInstructionsList},
32    },
33    solana_nonce::{
34        NONCED_TX_MARKER_IX_INDEX,
35        state::{DurableNonce, State as NonceState},
36        versions::Versions as NonceVersions,
37    },
38    solana_nonce_account::verify_nonce_account,
39    solana_program_runtime::{
40        execution_budget::{
41            SVMTransactionExecutionAndFeeBudgetLimits, SVMTransactionExecutionCost,
42        },
43        invoke_context::{EnvironmentConfig, InvokeContext},
44        loaded_programs::{
45            EpochBoundaryPreparation, ForkGraph, Percent, ProgramCache, ProgramCacheForTxBatch,
46            ProgramRuntimeEnvironment, ProgramRuntimeEnvironments, 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::{
63        DropOnBailOut,
64        transaction::{ComputeMeter, ExecutionRecord, TransactionContext},
65    },
66    solana_transaction_error::{TransactionError, TransactionResult},
67    std::{
68        collections::HashSet,
69        fmt::{Debug, Formatter},
70        rc::Rc,
71    },
72};
73#[cfg(feature = "dev-context-only-utils")]
74use {
75    qualifier_attr::{field_qualifiers, qualifiers},
76    std::sync::Weak,
77};
78
79/// A list of log messages emitted during a transaction
80pub type TransactionLogMessages = Vec<String>;
81
82/// The output of the transaction batch processor's
83/// `load_and_execute_sanitized_transactions` method.
84pub struct LoadAndExecuteSanitizedTransactionsOutput {
85    /// Error metrics for transactions that were processed.
86    pub error_metrics: TransactionErrorMetrics,
87    /// Timings for transaction batch execution.
88    pub execute_timings: ExecuteTimings,
89    /// Vector of results indicating whether a transaction was processed or
90    /// could not be processed. Note processed transactions can still have a
91    /// failure result meaning that the transaction will be rolled back.
92    pub processing_results: Vec<TransactionProcessingResult>,
93    /// Balances accumulated for TransactionStatusSender when
94    /// transaction balance recording is enabled.
95    pub balance_collector: Option<BalanceCollector>,
96}
97
98/// Configuration of the recording capabilities for transaction execution
99#[derive(Copy, Clone, Default)]
100pub struct ExecutionRecordingConfig {
101    pub enable_cpi_recording: bool,
102    pub enable_log_recording: bool,
103    pub enable_return_data_recording: bool,
104    pub enable_transaction_balance_recording: bool,
105}
106
107impl ExecutionRecordingConfig {
108    pub fn new_single_setting(option: bool) -> Self {
109        ExecutionRecordingConfig {
110            enable_return_data_recording: option,
111            enable_log_recording: option,
112            enable_cpi_recording: option,
113            enable_transaction_balance_recording: option,
114        }
115    }
116}
117
118/// Configurations for processing transactions.
119#[derive(Default)]
120pub struct TransactionProcessingConfig<'a> {
121    /// Encapsulates overridden accounts, typically used for transaction
122    /// simulation.
123    pub account_overrides: Option<&'a AccountOverrides>,
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    /// Drops transactions which bailed out in the program runtime.
152    ///
153    /// This is a leader-side filtering policy. It must not be enabled for replay.
154    pub drop_bail_out_transactions: bool,
155}
156
157/// Runtime environment for transaction batch processing.
158pub struct TransactionProcessingEnvironment {
159    /// The blockhash to use for the transaction batch.
160    pub blockhash: Hash,
161    /// Lamports per signature that corresponds to this blockhash.
162    ///
163    /// Note: This value is primarily used for nonce accounts. If set to zero,
164    /// it will disable transaction fees. However, any non-zero value will not
165    /// change transaction fees. For this reason, it is recommended to use the
166    /// `fee_per_signature` field to adjust transaction fees.
167    pub blockhash_lamports_per_signature: u64,
168    /// Whether the alpenglow migration has completed for this bank context.
169    pub alpenglow_migration_succeeded: bool,
170    /// The total stake for the current epoch.
171    pub epoch_total_stake: u64,
172    /// Runtime feature set to use for the transaction batch.
173    pub feature_set: SVMFeatureSet,
174    /// Program runtime environments for execution and deployment.
175    pub program_runtime_environments: ProgramRuntimeEnvironments,
176    /// Rent calculator to use for the transaction batch.
177    pub rent: Rent,
178}
179
180#[cfg(feature = "dev-context-only-utils")]
181pub fn get_mock_transaction_processing_environment() -> TransactionProcessingEnvironment {
182    TransactionProcessingEnvironment {
183        blockhash: Hash::default(),
184        blockhash_lamports_per_signature: 0,
185        alpenglow_migration_succeeded: false,
186        epoch_total_stake: 0,
187        feature_set: SVMFeatureSet::default(),
188        program_runtime_environments: ProgramRuntimeEnvironments::mock(),
189        rent: Rent::default(),
190    }
191}
192
193#[cfg_attr(
194    feature = "dev-context-only-utils",
195    field_qualifiers(slot(pub), epoch(pub), sysvar_cache(pub))
196)]
197pub struct TransactionBatchProcessor<FG: ForkGraph> {
198    /// Bank slot (i.e. block)
199    slot: Slot,
200
201    /// Bank epoch
202    epoch: Epoch,
203
204    /// SysvarCache is a collection of system variables that are
205    /// accessible from on chain programs. It is passed to SVM from
206    /// client code (e.g. Bank) and forwarded to process_message.
207    sysvar_cache: RwLock<SysvarCache>,
208
209    /// Anticipates the environments of the upcoming epoch
210    pub epoch_boundary_preparation: Arc<RwLock<EpochBoundaryPreparation>>,
211
212    /// Programs required for transaction batch processing
213    pub global_program_cache: Arc<RwLock<ProgramCache<FG>>>,
214
215    /// ProgramRuntimeEnvironment of the current epoch
216    pub program_runtime_environment: ProgramRuntimeEnvironment,
217
218    /// In the final slot of an epoch where we've activated a program runtime
219    /// feature, this override protects us from an `epoch_boundary_preparation`
220    /// environment that is outdated or otherwise incorrect.
221    deployment_env_override: Option<ProgramRuntimeEnvironment>,
222
223    /// Builtin program ids for this fork.
224    /// Used to see the `builtin_program_cache` between slots via
225    /// `new_from()`.
226    pub builtin_program_ids: RwLock<HashSet<Pubkey>>,
227
228    /// Cached ProgramCacheForTxBatch pre-populated with builtin entries.
229    /// Populated once per slot in `new_from()` from the global program cache.
230    ///
231    /// Keeping a dedicated builtin program cache for each slot:
232    ///
233    /// - Protects different forks from extracting the another fork's version
234    ///   of a builtin that may have been recently enabled or migrated to Core
235    ///   BPF.
236    /// - Avoids re-acquiring the lock and re-running extract() on every batch.
237    builtin_program_cache: RwLock<ProgramCacheForTxBatch>,
238
239    execution_cost: SVMTransactionExecutionCost,
240}
241
242impl<FG: ForkGraph> Debug for TransactionBatchProcessor<FG> {
243    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
244        f.debug_struct("TransactionBatchProcessor")
245            .field("slot", &self.slot)
246            .field("epoch", &self.epoch)
247            .field("sysvar_cache", &self.sysvar_cache)
248            .field("global_program_cache", &self.global_program_cache)
249            .finish()
250    }
251}
252
253impl<FG: ForkGraph> Default for TransactionBatchProcessor<FG> {
254    fn default() -> Self {
255        Self {
256            slot: Slot::default(),
257            epoch: Epoch::default(),
258            sysvar_cache: RwLock::<SysvarCache>::default(),
259            epoch_boundary_preparation: Arc::new(RwLock::new(EpochBoundaryPreparation::default())),
260            global_program_cache: Arc::new(RwLock::new(ProgramCache::new(Slot::default()))),
261            program_runtime_environment: ProgramRuntimeEnvironment::from(
262                BuiltinProgram::new_loader(VmConfig::default()),
263            ),
264            deployment_env_override: None,
265            builtin_program_ids: RwLock::new(HashSet::new()),
266            builtin_program_cache: RwLock::new(ProgramCacheForTxBatch::new(Slot::default())),
267            execution_cost: SVMTransactionExecutionCost::default(),
268        }
269    }
270}
271
272impl<FG: ForkGraph> TransactionBatchProcessor<FG> {
273    /// Create a new, uninitialized `TransactionBatchProcessor`.
274    ///
275    /// In this context, uninitialized means that the `TransactionBatchProcessor`
276    /// has been initialized with an empty program cache. The cache contains no
277    /// programs (including builtins) and has not been configured with a valid
278    /// fork graph.
279    ///
280    /// When using this method, it's advisable to call `set_fork_graph_in_program_cache`
281    /// as well as `add_builtin` to configure the cache before using the processor.
282    pub fn new_uninitialized(slot: Slot, epoch: Epoch) -> Self {
283        let epoch_boundary_preparation =
284            Arc::new(RwLock::new(EpochBoundaryPreparation::new(epoch)));
285        Self {
286            slot,
287            epoch,
288            epoch_boundary_preparation,
289            global_program_cache: Arc::new(RwLock::new(ProgramCache::new(slot))),
290            builtin_program_cache: RwLock::new(ProgramCacheForTxBatch::new(slot)),
291            ..Self::default()
292        }
293    }
294
295    /// Create a new `TransactionBatchProcessor`.
296    ///
297    /// The created processor's program cache is initialized with the provided
298    /// fork graph and loaders. If any loaders are omitted, a default "empty"
299    /// loader (no syscalls) will be used.
300    ///
301    /// The cache will still not contain any builtin programs. It's advisable to
302    /// call `add_builtin` to add the required builtins before using the processor.
303    #[cfg(feature = "dev-context-only-utils")]
304    pub fn new(
305        slot: Slot,
306        epoch: Epoch,
307        fork_graph: Weak<RwLock<FG>>,
308        program_runtime_environment: Option<ProgramRuntimeEnvironment>,
309    ) -> Self {
310        let mut processor = Self::new_uninitialized(slot, epoch);
311        processor
312            .global_program_cache
313            .write()
314            .unwrap()
315            .set_fork_graph(fork_graph);
316        let empty_loader = || ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
317        processor
318            .global_program_cache
319            .write()
320            .unwrap()
321            .latest_root_slot = processor.slot;
322        processor
323            .epoch_boundary_preparation
324            .write()
325            .unwrap()
326            .upcoming_epoch = processor.epoch;
327        processor.program_runtime_environment =
328            program_runtime_environment.unwrap_or(empty_loader());
329        processor
330    }
331
332    /// Create a new `TransactionBatchProcessor` from the current instance, but
333    /// with the provided slot and epoch.
334    ///
335    /// * Inherits the program cache and builtin program ids from the current
336    ///   instance.
337    /// * Resets the sysvar cache.
338    pub fn new_from(&self, slot: Slot, epoch: Epoch) -> Self {
339        let builtin_program_ids = self.builtin_program_ids.read().unwrap().clone();
340        let environments = self.program_runtime_environment.clone();
341
342        // Pre-populate the builtin program cache from the global cache.
343        // This is done once per block rather than once per batch.
344        //
345        // Note: Only the builtins already-listed in this slot's builtin program
346        // IDs can be extracted from the global program cache. This is important
347        // for cross-fork extraction when a builtin may be activated for the
348        // first time or migrated to Core BPF.
349        //
350        // The `builtin_program_ids` on the batch processor are effectively a
351        // guard against cross-fork extraction:
352        //
353        // - If a builtin appears in one fork's `builtin_program_ids`, it will
354        //   never be extracted by a fork whose `builtin_program_ids` _doesn't_
355        //   have it.
356        // - If a builtin is removed from one fork's `builtin_program_ids`, it
357        //   may still be extracted on another fork whose `builtin_program_ids`
358        //   still has it.
359        //
360        // `builtin_program_ids` seeds the `builtin_program_cache` below, which
361        // ensures only non-cached program IDs are extracted from the global
362        // program cache during `replenish_program_cache`.
363        let mut builtin_program_cache = ProgramCacheForTxBatch::new(slot);
364        let mut search_for: Vec<ProgramToLoad> = builtin_program_ids
365            .iter()
366            .map(|program_id| ProgramToLoad {
367                program_id,
368                loader: ProgramCacheEntryOwner::NativeLoader,
369                deployment_slot: 0,
370            })
371            .collect();
372        self.global_program_cache.read().unwrap().extract(
373            &mut search_for,
374            &mut builtin_program_cache,
375            &environments,
376            false,
377            false,
378        );
379
380        // Every registered builtin must be present in the global program cache,
381        // since `add_builtin` writes both together.
382        debug_assert!(
383            search_for.is_empty(),
384            "builtin(s) registered on this fork but missing from the global program cache: \
385             {search_for:?}"
386        );
387
388        Self {
389            slot,
390            epoch,
391            sysvar_cache: RwLock::<SysvarCache>::default(),
392            epoch_boundary_preparation: self.epoch_boundary_preparation.clone(),
393            global_program_cache: self.global_program_cache.clone(),
394            program_runtime_environment: environments,
395            deployment_env_override: None,
396            builtin_program_ids: RwLock::new(builtin_program_ids),
397            builtin_program_cache: RwLock::new(builtin_program_cache),
398            execution_cost: self.execution_cost,
399        }
400    }
401
402    /// Sets the base execution cost for the transactions that this instance of transaction processor
403    /// will execute.
404    pub fn set_execution_cost(&mut self, cost: SVMTransactionExecutionCost) {
405        self.execution_cost = cost;
406    }
407
408    /// Updates the environments when entering a new Epoch.
409    pub fn set_program_runtime_environment(&mut self, new_environment: ProgramRuntimeEnvironment) {
410        // First update the environment only if it is different
411        if *self.program_runtime_environment != *new_environment {
412            self.program_runtime_environment = new_environment;
413        }
414        // Then try to consolidate with the upcoming environment (to reuse the address)
415        if let Some(upcoming_environment) = &self
416            .epoch_boundary_preparation
417            .read()
418            .unwrap()
419            .upcoming_environment
420        {
421            let upcoming_environment = ProgramRuntimeEnvironment::clone(upcoming_environment);
422            if self.program_runtime_environment != upcoming_environment
423                && *self.program_runtime_environment == *upcoming_environment
424            {
425                // Use the prediction if equal but not identical
426                self.program_runtime_environment = upcoming_environment;
427            }
428        }
429    }
430
431    /// Returns the current environments depending on the given epoch
432    /// Returns None if the call could result in a deadlock
433    pub fn program_runtime_environment_for_epoch(&self, epoch: Epoch) -> ProgramRuntimeEnvironment {
434        self.epoch_boundary_preparation
435            .read()
436            .unwrap()
437            .get_upcoming_environment_for_epoch(epoch)
438            .unwrap_or_else(|| ProgramRuntimeEnvironment::clone(&self.program_runtime_environment))
439    }
440
441    pub fn deployment_env_override(&self) -> Option<&ProgramRuntimeEnvironment> {
442        self.deployment_env_override.as_ref()
443    }
444
445    pub fn set_deployment_env_override(&mut self, environment: ProgramRuntimeEnvironment) {
446        self.deployment_env_override = Some(environment);
447    }
448
449    pub fn sysvar_cache(&self) -> RwLockReadGuard<'_, SysvarCache> {
450        self.sysvar_cache.read().unwrap()
451    }
452
453    /// Main entrypoint to the SVM.
454    pub fn load_and_execute_sanitized_transactions<
455        CB: TransactionProcessingCallback + InvokeContextCallback,
456    >(
457        &self,
458        callbacks: &CB,
459        sanitized_txs: &[impl SVMTransaction],
460        check_results: Vec<TransactionCheckResult>,
461        environment: &TransactionProcessingEnvironment,
462        config: &TransactionProcessingConfig,
463    ) -> LoadAndExecuteSanitizedTransactionsOutput {
464        // If `check_results` does not have the same length as `sanitized_txs`,
465        // transactions could be truncated as a result of `.iter().zip()` in
466        // many of the below methods.
467        // See <https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.zip>.
468        debug_assert_eq!(
469            sanitized_txs.len(),
470            check_results.len(),
471            "Length of check_results does not match length of sanitized_txs"
472        );
473
474        // Initialize metrics.
475        let mut error_metrics = TransactionErrorMetrics::default();
476        let mut execute_timings = ExecuteTimings::default();
477        let mut processing_results = Vec::with_capacity(sanitized_txs.len());
478
479        // Determine a capacity for the internal account cache. This
480        // over-allocates but avoids ever reallocating, and spares us from
481        // deduplicating the account keys lists.
482        let account_keys_in_batch = sanitized_txs.iter().map(|tx| tx.account_keys().len()).sum();
483
484        // Create the account loader, which wraps all external account fetching.
485        let mut account_loader = AccountLoader::new_with_loaded_accounts_capacity(
486            config.account_overrides,
487            callbacks,
488            &environment.feature_set,
489            account_keys_in_batch,
490        );
491
492        // Create the transaction balance collector if recording is enabled.
493        let mut balance_collector = config
494            .recording_config
495            .enable_transaction_balance_recording
496            .then(|| BalanceCollector::new_with_transaction_count(sanitized_txs.len()));
497
498        // Clone the batch-local program cache (builtins already populated in new_from()).
499        // User-deployed programs are loaded per-transaction via replenish_program_cache
500        // in the transaction loop below.
501        let mut program_cache_for_tx_batch = self.builtin_program_cache.read().unwrap().clone();
502
503        if program_cache_for_tx_batch.hit_max_limit {
504            return LoadAndExecuteSanitizedTransactionsOutput {
505                error_metrics,
506                execute_timings,
507                processing_results: (0..sanitized_txs.len())
508                    .map(|_| Err(TransactionError::ProgramCacheHitMaxLimit))
509                    .collect(),
510                // If we abort the batch and balance recording is enabled, no balances should be
511                // collected. If this is a leader thread, no batch will be committed.
512                balance_collector: None,
513            };
514        }
515
516        let (mut load_us, mut execution_us): (u64, u64) = (0, 0);
517        let sysvar_cache = self.sysvar_cache();
518
519        // Validate, execute, and collect results from each transaction in order.
520        // Transactions must be executed in order, because transactions
521        // in the same batch may modify the same accounts. Transaction order is
522        // preserved within entries written to the ledger.
523        for (tx, check_result) in sanitized_txs.iter().zip(check_results) {
524            let (validate_result, validate_fees_us) = measure_us!(match check_result {
525                Ok(tx_details) => Self::validate_transaction_nonce_and_fee_payer(
526                    &mut account_loader,
527                    tx,
528                    tx_details,
529                    &environment.blockhash,
530                    environment.blockhash_lamports_per_signature,
531                    &environment.rent,
532                    environment.feature_set.relax_post_exec_min_balance_check,
533                    config.strict_nonce_size_check,
534                    &mut error_metrics,
535                ),
536                Err(e) => TransactionValidationResult::Unprocessable(e),
537            });
538            execute_timings
539                .saturating_add_in_place(ExecuteTimingType::ValidateFeesUs, validate_fees_us);
540
541            let (load_result, single_load_us) = measure_us!(load_transaction(
542                &mut account_loader,
543                tx,
544                validate_result,
545                &mut error_metrics,
546                &environment.rent,
547            ));
548            load_us = load_us.saturating_add(single_load_us);
549
550            let ((), collect_balances_us) =
551                measure_us!(balance_collector.collect_pre_balances(&mut account_loader, tx));
552            execute_timings
553                .saturating_add_in_place(ExecuteTimingType::CollectBalancesUs, collect_balances_us);
554
555            let (processing_result, single_execution_us) = measure_us!(match load_result {
556                // Unprocessable transactions always result in an error
557                TransactionLoadResult::Unprocessable(e) => Err(e),
558
559                // Validation failures that would be no-ops become errors with `drop_on_failure` or
560                // `drop_noop_transactions`. These may be produced by SIMD-0290 (fee-payer failure)
561                // or SIMD-0297 (nonce failure), but are dropped during block production to avoid commit.
562                TransactionLoadResult::NoOp(NoOpTransaction {
563                    validation_error: e,
564                    ..
565                }) if config.drop_on_failure || config.drop_noop_transactions => Err(e),
566
567                // SIMD-0290 (fee-payer failure) or SIMD-0297 (nonce failure) is a non-error no-op on replay
568                TransactionLoadResult::NoOp(no_op_tx) =>
569                    Ok(ProcessedTransaction::NoOp(Box::new(no_op_tx))),
570
571                // Loading failures that would be fee-only become errors with `drop_on_failure`
572                TransactionLoadResult::FeesOnly(FeesOnlyTransaction { load_error: e, .. })
573                    if config.drop_on_failure =>
574                    Err(e),
575
576                // Transactions that fail at account loading charge fees and roll nonces
577                TransactionLoadResult::FeesOnly(fees_only_tx) => {
578                    account_loader
579                        .update_accounts_for_failed_tx(&fees_only_tx.rollback_accounts, self.slot);
580                    Ok(ProcessedTransaction::FeesOnly(Box::new(fees_only_tx)))
581                }
582
583                // Transaction is able to be executed
584                TransactionLoadResult::Loaded(loaded_transaction) => {
585                    let (missing_programs, filter_executable_us) =
586                        measure_us!(filter_executable_program_accounts(
587                            &account_loader,
588                            &program_cache_for_tx_batch,
589                            tx.account_keys().iter(),
590                        ));
591                    execute_timings.saturating_add_in_place(
592                        ExecuteTimingType::FilterExecutableUs,
593                        filter_executable_us,
594                    );
595
596                    let ((), program_cache_us) = measure_us!({
597                        self.replenish_program_cache(
598                            &account_loader,
599                            missing_programs,
600                            environment
601                                .program_runtime_environments
602                                .get_env_for_execution(),
603                            &mut program_cache_for_tx_batch,
604                            &mut execute_timings,
605                            config.limit_to_load_programs,
606                            true, // increment_usage_counter
607                        );
608                    });
609                    execute_timings.saturating_add_in_place(
610                        ExecuteTimingType::ProgramCacheUs,
611                        program_cache_us,
612                    );
613
614                    if program_cache_for_tx_batch.hit_max_limit {
615                        return LoadAndExecuteSanitizedTransactionsOutput {
616                            error_metrics,
617                            execute_timings,
618                            processing_results: (0..sanitized_txs.len())
619                                .map(|_| Err(TransactionError::ProgramCacheHitMaxLimit))
620                                .collect(),
621                            // If we abort the batch and balance recording is enabled, no balances should be
622                            // collected. If this is a leader thread, no batch will be committed.
623                            balance_collector: None,
624                        };
625                    }
626
627                    let executed_tx = self.execute_loaded_transaction(
628                        callbacks,
629                        tx,
630                        &sysvar_cache,
631                        loaded_transaction,
632                        &mut execute_timings,
633                        &mut error_metrics,
634                        &mut program_cache_for_tx_batch,
635                        environment,
636                        config,
637                    );
638
639                    match (
640                        &executed_tx.execution_details.status,
641                        config.drop_on_failure,
642                    ) {
643                        // Successful transactions need to update the account loader cache as future
644                        // transactions in the batch may depend on them.
645                        (Ok(_), _) => {
646                            account_loader.update_accounts_for_successful_tx(
647                                tx,
648                                &executed_tx.loaded_transaction.accounts,
649                                &executed_tx.loaded_transaction.touched_flags,
650                                self.slot,
651                            );
652                            // Also update local program cache with modifications made by the
653                            // transaction, if it executed successfully.
654                            program_cache_for_tx_batch.merge(&executed_tx.programs_modified_by_tx);
655
656                            Ok(ProcessedTransaction::Executed(Box::new(executed_tx)))
657                        }
658                        // If it bailed out then this transaction will be dropped from the batch.
659                        (
660                            Err(TransactionError::InstructionError(_, InstructionError::BailOut)),
661                            _,
662                        ) => Err(TransactionError::BailOut),
663                        // If the transaction failed & drop on failure is set then we don't want to
664                        // update the accounts as this transaction will be dropped from the batch.
665                        (Err(err), true) => Err(err.clone()),
666                        // Unsuccessful transactions will still update rollback accounts (fee payer,
667                        // nonce, etc).
668                        (Err(_), false) => {
669                            account_loader.update_accounts_for_failed_tx(
670                                &executed_tx.loaded_transaction.rollback_accounts,
671                                self.slot,
672                            );
673
674                            Ok(ProcessedTransaction::Executed(Box::new(executed_tx)))
675                        }
676                    }
677                }
678            });
679            execution_us = execution_us.saturating_add(single_execution_us);
680
681            let ((), collect_balances_us) =
682                measure_us!(balance_collector.collect_post_balances(&mut account_loader, tx));
683            execute_timings
684                .saturating_add_in_place(ExecuteTimingType::CollectBalancesUs, collect_balances_us);
685
686            // If this is an all or nothing batch and we failed to process this transaction then we
687            // must abort all prior/remaining transactions.
688            if config.all_or_nothing && processing_result.is_err() {
689                // Abort prior transactions.
690                for res in processing_results.iter_mut() {
691                    *res = Err(TransactionError::CommitCancelled);
692                }
693
694                // Preserve the failure that triggered the batch to abort.
695                processing_results.push(processing_result);
696
697                // Abort remaining transactions.
698                processing_results.extend(
699                    (0..sanitized_txs.len() - processing_results.len())
700                        .map(|_| Err(TransactionError::CommitCancelled)),
701                );
702
703                return LoadAndExecuteSanitizedTransactionsOutput {
704                    error_metrics,
705                    execute_timings,
706                    processing_results,
707                    // If we abort the batch and balance recording is enabled, no balances should be
708                    // collected. If this is a leader thread, no batch will be committed.
709                    balance_collector: None,
710                };
711            }
712
713            processing_results.push(processing_result);
714        }
715
716        // Skip eviction when there's no chance this particular tx batch has increased the size of
717        // ProgramCache entries. Note that loaded_missing is deliberately defined, so that there's
718        // still at least one other batch, which will evict the program cache, even after the
719        // occurrences of cooperative loading.
720        if program_cache_for_tx_batch.loaded_missing || program_cache_for_tx_batch.merged_modified {
721            // NOTE: this is a percentage; do not set above 100.
722            const SHRINK_LOADED_PROGRAMS_TO_PERCENTAGE: Percent = 90;
723            self.global_program_cache
724                .write()
725                .unwrap()
726                .evict_using_random_selection(SHRINK_LOADED_PROGRAMS_TO_PERCENTAGE, self.slot);
727        }
728
729        debug!(
730            "load: {}us execute: {}us txs_len={}",
731            load_us,
732            execution_us,
733            sanitized_txs.len(),
734        );
735        execute_timings.saturating_add_in_place(ExecuteTimingType::LoadUs, load_us);
736        execute_timings.saturating_add_in_place(ExecuteTimingType::ExecuteUs, execution_us);
737
738        if let Some(ref balance_collector) = balance_collector {
739            debug_assert!(balance_collector.lengths_match_expected(sanitized_txs.len()));
740        }
741
742        LoadAndExecuteSanitizedTransactionsOutput {
743            error_metrics,
744            execute_timings,
745            processing_results,
746            balance_collector,
747        }
748    }
749
750    fn validate_transaction_nonce_and_fee_payer<CB: TransactionProcessingCallback>(
751        account_loader: &mut AccountLoader<CB>,
752        message: &impl SVMMessage,
753        checked_details: CheckedTransactionDetails,
754        environment_blockhash: &Hash,
755        next_lamports_per_signature: u64,
756        rent: &Rent,
757        relax_post_exec_min_balance_check: bool,
758        strict_nonce_size_check: bool,
759        error_counters: &mut TransactionErrorMetrics,
760    ) -> TransactionValidationResult {
761        let CheckedTransactionDetails {
762            nonce_address,
763            compute_budget_and_limits,
764        } = checked_details;
765
766        // If this is a nonce transaction, validate the nonce info.
767        // This must be done for every transaction because the nonce account
768        // may have changed due to use, authorization, or deallocation.
769        let nonce_info = if let Some(ref nonce_address) = nonce_address {
770            let next_durable_nonce = DurableNonce::from_blockhash(environment_blockhash);
771            let nonce_result = Self::validate_transaction_nonce(
772                account_loader,
773                message,
774                nonce_address,
775                &next_durable_nonce,
776                next_lamports_per_signature,
777                strict_nonce_size_check,
778                error_counters,
779            );
780
781            match nonce_result {
782                Ok(nonce_info) => Some(nonce_info),
783                Err(e) => return TransactionValidationResult::Unprocessable(e),
784            }
785        } else {
786            None
787        };
788
789        let is_blockhash_transaction = nonce_info.is_none();
790
791        // Now validate the fee-payer for the transaction unconditionally.
792        let fee_payer_result = Self::validate_transaction_fee_payer(
793            account_loader,
794            message,
795            nonce_info,
796            compute_budget_and_limits,
797            rent,
798            relax_post_exec_min_balance_check,
799            error_counters,
800        );
801
802        // With SIMD-0290 enabled, a blockhash transaction with an invalid fee-payer can
803        // be processed as a no-op. However, nonce transactions must ALWAYS be discarded,
804        // regardless of whether they use a standard or nonce-based fee-payer.
805        // Fee-payer failure for nonces will be handled the same as nonce failures
806        // themselves when we implement SIMD-0297.
807        let allow_noop =
808            account_loader.feature_set.relax_fee_payer_constraint && is_blockhash_transaction;
809
810        match fee_payer_result {
811            Ok(details) => TransactionValidationResult::Loadable(details),
812            Err(e) if allow_noop => {
813                // We correctly report the fee-payer balance if the account exists but is invalid.
814                let fee_payer_balance = account_loader
815                    .load_account(message.fee_payer())
816                    .map(|account| account.lamports());
817
818                // Per SIMD-0290, we report the maximum allowed compute and data usage.
819                // In essence the intent is if you pack a block with n non-paying transactions,
820                // you could have packed n or more of the same paying transactions, rather than
821                // being able to pack some multiple more non-paying than paying.
822                TransactionValidationResult::NoOp(NoOpTransaction {
823                    validation_error: e,
824                    fee_payer_balance,
825                    compute_unit_limit: compute_budget_and_limits.budget.compute_unit_limit,
826                    loaded_accounts_bytes_limit: compute_budget_and_limits
827                        .loaded_accounts_data_size_limit,
828                    nonce_address,
829                })
830            }
831            Err(e) => TransactionValidationResult::Unprocessable(e),
832        }
833    }
834
835    // Loads transaction fee payer, collects rent if necessary, then calculates
836    // transaction fees, and deducts them from the fee payer balance. If the
837    // account is not found or has insufficient funds, an error is returned.
838    fn validate_transaction_fee_payer<CB: TransactionProcessingCallback>(
839        account_loader: &mut AccountLoader<CB>,
840        message: &impl SVMMessage,
841        nonce_info: Option<NonceInfo>,
842        compute_budget_and_limits: SVMTransactionExecutionAndFeeBudgetLimits,
843        rent: &Rent,
844        relax_post_exec_min_balance_check: bool,
845        error_counters: &mut TransactionErrorMetrics,
846    ) -> TransactionResult<ValidatedTransactionDetails> {
847        let fee_payer_address = message.fee_payer();
848
849        // We *must* use load_transaction_account() here because *this* is when the fee-payer
850        // is loaded for the transaction. Transaction loading skips the first account and
851        // loads (and thus inspects) all others normally.
852        let Some(mut loaded_fee_payer) =
853            account_loader.load_transaction_account(fee_payer_address, true)
854        else {
855            error_counters.account_not_found += 1;
856            return Err(TransactionError::AccountNotFound);
857        };
858
859        let fee_payer_loaded_rent_epoch = loaded_fee_payer.account.rent_epoch();
860        update_rent_exempt_status_for_account(rent, &mut loaded_fee_payer.account);
861
862        let fee_payer_index = 0;
863        validate_fee_payer(
864            &mut loaded_fee_payer.account,
865            fee_payer_index,
866            error_counters,
867            rent,
868            compute_budget_and_limits.fee_details.total_fee(),
869            relax_post_exec_min_balance_check,
870        )?;
871
872        // Capture fee-subtracted fee payer account and next nonce account state
873        // to commit if transaction execution fails.
874        let rollback_accounts = RollbackAccounts::new(
875            nonce_info,
876            *fee_payer_address,
877            loaded_fee_payer.account.clone(),
878            fee_payer_loaded_rent_epoch,
879        );
880
881        Ok(ValidatedTransactionDetails {
882            fee_details: compute_budget_and_limits.fee_details,
883            rollback_accounts,
884            loaded_accounts_bytes_limit: compute_budget_and_limits.loaded_accounts_data_size_limit,
885            compute_budget: compute_budget_and_limits.budget,
886            loaded_fee_payer_account: loaded_fee_payer,
887        })
888    }
889
890    fn validate_transaction_nonce<CB: TransactionProcessingCallback>(
891        account_loader: &mut AccountLoader<CB>,
892        message: &impl SVMMessage,
893        nonce_address: &Pubkey,
894        next_durable_nonce: &DurableNonce,
895        next_lamports_per_signature: u64,
896        strict_nonce_size_check: bool,
897        error_counters: &mut TransactionErrorMetrics,
898    ) -> TransactionResult<NonceInfo> {
899        // If the nonce has been used in this batch already, we must drop the transaction.
900        // This is the same as if it was used in different batches in the same slot.
901        // It is possible that the nonce account was used, closed, closed and reopened, closed and
902        // spoofed by a non-system program, or had its authority changed. Such a transaction cannot
903        // be processed, even as fee-only.
904
905        // Check if nonce is writable after write-lock demotion, which runtime may not enforce.
906        if !message
907            .account_keys()
908            .iter()
909            .position(|key| key == nonce_address)
910            .is_some_and(|index| message.is_writable(index))
911        {
912            error_counters.blockhash_not_found += 1;
913            return Err(TransactionError::BlockhashNotFound);
914        }
915
916        let Some(mut nonce_account) = account_loader
917            .load_transaction_account(nonce_address, true)
918            .map(|loaded| loaded.account)
919        else {
920            error_counters.account_not_found += 1;
921            return Err(TransactionError::AccountNotFound);
922        };
923
924        if strict_nonce_size_check && nonce_account.data().len() != NonceState::size() {
925            error_counters.blockhash_not_found += 1;
926            return Err(TransactionError::BlockhashNotFound);
927        }
928
929        // This function verifies:
930        // * Nonce account owner is SystemProgram
931        // * Nonce account parses as State::Initialized
932        // * Stored durable nonce matches the message blockhash
933        let Some(nonce_data) = verify_nonce_account(&nonce_account, message.recent_blockhash())
934        else {
935            error_counters.blockhash_not_found += 1;
936            return Err(TransactionError::BlockhashNotFound);
937        };
938
939        // We must still check that the nonce account is usable and that its authority has signed.
940        let nonce_can_be_advanced = &nonce_data.durable_nonce != next_durable_nonce;
941        let nonce_authority_is_valid = message
942            .get_ix_signers(NONCED_TX_MARKER_IX_INDEX as usize)
943            .any(|signer| signer == &nonce_data.authority);
944
945        if nonce_can_be_advanced && nonce_authority_is_valid {
946            let next_nonce_state = NonceState::new_initialized(
947                &nonce_data.authority,
948                *next_durable_nonce,
949                next_lamports_per_signature,
950            );
951            nonce_account
952                .set_state(&NonceVersions::new(next_nonce_state))
953                .expect("Serializing into a validated nonce account cannot fail");
954
955            Ok(NonceInfo::new(*nonce_address, nonce_account))
956        } else {
957            error_counters.blockhash_not_found += 1;
958            Err(TransactionError::BlockhashNotFound)
959        }
960    }
961
962    #[cfg_attr(feature = "dev-context-only-utils", qualifiers(pub))]
963    fn replenish_program_cache<CB: TransactionProcessingCallback>(
964        &self,
965        account_loader: &AccountLoader<CB>,
966        mut missing_programs: Vec<ProgramToLoad>,
967        program_runtime_environment_for_execution: &ProgramRuntimeEnvironment,
968        program_cache_for_tx_batch: &mut ProgramCacheForTxBatch,
969        execute_timings: &mut ExecuteTimings,
970        limit_to_load_programs: bool,
971        increment_usage_counter: bool,
972    ) {
973        if missing_programs.is_empty() {
974            // Nothing to load, so skip the global cache and fork graph locks.
975            // Program-cache hit/miss counters are unchanged for empty work.
976            return;
977        }
978        let mut count_hits_and_misses = true;
979        loop {
980            // Lock the global cache.
981            let global_program_cache = self.global_program_cache.read().unwrap();
982            // Figure out which program needs to be loaded next.
983            let program_to_load = global_program_cache.extract(
984                &mut missing_programs,
985                program_cache_for_tx_batch,
986                program_runtime_environment_for_execution,
987                increment_usage_counter,
988                count_hits_and_misses,
989            );
990            count_hits_and_misses = false;
991            let task_waiter = Arc::clone(&global_program_cache.loading_task_waiter);
992            let task_cookie = task_waiter.cookie();
993            // Unlock the global cache again.
994            drop(global_program_cache);
995
996            let program_to_store = program_to_load.map(|key| {
997                // Load, verify and compile one program.
998                let program = load_program_with_pubkey(
999                    account_loader,
1000                    program_runtime_environment_for_execution,
1001                    &key,
1002                    self.slot,
1003                    execute_timings,
1004                )
1005                .expect("called load_program_with_pubkey() with nonexistent account");
1006                (key, program)
1007            });
1008
1009            if let Some((key, program)) = program_to_store {
1010                program_cache_for_tx_batch.loaded_missing = true;
1011                let mut global_program_cache = self.global_program_cache.write().unwrap();
1012                // Submit our last completed loading task.
1013                if global_program_cache.finish_cooperative_loading_task(
1014                    program_runtime_environment_for_execution,
1015                    self.slot,
1016                    key,
1017                    program,
1018                ) && limit_to_load_programs
1019                {
1020                    // This branch is taken when there is an error in assigning a program to a
1021                    // cache slot. It is not possible to mock this error for SVM unit
1022                    // tests purposes.
1023                    *program_cache_for_tx_batch = ProgramCacheForTxBatch::new(self.slot);
1024                    program_cache_for_tx_batch.hit_max_limit = true;
1025                    return;
1026                }
1027            } else if missing_programs.is_empty() {
1028                break;
1029            } else {
1030                // Remember: there are multiple transaction processor threads running concurrently
1031                // and those other threads may be loading this or other programs.
1032                //
1033                // So, sleep until some other thread submits a program with their
1034                // `finish_cooperative_loading_task` call. We'll then wake up and try to load the
1035                // missing programs inside the tx batch again.
1036                let _new_cookie = task_waiter.wait(task_cookie);
1037            }
1038        }
1039    }
1040
1041    /// Similar to replenish_program_cache() but only used in Bank::prepare_program_cache_for_upcoming_feature_set().
1042    pub fn prepare_one_program_for_upcoming_feature_set<CB: TransactionProcessingCallback>(
1043        &self,
1044        account_loader: &CB,
1045        upcoming_environment: &ProgramRuntimeEnvironment,
1046        key: &Pubkey,
1047        stats_of_enqueued_program: &ProgramStatistics,
1048    ) {
1049        let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::new(self.slot);
1050        let mut missing_programs = filter_executable_program_accounts(
1051            account_loader,
1052            &program_cache_for_tx_batch,
1053            std::iter::once(key),
1054        );
1055        if missing_programs.is_empty() {
1056            // Program account was closed
1057            return;
1058        }
1059        let program_to_load = {
1060            let program_cache_guard = self.global_program_cache.read().unwrap();
1061            program_cache_guard.extract(
1062                &mut missing_programs,
1063                &mut program_cache_for_tx_batch,
1064                upcoming_environment,
1065                false, // increment_usage_counter
1066                false, // count_hits_and_misses
1067            )
1068            // Unlock again because load_program_with_pubkey() might take a while.
1069        };
1070        // Maybe the enqueued program was already loaded and can be skipped.
1071        if let Some(key) = program_to_load {
1072            // Load, verify and compile one program.
1073            let recompiled = load_program_with_pubkey(
1074                account_loader,
1075                upcoming_environment,
1076                &key,
1077                self.slot,
1078                &mut ExecuteTimings::default(),
1079            )
1080            .expect("called load_program_with_pubkey() with nonexistent account");
1081            recompiled.stats.merge_from(stats_of_enqueued_program);
1082            // Lock the global cache as writable this time.
1083            let mut program_cache_guard = self.global_program_cache.write().unwrap();
1084            // Submit our last completed loading task.
1085            program_cache_guard.finish_cooperative_loading_task(
1086                upcoming_environment,
1087                self.slot,
1088                key,
1089                recompiled,
1090            );
1091        }
1092    }
1093
1094    /// Execute a transaction using the provided loaded accounts and update
1095    /// the executors cache if the transaction was successful.
1096    #[allow(clippy::too_many_arguments)]
1097    fn execute_loaded_transaction<CB: InvokeContextCallback>(
1098        &self,
1099        callback: &CB,
1100        tx: &impl SVMTransaction,
1101        sysvar_cache: &SysvarCache,
1102        mut loaded_transaction: LoadedTransaction,
1103        execute_timings: &mut ExecuteTimings,
1104        error_metrics: &mut TransactionErrorMetrics,
1105        program_cache_for_tx_batch: &mut ProgramCacheForTxBatch,
1106        environment: &TransactionProcessingEnvironment,
1107        config: &TransactionProcessingConfig,
1108    ) -> ExecutedTransaction {
1109        let transaction_accounts = std::mem::take(&mut loaded_transaction.accounts);
1110
1111        // Ensure the length of accounts matches the expected length from tx.account_keys().
1112        // This is a sanity check in case that someone starts adding some additional accounts
1113        // since this has been done before. See discussion in PR #4497 for details
1114        debug_assert!(transaction_accounts.len() == tx.account_keys().len());
1115
1116        fn transaction_accounts_lamports_sum(
1117            accounts: &[(Pubkey, AccountSharedData)],
1118        ) -> Option<u128> {
1119            accounts.iter().try_fold(0u128, |sum, (_, account)| {
1120                sum.checked_add(u128::from(account.lamports()))
1121            })
1122        }
1123
1124        let lamports_before_tx =
1125            transaction_accounts_lamports_sum(&transaction_accounts).unwrap_or(0);
1126
1127        let compute_budget = loaded_transaction.compute_budget;
1128
1129        let mut transaction_context = TransactionContext::new_with_feature_flags(
1130            transaction_accounts,
1131            environment.rent.clone(),
1132            compute_budget.max_instruction_stack_depth,
1133            compute_budget.max_instruction_trace_length,
1134            tx.num_instructions(),
1135            ComputeMeter::new(compute_budget.compute_unit_limit),
1136            if config.drop_bail_out_transactions {
1137                DropOnBailOut::Enabled
1138            } else {
1139                DropOnBailOut::Disabled
1140            },
1141        );
1142
1143        let relax_post_exec_min_balance_check =
1144            environment.feature_set.relax_post_exec_min_balance_check;
1145        let pre_account_state_info = TransactionAccountStateInfo::new_pre_exec(
1146            &transaction_context,
1147            tx,
1148            &environment.rent,
1149            relax_post_exec_min_balance_check,
1150        );
1151
1152        let log_collector = if config.recording_config.enable_log_recording {
1153            match config.log_messages_bytes_limit {
1154                None => Some(LogCollector::new_ref()),
1155                Some(log_messages_bytes_limit) => Some(LogCollector::new_ref_with_limit(Some(
1156                    log_messages_bytes_limit,
1157                ))),
1158            }
1159        } else {
1160            None
1161        };
1162
1163        let mut executed_units = 0u64;
1164
1165        let mut invoke_context = InvokeContext::new(
1166            &mut transaction_context,
1167            program_cache_for_tx_batch,
1168            EnvironmentConfig::new(
1169                environment.blockhash,
1170                environment.blockhash_lamports_per_signature,
1171                environment.alpenglow_migration_succeeded,
1172                callback,
1173                &environment.feature_set,
1174                &environment.program_runtime_environments,
1175                sysvar_cache,
1176            ),
1177            log_collector.clone(),
1178            compute_budget,
1179            self.execution_cost,
1180        );
1181
1182        let mut process_message_time = Measure::start("process_message_time");
1183        let process_result = invoke_context
1184            .process_message(tx, execute_timings, &mut executed_units)
1185            .map_err(|(index, err)| TransactionError::InstructionError(index, err));
1186        process_message_time.stop();
1187
1188        drop(invoke_context);
1189
1190        execute_timings.execute_accessories.process_message_us += process_message_time.as_us();
1191
1192        let mut post_account_state_info_result = process_result
1193            .and_then(|_info| {
1194                let post_account_state_info = TransactionAccountStateInfo::new_post_exec(
1195                    &transaction_context,
1196                    tx,
1197                    &pre_account_state_info,
1198                    &environment.rent,
1199                    relax_post_exec_min_balance_check,
1200                );
1201                verify_changes(
1202                    &pre_account_state_info,
1203                    &post_account_state_info,
1204                    &transaction_context,
1205                )
1206                .map(|_| post_account_state_info)
1207            })
1208            .map_err(|err| {
1209                match err {
1210                    TransactionError::InvalidRentPayingAccount
1211                    | TransactionError::InsufficientFundsForRent { .. } => {
1212                        error_metrics.invalid_rent_paying_account += 1;
1213                    }
1214                    TransactionError::InvalidAccountIndex => {
1215                        error_metrics.invalid_account_index += 1;
1216                    }
1217                    _ => {
1218                        error_metrics.instruction_error += 1;
1219                    }
1220                }
1221                err
1222            });
1223
1224        let log_messages: Option<TransactionLogMessages> =
1225            log_collector.and_then(|log_collector| {
1226                Rc::try_unwrap(log_collector)
1227                    .map(|log_collector| log_collector.into_inner().into_messages())
1228                    .ok()
1229            });
1230
1231        let (execution_record, inner_instructions) = Self::deconstruct_transaction(
1232            transaction_context,
1233            config.recording_config.enable_cpi_recording,
1234        );
1235
1236        let ExecutionRecord {
1237            accounts,
1238            return_data,
1239            mut touched_flags,
1240            accounts_resize_delta,
1241        } = execution_record;
1242
1243        // The fee payer (account index 0) is debited during loading, outside the
1244        // VM, so it carries no VM touch flag but must still be written back.
1245        if let Some(fee_payer_touched) = touched_flags.first_mut() {
1246            *fee_payer_touched = true;
1247        }
1248
1249        // changed_account_count reflects every account that will be written back,
1250        // including the fee payer marked above.
1251        let touched_account_count = touched_flags.iter().filter(|touched| **touched).count();
1252
1253        if post_account_state_info_result.is_ok()
1254            && transaction_accounts_lamports_sum(&accounts)
1255                .filter(|lamports_after_tx| lamports_before_tx == *lamports_after_tx)
1256                .is_none()
1257        {
1258            post_account_state_info_result = Err(TransactionError::UnbalancedTransaction);
1259        }
1260
1261        // accounts_resize_delta and accounts_uninitialized_size must be set to None
1262        // in the result if status is an error
1263        let (status, accounts_deltas) = post_account_state_info_result
1264            .map(|post_state_info| {
1265                (
1266                    Ok(()),
1267                    Some(AccountsDeltas {
1268                        accounts_resize_delta,
1269                        accounts_uninitialized_size: get_uninitialized_accounts_size(
1270                            &post_state_info,
1271                        ),
1272                    }),
1273                )
1274            })
1275            .unwrap_or_else(|err| (Err(err), None));
1276
1277        loaded_transaction.accounts = accounts;
1278        loaded_transaction.touched_flags = touched_flags;
1279        execute_timings.details.total_account_count += loaded_transaction.accounts.len() as u64;
1280        execute_timings.details.changed_account_count += touched_account_count as u64;
1281
1282        let return_data = if config.recording_config.enable_return_data_recording
1283            && !return_data.data.is_empty()
1284        {
1285            Some(return_data)
1286        } else {
1287            None
1288        };
1289
1290        ExecutedTransaction {
1291            execution_details: TransactionExecutionDetails {
1292                status,
1293                log_messages,
1294                inner_instructions,
1295                return_data,
1296                executed_units,
1297                accounts_deltas,
1298            },
1299            loaded_transaction,
1300            programs_modified_by_tx: program_cache_for_tx_batch.drain_modified_entries(),
1301        }
1302    }
1303
1304    /// Extract an ExecutionRecord and an InnerInstructionsList from a TransactionContext
1305    fn deconstruct_transaction(
1306        mut transaction_context: TransactionContext,
1307        record_inner_instructions: bool,
1308    ) -> (ExecutionRecord, Option<InnerInstructionsList>) {
1309        let inner_ix = if record_inner_instructions {
1310            debug_assert!(
1311                transaction_context
1312                    .get_instruction_context_at_index_in_trace(0)
1313                    .map(|instruction_context| instruction_context.get_stack_height()
1314                        == TRANSACTION_LEVEL_STACK_HEIGHT)
1315                    .unwrap_or(true)
1316            );
1317
1318            let top_level_ixs_num = transaction_context
1319                .get_instruction_trace_length()
1320                .saturating_sub(transaction_context.number_of_cpis_in_trace());
1321            // This vector is a map between CPI number in trace (not counting top level
1322            // instructions) and the top level caller index.
1323            // In TransactionContext, caller instructions always precede callee instructions, so
1324            // we can use it to avoid backtracking on instructions callers to
1325            // find the top level instruction that started the call chain.
1326            let mut parent_positions: Vec<usize> =
1327                vec![usize::MAX; transaction_context.number_of_cpis_in_trace()];
1328            let (ix_trace, accounts, ix_data_trace) = transaction_context.take_instruction_trace();
1329            let mut outer_instructions: Vec<Vec<InnerInstruction>> =
1330                vec![Vec::new(); top_level_ixs_num];
1331            for (cpi_num, ((ix_in_trace, ix_data), ix_accounts)) in ix_trace
1332                .into_iter()
1333                .zip(ix_data_trace)
1334                .zip(accounts)
1335                .skip(top_level_ixs_num)
1336                .enumerate()
1337            {
1338                let caller_ix = ix_in_trace.index_of_caller_instruction;
1339                debug_assert_ne!(caller_ix, u16::MAX, "Instruction is not a CPI");
1340
1341                // If the caller index is less than the number of top level instructions,
1342                // it directly represents a top level instruction index.
1343                // Top level instructions precede all CPIs in the instruction trace.
1344                let outer_index = if (caller_ix as usize) < top_level_ixs_num {
1345                    *parent_positions.get_mut(cpi_num).unwrap() = caller_ix as usize;
1346                    caller_ix as usize
1347                // If the above condition was false, we are dealing with a nested CPI.
1348                // The caller_ix represents the CPI index in the instruction trace.
1349                // To calculate its cpi_number (i.e. the index in `parent_positions)`
1350                // we subtract is from the number of top level instructions.
1351                } else if let Some(caller_index) = parent_positions
1352                    .get((caller_ix as usize).saturating_sub(top_level_ixs_num))
1353                    .copied()
1354                    && caller_index != usize::MAX
1355                {
1356                    *parent_positions.get_mut(cpi_num).unwrap() = caller_index;
1357                    caller_index
1358                } else {
1359                    // This case shall never happen. Program runtime always executes caller before
1360                    // callees, so the if-statement can only be broken into two different cases:
1361                    // 1. Top-level instructions doing a CPI
1362                    // 2. A nested CPI.
1363                    debug_assert!(false);
1364                    usize::MAX
1365                };
1366
1367                if let Some(inner_instructions) = outer_instructions.get_mut(outer_index) {
1368                    let stack_height = ix_in_trace.nesting_level.saturating_add(1);
1369                    let stack_height = u8::try_from(stack_height).unwrap_or(u8::MAX);
1370                    inner_instructions.push(InnerInstruction {
1371                        instruction: CompiledInstruction::new_from_raw_parts(
1372                            ix_in_trace.program_account_index_in_tx as u8,
1373                            ix_data.into_owned(),
1374                            ix_accounts
1375                                .iter()
1376                                .map(|acc| acc.index_in_transaction as u8)
1377                                .collect(),
1378                        ),
1379                        stack_height,
1380                    });
1381                } else {
1382                    debug_assert!(false);
1383                }
1384            }
1385
1386            Some(outer_instructions)
1387        } else {
1388            None
1389        };
1390
1391        let record: ExecutionRecord = transaction_context.into();
1392
1393        (record, inner_ix)
1394    }
1395
1396    pub fn fill_missing_sysvar_cache_entries<CB: TransactionProcessingCallback>(
1397        &self,
1398        callbacks: &CB,
1399    ) {
1400        let mut sysvar_cache = self.sysvar_cache.write().unwrap();
1401        Self::fill_missing_sysvar_cache_entries_from_accounts(&mut sysvar_cache, callbacks);
1402    }
1403
1404    pub fn reset_and_fill_sysvar_cache_entries<CB: TransactionProcessingCallback>(
1405        &self,
1406        callbacks: &CB,
1407    ) {
1408        let mut sysvar_cache = self.sysvar_cache.write().unwrap();
1409        sysvar_cache.reset();
1410        Self::fill_missing_sysvar_cache_entries_from_accounts(&mut sysvar_cache, callbacks);
1411    }
1412
1413    fn fill_missing_sysvar_cache_entries_from_accounts<CB: TransactionProcessingCallback>(
1414        sysvar_cache: &mut SysvarCache,
1415        callbacks: &CB,
1416    ) {
1417        sysvar_cache.fill_missing_entries(|pubkey, set_sysvar| {
1418            if let Some(account) = callbacks.get_account_shared_data(pubkey) {
1419                set_sysvar(account.data());
1420            }
1421        });
1422    }
1423
1424    pub fn reset_sysvar_cache(&self) {
1425        let mut sysvar_cache = self.sysvar_cache.write().unwrap();
1426        sysvar_cache.reset();
1427    }
1428
1429    pub fn get_sysvar_cache_for_tests(&self) -> SysvarCache {
1430        self.sysvar_cache.read().unwrap().clone()
1431    }
1432
1433    /// Add a built-in program to this fork.
1434    ///
1435    /// A builtin program should _never_ be inserted directly into the global
1436    /// program cache without updating the fork guard: `builtin_program_ids`
1437    /// and `builtin_program_cache`.
1438    pub fn add_builtin(&self, program_id: Pubkey, builtin: ProgramCacheEntry) {
1439        self.builtin_program_ids.write().unwrap().insert(program_id);
1440        let entry = Arc::new(builtin);
1441        self.global_program_cache.write().unwrap().assign_program(
1442            &self.program_runtime_environment,
1443            program_id,
1444            0,
1445            Arc::clone(&entry),
1446        );
1447        self.builtin_program_cache
1448            .write()
1449            .unwrap()
1450            .replenish(program_id, entry);
1451    }
1452
1453    /// Remove a builtin-program from this fork.
1454    ///
1455    /// This removes the builtin from the fork guard (`builtin_program_ids` and
1456    /// `builtin_program_cache`), but it does not remove it from the global
1457    /// program cache. Another fork could be relying on the global entry.
1458    pub fn remove_builtin(&self, program_id: &Pubkey) {
1459        self.builtin_program_cache
1460            .write()
1461            .unwrap()
1462            .remove_entry(program_id);
1463        self.builtin_program_ids.write().unwrap().remove(program_id);
1464    }
1465
1466    #[cfg(feature = "dev-context-only-utils")]
1467    #[cfg_attr(feature = "dev-context-only-utils", qualifiers(pub))]
1468    fn writable_sysvar_cache(&self) -> &RwLock<SysvarCache> {
1469        &self.sysvar_cache
1470    }
1471}
1472
1473#[cfg(test)]
1474mod tests {
1475    #[allow(deprecated)]
1476    use solana_sysvar::fees::Fees;
1477    use {
1478        super::*,
1479        crate::{
1480            account_loader::{
1481                LoadedTransactionAccount, TRANSACTION_ACCOUNT_BASE_SIZE,
1482                ValidatedTransactionDetails,
1483            },
1484            nonce_info::NonceInfo,
1485            program_loader::test_utils::*,
1486            rent_calculator::RENT_EXEMPT_RENT_EPOCH,
1487            rollback_accounts::RollbackAccounts,
1488        },
1489        solana_account::WritableAccount,
1490        solana_clock::Clock,
1491        solana_compute_budget_interface::ComputeBudgetInstruction,
1492        solana_epoch_schedule::EpochSchedule,
1493        solana_fee_calculator::FeeCalculator,
1494        solana_fee_structure::FeeDetails,
1495        solana_hash::Hash,
1496        solana_loader_v4_interface::state::{LoaderV4State, LoaderV4Status},
1497        solana_message::{LegacyMessage, Message, MessageHeader, SanitizedMessage},
1498        solana_nonce as nonce,
1499        solana_program_runtime::{
1500            execution_budget::{
1501                SVMTransactionExecutionAndFeeBudgetLimits, SVMTransactionExecutionBudget,
1502            },
1503            invoke_context::BuiltinFunctionRegisterer,
1504            loaded_programs::BlockRelation,
1505            program_cache_entry::ProgramCacheEntryType,
1506        },
1507        solana_rent::Rent,
1508        solana_sbpf::vm,
1509        solana_sdk_ids::{
1510            bpf_loader, bpf_loader_upgradeable, loader_v4, native_loader, system_program, sysvar,
1511        },
1512        solana_signature::Signature,
1513        solana_svm_callback::{AccountState, InvokeContextCallback},
1514        solana_svm_type_overrides::sync::atomic::Ordering,
1515        solana_system_interface::instruction as system_instruction,
1516        solana_sysvar_id::SysvarId,
1517        solana_transaction::sanitized::SanitizedTransaction,
1518        solana_transaction_context::transaction::TransactionContext,
1519        solana_transaction_error::TransactionError,
1520        std::{borrow::Cow, collections::HashMap},
1521        test_case::test_case,
1522    };
1523
1524    fn create_sysvar_account<T>(value: &T) -> AccountSharedData
1525    where
1526        T: wincode::Serialize<Src = T> + SysvarId,
1527    {
1528        let serialized_len = wincode::serialized_size(value).unwrap() as usize;
1529        let canonical_data_len = match T::id() {
1530            sysvar::clock::ID => solana_clock::SIZE,
1531            sysvar::epoch_schedule::ID => solana_epoch_schedule::SIZE,
1532            sysvar::fees::ID => solana_sysvar::fees::SIZE,
1533            sysvar::rent::ID => solana_rent::SIZE,
1534            id => panic!("unsupported sysvar: {id}"),
1535        };
1536        let required_data_len = canonical_data_len.max(serialized_len);
1537        let mut account = AccountSharedData::new(1, required_data_len, &sysvar::id());
1538        wincode::serialize_into(account.data_as_mut_slice(), value).unwrap();
1539        account
1540    }
1541
1542    fn new_unchecked_sanitized_message(message: Message) -> SanitizedMessage {
1543        SanitizedMessage::Legacy(LegacyMessage::new(message, &HashSet::new()))
1544    }
1545
1546    struct TestForkGraph {}
1547
1548    impl ForkGraph for TestForkGraph {
1549        fn relationship(&self, _a: Slot, _b: Slot) -> BlockRelation {
1550            BlockRelation::Unknown
1551        }
1552    }
1553
1554    #[derive(Clone)]
1555    struct MockBankCallback {
1556        account_shared_data: Arc<RwLock<HashMap<Pubkey, AccountSharedData>>>,
1557        #[allow(clippy::type_complexity)]
1558        inspected_accounts:
1559            Arc<RwLock<HashMap<Pubkey, Vec<(Option<AccountSharedData>, /* is_writable */ bool)>>>>,
1560        feature_set: SVMFeatureSet,
1561    }
1562
1563    impl Default for MockBankCallback {
1564        fn default() -> Self {
1565            Self {
1566                account_shared_data: Arc::default(),
1567                inspected_accounts: Arc::default(),
1568                feature_set: SVMFeatureSet::all_enabled(),
1569            }
1570        }
1571    }
1572
1573    impl InvokeContextCallback for MockBankCallback {}
1574
1575    impl TransactionProcessingCallback for MockBankCallback {
1576        fn get_account_shared_data(&self, pubkey: &Pubkey) -> Option<AccountSharedData> {
1577            self.account_shared_data
1578                .read()
1579                .unwrap()
1580                .get(pubkey)
1581                .cloned()
1582        }
1583
1584        fn inspect_account(
1585            &self,
1586            address: &Pubkey,
1587            account_state: AccountState,
1588            is_writable: bool,
1589        ) {
1590            let account = match account_state {
1591                AccountState::Dead => None,
1592                AccountState::Alive(account) => Some(account.clone()),
1593            };
1594            self.inspected_accounts
1595                .write()
1596                .unwrap()
1597                .entry(*address)
1598                .or_default()
1599                .push((account, is_writable));
1600        }
1601    }
1602
1603    impl MockBankCallback {
1604        pub fn calculate_fee_details(
1605            message: &impl SVMMessage,
1606            lamports_per_signature: u64,
1607            prioritization_fee: u64,
1608        ) -> FeeDetails {
1609            let signature_count = message
1610                .num_transaction_signatures()
1611                .saturating_add(message.num_ed25519_signatures())
1612                .saturating_add(message.num_secp256k1_signatures())
1613                .saturating_add(message.num_secp256r1_signatures());
1614
1615            FeeDetails::new(
1616                signature_count.saturating_mul(lamports_per_signature),
1617                prioritization_fee,
1618            )
1619        }
1620    }
1621
1622    impl<'a> From<&'a MockBankCallback> for AccountLoader<'a, MockBankCallback> {
1623        fn from(callbacks: &'a MockBankCallback) -> AccountLoader<'a, MockBankCallback> {
1624            AccountLoader::new_with_loaded_accounts_capacity(
1625                None,
1626                callbacks,
1627                &callbacks.feature_set,
1628                0,
1629            )
1630        }
1631    }
1632
1633    #[test_case(1; "Check results too small")]
1634    #[test_case(3; "Check results too large")]
1635    #[should_panic(expected = "Length of check_results does not match length of sanitized_txs")]
1636    fn test_check_results_txs_length_mismatch(check_results_len: usize) {
1637        let sanitized_message = new_unchecked_sanitized_message(Message {
1638            account_keys: vec![Pubkey::new_from_array([0; 32])],
1639            header: MessageHeader::default(),
1640            instructions: vec![CompiledInstruction {
1641                program_id_index: 0,
1642                accounts: vec![],
1643                data: vec![],
1644            }],
1645            recent_blockhash: Hash::default(),
1646        });
1647
1648        // Transactions, length 2.
1649        let sanitized_txs = vec![
1650            SanitizedTransaction::new_for_tests(
1651                sanitized_message,
1652                vec![Signature::new_unique()],
1653                false,
1654            );
1655            2
1656        ];
1657
1658        let check_results = vec![
1659            TransactionCheckResult::Ok(CheckedTransactionDetails::default());
1660            check_results_len
1661        ];
1662
1663        let batch_processor = TransactionBatchProcessor::<TestForkGraph>::default();
1664        let callback = MockBankCallback::default();
1665
1666        batch_processor.load_and_execute_sanitized_transactions(
1667            &callback,
1668            &sanitized_txs,
1669            check_results,
1670            &get_mock_transaction_processing_environment(),
1671            &TransactionProcessingConfig::default(),
1672        );
1673    }
1674
1675    #[test]
1676    fn test_inner_instructions_list_from_instruction_trace() {
1677        let mut transaction_context = TransactionContext::new(
1678            vec![(
1679                Pubkey::new_unique(),
1680                AccountSharedData::new(1, 1, &bpf_loader::ID),
1681            )],
1682            Rent::default(),
1683            4,
1684            11,
1685            4,
1686        );
1687        let num_transaction_accounts = usize::from(transaction_context.get_number_of_accounts());
1688
1689        // Four top level instructions
1690        for i in 0..4 {
1691            transaction_context
1692                .configure_instruction_at_index(
1693                    i,
1694                    0,
1695                    vec![],
1696                    vec![u8::MAX; num_transaction_accounts],
1697                    Cow::Owned(vec![i as u8]),
1698                    None,
1699                )
1700                .unwrap();
1701        }
1702
1703        // Execute ix #0
1704        transaction_context.push().unwrap();
1705        // ix #0 does a CPI
1706        transaction_context
1707            .configure_next_cpi_for_tests(0, vec![], vec![0, 0])
1708            .unwrap();
1709        transaction_context.push().unwrap();
1710        // Returning from everything
1711        transaction_context.pop().unwrap();
1712        transaction_context.pop().unwrap();
1713        // Execute ix #1
1714        transaction_context.push().unwrap();
1715        transaction_context.pop().unwrap();
1716        // Execute ix #2
1717        transaction_context.push().unwrap();
1718        // ix #2 does a CPI
1719        transaction_context
1720            .configure_next_cpi_for_tests(0, vec![], vec![2, 0])
1721            .unwrap();
1722        transaction_context.push().unwrap();
1723        // A nested CPI
1724        transaction_context
1725            .configure_next_cpi_for_tests(0, vec![], vec![2, 1])
1726            .unwrap();
1727        transaction_context.push().unwrap();
1728        // Return from nested CPI
1729        transaction_context.pop().unwrap();
1730        // Return from CPI
1731        transaction_context.pop().unwrap();
1732        // ix #2 does another CPI
1733        transaction_context
1734            .configure_next_cpi_for_tests(0, vec![], vec![2, 2])
1735            .unwrap();
1736        transaction_context.push().unwrap();
1737        // Return from everything related to ix #2
1738        transaction_context.pop().unwrap();
1739        transaction_context.pop().unwrap();
1740        // Execute ix #3
1741        transaction_context.push().unwrap();
1742        // ix #3 does a CPI
1743        transaction_context
1744            .configure_next_cpi_for_tests(0, vec![], vec![3, 0])
1745            .unwrap();
1746        transaction_context.push().unwrap();
1747        // ix #3 does a nested CPI
1748        transaction_context
1749            .configure_next_cpi_for_tests(0, vec![], vec![3, 1])
1750            .unwrap();
1751        transaction_context.push().unwrap();
1752        // ix #3 does a second nested CPI
1753        transaction_context
1754            .configure_next_cpi_for_tests(0, vec![], vec![3, 2])
1755            .unwrap();
1756        transaction_context.push().unwrap();
1757        // Return from everything related to ix #3
1758        transaction_context.pop().unwrap();
1759        transaction_context.pop().unwrap();
1760        transaction_context.pop().unwrap();
1761        transaction_context.pop().unwrap();
1762
1763        let inner_instructions =
1764            TransactionBatchProcessor::<TestForkGraph>::deconstruct_transaction(
1765                transaction_context,
1766                true,
1767            )
1768            .1
1769            .unwrap();
1770
1771        assert_eq!(
1772            inner_instructions,
1773            vec![
1774                vec![InnerInstruction {
1775                    instruction: CompiledInstruction::new_from_raw_parts(0, vec![0, 0], vec![]),
1776                    stack_height: 2,
1777                }],
1778                vec![],
1779                vec![
1780                    InnerInstruction {
1781                        instruction: CompiledInstruction::new_from_raw_parts(0, vec![2, 0], vec![]),
1782                        stack_height: 2,
1783                    },
1784                    InnerInstruction {
1785                        instruction: CompiledInstruction::new_from_raw_parts(0, vec![2, 1], vec![]),
1786                        stack_height: 3,
1787                    },
1788                    InnerInstruction {
1789                        instruction: CompiledInstruction::new_from_raw_parts(0, vec![2, 2], vec![]),
1790                        stack_height: 2,
1791                    },
1792                ],
1793                vec![
1794                    InnerInstruction {
1795                        instruction: CompiledInstruction::new_from_raw_parts(0, vec![3, 0], vec![]),
1796                        stack_height: 2,
1797                    },
1798                    InnerInstruction {
1799                        instruction: CompiledInstruction::new_from_raw_parts(0, vec![3, 1], vec![]),
1800                        stack_height: 3,
1801                    },
1802                    InnerInstruction {
1803                        instruction: CompiledInstruction::new_from_raw_parts(0, vec![3, 2], vec![]),
1804                        stack_height: 4,
1805                    },
1806                ]
1807            ]
1808        );
1809    }
1810
1811    #[test]
1812    fn test_execute_loaded_transaction_recordings() {
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 message = Message {
1817            account_keys: vec![Pubkey::new_from_array([0; 32])],
1818            header: MessageHeader::default(),
1819            instructions: vec![CompiledInstruction {
1820                program_id_index: 0,
1821                accounts: vec![],
1822                data: vec![],
1823            }],
1824            recent_blockhash: Hash::default(),
1825        };
1826
1827        let sanitized_message = new_unchecked_sanitized_message(message);
1828        let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
1829        let batch_processor = TransactionBatchProcessor::<TestForkGraph>::default();
1830
1831        let sanitized_transaction = SanitizedTransaction::new_for_tests(
1832            sanitized_message,
1833            vec![Signature::new_unique()],
1834            false,
1835        );
1836
1837        let loaded_transaction = LoadedTransaction {
1838            accounts: vec![(Pubkey::new_unique(), AccountSharedData::default())],
1839            touched_flags: Box::default(),
1840            fee_details: FeeDetails::default(),
1841            rollback_accounts: RollbackAccounts::default(),
1842            compute_budget: SVMTransactionExecutionBudget::default(),
1843            loaded_accounts_data_size: 32,
1844        };
1845
1846        let processing_environment = get_mock_transaction_processing_environment();
1847
1848        let mut processing_config = TransactionProcessingConfig::default();
1849        processing_config.recording_config.enable_log_recording = true;
1850
1851        let mock_bank = MockBankCallback::default();
1852        let sysvar_cache = batch_processor.sysvar_cache();
1853
1854        let executed_tx = batch_processor.execute_loaded_transaction(
1855            &mock_bank,
1856            &sanitized_transaction,
1857            &sysvar_cache,
1858            loaded_transaction.clone(),
1859            &mut ExecuteTimings::default(),
1860            &mut TransactionErrorMetrics::default(),
1861            &mut program_cache_for_tx_batch,
1862            &processing_environment,
1863            &processing_config,
1864        );
1865        assert!(executed_tx.execution_details.log_messages.is_some());
1866
1867        processing_config.log_messages_bytes_limit = Some(2);
1868
1869        let executed_tx = batch_processor.execute_loaded_transaction(
1870            &mock_bank,
1871            &sanitized_transaction,
1872            &sysvar_cache,
1873            loaded_transaction.clone(),
1874            &mut ExecuteTimings::default(),
1875            &mut TransactionErrorMetrics::default(),
1876            &mut program_cache_for_tx_batch,
1877            &processing_environment,
1878            &processing_config,
1879        );
1880        assert!(executed_tx.execution_details.log_messages.is_some());
1881        assert!(executed_tx.execution_details.inner_instructions.is_none());
1882
1883        processing_config.recording_config.enable_log_recording = false;
1884        processing_config.recording_config.enable_cpi_recording = true;
1885        processing_config.log_messages_bytes_limit = None;
1886
1887        let executed_tx = batch_processor.execute_loaded_transaction(
1888            &mock_bank,
1889            &sanitized_transaction,
1890            &sysvar_cache,
1891            loaded_transaction,
1892            &mut ExecuteTimings::default(),
1893            &mut TransactionErrorMetrics::default(),
1894            &mut program_cache_for_tx_batch,
1895            &processing_environment,
1896            &processing_config,
1897        );
1898
1899        assert!(executed_tx.execution_details.log_messages.is_none());
1900        assert!(executed_tx.execution_details.inner_instructions.is_some());
1901    }
1902
1903    #[test]
1904    fn test_execute_loaded_transaction_error_metrics() {
1905        // Setting all the arguments correctly is too burdensome for testing
1906        // execute_loaded_transaction separately.This function will be tested in an integration
1907        // test with load_and_execute_sanitized_transactions
1908        let key1 = Pubkey::new_unique();
1909        let key2 = Pubkey::new_unique();
1910        let message = Message {
1911            account_keys: vec![key1, key2],
1912            header: MessageHeader::default(),
1913            instructions: vec![CompiledInstruction {
1914                program_id_index: 0,
1915                accounts: vec![1],
1916                data: vec![],
1917            }],
1918            recent_blockhash: Hash::default(),
1919        };
1920
1921        let sanitized_message = new_unchecked_sanitized_message(message);
1922        let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
1923        let batch_processor = TransactionBatchProcessor::<TestForkGraph>::default();
1924
1925        let sanitized_transaction = SanitizedTransaction::new_for_tests(
1926            sanitized_message,
1927            vec![Signature::new_unique()],
1928            false,
1929        );
1930
1931        let loaded_transaction = LoadedTransaction {
1932            accounts: vec![
1933                (key1, AccountSharedData::default()),
1934                (key2, AccountSharedData::default()),
1935            ],
1936            touched_flags: Box::default(),
1937            fee_details: FeeDetails::default(),
1938            rollback_accounts: RollbackAccounts::default(),
1939            compute_budget: SVMTransactionExecutionBudget::default(),
1940            loaded_accounts_data_size: 0,
1941        };
1942
1943        let processing_config = TransactionProcessingConfig {
1944            recording_config: ExecutionRecordingConfig::new_single_setting(false),
1945            ..Default::default()
1946        };
1947        let mut error_metrics = TransactionErrorMetrics::new();
1948        let mock_bank = MockBankCallback::default();
1949        let sysvar_cache = batch_processor.sysvar_cache();
1950
1951        let _ = batch_processor.execute_loaded_transaction(
1952            &mock_bank,
1953            &sanitized_transaction,
1954            &sysvar_cache,
1955            loaded_transaction,
1956            &mut ExecuteTimings::default(),
1957            &mut error_metrics,
1958            &mut program_cache_for_tx_batch,
1959            &get_mock_transaction_processing_environment(),
1960            &processing_config,
1961        );
1962
1963        assert_eq!(error_metrics.instruction_error.0, 1);
1964    }
1965
1966    #[test]
1967    #[allow(deprecated)]
1968    fn test_sysvar_cache_initialization1() {
1969        let mock_bank = MockBankCallback::default();
1970
1971        let clock = Clock {
1972            slot: 1,
1973            epoch_start_timestamp: 2,
1974            epoch: 3,
1975            leader_schedule_epoch: 4,
1976            unix_timestamp: 5,
1977        };
1978        let clock_account = create_sysvar_account(&clock);
1979        mock_bank
1980            .account_shared_data
1981            .write()
1982            .unwrap()
1983            .insert(sysvar::clock::id(), clock_account);
1984
1985        let epoch_schedule = EpochSchedule::custom(64, 2, true);
1986        let epoch_schedule_account = create_sysvar_account(&epoch_schedule);
1987        mock_bank
1988            .account_shared_data
1989            .write()
1990            .unwrap()
1991            .insert(sysvar::epoch_schedule::id(), epoch_schedule_account);
1992
1993        let fees = Fees {
1994            fee_calculator: FeeCalculator {
1995                lamports_per_signature: 123,
1996            },
1997        };
1998        let fees_account = create_sysvar_account(&fees);
1999        mock_bank
2000            .account_shared_data
2001            .write()
2002            .unwrap()
2003            .insert(sysvar::fees::id(), fees_account);
2004
2005        let rent = Rent::default();
2006        let rent_account = create_sysvar_account(&rent);
2007        mock_bank
2008            .account_shared_data
2009            .write()
2010            .unwrap()
2011            .insert(sysvar::rent::id(), rent_account);
2012
2013        let transaction_processor = TransactionBatchProcessor::<TestForkGraph>::default();
2014        transaction_processor.fill_missing_sysvar_cache_entries(&mock_bank);
2015
2016        let sysvar_cache = transaction_processor.sysvar_cache.read().unwrap();
2017        let cached_clock = sysvar_cache.get_clock();
2018        let cached_epoch_schedule = sysvar_cache.get_epoch_schedule();
2019        let cached_fees = sysvar_cache.get_fees();
2020        let cached_rent = sysvar_cache.get_rent();
2021
2022        assert_eq!(
2023            cached_clock.expect("clock sysvar missing in cache"),
2024            clock.into()
2025        );
2026        assert_eq!(
2027            cached_epoch_schedule.expect("epoch_schedule sysvar missing in cache"),
2028            epoch_schedule.into()
2029        );
2030        assert_eq!(
2031            cached_fees.expect("fees sysvar missing in cache"),
2032            fees.into()
2033        );
2034        assert_eq!(
2035            cached_rent.expect("rent sysvar missing in cache"),
2036            rent.into()
2037        );
2038        assert!(sysvar_cache.get_slot_hashes().is_err());
2039        assert!(sysvar_cache.get_epoch_rewards().is_err());
2040    }
2041
2042    #[test]
2043    #[allow(deprecated)]
2044    fn test_reset_and_fill_sysvar_cache() {
2045        let mock_bank = MockBankCallback::default();
2046
2047        let clock = Clock {
2048            slot: 1,
2049            epoch_start_timestamp: 2,
2050            epoch: 3,
2051            leader_schedule_epoch: 4,
2052            unix_timestamp: 5,
2053        };
2054        let clock_account = create_sysvar_account(&clock);
2055        mock_bank
2056            .account_shared_data
2057            .write()
2058            .unwrap()
2059            .insert(sysvar::clock::id(), clock_account);
2060
2061        let epoch_schedule = EpochSchedule::custom(64, 2, true);
2062        let epoch_schedule_account = create_sysvar_account(&epoch_schedule);
2063        mock_bank
2064            .account_shared_data
2065            .write()
2066            .unwrap()
2067            .insert(sysvar::epoch_schedule::id(), epoch_schedule_account);
2068
2069        let fees = Fees {
2070            fee_calculator: FeeCalculator {
2071                lamports_per_signature: 123,
2072            },
2073        };
2074        let fees_account = create_sysvar_account(&fees);
2075        mock_bank
2076            .account_shared_data
2077            .write()
2078            .unwrap()
2079            .insert(sysvar::fees::id(), fees_account);
2080
2081        let rent = Rent::default();
2082        let rent_account = create_sysvar_account(&rent);
2083        mock_bank
2084            .account_shared_data
2085            .write()
2086            .unwrap()
2087            .insert(sysvar::rent::id(), rent_account);
2088
2089        let transaction_processor = TransactionBatchProcessor::<TestForkGraph>::default();
2090        // Fill the sysvar cache
2091        transaction_processor.fill_missing_sysvar_cache_entries(&mock_bank);
2092
2093        let updated_clock = Clock {
2094            slot: 6,
2095            epoch_start_timestamp: 7,
2096            epoch: 8,
2097            leader_schedule_epoch: 9,
2098            unix_timestamp: 10,
2099        };
2100        let updated_clock_account = create_sysvar_account(&updated_clock);
2101        mock_bank
2102            .account_shared_data
2103            .write()
2104            .unwrap()
2105            .insert(sysvar::clock::id(), updated_clock_account);
2106        transaction_processor.reset_and_fill_sysvar_cache_entries(&mock_bank);
2107        {
2108            let sysvar_cache = transaction_processor.sysvar_cache.read().unwrap();
2109            assert_eq!(
2110                sysvar_cache
2111                    .get_clock()
2112                    .expect("clock sysvar missing in cache"),
2113                updated_clock.clone().into()
2114            );
2115        }
2116
2117        // Reset the sysvar cache
2118        transaction_processor.reset_sysvar_cache();
2119
2120        {
2121            let sysvar_cache = transaction_processor.sysvar_cache.read().unwrap();
2122            // Test that sysvar cache is empty and none of the values are found
2123            assert!(sysvar_cache.get_clock().is_err());
2124            assert!(sysvar_cache.get_epoch_schedule().is_err());
2125            assert!(sysvar_cache.get_fees().is_err());
2126            assert!(sysvar_cache.get_epoch_rewards().is_err());
2127            assert!(sysvar_cache.get_rent().is_err());
2128            assert!(sysvar_cache.get_epoch_rewards().is_err());
2129        }
2130
2131        // Refill the cache and test the values are available.
2132        transaction_processor.fill_missing_sysvar_cache_entries(&mock_bank);
2133
2134        let sysvar_cache = transaction_processor.sysvar_cache.read().unwrap();
2135        let cached_clock = sysvar_cache.get_clock();
2136        let cached_epoch_schedule = sysvar_cache.get_epoch_schedule();
2137        let cached_fees = sysvar_cache.get_fees();
2138        let cached_rent = sysvar_cache.get_rent();
2139
2140        assert_eq!(
2141            cached_clock.expect("clock sysvar missing in cache"),
2142            updated_clock.into()
2143        );
2144        assert_eq!(
2145            cached_epoch_schedule.expect("epoch_schedule sysvar missing in cache"),
2146            epoch_schedule.into()
2147        );
2148        assert_eq!(
2149            cached_fees.expect("fees sysvar missing in cache"),
2150            fees.into()
2151        );
2152        assert_eq!(
2153            cached_rent.expect("rent sysvar missing in cache"),
2154            rent.into()
2155        );
2156        assert!(sysvar_cache.get_slot_hashes().is_err());
2157        assert!(sysvar_cache.get_epoch_rewards().is_err());
2158    }
2159
2160    #[test]
2161    fn test_add_builtin() {
2162        let fork_graph = Arc::new(RwLock::new(TestForkGraph {}));
2163        let batch_processor =
2164            TransactionBatchProcessor::new(0, 0, Arc::downgrade(&fork_graph), None);
2165
2166        let key = Pubkey::new_unique();
2167        let register_fn: BuiltinFunctionRegisterer = |p, n| {
2168            p.register_function(
2169                n,
2170                (
2171                    |_invoke_context, _param0, _param1, _param2, _param3, _param4| {},
2172                    |_| {},
2173                ),
2174            )
2175        };
2176        let program = ProgramCacheEntry::new_builtin(register_fn);
2177        batch_processor.add_builtin(key, program);
2178
2179        let mut loaded_programs_for_tx_batch = ProgramCacheForTxBatch::new(0);
2180        let program_runtime_environment =
2181            batch_processor.program_runtime_environment_for_epoch(batch_processor.epoch);
2182        batch_processor
2183            .global_program_cache
2184            .write()
2185            .unwrap()
2186            .extract(
2187                &mut vec![ProgramToLoad {
2188                    program_id: &key,
2189                    loader: ProgramCacheEntryOwner::NativeLoader,
2190                    deployment_slot: 0,
2191                }],
2192                &mut loaded_programs_for_tx_batch,
2193                &program_runtime_environment,
2194                true,
2195                true,
2196            );
2197        let entry = loaded_programs_for_tx_batch.find(&key).unwrap();
2198
2199        // Repeating code because ProgramCacheEntry does not implement clone.
2200        let program = ProgramCacheEntry::new_builtin(register_fn);
2201        assert_eq!(entry, Arc::new(program));
2202    }
2203
2204    #[test]
2205    fn test_builtin_in_global_cache_but_not_in_fork_is_not_extracted() {
2206        let fork_graph = Arc::new(RwLock::new(TestForkGraph {}));
2207        let batch_processor =
2208            TransactionBatchProcessor::new(0, 0, Arc::downgrade(&fork_graph), None);
2209
2210        let key = Pubkey::new_unique();
2211        let register_fn: BuiltinFunctionRegisterer = |p, n| {
2212            p.register_function(
2213                n,
2214                (
2215                    |_invoke_context, _param0, _param1, _param2, _param3, _param4| {},
2216                    |_| {},
2217                ),
2218            )
2219        };
2220
2221        // Insert the builtin into the global program cache only, but bypass the
2222        // `builtin_program_ids` fork guard.
2223        batch_processor
2224            .global_program_cache
2225            .write()
2226            .unwrap()
2227            .assign_program(
2228                &batch_processor.program_runtime_environment,
2229                key,
2230                0,
2231                Arc::new(ProgramCacheEntry::new_builtin(register_fn)),
2232            );
2233        assert!(
2234            !batch_processor
2235                .builtin_program_ids
2236                .read()
2237                .unwrap()
2238                .contains(&key)
2239        );
2240
2241        // Roll over into another slot.
2242        // Assert this bank does not pick up the inserted builtin.
2243        let next_slot = batch_processor.new_from(1, 0);
2244        assert!(!next_slot.builtin_program_ids.read().unwrap().contains(&key));
2245        assert!(
2246            next_slot
2247                .builtin_program_cache
2248                .read()
2249                .unwrap()
2250                .find(&key)
2251                .is_none()
2252        );
2253
2254        // Now register the address on this fork.
2255        // Assert the builtin is still not available without a slot rollover.
2256        batch_processor
2257            .builtin_program_ids
2258            .write()
2259            .unwrap()
2260            .insert(key);
2261        assert!(
2262            next_slot
2263                .builtin_program_cache
2264                .read()
2265                .unwrap()
2266                .find(&key)
2267                .is_none()
2268        );
2269
2270        // Now roll over into another slot.
2271        // Assert the builtin was extracted from the global cache.
2272        let next_slot = batch_processor.new_from(2, 0);
2273        let entry = next_slot
2274            .builtin_program_cache
2275            .read()
2276            .unwrap()
2277            .find(&key)
2278            .unwrap();
2279        assert_eq!(entry, Arc::new(ProgramCacheEntry::new_builtin(register_fn)));
2280    }
2281
2282    #[test]
2283    fn test_builtin_not_in_fork_is_never_searched_for() {
2284        let fork_graph = Arc::new(RwLock::new(TestForkGraph {}));
2285        let batch_processor =
2286            TransactionBatchProcessor::new(0, 0, Arc::downgrade(&fork_graph), None);
2287
2288        let key = Pubkey::new_unique();
2289        let register_fn: BuiltinFunctionRegisterer = |p, n| {
2290            p.register_function(
2291                n,
2292                (
2293                    |_invoke_context, _param0, _param1, _param2, _param3, _param4| {},
2294                    |_| {},
2295                ),
2296            )
2297        };
2298
2299        // Insert a builtin into the global program cache.
2300        // This is a builtin added by another fork, but not by ours.
2301        batch_processor
2302            .global_program_cache
2303            .write()
2304            .unwrap()
2305            .assign_program(
2306                &batch_processor.program_runtime_environment,
2307                key,
2308                0,
2309                Arc::new(ProgramCacheEntry::new_builtin(register_fn)),
2310            );
2311
2312        // Assert that a transaction naming the builtin's address does not
2313        // tamper with the global program cache.
2314        let mock_bank = MockBankCallback::default();
2315        for account_exists in [false, true] {
2316            if account_exists {
2317                // Even if the account exists owned by the native loader,
2318                // the invariant should hold.
2319                mock_bank
2320                    .account_shared_data
2321                    .write()
2322                    .unwrap()
2323                    .insert(key, AccountSharedData::new(1, 1, &native_loader::id()));
2324            }
2325
2326            let program_cache_for_tx_batch = batch_processor
2327                .builtin_program_cache
2328                .read()
2329                .unwrap()
2330                .clone();
2331            assert!(program_cache_for_tx_batch.find(&key).is_none());
2332
2333            let search_for = filter_executable_program_accounts(
2334                &mock_bank,
2335                &program_cache_for_tx_batch,
2336                std::iter::once(&key),
2337            );
2338            assert!(search_for.is_empty());
2339        }
2340    }
2341
2342    #[test]
2343    fn test_core_bpf_migration_of_a_builtin() {
2344        const BUILTIN_SLOT: Slot = 0;
2345        const MIGRATION_SLOT: Slot = 10;
2346        const NEXT_SLOT: Slot = 11;
2347
2348        struct SingleChainForkGraph;
2349        impl ForkGraph for SingleChainForkGraph {
2350            fn relationship(&self, a: Slot, b: Slot) -> BlockRelation {
2351                if a == b {
2352                    BlockRelation::Equal
2353                } else if a < b {
2354                    BlockRelation::Ancestor
2355                } else {
2356                    BlockRelation::Descendant
2357                }
2358            }
2359        }
2360
2361        let fork_graph = Arc::new(RwLock::new(SingleChainForkGraph));
2362        let batch_processor =
2363            TransactionBatchProcessor::new(BUILTIN_SLOT, 0, Arc::downgrade(&fork_graph), None);
2364
2365        let key = Pubkey::new_unique();
2366        let register_fn: BuiltinFunctionRegisterer = |p, n| {
2367            p.register_function(
2368                n,
2369                (
2370                    |_invoke_context, _param0, _param1, _param2, _param3, _param4| {},
2371                    |_| {},
2372                ),
2373            )
2374        };
2375
2376        // Start with a properly guarded builtin: registered on the fork and
2377        // present in the global program cache.
2378        batch_processor.add_builtin(key, ProgramCacheEntry::new_builtin(register_fn));
2379        assert!(
2380            batch_processor
2381                .builtin_program_ids
2382                .read()
2383                .unwrap()
2384                .contains(&key)
2385        );
2386
2387        // Simulate migrating the builtin program to Core BPF: "deploy" the
2388        // program by assigning it a new Unloaded entry owned by Loader V3 in
2389        // the global program cache, then remove it from `builtin_program_ids`.
2390        batch_processor
2391            .global_program_cache
2392            .write()
2393            .unwrap()
2394            .assign_program(
2395                &batch_processor.program_runtime_environment,
2396                key,
2397                MIGRATION_SLOT,
2398                Arc::new(ProgramCacheEntry::new_unloaded(
2399                    MIGRATION_SLOT,
2400                    ProgramCacheEntryOwner::LoaderV3,
2401                    batch_processor.program_runtime_environment.clone(),
2402                )),
2403            );
2404        batch_processor.remove_builtin(&key);
2405
2406        // The builtin leaves the batch cache in the same slot.
2407        let program_cache_for_tx_batch = batch_processor
2408            .builtin_program_cache
2409            .read()
2410            .unwrap()
2411            .clone();
2412        assert!(program_cache_for_tx_batch.find(&key).is_none());
2413
2414        // The new account state seeds the extraction search, and since the
2415        // program was just "deployed" in this slot, we get a delayed visibility
2416        // tombstone.
2417        let mut search_for = vec![ProgramToLoad {
2418            program_id: &key,
2419            loader: ProgramCacheEntryOwner::LoaderV3,
2420            deployment_slot: MIGRATION_SLOT,
2421        }];
2422        let mut extracted = ProgramCacheForTxBatch::new(MIGRATION_SLOT);
2423        batch_processor
2424            .global_program_cache
2425            .read()
2426            .unwrap()
2427            .extract(
2428                &mut search_for,
2429                &mut extracted,
2430                &batch_processor.program_runtime_environment,
2431                false,
2432                false,
2433            );
2434        assert!(matches!(
2435            extracted.find(&key).unwrap().program,
2436            ProgramCacheEntryType::DelayVisibility
2437        ));
2438
2439        // Rolling to the next slot rebuilds the batch cache from the fork guard,
2440        // which no longer lists the address, so the builtin is gone.
2441        let next_slot = batch_processor.new_from(NEXT_SLOT, 0);
2442        assert!(
2443            next_slot
2444                .builtin_program_cache
2445                .read()
2446                .unwrap()
2447                .find(&key)
2448                .is_none()
2449        );
2450
2451        // A caller reporting the deployed slot now reaches the migrated program's
2452        // entry, which is unloaded, so the cache signals a reload from account
2453        // state. Crucially the builtin is not served in its place.
2454        let mut search_for = vec![ProgramToLoad {
2455            program_id: &key,
2456            loader: ProgramCacheEntryOwner::LoaderV3,
2457            deployment_slot: MIGRATION_SLOT,
2458        }];
2459        let mut extracted = ProgramCacheForTxBatch::new(NEXT_SLOT);
2460        next_slot.global_program_cache.read().unwrap().extract(
2461            &mut search_for,
2462            &mut extracted,
2463            &next_slot.program_runtime_environment,
2464            false,
2465            false,
2466        );
2467        assert!(extracted.find(&key).is_none());
2468        assert_eq!(search_for.len(), 1);
2469
2470        // The builtin entry does survive in the global program cache, but keyed at
2471        // slot 0 under `NativeLoader`, which the lookup above cannot reach.
2472        let mut search_for = vec![ProgramToLoad {
2473            program_id: &key,
2474            loader: ProgramCacheEntryOwner::NativeLoader,
2475            deployment_slot: BUILTIN_SLOT,
2476        }];
2477        let mut extracted = ProgramCacheForTxBatch::new(NEXT_SLOT);
2478        next_slot.global_program_cache.read().unwrap().extract(
2479            &mut search_for,
2480            &mut extracted,
2481            &next_slot.program_runtime_environment,
2482            false,
2483            false,
2484        );
2485        assert!(matches!(
2486            extracted.find(&key).unwrap().program,
2487            ProgramCacheEntryType::Builtin(_)
2488        ));
2489    }
2490
2491    #[test]
2492    #[should_panic(expected = "missing from the global program cache")]
2493    fn test_builtin_in_fork_but_not_in_global_cache_panics() {
2494        let fork_graph = Arc::new(RwLock::new(TestForkGraph {}));
2495        let batch_processor =
2496            TransactionBatchProcessor::new(0, 0, Arc::downgrade(&fork_graph), None);
2497
2498        // Register a builtin without going through `add_builtin()` and just
2499        // add it to `builtin_program_ids` directly. This will trip the debug
2500        // assertion for the fork guard's `search_for`.
2501        batch_processor
2502            .builtin_program_ids
2503            .write()
2504            .unwrap()
2505            .insert(Pubkey::new_unique());
2506        batch_processor.new_from(1, 0);
2507    }
2508
2509    #[test]
2510    fn test_validate_transaction_fee_payer_exact_balance() {
2511        let lamports_per_signature = 5000;
2512        let message = new_unchecked_sanitized_message(Message::new_with_blockhash(
2513            &[
2514                ComputeBudgetInstruction::set_compute_unit_limit(2000u32),
2515                ComputeBudgetInstruction::set_compute_unit_price(1_000_000_000),
2516            ],
2517            Some(&Pubkey::new_unique()),
2518            &Hash::new_unique(),
2519        ));
2520        let fee_payer_address = message.fee_payer();
2521        let current_epoch = 42;
2522        let rent = Rent::default();
2523        let min_balance = rent.minimum_balance(nonce::state::State::size());
2524        let transaction_fee = lamports_per_signature;
2525        let priority_fee = 2_000_000u64;
2526        let starting_balance = transaction_fee + priority_fee;
2527        assert!(
2528            starting_balance > min_balance,
2529            "we're testing that a rent exempt fee payer can be fully drained, so ensure that the \
2530             starting balance is more than the min balance"
2531        );
2532
2533        let fee_payer_rent_epoch = current_epoch;
2534        let fee_payer_account = AccountSharedData::new_rent_epoch(
2535            starting_balance,
2536            0,
2537            &Pubkey::default(),
2538            fee_payer_rent_epoch,
2539        );
2540        let mut mock_accounts = HashMap::new();
2541        mock_accounts.insert(*fee_payer_address, fee_payer_account.clone());
2542        let mock_bank = MockBankCallback {
2543            account_shared_data: Arc::new(RwLock::new(mock_accounts)),
2544            ..Default::default()
2545        };
2546        let mut account_loader = (&mock_bank).into();
2547
2548        let mut error_counters = TransactionErrorMetrics::default();
2549        let compute_budget_and_limits = SVMTransactionExecutionAndFeeBudgetLimits {
2550            budget: SVMTransactionExecutionBudget {
2551                compute_unit_limit: 2000,
2552                ..SVMTransactionExecutionBudget::default()
2553            },
2554            fee_details: FeeDetails::new(transaction_fee, priority_fee),
2555            ..SVMTransactionExecutionAndFeeBudgetLimits::default()
2556        };
2557        let result =
2558            TransactionBatchProcessor::<TestForkGraph>::validate_transaction_nonce_and_fee_payer(
2559                &mut account_loader,
2560                &message,
2561                CheckedTransactionDetails::new(None, compute_budget_and_limits),
2562                &Hash::default(),
2563                lamports_per_signature,
2564                &rent,
2565                mock_bank.feature_set.relax_post_exec_min_balance_check,
2566                false,
2567                &mut error_counters,
2568            );
2569
2570        let post_validation_fee_payer_account = {
2571            let mut account = fee_payer_account.clone();
2572            account.set_rent_epoch(RENT_EXEMPT_RENT_EPOCH);
2573            account.set_lamports(0);
2574            account
2575        };
2576
2577        assert_eq!(
2578            result,
2579            TransactionValidationResult::Loadable(ValidatedTransactionDetails {
2580                rollback_accounts: RollbackAccounts::new(
2581                    None, // nonce
2582                    *fee_payer_address,
2583                    post_validation_fee_payer_account.clone(),
2584                    fee_payer_rent_epoch
2585                ),
2586                compute_budget: compute_budget_and_limits.budget,
2587                loaded_accounts_bytes_limit: compute_budget_and_limits
2588                    .loaded_accounts_data_size_limit,
2589                fee_details: FeeDetails::new(transaction_fee, priority_fee),
2590                loaded_fee_payer_account: LoadedTransactionAccount {
2591                    loaded_size: TRANSACTION_ACCOUNT_BASE_SIZE + fee_payer_account.data().len(),
2592                    account: post_validation_fee_payer_account,
2593                },
2594            })
2595        );
2596    }
2597
2598    #[test_case(false; "strict_fee_payer")]
2599    #[test_case(true; "relaxed_fee_payer")]
2600    fn test_validate_transaction_fee_payer_not_found(relax_fee_payer_constraint: bool) {
2601        let lamports_per_signature = 5000;
2602        let message =
2603            new_unchecked_sanitized_message(Message::new(&[], Some(&Pubkey::new_unique())));
2604
2605        let fee_and_limits = SVMTransactionExecutionAndFeeBudgetLimits::default();
2606        let mut mock_bank = MockBankCallback::default();
2607        mock_bank.feature_set.relax_fee_payer_constraint = relax_fee_payer_constraint;
2608        let mut account_loader = (&mock_bank).into();
2609        let mut error_counters = TransactionErrorMetrics::default();
2610
2611        let expected_error = TransactionError::AccountNotFound;
2612        let expected_result = if relax_fee_payer_constraint {
2613            TransactionValidationResult::NoOp(NoOpTransaction {
2614                validation_error: expected_error,
2615                fee_payer_balance: None,
2616                compute_unit_limit: fee_and_limits.budget.compute_unit_limit,
2617                loaded_accounts_bytes_limit: fee_and_limits.loaded_accounts_data_size_limit,
2618                nonce_address: None,
2619            })
2620        } else {
2621            TransactionValidationResult::Unprocessable(expected_error)
2622        };
2623
2624        let actual_result =
2625            TransactionBatchProcessor::<TestForkGraph>::validate_transaction_nonce_and_fee_payer(
2626                &mut account_loader,
2627                &message,
2628                CheckedTransactionDetails::new(None, fee_and_limits),
2629                &Hash::default(),
2630                lamports_per_signature,
2631                &Rent::default(),
2632                mock_bank.feature_set.relax_post_exec_min_balance_check,
2633                false,
2634                &mut error_counters,
2635            );
2636
2637        assert_eq!(error_counters.account_not_found.0, 1);
2638        assert_eq!(actual_result, expected_result);
2639    }
2640
2641    #[test_case(false; "strict_fee_payer")]
2642    #[test_case(true; "relaxed_fee_payer")]
2643    fn test_validate_transaction_fee_payer_insufficient_funds(relax_fee_payer_constraint: bool) {
2644        let lamports_per_signature = 5000;
2645        let fee_payer_balance = 1;
2646        let message =
2647            new_unchecked_sanitized_message(Message::new(&[], Some(&Pubkey::new_unique())));
2648        let fee_details =
2649            MockBankCallback::calculate_fee_details(&message, lamports_per_signature, 0);
2650        let fee_and_limits = SVMTransactionExecutionAndFeeBudgetLimits::with_fee(fee_details);
2651        let fee_payer_address = message.fee_payer();
2652        let fee_payer_account = AccountSharedData::new(fee_payer_balance, 0, &Pubkey::default());
2653        let mut mock_accounts = HashMap::new();
2654        mock_accounts.insert(*fee_payer_address, fee_payer_account);
2655        let mut mock_bank = MockBankCallback {
2656            account_shared_data: Arc::new(RwLock::new(mock_accounts)),
2657            ..Default::default()
2658        };
2659        mock_bank.feature_set.relax_fee_payer_constraint = relax_fee_payer_constraint;
2660        let mut account_loader = (&mock_bank).into();
2661
2662        let mut error_counters = TransactionErrorMetrics::default();
2663
2664        let expected_error = TransactionError::InsufficientFundsForFee;
2665        let expected_result = if relax_fee_payer_constraint {
2666            TransactionValidationResult::NoOp(NoOpTransaction {
2667                validation_error: expected_error,
2668                fee_payer_balance: Some(fee_payer_balance),
2669                compute_unit_limit: fee_and_limits.budget.compute_unit_limit,
2670                loaded_accounts_bytes_limit: fee_and_limits.loaded_accounts_data_size_limit,
2671                nonce_address: None,
2672            })
2673        } else {
2674            TransactionValidationResult::Unprocessable(expected_error)
2675        };
2676
2677        let actual_result =
2678            TransactionBatchProcessor::<TestForkGraph>::validate_transaction_nonce_and_fee_payer(
2679                &mut account_loader,
2680                &message,
2681                CheckedTransactionDetails::new(None, fee_and_limits),
2682                &Hash::default(),
2683                lamports_per_signature,
2684                &Rent::default(),
2685                mock_bank.feature_set.relax_post_exec_min_balance_check,
2686                false,
2687                &mut error_counters,
2688            );
2689
2690        assert_eq!(error_counters.insufficient_funds.0, 1);
2691        assert_eq!(actual_result, expected_result);
2692    }
2693
2694    #[test_case(false; "strict_fee_payer")]
2695    #[test_case(true; "relaxed_fee_payer")]
2696    fn test_validate_transaction_fee_payer_insufficient_rent(relax_fee_payer_constraint: bool) {
2697        let lamports_per_signature = 5000;
2698        let message =
2699            new_unchecked_sanitized_message(Message::new(&[], Some(&Pubkey::new_unique())));
2700        let fee_details =
2701            MockBankCallback::calculate_fee_details(&message, lamports_per_signature, 0);
2702        let fee_and_limits = SVMTransactionExecutionAndFeeBudgetLimits::with_fee(fee_details);
2703        let fee_payer_address = message.fee_payer();
2704        let transaction_fee = lamports_per_signature;
2705        let rent = Rent::default();
2706        let min_balance = rent.minimum_balance(0);
2707        let starting_balance = min_balance + transaction_fee - 1;
2708        let fee_payer_account = AccountSharedData::new(starting_balance, 0, &Pubkey::default());
2709        let mut mock_accounts = HashMap::new();
2710        mock_accounts.insert(*fee_payer_address, fee_payer_account);
2711        let mut mock_bank = MockBankCallback {
2712            account_shared_data: Arc::new(RwLock::new(mock_accounts)),
2713            ..Default::default()
2714        };
2715        mock_bank.feature_set.relax_fee_payer_constraint = relax_fee_payer_constraint;
2716        let mut account_loader = (&mock_bank).into();
2717
2718        let mut error_counters = TransactionErrorMetrics::default();
2719
2720        let expected_error = TransactionError::InsufficientFundsForRent { account_index: 0 };
2721        let expected_result = if relax_fee_payer_constraint {
2722            TransactionValidationResult::NoOp(NoOpTransaction {
2723                validation_error: expected_error,
2724                fee_payer_balance: Some(starting_balance),
2725                compute_unit_limit: fee_and_limits.budget.compute_unit_limit,
2726                loaded_accounts_bytes_limit: fee_and_limits.loaded_accounts_data_size_limit,
2727                nonce_address: None,
2728            })
2729        } else {
2730            TransactionValidationResult::Unprocessable(expected_error)
2731        };
2732
2733        let actual_result =
2734            TransactionBatchProcessor::<TestForkGraph>::validate_transaction_nonce_and_fee_payer(
2735                &mut account_loader,
2736                &message,
2737                CheckedTransactionDetails::new(None, fee_and_limits),
2738                &Hash::default(),
2739                lamports_per_signature,
2740                &rent,
2741                mock_bank.feature_set.relax_post_exec_min_balance_check,
2742                false,
2743                &mut error_counters,
2744            );
2745
2746        assert_eq!(actual_result, expected_result);
2747    }
2748
2749    #[test_case(false; "strict_fee_payer")]
2750    #[test_case(true; "relaxed_fee_payer")]
2751    fn test_validate_transaction_fee_payer_invalid(relax_fee_payer_constraint: bool) {
2752        let lamports_per_signature = 5000;
2753        let fee_payer_balance = 1_000_000;
2754        let message =
2755            new_unchecked_sanitized_message(Message::new(&[], Some(&Pubkey::new_unique())));
2756        let fee_details =
2757            MockBankCallback::calculate_fee_details(&message, lamports_per_signature, 0);
2758        let fee_and_limits = SVMTransactionExecutionAndFeeBudgetLimits::with_fee(fee_details);
2759        let fee_payer_address = message.fee_payer();
2760        let fee_payer_account = AccountSharedData::new(fee_payer_balance, 0, &Pubkey::new_unique());
2761        let mut mock_accounts = HashMap::new();
2762        mock_accounts.insert(*fee_payer_address, fee_payer_account);
2763        let mut mock_bank = MockBankCallback {
2764            account_shared_data: Arc::new(RwLock::new(mock_accounts)),
2765            ..Default::default()
2766        };
2767        mock_bank.feature_set.relax_fee_payer_constraint = relax_fee_payer_constraint;
2768        let mut account_loader = (&mock_bank).into();
2769
2770        let mut error_counters = TransactionErrorMetrics::default();
2771
2772        let expected_error = TransactionError::InvalidAccountForFee;
2773        let expected_result = if relax_fee_payer_constraint {
2774            TransactionValidationResult::NoOp(NoOpTransaction {
2775                validation_error: expected_error,
2776                fee_payer_balance: Some(fee_payer_balance),
2777                compute_unit_limit: fee_and_limits.budget.compute_unit_limit,
2778                loaded_accounts_bytes_limit: fee_and_limits.loaded_accounts_data_size_limit,
2779                nonce_address: None,
2780            })
2781        } else {
2782            TransactionValidationResult::Unprocessable(expected_error)
2783        };
2784
2785        let actual_result =
2786            TransactionBatchProcessor::<TestForkGraph>::validate_transaction_nonce_and_fee_payer(
2787                &mut account_loader,
2788                &message,
2789                CheckedTransactionDetails::new(None, fee_and_limits),
2790                &Hash::default(),
2791                lamports_per_signature,
2792                &Rent::default(),
2793                mock_bank.feature_set.relax_post_exec_min_balance_check,
2794                false,
2795                &mut error_counters,
2796            );
2797
2798        assert_eq!(error_counters.invalid_account_for_fee.0, 1);
2799        assert_eq!(actual_result, expected_result);
2800    }
2801
2802    #[derive(Debug, PartialEq, Eq)]
2803    enum ValidateNonce {
2804        Success,
2805        NoAccount,
2806        BadOwner,
2807        BlockhashMismatch,
2808        AlreadyUsed,
2809        BadSigner,
2810        NotWritable,
2811    }
2812
2813    #[test_case(ValidateNonce::Success)]
2814    #[test_case(ValidateNonce::NoAccount)]
2815    #[test_case(ValidateNonce::BadOwner)]
2816    #[test_case(ValidateNonce::BlockhashMismatch)]
2817    #[test_case(ValidateNonce::AlreadyUsed)]
2818    #[test_case(ValidateNonce::BadSigner)]
2819    #[test_case(ValidateNonce::NotWritable)]
2820    fn test_validate_transaction_nonce(case: ValidateNonce) {
2821        let lamports_per_signature = 5000;
2822        let previous_durable_nonce = DurableNonce::from_blockhash(&Hash::new_unique());
2823        let nonce_address = Pubkey::new_unique();
2824        let authority_address = Pubkey::new_unique();
2825
2826        let message_blockhash = if case == ValidateNonce::BlockhashMismatch {
2827            Hash::new_unique()
2828        } else {
2829            *previous_durable_nonce.as_hash()
2830        };
2831
2832        let message_authority = if case == ValidateNonce::BadSigner {
2833            Pubkey::new_unique()
2834        } else {
2835            authority_address
2836        };
2837
2838        let mut advance_nonce_instruction =
2839            system_instruction::advance_nonce_account(&nonce_address, &message_authority);
2840        if case == ValidateNonce::NotWritable {
2841            advance_nonce_instruction.accounts[0].is_writable = false;
2842        }
2843
2844        let message = new_unchecked_sanitized_message(Message::new_with_blockhash(
2845            &[advance_nonce_instruction],
2846            Some(&Pubkey::new_unique()),
2847            &message_blockhash,
2848        ));
2849
2850        let environment_blockhash = Hash::new_unique();
2851        let next_durable_nonce = DurableNonce::from_blockhash(&environment_blockhash);
2852
2853        let stored_durable_nonce = if case == ValidateNonce::AlreadyUsed {
2854            next_durable_nonce
2855        } else {
2856            previous_durable_nonce
2857        };
2858
2859        let nonce_versions = nonce::versions::Versions::new(nonce::state::State::Initialized(
2860            nonce::state::Data::new(
2861                authority_address,
2862                stored_durable_nonce,
2863                lamports_per_signature,
2864            ),
2865        ));
2866
2867        let nonce_owner = if case == ValidateNonce::BadOwner {
2868            Pubkey::new_unique()
2869        } else {
2870            system_program::id()
2871        };
2872
2873        let nonce_account = AccountSharedData::new_data(1, &nonce_versions, &nonce_owner).unwrap();
2874
2875        let mut mock_accounts = HashMap::new();
2876
2877        if case != ValidateNonce::NoAccount {
2878            mock_accounts.insert(nonce_address, nonce_account.clone());
2879        }
2880
2881        let mock_bank = MockBankCallback {
2882            account_shared_data: Arc::new(RwLock::new(mock_accounts)),
2883            ..Default::default()
2884        };
2885        let mut account_loader = (&mock_bank).into();
2886
2887        let mut error_counters = TransactionErrorMetrics::default();
2888        let result = TransactionBatchProcessor::<TestForkGraph>::validate_transaction_nonce(
2889            &mut account_loader,
2890            &message,
2891            &nonce_address,
2892            &next_durable_nonce,
2893            lamports_per_signature,
2894            false,
2895            &mut error_counters,
2896        );
2897
2898        match case {
2899            ValidateNonce::Success => {
2900                let mut future_nonce_info = NonceInfo::new(nonce_address, nonce_account);
2901                future_nonce_info
2902                    .try_advance_nonce(next_durable_nonce, lamports_per_signature)
2903                    .unwrap();
2904
2905                assert_eq!(result, Ok(future_nonce_info));
2906            }
2907            ValidateNonce::NoAccount => {
2908                assert_eq!(error_counters.account_not_found.0, 1);
2909                assert_eq!(result, Err(TransactionError::AccountNotFound));
2910            }
2911            _ => {
2912                assert_eq!(error_counters.blockhash_not_found.0, 1);
2913                assert_eq!(result, Err(TransactionError::BlockhashNotFound));
2914            }
2915        }
2916    }
2917
2918    #[test_case(false; "strict_fee_payer")]
2919    #[test_case(true; "relaxed_fee_payer")]
2920    fn test_validate_transaction_fee_payer_is_nonce(relax_fee_payer_constraint: bool) {
2921        let lamports_per_signature = 5000;
2922        let rent = Rent::default();
2923        let compute_unit_limit = 1000u64;
2924        let previous_durable_nonce = DurableNonce::from_blockhash(&Hash::new_unique());
2925        let fee_payer_address = &Pubkey::new_unique();
2926        let message = new_unchecked_sanitized_message(Message::new_with_blockhash(
2927            &[
2928                system_instruction::advance_nonce_account(fee_payer_address, fee_payer_address),
2929                ComputeBudgetInstruction::set_compute_unit_limit(compute_unit_limit as u32),
2930                ComputeBudgetInstruction::set_compute_unit_price(1_000_000),
2931            ],
2932            Some(fee_payer_address),
2933            previous_durable_nonce.as_hash(),
2934        ));
2935        let transaction_fee = lamports_per_signature;
2936        let compute_budget_and_limits = SVMTransactionExecutionAndFeeBudgetLimits {
2937            fee_details: FeeDetails::new(transaction_fee, compute_unit_limit),
2938            ..SVMTransactionExecutionAndFeeBudgetLimits::default()
2939        };
2940        let min_balance = Rent::default().minimum_balance(nonce::state::State::size());
2941        let priority_fee = compute_unit_limit;
2942
2943        let nonce_versions = nonce::versions::Versions::new(nonce::state::State::Initialized(
2944            nonce::state::Data::new(
2945                *fee_payer_address,
2946                previous_durable_nonce,
2947                lamports_per_signature,
2948            ),
2949        ));
2950
2951        let environment_blockhash = Hash::new_unique();
2952        let next_durable_nonce = DurableNonce::from_blockhash(&environment_blockhash);
2953
2954        // Sufficient Fees
2955        {
2956            let fee_payer_account = AccountSharedData::new_data(
2957                min_balance + transaction_fee + priority_fee,
2958                &nonce_versions,
2959                &system_program::id(),
2960            )
2961            .unwrap();
2962
2963            let mut future_nonce = NonceInfo::new(*fee_payer_address, fee_payer_account.clone());
2964            future_nonce
2965                .try_advance_nonce(next_durable_nonce, lamports_per_signature)
2966                .unwrap();
2967
2968            let mut mock_accounts = HashMap::new();
2969            mock_accounts.insert(*fee_payer_address, fee_payer_account.clone());
2970            let mut mock_bank = MockBankCallback {
2971                account_shared_data: Arc::new(RwLock::new(mock_accounts)),
2972                ..Default::default()
2973            };
2974            mock_bank.feature_set.relax_fee_payer_constraint = relax_fee_payer_constraint;
2975            let mut account_loader = (&mock_bank).into();
2976
2977            let mut error_counters = TransactionErrorMetrics::default();
2978
2979            let tx_details =
2980                CheckedTransactionDetails::new(Some(*fee_payer_address), compute_budget_and_limits);
2981
2982            let result = TransactionBatchProcessor::<TestForkGraph>::validate_transaction_nonce_and_fee_payer(
2983                &mut account_loader,
2984                &message,
2985                tx_details,
2986                &environment_blockhash,
2987                lamports_per_signature,
2988                &rent,
2989                mock_bank.feature_set.relax_post_exec_min_balance_check,
2990                false,
2991                &mut error_counters,
2992            );
2993
2994            let post_validation_fee_payer_account = {
2995                let mut account = fee_payer_account.clone();
2996                account.set_rent_epoch(RENT_EXEMPT_RENT_EPOCH);
2997                account.set_lamports(min_balance);
2998                account
2999            };
3000
3001            assert_eq!(
3002                result,
3003                TransactionValidationResult::Loadable(ValidatedTransactionDetails {
3004                    rollback_accounts: RollbackAccounts::new(
3005                        Some(future_nonce),
3006                        *fee_payer_address,
3007                        post_validation_fee_payer_account.clone(),
3008                        0, // fee_payer_rent_epoch
3009                    ),
3010                    compute_budget: compute_budget_and_limits.budget,
3011                    loaded_accounts_bytes_limit: compute_budget_and_limits
3012                        .loaded_accounts_data_size_limit,
3013                    fee_details: FeeDetails::new(transaction_fee, priority_fee),
3014                    loaded_fee_payer_account: LoadedTransactionAccount {
3015                        loaded_size: TRANSACTION_ACCOUNT_BASE_SIZE + fee_payer_account.data().len(),
3016                        account: post_validation_fee_payer_account,
3017                    }
3018                })
3019            );
3020        }
3021
3022        // Insufficient Fees
3023        {
3024            let fee_payer_account = AccountSharedData::new_data(
3025                transaction_fee + priority_fee, // no min_balance this time
3026                &nonce_versions,
3027                &system_program::id(),
3028            )
3029            .unwrap();
3030
3031            let mut mock_accounts = HashMap::new();
3032            mock_accounts.insert(*fee_payer_address, fee_payer_account);
3033            let mut mock_bank = MockBankCallback {
3034                account_shared_data: Arc::new(RwLock::new(mock_accounts)),
3035                ..Default::default()
3036            };
3037            mock_bank.feature_set.relax_fee_payer_constraint = relax_fee_payer_constraint;
3038            let mut account_loader = (&mock_bank).into();
3039
3040            let mut error_counters = TransactionErrorMetrics::default();
3041
3042            let tx_details =
3043                CheckedTransactionDetails::new(Some(*fee_payer_address), compute_budget_and_limits);
3044
3045            let result = TransactionBatchProcessor::<TestForkGraph>::validate_transaction_nonce_and_fee_payer(
3046                &mut account_loader,
3047                &message,
3048                tx_details,
3049                &environment_blockhash,
3050                lamports_per_signature,
3051                &rent,
3052                mock_bank.feature_set.relax_post_exec_min_balance_check,
3053                false,
3054                &mut error_counters,
3055            );
3056
3057            // nonce transactions are never processable under SIMD-0290 rules
3058            assert_eq!(error_counters.insufficient_funds.0, 1);
3059            assert_eq!(
3060                result,
3061                TransactionValidationResult::Unprocessable(
3062                    TransactionError::InsufficientFundsForFee
3063                )
3064            );
3065        }
3066    }
3067
3068    // Ensure `TransactionProcessingCallback::inspect_account()` is called when
3069    // validating the fee payer, since that's when the fee payer account is loaded.
3070    #[test]
3071    fn test_inspect_account_fee_payer() {
3072        let lamports_per_signature = 5000;
3073        let fee_payer_address = Pubkey::new_unique();
3074        let fee_payer_account = AccountSharedData::new_rent_epoch(
3075            123_000_000_000,
3076            0,
3077            &Pubkey::default(),
3078            RENT_EXEMPT_RENT_EPOCH,
3079        );
3080        let mock_bank = MockBankCallback::default();
3081        mock_bank
3082            .account_shared_data
3083            .write()
3084            .unwrap()
3085            .insert(fee_payer_address, fee_payer_account.clone());
3086        let mut account_loader = (&mock_bank).into();
3087
3088        let message = new_unchecked_sanitized_message(Message::new_with_blockhash(
3089            &[
3090                ComputeBudgetInstruction::set_compute_unit_limit(2000u32),
3091                ComputeBudgetInstruction::set_compute_unit_price(1_000_000_000),
3092            ],
3093            Some(&fee_payer_address),
3094            &Hash::new_unique(),
3095        ));
3096
3097        assert!(matches!(
3098            TransactionBatchProcessor::<TestForkGraph>::validate_transaction_nonce_and_fee_payer(
3099                &mut account_loader,
3100                &message,
3101                CheckedTransactionDetails::new(
3102                    None,
3103                    SVMTransactionExecutionAndFeeBudgetLimits::with_fee(
3104                        MockBankCallback::calculate_fee_details(&message, 5000, 0),
3105                    ),
3106                ),
3107                &Hash::default(),
3108                lamports_per_signature,
3109                &Rent::default(),
3110                mock_bank.feature_set.relax_post_exec_min_balance_check,
3111                false,
3112                &mut TransactionErrorMetrics::default(),
3113            ),
3114            TransactionValidationResult::Loadable(_)
3115        ));
3116
3117        // ensure the fee payer is an inspected account
3118        let actual_inspected_accounts: Vec<_> = mock_bank
3119            .inspected_accounts
3120            .read()
3121            .unwrap()
3122            .iter()
3123            .map(|(k, v)| (*k, v.clone()))
3124            .collect();
3125        assert_eq!(
3126            actual_inspected_accounts.as_slice(),
3127            &[(fee_payer_address, vec![(Some(fee_payer_account), true)])],
3128        );
3129    }
3130
3131    #[test]
3132    fn test_set_program_runtime_environment() {
3133        let mut transaction_processor = TransactionBatchProcessor::<TestForkGraph>::default();
3134        let current_environment =
3135            ProgramRuntimeEnvironment::clone(&transaction_processor.program_runtime_environment);
3136        let new_environment = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
3137        let config = vm::Config {
3138            enable_symbol_and_section_labels: true,
3139            ..vm::Config::default()
3140        };
3141        let new_environment2 = ProgramRuntimeEnvironment::from(BuiltinProgram::new_loader(config));
3142        assert_ne!(current_environment, new_environment);
3143        assert_ne!(current_environment, new_environment2);
3144        assert_ne!(new_environment, new_environment2);
3145        // Assign an equal and identical environment: No changes
3146        transaction_processor.set_program_runtime_environment(ProgramRuntimeEnvironment::clone(
3147            &current_environment,
3148        ));
3149        assert_eq!(
3150            transaction_processor.program_runtime_environment,
3151            current_environment,
3152        );
3153        // Assign an equal but not identical environment: No changes
3154        transaction_processor
3155            .set_program_runtime_environment(ProgramRuntimeEnvironment::clone(&new_environment));
3156        assert_eq!(
3157            transaction_processor.program_runtime_environment,
3158            current_environment,
3159        );
3160        // Assign a different and not identical environment: Overwritten
3161        transaction_processor
3162            .set_program_runtime_environment(ProgramRuntimeEnvironment::clone(&new_environment2));
3163        assert_eq!(
3164            transaction_processor.program_runtime_environment,
3165            new_environment2,
3166        );
3167        // Assign an environment which is equal to the upcoming_environment: Overwritten
3168        transaction_processor
3169            .epoch_boundary_preparation
3170            .write()
3171            .unwrap()
3172            .upcoming_environment = Some(ProgramRuntimeEnvironment::clone(&new_environment));
3173        transaction_processor.set_program_runtime_environment(ProgramRuntimeEnvironment::clone(
3174            &current_environment,
3175        ));
3176        assert_eq!(
3177            transaction_processor.program_runtime_environment,
3178            new_environment,
3179        );
3180    }
3181
3182    fn catch_panic(f: impl FnOnce()) -> Option<String> {
3183        let previous_hook = std::panic::take_hook();
3184        std::panic::set_hook(Box::new(|_| {}));
3185        let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(f));
3186        std::panic::set_hook(previous_hook);
3187        result.err().map(|payload| {
3188            payload
3189                .downcast_ref::<&str>()
3190                .map(|s| s.to_string())
3191                .or_else(|| payload.downcast_ref::<String>().cloned())
3192                .unwrap_or_else(|| "<non-string panic payload>".to_string())
3193        })
3194    }
3195
3196    #[test_case(ProgramCacheEntryOwner::LoaderV1)]
3197    #[test_case(ProgramCacheEntryOwner::LoaderV2)]
3198    #[test_case(ProgramCacheEntryOwner::LoaderV3)]
3199    #[test_case(ProgramCacheEntryOwner::LoaderV4)]
3200    fn test_replenish_program_cache_success(loader: ProgramCacheEntryOwner) {
3201        const BATCH_SLOT: u64 = 200;
3202        const DEPLOYMENT_SLOT: u64 = 10;
3203
3204        let mock_bank = MockBankCallback::default();
3205        let account_loader = (&mock_bank).into();
3206        let fork_graph = Arc::new(RwLock::new(TestForkGraph {}));
3207        let batch_processor =
3208            TransactionBatchProcessor::new(BATCH_SLOT, 0, Arc::downgrade(&fork_graph), None);
3209        let environment = batch_processor.program_runtime_environment_for_epoch(0);
3210        let program_id = Pubkey::new_unique();
3211
3212        let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::new(batch_processor.slot);
3213
3214        // Set up a valid program, with a valid ELF, for each loader. Note V1
3215        // and V2 do not retain a deployment slot, so they report zero.
3216        let expected_deployment_slot = match loader {
3217            ProgramCacheEntryOwner::LoaderV1 | ProgramCacheEntryOwner::LoaderV2 => {
3218                let mut account = AccountSharedData::default();
3219                account.set_owner(Pubkey::from(loader));
3220                account.set_data_from_slice(&load_test_program());
3221                mock_bank
3222                    .account_shared_data
3223                    .write()
3224                    .unwrap()
3225                    .insert(program_id, account);
3226                0
3227            }
3228            ProgramCacheEntryOwner::LoaderV3 => {
3229                let programdata_id = Pubkey::new_unique();
3230                mock_bank
3231                    .account_shared_data
3232                    .write()
3233                    .unwrap()
3234                    .insert(program_id, loader_v3_program_account(programdata_id));
3235                mock_bank.account_shared_data.write().unwrap().insert(
3236                    programdata_id,
3237                    loader_v3_programdata_account(DEPLOYMENT_SLOT, &load_test_program()),
3238                );
3239                DEPLOYMENT_SLOT
3240            }
3241            ProgramCacheEntryOwner::LoaderV4 => {
3242                mock_bank.account_shared_data.write().unwrap().insert(
3243                    program_id,
3244                    loader_v4_account(
3245                        DEPLOYMENT_SLOT,
3246                        LoaderV4Status::Deployed,
3247                        &load_test_program(),
3248                    ),
3249                );
3250                DEPLOYMENT_SLOT
3251            }
3252            ProgramCacheEntryOwner::NativeLoader => unreachable!(),
3253        };
3254
3255        // The program should be identified for extraction.
3256        let keys = [program_id];
3257        let missing_programs = filter_executable_program_accounts(
3258            &mock_bank,
3259            &program_cache_for_tx_batch,
3260            keys.iter(),
3261        );
3262        assert_eq!(
3263            missing_programs,
3264            &[ProgramToLoad {
3265                program_id: &program_id,
3266                loader,
3267                deployment_slot: expected_deployment_slot,
3268            }]
3269        );
3270
3271        // The load succeeds, leaving a `Loaded` entry in the batch cache.
3272        batch_processor.replenish_program_cache(
3273            &account_loader,
3274            missing_programs,
3275            &environment,
3276            &mut program_cache_for_tx_batch,
3277            &mut ExecuteTimings::default(),
3278            true,
3279            true,
3280        );
3281        let entry = program_cache_for_tx_batch.find(&program_id).unwrap();
3282        assert!(matches!(entry.program, ProgramCacheEntryType::Loaded(_)));
3283        assert_eq!(entry.deployment_slot, expected_deployment_slot);
3284        assert_eq!(entry.account_owner, loader);
3285    }
3286
3287    #[test_case(ProgramCacheEntryOwner::LoaderV1)]
3288    #[test_case(ProgramCacheEntryOwner::LoaderV2)]
3289    #[test_case(ProgramCacheEntryOwner::LoaderV3)]
3290    #[test_case(ProgramCacheEntryOwner::LoaderV4)]
3291    #[test_case(ProgramCacheEntryOwner::NativeLoader)]
3292    fn test_replenish_program_cache_program_account_not_found(loader: ProgramCacheEntryOwner) {
3293        let mock_bank = MockBankCallback::default();
3294        let account_loader = (&mock_bank).into();
3295        let fork_graph = Arc::new(RwLock::new(TestForkGraph {}));
3296        let batch_processor =
3297            TransactionBatchProcessor::new(0, 0, Arc::downgrade(&fork_graph), None);
3298        let environment = batch_processor.program_runtime_environment_for_epoch(0);
3299        let program_id = Pubkey::new_unique();
3300
3301        let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::new(batch_processor.slot);
3302
3303        // The program should not be identified as a program we must extract.
3304        let keys = [program_id];
3305        let missing_programs = filter_executable_program_accounts(
3306            &mock_bank,
3307            &program_cache_for_tx_batch,
3308            keys.iter(),
3309        );
3310        assert!(missing_programs.is_empty());
3311
3312        // But still, even if we did happen to try to extract this program, it
3313        // should panic.
3314        let panicked = catch_panic(|| {
3315            batch_processor.replenish_program_cache(
3316                &account_loader,
3317                vec![ProgramToLoad {
3318                    program_id: &program_id,
3319                    loader,
3320                    deployment_slot: 0,
3321                }],
3322                &environment,
3323                &mut program_cache_for_tx_batch,
3324                &mut ExecuteTimings::default(),
3325                true,
3326                true,
3327            )
3328        });
3329        assert_eq!(
3330            panicked.as_deref(),
3331            Some("called load_program_with_pubkey() with nonexistent account")
3332        );
3333    }
3334
3335    #[test_case(ProgramCacheEntryOwner::LoaderV1)]
3336    #[test_case(ProgramCacheEntryOwner::LoaderV2)]
3337    #[test_case(ProgramCacheEntryOwner::LoaderV3)]
3338    #[test_case(ProgramCacheEntryOwner::LoaderV4)]
3339    #[test_case(ProgramCacheEntryOwner::NativeLoader)]
3340    fn test_replenish_program_cache_program_account_empty(loader: ProgramCacheEntryOwner) {
3341        const BATCH_SLOT: u64 = 200;
3342
3343        let mock_bank = MockBankCallback::default();
3344        let account_loader = (&mock_bank).into();
3345        let fork_graph = Arc::new(RwLock::new(TestForkGraph {}));
3346        let batch_processor =
3347            TransactionBatchProcessor::new(BATCH_SLOT, 0, Arc::downgrade(&fork_graph), None);
3348        let environment = batch_processor.program_runtime_environment_for_epoch(0);
3349        let program_id = Pubkey::new_unique();
3350
3351        let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::new(batch_processor.slot);
3352
3353        // Insert a program account owned by the proper loader, but with no
3354        // state in it at all.
3355        let mut program_account = AccountSharedData::default();
3356        program_account.set_owner(Pubkey::from(loader));
3357        mock_bank
3358            .account_shared_data
3359            .write()
3360            .unwrap()
3361            .insert(program_id, program_account);
3362
3363        // The program should not be identified as a program we must extract.
3364        let keys = [program_id];
3365        let missing_programs = filter_executable_program_accounts(
3366            &mock_bank,
3367            &program_cache_for_tx_batch,
3368            keys.iter(),
3369        );
3370        assert!(missing_programs.is_empty());
3371
3372        // Try to extract it anyway.
3373        let panicked = catch_panic(|| {
3374            batch_processor.replenish_program_cache(
3375                &account_loader,
3376                vec![ProgramToLoad {
3377                    program_id: &program_id,
3378                    loader,
3379                    deployment_slot: 0,
3380                }],
3381                &environment,
3382                &mut program_cache_for_tx_batch,
3383                &mut ExecuteTimings::default(),
3384                true,
3385                true,
3386            )
3387        });
3388
3389        match loader {
3390            ProgramCacheEntryOwner::LoaderV1 | ProgramCacheEntryOwner::LoaderV2 => {
3391                // For Loader V1 & V2, the entire account contents is taken
3392                // as-is and passed to `load`. Empty bytes == invalid ELF.
3393                assert_eq!(
3394                    panicked.as_deref(),
3395                    None, // <-- no panic
3396                );
3397                let entry = program_cache_for_tx_batch.find(&program_id).unwrap();
3398                assert_eq!(
3399                    entry,
3400                    Arc::new(ProgramCacheEntry::new_failed_verification_tombstone(
3401                        0, // <-- V1/V2 programs are always deployment slot 0
3402                        loader,
3403                        environment,
3404                    ))
3405                );
3406            }
3407            ProgramCacheEntryOwner::LoaderV3 | ProgramCacheEntryOwner::LoaderV4 => {
3408                // For Loader V3 & V4, data is too small for the respective
3409                // metadata section, so the program is considered *closed*.
3410                //
3411                // We observe a panic here from `assign_program`, as extraction
3412                // enters into a **continuous loop** of inserting a `Closed`
3413                // tombstone for the batch slot and missing it on extraction,
3414                // which targets the *deployment slot*.
3415                //
3416                // More information on this loop condition is provided below in
3417                // `test_replenish_program_cache_loader_v3_closed`.
3418                assert_eq!(
3419                    panicked.as_deref(),
3420                    Some("Unexpected replacement of an entry")
3421                );
3422            }
3423            ProgramCacheEntryOwner::NativeLoader => {
3424                // Native loader-owned accounts panic, since the call to
3425                // `load_program_with_pubkey` just returns an error which gets
3426                // unwrapped via `expect` in `replenish_program_cache`.
3427                assert_eq!(
3428                    panicked.as_deref(),
3429                    Some("called load_program_with_pubkey() with nonexistent account")
3430                );
3431            }
3432        }
3433    }
3434
3435    enum ProgramDataCase {
3436        DoesNotExist,
3437        OwnedButEmpty,
3438        Uninitialized,
3439    }
3440
3441    #[test_case(ProgramDataCase::DoesNotExist)]
3442    #[test_case(ProgramDataCase::OwnedButEmpty)]
3443    #[test_case(ProgramDataCase::Uninitialized)]
3444    fn test_replenish_program_cache_loader_v3_closed(case: ProgramDataCase) {
3445        const BATCH_SLOT: u64 = 200;
3446        const DEPLOYMENT_SLOT: u64 = 10;
3447
3448        let mock_bank = MockBankCallback::default();
3449        let account_loader = (&mock_bank).into();
3450        let fork_graph = Arc::new(RwLock::new(TestForkGraph {}));
3451        let batch_processor =
3452            TransactionBatchProcessor::new(BATCH_SLOT, 0, Arc::downgrade(&fork_graph), None);
3453        let environment = batch_processor.program_runtime_environment_for_epoch(0);
3454        let program_id = Pubkey::new_unique();
3455        let programdata_id = Pubkey::new_unique();
3456
3457        let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::new(batch_processor.slot);
3458
3459        // Insert a legit program account.
3460        mock_bank
3461            .account_shared_data
3462            .write()
3463            .unwrap()
3464            .insert(program_id, loader_v3_program_account(programdata_id));
3465
3466        let programdata_account = match case {
3467            ProgramDataCase::DoesNotExist => {
3468                // Case: Programdata does not exist
3469                AccountSharedData::default()
3470            }
3471            ProgramDataCase::OwnedButEmpty => {
3472                // Case: Programdata is owned by Loader V3, but no state.
3473                let mut account = AccountSharedData::default();
3474                account.set_owner(bpf_loader_upgradeable::id());
3475                account
3476            }
3477            ProgramDataCase::Uninitialized => {
3478                // Case: Programdata is `Uninitialized`.
3479                let mut account = AccountSharedData::default();
3480                account.set_owner(bpf_loader_upgradeable::id());
3481                account.set_data_from_slice(&[0u8; 4]);
3482                account
3483            }
3484        };
3485        mock_bank
3486            .account_shared_data
3487            .write()
3488            .unwrap()
3489            .insert(programdata_id, programdata_account);
3490
3491        // The program should not be identified as a program we must extract.
3492        let keys = [program_id];
3493        let missing_programs = filter_executable_program_accounts(
3494            &mock_bank,
3495            &program_cache_for_tx_batch,
3496            keys.iter(),
3497        );
3498        assert!(missing_programs.is_empty());
3499
3500        // `filter_executable_program_accounts` is the guard. As long as we
3501        // don't place a closed/non-existant program in the search list for
3502        // extraction, we correctly treat it as a `Closed` program in the batch
3503        // cache.
3504        //
3505        // If this ever goes wrong, and a closed/non-existant program appears
3506        // in the search list for extraction, we can end up with a **continuous
3507        // loop**.
3508        //
3509        // The loop works like this:
3510        //
3511        // - Closed program is requested for extraction. Global cache doesn't
3512        //   have it, so it signals for reload.
3513        // - Reloading via `load_program_with_pubkey` returns a `Closed`
3514        //   tombstone. This tombstone is inserted into the global program
3515        //   cache via `finish_cooperative_loading_task`, which calls into
3516        //   `assign_program`.
3517        // - After this step, extraction is *supposed* to run again, so the
3518        //   program key is still present in the search list. However, each
3519        //   subsequent call to `extract` is going to MISS. This is due to the
3520        //   fact that extraction MUST match on the deployment slot, and a
3521        //   `Closed` tombstone holds the **batch slot**.
3522        //
3523        // The loop can only happen in production. In debug mode, we trip on
3524        // the `debug_assert!` inside of `assign_program`, which traps on an
3525        // invalid `Closed`-`Closed` transition. We observe this below.
3526        let panicked = catch_panic(|| {
3527            batch_processor.replenish_program_cache(
3528                &account_loader,
3529                vec![ProgramToLoad {
3530                    program_id: &program_id,
3531                    loader: ProgramCacheEntryOwner::LoaderV3,
3532                    deployment_slot: DEPLOYMENT_SLOT,
3533                }],
3534                &environment,
3535                &mut program_cache_for_tx_batch,
3536                &mut ExecuteTimings::default(),
3537                true,
3538                true,
3539            )
3540        });
3541        assert_eq!(
3542            panicked.as_deref(),
3543            Some("Unexpected replacement of an entry")
3544        );
3545    }
3546
3547    #[test_case(true)]
3548    #[test_case(false)]
3549    fn test_replenish_program_cache_loader_v4_retracted(just_zeroes: bool) {
3550        const BATCH_SLOT: u64 = 200;
3551
3552        let mock_bank = MockBankCallback::default();
3553        let account_loader = (&mock_bank).into();
3554        let fork_graph = Arc::new(RwLock::new(TestForkGraph {}));
3555        let batch_processor =
3556            TransactionBatchProcessor::new(BATCH_SLOT, 0, Arc::downgrade(&fork_graph), None);
3557        let environment = batch_processor.program_runtime_environment_for_epoch(0);
3558        let program_id = Pubkey::new_unique();
3559
3560        let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::new(batch_processor.slot);
3561
3562        let program_account = if just_zeroes {
3563            // Case: "gifted" state
3564            // Sized correctly, all-zeroes. Since `LoaderV4Status::Retracted`
3565            // holds variant `0`, we check this case, too.
3566            let mut account = AccountSharedData::default();
3567            account.set_owner(loader_v4::id());
3568            account.set_data_from_slice(&[0u8; LoaderV4State::program_data_offset()]);
3569            account
3570        } else {
3571            // Case: status is Retracted
3572            loader_v4_account(9, LoaderV4Status::Retracted, &load_test_program())
3573        };
3574        mock_bank
3575            .account_shared_data
3576            .write()
3577            .unwrap()
3578            .insert(program_id, program_account);
3579
3580        // The program should not be identified as a program we must extract.
3581        let keys = [program_id];
3582        let missing_programs = filter_executable_program_accounts(
3583            &mock_bank,
3584            &program_cache_for_tx_batch,
3585            keys.iter(),
3586        );
3587        assert!(missing_programs.is_empty());
3588
3589        // As in `test_replenish_program_cache_loader_v3_closed`,
3590        // `filter_executable_program_accounts` is the guard. Forcing the
3591        // program into the search list anyway walks into the continuous loop
3592        // described there, which debug mode traps on.
3593        let panicked = catch_panic(|| {
3594            batch_processor.replenish_program_cache(
3595                &account_loader,
3596                vec![ProgramToLoad {
3597                    program_id: &program_id,
3598                    loader: ProgramCacheEntryOwner::LoaderV4,
3599                    deployment_slot: if just_zeroes { 0 } else { 9 },
3600                }],
3601                &environment,
3602                &mut program_cache_for_tx_batch,
3603                &mut ExecuteTimings::default(),
3604                true,
3605                true,
3606            )
3607        });
3608        assert_eq!(
3609            panicked.as_deref(),
3610            Some("Unexpected replacement of an entry")
3611        );
3612    }
3613
3614    fn store_loader_v3_program(
3615        mock_bank: &MockBankCallback,
3616        program_id: &Pubkey,
3617        deployment_slot: Slot,
3618        elf: &[u8],
3619    ) {
3620        let programdata_id = Pubkey::new_unique();
3621        let mut accounts = mock_bank.account_shared_data.write().unwrap();
3622        accounts.insert(*program_id, loader_v3_program_account(programdata_id));
3623        accounts.insert(
3624            programdata_id,
3625            loader_v3_programdata_account(deployment_slot, elf),
3626        );
3627    }
3628
3629    #[test_case(false)]
3630    #[test_case(true)]
3631    fn test_replenish_program_cache_delay_visibility(already_cached: bool) {
3632        // Tests delay visibility behavior in `replenish_program_cache`.
3633        //
3634        // Deployments currently insert an unloaded entry into the cache, so we
3635        // should always see an existing entry. If, for some reason, the
3636        // program is not already cached as unloaded, we end up doing a
3637        // cooperative load one slot too early.
3638        const BATCH_SLOT: u64 = 200;
3639
3640        let mock_bank = MockBankCallback::default();
3641        let account_loader = (&mock_bank).into();
3642        let fork_graph = Arc::new(RwLock::new(TestForkGraph {}));
3643        let batch_processor =
3644            TransactionBatchProcessor::new(BATCH_SLOT, 0, Arc::downgrade(&fork_graph), None);
3645        let environment = batch_processor.program_runtime_environment_for_epoch(0);
3646        let program_id = Pubkey::new_unique();
3647
3648        // Deployed in the batch's own slot, so it is not effective yet.
3649        store_loader_v3_program(&mock_bank, &program_id, BATCH_SLOT, &load_test_program());
3650
3651        if already_cached {
3652            batch_processor
3653                .global_program_cache
3654                .write()
3655                .unwrap()
3656                .assign_program(
3657                    &environment,
3658                    program_id,
3659                    BATCH_SLOT,
3660                    Arc::new(ProgramCacheEntry::new_unloaded(
3661                        BATCH_SLOT,
3662                        ProgramCacheEntryOwner::LoaderV3,
3663                        environment.clone(),
3664                    )),
3665                );
3666            batch_processor
3667                .global_program_cache
3668                .write()
3669                .unwrap()
3670                .stats
3671                .reset();
3672        }
3673
3674        let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::new(batch_processor.slot);
3675        let keys = [program_id];
3676        batch_processor.replenish_program_cache(
3677            &account_loader,
3678            filter_executable_program_accounts(
3679                &mock_bank,
3680                &program_cache_for_tx_batch,
3681                keys.iter(),
3682            ),
3683            &environment,
3684            &mut program_cache_for_tx_batch,
3685            &mut ExecuteTimings::default(),
3686            true,
3687            true,
3688        );
3689
3690        // Either way the batch is handed a tombstone.
3691        // Here we read the entry directly.
3692        let stored = program_cache_for_tx_batch
3693            .get_entry_for_tests(&program_id)
3694            .unwrap();
3695        assert!(matches!(
3696            stored.program,
3697            ProgramCacheEntryType::DelayVisibility
3698        ));
3699        assert_eq!(stored.deployment_slot, BATCH_SLOT);
3700
3701        // And here we query with `::find`, and see the same thing.
3702        let entry = program_cache_for_tx_batch.find(&program_id).unwrap();
3703        assert!(matches!(
3704            entry.program,
3705            ProgramCacheEntryType::DelayVisibility
3706        ));
3707        assert_eq!(entry.deployment_slot, BATCH_SLOT);
3708
3709        // The global cache should just have one entry for this program.
3710        // What that entry looks like will differ, though.
3711        let global_program_cache = batch_processor.global_program_cache.read().unwrap();
3712        let slot_versions = global_program_cache.get_slot_versions_for_tests(&program_id);
3713        assert_eq!(slot_versions.len(), 1);
3714
3715        if already_cached {
3716            // If the program was already cached as unloaded, we did not waste
3717            // a load here, and it remains unloaded until effective.
3718            assert!(matches!(
3719                slot_versions[0].program,
3720                ProgramCacheEntryType::Unloaded(_)
3721            ));
3722            assert!(!program_cache_for_tx_batch.loaded_missing);
3723            assert_eq!(global_program_cache.stats.hits.load(Ordering::Relaxed), 1);
3724            assert_eq!(global_program_cache.stats.misses.load(Ordering::Relaxed), 0);
3725        } else {
3726            // If the program was not cached, we actually loaded it, even
3727            // though we couldn't use it.
3728            //
3729            // It's fine, since the batch cache still returns the tombstone.
3730            assert!(matches!(
3731                slot_versions[0].program,
3732                ProgramCacheEntryType::Loaded(_)
3733            ));
3734            assert!(program_cache_for_tx_batch.loaded_missing);
3735            assert_eq!(global_program_cache.stats.hits.load(Ordering::Relaxed), 0);
3736            assert_eq!(global_program_cache.stats.misses.load(Ordering::Relaxed), 1);
3737        }
3738    }
3739
3740    #[test]
3741    fn test_replenish_program_cache_failed_verification_is_cached() {
3742        const BATCH_SLOT: u64 = 200;
3743        const DEPLOYMENT_SLOT: u64 = 10;
3744
3745        let mock_bank = MockBankCallback::default();
3746        let account_loader = (&mock_bank).into();
3747        let fork_graph = Arc::new(RwLock::new(TestForkGraph {}));
3748        let batch_processor =
3749            TransactionBatchProcessor::new(BATCH_SLOT, 0, Arc::downgrade(&fork_graph), None);
3750        let environment = batch_processor.program_runtime_environment_for_epoch(0);
3751        let program_id = Pubkey::new_unique();
3752
3753        // Valid programdata, bad ELF.
3754        store_loader_v3_program(&mock_bank, &program_id, DEPLOYMENT_SLOT, &[1u8; 64]);
3755
3756        let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::new(batch_processor.slot);
3757        let keys = [program_id];
3758        let missing_programs = filter_executable_program_accounts(
3759            &mock_bank,
3760            &program_cache_for_tx_batch,
3761            keys.iter(),
3762        );
3763        assert_eq!(missing_programs.len(), 1);
3764
3765        batch_processor.replenish_program_cache(
3766            &account_loader,
3767            missing_programs,
3768            &environment,
3769            &mut program_cache_for_tx_batch,
3770            &mut ExecuteTimings::default(),
3771            true,
3772            true,
3773        );
3774
3775        // Unlike a closed program, a program which fails verification is
3776        // tombstoned at its own deployment slot, so the extraction which
3777        // follows the load finds it.
3778        let entry = program_cache_for_tx_batch.find(&program_id).unwrap();
3779        assert!(matches!(
3780            entry.program,
3781            ProgramCacheEntryType::FailedVerification(_)
3782        ));
3783        assert_eq!(entry.deployment_slot, DEPLOYMENT_SLOT);
3784
3785        // It is cached globally too, so the next batch does not recompile it.
3786        let global_program_cache = batch_processor.global_program_cache.read().unwrap();
3787        let slot_versions = global_program_cache.get_slot_versions_for_tests(&program_id);
3788        assert_eq!(slot_versions.len(), 1);
3789        assert!(matches!(
3790            slot_versions[0].program,
3791            ProgramCacheEntryType::FailedVerification(_)
3792        ));
3793        assert!(matches!(slot_versions[0].deployment_slot, DEPLOYMENT_SLOT));
3794    }
3795}