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 },
33 solana_program_runtime::{
34 execution_budget::SVMTransactionExecutionCost,
35 invoke_context::{EnvironmentConfig, InvokeContext},
36 loaded_programs::{
37 EpochBoundaryPreparation, ForkGraph, ProgramCache, ProgramCacheEntry,
38 ProgramCacheForTxBatch, ProgramCacheMatchCriteria, ProgramRuntimeEnvironments,
39 },
40 sysvar_cache::SysvarCache,
41 },
42 solana_pubkey::Pubkey,
43 solana_rent::Rent,
44 solana_sdk_ids::system_program,
45 solana_svm_callback::TransactionProcessingCallback,
46 solana_svm_feature_set::SVMFeatureSet,
47 solana_svm_log_collector::LogCollector,
48 solana_svm_measure::{measure::Measure, measure_us},
49 solana_svm_timings::{ExecuteTimingType, ExecuteTimings},
50 solana_svm_transaction::{svm_message::SVMMessage, svm_transaction::SVMTransaction},
51 solana_svm_type_overrides::sync::{atomic::Ordering, Arc, RwLock, RwLockReadGuard},
52 solana_transaction_context::{ExecutionRecord, TransactionContext},
53 solana_transaction_error::{TransactionError, TransactionResult},
54 std::{
55 collections::HashSet,
56 fmt::{Debug, Formatter},
57 rc::Rc,
58 },
59};
60#[cfg(feature = "dev-context-only-utils")]
61use {
62 qualifier_attr::{field_qualifiers, qualifiers},
63 solana_program_runtime::{
64 loaded_programs::ProgramRuntimeEnvironment,
65 solana_sbpf::{program::BuiltinProgram, vm::Config as VmConfig},
66 },
67 std::sync::Weak,
68};
69
70pub type TransactionLogMessages = Vec<String>;
72
73pub struct LoadAndExecuteSanitizedTransactionsOutput {
76 pub error_metrics: TransactionErrorMetrics,
78 pub execute_timings: ExecuteTimings,
80 pub processing_results: Vec<TransactionProcessingResult>,
84 pub balance_collector: Option<BalanceCollector>,
87}
88
89#[derive(Copy, Clone, Default)]
91pub struct ExecutionRecordingConfig {
92 pub enable_cpi_recording: bool,
93 pub enable_log_recording: bool,
94 pub enable_return_data_recording: bool,
95 pub enable_transaction_balance_recording: bool,
96}
97
98impl ExecutionRecordingConfig {
99 pub fn new_single_setting(option: bool) -> Self {
100 ExecutionRecordingConfig {
101 enable_return_data_recording: option,
102 enable_log_recording: option,
103 enable_cpi_recording: option,
104 enable_transaction_balance_recording: option,
105 }
106 }
107}
108
109#[derive(Default)]
111pub struct TransactionProcessingConfig<'a> {
112 pub account_overrides: Option<&'a AccountOverrides>,
115 pub check_program_modification_slot: bool,
118 pub log_messages_bytes_limit: Option<usize>,
120 pub limit_to_load_programs: bool,
123 pub recording_config: ExecutionRecordingConfig,
125}
126
127#[derive(Default)]
129pub struct TransactionProcessingEnvironment {
130 pub blockhash: Hash,
132 pub blockhash_lamports_per_signature: u64,
139 pub epoch_total_stake: u64,
141 pub feature_set: SVMFeatureSet,
143 pub program_runtime_environments_for_execution: ProgramRuntimeEnvironments,
145 pub program_runtime_environments_for_deployment: ProgramRuntimeEnvironments,
148 pub rent: Rent,
150}
151
152#[cfg_attr(feature = "frozen-abi", derive(AbiExample))]
153#[cfg_attr(
154 feature = "dev-context-only-utils",
155 field_qualifiers(slot(pub), epoch(pub), sysvar_cache(pub))
156)]
157pub struct TransactionBatchProcessor<FG: ForkGraph> {
158 slot: Slot,
160
161 epoch: Epoch,
163
164 sysvar_cache: RwLock<SysvarCache>,
168
169 pub epoch_boundary_preparation: Arc<RwLock<EpochBoundaryPreparation>>,
171
172 pub global_program_cache: Arc<RwLock<ProgramCache<FG>>>,
174
175 pub environments: ProgramRuntimeEnvironments,
177
178 pub builtin_program_ids: RwLock<HashSet<Pubkey>>,
180
181 execution_cost: SVMTransactionExecutionCost,
182}
183
184impl<FG: ForkGraph> Debug for TransactionBatchProcessor<FG> {
185 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
186 f.debug_struct("TransactionBatchProcessor")
187 .field("slot", &self.slot)
188 .field("epoch", &self.epoch)
189 .field("sysvar_cache", &self.sysvar_cache)
190 .field("global_program_cache", &self.global_program_cache)
191 .finish()
192 }
193}
194
195impl<FG: ForkGraph> Default for TransactionBatchProcessor<FG> {
196 fn default() -> Self {
197 Self {
198 slot: Slot::default(),
199 epoch: Epoch::default(),
200 sysvar_cache: RwLock::<SysvarCache>::default(),
201 epoch_boundary_preparation: Arc::new(RwLock::new(EpochBoundaryPreparation::default())),
202 global_program_cache: Arc::new(RwLock::new(ProgramCache::new(Slot::default()))),
203 environments: ProgramRuntimeEnvironments::default(),
204 builtin_program_ids: RwLock::new(HashSet::new()),
205 execution_cost: SVMTransactionExecutionCost::default(),
206 }
207 }
208}
209
210impl<FG: ForkGraph> TransactionBatchProcessor<FG> {
211 pub fn new_uninitialized(slot: Slot, epoch: Epoch) -> Self {
221 let epoch_boundary_preparation =
222 Arc::new(RwLock::new(EpochBoundaryPreparation::new(epoch)));
223 Self {
224 slot,
225 epoch,
226 epoch_boundary_preparation,
227 global_program_cache: Arc::new(RwLock::new(ProgramCache::new(slot))),
228 ..Self::default()
229 }
230 }
231
232 #[cfg(feature = "dev-context-only-utils")]
241 pub fn new(
242 slot: Slot,
243 epoch: Epoch,
244 fork_graph: Weak<RwLock<FG>>,
245 program_runtime_environment_v1: Option<ProgramRuntimeEnvironment>,
246 program_runtime_environment_v2: Option<ProgramRuntimeEnvironment>,
247 ) -> Self {
248 let mut processor = Self::new_uninitialized(slot, epoch);
249 processor
250 .global_program_cache
251 .write()
252 .unwrap()
253 .set_fork_graph(fork_graph);
254 let empty_loader = || Arc::new(BuiltinProgram::new_loader(VmConfig::default()));
255 processor
256 .global_program_cache
257 .write()
258 .unwrap()
259 .latest_root_slot = processor.slot;
260 processor
261 .epoch_boundary_preparation
262 .write()
263 .unwrap()
264 .upcoming_epoch = processor.epoch;
265 processor.environments.program_runtime_v1 =
266 program_runtime_environment_v1.unwrap_or(empty_loader());
267 processor.environments.program_runtime_v2 =
268 program_runtime_environment_v2.unwrap_or(empty_loader());
269 processor
270 }
271
272 pub fn new_from(&self, slot: Slot, epoch: Epoch) -> Self {
279 Self {
280 slot,
281 epoch,
282 sysvar_cache: RwLock::<SysvarCache>::default(),
283 epoch_boundary_preparation: self.epoch_boundary_preparation.clone(),
284 global_program_cache: self.global_program_cache.clone(),
285 environments: self.environments.clone(),
286 builtin_program_ids: RwLock::new(self.builtin_program_ids.read().unwrap().clone()),
287 execution_cost: self.execution_cost,
288 }
289 }
290
291 pub fn set_execution_cost(&mut self, cost: SVMTransactionExecutionCost) {
294 self.execution_cost = cost;
295 }
296
297 pub fn set_environments(&mut self, new_environments: ProgramRuntimeEnvironments) {
299 if self.environments.program_runtime_v1 != new_environments.program_runtime_v1 {
301 self.environments.program_runtime_v1 = new_environments.program_runtime_v1;
302 }
303 if self.environments.program_runtime_v2 != new_environments.program_runtime_v2 {
304 self.environments.program_runtime_v2 = new_environments.program_runtime_v2;
305 }
306 if let Some(upcoming_environments) = &self
308 .epoch_boundary_preparation
309 .read()
310 .unwrap()
311 .upcoming_environments
312 {
313 if self.environments.program_runtime_v1 == upcoming_environments.program_runtime_v1
314 && !Arc::ptr_eq(
315 &self.environments.program_runtime_v1,
316 &upcoming_environments.program_runtime_v1,
317 )
318 {
319 self.environments.program_runtime_v1 =
320 upcoming_environments.program_runtime_v1.clone();
321 }
322 if self.environments.program_runtime_v2 == upcoming_environments.program_runtime_v2
323 && !Arc::ptr_eq(
324 &self.environments.program_runtime_v2,
325 &upcoming_environments.program_runtime_v2,
326 )
327 {
328 self.environments.program_runtime_v2 =
329 upcoming_environments.program_runtime_v2.clone();
330 }
331 }
332 }
333
334 pub fn get_environments_for_epoch(&self, epoch: Epoch) -> ProgramRuntimeEnvironments {
337 self.epoch_boundary_preparation
338 .read()
339 .unwrap()
340 .get_upcoming_environments_for_epoch(epoch)
341 .unwrap_or_else(|| self.environments.clone())
342 }
343
344 pub fn sysvar_cache(&self) -> RwLockReadGuard<'_, SysvarCache> {
345 self.sysvar_cache.read().unwrap()
346 }
347
348 pub fn load_and_execute_sanitized_transactions<CB: TransactionProcessingCallback>(
350 &self,
351 callbacks: &CB,
352 sanitized_txs: &[impl SVMTransaction],
353 check_results: Vec<TransactionCheckResult>,
354 environment: &TransactionProcessingEnvironment,
355 config: &TransactionProcessingConfig,
356 ) -> LoadAndExecuteSanitizedTransactionsOutput {
357 debug_assert_eq!(
362 sanitized_txs.len(),
363 check_results.len(),
364 "Length of check_results does not match length of sanitized_txs"
365 );
366
367 let mut error_metrics = TransactionErrorMetrics::default();
369 let mut execute_timings = ExecuteTimings::default();
370 let mut processing_results = Vec::with_capacity(sanitized_txs.len());
371
372 let account_keys_in_batch = sanitized_txs.iter().map(|tx| tx.account_keys().len()).sum();
376
377 let mut account_loader = AccountLoader::new_with_loaded_accounts_capacity(
379 config.account_overrides,
380 callbacks,
381 &environment.feature_set,
382 account_keys_in_batch,
383 );
384
385 let mut balance_collector = config
387 .recording_config
388 .enable_transaction_balance_recording
389 .then(|| BalanceCollector::new_with_transaction_count(sanitized_txs.len()));
390
391 let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::new(self.slot);
393 let builtins = self.builtin_program_ids.read().unwrap().clone();
394 let ((), program_cache_us) = measure_us!({
395 self.replenish_program_cache(
396 &account_loader,
397 &builtins,
398 &environment.program_runtime_environments_for_execution,
399 &mut program_cache_for_tx_batch,
400 &mut execute_timings,
401 config.check_program_modification_slot,
402 config.limit_to_load_programs,
403 false, );
405 });
406 execute_timings
407 .saturating_add_in_place(ExecuteTimingType::ProgramCacheUs, program_cache_us);
408
409 if program_cache_for_tx_batch.hit_max_limit {
410 return LoadAndExecuteSanitizedTransactionsOutput {
411 error_metrics,
412 execute_timings,
413 processing_results: (0..sanitized_txs.len())
414 .map(|_| Err(TransactionError::ProgramCacheHitMaxLimit))
415 .collect(),
416 balance_collector: None,
419 };
420 }
421
422 let (mut load_us, mut execution_us): (u64, u64) = (0, 0);
423
424 for (tx, check_result) in sanitized_txs.iter().zip(check_results) {
429 let (validate_result, validate_fees_us) =
430 measure_us!(check_result.and_then(|tx_details| {
431 Self::validate_transaction_nonce_and_fee_payer(
432 &mut account_loader,
433 tx,
434 tx_details,
435 &environment.blockhash,
436 &environment.rent,
437 &mut error_metrics,
438 )
439 }));
440 execute_timings
441 .saturating_add_in_place(ExecuteTimingType::ValidateFeesUs, validate_fees_us);
442
443 let (load_result, single_load_us) = measure_us!(load_transaction(
444 &mut account_loader,
445 tx,
446 validate_result,
447 &mut error_metrics,
448 &environment.rent,
449 ));
450 load_us = load_us.saturating_add(single_load_us);
451
452 let ((), collect_balances_us) =
453 measure_us!(balance_collector.collect_pre_balances(&mut account_loader, tx));
454 execute_timings
455 .saturating_add_in_place(ExecuteTimingType::CollectBalancesUs, collect_balances_us);
456
457 let (processing_result, single_execution_us) = measure_us!(match load_result {
458 TransactionLoadResult::NotLoaded(err) => Err(err),
459 TransactionLoadResult::FeesOnly(fees_only_tx) => {
460 account_loader.update_accounts_for_failed_tx(&fees_only_tx.rollback_accounts);
462
463 Ok(ProcessedTransaction::FeesOnly(Box::new(fees_only_tx)))
464 }
465 TransactionLoadResult::Loaded(loaded_transaction) => {
466 let (program_accounts_set, filter_executable_us) = measure_us!(self
467 .filter_executable_program_accounts(
468 &account_loader,
469 &mut program_cache_for_tx_batch,
470 tx,
471 ));
472 execute_timings.saturating_add_in_place(
473 ExecuteTimingType::FilterExecutableUs,
474 filter_executable_us,
475 );
476
477 let ((), program_cache_us) = measure_us!({
478 self.replenish_program_cache(
479 &account_loader,
480 &program_accounts_set,
481 &environment.program_runtime_environments_for_execution,
482 &mut program_cache_for_tx_batch,
483 &mut execute_timings,
484 config.check_program_modification_slot,
485 config.limit_to_load_programs,
486 true, );
488 });
489 execute_timings.saturating_add_in_place(
490 ExecuteTimingType::ProgramCacheUs,
491 program_cache_us,
492 );
493
494 if program_cache_for_tx_batch.hit_max_limit {
495 return LoadAndExecuteSanitizedTransactionsOutput {
496 error_metrics,
497 execute_timings,
498 processing_results: (0..sanitized_txs.len())
499 .map(|_| Err(TransactionError::ProgramCacheHitMaxLimit))
500 .collect(),
501 balance_collector: None,
504 };
505 }
506
507 let executed_tx = self.execute_loaded_transaction(
508 callbacks,
509 tx,
510 loaded_transaction,
511 &mut execute_timings,
512 &mut error_metrics,
513 &mut program_cache_for_tx_batch,
514 environment,
515 config,
516 );
517
518 account_loader.update_accounts_for_executed_tx(tx, &executed_tx);
522
523 if executed_tx.was_successful() {
524 program_cache_for_tx_batch.merge(&executed_tx.programs_modified_by_tx);
525 }
526
527 Ok(ProcessedTransaction::Executed(Box::new(executed_tx)))
528 }
529 });
530 execution_us = execution_us.saturating_add(single_execution_us);
531
532 let ((), collect_balances_us) =
533 measure_us!(balance_collector.collect_post_balances(&mut account_loader, tx));
534 execute_timings
535 .saturating_add_in_place(ExecuteTimingType::CollectBalancesUs, collect_balances_us);
536
537 processing_results.push(processing_result);
538 }
539
540 if program_cache_for_tx_batch.loaded_missing || program_cache_for_tx_batch.merged_modified {
545 const SHRINK_LOADED_PROGRAMS_TO_PERCENTAGE: u8 = 90;
546 self.global_program_cache
547 .write()
548 .unwrap()
549 .evict_using_2s_random_selection(
550 Percentage::from(SHRINK_LOADED_PROGRAMS_TO_PERCENTAGE),
551 self.slot,
552 );
553 }
554
555 debug!(
556 "load: {}us execute: {}us txs_len={}",
557 load_us,
558 execution_us,
559 sanitized_txs.len(),
560 );
561 execute_timings.saturating_add_in_place(ExecuteTimingType::LoadUs, load_us);
562 execute_timings.saturating_add_in_place(ExecuteTimingType::ExecuteUs, execution_us);
563
564 if let Some(ref balance_collector) = balance_collector {
565 debug_assert!(balance_collector.lengths_match_expected(sanitized_txs.len()));
566 }
567
568 LoadAndExecuteSanitizedTransactionsOutput {
569 error_metrics,
570 execute_timings,
571 processing_results,
572 balance_collector,
573 }
574 }
575
576 fn validate_transaction_nonce_and_fee_payer<CB: TransactionProcessingCallback>(
577 account_loader: &mut AccountLoader<CB>,
578 message: &impl SVMMessage,
579 checked_details: CheckedTransactionDetails,
580 environment_blockhash: &Hash,
581 rent: &Rent,
582 error_counters: &mut TransactionErrorMetrics,
583 ) -> TransactionResult<ValidatedTransactionDetails> {
584 if let CheckedTransactionDetails {
589 nonce: Some(ref nonce_info),
590 compute_budget_and_limits: _,
591 } = checked_details
592 {
593 let next_durable_nonce = DurableNonce::from_blockhash(environment_blockhash);
594 Self::validate_transaction_nonce(
595 account_loader,
596 message,
597 nonce_info,
598 &next_durable_nonce,
599 error_counters,
600 )?;
601 }
602
603 Self::validate_transaction_fee_payer(
605 account_loader,
606 message,
607 checked_details,
608 rent,
609 error_counters,
610 )
611 }
612
613 fn validate_transaction_fee_payer<CB: TransactionProcessingCallback>(
617 account_loader: &mut AccountLoader<CB>,
618 message: &impl SVMMessage,
619 checked_details: CheckedTransactionDetails,
620 rent: &Rent,
621 error_counters: &mut TransactionErrorMetrics,
622 ) -> TransactionResult<ValidatedTransactionDetails> {
623 let CheckedTransactionDetails {
624 nonce,
625 compute_budget_and_limits,
626 } = checked_details;
627
628 let compute_budget_and_limits = compute_budget_and_limits.inspect_err(|_err| {
629 error_counters.invalid_compute_budget += 1;
630 })?;
631
632 let fee_payer_address = message.fee_payer();
633
634 let Some(mut loaded_fee_payer) =
638 account_loader.load_transaction_account(fee_payer_address, true)
639 else {
640 error_counters.account_not_found += 1;
641 return Err(TransactionError::AccountNotFound);
642 };
643
644 let fee_payer_loaded_rent_epoch = loaded_fee_payer.account.rent_epoch();
645 update_rent_exempt_status_for_account(rent, &mut loaded_fee_payer.account);
646
647 let fee_payer_index = 0;
648 validate_fee_payer(
649 fee_payer_address,
650 &mut loaded_fee_payer.account,
651 fee_payer_index,
652 error_counters,
653 rent,
654 compute_budget_and_limits.fee_details.total_fee(),
655 )?;
656
657 let rollback_accounts = RollbackAccounts::new(
660 nonce,
661 *fee_payer_address,
662 loaded_fee_payer.account.clone(),
663 fee_payer_loaded_rent_epoch,
664 );
665
666 Ok(ValidatedTransactionDetails {
667 fee_details: compute_budget_and_limits.fee_details,
668 rollback_accounts,
669 loaded_accounts_bytes_limit: compute_budget_and_limits.loaded_accounts_data_size_limit,
670 compute_budget: compute_budget_and_limits.budget,
671 loaded_fee_payer_account: loaded_fee_payer,
672 })
673 }
674
675 fn validate_transaction_nonce<CB: TransactionProcessingCallback>(
676 account_loader: &mut AccountLoader<CB>,
677 message: &impl SVMMessage,
678 nonce_info: &NonceInfo,
679 next_durable_nonce: &DurableNonce,
680 error_counters: &mut TransactionErrorMetrics,
681 ) -> TransactionResult<()> {
682 let nonce_is_valid = account_loader
690 .load_transaction_account(nonce_info.address(), true)
691 .and_then(|ref current_nonce| {
692 system_program::check_id(current_nonce.account.owner()).then_some(())?;
693 StateMut::<NonceVersions>::state(¤t_nonce.account).ok()
694 })
695 .and_then(
696 |current_nonce_versions| match current_nonce_versions.state() {
697 NonceState::Initialized(ref current_nonce_data) => {
698 let nonce_can_be_advanced =
699 ¤t_nonce_data.durable_nonce != next_durable_nonce;
700
701 let nonce_authority_is_valid = message
702 .account_keys()
703 .iter()
704 .enumerate()
705 .any(|(i, address)| {
706 address == ¤t_nonce_data.authority && message.is_signer(i)
707 });
708
709 if nonce_authority_is_valid {
710 Some(nonce_can_be_advanced)
711 } else {
712 None
713 }
714 }
715 _ => None,
716 },
717 );
718
719 match nonce_is_valid {
720 None => {
721 error_counters.blockhash_not_found += 1;
722 Err(TransactionError::BlockhashNotFound)
723 }
724 Some(false) => {
725 error_counters.account_not_found += 1;
726 Err(TransactionError::AccountNotFound)
727 }
728 Some(true) => Ok(()),
729 }
730 }
731
732 fn filter_executable_program_accounts<CB: TransactionProcessingCallback>(
735 &self,
736 account_loader: &AccountLoader<CB>,
737 program_cache_for_tx_batch: &mut ProgramCacheForTxBatch,
738 tx: &impl SVMMessage,
739 ) -> HashSet<Pubkey> {
740 let mut program_accounts_set = HashSet::default();
741 for account_key in tx.account_keys().iter() {
742 if let Some(cache_entry) = program_cache_for_tx_batch.find(account_key) {
743 cache_entry.tx_usage_counter.fetch_add(1, Ordering::Relaxed);
744 } else if account_loader
745 .get_account_shared_data(account_key)
746 .map(|(account, _slot)| PROGRAM_OWNERS.contains(account.owner()))
747 .unwrap_or(false)
748 {
749 program_accounts_set.insert(*account_key);
750 }
751 }
752 program_accounts_set
753 }
754
755 #[cfg_attr(feature = "dev-context-only-utils", qualifiers(pub))]
756 fn replenish_program_cache<CB: TransactionProcessingCallback>(
757 &self,
758 account_loader: &AccountLoader<CB>,
759 program_accounts_set: &HashSet<Pubkey>,
760 program_runtime_environments_for_execution: &ProgramRuntimeEnvironments,
761 program_cache_for_tx_batch: &mut ProgramCacheForTxBatch,
762 execute_timings: &mut ExecuteTimings,
763 check_program_modification_slot: bool,
764 limit_to_load_programs: bool,
765 increment_usage_counter: bool,
766 ) {
767 let mut missing_programs: Vec<(Pubkey, ProgramCacheMatchCriteria)> = program_accounts_set
768 .iter()
769 .map(|pubkey| {
770 let match_criteria = if check_program_modification_slot {
771 get_program_modification_slot(account_loader, pubkey)
772 .map_or(ProgramCacheMatchCriteria::Tombstone, |slot| {
773 ProgramCacheMatchCriteria::DeployedOnOrAfterSlot(slot)
774 })
775 } else {
776 ProgramCacheMatchCriteria::NoCriteria
777 };
778 (*pubkey, match_criteria)
779 })
780 .collect();
781
782 let mut count_hits_and_misses = true;
783 loop {
784 let (program_to_store, task_cookie, task_waiter) = {
785 let global_program_cache = self.global_program_cache.read().unwrap();
787 let program_to_load = global_program_cache.extract(
789 &mut missing_programs,
790 program_cache_for_tx_batch,
791 program_runtime_environments_for_execution,
792 increment_usage_counter,
793 count_hits_and_misses,
794 );
795 count_hits_and_misses = false;
796
797 let program_to_store = program_to_load.map(|key| {
798 let program = load_program_with_pubkey(
800 account_loader,
801 program_runtime_environments_for_execution,
802 &key,
803 self.slot,
804 execute_timings,
805 false,
806 )
807 .expect("called load_program_with_pubkey() with nonexistent account");
808 (key, program)
809 });
810
811 let task_waiter = Arc::clone(&global_program_cache.loading_task_waiter);
812 (program_to_store, task_waiter.cookie(), task_waiter)
813 };
815
816 if let Some((key, program)) = program_to_store {
817 program_cache_for_tx_batch.loaded_missing = true;
818 let mut global_program_cache = self.global_program_cache.write().unwrap();
819 if global_program_cache.finish_cooperative_loading_task(
821 program_runtime_environments_for_execution,
822 self.slot,
823 key,
824 program,
825 ) && limit_to_load_programs
826 {
827 *program_cache_for_tx_batch = ProgramCacheForTxBatch::new(self.slot);
831 program_cache_for_tx_batch.hit_max_limit = true;
832 return;
833 }
834 } else if missing_programs.is_empty() {
835 break;
836 } else {
837 let _new_cookie = task_waiter.wait(task_cookie);
841 }
842 }
843 }
844
845 #[allow(clippy::too_many_arguments)]
848 fn execute_loaded_transaction<CB: TransactionProcessingCallback>(
849 &self,
850 callback: &CB,
851 tx: &impl SVMTransaction,
852 mut loaded_transaction: LoadedTransaction,
853 execute_timings: &mut ExecuteTimings,
854 error_metrics: &mut TransactionErrorMetrics,
855 program_cache_for_tx_batch: &mut ProgramCacheForTxBatch,
856 environment: &TransactionProcessingEnvironment,
857 config: &TransactionProcessingConfig,
858 ) -> ExecutedTransaction {
859 let transaction_accounts = std::mem::take(&mut loaded_transaction.accounts);
860
861 debug_assert!(transaction_accounts.len() == tx.account_keys().len());
865
866 fn transaction_accounts_lamports_sum(
867 accounts: &[(Pubkey, AccountSharedData)],
868 ) -> Option<u128> {
869 accounts.iter().try_fold(0u128, |sum, (_, account)| {
870 sum.checked_add(u128::from(account.lamports()))
871 })
872 }
873
874 let lamports_before_tx =
875 transaction_accounts_lamports_sum(&transaction_accounts).unwrap_or(0);
876
877 let compute_budget = loaded_transaction.compute_budget;
878
879 let mut transaction_context = TransactionContext::new(
880 transaction_accounts,
881 environment.rent.clone(),
882 compute_budget.max_instruction_stack_depth,
883 compute_budget.max_instruction_trace_length,
884 );
885
886 let pre_account_state_info =
887 TransactionAccountStateInfo::new(&transaction_context, tx, &environment.rent);
888
889 let log_collector = if config.recording_config.enable_log_recording {
890 match config.log_messages_bytes_limit {
891 None => Some(LogCollector::new_ref()),
892 Some(log_messages_bytes_limit) => Some(LogCollector::new_ref_with_limit(Some(
893 log_messages_bytes_limit,
894 ))),
895 }
896 } else {
897 None
898 };
899
900 let mut executed_units = 0u64;
901 let sysvar_cache = &self.sysvar_cache.read().unwrap();
902
903 let mut invoke_context = InvokeContext::new(
904 &mut transaction_context,
905 program_cache_for_tx_batch,
906 EnvironmentConfig::new(
907 environment.blockhash,
908 environment.blockhash_lamports_per_signature,
909 callback,
910 &environment.feature_set,
911 &environment.program_runtime_environments_for_execution,
912 &environment.program_runtime_environments_for_deployment,
913 sysvar_cache,
914 ),
915 log_collector.clone(),
916 compute_budget,
917 self.execution_cost,
918 );
919
920 let mut process_message_time = Measure::start("process_message_time");
921 let process_result = process_message(
922 tx,
923 &loaded_transaction.program_indices,
924 &mut invoke_context,
925 execute_timings,
926 &mut executed_units,
927 );
928 process_message_time.stop();
929
930 drop(invoke_context);
931
932 execute_timings.execute_accessories.process_message_us += process_message_time.as_us();
933
934 let mut status = process_result
935 .and_then(|info| {
936 let post_account_state_info =
937 TransactionAccountStateInfo::new(&transaction_context, tx, &environment.rent);
938 TransactionAccountStateInfo::verify_changes(
939 &pre_account_state_info,
940 &post_account_state_info,
941 &transaction_context,
942 )
943 .map(|_| info)
944 })
945 .map_err(|err| {
946 match err {
947 TransactionError::InvalidRentPayingAccount
948 | TransactionError::InsufficientFundsForRent { .. } => {
949 error_metrics.invalid_rent_paying_account += 1;
950 }
951 TransactionError::InvalidAccountIndex => {
952 error_metrics.invalid_account_index += 1;
953 }
954 _ => {
955 error_metrics.instruction_error += 1;
956 }
957 }
958 err
959 });
960
961 let log_messages: Option<TransactionLogMessages> =
962 log_collector.and_then(|log_collector| {
963 Rc::try_unwrap(log_collector)
964 .map(|log_collector| log_collector.into_inner().into_messages())
965 .ok()
966 });
967
968 let (execution_record, inner_instructions) = Self::deconstruct_transaction(
969 transaction_context,
970 config.recording_config.enable_cpi_recording,
971 );
972
973 let ExecutionRecord {
974 accounts,
975 return_data,
976 touched_account_count,
977 accounts_resize_delta: accounts_data_len_delta,
978 } = execution_record;
979
980 if status.is_ok()
981 && transaction_accounts_lamports_sum(&accounts)
982 .filter(|lamports_after_tx| lamports_before_tx == *lamports_after_tx)
983 .is_none()
984 {
985 status = Err(TransactionError::UnbalancedTransaction);
986 }
987 let status = status.map(|_| ());
988
989 loaded_transaction.accounts = accounts;
990 execute_timings.details.total_account_count += loaded_transaction.accounts.len() as u64;
991 execute_timings.details.changed_account_count += touched_account_count;
992
993 let return_data = if config.recording_config.enable_return_data_recording
994 && !return_data.data.is_empty()
995 {
996 Some(return_data)
997 } else {
998 None
999 };
1000
1001 ExecutedTransaction {
1002 execution_details: TransactionExecutionDetails {
1003 status,
1004 log_messages,
1005 inner_instructions,
1006 return_data,
1007 executed_units,
1008 accounts_data_len_delta,
1009 },
1010 loaded_transaction,
1011 programs_modified_by_tx: program_cache_for_tx_batch.drain_modified_entries(),
1012 }
1013 }
1014
1015 fn deconstruct_transaction(
1017 mut transaction_context: TransactionContext,
1018 record_inner_instructions: bool,
1019 ) -> (ExecutionRecord, Option<InnerInstructionsList>) {
1020 let inner_ix = if record_inner_instructions {
1021 debug_assert!(transaction_context
1022 .get_instruction_context_at_index_in_trace(0)
1023 .map(|instruction_context| instruction_context.get_stack_height()
1024 == TRANSACTION_LEVEL_STACK_HEIGHT)
1025 .unwrap_or(true));
1026
1027 let ix_trace = transaction_context.take_instruction_trace();
1028 let mut outer_instructions = Vec::new();
1029 for ix_in_trace in ix_trace.into_iter() {
1030 let stack_height = ix_in_trace.nesting_level.saturating_add(1);
1031 if stack_height == TRANSACTION_LEVEL_STACK_HEIGHT {
1032 outer_instructions.push(Vec::new());
1033 } else if let Some(inner_instructions) = outer_instructions.last_mut() {
1034 let stack_height = u8::try_from(stack_height).unwrap_or(u8::MAX);
1035 inner_instructions.push(InnerInstruction {
1036 instruction: CompiledInstruction::new_from_raw_parts(
1037 ix_in_trace.program_account_index_in_tx as u8,
1038 ix_in_trace.instruction_data.into_owned(),
1039 ix_in_trace
1040 .instruction_accounts
1041 .iter()
1042 .map(|acc| acc.index_in_transaction as u8)
1043 .collect(),
1044 ),
1045 stack_height,
1046 });
1047 } else {
1048 debug_assert!(false);
1049 }
1050 }
1051
1052 Some(outer_instructions)
1053 } else {
1054 None
1055 };
1056
1057 let record: ExecutionRecord = transaction_context.into();
1058
1059 (record, inner_ix)
1060 }
1061
1062 pub fn fill_missing_sysvar_cache_entries<CB: TransactionProcessingCallback>(
1063 &self,
1064 callbacks: &CB,
1065 ) {
1066 let mut sysvar_cache = self.sysvar_cache.write().unwrap();
1067 sysvar_cache.fill_missing_entries(|pubkey, set_sysvar| {
1068 if let Some((account, _slot)) = callbacks.get_account_shared_data(pubkey) {
1069 set_sysvar(account.data());
1070 }
1071 });
1072 }
1073
1074 pub fn reset_sysvar_cache(&self) {
1075 let mut sysvar_cache = self.sysvar_cache.write().unwrap();
1076 sysvar_cache.reset();
1077 }
1078
1079 pub fn get_sysvar_cache_for_tests(&self) -> SysvarCache {
1080 self.sysvar_cache.read().unwrap().clone()
1081 }
1082
1083 pub fn add_builtin(&self, program_id: Pubkey, builtin: ProgramCacheEntry) {
1085 self.builtin_program_ids.write().unwrap().insert(program_id);
1086 self.global_program_cache.write().unwrap().assign_program(
1087 &self.environments,
1088 program_id,
1089 Arc::new(builtin),
1090 );
1091 }
1092
1093 #[cfg(feature = "dev-context-only-utils")]
1094 #[cfg_attr(feature = "dev-context-only-utils", qualifiers(pub))]
1095 fn writable_sysvar_cache(&self) -> &RwLock<SysvarCache> {
1096 &self.sysvar_cache
1097 }
1098}
1099
1100#[cfg(test)]
1101mod tests {
1102 #[allow(deprecated)]
1103 use solana_sysvar::fees::Fees;
1104 use {
1105 super::*,
1106 crate::{
1107 account_loader::{
1108 LoadedTransactionAccount, ValidatedTransactionDetails,
1109 TRANSACTION_ACCOUNT_BASE_SIZE,
1110 },
1111 nonce_info::NonceInfo,
1112 rent_calculator::RENT_EXEMPT_RENT_EPOCH,
1113 rollback_accounts::RollbackAccounts,
1114 },
1115 solana_account::{create_account_shared_data_for_test, WritableAccount},
1116 solana_clock::Clock,
1117 solana_compute_budget_interface::ComputeBudgetInstruction,
1118 solana_epoch_schedule::EpochSchedule,
1119 solana_fee_calculator::FeeCalculator,
1120 solana_fee_structure::FeeDetails,
1121 solana_hash::Hash,
1122 solana_keypair::Keypair,
1123 solana_message::{LegacyMessage, Message, MessageHeader, SanitizedMessage},
1124 solana_nonce as nonce,
1125 solana_program_runtime::{
1126 execution_budget::{
1127 SVMTransactionExecutionAndFeeBudgetLimits, SVMTransactionExecutionBudget,
1128 },
1129 loaded_programs::{BlockRelation, ProgramCacheEntryType},
1130 },
1131 solana_rent::Rent,
1132 solana_sdk_ids::{bpf_loader, loader_v4, system_program, sysvar},
1133 solana_signature::Signature,
1134 solana_svm_callback::{AccountState, InvokeContextCallback},
1135 solana_transaction::{sanitized::SanitizedTransaction, Transaction},
1136 solana_transaction_context::TransactionContext,
1137 solana_transaction_error::{TransactionError, TransactionError::DuplicateInstruction},
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, Ok(compute_budget_and_limits)),
1986 &Hash::default(),
1987 &rent,
1988 &mut error_counters,
1989 );
1990
1991 let post_validation_fee_payer_account = {
1992 let mut account = fee_payer_account.clone();
1993 account.set_rent_epoch(RENT_EXEMPT_RENT_EPOCH);
1994 account.set_lamports(0);
1995 account
1996 };
1997
1998 let base_account_size = if formalize_loaded_transaction_data_size {
1999 TRANSACTION_ACCOUNT_BASE_SIZE
2000 } else {
2001 0
2002 };
2003
2004 assert_eq!(
2005 result,
2006 Ok(ValidatedTransactionDetails {
2007 rollback_accounts: RollbackAccounts::new(
2008 None, *fee_payer_address,
2010 post_validation_fee_payer_account.clone(),
2011 fee_payer_rent_epoch
2012 ),
2013 compute_budget: compute_budget_and_limits.budget,
2014 loaded_accounts_bytes_limit: compute_budget_and_limits
2015 .loaded_accounts_data_size_limit,
2016 fee_details: FeeDetails::new(transaction_fee, priority_fee),
2017 loaded_fee_payer_account: LoadedTransactionAccount {
2018 loaded_size: base_account_size + fee_payer_account.data().len(),
2019 account: post_validation_fee_payer_account,
2020 },
2021 })
2022 );
2023 }
2024
2025 #[test_case(false; "informal_loaded_size")]
2026 #[test_case(true; "simd186_loaded_size")]
2027 fn test_validate_transaction_fee_payer_rent_paying(
2028 formalize_loaded_transaction_data_size: bool,
2029 ) {
2030 let lamports_per_signature = 5000;
2031 let message = new_unchecked_sanitized_message(Message::new_with_blockhash(
2032 &[],
2033 Some(&Pubkey::new_unique()),
2034 &Hash::new_unique(),
2035 ));
2036 let fee_payer_address = message.fee_payer();
2037 let rent = Rent {
2038 lamports_per_byte_year: 1_000_000,
2039 ..Default::default()
2040 };
2041 let min_balance = rent.minimum_balance(0);
2042 let transaction_fee = lamports_per_signature;
2043 let starting_balance = min_balance - 1;
2044 let fee_payer_account = AccountSharedData::new(starting_balance, 0, &Pubkey::default());
2045
2046 let mut mock_accounts = HashMap::new();
2047 mock_accounts.insert(*fee_payer_address, fee_payer_account.clone());
2048 let mut mock_bank = MockBankCallback {
2049 account_shared_data: Arc::new(RwLock::new(mock_accounts)),
2050 ..Default::default()
2051 };
2052 mock_bank.feature_set.formalize_loaded_transaction_data_size =
2053 formalize_loaded_transaction_data_size;
2054 let mut account_loader = (&mock_bank).into();
2055
2056 let mut error_counters = TransactionErrorMetrics::default();
2057 let compute_budget_and_limits = SVMTransactionExecutionAndFeeBudgetLimits::with_fee(
2058 MockBankCallback::calculate_fee_details(&message, lamports_per_signature, 0),
2059 );
2060 let result =
2061 TransactionBatchProcessor::<TestForkGraph>::validate_transaction_nonce_and_fee_payer(
2062 &mut account_loader,
2063 &message,
2064 CheckedTransactionDetails::new(None, Ok(compute_budget_and_limits)),
2065 &Hash::default(),
2066 &rent,
2067 &mut error_counters,
2068 );
2069
2070 let post_validation_fee_payer_account = {
2071 let mut account = fee_payer_account.clone();
2072 account.set_lamports(starting_balance - transaction_fee);
2073 account
2074 };
2075
2076 let base_account_size = if formalize_loaded_transaction_data_size {
2077 TRANSACTION_ACCOUNT_BASE_SIZE
2078 } else {
2079 0
2080 };
2081
2082 assert_eq!(
2083 result,
2084 Ok(ValidatedTransactionDetails {
2085 rollback_accounts: RollbackAccounts::new(
2086 None, *fee_payer_address,
2088 post_validation_fee_payer_account.clone(),
2089 0, ),
2091 compute_budget: compute_budget_and_limits.budget,
2092 loaded_accounts_bytes_limit: compute_budget_and_limits
2093 .loaded_accounts_data_size_limit,
2094 fee_details: FeeDetails::new(transaction_fee, 0),
2095 loaded_fee_payer_account: LoadedTransactionAccount {
2096 loaded_size: base_account_size + fee_payer_account.data().len(),
2097 account: post_validation_fee_payer_account,
2098 }
2099 })
2100 );
2101 }
2102
2103 #[test]
2104 fn test_validate_transaction_fee_payer_not_found() {
2105 let message =
2106 new_unchecked_sanitized_message(Message::new(&[], Some(&Pubkey::new_unique())));
2107
2108 let mock_bank = MockBankCallback::default();
2109 let mut account_loader = (&mock_bank).into();
2110 let mut error_counters = TransactionErrorMetrics::default();
2111 let result =
2112 TransactionBatchProcessor::<TestForkGraph>::validate_transaction_nonce_and_fee_payer(
2113 &mut account_loader,
2114 &message,
2115 CheckedTransactionDetails::new(
2116 None,
2117 Ok(SVMTransactionExecutionAndFeeBudgetLimits::default()),
2118 ),
2119 &Hash::default(),
2120 &Rent::default(),
2121 &mut error_counters,
2122 );
2123
2124 assert_eq!(error_counters.account_not_found.0, 1);
2125 assert_eq!(result, Err(TransactionError::AccountNotFound));
2126 }
2127
2128 #[test]
2129 fn test_validate_transaction_fee_payer_insufficient_funds() {
2130 let lamports_per_signature = 5000;
2131 let message =
2132 new_unchecked_sanitized_message(Message::new(&[], Some(&Pubkey::new_unique())));
2133 let fee_payer_address = message.fee_payer();
2134 let fee_payer_account = AccountSharedData::new(1, 0, &Pubkey::default());
2135 let mut mock_accounts = HashMap::new();
2136 mock_accounts.insert(*fee_payer_address, fee_payer_account.clone());
2137 let mock_bank = MockBankCallback {
2138 account_shared_data: Arc::new(RwLock::new(mock_accounts)),
2139 ..Default::default()
2140 };
2141 let mut account_loader = (&mock_bank).into();
2142
2143 let mut error_counters = TransactionErrorMetrics::default();
2144 let result =
2145 TransactionBatchProcessor::<TestForkGraph>::validate_transaction_nonce_and_fee_payer(
2146 &mut account_loader,
2147 &message,
2148 CheckedTransactionDetails::new(
2149 None,
2150 Ok(SVMTransactionExecutionAndFeeBudgetLimits::with_fee(
2151 MockBankCallback::calculate_fee_details(
2152 &message,
2153 lamports_per_signature,
2154 0,
2155 ),
2156 )),
2157 ),
2158 &Hash::default(),
2159 &Rent::default(),
2160 &mut error_counters,
2161 );
2162
2163 assert_eq!(error_counters.insufficient_funds.0, 1);
2164 assert_eq!(result, Err(TransactionError::InsufficientFundsForFee));
2165 }
2166
2167 #[test]
2168 fn test_validate_transaction_fee_payer_insufficient_rent() {
2169 let lamports_per_signature = 5000;
2170 let message =
2171 new_unchecked_sanitized_message(Message::new(&[], Some(&Pubkey::new_unique())));
2172 let fee_payer_address = message.fee_payer();
2173 let transaction_fee = lamports_per_signature;
2174 let rent = Rent::default();
2175 let min_balance = rent.minimum_balance(0);
2176 let starting_balance = min_balance + transaction_fee - 1;
2177 let fee_payer_account = AccountSharedData::new(starting_balance, 0, &Pubkey::default());
2178 let mut mock_accounts = HashMap::new();
2179 mock_accounts.insert(*fee_payer_address, fee_payer_account.clone());
2180 let mock_bank = MockBankCallback {
2181 account_shared_data: Arc::new(RwLock::new(mock_accounts)),
2182 ..Default::default()
2183 };
2184 let mut account_loader = (&mock_bank).into();
2185
2186 let mut error_counters = TransactionErrorMetrics::default();
2187 let result =
2188 TransactionBatchProcessor::<TestForkGraph>::validate_transaction_nonce_and_fee_payer(
2189 &mut account_loader,
2190 &message,
2191 CheckedTransactionDetails::new(
2192 None,
2193 Ok(SVMTransactionExecutionAndFeeBudgetLimits::with_fee(
2194 MockBankCallback::calculate_fee_details(
2195 &message,
2196 lamports_per_signature,
2197 0,
2198 ),
2199 )),
2200 ),
2201 &Hash::default(),
2202 &rent,
2203 &mut error_counters,
2204 );
2205
2206 assert_eq!(
2207 result,
2208 Err(TransactionError::InsufficientFundsForRent { account_index: 0 })
2209 );
2210 }
2211
2212 #[test]
2213 fn test_validate_transaction_fee_payer_invalid() {
2214 let lamports_per_signature = 5000;
2215 let message =
2216 new_unchecked_sanitized_message(Message::new(&[], Some(&Pubkey::new_unique())));
2217 let fee_payer_address = message.fee_payer();
2218 let fee_payer_account = AccountSharedData::new(1_000_000, 0, &Pubkey::new_unique());
2219 let mut mock_accounts = HashMap::new();
2220 mock_accounts.insert(*fee_payer_address, fee_payer_account.clone());
2221 let mock_bank = MockBankCallback {
2222 account_shared_data: Arc::new(RwLock::new(mock_accounts)),
2223 ..Default::default()
2224 };
2225 let mut account_loader = (&mock_bank).into();
2226
2227 let mut error_counters = TransactionErrorMetrics::default();
2228 let result =
2229 TransactionBatchProcessor::<TestForkGraph>::validate_transaction_nonce_and_fee_payer(
2230 &mut account_loader,
2231 &message,
2232 CheckedTransactionDetails::new(
2233 None,
2234 Ok(SVMTransactionExecutionAndFeeBudgetLimits::with_fee(
2235 MockBankCallback::calculate_fee_details(
2236 &message,
2237 lamports_per_signature,
2238 0,
2239 ),
2240 )),
2241 ),
2242 &Hash::default(),
2243 &Rent::default(),
2244 &mut error_counters,
2245 );
2246
2247 assert_eq!(error_counters.invalid_account_for_fee.0, 1);
2248 assert_eq!(result, Err(TransactionError::InvalidAccountForFee));
2249 }
2250
2251 #[test]
2252 fn test_validate_transaction_fee_payer_invalid_compute_budget() {
2253 let message = new_unchecked_sanitized_message(Message::new(
2254 &[
2255 ComputeBudgetInstruction::set_compute_unit_limit(2000u32),
2256 ComputeBudgetInstruction::set_compute_unit_limit(42u32),
2257 ],
2258 Some(&Pubkey::new_unique()),
2259 ));
2260
2261 let mock_bank = MockBankCallback::default();
2262 let mut account_loader = (&mock_bank).into();
2263 let mut error_counters = TransactionErrorMetrics::default();
2264 let result =
2265 TransactionBatchProcessor::<TestForkGraph>::validate_transaction_nonce_and_fee_payer(
2266 &mut account_loader,
2267 &message,
2268 CheckedTransactionDetails::new(None, Err(DuplicateInstruction(1))),
2269 &Hash::default(),
2270 &Rent::default(),
2271 &mut error_counters,
2272 );
2273
2274 assert_eq!(error_counters.invalid_compute_budget.0, 1);
2275 assert_eq!(result, Err(TransactionError::DuplicateInstruction(1u8)));
2276 }
2277
2278 #[test_case(false; "informal_loaded_size")]
2279 #[test_case(true; "simd186_loaded_size")]
2280 fn test_validate_transaction_fee_payer_is_nonce(formalize_loaded_transaction_data_size: bool) {
2281 let lamports_per_signature = 5000;
2282 let rent = Rent::default();
2283 let compute_unit_limit = 1000u64;
2284 let last_blockhash = Hash::new_unique();
2285 let message = new_unchecked_sanitized_message(Message::new_with_blockhash(
2286 &[
2287 ComputeBudgetInstruction::set_compute_unit_limit(compute_unit_limit as u32),
2288 ComputeBudgetInstruction::set_compute_unit_price(1_000_000),
2289 ],
2290 Some(&Pubkey::new_unique()),
2291 &last_blockhash,
2292 ));
2293 let transaction_fee = lamports_per_signature;
2294 let compute_budget_and_limits = SVMTransactionExecutionAndFeeBudgetLimits {
2295 fee_details: FeeDetails::new(transaction_fee, compute_unit_limit),
2296 ..SVMTransactionExecutionAndFeeBudgetLimits::default()
2297 };
2298 let fee_payer_address = message.fee_payer();
2299 let min_balance = Rent::default().minimum_balance(nonce::state::State::size());
2300 let priority_fee = compute_unit_limit;
2301
2302 {
2304 let fee_payer_account = AccountSharedData::new_data(
2305 min_balance + transaction_fee + priority_fee,
2306 &nonce::versions::Versions::new(nonce::state::State::Initialized(
2307 nonce::state::Data::new(
2308 *fee_payer_address,
2309 DurableNonce::default(),
2310 lamports_per_signature,
2311 ),
2312 )),
2313 &system_program::id(),
2314 )
2315 .unwrap();
2316
2317 let mut mock_accounts = HashMap::new();
2318 mock_accounts.insert(*fee_payer_address, fee_payer_account.clone());
2319 let mut mock_bank = MockBankCallback {
2320 account_shared_data: Arc::new(RwLock::new(mock_accounts)),
2321 ..Default::default()
2322 };
2323 mock_bank.feature_set.formalize_loaded_transaction_data_size =
2324 formalize_loaded_transaction_data_size;
2325 let mut account_loader = (&mock_bank).into();
2326
2327 let mut error_counters = TransactionErrorMetrics::default();
2328
2329 let environment_blockhash = Hash::new_unique();
2330 let next_durable_nonce = DurableNonce::from_blockhash(&environment_blockhash);
2331 let mut future_nonce = NonceInfo::new(*fee_payer_address, fee_payer_account.clone());
2332 future_nonce
2333 .try_advance_nonce(next_durable_nonce, lamports_per_signature)
2334 .unwrap();
2335
2336 let tx_details = CheckedTransactionDetails::new(
2337 Some(future_nonce.clone()),
2338 Ok(compute_budget_and_limits),
2339 );
2340
2341 let result = TransactionBatchProcessor::<TestForkGraph>::validate_transaction_nonce_and_fee_payer(
2342 &mut account_loader,
2343 &message,
2344 tx_details,
2345 &environment_blockhash,
2346 &rent,
2347 &mut error_counters,
2348 );
2349
2350 let post_validation_fee_payer_account = {
2351 let mut account = fee_payer_account.clone();
2352 account.set_rent_epoch(RENT_EXEMPT_RENT_EPOCH);
2353 account.set_lamports(min_balance);
2354 account
2355 };
2356
2357 let base_account_size = if formalize_loaded_transaction_data_size {
2358 TRANSACTION_ACCOUNT_BASE_SIZE
2359 } else {
2360 0
2361 };
2362
2363 assert_eq!(
2364 result,
2365 Ok(ValidatedTransactionDetails {
2366 rollback_accounts: RollbackAccounts::new(
2367 Some(future_nonce),
2368 *fee_payer_address,
2369 post_validation_fee_payer_account.clone(),
2370 0, ),
2372 compute_budget: compute_budget_and_limits.budget,
2373 loaded_accounts_bytes_limit: compute_budget_and_limits
2374 .loaded_accounts_data_size_limit,
2375 fee_details: FeeDetails::new(transaction_fee, priority_fee),
2376 loaded_fee_payer_account: LoadedTransactionAccount {
2377 loaded_size: base_account_size + fee_payer_account.data().len(),
2378 account: post_validation_fee_payer_account,
2379 }
2380 })
2381 );
2382 }
2383
2384 {
2386 let fee_payer_account = AccountSharedData::new_data(
2387 transaction_fee + priority_fee, &nonce::versions::Versions::new(nonce::state::State::Initialized(
2389 nonce::state::Data::default(),
2390 )),
2391 &system_program::id(),
2392 )
2393 .unwrap();
2394
2395 let mut mock_accounts = HashMap::new();
2396 mock_accounts.insert(*fee_payer_address, fee_payer_account.clone());
2397 let mock_bank = MockBankCallback {
2398 account_shared_data: Arc::new(RwLock::new(mock_accounts)),
2399 ..Default::default()
2400 };
2401 let mut account_loader = (&mock_bank).into();
2402
2403 let mut error_counters = TransactionErrorMetrics::default();
2404 let result = TransactionBatchProcessor::<TestForkGraph>::validate_transaction_nonce_and_fee_payer(
2405 &mut account_loader,
2406 &message,
2407 CheckedTransactionDetails::new(None, Ok(compute_budget_and_limits)),
2408 &Hash::default(),
2409 &rent,
2410 &mut error_counters,
2411 );
2412
2413 assert_eq!(error_counters.insufficient_funds.0, 1);
2414 assert_eq!(result, Err(TransactionError::InsufficientFundsForFee));
2415 }
2416 }
2417
2418 #[test]
2421 fn test_inspect_account_fee_payer() {
2422 let fee_payer_address = Pubkey::new_unique();
2423 let fee_payer_account = AccountSharedData::new_rent_epoch(
2424 123_000_000_000,
2425 0,
2426 &Pubkey::default(),
2427 RENT_EXEMPT_RENT_EPOCH,
2428 );
2429 let mock_bank = MockBankCallback::default();
2430 mock_bank
2431 .account_shared_data
2432 .write()
2433 .unwrap()
2434 .insert(fee_payer_address, fee_payer_account.clone());
2435 let mut account_loader = (&mock_bank).into();
2436
2437 let message = new_unchecked_sanitized_message(Message::new_with_blockhash(
2438 &[
2439 ComputeBudgetInstruction::set_compute_unit_limit(2000u32),
2440 ComputeBudgetInstruction::set_compute_unit_price(1_000_000_000),
2441 ],
2442 Some(&fee_payer_address),
2443 &Hash::new_unique(),
2444 ));
2445 TransactionBatchProcessor::<TestForkGraph>::validate_transaction_nonce_and_fee_payer(
2446 &mut account_loader,
2447 &message,
2448 CheckedTransactionDetails::new(
2449 None,
2450 Ok(SVMTransactionExecutionAndFeeBudgetLimits::with_fee(
2451 MockBankCallback::calculate_fee_details(&message, 5000, 0),
2452 )),
2453 ),
2454 &Hash::default(),
2455 &Rent::default(),
2456 &mut TransactionErrorMetrics::default(),
2457 )
2458 .unwrap();
2459
2460 let actual_inspected_accounts: Vec<_> = mock_bank
2462 .inspected_accounts
2463 .read()
2464 .unwrap()
2465 .iter()
2466 .map(|(k, v)| (*k, v.clone()))
2467 .collect();
2468 assert_eq!(
2469 actual_inspected_accounts.as_slice(),
2470 &[(fee_payer_address, vec![(Some(fee_payer_account), true)])],
2471 );
2472 }
2473}