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