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