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