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