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
79pub type TransactionLogMessages = Vec<String>;
81
82pub struct LoadAndExecuteSanitizedTransactionsOutput {
85 pub error_metrics: TransactionErrorMetrics,
87 pub execute_timings: ExecuteTimings,
89 pub processing_results: Vec<TransactionProcessingResult>,
93 pub balance_collector: Option<BalanceCollector>,
96}
97
98#[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#[derive(Default)]
120pub struct TransactionProcessingConfig<'a> {
121 pub account_overrides: Option<&'a AccountOverrides>,
124 pub log_messages_bytes_limit: Option<usize>,
126 pub limit_to_load_programs: bool,
129 pub recording_config: ExecutionRecordingConfig,
131 pub drop_on_failure: bool,
134 pub all_or_nothing: bool,
143 pub strict_nonce_size_check: bool,
147 pub drop_noop_transactions: bool,
151 pub drop_bail_out_transactions: bool,
155}
156
157pub struct TransactionProcessingEnvironment {
159 pub blockhash: Hash,
161 pub blockhash_lamports_per_signature: u64,
168 pub alpenglow_migration_succeeded: bool,
170 pub epoch_total_stake: u64,
172 pub feature_set: SVMFeatureSet,
174 pub program_runtime_environments: ProgramRuntimeEnvironments,
176 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 slot: Slot,
200
201 epoch: Epoch,
203
204 sysvar_cache: RwLock<SysvarCache>,
208
209 pub epoch_boundary_preparation: Arc<RwLock<EpochBoundaryPreparation>>,
211
212 pub global_program_cache: Arc<RwLock<ProgramCache<FG>>>,
214
215 pub program_runtime_environment: ProgramRuntimeEnvironment,
217
218 deployment_env_override: Option<ProgramRuntimeEnvironment>,
222
223 pub builtin_program_ids: RwLock<HashSet<Pubkey>>,
227
228 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 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 #[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 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 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 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 pub fn set_execution_cost(&mut self, cost: SVMTransactionExecutionCost) {
405 self.execution_cost = cost;
406 }
407
408 pub fn set_program_runtime_environment(&mut self, new_environment: ProgramRuntimeEnvironment) {
410 if *self.program_runtime_environment != *new_environment {
412 self.program_runtime_environment = new_environment;
413 }
414 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 self.program_runtime_environment = upcoming_environment;
427 }
428 }
429 }
430
431 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 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 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 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 let account_keys_in_batch = sanitized_txs.iter().map(|tx| tx.account_keys().len()).sum();
483
484 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 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 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 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 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 TransactionLoadResult::Unprocessable(e) => Err(e),
558
559 TransactionLoadResult::NoOp(NoOpTransaction {
563 validation_error: e,
564 ..
565 }) if config.drop_on_failure || config.drop_noop_transactions => Err(e),
566
567 TransactionLoadResult::NoOp(no_op_tx) =>
569 Ok(ProcessedTransaction::NoOp(Box::new(no_op_tx))),
570
571 TransactionLoadResult::FeesOnly(FeesOnlyTransaction { load_error: e, .. })
573 if config.drop_on_failure =>
574 Err(e),
575
576 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 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, );
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 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 (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 program_cache_for_tx_batch.merge(&executed_tx.programs_modified_by_tx);
655
656 Ok(ProcessedTransaction::Executed(Box::new(executed_tx)))
657 }
658 (
660 Err(TransactionError::InstructionError(_, InstructionError::BailOut)),
661 _,
662 ) => Err(TransactionError::BailOut),
663 (Err(err), true) => Err(err.clone()),
666 (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 config.all_or_nothing && processing_result.is_err() {
689 for res in processing_results.iter_mut() {
691 *res = Err(TransactionError::CommitCancelled);
692 }
693
694 processing_results.push(processing_result);
696
697 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 balance_collector: None,
710 };
711 }
712
713 processing_results.push(processing_result);
714 }
715
716 if program_cache_for_tx_batch.loaded_missing || program_cache_for_tx_batch.merged_modified {
721 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 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 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 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 let fee_payer_balance = account_loader
815 .load_account(message.fee_payer())
816 .map(|account| account.lamports());
817
818 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 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 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 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 !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 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 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 return;
977 }
978 let mut count_hits_and_misses = true;
979 loop {
980 let global_program_cache = self.global_program_cache.read().unwrap();
982 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 drop(global_program_cache);
995
996 let program_to_store = program_to_load.map(|key| {
997 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 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 *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 let _new_cookie = task_waiter.wait(task_cookie);
1037 }
1038 }
1039 }
1040
1041 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 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, false, )
1068 };
1070 if let Some(key) = program_to_load {
1072 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 let mut program_cache_guard = self.global_program_cache.write().unwrap();
1084 program_cache_guard.finish_cooperative_loading_task(
1086 upcoming_environment,
1087 self.slot,
1088 key,
1089 recompiled,
1090 );
1091 }
1092 }
1093
1094 #[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 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 if let Some(fee_payer_touched) = touched_flags.first_mut() {
1246 *fee_payer_touched = true;
1247 }
1248
1249 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 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 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 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 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 } 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 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 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 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>, 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 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 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 transaction_context.push().unwrap();
1705 transaction_context
1707 .configure_next_cpi_for_tests(0, vec![], vec![0, 0])
1708 .unwrap();
1709 transaction_context.push().unwrap();
1710 transaction_context.pop().unwrap();
1712 transaction_context.pop().unwrap();
1713 transaction_context.push().unwrap();
1715 transaction_context.pop().unwrap();
1716 transaction_context.push().unwrap();
1718 transaction_context
1720 .configure_next_cpi_for_tests(0, vec![], vec![2, 0])
1721 .unwrap();
1722 transaction_context.push().unwrap();
1723 transaction_context
1725 .configure_next_cpi_for_tests(0, vec![], vec![2, 1])
1726 .unwrap();
1727 transaction_context.push().unwrap();
1728 transaction_context.pop().unwrap();
1730 transaction_context.pop().unwrap();
1732 transaction_context
1734 .configure_next_cpi_for_tests(0, vec![], vec![2, 2])
1735 .unwrap();
1736 transaction_context.push().unwrap();
1737 transaction_context.pop().unwrap();
1739 transaction_context.pop().unwrap();
1740 transaction_context.push().unwrap();
1742 transaction_context
1744 .configure_next_cpi_for_tests(0, vec![], vec![3, 0])
1745 .unwrap();
1746 transaction_context.push().unwrap();
1747 transaction_context
1749 .configure_next_cpi_for_tests(0, vec![], vec![3, 1])
1750 .unwrap();
1751 transaction_context.push().unwrap();
1752 transaction_context
1754 .configure_next_cpi_for_tests(0, vec![], vec![3, 2])
1755 .unwrap();
1756 transaction_context.push().unwrap();
1757 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 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 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 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 transaction_processor.reset_sysvar_cache();
2119
2120 {
2121 let sysvar_cache = transaction_processor.sysvar_cache.read().unwrap();
2122 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 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 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 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 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 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 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 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 let mock_bank = MockBankCallback::default();
2315 for account_exists in [false, true] {
2316 if account_exists {
2317 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 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 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 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 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 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 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 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 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, *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 {
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, ),
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 {
3024 let fee_payer_account = AccountSharedData::new_data(
3025 transaction_fee + priority_fee, &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 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 #[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 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 transaction_processor.set_program_runtime_environment(ProgramRuntimeEnvironment::clone(
3147 ¤t_environment,
3148 ));
3149 assert_eq!(
3150 transaction_processor.program_runtime_environment,
3151 current_environment,
3152 );
3153 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 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 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 ¤t_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 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 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 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 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 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 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 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 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 assert_eq!(
3394 panicked.as_deref(),
3395 None, );
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, loader,
3403 environment,
3404 ))
3405 );
3406 }
3407 ProgramCacheEntryOwner::LoaderV3 | ProgramCacheEntryOwner::LoaderV4 => {
3408 assert_eq!(
3419 panicked.as_deref(),
3420 Some("Unexpected replacement of an entry")
3421 );
3422 }
3423 ProgramCacheEntryOwner::NativeLoader => {
3424 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 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 AccountSharedData::default()
3470 }
3471 ProgramDataCase::OwnedButEmpty => {
3472 let mut account = AccountSharedData::default();
3474 account.set_owner(bpf_loader_upgradeable::id());
3475 account
3476 }
3477 ProgramDataCase::Uninitialized => {
3478 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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}