From d88050cda335f87e872eddbdf8506bc063f039d3 Mon Sep 17 00:00:00 2001 From: Dmitri Makarov Date: Fri, 8 Mar 2024 14:04:07 -0500 Subject: [PATCH] SVM: Add doc comments, restrict visibility of some xfaces to crate (#136) --- runtime/src/bank.rs | 2 +- svm/src/account_loader.rs | 385 ++++++++++++++++++---- svm/src/account_overrides.rs | 1 + svm/src/account_rent_state.rs | 33 +- svm/src/transaction_account_state_info.rs | 4 +- svm/src/transaction_processor.rs | 231 ++++++------- svm/tests/account_loader.rs | 214 ------------ svm/tests/rent_state.rs | 90 ----- 8 files changed, 463 insertions(+), 497 deletions(-) delete mode 100644 svm/tests/account_loader.rs delete mode 100644 svm/tests/rent_state.rs diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index 3e504d470de744..f0ba75defa0517 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -7499,7 +7499,7 @@ impl Bank { effective_epoch: Epoch, ) -> Arc { self.transaction_processor - .load_program(self, pubkey, reload, effective_epoch) + .load_program_with_pubkey(self, pubkey, reload, effective_epoch) } } diff --git a/svm/src/account_loader.rs b/svm/src/account_loader.rs index 1c02ded24665ff..bf9b5b9c40bfee 100644 --- a/svm/src/account_loader.rs +++ b/svm/src/account_loader.rs @@ -38,8 +38,11 @@ use { }; // for the load instructions -pub type TransactionRent = u64; -pub type TransactionProgramIndices = Vec>; +pub(crate) type TransactionRent = u64; +pub(crate) type TransactionProgramIndices = Vec>; +pub type TransactionCheckResult = (transaction::Result<()>, Option, Option); +pub type TransactionLoadResult = (Result, Option); + #[derive(PartialEq, Eq, Debug, Clone)] pub struct LoadedTransaction { pub accounts: Vec, @@ -48,10 +51,66 @@ pub struct LoadedTransaction { pub rent_debits: RentDebits, } -pub type TransactionLoadResult = (Result, Option); -pub type TransactionCheckResult = (transaction::Result<()>, Option, Option); +/// Check whether the payer_account is capable of paying the fee. The +/// side effect is to subtract the fee amount from the payer_account +/// balance of lamports. If the payer_acount is not able to pay the +/// fee, the error_counters is incremented, and a specific error is +/// returned. +pub fn validate_fee_payer( + payer_address: &Pubkey, + payer_account: &mut AccountSharedData, + payer_index: IndexOfAccount, + error_counters: &mut TransactionErrorMetrics, + rent_collector: &RentCollector, + fee: u64, +) -> Result<()> { + if payer_account.lamports() == 0 { + error_counters.account_not_found += 1; + return Err(TransactionError::AccountNotFound); + } + let system_account_kind = get_system_account_kind(payer_account).ok_or_else(|| { + error_counters.invalid_account_for_fee += 1; + TransactionError::InvalidAccountForFee + })?; + let min_balance = match system_account_kind { + SystemAccountKind::System => 0, + SystemAccountKind::Nonce => { + // Should we ever allow a fees charge to zero a nonce account's + // balance. The state MUST be set to uninitialized in that case + rent_collector.rent.minimum_balance(NonceState::size()) + } + }; + + payer_account + .lamports() + .checked_sub(min_balance) + .and_then(|v| v.checked_sub(fee)) + .ok_or_else(|| { + error_counters.insufficient_funds += 1; + TransactionError::InsufficientFundsForFee + })?; -pub fn load_accounts( + let payer_pre_rent_state = RentState::from_account(payer_account, &rent_collector.rent); + payer_account + .checked_sub_lamports(fee) + .map_err(|_| TransactionError::InsufficientFundsForFee)?; + + let payer_post_rent_state = RentState::from_account(payer_account, &rent_collector.rent); + RentState::check_rent_state_with_account( + &payer_pre_rent_state, + &payer_post_rent_state, + payer_address, + payer_account, + payer_index, + ) +} + +/// Collect information about accounts used in txs transactions and +/// return vector of tuples, one for each transaction in the +/// batch. Each tuple contains struct of information about accounts as +/// its first element and an optional transaction nonce info as its +/// second element. +pub(crate) fn load_accounts( callbacks: &CB, txs: &[SanitizedTransaction], lock_results: &[TransactionCheckResult], @@ -399,55 +458,6 @@ fn accumulate_and_check_loaded_account_data_size( } } -pub fn validate_fee_payer( - payer_address: &Pubkey, - payer_account: &mut AccountSharedData, - payer_index: IndexOfAccount, - error_counters: &mut TransactionErrorMetrics, - rent_collector: &RentCollector, - fee: u64, -) -> Result<()> { - if payer_account.lamports() == 0 { - error_counters.account_not_found += 1; - return Err(TransactionError::AccountNotFound); - } - let system_account_kind = get_system_account_kind(payer_account).ok_or_else(|| { - error_counters.invalid_account_for_fee += 1; - TransactionError::InvalidAccountForFee - })?; - let min_balance = match system_account_kind { - SystemAccountKind::System => 0, - SystemAccountKind::Nonce => { - // Should we ever allow a fees charge to zero a nonce account's - // balance. The state MUST be set to uninitialized in that case - rent_collector.rent.minimum_balance(NonceState::size()) - } - }; - - payer_account - .lamports() - .checked_sub(min_balance) - .and_then(|v| v.checked_sub(fee)) - .ok_or_else(|| { - error_counters.insufficient_funds += 1; - TransactionError::InsufficientFundsForFee - })?; - - let payer_pre_rent_state = RentState::from_account(payer_account, &rent_collector.rent); - payer_account - .checked_sub_lamports(fee) - .map_err(|_| TransactionError::InsufficientFundsForFee)?; - - let payer_post_rent_state = RentState::from_account(payer_account, &rent_collector.rent); - RentState::check_rent_state_with_account( - &payer_pre_rent_state, - &payer_post_rent_state, - payer_address, - payer_account, - payer_index, - ) -} - fn construct_instructions_account(message: &SanitizedMessage) -> AccountSharedData { AccountSharedData::from(Account { data: construct_instructions_data(&message.decompile_instructions()), @@ -460,11 +470,15 @@ fn construct_instructions_account(message: &SanitizedMessage) -> AccountSharedDa mod tests { use { super::*, - crate::transaction_processor::TransactionProcessingCallback, + crate::{ + transaction_account_state_info::TransactionAccountStateInfo, + transaction_processor::TransactionProcessingCallback, + }, nonce::state::Versions as NonceVersions, solana_program_runtime::{ + compute_budget::ComputeBudget, compute_budget_processor, - loaded_programs::LoadedProgram, + loaded_programs::{LoadedProgram, LoadedProgramsForTxBatch}, prioritization_fee::{PrioritizationFeeDetails, PrioritizationFeeType}, }, solana_sdk::{ @@ -473,22 +487,27 @@ mod tests { compute_budget::ComputeBudgetInstruction, epoch_schedule::EpochSchedule, feature_set::FeatureSet, + fee::FeeStructure, hash::Hash, instruction::CompiledInstruction, message::{ v0::{LoadedAddresses, LoadedMessage}, LegacyMessage, Message, MessageHeader, SanitizedMessage, }, + native_loader, + native_token::sol_to_lamports, nonce, + nonce_info::{NonceFull, NoncePartial}, pubkey::Pubkey, rent::Rent, - rent_collector::RentCollector, + rent_collector::{RentCollector, RENT_EXEMPT_RENT_EPOCH}, + rent_debits::RentDebits, signature::{Keypair, Signature, Signer}, - system_program, sysvar, - transaction::{Result, Transaction, TransactionError}, - transaction_context::TransactionAccount, + system_program, system_transaction, sysvar, + transaction::{Result, SanitizedTransaction, Transaction, TransactionError}, + transaction_context::{TransactionAccount, TransactionContext}, }, - std::{borrow::Cow, convert::TryFrom, sync::Arc}, + std::{borrow::Cow, collections::HashMap, convert::TryFrom, sync::Arc}, }; #[derive(Default)] @@ -2017,4 +2036,248 @@ mod tests { } ); } + + #[test] + fn test_rent_state_list_len() { + let mint_keypair = Keypair::new(); + let mut bank = TestCallbacks::default(); + let recipient = Pubkey::new_unique(); + let last_block_hash = Hash::new_unique(); + + let mut system_data = AccountSharedData::default(); + system_data.set_executable(true); + system_data.set_owner(native_loader::id()); + bank.accounts_map + .insert(Pubkey::new_from_array([0u8; 32]), system_data); + + let mut mint_data = AccountSharedData::default(); + mint_data.set_lamports(2); + bank.accounts_map.insert(mint_keypair.pubkey(), mint_data); + + bank.accounts_map + .insert(recipient, AccountSharedData::default()); + + let tx = system_transaction::transfer( + &mint_keypair, + &recipient, + sol_to_lamports(1.), + last_block_hash, + ); + let num_accounts = tx.message().account_keys.len(); + let sanitized_tx = SanitizedTransaction::from_transaction_for_tests(tx); + let mut error_counters = TransactionErrorMetrics::default(); + let loaded_txs = load_accounts( + &bank, + &[sanitized_tx.clone()], + &[(Ok(()), None, Some(0))], + &mut error_counters, + &FeeStructure::default(), + None, + &HashMap::new(), + &LoadedProgramsForTxBatch::default(), + ); + + let compute_budget = ComputeBudget::new(u64::from( + compute_budget_processor::DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT, + )); + let transaction_context = TransactionContext::new( + loaded_txs[0].0.as_ref().unwrap().accounts.clone(), + Rent::default(), + compute_budget.max_invoke_stack_height, + compute_budget.max_instruction_trace_length, + ); + + assert_eq!( + TransactionAccountStateInfo::new( + &Rent::default(), + &transaction_context, + sanitized_tx.message() + ) + .len(), + num_accounts, + ); + } + + #[test] + fn test_load_accounts_success() { + let key1 = Keypair::new(); + let key2 = Keypair::new(); + let key3 = Keypair::new(); + let key4 = Keypair::new(); + + let message = Message { + account_keys: vec![key2.pubkey(), key1.pubkey(), key4.pubkey()], + header: MessageHeader::default(), + instructions: vec![ + CompiledInstruction { + program_id_index: 1, + accounts: vec![0], + data: vec![], + }, + CompiledInstruction { + program_id_index: 1, + accounts: vec![2], + data: vec![], + }, + ], + recent_blockhash: Hash::default(), + }; + + let legacy = LegacyMessage::new(message); + let sanitized_message = SanitizedMessage::Legacy(legacy); + let mut mock_bank = TestCallbacks::default(); + let mut account_data = AccountSharedData::default(); + account_data.set_executable(true); + account_data.set_owner(key3.pubkey()); + mock_bank.accounts_map.insert(key1.pubkey(), account_data); + + let mut account_data = AccountSharedData::default(); + account_data.set_lamports(200); + mock_bank.accounts_map.insert(key2.pubkey(), account_data); + + let mut account_data = AccountSharedData::default(); + account_data.set_executable(true); + account_data.set_owner(native_loader::id()); + mock_bank.accounts_map.insert(key3.pubkey(), account_data); + + let mut error_counter = TransactionErrorMetrics::default(); + let loaded_programs = LoadedProgramsForTxBatch::default(); + + let sanitized_transaction = SanitizedTransaction::new_for_tests( + sanitized_message, + vec![Signature::new_unique()], + false, + ); + let lock_results = + (Ok(()), Some(NoncePartial::default()), Some(20u64)) as TransactionCheckResult; + + let results = load_accounts( + &mock_bank, + &[sanitized_transaction], + &[lock_results], + &mut error_counter, + &FeeStructure::default(), + None, + &HashMap::new(), + &loaded_programs, + ); + + let mut account_data = AccountSharedData::default(); + account_data.set_rent_epoch(RENT_EXEMPT_RENT_EPOCH); + + assert_eq!(results.len(), 1); + let (loaded_result, nonce) = results[0].clone(); + assert_eq!( + loaded_result.unwrap(), + LoadedTransaction { + accounts: vec![ + ( + key2.pubkey(), + mock_bank.accounts_map[&key2.pubkey()].clone() + ), + ( + key1.pubkey(), + mock_bank.accounts_map[&key1.pubkey()].clone() + ), + (key4.pubkey(), account_data), + ( + key3.pubkey(), + mock_bank.accounts_map[&key3.pubkey()].clone() + ), + ], + program_indices: vec![vec![3, 1], vec![3, 1]], + rent: 0, + rent_debits: RentDebits::default() + } + ); + + assert_eq!( + nonce.unwrap(), + NonceFull::new( + Pubkey::from([0; 32]), + AccountSharedData::default(), + Some(mock_bank.accounts_map[&key2.pubkey()].clone()) + ) + ); + } + + #[test] + fn test_load_accounts_error() { + let mock_bank = TestCallbacks::default(); + let message = Message { + account_keys: vec![Pubkey::new_from_array([0; 32])], + header: MessageHeader::default(), + instructions: vec![CompiledInstruction { + program_id_index: 0, + accounts: vec![], + data: vec![], + }], + recent_blockhash: Hash::default(), + }; + + let legacy = LegacyMessage::new(message); + let sanitized_message = SanitizedMessage::Legacy(legacy); + let sanitized_transaction = SanitizedTransaction::new_for_tests( + sanitized_message, + vec![Signature::new_unique()], + false, + ); + + let lock_results = (Ok(()), Some(NoncePartial::default()), None) as TransactionCheckResult; + let fee_structure = FeeStructure::default(); + + let result = load_accounts( + &mock_bank, + &[sanitized_transaction.clone()], + &[lock_results], + &mut TransactionErrorMetrics::default(), + &fee_structure, + None, + &HashMap::new(), + &LoadedProgramsForTxBatch::default(), + ); + + assert_eq!( + result, + vec![(Err(TransactionError::BlockhashNotFound), None)] + ); + + let lock_results = + (Ok(()), Some(NoncePartial::default()), Some(20u64)) as TransactionCheckResult; + + let result = load_accounts( + &mock_bank, + &[sanitized_transaction.clone()], + &[lock_results.clone()], + &mut TransactionErrorMetrics::default(), + &fee_structure, + None, + &HashMap::new(), + &LoadedProgramsForTxBatch::default(), + ); + + assert_eq!(result, vec![(Err(TransactionError::AccountNotFound), None)]); + + let lock_results = ( + Err(TransactionError::InvalidWritableAccount), + Some(NoncePartial::default()), + Some(20u64), + ) as TransactionCheckResult; + + let result = load_accounts( + &mock_bank, + &[sanitized_transaction.clone()], + &[lock_results], + &mut TransactionErrorMetrics::default(), + &fee_structure, + None, + &HashMap::new(), + &LoadedProgramsForTxBatch::default(), + ); + + assert_eq!( + result, + vec![(Err(TransactionError::InvalidWritableAccount), None)] + ); + } } diff --git a/svm/src/account_overrides.rs b/svm/src/account_overrides.rs index c88d77d54f30a9..8a205a798f66b1 100644 --- a/svm/src/account_overrides.rs +++ b/svm/src/account_overrides.rs @@ -10,6 +10,7 @@ pub struct AccountOverrides { } impl AccountOverrides { + /// Insert or remove an account with a given pubkey to/from the list of overrides. pub fn set_account(&mut self, pubkey: &Pubkey, account: Option) { match account { Some(account) => self.accounts.insert(*pubkey, account), diff --git a/svm/src/account_rent_state.rs b/svm/src/account_rent_state.rs index 6fae6e9033bd39..7e3501d0d6c649 100644 --- a/svm/src/account_rent_state.rs +++ b/svm/src/account_rent_state.rs @@ -23,6 +23,7 @@ pub enum RentState { } impl RentState { + /// Return a new RentState instance for a given account and rent. pub fn from_account(account: &AccountSharedData, rent: &Rent) -> Self { if account.lamports() == 0 { Self::Uninitialized @@ -36,6 +37,8 @@ impl RentState { } } + /// Check whether a transition from the pre_rent_state to this + /// state is valid. pub fn transition_allowed_from(&self, pre_rent_state: &RentState) -> bool { match self { Self::Uninitialized | Self::RentExempt => true, @@ -57,21 +60,6 @@ impl RentState { } } - fn submit_rent_state_metrics(pre_rent_state: &Self, post_rent_state: &Self) { - match (pre_rent_state, post_rent_state) { - (&RentState::Uninitialized, &RentState::RentPaying { .. }) => { - inc_new_counter_info!("rent_paying_err-new_account", 1); - } - (&RentState::RentPaying { .. }, &RentState::RentPaying { .. }) => { - inc_new_counter_info!("rent_paying_ok-legacy", 1); - } - (_, &RentState::RentPaying { .. }) => { - inc_new_counter_info!("rent_paying_err-other", 1); - } - _ => {} - } - } - pub(crate) fn check_rent_state( pre_rent_state: Option<&Self>, post_rent_state: Option<&Self>, @@ -118,6 +106,21 @@ impl RentState { Ok(()) } } + + fn submit_rent_state_metrics(pre_rent_state: &Self, post_rent_state: &Self) { + match (pre_rent_state, post_rent_state) { + (&RentState::Uninitialized, &RentState::RentPaying { .. }) => { + inc_new_counter_info!("rent_paying_err-new_account", 1); + } + (&RentState::RentPaying { .. }, &RentState::RentPaying { .. }) => { + inc_new_counter_info!("rent_paying_ok-legacy", 1); + } + (_, &RentState::RentPaying { .. }) => { + inc_new_counter_info!("rent_paying_err-other", 1); + } + _ => {} + } + } } #[cfg(test)] diff --git a/svm/src/transaction_account_state_info.rs b/svm/src/transaction_account_state_info.rs index ff5b93f6a6c459..0631050fe0e765 100644 --- a/svm/src/transaction_account_state_info.rs +++ b/svm/src/transaction_account_state_info.rs @@ -11,12 +11,12 @@ use { }; #[derive(PartialEq, Debug)] -pub struct TransactionAccountStateInfo { +pub(crate) struct TransactionAccountStateInfo { rent_state: Option, // None: readonly account } impl TransactionAccountStateInfo { - pub fn new( + pub(crate) fn new( rent: &Rent, transaction_context: &TransactionContext, message: &SanitizedMessage, diff --git a/svm/src/transaction_processor.rs b/svm/src/transaction_processor.rs index d90afb0a428ea3..fec908619f14f8 100644 --- a/svm/src/transaction_processor.rs +++ b/svm/src/transaction_processor.rs @@ -195,6 +195,7 @@ impl TransactionBatchProcessor { } } + /// Main entrypoint to the SVM. #[allow(clippy::too_many_arguments)] pub fn load_and_execute_sanitized_transactions<'a, CB: TransactionProcessingCallback>( &self, @@ -377,6 +378,112 @@ impl TransactionBatchProcessor { result } + /// Load program with a specific pubkey from loaded programs + /// cache, and update the program's access slot as a side-effect. + pub fn load_program_with_pubkey( + &self, + callbacks: &CB, + pubkey: &Pubkey, + reload: bool, + effective_epoch: Epoch, + ) -> Arc { + let loaded_programs_cache = self.loaded_programs_cache.read().unwrap(); + let environments = loaded_programs_cache.get_environments_for_epoch(effective_epoch); + let mut load_program_metrics = LoadProgramMetrics { + program_id: pubkey.to_string(), + ..LoadProgramMetrics::default() + }; + + let mut loaded_program = + match self.load_program_accounts(callbacks, pubkey, environments) { + ProgramAccountLoadResult::AccountNotFound => Ok(LoadedProgram::new_tombstone( + self.slot, + LoadedProgramType::Closed, + )), + + ProgramAccountLoadResult::InvalidAccountData(env) => Err((self.slot, env)), + + ProgramAccountLoadResult::ProgramOfLoaderV1orV2(program_account) => { + Self::load_program_from_bytes( + &mut load_program_metrics, + program_account.data(), + program_account.owner(), + program_account.data().len(), + 0, + environments.program_runtime_v1.clone(), + reload, + ) + .map_err(|_| (0, environments.program_runtime_v1.clone())) + } + + ProgramAccountLoadResult::ProgramOfLoaderV3( + program_account, + programdata_account, + slot, + ) => programdata_account + .data() + .get(UpgradeableLoaderState::size_of_programdata_metadata()..) + .ok_or(Box::new(InstructionError::InvalidAccountData).into()) + .and_then(|programdata| { + Self::load_program_from_bytes( + &mut load_program_metrics, + programdata, + program_account.owner(), + program_account + .data() + .len() + .saturating_add(programdata_account.data().len()), + slot, + environments.program_runtime_v1.clone(), + reload, + ) + }) + .map_err(|_| (slot, environments.program_runtime_v1.clone())), + + ProgramAccountLoadResult::ProgramOfLoaderV4(program_account, slot) => { + program_account + .data() + .get(LoaderV4State::program_data_offset()..) + .ok_or(Box::new(InstructionError::InvalidAccountData).into()) + .and_then(|elf_bytes| { + Self::load_program_from_bytes( + &mut load_program_metrics, + elf_bytes, + &loader_v4::id(), + program_account.data().len(), + slot, + environments.program_runtime_v2.clone(), + reload, + ) + }) + .map_err(|_| (slot, environments.program_runtime_v2.clone())) + } + } + .unwrap_or_else(|(slot, env)| { + LoadedProgram::new_tombstone(slot, LoadedProgramType::FailedVerification(env)) + }); + + let mut timings = ExecuteDetailsTimings::default(); + load_program_metrics.submit_datapoint(&mut timings); + if !Arc::ptr_eq( + &environments.program_runtime_v1, + &loaded_programs_cache.environments.program_runtime_v1, + ) || !Arc::ptr_eq( + &environments.program_runtime_v2, + &loaded_programs_cache.environments.program_runtime_v2, + ) { + // There can be two entries per program when the environment changes. + // One for the old environment before the epoch boundary and one for the new environment after the epoch boundary. + // These two entries have the same deployment slot, so they must differ in their effective slot instead. + // This is done by setting the effective slot of the entry for the new environment to the epoch boundary. + loaded_program.effective_slot = loaded_program + .effective_slot + .max(self.epoch_schedule.get_first_slot_in_epoch(effective_epoch)); + } + loaded_program.update_access_slot(self.slot); + Arc::new(loaded_program) + } + fn replenish_program_cache( &self, callback: &CB, @@ -454,7 +561,7 @@ impl TransactionBatchProcessor { if let Some((key, count)) = program_to_load { // Load, verify and compile one program. - let program = self.load_program(callback, &key, false, self.epoch); + let program = self.load_program_with_pubkey(callback, &key, false, self.epoch); program.tx_usage_counter.store(count, Ordering::Relaxed); program_to_store = Some((key, program)); } else if missing_programs.is_empty() { @@ -683,110 +790,6 @@ impl TransactionBatchProcessor { } } - pub fn load_program( - &self, - callbacks: &CB, - pubkey: &Pubkey, - reload: bool, - effective_epoch: Epoch, - ) -> Arc { - let loaded_programs_cache = self.loaded_programs_cache.read().unwrap(); - let environments = loaded_programs_cache.get_environments_for_epoch(effective_epoch); - let mut load_program_metrics = LoadProgramMetrics { - program_id: pubkey.to_string(), - ..LoadProgramMetrics::default() - }; - - let mut loaded_program = - match self.load_program_accounts(callbacks, pubkey, environments) { - ProgramAccountLoadResult::AccountNotFound => Ok(LoadedProgram::new_tombstone( - self.slot, - LoadedProgramType::Closed, - )), - - ProgramAccountLoadResult::InvalidAccountData(env) => Err((self.slot, env)), - - ProgramAccountLoadResult::ProgramOfLoaderV1orV2(program_account) => { - Self::load_program_from_bytes( - &mut load_program_metrics, - program_account.data(), - program_account.owner(), - program_account.data().len(), - 0, - environments.program_runtime_v1.clone(), - reload, - ) - .map_err(|_| (0, environments.program_runtime_v1.clone())) - } - - ProgramAccountLoadResult::ProgramOfLoaderV3( - program_account, - programdata_account, - slot, - ) => programdata_account - .data() - .get(UpgradeableLoaderState::size_of_programdata_metadata()..) - .ok_or(Box::new(InstructionError::InvalidAccountData).into()) - .and_then(|programdata| { - Self::load_program_from_bytes( - &mut load_program_metrics, - programdata, - program_account.owner(), - program_account - .data() - .len() - .saturating_add(programdata_account.data().len()), - slot, - environments.program_runtime_v1.clone(), - reload, - ) - }) - .map_err(|_| (slot, environments.program_runtime_v1.clone())), - - ProgramAccountLoadResult::ProgramOfLoaderV4(program_account, slot) => { - program_account - .data() - .get(LoaderV4State::program_data_offset()..) - .ok_or(Box::new(InstructionError::InvalidAccountData).into()) - .and_then(|elf_bytes| { - Self::load_program_from_bytes( - &mut load_program_metrics, - elf_bytes, - &loader_v4::id(), - program_account.data().len(), - slot, - environments.program_runtime_v2.clone(), - reload, - ) - }) - .map_err(|_| (slot, environments.program_runtime_v2.clone())) - } - } - .unwrap_or_else(|(slot, env)| { - LoadedProgram::new_tombstone(slot, LoadedProgramType::FailedVerification(env)) - }); - - let mut timings = ExecuteDetailsTimings::default(); - load_program_metrics.submit_datapoint(&mut timings); - if !Arc::ptr_eq( - &environments.program_runtime_v1, - &loaded_programs_cache.environments.program_runtime_v1, - ) || !Arc::ptr_eq( - &environments.program_runtime_v2, - &loaded_programs_cache.environments.program_runtime_v2, - ) { - // There can be two entries per program when the environment changes. - // One for the old environment before the epoch boundary and one for the new environment after the epoch boundary. - // These two entries have the same deployment slot, so they must differ in their effective slot instead. - // This is done by setting the effective slot of the entry for the new environment to the epoch boundary. - loaded_program.effective_slot = loaded_program - .effective_slot - .max(self.epoch_schedule.get_first_slot_in_epoch(effective_epoch)); - } - loaded_program.update_access_slot(self.slot); - Arc::new(loaded_program) - } - fn load_program_from_bytes( load_program_metrics: &mut LoadProgramMetrics, programdata: &[u8], @@ -1242,7 +1245,7 @@ mod tests { let key = Pubkey::new_unique(); let batch_processor = TransactionBatchProcessor::::default(); - let result = batch_processor.load_program(&mock_bank, &key, false, 50); + let result = batch_processor.load_program_with_pubkey(&mock_bank, &key, false, 50); let loaded_program = LoadedProgram::new_tombstone(0, LoadedProgramType::Closed); assert_eq!(result, Arc::new(loaded_program)); @@ -1259,7 +1262,7 @@ mod tests { .account_shared_data .insert(key, account_data.clone()); - let result = batch_processor.load_program(&mock_bank, &key, false, 20); + let result = batch_processor.load_program_with_pubkey(&mock_bank, &key, false, 20); let loaded_program = LoadedProgram::new_tombstone( 0, @@ -1288,7 +1291,7 @@ mod tests { .insert(key, account_data.clone()); // This should return an error - let result = batch_processor.load_program(&mock_bank, &key, false, 20); + let result = batch_processor.load_program_with_pubkey(&mock_bank, &key, false, 20); let loaded_program = LoadedProgram::new_tombstone( 0, LoadedProgramType::FailedVerification( @@ -1316,7 +1319,7 @@ mod tests { .account_shared_data .insert(key, account_data.clone()); - let result = batch_processor.load_program(&mock_bank, &key, false, 20); + let result = batch_processor.load_program_with_pubkey(&mock_bank, &key, false, 20); let environments = ProgramRuntimeEnvironments::default(); let expected = TransactionBatchProcessor::::load_program_from_bytes( @@ -1361,7 +1364,7 @@ mod tests { .insert(key2, account_data2.clone()); // This should return an error - let result = batch_processor.load_program(&mock_bank, &key1, false, 0); + let result = batch_processor.load_program_with_pubkey(&mock_bank, &key1, false, 0); let loaded_program = LoadedProgram::new_tombstone( 0, LoadedProgramType::FailedVerification( @@ -1399,7 +1402,7 @@ mod tests { .account_shared_data .insert(key2, account_data.clone()); - let result = batch_processor.load_program(&mock_bank, &key1, false, 20); + let result = batch_processor.load_program_with_pubkey(&mock_bank, &key1, false, 20); let data = account_data.data(); account_data @@ -1441,7 +1444,7 @@ mod tests { .account_shared_data .insert(key, account_data.clone()); - let result = batch_processor.load_program(&mock_bank, &key, false, 0); + let result = batch_processor.load_program_with_pubkey(&mock_bank, &key, false, 0); let loaded_program = LoadedProgram::new_tombstone( 0, LoadedProgramType::FailedVerification( @@ -1475,7 +1478,7 @@ mod tests { .account_shared_data .insert(key, account_data.clone()); - let result = batch_processor.load_program(&mock_bank, &key, false, 20); + let result = batch_processor.load_program_with_pubkey(&mock_bank, &key, false, 20); let data = account_data.data()[LoaderV4State::program_data_offset()..].to_vec(); account_data.set_data(data); @@ -1513,7 +1516,7 @@ mod tests { .account_shared_data .insert(key, account_data.clone()); - let result = batch_processor.load_program(&mock_bank, &key, false, 20); + let result = batch_processor.load_program_with_pubkey(&mock_bank, &key, false, 20); let slot = batch_processor.epoch_schedule.get_first_slot_in_epoch(20); assert_eq!(result.effective_slot, slot); diff --git a/svm/tests/account_loader.rs b/svm/tests/account_loader.rs deleted file mode 100644 index dd4cd046046399..00000000000000 --- a/svm/tests/account_loader.rs +++ /dev/null @@ -1,214 +0,0 @@ -use { - crate::mock_bank::MockBankCallback, - solana_program_runtime::loaded_programs::LoadedProgramsForTxBatch, - solana_sdk::{ - account::{AccountSharedData, WritableAccount}, - fee::FeeStructure, - hash::Hash, - instruction::CompiledInstruction, - message::{LegacyMessage, Message, MessageHeader, SanitizedMessage}, - native_loader, - nonce_info::{NonceFull, NoncePartial}, - pubkey::Pubkey, - rent_collector::RENT_EXEMPT_RENT_EPOCH, - rent_debits::RentDebits, - signature::{Keypair, Signature, Signer}, - transaction::{SanitizedTransaction, TransactionError}, - }, - solana_svm::{ - account_loader::{load_accounts, LoadedTransaction, TransactionCheckResult}, - transaction_error_metrics::TransactionErrorMetrics, - }, - std::collections::HashMap, -}; - -mod mock_bank; - -#[test] -fn test_load_accounts_success() { - let key1 = Keypair::new(); - let key2 = Keypair::new(); - let key3 = Keypair::new(); - let key4 = Keypair::new(); - - let message = Message { - account_keys: vec![key2.pubkey(), key1.pubkey(), key4.pubkey()], - header: MessageHeader::default(), - instructions: vec![ - CompiledInstruction { - program_id_index: 1, - accounts: vec![0], - data: vec![], - }, - CompiledInstruction { - program_id_index: 1, - accounts: vec![2], - data: vec![], - }, - ], - recent_blockhash: Hash::default(), - }; - - let legacy = LegacyMessage::new(message); - let sanitized_message = SanitizedMessage::Legacy(legacy); - let mut mock_bank = MockBankCallback::default(); - let mut account_data = AccountSharedData::default(); - account_data.set_executable(true); - account_data.set_owner(key3.pubkey()); - mock_bank - .account_shared_data - .insert(key1.pubkey(), account_data); - - let mut account_data = AccountSharedData::default(); - account_data.set_lamports(200); - mock_bank - .account_shared_data - .insert(key2.pubkey(), account_data); - - let mut account_data = AccountSharedData::default(); - account_data.set_executable(true); - account_data.set_owner(native_loader::id()); - mock_bank - .account_shared_data - .insert(key3.pubkey(), account_data); - - let mut error_counter = TransactionErrorMetrics::default(); - let loaded_programs = LoadedProgramsForTxBatch::default(); - - let sanitized_transaction = SanitizedTransaction::new_for_tests( - sanitized_message, - vec![Signature::new_unique()], - false, - ); - let lock_results = - (Ok(()), Some(NoncePartial::default()), Some(20u64)) as TransactionCheckResult; - - let results = load_accounts( - &mock_bank, - &[sanitized_transaction], - &[lock_results], - &mut error_counter, - &FeeStructure::default(), - None, - &HashMap::new(), - &loaded_programs, - ); - - let mut account_data = AccountSharedData::default(); - account_data.set_rent_epoch(RENT_EXEMPT_RENT_EPOCH); - - assert_eq!(results.len(), 1); - let (loaded_result, nonce) = results[0].clone(); - assert_eq!( - loaded_result.unwrap(), - LoadedTransaction { - accounts: vec![ - ( - key2.pubkey(), - mock_bank.account_shared_data[&key2.pubkey()].clone() - ), - ( - key1.pubkey(), - mock_bank.account_shared_data[&key1.pubkey()].clone() - ), - (key4.pubkey(), account_data), - ( - key3.pubkey(), - mock_bank.account_shared_data[&key3.pubkey()].clone() - ), - ], - program_indices: vec![vec![3, 1], vec![3, 1]], - rent: 0, - rent_debits: RentDebits::default() - } - ); - - assert_eq!( - nonce.unwrap(), - NonceFull::new( - Pubkey::from([0; 32]), - AccountSharedData::default(), - Some(mock_bank.account_shared_data[&key2.pubkey()].clone()) - ) - ); -} - -#[test] -fn test_load_accounts_error() { - let mock_bank = MockBankCallback::default(); - let message = Message { - account_keys: vec![Pubkey::new_from_array([0; 32])], - header: MessageHeader::default(), - instructions: vec![CompiledInstruction { - program_id_index: 0, - accounts: vec![], - data: vec![], - }], - recent_blockhash: Hash::default(), - }; - - let legacy = LegacyMessage::new(message); - let sanitized_message = SanitizedMessage::Legacy(legacy); - let sanitized_transaction = SanitizedTransaction::new_for_tests( - sanitized_message, - vec![Signature::new_unique()], - false, - ); - - let lock_results = (Ok(()), Some(NoncePartial::default()), None) as TransactionCheckResult; - let fee_structure = FeeStructure::default(); - - let result = load_accounts( - &mock_bank, - &[sanitized_transaction.clone()], - &[lock_results], - &mut TransactionErrorMetrics::default(), - &fee_structure, - None, - &HashMap::new(), - &LoadedProgramsForTxBatch::default(), - ); - - assert_eq!( - result, - vec![(Err(TransactionError::BlockhashNotFound), None)] - ); - - let lock_results = - (Ok(()), Some(NoncePartial::default()), Some(20u64)) as TransactionCheckResult; - - let result = load_accounts( - &mock_bank, - &[sanitized_transaction.clone()], - &[lock_results.clone()], - &mut TransactionErrorMetrics::default(), - &fee_structure, - None, - &HashMap::new(), - &LoadedProgramsForTxBatch::default(), - ); - - assert_eq!(result, vec![(Err(TransactionError::AccountNotFound), None)]); - - let lock_results = ( - Err(TransactionError::InvalidWritableAccount), - Some(NoncePartial::default()), - Some(20u64), - ) as TransactionCheckResult; - - let result = load_accounts( - &mock_bank, - &[sanitized_transaction.clone()], - &[lock_results], - &mut TransactionErrorMetrics::default(), - &fee_structure, - None, - &HashMap::new(), - &LoadedProgramsForTxBatch::default(), - ); - - assert_eq!( - result, - vec![(Err(TransactionError::InvalidWritableAccount), None)] - ); -} diff --git a/svm/tests/rent_state.rs b/svm/tests/rent_state.rs deleted file mode 100644 index f3ea728f6b874f..00000000000000 --- a/svm/tests/rent_state.rs +++ /dev/null @@ -1,90 +0,0 @@ -#![cfg(test)] - -use { - solana_program_runtime::{ - compute_budget::ComputeBudget, compute_budget_processor, - loaded_programs::LoadedProgramsForTxBatch, - }, - solana_sdk::{ - account::{AccountSharedData, WritableAccount}, - fee::FeeStructure, - hash::Hash, - native_loader, - native_token::sol_to_lamports, - pubkey::Pubkey, - rent::Rent, - signature::{Keypair, Signer}, - system_transaction, - transaction::SanitizedTransaction, - transaction_context::TransactionContext, - }, - solana_svm::{ - account_loader::load_accounts, transaction_account_state_info::TransactionAccountStateInfo, - transaction_error_metrics::TransactionErrorMetrics, - }, - std::collections::HashMap, -}; - -mod mock_bank; - -#[test] -fn test_rent_state_list_len() { - let mint_keypair = Keypair::new(); - let mut bank = mock_bank::MockBankCallback::default(); - let recipient = Pubkey::new_unique(); - let last_block_hash = Hash::new_unique(); - - let mut system_data = AccountSharedData::default(); - system_data.set_executable(true); - system_data.set_owner(native_loader::id()); - bank.account_shared_data - .insert(Pubkey::new_from_array([0u8; 32]), system_data); - - let mut mint_data = AccountSharedData::default(); - mint_data.set_lamports(2); - bank.account_shared_data - .insert(mint_keypair.pubkey(), mint_data); - - bank.account_shared_data - .insert(recipient, AccountSharedData::default()); - - let tx = system_transaction::transfer( - &mint_keypair, - &recipient, - sol_to_lamports(1.), - last_block_hash, - ); - let num_accounts = tx.message().account_keys.len(); - let sanitized_tx = SanitizedTransaction::from_transaction_for_tests(tx); - let mut error_counters = TransactionErrorMetrics::default(); - let loaded_txs = load_accounts( - &bank, - &[sanitized_tx.clone()], - &[(Ok(()), None, Some(0))], - &mut error_counters, - &FeeStructure::default(), - None, - &HashMap::new(), - &LoadedProgramsForTxBatch::default(), - ); - - let compute_budget = ComputeBudget::new(u64::from( - compute_budget_processor::DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT, - )); - let transaction_context = TransactionContext::new( - loaded_txs[0].0.as_ref().unwrap().accounts.clone(), - Rent::default(), - compute_budget.max_invoke_stack_height, - compute_budget.max_instruction_trace_length, - ); - - assert_eq!( - TransactionAccountStateInfo::new( - &Rent::default(), - &transaction_context, - sanitized_tx.message() - ) - .len(), - num_accounts, - ); -}