1use {
2 crate::{
3 account_loader::{
4 AccountLoader, CheckedTransactionDetails, FeesOnlyTransaction, LoadedTransaction,
5 NoOpTransaction, TransactionCheckResult, TransactionLoadResult,
6 TransactionValidationResult, ValidatedTransactionDetails, load_transaction,
7 update_rent_exempt_status_for_account, validate_fee_payer,
8 },
9 account_overrides::AccountOverrides,
10 nonce_info::NonceInfo,
11 program_loader::{filter_executable_program_accounts, load_program_with_pubkey},
12 rollback_accounts::RollbackAccounts,
13 transaction_account_state_info::{
14 TransactionAccountStateInfo, get_uninitialized_accounts_size, verify_changes,
15 },
16 transaction_balances::{BalanceCollectionRoutines, BalanceCollector},
17 transaction_error_metrics::TransactionErrorMetrics,
18 transaction_execution_result::{
19 AccountsDeltas, ExecutedTransaction, TransactionExecutionDetails,
20 },
21 transaction_processing_result::{ProcessedTransaction, TransactionProcessingResult},
22 },
23 log::debug,
24 solana_account::{AccountSharedData, ReadableAccount, state_traits::StateMutWincode as _},
25 solana_clock::{Epoch, Slot},
26 solana_hash::Hash,
27 solana_instruction::TRANSACTION_LEVEL_STACK_HEIGHT,
28 solana_message::{
29 compiled_instruction::CompiledInstruction,
30 inner_instruction::{InnerInstruction, InnerInstructionsList},
31 },
32 solana_nonce::{
33 NONCED_TX_MARKER_IX_INDEX,
34 state::{DurableNonce, State as NonceState},
35 versions::Versions as NonceVersions,
36 },
37 solana_nonce_account::verify_nonce_account,
38 solana_program_runtime::{
39 execution_budget::{
40 SVMTransactionExecutionAndFeeBudgetLimits, SVMTransactionExecutionCost,
41 },
42 invoke_context::{EnvironmentConfig, InvokeContext},
43 loaded_programs::{
44 EpochBoundaryPreparation, ForkGraph, Percent, ProgramCache, ProgramCacheForTxBatch,
45 ProgramCacheMatchCriteria, ProgramRuntimeEnvironment, ProgramRuntimeEnvironments,
46 ProgramToLoad,
47 },
48 program_cache_entry::{ProgramCacheEntry, ProgramCacheEntryOwner},
49 program_metrics::ProgramStatistics,
50 solana_sbpf::{program::BuiltinProgram, vm::Config as VmConfig},
51 sysvar_cache::SysvarCache,
52 },
53 solana_pubkey::Pubkey,
54 solana_rent::Rent,
55 solana_svm_callback::{InvokeContextCallback, TransactionProcessingCallback},
56 solana_svm_feature_set::SVMFeatureSet,
57 solana_svm_log_collector::LogCollector,
58 solana_svm_measure::{measure::Measure, measure_us},
59 solana_svm_timings::{ExecuteTimingType, ExecuteTimings},
60 solana_svm_transaction::{svm_message::SVMMessage, svm_transaction::SVMTransaction},
61 solana_svm_type_overrides::sync::{Arc, RwLock, RwLockReadGuard},
62 solana_transaction_context::transaction::{ExecutionRecord, TransactionContext},
63 solana_transaction_error::{TransactionError, TransactionResult},
64 std::{
65 collections::HashSet,
66 fmt::{Debug, Formatter},
67 rc::Rc,
68 },
69};
70#[cfg(feature = "dev-context-only-utils")]
71use {
72 qualifier_attr::{field_qualifiers, qualifiers},
73 std::sync::Weak,
74};
75
76pub type TransactionLogMessages = Vec<String>;
78
79pub struct LoadAndExecuteSanitizedTransactionsOutput {
82 pub error_metrics: TransactionErrorMetrics,
84 pub execute_timings: ExecuteTimings,
86 pub processing_results: Vec<TransactionProcessingResult>,
90 pub balance_collector: Option<BalanceCollector>,
93}
94
95#[derive(Copy, Clone, Default)]
97pub struct ExecutionRecordingConfig {
98 pub enable_cpi_recording: bool,
99 pub enable_log_recording: bool,
100 pub enable_return_data_recording: bool,
101 pub enable_transaction_balance_recording: bool,
102}
103
104impl ExecutionRecordingConfig {
105 pub fn new_single_setting(option: bool) -> Self {
106 ExecutionRecordingConfig {
107 enable_return_data_recording: option,
108 enable_log_recording: option,
109 enable_cpi_recording: option,
110 enable_transaction_balance_recording: option,
111 }
112 }
113}
114
115#[derive(Default)]
117pub struct TransactionProcessingConfig<'a> {
118 pub account_overrides: Option<&'a AccountOverrides>,
121 pub check_program_deployment_slot: bool,
124 pub log_messages_bytes_limit: Option<usize>,
126 pub limit_to_load_programs: bool,
129 pub recording_config: ExecutionRecordingConfig,
131 pub drop_on_failure: bool,
134 pub all_or_nothing: bool,
143 pub strict_nonce_size_check: bool,
147 pub drop_noop_transactions: bool,
151}
152
153pub struct TransactionProcessingEnvironment {
155 pub blockhash: Hash,
157 pub blockhash_lamports_per_signature: u64,
164 pub alpenglow_migration_succeeded: bool,
166 pub epoch_total_stake: u64,
168 pub feature_set: SVMFeatureSet,
170 pub program_runtime_environments: ProgramRuntimeEnvironments,
172 pub rent: Rent,
174}
175
176#[cfg(feature = "dev-context-only-utils")]
177pub fn get_mock_transaction_processing_environment() -> TransactionProcessingEnvironment {
178 TransactionProcessingEnvironment {
179 blockhash: Hash::default(),
180 blockhash_lamports_per_signature: 0,
181 alpenglow_migration_succeeded: false,
182 epoch_total_stake: 0,
183 feature_set: SVMFeatureSet::default(),
184 program_runtime_environments: ProgramRuntimeEnvironments::mock(),
185 rent: Rent::default(),
186 }
187}
188
189#[cfg_attr(feature = "frozen-abi", derive(AbiExample))]
190#[cfg_attr(
191 feature = "dev-context-only-utils",
192 field_qualifiers(slot(pub), epoch(pub), sysvar_cache(pub))
193)]
194pub struct TransactionBatchProcessor<FG: ForkGraph> {
195 slot: Slot,
197
198 epoch: Epoch,
200
201 sysvar_cache: RwLock<SysvarCache>,
205
206 pub epoch_boundary_preparation: Arc<RwLock<EpochBoundaryPreparation>>,
208
209 pub global_program_cache: Arc<RwLock<ProgramCache<FG>>>,
211
212 pub program_runtime_environment: ProgramRuntimeEnvironment,
214
215 pub builtin_program_ids: RwLock<HashSet<Pubkey>>,
217
218 builtin_program_cache: RwLock<ProgramCacheForTxBatch>,
222
223 execution_cost: SVMTransactionExecutionCost,
224}
225
226impl<FG: ForkGraph> Debug for TransactionBatchProcessor<FG> {
227 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
228 f.debug_struct("TransactionBatchProcessor")
229 .field("slot", &self.slot)
230 .field("epoch", &self.epoch)
231 .field("sysvar_cache", &self.sysvar_cache)
232 .field("global_program_cache", &self.global_program_cache)
233 .finish()
234 }
235}
236
237impl<FG: ForkGraph> Default for TransactionBatchProcessor<FG> {
238 fn default() -> Self {
239 Self {
240 slot: Slot::default(),
241 epoch: Epoch::default(),
242 sysvar_cache: RwLock::<SysvarCache>::default(),
243 epoch_boundary_preparation: Arc::new(RwLock::new(EpochBoundaryPreparation::default())),
244 global_program_cache: Arc::new(RwLock::new(ProgramCache::new(Slot::default()))),
245 program_runtime_environment: ProgramRuntimeEnvironment::from(
246 BuiltinProgram::new_loader(VmConfig::default()),
247 ),
248 builtin_program_ids: RwLock::new(HashSet::new()),
249 builtin_program_cache: RwLock::new(ProgramCacheForTxBatch::new(Slot::default())),
250 execution_cost: SVMTransactionExecutionCost::default(),
251 }
252 }
253}
254
255impl<FG: ForkGraph> TransactionBatchProcessor<FG> {
256 pub fn new_uninitialized(slot: Slot, epoch: Epoch) -> Self {
266 let epoch_boundary_preparation =
267 Arc::new(RwLock::new(EpochBoundaryPreparation::new(epoch)));
268 Self {
269 slot,
270 epoch,
271 epoch_boundary_preparation,
272 global_program_cache: Arc::new(RwLock::new(ProgramCache::new(slot))),
273 builtin_program_cache: RwLock::new(ProgramCacheForTxBatch::new(slot)),
274 ..Self::default()
275 }
276 }
277
278 #[cfg(feature = "dev-context-only-utils")]
287 pub fn new(
288 slot: Slot,
289 epoch: Epoch,
290 fork_graph: Weak<RwLock<FG>>,
291 program_runtime_environment: Option<ProgramRuntimeEnvironment>,
292 ) -> Self {
293 let mut processor = Self::new_uninitialized(slot, epoch);
294 processor
295 .global_program_cache
296 .write()
297 .unwrap()
298 .set_fork_graph(fork_graph);
299 let empty_loader = || ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
300 processor
301 .global_program_cache
302 .write()
303 .unwrap()
304 .latest_root_slot = processor.slot;
305 processor
306 .epoch_boundary_preparation
307 .write()
308 .unwrap()
309 .upcoming_epoch = processor.epoch;
310 processor.program_runtime_environment =
311 program_runtime_environment.unwrap_or(empty_loader());
312 processor
313 }
314
315 pub fn new_from(&self, slot: Slot, epoch: Epoch) -> Self {
322 let builtin_program_ids = self.builtin_program_ids.read().unwrap().clone();
323 let environments = self.program_runtime_environment.clone();
324
325 let mut builtin_program_cache = ProgramCacheForTxBatch::new(slot);
328 let mut search_for: Vec<ProgramToLoad> = builtin_program_ids
329 .iter()
330 .map(|program_id| ProgramToLoad {
331 program_id,
332 loader: ProgramCacheEntryOwner::NativeLoader,
333 match_criteria: ProgramCacheMatchCriteria::NoCriteria,
334 last_modification_slot: 0,
335 })
336 .collect();
337 self.global_program_cache.read().unwrap().extract(
338 &mut search_for,
339 &mut builtin_program_cache,
340 &environments,
341 false,
342 false,
343 );
344
345 Self {
346 slot,
347 epoch,
348 sysvar_cache: RwLock::<SysvarCache>::default(),
349 epoch_boundary_preparation: self.epoch_boundary_preparation.clone(),
350 global_program_cache: self.global_program_cache.clone(),
351 program_runtime_environment: environments,
352 builtin_program_ids: RwLock::new(builtin_program_ids),
353 builtin_program_cache: RwLock::new(builtin_program_cache),
354 execution_cost: self.execution_cost,
355 }
356 }
357
358 pub fn set_execution_cost(&mut self, cost: SVMTransactionExecutionCost) {
361 self.execution_cost = cost;
362 }
363
364 pub fn set_program_runtime_environment(&mut self, new_environment: ProgramRuntimeEnvironment) {
366 if *self.program_runtime_environment != *new_environment {
368 self.program_runtime_environment = new_environment;
369 }
370 if let Some(upcoming_environment) = &self
372 .epoch_boundary_preparation
373 .read()
374 .unwrap()
375 .upcoming_environment
376 {
377 let upcoming_environment = ProgramRuntimeEnvironment::clone(upcoming_environment);
378 if self.program_runtime_environment != upcoming_environment
379 && *self.program_runtime_environment == *upcoming_environment
380 {
381 self.program_runtime_environment = upcoming_environment;
383 }
384 }
385 }
386
387 pub fn program_runtime_environment_for_epoch(&self, epoch: Epoch) -> ProgramRuntimeEnvironment {
390 self.epoch_boundary_preparation
391 .read()
392 .unwrap()
393 .get_upcoming_environment_for_epoch(epoch)
394 .unwrap_or_else(|| ProgramRuntimeEnvironment::clone(&self.program_runtime_environment))
395 }
396
397 pub fn sysvar_cache(&self) -> RwLockReadGuard<'_, SysvarCache> {
398 self.sysvar_cache.read().unwrap()
399 }
400
401 pub fn load_and_execute_sanitized_transactions<
403 CB: TransactionProcessingCallback + InvokeContextCallback,
404 >(
405 &self,
406 callbacks: &CB,
407 sanitized_txs: &[impl SVMTransaction],
408 check_results: Vec<TransactionCheckResult>,
409 environment: &TransactionProcessingEnvironment,
410 config: &TransactionProcessingConfig,
411 ) -> LoadAndExecuteSanitizedTransactionsOutput {
412 debug_assert_eq!(
417 sanitized_txs.len(),
418 check_results.len(),
419 "Length of check_results does not match length of sanitized_txs"
420 );
421
422 let mut error_metrics = TransactionErrorMetrics::default();
424 let mut execute_timings = ExecuteTimings::default();
425 let mut processing_results = Vec::with_capacity(sanitized_txs.len());
426
427 let account_keys_in_batch = sanitized_txs.iter().map(|tx| tx.account_keys().len()).sum();
431
432 let mut account_loader = AccountLoader::new_with_loaded_accounts_capacity(
434 config.account_overrides,
435 callbacks,
436 &environment.feature_set,
437 account_keys_in_batch,
438 );
439
440 let mut balance_collector = config
442 .recording_config
443 .enable_transaction_balance_recording
444 .then(|| BalanceCollector::new_with_transaction_count(sanitized_txs.len()));
445
446 let mut program_cache_for_tx_batch = self.builtin_program_cache.read().unwrap().clone();
450
451 if program_cache_for_tx_batch.hit_max_limit {
452 return LoadAndExecuteSanitizedTransactionsOutput {
453 error_metrics,
454 execute_timings,
455 processing_results: (0..sanitized_txs.len())
456 .map(|_| Err(TransactionError::ProgramCacheHitMaxLimit))
457 .collect(),
458 balance_collector: None,
461 };
462 }
463
464 let (mut load_us, mut execution_us): (u64, u64) = (0, 0);
465 let sysvar_cache = self.sysvar_cache();
466
467 for (tx, check_result) in sanitized_txs.iter().zip(check_results) {
472 let (validate_result, validate_fees_us) = measure_us!(match check_result {
473 Ok(tx_details) => Self::validate_transaction_nonce_and_fee_payer(
474 &mut account_loader,
475 tx,
476 tx_details,
477 &environment.blockhash,
478 environment.blockhash_lamports_per_signature,
479 &environment.rent,
480 environment.feature_set.relax_post_exec_min_balance_check,
481 config.strict_nonce_size_check,
482 &mut error_metrics,
483 ),
484 Err(e) => TransactionValidationResult::Unprocessable(e),
485 });
486 execute_timings
487 .saturating_add_in_place(ExecuteTimingType::ValidateFeesUs, validate_fees_us);
488
489 let (load_result, single_load_us) = measure_us!(load_transaction(
490 &mut account_loader,
491 tx,
492 validate_result,
493 &mut error_metrics,
494 &environment.rent,
495 ));
496 load_us = load_us.saturating_add(single_load_us);
497
498 let ((), collect_balances_us) =
499 measure_us!(balance_collector.collect_pre_balances(&mut account_loader, tx));
500 execute_timings
501 .saturating_add_in_place(ExecuteTimingType::CollectBalancesUs, collect_balances_us);
502
503 let (processing_result, single_execution_us) = measure_us!(match load_result {
504 TransactionLoadResult::Unprocessable(e) => Err(e),
506
507 TransactionLoadResult::NoOp(NoOpTransaction {
511 validation_error: e,
512 ..
513 }) if config.drop_on_failure || config.drop_noop_transactions => Err(e),
514
515 TransactionLoadResult::NoOp(no_op_tx) =>
517 Ok(ProcessedTransaction::NoOp(Box::new(no_op_tx))),
518
519 TransactionLoadResult::FeesOnly(FeesOnlyTransaction { load_error: e, .. })
521 if config.drop_on_failure =>
522 Err(e),
523
524 TransactionLoadResult::FeesOnly(fees_only_tx) => {
526 account_loader
527 .update_accounts_for_failed_tx(&fees_only_tx.rollback_accounts, self.slot);
528 Ok(ProcessedTransaction::FeesOnly(Box::new(fees_only_tx)))
529 }
530
531 TransactionLoadResult::Loaded(loaded_transaction) => {
533 let (missing_programs, filter_executable_us) =
534 measure_us!(filter_executable_program_accounts(
535 &account_loader,
536 &program_cache_for_tx_batch,
537 tx.account_keys().iter(),
538 config.check_program_deployment_slot,
539 ));
540 execute_timings.saturating_add_in_place(
541 ExecuteTimingType::FilterExecutableUs,
542 filter_executable_us,
543 );
544
545 let ((), program_cache_us) = measure_us!({
546 self.replenish_program_cache(
547 &account_loader,
548 missing_programs,
549 environment
550 .program_runtime_environments
551 .get_env_for_execution(),
552 &mut program_cache_for_tx_batch,
553 &mut execute_timings,
554 config.limit_to_load_programs,
555 true, );
557 });
558 execute_timings.saturating_add_in_place(
559 ExecuteTimingType::ProgramCacheUs,
560 program_cache_us,
561 );
562
563 if program_cache_for_tx_batch.hit_max_limit {
564 return LoadAndExecuteSanitizedTransactionsOutput {
565 error_metrics,
566 execute_timings,
567 processing_results: (0..sanitized_txs.len())
568 .map(|_| Err(TransactionError::ProgramCacheHitMaxLimit))
569 .collect(),
570 balance_collector: None,
573 };
574 }
575
576 let executed_tx = self.execute_loaded_transaction(
577 callbacks,
578 tx,
579 &sysvar_cache,
580 loaded_transaction,
581 &mut execute_timings,
582 &mut error_metrics,
583 &mut program_cache_for_tx_batch,
584 environment,
585 config,
586 );
587
588 match (
589 &executed_tx.execution_details.status,
590 config.drop_on_failure,
591 ) {
592 (Ok(_), _) => {
595 account_loader.update_accounts_for_successful_tx(
596 tx,
597 &executed_tx.loaded_transaction.accounts,
598 &executed_tx.loaded_transaction.touched_flags,
599 self.slot,
600 );
601 program_cache_for_tx_batch.merge(&executed_tx.programs_modified_by_tx);
604
605 Ok(ProcessedTransaction::Executed(Box::new(executed_tx)))
606 }
607 (Err(err), true) => Err(err.clone()),
610 (Err(_), false) => {
613 account_loader.update_accounts_for_failed_tx(
614 &executed_tx.loaded_transaction.rollback_accounts,
615 self.slot,
616 );
617
618 Ok(ProcessedTransaction::Executed(Box::new(executed_tx)))
619 }
620 }
621 }
622 });
623 execution_us = execution_us.saturating_add(single_execution_us);
624
625 let ((), collect_balances_us) =
626 measure_us!(balance_collector.collect_post_balances(&mut account_loader, tx));
627 execute_timings
628 .saturating_add_in_place(ExecuteTimingType::CollectBalancesUs, collect_balances_us);
629
630 if config.all_or_nothing && processing_result.is_err() {
633 for res in processing_results.iter_mut() {
635 *res = Err(TransactionError::CommitCancelled);
636 }
637
638 processing_results.push(processing_result);
640
641 processing_results.extend(
643 (0..sanitized_txs.len() - processing_results.len())
644 .map(|_| Err(TransactionError::CommitCancelled)),
645 );
646
647 return LoadAndExecuteSanitizedTransactionsOutput {
648 error_metrics,
649 execute_timings,
650 processing_results,
651 balance_collector: None,
654 };
655 }
656
657 processing_results.push(processing_result);
658 }
659
660 if program_cache_for_tx_batch.loaded_missing || program_cache_for_tx_batch.merged_modified {
665 const SHRINK_LOADED_PROGRAMS_TO_PERCENTAGE: Percent = 90;
667 self.global_program_cache
668 .write()
669 .unwrap()
670 .evict_using_random_selection(SHRINK_LOADED_PROGRAMS_TO_PERCENTAGE, self.slot);
671 }
672
673 debug!(
674 "load: {}us execute: {}us txs_len={}",
675 load_us,
676 execution_us,
677 sanitized_txs.len(),
678 );
679 execute_timings.saturating_add_in_place(ExecuteTimingType::LoadUs, load_us);
680 execute_timings.saturating_add_in_place(ExecuteTimingType::ExecuteUs, execution_us);
681
682 if let Some(ref balance_collector) = balance_collector {
683 debug_assert!(balance_collector.lengths_match_expected(sanitized_txs.len()));
684 }
685
686 LoadAndExecuteSanitizedTransactionsOutput {
687 error_metrics,
688 execute_timings,
689 processing_results,
690 balance_collector,
691 }
692 }
693
694 fn validate_transaction_nonce_and_fee_payer<CB: TransactionProcessingCallback>(
695 account_loader: &mut AccountLoader<CB>,
696 message: &impl SVMMessage,
697 checked_details: CheckedTransactionDetails,
698 environment_blockhash: &Hash,
699 next_lamports_per_signature: u64,
700 rent: &Rent,
701 relax_post_exec_min_balance_check: bool,
702 strict_nonce_size_check: bool,
703 error_counters: &mut TransactionErrorMetrics,
704 ) -> TransactionValidationResult {
705 let CheckedTransactionDetails {
706 nonce_address,
707 compute_budget_and_limits,
708 } = checked_details;
709
710 let nonce_info = if let Some(ref nonce_address) = nonce_address {
714 let next_durable_nonce = DurableNonce::from_blockhash(environment_blockhash);
715 let nonce_result = Self::validate_transaction_nonce(
716 account_loader,
717 message,
718 nonce_address,
719 &next_durable_nonce,
720 next_lamports_per_signature,
721 strict_nonce_size_check,
722 error_counters,
723 );
724
725 match nonce_result {
726 Ok(nonce_info) => Some(nonce_info),
727 Err(e) => return TransactionValidationResult::Unprocessable(e),
728 }
729 } else {
730 None
731 };
732
733 let is_blockhash_transaction = nonce_info.is_none();
734
735 let fee_payer_result = Self::validate_transaction_fee_payer(
737 account_loader,
738 message,
739 nonce_info,
740 compute_budget_and_limits,
741 rent,
742 relax_post_exec_min_balance_check,
743 error_counters,
744 );
745
746 let allow_noop =
752 account_loader.feature_set.relax_fee_payer_constraint && is_blockhash_transaction;
753
754 match fee_payer_result {
755 Ok(details) => TransactionValidationResult::Loadable(details),
756 Err(e) if allow_noop => {
757 let fee_payer_balance = account_loader
759 .load_account(message.fee_payer())
760 .map(|account| account.lamports());
761
762 TransactionValidationResult::NoOp(NoOpTransaction {
767 validation_error: e,
768 fee_payer_balance,
769 compute_unit_limit: compute_budget_and_limits.budget.compute_unit_limit,
770 loaded_accounts_bytes_limit: compute_budget_and_limits
771 .loaded_accounts_data_size_limit,
772 })
773 }
774 Err(e) => TransactionValidationResult::Unprocessable(e),
775 }
776 }
777
778 fn validate_transaction_fee_payer<CB: TransactionProcessingCallback>(
782 account_loader: &mut AccountLoader<CB>,
783 message: &impl SVMMessage,
784 nonce_info: Option<NonceInfo>,
785 compute_budget_and_limits: SVMTransactionExecutionAndFeeBudgetLimits,
786 rent: &Rent,
787 relax_post_exec_min_balance_check: bool,
788 error_counters: &mut TransactionErrorMetrics,
789 ) -> TransactionResult<ValidatedTransactionDetails> {
790 let fee_payer_address = message.fee_payer();
791
792 let Some(mut loaded_fee_payer) =
796 account_loader.load_transaction_account(fee_payer_address, true)
797 else {
798 error_counters.account_not_found += 1;
799 return Err(TransactionError::AccountNotFound);
800 };
801
802 let fee_payer_loaded_rent_epoch = loaded_fee_payer.account.rent_epoch();
803 update_rent_exempt_status_for_account(rent, &mut loaded_fee_payer.account);
804
805 let fee_payer_index = 0;
806 validate_fee_payer(
807 &mut loaded_fee_payer.account,
808 fee_payer_index,
809 error_counters,
810 rent,
811 compute_budget_and_limits.fee_details.total_fee(),
812 relax_post_exec_min_balance_check,
813 )?;
814
815 let rollback_accounts = RollbackAccounts::new(
818 nonce_info,
819 *fee_payer_address,
820 loaded_fee_payer.account.clone(),
821 fee_payer_loaded_rent_epoch,
822 );
823
824 Ok(ValidatedTransactionDetails {
825 fee_details: compute_budget_and_limits.fee_details,
826 rollback_accounts,
827 loaded_accounts_bytes_limit: compute_budget_and_limits.loaded_accounts_data_size_limit,
828 compute_budget: compute_budget_and_limits.budget,
829 loaded_fee_payer_account: loaded_fee_payer,
830 })
831 }
832
833 fn validate_transaction_nonce<CB: TransactionProcessingCallback>(
834 account_loader: &mut AccountLoader<CB>,
835 message: &impl SVMMessage,
836 nonce_address: &Pubkey,
837 next_durable_nonce: &DurableNonce,
838 next_lamports_per_signature: u64,
839 strict_nonce_size_check: bool,
840 error_counters: &mut TransactionErrorMetrics,
841 ) -> TransactionResult<NonceInfo> {
842 let Some(mut nonce_account) = account_loader
849 .load_transaction_account(nonce_address, true)
850 .map(|loaded| loaded.account)
851 else {
852 error_counters.account_not_found += 1;
853 return Err(TransactionError::AccountNotFound);
854 };
855
856 if strict_nonce_size_check && nonce_account.data().len() != NonceState::size() {
857 error_counters.blockhash_not_found += 1;
858 return Err(TransactionError::BlockhashNotFound);
859 }
860
861 let Some(nonce_data) = verify_nonce_account(&nonce_account, message.recent_blockhash())
866 else {
867 error_counters.blockhash_not_found += 1;
868 return Err(TransactionError::BlockhashNotFound);
869 };
870
871 let nonce_can_be_advanced = &nonce_data.durable_nonce != next_durable_nonce;
873 let nonce_authority_is_valid = message
874 .get_ix_signers(NONCED_TX_MARKER_IX_INDEX as usize)
875 .any(|signer| signer == &nonce_data.authority);
876
877 if nonce_can_be_advanced && nonce_authority_is_valid {
878 let next_nonce_state = NonceState::new_initialized(
879 &nonce_data.authority,
880 *next_durable_nonce,
881 next_lamports_per_signature,
882 );
883 nonce_account
884 .set_state(&NonceVersions::new(next_nonce_state))
885 .expect("Serializing into a validated nonce account cannot fail");
886
887 Ok(NonceInfo::new(*nonce_address, nonce_account))
888 } else {
889 error_counters.blockhash_not_found += 1;
890 Err(TransactionError::BlockhashNotFound)
891 }
892 }
893
894 #[cfg_attr(feature = "dev-context-only-utils", qualifiers(pub))]
895 fn replenish_program_cache<CB: TransactionProcessingCallback>(
896 &self,
897 account_loader: &AccountLoader<CB>,
898 mut missing_programs: Vec<ProgramToLoad>,
899 program_runtime_environment_for_execution: &ProgramRuntimeEnvironment,
900 program_cache_for_tx_batch: &mut ProgramCacheForTxBatch,
901 execute_timings: &mut ExecuteTimings,
902 limit_to_load_programs: bool,
903 increment_usage_counter: bool,
904 ) {
905 if missing_programs.is_empty() {
906 return;
909 }
910 let mut count_hits_and_misses = true;
911 loop {
912 let global_program_cache = self.global_program_cache.read().unwrap();
914 let program_to_load = global_program_cache.extract(
916 &mut missing_programs,
917 program_cache_for_tx_batch,
918 program_runtime_environment_for_execution,
919 increment_usage_counter,
920 count_hits_and_misses,
921 );
922 count_hits_and_misses = false;
923 let task_waiter = Arc::clone(&global_program_cache.loading_task_waiter);
924 let task_cookie = task_waiter.cookie();
925 drop(global_program_cache);
927
928 let program_to_store = program_to_load.map(|key| {
929 let (program, last_modification_slot) = load_program_with_pubkey(
931 account_loader,
932 program_runtime_environment_for_execution,
933 &key,
934 self.slot,
935 execute_timings,
936 )
937 .expect("called load_program_with_pubkey() with nonexistent account");
938 (key, program, last_modification_slot)
939 });
940
941 if let Some((key, program, last_modification_slot)) = program_to_store {
942 program_cache_for_tx_batch.loaded_missing = true;
943 let mut global_program_cache = self.global_program_cache.write().unwrap();
944 if global_program_cache.finish_cooperative_loading_task(
946 program_runtime_environment_for_execution,
947 self.slot,
948 key,
949 last_modification_slot,
950 program,
951 ) && limit_to_load_programs
952 {
953 *program_cache_for_tx_batch = ProgramCacheForTxBatch::new(self.slot);
957 program_cache_for_tx_batch.hit_max_limit = true;
958 return;
959 }
960 } else if missing_programs.is_empty() {
961 break;
962 } else {
963 let _new_cookie = task_waiter.wait(task_cookie);
970 }
971 }
972 }
973
974 pub fn prepare_one_program_for_upcoming_feature_set<CB: TransactionProcessingCallback>(
976 &self,
977 account_loader: &CB,
978 check_program_deployment_slot: bool,
979 upcoming_environment: &ProgramRuntimeEnvironment,
980 key: &Pubkey,
981 stats_of_enqueued_program: &ProgramStatistics,
982 ) {
983 let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::new(self.slot);
984 let mut missing_programs = filter_executable_program_accounts(
985 account_loader,
986 &program_cache_for_tx_batch,
987 std::iter::once(key),
988 check_program_deployment_slot,
989 );
990 if missing_programs.is_empty() {
991 return;
993 }
994 let program_to_load = {
995 let program_cache_guard = self.global_program_cache.read().unwrap();
996 program_cache_guard.extract(
997 &mut missing_programs,
998 &mut program_cache_for_tx_batch,
999 upcoming_environment,
1000 false, false, )
1003 };
1005 if let Some(key) = program_to_load {
1007 let (recompiled, last_modification_slot) = load_program_with_pubkey(
1009 account_loader,
1010 upcoming_environment,
1011 &key,
1012 self.slot,
1013 &mut ExecuteTimings::default(),
1014 )
1015 .expect("called load_program_with_pubkey() with nonexistent account");
1016 recompiled.stats.merge_from(stats_of_enqueued_program);
1017 let mut program_cache_guard = self.global_program_cache.write().unwrap();
1019 program_cache_guard.finish_cooperative_loading_task(
1021 upcoming_environment,
1022 self.slot,
1023 key,
1024 last_modification_slot,
1025 recompiled,
1026 );
1027 }
1028 }
1029
1030 #[allow(clippy::too_many_arguments)]
1033 fn execute_loaded_transaction<CB: InvokeContextCallback>(
1034 &self,
1035 callback: &CB,
1036 tx: &impl SVMTransaction,
1037 sysvar_cache: &SysvarCache,
1038 mut loaded_transaction: LoadedTransaction,
1039 execute_timings: &mut ExecuteTimings,
1040 error_metrics: &mut TransactionErrorMetrics,
1041 program_cache_for_tx_batch: &mut ProgramCacheForTxBatch,
1042 environment: &TransactionProcessingEnvironment,
1043 config: &TransactionProcessingConfig,
1044 ) -> ExecutedTransaction {
1045 let transaction_accounts = std::mem::take(&mut loaded_transaction.accounts);
1046
1047 debug_assert!(transaction_accounts.len() == tx.account_keys().len());
1051
1052 fn transaction_accounts_lamports_sum(
1053 accounts: &[(Pubkey, AccountSharedData)],
1054 ) -> Option<u128> {
1055 accounts.iter().try_fold(0u128, |sum, (_, account)| {
1056 sum.checked_add(u128::from(account.lamports()))
1057 })
1058 }
1059
1060 let lamports_before_tx =
1061 transaction_accounts_lamports_sum(&transaction_accounts).unwrap_or(0);
1062
1063 let compute_budget = loaded_transaction.compute_budget;
1064
1065 let mut transaction_context = TransactionContext::new(
1066 transaction_accounts,
1067 environment.rent.clone(),
1068 compute_budget.max_instruction_stack_depth,
1069 compute_budget.max_instruction_trace_length,
1070 tx.num_instructions(),
1071 );
1072
1073 let relax_post_exec_min_balance_check =
1074 environment.feature_set.relax_post_exec_min_balance_check;
1075 let pre_account_state_info = TransactionAccountStateInfo::new_pre_exec(
1076 &transaction_context,
1077 tx,
1078 &environment.rent,
1079 relax_post_exec_min_balance_check,
1080 );
1081
1082 let log_collector = if config.recording_config.enable_log_recording {
1083 match config.log_messages_bytes_limit {
1084 None => Some(LogCollector::new_ref()),
1085 Some(log_messages_bytes_limit) => Some(LogCollector::new_ref_with_limit(Some(
1086 log_messages_bytes_limit,
1087 ))),
1088 }
1089 } else {
1090 None
1091 };
1092
1093 let mut executed_units = 0u64;
1094
1095 let mut invoke_context = InvokeContext::new(
1096 &mut transaction_context,
1097 program_cache_for_tx_batch,
1098 EnvironmentConfig::new(
1099 environment.blockhash,
1100 environment.blockhash_lamports_per_signature,
1101 environment.alpenglow_migration_succeeded,
1102 callback,
1103 &environment.feature_set,
1104 &environment.program_runtime_environments,
1105 sysvar_cache,
1106 ),
1107 log_collector.clone(),
1108 compute_budget,
1109 self.execution_cost,
1110 );
1111
1112 let mut process_message_time = Measure::start("process_message_time");
1113 let process_result = invoke_context
1114 .process_message(tx, execute_timings, &mut executed_units)
1115 .map_err(|(index, err)| TransactionError::InstructionError(index, err));
1116 process_message_time.stop();
1117
1118 drop(invoke_context);
1119
1120 execute_timings.execute_accessories.process_message_us += process_message_time.as_us();
1121
1122 let mut post_account_state_info_result = process_result
1123 .and_then(|_info| {
1124 let post_account_state_info = TransactionAccountStateInfo::new_post_exec(
1125 &transaction_context,
1126 tx,
1127 &pre_account_state_info,
1128 &environment.rent,
1129 relax_post_exec_min_balance_check,
1130 );
1131 verify_changes(
1132 &pre_account_state_info,
1133 &post_account_state_info,
1134 &transaction_context,
1135 )
1136 .map(|_| post_account_state_info)
1137 })
1138 .map_err(|err| {
1139 match err {
1140 TransactionError::InvalidRentPayingAccount
1141 | TransactionError::InsufficientFundsForRent { .. } => {
1142 error_metrics.invalid_rent_paying_account += 1;
1143 }
1144 TransactionError::InvalidAccountIndex => {
1145 error_metrics.invalid_account_index += 1;
1146 }
1147 _ => {
1148 error_metrics.instruction_error += 1;
1149 }
1150 }
1151 err
1152 });
1153
1154 let log_messages: Option<TransactionLogMessages> =
1155 log_collector.and_then(|log_collector| {
1156 Rc::try_unwrap(log_collector)
1157 .map(|log_collector| log_collector.into_inner().into_messages())
1158 .ok()
1159 });
1160
1161 let (execution_record, inner_instructions) = Self::deconstruct_transaction(
1162 transaction_context,
1163 config.recording_config.enable_cpi_recording,
1164 );
1165
1166 let ExecutionRecord {
1167 accounts,
1168 return_data,
1169 mut touched_flags,
1170 accounts_resize_delta,
1171 } = execution_record;
1172
1173 if let Some(fee_payer_touched) = touched_flags.first_mut() {
1176 *fee_payer_touched = true;
1177 }
1178
1179 let touched_account_count = touched_flags.iter().filter(|touched| **touched).count();
1182
1183 if post_account_state_info_result.is_ok()
1184 && transaction_accounts_lamports_sum(&accounts)
1185 .filter(|lamports_after_tx| lamports_before_tx == *lamports_after_tx)
1186 .is_none()
1187 {
1188 post_account_state_info_result = Err(TransactionError::UnbalancedTransaction);
1189 }
1190
1191 let (status, accounts_deltas) = post_account_state_info_result
1194 .map(|post_state_info| {
1195 (
1196 Ok(()),
1197 Some(AccountsDeltas {
1198 accounts_resize_delta,
1199 accounts_uninitialized_size: get_uninitialized_accounts_size(
1200 &post_state_info,
1201 ),
1202 }),
1203 )
1204 })
1205 .unwrap_or_else(|err| (Err(err), None));
1206
1207 loaded_transaction.accounts = accounts;
1208 loaded_transaction.touched_flags = touched_flags;
1209 execute_timings.details.total_account_count += loaded_transaction.accounts.len() as u64;
1210 execute_timings.details.changed_account_count += touched_account_count as u64;
1211
1212 let return_data = if config.recording_config.enable_return_data_recording
1213 && !return_data.data.is_empty()
1214 {
1215 Some(return_data)
1216 } else {
1217 None
1218 };
1219
1220 ExecutedTransaction {
1221 execution_details: TransactionExecutionDetails {
1222 status,
1223 log_messages,
1224 inner_instructions,
1225 return_data,
1226 executed_units,
1227 accounts_deltas,
1228 },
1229 loaded_transaction,
1230 programs_modified_by_tx: program_cache_for_tx_batch.drain_modified_entries(),
1231 }
1232 }
1233
1234 fn deconstruct_transaction(
1236 mut transaction_context: TransactionContext,
1237 record_inner_instructions: bool,
1238 ) -> (ExecutionRecord, Option<InnerInstructionsList>) {
1239 let inner_ix = if record_inner_instructions {
1240 debug_assert!(
1241 transaction_context
1242 .get_instruction_context_at_index_in_trace(0)
1243 .map(|instruction_context| instruction_context.get_stack_height()
1244 == TRANSACTION_LEVEL_STACK_HEIGHT)
1245 .unwrap_or(true)
1246 );
1247
1248 let top_level_ixs_num = transaction_context
1249 .get_instruction_trace_length()
1250 .saturating_sub(transaction_context.number_of_cpis_in_trace());
1251 let mut parent_positions: Vec<usize> =
1257 vec![usize::MAX; transaction_context.number_of_cpis_in_trace()];
1258 let (ix_trace, accounts, ix_data_trace) = transaction_context.take_instruction_trace();
1259 let mut outer_instructions: Vec<Vec<InnerInstruction>> =
1260 vec![Vec::new(); top_level_ixs_num];
1261 for (cpi_num, ((ix_in_trace, ix_data), ix_accounts)) in ix_trace
1262 .into_iter()
1263 .zip(ix_data_trace)
1264 .zip(accounts)
1265 .skip(top_level_ixs_num)
1266 .enumerate()
1267 {
1268 let caller_ix = ix_in_trace.index_of_caller_instruction;
1269 debug_assert_ne!(caller_ix, u16::MAX, "Instruction is not a CPI");
1270
1271 let outer_index = if (caller_ix as usize) < top_level_ixs_num {
1275 *parent_positions.get_mut(cpi_num).unwrap() = caller_ix as usize;
1276 caller_ix as usize
1277 } else if let Some(caller_index) = parent_positions
1282 .get((caller_ix as usize).saturating_sub(top_level_ixs_num))
1283 .copied()
1284 && caller_index != usize::MAX
1285 {
1286 *parent_positions.get_mut(cpi_num).unwrap() = caller_index;
1287 caller_index
1288 } else {
1289 debug_assert!(false);
1294 usize::MAX
1295 };
1296
1297 if let Some(inner_instructions) = outer_instructions.get_mut(outer_index) {
1298 let stack_height = ix_in_trace.nesting_level.saturating_add(1);
1299 let stack_height = u8::try_from(stack_height).unwrap_or(u8::MAX);
1300 inner_instructions.push(InnerInstruction {
1301 instruction: CompiledInstruction::new_from_raw_parts(
1302 ix_in_trace.program_account_index_in_tx as u8,
1303 ix_data.into_owned(),
1304 ix_accounts
1305 .iter()
1306 .map(|acc| acc.index_in_transaction as u8)
1307 .collect(),
1308 ),
1309 stack_height,
1310 });
1311 } else {
1312 debug_assert!(false);
1313 }
1314 }
1315
1316 Some(outer_instructions)
1317 } else {
1318 None
1319 };
1320
1321 let record: ExecutionRecord = transaction_context.into();
1322
1323 (record, inner_ix)
1324 }
1325
1326 pub fn fill_missing_sysvar_cache_entries<CB: TransactionProcessingCallback>(
1327 &self,
1328 callbacks: &CB,
1329 ) {
1330 let mut sysvar_cache = self.sysvar_cache.write().unwrap();
1331 Self::fill_missing_sysvar_cache_entries_from_accounts(&mut sysvar_cache, callbacks);
1332 }
1333
1334 pub fn reset_and_fill_sysvar_cache_entries<CB: TransactionProcessingCallback>(
1335 &self,
1336 callbacks: &CB,
1337 ) {
1338 let mut sysvar_cache = self.sysvar_cache.write().unwrap();
1339 sysvar_cache.reset();
1340 Self::fill_missing_sysvar_cache_entries_from_accounts(&mut sysvar_cache, callbacks);
1341 }
1342
1343 fn fill_missing_sysvar_cache_entries_from_accounts<CB: TransactionProcessingCallback>(
1344 sysvar_cache: &mut SysvarCache,
1345 callbacks: &CB,
1346 ) {
1347 sysvar_cache.fill_missing_entries(|pubkey, set_sysvar| {
1348 if let Some((account, _slot)) = callbacks.get_account_shared_data(pubkey) {
1349 set_sysvar(account.data());
1350 }
1351 });
1352 }
1353
1354 pub fn reset_sysvar_cache(&self) {
1355 let mut sysvar_cache = self.sysvar_cache.write().unwrap();
1356 sysvar_cache.reset();
1357 }
1358
1359 pub fn get_sysvar_cache_for_tests(&self) -> SysvarCache {
1360 self.sysvar_cache.read().unwrap().clone()
1361 }
1362
1363 pub fn add_builtin(&self, program_id: Pubkey, builtin: ProgramCacheEntry) {
1365 self.builtin_program_ids.write().unwrap().insert(program_id);
1366 let entry = Arc::new(builtin);
1367 self.global_program_cache.write().unwrap().assign_program(
1368 &self.program_runtime_environment,
1369 program_id,
1370 0,
1371 Arc::clone(&entry),
1372 );
1373 self.builtin_program_cache
1374 .write()
1375 .unwrap()
1376 .replenish(program_id, entry);
1377 }
1378
1379 #[cfg(feature = "dev-context-only-utils")]
1380 #[cfg_attr(feature = "dev-context-only-utils", qualifiers(pub))]
1381 fn writable_sysvar_cache(&self) -> &RwLock<SysvarCache> {
1382 &self.sysvar_cache
1383 }
1384}
1385
1386#[cfg(test)]
1387mod tests {
1388 #[allow(deprecated)]
1389 use solana_sysvar::fees::Fees;
1390 use {
1391 super::*,
1392 crate::{
1393 account_loader::{
1394 LoadedTransactionAccount, TRANSACTION_ACCOUNT_BASE_SIZE,
1395 ValidatedTransactionDetails,
1396 },
1397 nonce_info::NonceInfo,
1398 rent_calculator::RENT_EXEMPT_RENT_EPOCH,
1399 rollback_accounts::RollbackAccounts,
1400 },
1401 solana_account::WritableAccount,
1402 solana_clock::Clock,
1403 solana_compute_budget_interface::ComputeBudgetInstruction,
1404 solana_epoch_schedule::EpochSchedule,
1405 solana_fee_calculator::FeeCalculator,
1406 solana_fee_structure::FeeDetails,
1407 solana_hash::Hash,
1408 solana_message::{LegacyMessage, Message, MessageHeader, SanitizedMessage},
1409 solana_nonce as nonce,
1410 solana_program_runtime::{
1411 execution_budget::{
1412 SVMTransactionExecutionAndFeeBudgetLimits, SVMTransactionExecutionBudget,
1413 },
1414 invoke_context::BuiltinFunctionRegisterer,
1415 loaded_programs::BlockRelation,
1416 program_cache_entry::ProgramCacheEntryType,
1417 },
1418 solana_rent::Rent,
1419 solana_sbpf::vm,
1420 solana_sdk_ids::{bpf_loader, system_program, sysvar},
1421 solana_signature::Signature,
1422 solana_svm_callback::{AccountState, InvokeContextCallback},
1423 solana_system_interface::instruction as system_instruction,
1424 solana_sysvar_id::SysvarId,
1425 solana_transaction::sanitized::SanitizedTransaction,
1426 solana_transaction_context::transaction::TransactionContext,
1427 solana_transaction_error::TransactionError,
1428 std::{borrow::Cow, collections::HashMap},
1429 test_case::test_case,
1430 };
1431
1432 fn create_sysvar_account<T>(value: &T) -> AccountSharedData
1433 where
1434 T: wincode::Serialize<Src = T> + SysvarId,
1435 {
1436 let serialized_len = wincode::serialized_size(value).unwrap() as usize;
1437 let canonical_data_len = match T::id() {
1438 sysvar::clock::ID => solana_clock::SIZE,
1439 sysvar::epoch_schedule::ID => solana_epoch_schedule::SIZE,
1440 sysvar::fees::ID => solana_sysvar::fees::SIZE,
1441 sysvar::rent::ID => solana_rent::SIZE,
1442 id => panic!("unsupported sysvar: {id}"),
1443 };
1444 let required_data_len = canonical_data_len.max(serialized_len);
1445 let mut account = AccountSharedData::new(1, required_data_len, &sysvar::id());
1446 wincode::serialize_into(account.data_as_mut_slice(), value).unwrap();
1447 account
1448 }
1449
1450 fn new_unchecked_sanitized_message(message: Message) -> SanitizedMessage {
1451 SanitizedMessage::Legacy(LegacyMessage::new(message, &HashSet::new()))
1452 }
1453
1454 struct TestForkGraph {}
1455
1456 impl ForkGraph for TestForkGraph {
1457 fn relationship(&self, _a: Slot, _b: Slot) -> BlockRelation {
1458 BlockRelation::Unknown
1459 }
1460 }
1461
1462 #[derive(Clone)]
1463 struct MockBankCallback {
1464 account_shared_data: Arc<RwLock<HashMap<Pubkey, AccountSharedData>>>,
1465 #[allow(clippy::type_complexity)]
1466 inspected_accounts:
1467 Arc<RwLock<HashMap<Pubkey, Vec<(Option<AccountSharedData>, bool)>>>>,
1468 feature_set: SVMFeatureSet,
1469 }
1470
1471 impl Default for MockBankCallback {
1472 fn default() -> Self {
1473 Self {
1474 account_shared_data: Arc::default(),
1475 inspected_accounts: Arc::default(),
1476 feature_set: SVMFeatureSet::all_enabled(),
1477 }
1478 }
1479 }
1480
1481 impl InvokeContextCallback for MockBankCallback {}
1482
1483 impl TransactionProcessingCallback for MockBankCallback {
1484 fn get_account_shared_data(&self, pubkey: &Pubkey) -> Option<(AccountSharedData, Slot)> {
1485 self.account_shared_data
1486 .read()
1487 .unwrap()
1488 .get(pubkey)
1489 .map(|account| (account.clone(), 0))
1490 }
1491
1492 fn inspect_account(
1493 &self,
1494 address: &Pubkey,
1495 account_state: AccountState,
1496 is_writable: bool,
1497 ) {
1498 let account = match account_state {
1499 AccountState::Dead => None,
1500 AccountState::Alive(account) => Some(account.clone()),
1501 };
1502 self.inspected_accounts
1503 .write()
1504 .unwrap()
1505 .entry(*address)
1506 .or_default()
1507 .push((account, is_writable));
1508 }
1509 }
1510
1511 impl MockBankCallback {
1512 pub fn calculate_fee_details(
1513 message: &impl SVMMessage,
1514 lamports_per_signature: u64,
1515 prioritization_fee: u64,
1516 ) -> FeeDetails {
1517 let signature_count = message
1518 .num_transaction_signatures()
1519 .saturating_add(message.num_ed25519_signatures())
1520 .saturating_add(message.num_secp256k1_signatures())
1521 .saturating_add(message.num_secp256r1_signatures());
1522
1523 FeeDetails::new(
1524 signature_count.saturating_mul(lamports_per_signature),
1525 prioritization_fee,
1526 )
1527 }
1528 }
1529
1530 impl<'a> From<&'a MockBankCallback> for AccountLoader<'a, MockBankCallback> {
1531 fn from(callbacks: &'a MockBankCallback) -> AccountLoader<'a, MockBankCallback> {
1532 AccountLoader::new_with_loaded_accounts_capacity(
1533 None,
1534 callbacks,
1535 &callbacks.feature_set,
1536 0,
1537 )
1538 }
1539 }
1540
1541 #[test_case(1; "Check results too small")]
1542 #[test_case(3; "Check results too large")]
1543 #[should_panic(expected = "Length of check_results does not match length of sanitized_txs")]
1544 fn test_check_results_txs_length_mismatch(check_results_len: usize) {
1545 let sanitized_message = new_unchecked_sanitized_message(Message {
1546 account_keys: vec![Pubkey::new_from_array([0; 32])],
1547 header: MessageHeader::default(),
1548 instructions: vec![CompiledInstruction {
1549 program_id_index: 0,
1550 accounts: vec![],
1551 data: vec![],
1552 }],
1553 recent_blockhash: Hash::default(),
1554 });
1555
1556 let sanitized_txs = vec![
1558 SanitizedTransaction::new_for_tests(
1559 sanitized_message,
1560 vec![Signature::new_unique()],
1561 false,
1562 );
1563 2
1564 ];
1565
1566 let check_results = vec![
1567 TransactionCheckResult::Ok(CheckedTransactionDetails::default());
1568 check_results_len
1569 ];
1570
1571 let batch_processor = TransactionBatchProcessor::<TestForkGraph>::default();
1572 let callback = MockBankCallback::default();
1573
1574 batch_processor.load_and_execute_sanitized_transactions(
1575 &callback,
1576 &sanitized_txs,
1577 check_results,
1578 &get_mock_transaction_processing_environment(),
1579 &TransactionProcessingConfig::default(),
1580 );
1581 }
1582
1583 #[test]
1584 fn test_inner_instructions_list_from_instruction_trace() {
1585 let mut transaction_context = TransactionContext::new(
1586 vec![(
1587 Pubkey::new_unique(),
1588 AccountSharedData::new(1, 1, &bpf_loader::ID),
1589 )],
1590 Rent::default(),
1591 4,
1592 11,
1593 4,
1594 );
1595 let num_transaction_accounts = usize::from(transaction_context.get_number_of_accounts());
1596
1597 for i in 0..4 {
1599 transaction_context
1600 .configure_instruction_at_index(
1601 i,
1602 0,
1603 vec![],
1604 vec![u16::MAX; num_transaction_accounts],
1605 Cow::Owned(vec![i as u8]),
1606 None,
1607 )
1608 .unwrap();
1609 }
1610
1611 transaction_context.push().unwrap();
1613 transaction_context
1615 .configure_next_cpi_for_tests(0, vec![], vec![0, 0])
1616 .unwrap();
1617 transaction_context.push().unwrap();
1618 transaction_context.pop().unwrap();
1620 transaction_context.pop().unwrap();
1621 transaction_context.push().unwrap();
1623 transaction_context.pop().unwrap();
1624 transaction_context.push().unwrap();
1626 transaction_context
1628 .configure_next_cpi_for_tests(0, vec![], vec![2, 0])
1629 .unwrap();
1630 transaction_context.push().unwrap();
1631 transaction_context
1633 .configure_next_cpi_for_tests(0, vec![], vec![2, 1])
1634 .unwrap();
1635 transaction_context.push().unwrap();
1636 transaction_context.pop().unwrap();
1638 transaction_context.pop().unwrap();
1640 transaction_context
1642 .configure_next_cpi_for_tests(0, vec![], vec![2, 2])
1643 .unwrap();
1644 transaction_context.push().unwrap();
1645 transaction_context.pop().unwrap();
1647 transaction_context.pop().unwrap();
1648 transaction_context.push().unwrap();
1650 transaction_context
1652 .configure_next_cpi_for_tests(0, vec![], vec![3, 0])
1653 .unwrap();
1654 transaction_context.push().unwrap();
1655 transaction_context
1657 .configure_next_cpi_for_tests(0, vec![], vec![3, 1])
1658 .unwrap();
1659 transaction_context.push().unwrap();
1660 transaction_context
1662 .configure_next_cpi_for_tests(0, vec![], vec![3, 2])
1663 .unwrap();
1664 transaction_context.push().unwrap();
1665 transaction_context.pop().unwrap();
1667 transaction_context.pop().unwrap();
1668 transaction_context.pop().unwrap();
1669 transaction_context.pop().unwrap();
1670
1671 let inner_instructions =
1672 TransactionBatchProcessor::<TestForkGraph>::deconstruct_transaction(
1673 transaction_context,
1674 true,
1675 )
1676 .1
1677 .unwrap();
1678
1679 assert_eq!(
1680 inner_instructions,
1681 vec![
1682 vec![InnerInstruction {
1683 instruction: CompiledInstruction::new_from_raw_parts(0, vec![0, 0], vec![]),
1684 stack_height: 2,
1685 }],
1686 vec![],
1687 vec![
1688 InnerInstruction {
1689 instruction: CompiledInstruction::new_from_raw_parts(0, vec![2, 0], vec![]),
1690 stack_height: 2,
1691 },
1692 InnerInstruction {
1693 instruction: CompiledInstruction::new_from_raw_parts(0, vec![2, 1], vec![]),
1694 stack_height: 3,
1695 },
1696 InnerInstruction {
1697 instruction: CompiledInstruction::new_from_raw_parts(0, vec![2, 2], vec![]),
1698 stack_height: 2,
1699 },
1700 ],
1701 vec![
1702 InnerInstruction {
1703 instruction: CompiledInstruction::new_from_raw_parts(0, vec![3, 0], vec![]),
1704 stack_height: 2,
1705 },
1706 InnerInstruction {
1707 instruction: CompiledInstruction::new_from_raw_parts(0, vec![3, 1], vec![]),
1708 stack_height: 3,
1709 },
1710 InnerInstruction {
1711 instruction: CompiledInstruction::new_from_raw_parts(0, vec![3, 2], vec![]),
1712 stack_height: 4,
1713 },
1714 ]
1715 ]
1716 );
1717 }
1718
1719 #[test]
1720 fn test_execute_loaded_transaction_recordings() {
1721 let message = Message {
1725 account_keys: vec![Pubkey::new_from_array([0; 32])],
1726 header: MessageHeader::default(),
1727 instructions: vec![CompiledInstruction {
1728 program_id_index: 0,
1729 accounts: vec![],
1730 data: vec![],
1731 }],
1732 recent_blockhash: Hash::default(),
1733 };
1734
1735 let sanitized_message = new_unchecked_sanitized_message(message);
1736 let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
1737 let batch_processor = TransactionBatchProcessor::<TestForkGraph>::default();
1738
1739 let sanitized_transaction = SanitizedTransaction::new_for_tests(
1740 sanitized_message,
1741 vec![Signature::new_unique()],
1742 false,
1743 );
1744
1745 let loaded_transaction = LoadedTransaction {
1746 accounts: vec![(Pubkey::new_unique(), AccountSharedData::default())],
1747 touched_flags: Box::default(),
1748 fee_details: FeeDetails::default(),
1749 rollback_accounts: RollbackAccounts::default(),
1750 compute_budget: SVMTransactionExecutionBudget::default(),
1751 loaded_accounts_data_size: 32,
1752 };
1753
1754 let processing_environment = get_mock_transaction_processing_environment();
1755
1756 let mut processing_config = TransactionProcessingConfig::default();
1757 processing_config.recording_config.enable_log_recording = true;
1758
1759 let mock_bank = MockBankCallback::default();
1760 let sysvar_cache = batch_processor.sysvar_cache();
1761
1762 let executed_tx = batch_processor.execute_loaded_transaction(
1763 &mock_bank,
1764 &sanitized_transaction,
1765 &sysvar_cache,
1766 loaded_transaction.clone(),
1767 &mut ExecuteTimings::default(),
1768 &mut TransactionErrorMetrics::default(),
1769 &mut program_cache_for_tx_batch,
1770 &processing_environment,
1771 &processing_config,
1772 );
1773 assert!(executed_tx.execution_details.log_messages.is_some());
1774
1775 processing_config.log_messages_bytes_limit = Some(2);
1776
1777 let executed_tx = batch_processor.execute_loaded_transaction(
1778 &mock_bank,
1779 &sanitized_transaction,
1780 &sysvar_cache,
1781 loaded_transaction.clone(),
1782 &mut ExecuteTimings::default(),
1783 &mut TransactionErrorMetrics::default(),
1784 &mut program_cache_for_tx_batch,
1785 &processing_environment,
1786 &processing_config,
1787 );
1788 assert!(executed_tx.execution_details.log_messages.is_some());
1789 assert!(executed_tx.execution_details.inner_instructions.is_none());
1790
1791 processing_config.recording_config.enable_log_recording = false;
1792 processing_config.recording_config.enable_cpi_recording = true;
1793 processing_config.log_messages_bytes_limit = None;
1794
1795 let executed_tx = batch_processor.execute_loaded_transaction(
1796 &mock_bank,
1797 &sanitized_transaction,
1798 &sysvar_cache,
1799 loaded_transaction,
1800 &mut ExecuteTimings::default(),
1801 &mut TransactionErrorMetrics::default(),
1802 &mut program_cache_for_tx_batch,
1803 &processing_environment,
1804 &processing_config,
1805 );
1806
1807 assert!(executed_tx.execution_details.log_messages.is_none());
1808 assert!(executed_tx.execution_details.inner_instructions.is_some());
1809 }
1810
1811 #[test]
1812 fn test_execute_loaded_transaction_error_metrics() {
1813 let key1 = Pubkey::new_unique();
1817 let key2 = Pubkey::new_unique();
1818 let message = Message {
1819 account_keys: vec![key1, key2],
1820 header: MessageHeader::default(),
1821 instructions: vec![CompiledInstruction {
1822 program_id_index: 0,
1823 accounts: vec![1],
1824 data: vec![],
1825 }],
1826 recent_blockhash: Hash::default(),
1827 };
1828
1829 let sanitized_message = new_unchecked_sanitized_message(message);
1830 let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default();
1831 let batch_processor = TransactionBatchProcessor::<TestForkGraph>::default();
1832
1833 let sanitized_transaction = SanitizedTransaction::new_for_tests(
1834 sanitized_message,
1835 vec![Signature::new_unique()],
1836 false,
1837 );
1838
1839 let loaded_transaction = LoadedTransaction {
1840 accounts: vec![
1841 (key1, AccountSharedData::default()),
1842 (key2, AccountSharedData::default()),
1843 ],
1844 touched_flags: Box::default(),
1845 fee_details: FeeDetails::default(),
1846 rollback_accounts: RollbackAccounts::default(),
1847 compute_budget: SVMTransactionExecutionBudget::default(),
1848 loaded_accounts_data_size: 0,
1849 };
1850
1851 let processing_config = TransactionProcessingConfig {
1852 recording_config: ExecutionRecordingConfig::new_single_setting(false),
1853 ..Default::default()
1854 };
1855 let mut error_metrics = TransactionErrorMetrics::new();
1856 let mock_bank = MockBankCallback::default();
1857 let sysvar_cache = batch_processor.sysvar_cache();
1858
1859 let _ = batch_processor.execute_loaded_transaction(
1860 &mock_bank,
1861 &sanitized_transaction,
1862 &sysvar_cache,
1863 loaded_transaction,
1864 &mut ExecuteTimings::default(),
1865 &mut error_metrics,
1866 &mut program_cache_for_tx_batch,
1867 &get_mock_transaction_processing_environment(),
1868 &processing_config,
1869 );
1870
1871 assert_eq!(error_metrics.instruction_error.0, 1);
1872 }
1873
1874 #[test]
1875 #[should_panic = "called load_program_with_pubkey() with nonexistent account"]
1876 fn test_replenish_program_cache_with_nonexistent_accounts() {
1877 let mock_bank = MockBankCallback::default();
1878 let account_loader = (&mock_bank).into();
1879 let fork_graph = Arc::new(RwLock::new(TestForkGraph {}));
1880 let batch_processor =
1881 TransactionBatchProcessor::new(0, 0, Arc::downgrade(&fork_graph), None);
1882 let program_runtime_environment_for_execution =
1883 batch_processor.program_runtime_environment_for_epoch(0);
1884 let key = Pubkey::new_unique();
1885
1886 let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::new(batch_processor.slot);
1887
1888 batch_processor.replenish_program_cache(
1889 &account_loader,
1890 vec![ProgramToLoad {
1891 program_id: &key,
1892 loader: ProgramCacheEntryOwner::LoaderV3,
1893 match_criteria: ProgramCacheMatchCriteria::NoCriteria,
1894 last_modification_slot: 0,
1895 }],
1896 &program_runtime_environment_for_execution,
1897 &mut program_cache_for_tx_batch,
1898 &mut ExecuteTimings::default(),
1899 true,
1900 true,
1901 );
1902 }
1903
1904 #[test]
1905 fn test_replenish_program_cache() {
1906 let mock_bank = MockBankCallback::default();
1907 let fork_graph = Arc::new(RwLock::new(TestForkGraph {}));
1908 let batch_processor =
1909 TransactionBatchProcessor::new(0, 0, Arc::downgrade(&fork_graph), None);
1910 let program_runtime_environment_for_execution =
1911 batch_processor.program_runtime_environment_for_epoch(0);
1912 let key = Pubkey::new_unique();
1913
1914 let mut account_data = AccountSharedData::default();
1915 account_data.set_owner(bpf_loader::id());
1916 mock_bank
1917 .account_shared_data
1918 .write()
1919 .unwrap()
1920 .insert(key, account_data);
1921 let account_loader = (&mock_bank).into();
1922
1923 let mut loaded_missing = 0;
1924 for limit_to_load_programs in [false, true] {
1925 let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::new(batch_processor.slot);
1926
1927 batch_processor.replenish_program_cache(
1928 &account_loader,
1929 vec![ProgramToLoad {
1930 program_id: &key,
1931 loader: ProgramCacheEntryOwner::LoaderV2,
1932 match_criteria: ProgramCacheMatchCriteria::NoCriteria,
1933 last_modification_slot: 0,
1934 }],
1935 &program_runtime_environment_for_execution,
1936 &mut program_cache_for_tx_batch,
1937 &mut ExecuteTimings::default(),
1938 limit_to_load_programs,
1939 true,
1940 );
1941 assert!(!program_cache_for_tx_batch.hit_max_limit);
1942 if program_cache_for_tx_batch.loaded_missing {
1943 loaded_missing += 1;
1944 }
1945
1946 let program = program_cache_for_tx_batch.find(&key).unwrap();
1947 assert!(matches!(
1948 program.program,
1949 ProgramCacheEntryType::FailedVerification(_)
1950 ));
1951 }
1952 assert!(loaded_missing > 0);
1953 }
1954
1955 #[test]
1956 #[allow(deprecated)]
1957 fn test_sysvar_cache_initialization1() {
1958 let mock_bank = MockBankCallback::default();
1959
1960 let clock = Clock {
1961 slot: 1,
1962 epoch_start_timestamp: 2,
1963 epoch: 3,
1964 leader_schedule_epoch: 4,
1965 unix_timestamp: 5,
1966 };
1967 let clock_account = create_sysvar_account(&clock);
1968 mock_bank
1969 .account_shared_data
1970 .write()
1971 .unwrap()
1972 .insert(sysvar::clock::id(), clock_account);
1973
1974 let epoch_schedule = EpochSchedule::custom(64, 2, true);
1975 let epoch_schedule_account = create_sysvar_account(&epoch_schedule);
1976 mock_bank
1977 .account_shared_data
1978 .write()
1979 .unwrap()
1980 .insert(sysvar::epoch_schedule::id(), epoch_schedule_account);
1981
1982 let fees = Fees {
1983 fee_calculator: FeeCalculator {
1984 lamports_per_signature: 123,
1985 },
1986 };
1987 let fees_account = create_sysvar_account(&fees);
1988 mock_bank
1989 .account_shared_data
1990 .write()
1991 .unwrap()
1992 .insert(sysvar::fees::id(), fees_account);
1993
1994 let rent = Rent::default();
1995 let rent_account = create_sysvar_account(&rent);
1996 mock_bank
1997 .account_shared_data
1998 .write()
1999 .unwrap()
2000 .insert(sysvar::rent::id(), rent_account);
2001
2002 let transaction_processor = TransactionBatchProcessor::<TestForkGraph>::default();
2003 transaction_processor.fill_missing_sysvar_cache_entries(&mock_bank);
2004
2005 let sysvar_cache = transaction_processor.sysvar_cache.read().unwrap();
2006 let cached_clock = sysvar_cache.get_clock();
2007 let cached_epoch_schedule = sysvar_cache.get_epoch_schedule();
2008 let cached_fees = sysvar_cache.get_fees();
2009 let cached_rent = sysvar_cache.get_rent();
2010
2011 assert_eq!(
2012 cached_clock.expect("clock sysvar missing in cache"),
2013 clock.into()
2014 );
2015 assert_eq!(
2016 cached_epoch_schedule.expect("epoch_schedule sysvar missing in cache"),
2017 epoch_schedule.into()
2018 );
2019 assert_eq!(
2020 cached_fees.expect("fees sysvar missing in cache"),
2021 fees.into()
2022 );
2023 assert_eq!(
2024 cached_rent.expect("rent sysvar missing in cache"),
2025 rent.into()
2026 );
2027 assert!(sysvar_cache.get_slot_hashes().is_err());
2028 assert!(sysvar_cache.get_epoch_rewards().is_err());
2029 }
2030
2031 #[test]
2032 #[allow(deprecated)]
2033 fn test_reset_and_fill_sysvar_cache() {
2034 let mock_bank = MockBankCallback::default();
2035
2036 let clock = Clock {
2037 slot: 1,
2038 epoch_start_timestamp: 2,
2039 epoch: 3,
2040 leader_schedule_epoch: 4,
2041 unix_timestamp: 5,
2042 };
2043 let clock_account = create_sysvar_account(&clock);
2044 mock_bank
2045 .account_shared_data
2046 .write()
2047 .unwrap()
2048 .insert(sysvar::clock::id(), clock_account);
2049
2050 let epoch_schedule = EpochSchedule::custom(64, 2, true);
2051 let epoch_schedule_account = create_sysvar_account(&epoch_schedule);
2052 mock_bank
2053 .account_shared_data
2054 .write()
2055 .unwrap()
2056 .insert(sysvar::epoch_schedule::id(), epoch_schedule_account);
2057
2058 let fees = Fees {
2059 fee_calculator: FeeCalculator {
2060 lamports_per_signature: 123,
2061 },
2062 };
2063 let fees_account = create_sysvar_account(&fees);
2064 mock_bank
2065 .account_shared_data
2066 .write()
2067 .unwrap()
2068 .insert(sysvar::fees::id(), fees_account);
2069
2070 let rent = Rent::default();
2071 let rent_account = create_sysvar_account(&rent);
2072 mock_bank
2073 .account_shared_data
2074 .write()
2075 .unwrap()
2076 .insert(sysvar::rent::id(), rent_account);
2077
2078 let transaction_processor = TransactionBatchProcessor::<TestForkGraph>::default();
2079 transaction_processor.fill_missing_sysvar_cache_entries(&mock_bank);
2081
2082 let updated_clock = Clock {
2083 slot: 6,
2084 epoch_start_timestamp: 7,
2085 epoch: 8,
2086 leader_schedule_epoch: 9,
2087 unix_timestamp: 10,
2088 };
2089 let updated_clock_account = create_sysvar_account(&updated_clock);
2090 mock_bank
2091 .account_shared_data
2092 .write()
2093 .unwrap()
2094 .insert(sysvar::clock::id(), updated_clock_account);
2095 transaction_processor.reset_and_fill_sysvar_cache_entries(&mock_bank);
2096 {
2097 let sysvar_cache = transaction_processor.sysvar_cache.read().unwrap();
2098 assert_eq!(
2099 sysvar_cache
2100 .get_clock()
2101 .expect("clock sysvar missing in cache"),
2102 updated_clock.clone().into()
2103 );
2104 }
2105
2106 transaction_processor.reset_sysvar_cache();
2108
2109 {
2110 let sysvar_cache = transaction_processor.sysvar_cache.read().unwrap();
2111 assert!(sysvar_cache.get_clock().is_err());
2113 assert!(sysvar_cache.get_epoch_schedule().is_err());
2114 assert!(sysvar_cache.get_fees().is_err());
2115 assert!(sysvar_cache.get_epoch_rewards().is_err());
2116 assert!(sysvar_cache.get_rent().is_err());
2117 assert!(sysvar_cache.get_epoch_rewards().is_err());
2118 }
2119
2120 transaction_processor.fill_missing_sysvar_cache_entries(&mock_bank);
2122
2123 let sysvar_cache = transaction_processor.sysvar_cache.read().unwrap();
2124 let cached_clock = sysvar_cache.get_clock();
2125 let cached_epoch_schedule = sysvar_cache.get_epoch_schedule();
2126 let cached_fees = sysvar_cache.get_fees();
2127 let cached_rent = sysvar_cache.get_rent();
2128
2129 assert_eq!(
2130 cached_clock.expect("clock sysvar missing in cache"),
2131 updated_clock.into()
2132 );
2133 assert_eq!(
2134 cached_epoch_schedule.expect("epoch_schedule sysvar missing in cache"),
2135 epoch_schedule.into()
2136 );
2137 assert_eq!(
2138 cached_fees.expect("fees sysvar missing in cache"),
2139 fees.into()
2140 );
2141 assert_eq!(
2142 cached_rent.expect("rent sysvar missing in cache"),
2143 rent.into()
2144 );
2145 assert!(sysvar_cache.get_slot_hashes().is_err());
2146 assert!(sysvar_cache.get_epoch_rewards().is_err());
2147 }
2148
2149 #[test]
2150 fn test_add_builtin() {
2151 let fork_graph = Arc::new(RwLock::new(TestForkGraph {}));
2152 let batch_processor =
2153 TransactionBatchProcessor::new(0, 0, Arc::downgrade(&fork_graph), None);
2154
2155 let key = Pubkey::new_unique();
2156 let register_fn: BuiltinFunctionRegisterer = |p, n| {
2157 p.register_function(
2158 n,
2159 (
2160 |_invoke_context, _param0, _param1, _param2, _param3, _param4| {},
2161 |_| {},
2162 ),
2163 )
2164 };
2165 let program = ProgramCacheEntry::new_builtin(0, register_fn);
2166 batch_processor.add_builtin(key, program);
2167
2168 let mut loaded_programs_for_tx_batch = ProgramCacheForTxBatch::new(0);
2169 let program_runtime_environment =
2170 batch_processor.program_runtime_environment_for_epoch(batch_processor.epoch);
2171 batch_processor
2172 .global_program_cache
2173 .write()
2174 .unwrap()
2175 .extract(
2176 &mut vec![ProgramToLoad {
2177 program_id: &key,
2178 loader: ProgramCacheEntryOwner::NativeLoader,
2179 match_criteria: ProgramCacheMatchCriteria::NoCriteria,
2180 last_modification_slot: 0,
2181 }],
2182 &mut loaded_programs_for_tx_batch,
2183 &program_runtime_environment,
2184 true,
2185 true,
2186 );
2187 let entry = loaded_programs_for_tx_batch.find(&key).unwrap();
2188
2189 let program = ProgramCacheEntry::new_builtin(0, register_fn);
2191 assert_eq!(entry, Arc::new(program));
2192 }
2193
2194 #[test]
2195 fn test_validate_transaction_fee_payer_exact_balance() {
2196 let lamports_per_signature = 5000;
2197 let message = new_unchecked_sanitized_message(Message::new_with_blockhash(
2198 &[
2199 ComputeBudgetInstruction::set_compute_unit_limit(2000u32),
2200 ComputeBudgetInstruction::set_compute_unit_price(1_000_000_000),
2201 ],
2202 Some(&Pubkey::new_unique()),
2203 &Hash::new_unique(),
2204 ));
2205 let fee_payer_address = message.fee_payer();
2206 let current_epoch = 42;
2207 let rent = Rent::default();
2208 let min_balance = rent.minimum_balance(nonce::state::State::size());
2209 let transaction_fee = lamports_per_signature;
2210 let priority_fee = 2_000_000u64;
2211 let starting_balance = transaction_fee + priority_fee;
2212 assert!(
2213 starting_balance > min_balance,
2214 "we're testing that a rent exempt fee payer can be fully drained, so ensure that the \
2215 starting balance is more than the min balance"
2216 );
2217
2218 let fee_payer_rent_epoch = current_epoch;
2219 let fee_payer_account = AccountSharedData::new_rent_epoch(
2220 starting_balance,
2221 0,
2222 &Pubkey::default(),
2223 fee_payer_rent_epoch,
2224 );
2225 let mut mock_accounts = HashMap::new();
2226 mock_accounts.insert(*fee_payer_address, fee_payer_account.clone());
2227 let mock_bank = MockBankCallback {
2228 account_shared_data: Arc::new(RwLock::new(mock_accounts)),
2229 ..Default::default()
2230 };
2231 let mut account_loader = (&mock_bank).into();
2232
2233 let mut error_counters = TransactionErrorMetrics::default();
2234 let compute_budget_and_limits = SVMTransactionExecutionAndFeeBudgetLimits {
2235 budget: SVMTransactionExecutionBudget {
2236 compute_unit_limit: 2000,
2237 ..SVMTransactionExecutionBudget::default()
2238 },
2239 fee_details: FeeDetails::new(transaction_fee, priority_fee),
2240 ..SVMTransactionExecutionAndFeeBudgetLimits::default()
2241 };
2242 let result =
2243 TransactionBatchProcessor::<TestForkGraph>::validate_transaction_nonce_and_fee_payer(
2244 &mut account_loader,
2245 &message,
2246 CheckedTransactionDetails::new(None, compute_budget_and_limits),
2247 &Hash::default(),
2248 lamports_per_signature,
2249 &rent,
2250 mock_bank.feature_set.relax_post_exec_min_balance_check,
2251 false,
2252 &mut error_counters,
2253 );
2254
2255 let post_validation_fee_payer_account = {
2256 let mut account = fee_payer_account.clone();
2257 account.set_rent_epoch(RENT_EXEMPT_RENT_EPOCH);
2258 account.set_lamports(0);
2259 account
2260 };
2261
2262 assert_eq!(
2263 result,
2264 TransactionValidationResult::Loadable(ValidatedTransactionDetails {
2265 rollback_accounts: RollbackAccounts::new(
2266 None, *fee_payer_address,
2268 post_validation_fee_payer_account.clone(),
2269 fee_payer_rent_epoch
2270 ),
2271 compute_budget: compute_budget_and_limits.budget,
2272 loaded_accounts_bytes_limit: compute_budget_and_limits
2273 .loaded_accounts_data_size_limit,
2274 fee_details: FeeDetails::new(transaction_fee, priority_fee),
2275 loaded_fee_payer_account: LoadedTransactionAccount {
2276 loaded_size: TRANSACTION_ACCOUNT_BASE_SIZE + fee_payer_account.data().len(),
2277 account: post_validation_fee_payer_account,
2278 },
2279 })
2280 );
2281 }
2282
2283 #[test_case(false; "strict_fee_payer")]
2284 #[test_case(true; "relaxed_fee_payer")]
2285 fn test_validate_transaction_fee_payer_not_found(relax_fee_payer_constraint: bool) {
2286 let lamports_per_signature = 5000;
2287 let message =
2288 new_unchecked_sanitized_message(Message::new(&[], Some(&Pubkey::new_unique())));
2289
2290 let fee_and_limits = SVMTransactionExecutionAndFeeBudgetLimits::default();
2291 let mut mock_bank = MockBankCallback::default();
2292 mock_bank.feature_set.relax_fee_payer_constraint = relax_fee_payer_constraint;
2293 let mut account_loader = (&mock_bank).into();
2294 let mut error_counters = TransactionErrorMetrics::default();
2295
2296 let expected_error = TransactionError::AccountNotFound;
2297 let expected_result = if relax_fee_payer_constraint {
2298 TransactionValidationResult::NoOp(NoOpTransaction {
2299 validation_error: expected_error,
2300 fee_payer_balance: None,
2301 compute_unit_limit: fee_and_limits.budget.compute_unit_limit,
2302 loaded_accounts_bytes_limit: fee_and_limits.loaded_accounts_data_size_limit,
2303 })
2304 } else {
2305 TransactionValidationResult::Unprocessable(expected_error)
2306 };
2307
2308 let actual_result =
2309 TransactionBatchProcessor::<TestForkGraph>::validate_transaction_nonce_and_fee_payer(
2310 &mut account_loader,
2311 &message,
2312 CheckedTransactionDetails::new(None, fee_and_limits),
2313 &Hash::default(),
2314 lamports_per_signature,
2315 &Rent::default(),
2316 mock_bank.feature_set.relax_post_exec_min_balance_check,
2317 false,
2318 &mut error_counters,
2319 );
2320
2321 assert_eq!(error_counters.account_not_found.0, 1);
2322 assert_eq!(actual_result, expected_result);
2323 }
2324
2325 #[test_case(false; "strict_fee_payer")]
2326 #[test_case(true; "relaxed_fee_payer")]
2327 fn test_validate_transaction_fee_payer_insufficient_funds(relax_fee_payer_constraint: bool) {
2328 let lamports_per_signature = 5000;
2329 let fee_payer_balance = 1;
2330 let message =
2331 new_unchecked_sanitized_message(Message::new(&[], Some(&Pubkey::new_unique())));
2332 let fee_details =
2333 MockBankCallback::calculate_fee_details(&message, lamports_per_signature, 0);
2334 let fee_and_limits = SVMTransactionExecutionAndFeeBudgetLimits::with_fee(fee_details);
2335 let fee_payer_address = message.fee_payer();
2336 let fee_payer_account = AccountSharedData::new(fee_payer_balance, 0, &Pubkey::default());
2337 let mut mock_accounts = HashMap::new();
2338 mock_accounts.insert(*fee_payer_address, fee_payer_account);
2339 let mut mock_bank = MockBankCallback {
2340 account_shared_data: Arc::new(RwLock::new(mock_accounts)),
2341 ..Default::default()
2342 };
2343 mock_bank.feature_set.relax_fee_payer_constraint = relax_fee_payer_constraint;
2344 let mut account_loader = (&mock_bank).into();
2345
2346 let mut error_counters = TransactionErrorMetrics::default();
2347
2348 let expected_error = TransactionError::InsufficientFundsForFee;
2349 let expected_result = if relax_fee_payer_constraint {
2350 TransactionValidationResult::NoOp(NoOpTransaction {
2351 validation_error: expected_error,
2352 fee_payer_balance: Some(fee_payer_balance),
2353 compute_unit_limit: fee_and_limits.budget.compute_unit_limit,
2354 loaded_accounts_bytes_limit: fee_and_limits.loaded_accounts_data_size_limit,
2355 })
2356 } else {
2357 TransactionValidationResult::Unprocessable(expected_error)
2358 };
2359
2360 let actual_result =
2361 TransactionBatchProcessor::<TestForkGraph>::validate_transaction_nonce_and_fee_payer(
2362 &mut account_loader,
2363 &message,
2364 CheckedTransactionDetails::new(None, fee_and_limits),
2365 &Hash::default(),
2366 lamports_per_signature,
2367 &Rent::default(),
2368 mock_bank.feature_set.relax_post_exec_min_balance_check,
2369 false,
2370 &mut error_counters,
2371 );
2372
2373 assert_eq!(error_counters.insufficient_funds.0, 1);
2374 assert_eq!(actual_result, expected_result);
2375 }
2376
2377 #[test_case(false; "strict_fee_payer")]
2378 #[test_case(true; "relaxed_fee_payer")]
2379 fn test_validate_transaction_fee_payer_insufficient_rent(relax_fee_payer_constraint: bool) {
2380 let lamports_per_signature = 5000;
2381 let message =
2382 new_unchecked_sanitized_message(Message::new(&[], Some(&Pubkey::new_unique())));
2383 let fee_details =
2384 MockBankCallback::calculate_fee_details(&message, lamports_per_signature, 0);
2385 let fee_and_limits = SVMTransactionExecutionAndFeeBudgetLimits::with_fee(fee_details);
2386 let fee_payer_address = message.fee_payer();
2387 let transaction_fee = lamports_per_signature;
2388 let rent = Rent::default();
2389 let min_balance = rent.minimum_balance(0);
2390 let starting_balance = min_balance + transaction_fee - 1;
2391 let fee_payer_account = AccountSharedData::new(starting_balance, 0, &Pubkey::default());
2392 let mut mock_accounts = HashMap::new();
2393 mock_accounts.insert(*fee_payer_address, fee_payer_account);
2394 let mut mock_bank = MockBankCallback {
2395 account_shared_data: Arc::new(RwLock::new(mock_accounts)),
2396 ..Default::default()
2397 };
2398 mock_bank.feature_set.relax_fee_payer_constraint = relax_fee_payer_constraint;
2399 let mut account_loader = (&mock_bank).into();
2400
2401 let mut error_counters = TransactionErrorMetrics::default();
2402
2403 let expected_error = TransactionError::InsufficientFundsForRent { account_index: 0 };
2404 let expected_result = if relax_fee_payer_constraint {
2405 TransactionValidationResult::NoOp(NoOpTransaction {
2406 validation_error: expected_error,
2407 fee_payer_balance: Some(starting_balance),
2408 compute_unit_limit: fee_and_limits.budget.compute_unit_limit,
2409 loaded_accounts_bytes_limit: fee_and_limits.loaded_accounts_data_size_limit,
2410 })
2411 } else {
2412 TransactionValidationResult::Unprocessable(expected_error)
2413 };
2414
2415 let actual_result =
2416 TransactionBatchProcessor::<TestForkGraph>::validate_transaction_nonce_and_fee_payer(
2417 &mut account_loader,
2418 &message,
2419 CheckedTransactionDetails::new(None, fee_and_limits),
2420 &Hash::default(),
2421 lamports_per_signature,
2422 &rent,
2423 mock_bank.feature_set.relax_post_exec_min_balance_check,
2424 false,
2425 &mut error_counters,
2426 );
2427
2428 assert_eq!(actual_result, expected_result);
2429 }
2430
2431 #[test_case(false; "strict_fee_payer")]
2432 #[test_case(true; "relaxed_fee_payer")]
2433 fn test_validate_transaction_fee_payer_invalid(relax_fee_payer_constraint: bool) {
2434 let lamports_per_signature = 5000;
2435 let fee_payer_balance = 1_000_000;
2436 let message =
2437 new_unchecked_sanitized_message(Message::new(&[], Some(&Pubkey::new_unique())));
2438 let fee_details =
2439 MockBankCallback::calculate_fee_details(&message, lamports_per_signature, 0);
2440 let fee_and_limits = SVMTransactionExecutionAndFeeBudgetLimits::with_fee(fee_details);
2441 let fee_payer_address = message.fee_payer();
2442 let fee_payer_account = AccountSharedData::new(fee_payer_balance, 0, &Pubkey::new_unique());
2443 let mut mock_accounts = HashMap::new();
2444 mock_accounts.insert(*fee_payer_address, fee_payer_account);
2445 let mut mock_bank = MockBankCallback {
2446 account_shared_data: Arc::new(RwLock::new(mock_accounts)),
2447 ..Default::default()
2448 };
2449 mock_bank.feature_set.relax_fee_payer_constraint = relax_fee_payer_constraint;
2450 let mut account_loader = (&mock_bank).into();
2451
2452 let mut error_counters = TransactionErrorMetrics::default();
2453
2454 let expected_error = TransactionError::InvalidAccountForFee;
2455 let expected_result = if relax_fee_payer_constraint {
2456 TransactionValidationResult::NoOp(NoOpTransaction {
2457 validation_error: expected_error,
2458 fee_payer_balance: Some(fee_payer_balance),
2459 compute_unit_limit: fee_and_limits.budget.compute_unit_limit,
2460 loaded_accounts_bytes_limit: fee_and_limits.loaded_accounts_data_size_limit,
2461 })
2462 } else {
2463 TransactionValidationResult::Unprocessable(expected_error)
2464 };
2465
2466 let actual_result =
2467 TransactionBatchProcessor::<TestForkGraph>::validate_transaction_nonce_and_fee_payer(
2468 &mut account_loader,
2469 &message,
2470 CheckedTransactionDetails::new(None, fee_and_limits),
2471 &Hash::default(),
2472 lamports_per_signature,
2473 &Rent::default(),
2474 mock_bank.feature_set.relax_post_exec_min_balance_check,
2475 false,
2476 &mut error_counters,
2477 );
2478
2479 assert_eq!(error_counters.invalid_account_for_fee.0, 1);
2480 assert_eq!(actual_result, expected_result);
2481 }
2482
2483 #[derive(Debug, PartialEq, Eq)]
2484 enum ValidateNonce {
2485 Success,
2486 NoAccount,
2487 BadOwner,
2488 BlockhashMismatch,
2489 AlreadyUsed,
2490 BadSigner,
2491 }
2492
2493 #[test_case(ValidateNonce::Success)]
2494 #[test_case(ValidateNonce::NoAccount)]
2495 #[test_case(ValidateNonce::BadOwner)]
2496 #[test_case(ValidateNonce::BlockhashMismatch)]
2497 #[test_case(ValidateNonce::AlreadyUsed)]
2498 #[test_case(ValidateNonce::BadSigner)]
2499 fn test_validate_transaction_nonce(case: ValidateNonce) {
2500 let lamports_per_signature = 5000;
2501 let previous_durable_nonce = DurableNonce::from_blockhash(&Hash::new_unique());
2502 let nonce_address = Pubkey::new_unique();
2503 let authority_address = Pubkey::new_unique();
2504
2505 let message_blockhash = if case == ValidateNonce::BlockhashMismatch {
2506 Hash::new_unique()
2507 } else {
2508 *previous_durable_nonce.as_hash()
2509 };
2510
2511 let message_authority = if case == ValidateNonce::BadSigner {
2512 Pubkey::new_unique()
2513 } else {
2514 authority_address
2515 };
2516
2517 let message = new_unchecked_sanitized_message(Message::new_with_blockhash(
2518 &[system_instruction::advance_nonce_account(
2519 &nonce_address,
2520 &message_authority,
2521 )],
2522 Some(&Pubkey::new_unique()),
2523 &message_blockhash,
2524 ));
2525
2526 let environment_blockhash = Hash::new_unique();
2527 let next_durable_nonce = DurableNonce::from_blockhash(&environment_blockhash);
2528
2529 let stored_durable_nonce = if case == ValidateNonce::AlreadyUsed {
2530 next_durable_nonce
2531 } else {
2532 previous_durable_nonce
2533 };
2534
2535 let nonce_versions = nonce::versions::Versions::new(nonce::state::State::Initialized(
2536 nonce::state::Data::new(
2537 authority_address,
2538 stored_durable_nonce,
2539 lamports_per_signature,
2540 ),
2541 ));
2542
2543 let nonce_owner = if case == ValidateNonce::BadOwner {
2544 Pubkey::new_unique()
2545 } else {
2546 system_program::id()
2547 };
2548
2549 let nonce_account = AccountSharedData::new_data(1, &nonce_versions, &nonce_owner).unwrap();
2550
2551 let mut mock_accounts = HashMap::new();
2552
2553 if case != ValidateNonce::NoAccount {
2554 mock_accounts.insert(nonce_address, nonce_account.clone());
2555 }
2556
2557 let mock_bank = MockBankCallback {
2558 account_shared_data: Arc::new(RwLock::new(mock_accounts)),
2559 ..Default::default()
2560 };
2561 let mut account_loader = (&mock_bank).into();
2562
2563 let mut error_counters = TransactionErrorMetrics::default();
2564 let result = TransactionBatchProcessor::<TestForkGraph>::validate_transaction_nonce(
2565 &mut account_loader,
2566 &message,
2567 &nonce_address,
2568 &next_durable_nonce,
2569 lamports_per_signature,
2570 false,
2571 &mut error_counters,
2572 );
2573
2574 match case {
2575 ValidateNonce::Success => {
2576 let mut future_nonce_info = NonceInfo::new(nonce_address, nonce_account);
2577 future_nonce_info
2578 .try_advance_nonce(next_durable_nonce, lamports_per_signature)
2579 .unwrap();
2580
2581 assert_eq!(result, Ok(future_nonce_info));
2582 }
2583 ValidateNonce::NoAccount => {
2584 assert_eq!(error_counters.account_not_found.0, 1);
2585 assert_eq!(result, Err(TransactionError::AccountNotFound));
2586 }
2587 _ => {
2588 assert_eq!(error_counters.blockhash_not_found.0, 1);
2589 assert_eq!(result, Err(TransactionError::BlockhashNotFound));
2590 }
2591 }
2592 }
2593
2594 #[test_case(false; "strict_fee_payer")]
2595 #[test_case(true; "relaxed_fee_payer")]
2596 fn test_validate_transaction_fee_payer_is_nonce(relax_fee_payer_constraint: bool) {
2597 let lamports_per_signature = 5000;
2598 let rent = Rent::default();
2599 let compute_unit_limit = 1000u64;
2600 let previous_durable_nonce = DurableNonce::from_blockhash(&Hash::new_unique());
2601 let fee_payer_address = &Pubkey::new_unique();
2602 let message = new_unchecked_sanitized_message(Message::new_with_blockhash(
2603 &[
2604 system_instruction::advance_nonce_account(fee_payer_address, fee_payer_address),
2605 ComputeBudgetInstruction::set_compute_unit_limit(compute_unit_limit as u32),
2606 ComputeBudgetInstruction::set_compute_unit_price(1_000_000),
2607 ],
2608 Some(fee_payer_address),
2609 previous_durable_nonce.as_hash(),
2610 ));
2611 let transaction_fee = lamports_per_signature;
2612 let compute_budget_and_limits = SVMTransactionExecutionAndFeeBudgetLimits {
2613 fee_details: FeeDetails::new(transaction_fee, compute_unit_limit),
2614 ..SVMTransactionExecutionAndFeeBudgetLimits::default()
2615 };
2616 let min_balance = Rent::default().minimum_balance(nonce::state::State::size());
2617 let priority_fee = compute_unit_limit;
2618
2619 let nonce_versions = nonce::versions::Versions::new(nonce::state::State::Initialized(
2620 nonce::state::Data::new(
2621 *fee_payer_address,
2622 previous_durable_nonce,
2623 lamports_per_signature,
2624 ),
2625 ));
2626
2627 let environment_blockhash = Hash::new_unique();
2628 let next_durable_nonce = DurableNonce::from_blockhash(&environment_blockhash);
2629
2630 {
2632 let fee_payer_account = AccountSharedData::new_data(
2633 min_balance + transaction_fee + priority_fee,
2634 &nonce_versions,
2635 &system_program::id(),
2636 )
2637 .unwrap();
2638
2639 let mut future_nonce = NonceInfo::new(*fee_payer_address, fee_payer_account.clone());
2640 future_nonce
2641 .try_advance_nonce(next_durable_nonce, lamports_per_signature)
2642 .unwrap();
2643
2644 let mut mock_accounts = HashMap::new();
2645 mock_accounts.insert(*fee_payer_address, fee_payer_account.clone());
2646 let mut mock_bank = MockBankCallback {
2647 account_shared_data: Arc::new(RwLock::new(mock_accounts)),
2648 ..Default::default()
2649 };
2650 mock_bank.feature_set.relax_fee_payer_constraint = relax_fee_payer_constraint;
2651 let mut account_loader = (&mock_bank).into();
2652
2653 let mut error_counters = TransactionErrorMetrics::default();
2654
2655 let tx_details =
2656 CheckedTransactionDetails::new(Some(*fee_payer_address), compute_budget_and_limits);
2657
2658 let result = TransactionBatchProcessor::<TestForkGraph>::validate_transaction_nonce_and_fee_payer(
2659 &mut account_loader,
2660 &message,
2661 tx_details,
2662 &environment_blockhash,
2663 lamports_per_signature,
2664 &rent,
2665 mock_bank.feature_set.relax_post_exec_min_balance_check,
2666 false,
2667 &mut error_counters,
2668 );
2669
2670 let post_validation_fee_payer_account = {
2671 let mut account = fee_payer_account.clone();
2672 account.set_rent_epoch(RENT_EXEMPT_RENT_EPOCH);
2673 account.set_lamports(min_balance);
2674 account
2675 };
2676
2677 assert_eq!(
2678 result,
2679 TransactionValidationResult::Loadable(ValidatedTransactionDetails {
2680 rollback_accounts: RollbackAccounts::new(
2681 Some(future_nonce),
2682 *fee_payer_address,
2683 post_validation_fee_payer_account.clone(),
2684 0, ),
2686 compute_budget: compute_budget_and_limits.budget,
2687 loaded_accounts_bytes_limit: compute_budget_and_limits
2688 .loaded_accounts_data_size_limit,
2689 fee_details: FeeDetails::new(transaction_fee, priority_fee),
2690 loaded_fee_payer_account: LoadedTransactionAccount {
2691 loaded_size: TRANSACTION_ACCOUNT_BASE_SIZE + fee_payer_account.data().len(),
2692 account: post_validation_fee_payer_account,
2693 }
2694 })
2695 );
2696 }
2697
2698 {
2700 let fee_payer_account = AccountSharedData::new_data(
2701 transaction_fee + priority_fee, &nonce_versions,
2703 &system_program::id(),
2704 )
2705 .unwrap();
2706
2707 let mut mock_accounts = HashMap::new();
2708 mock_accounts.insert(*fee_payer_address, fee_payer_account);
2709 let mut mock_bank = MockBankCallback {
2710 account_shared_data: Arc::new(RwLock::new(mock_accounts)),
2711 ..Default::default()
2712 };
2713 mock_bank.feature_set.relax_fee_payer_constraint = relax_fee_payer_constraint;
2714 let mut account_loader = (&mock_bank).into();
2715
2716 let mut error_counters = TransactionErrorMetrics::default();
2717
2718 let tx_details =
2719 CheckedTransactionDetails::new(Some(*fee_payer_address), compute_budget_and_limits);
2720
2721 let result = TransactionBatchProcessor::<TestForkGraph>::validate_transaction_nonce_and_fee_payer(
2722 &mut account_loader,
2723 &message,
2724 tx_details,
2725 &environment_blockhash,
2726 lamports_per_signature,
2727 &rent,
2728 mock_bank.feature_set.relax_post_exec_min_balance_check,
2729 false,
2730 &mut error_counters,
2731 );
2732
2733 assert_eq!(error_counters.insufficient_funds.0, 1);
2735 assert_eq!(
2736 result,
2737 TransactionValidationResult::Unprocessable(
2738 TransactionError::InsufficientFundsForFee
2739 )
2740 );
2741 }
2742 }
2743
2744 #[test]
2747 fn test_inspect_account_fee_payer() {
2748 let lamports_per_signature = 5000;
2749 let fee_payer_address = Pubkey::new_unique();
2750 let fee_payer_account = AccountSharedData::new_rent_epoch(
2751 123_000_000_000,
2752 0,
2753 &Pubkey::default(),
2754 RENT_EXEMPT_RENT_EPOCH,
2755 );
2756 let mock_bank = MockBankCallback::default();
2757 mock_bank
2758 .account_shared_data
2759 .write()
2760 .unwrap()
2761 .insert(fee_payer_address, fee_payer_account.clone());
2762 let mut account_loader = (&mock_bank).into();
2763
2764 let message = new_unchecked_sanitized_message(Message::new_with_blockhash(
2765 &[
2766 ComputeBudgetInstruction::set_compute_unit_limit(2000u32),
2767 ComputeBudgetInstruction::set_compute_unit_price(1_000_000_000),
2768 ],
2769 Some(&fee_payer_address),
2770 &Hash::new_unique(),
2771 ));
2772
2773 assert!(matches!(
2774 TransactionBatchProcessor::<TestForkGraph>::validate_transaction_nonce_and_fee_payer(
2775 &mut account_loader,
2776 &message,
2777 CheckedTransactionDetails::new(
2778 None,
2779 SVMTransactionExecutionAndFeeBudgetLimits::with_fee(
2780 MockBankCallback::calculate_fee_details(&message, 5000, 0),
2781 ),
2782 ),
2783 &Hash::default(),
2784 lamports_per_signature,
2785 &Rent::default(),
2786 mock_bank.feature_set.relax_post_exec_min_balance_check,
2787 false,
2788 &mut TransactionErrorMetrics::default(),
2789 ),
2790 TransactionValidationResult::Loadable(_)
2791 ));
2792
2793 let actual_inspected_accounts: Vec<_> = mock_bank
2795 .inspected_accounts
2796 .read()
2797 .unwrap()
2798 .iter()
2799 .map(|(k, v)| (*k, v.clone()))
2800 .collect();
2801 assert_eq!(
2802 actual_inspected_accounts.as_slice(),
2803 &[(fee_payer_address, vec![(Some(fee_payer_account), true)])],
2804 );
2805 }
2806
2807 #[test]
2808 fn test_set_program_runtime_environment() {
2809 let mut transaction_processor = TransactionBatchProcessor::<TestForkGraph>::default();
2810 let current_environment =
2811 ProgramRuntimeEnvironment::clone(&transaction_processor.program_runtime_environment);
2812 let new_environment = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
2813 let config = vm::Config {
2814 enable_symbol_and_section_labels: true,
2815 ..vm::Config::default()
2816 };
2817 let new_environment2 = ProgramRuntimeEnvironment::from(BuiltinProgram::new_loader(config));
2818 assert_ne!(current_environment, new_environment);
2819 assert_ne!(current_environment, new_environment2);
2820 assert_ne!(new_environment, new_environment2);
2821 transaction_processor.set_program_runtime_environment(ProgramRuntimeEnvironment::clone(
2823 ¤t_environment,
2824 ));
2825 assert_eq!(
2826 transaction_processor.program_runtime_environment,
2827 current_environment,
2828 );
2829 transaction_processor
2831 .set_program_runtime_environment(ProgramRuntimeEnvironment::clone(&new_environment));
2832 assert_eq!(
2833 transaction_processor.program_runtime_environment,
2834 current_environment,
2835 );
2836 transaction_processor
2838 .set_program_runtime_environment(ProgramRuntimeEnvironment::clone(&new_environment2));
2839 assert_eq!(
2840 transaction_processor.program_runtime_environment,
2841 new_environment2,
2842 );
2843 transaction_processor
2845 .epoch_boundary_preparation
2846 .write()
2847 .unwrap()
2848 .upcoming_environment = Some(ProgramRuntimeEnvironment::clone(&new_environment));
2849 transaction_processor.set_program_runtime_environment(ProgramRuntimeEnvironment::clone(
2850 ¤t_environment,
2851 ));
2852 assert_eq!(
2853 transaction_processor.program_runtime_environment,
2854 new_environment,
2855 );
2856 }
2857}