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