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