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