From 124383024a3c123d78f8c552dc7fae0399758cab Mon Sep 17 00:00:00 2001 From: hanako mumei <81144685+2501babe@users.noreply.github.com> Date: Tue, 15 Oct 2024 09:41:39 -0700 Subject: [PATCH 1/3] svm: improve integration test framework for SIMD83 --- .../write-to-account/Cargo.toml | 12 ++ .../write-to-account/src/lib.rs | 62 ++++++ .../write_to_account_program.so | Bin 0 -> 21992 bytes svm/tests/integration_test.rs | 179 ++++++++++++++++-- svm/tests/mock_bank.rs | 20 +- 5 files changed, 252 insertions(+), 21 deletions(-) create mode 100644 svm/tests/example-programs/write-to-account/Cargo.toml create mode 100644 svm/tests/example-programs/write-to-account/src/lib.rs create mode 100755 svm/tests/example-programs/write-to-account/write_to_account_program.so diff --git a/svm/tests/example-programs/write-to-account/Cargo.toml b/svm/tests/example-programs/write-to-account/Cargo.toml new file mode 100644 index 00000000000000..903be78c584f66 --- /dev/null +++ b/svm/tests/example-programs/write-to-account/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "write-to-account" +version = "2.1.0" +edition = "2021" + +[dependencies] +solana-program = { path = "../../../../sdk/program", version = "=2.1.0" } + +[lib] +crate-type = ["cdylib", "rlib"] + +[workspace] diff --git a/svm/tests/example-programs/write-to-account/src/lib.rs b/svm/tests/example-programs/write-to-account/src/lib.rs new file mode 100644 index 00000000000000..bb7e6065547336 --- /dev/null +++ b/svm/tests/example-programs/write-to-account/src/lib.rs @@ -0,0 +1,62 @@ +use solana_program::{ + account_info::{next_account_info, AccountInfo}, + entrypoint, + entrypoint::ProgramResult, + incinerator, msg, + program_error::ProgramError, + pubkey::Pubkey, +}; + +entrypoint!(process_instruction); + +fn process_instruction( + _program_id: &Pubkey, + accounts: &[AccountInfo], + data: &[u8], +) -> ProgramResult { + let accounts_iter = &mut accounts.iter(); + let target_account_info = next_account_info(accounts_iter)?; + match data[0] { + // print account size + 0 => { + msg!( + "account size {}", + target_account_info.try_borrow_data()?.len() + ); + } + // set account data + 1 => { + let mut account_data = target_account_info.try_borrow_mut_data()?; + account_data[0] = 100; + } + // deallocate account + 2 => { + let incinerator_info = next_account_info(accounts_iter)?; + if !incinerator::check_id(incinerator_info.key) { + return Err(ProgramError::InvalidAccountData); + } + + let mut target_lamports = target_account_info.try_borrow_mut_lamports()?; + let mut incinerator_lamports = incinerator_info.try_borrow_mut_lamports()?; + + **incinerator_lamports = incinerator_lamports + .checked_add(**target_lamports) + .ok_or(ProgramError::ArithmeticOverflow)?; + + **target_lamports = target_lamports + .checked_sub(**target_lamports) + .ok_or(ProgramError::InsufficientFunds)?; + } + // reallocate account + 3 => { + let new_size = usize::from_le_bytes(data[1..9].try_into().unwrap()); + target_account_info.realloc(new_size, false)?; + } + // bad ixn + _ => { + return Err(ProgramError::InvalidArgument); + } + } + + Ok(()) +} diff --git a/svm/tests/example-programs/write-to-account/write_to_account_program.so b/svm/tests/example-programs/write-to-account/write_to_account_program.so new file mode 100755 index 0000000000000000000000000000000000000000..e43642631bc6880b32bbd7d9facef6977f69eb84 GIT binary patch literal 21992 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 literal 0 HcmV?d00001 diff --git a/svm/tests/integration_test.rs b/svm/tests/integration_test.rs index 9f781607aa3112..fdd160b853441e 100644 --- a/svm/tests/integration_test.rs +++ b/svm/tests/integration_test.rs @@ -1,4 +1,5 @@ #![cfg(test)] +#![allow(clippy::arithmetic_side_effects)] use { crate::mock_bank::{ @@ -9,6 +10,7 @@ use { solana_sdk::{ account::{AccountSharedData, ReadableAccount, WritableAccount}, clock::Slot, + compute_budget::ComputeBudgetInstruction, feature_set::{self, FeatureSet}, hash::Hash, instruction::{AccountMeta, Instruction}, @@ -27,7 +29,7 @@ use { nonce_info::NonceInfo, rollback_accounts::RollbackAccounts, transaction_execution_result::TransactionExecutionDetails, - transaction_processing_result::ProcessedTransaction, + transaction_processing_result::{ProcessedTransaction, TransactionProcessingResult}, transaction_processor::{ ExecutionRecordingConfig, TransactionBatchProcessor, TransactionProcessingConfig, TransactionProcessingEnvironment, @@ -49,7 +51,7 @@ const LAST_BLOCKHASH: Hash = Hash::new_from_array([7; 32]); // Arbitrary constan pub type AccountsMap = HashMap; // container for a transaction batch and all data needed to run and verify it against svm -#[derive(Debug, Default)] +#[derive(Clone, Debug, Default)] pub struct SvmTestEntry { // features are disabled by default; these will be enabled pub enabled_features: Vec, @@ -156,12 +158,14 @@ impl SvmTestEntry { mut nonce_info: NonceInfo, status: ExecutionStatus, ) { - nonce_info - .try_advance_nonce( - DurableNonce::from_blockhash(&LAST_BLOCKHASH), - LAMPORTS_PER_SIGNATURE, - ) - .unwrap(); + if status != ExecutionStatus::Discarded { + nonce_info + .try_advance_nonce( + DurableNonce::from_blockhash(&LAST_BLOCKHASH), + LAMPORTS_PER_SIGNATURE, + ) + .unwrap(); + } self.transaction_batch.push(TransactionBatchItem { transaction, @@ -321,6 +325,22 @@ impl ExecutionStatus { } } +impl From<&TransactionProcessingResult> for ExecutionStatus { + fn from(processing_result: &TransactionProcessingResult) -> Self { + match processing_result { + Ok(ProcessedTransaction::Executed(executed_transaction)) => { + if executed_transaction.execution_details.status.is_ok() { + ExecutionStatus::Succeeded + } else { + ExecutionStatus::ExecutedFailed + } + } + Ok(ProcessedTransaction::FeesOnly(_)) => ExecutionStatus::ProcessedFailed, + Err(_) => ExecutionStatus::Discarded, + } + } +} + #[derive(Clone, Debug, Default, PartialEq, Eq)] pub enum ReturnDataAssert { Some(TransactionReturnData), @@ -666,7 +686,11 @@ fn simple_nonce(enable_fee_only_transactions: bool, fee_paying_nonce: bool) -> V // * true/false: normal nonce account used to pay fees with rent minimum plus 1sol // * false/true: normal nonce account with rent minimum, fee payer doesnt exist // * true/true: same account for both which does not exist - let mk_nonce_transaction = |test_entry: &mut SvmTestEntry, program_id, fake_fee_payer: bool| { + // we also provide a side door to bring a fee-paying nonce account below rent-exemption + let mk_nonce_transaction = |test_entry: &mut SvmTestEntry, + program_id, + fake_fee_payer: bool, + rent_paying_nonce: bool| { let fee_payer_keypair = Keypair::new(); let fee_payer = fee_payer_keypair.pubkey(); let nonce_pubkey = if fee_paying_nonce { @@ -682,8 +706,12 @@ fn simple_nonce(enable_fee_only_transactions: bool, fee_paying_nonce: bool) -> V let mut fee_payer_data = AccountSharedData::default(); fee_payer_data.set_lamports(LAMPORTS_PER_SOL); test_entry.add_initial_account(fee_payer, &fee_payer_data); + } else if rent_paying_nonce { + assert!(fee_paying_nonce); + nonce_balance += LAMPORTS_PER_SIGNATURE; + nonce_balance -= 1; } else if fee_paying_nonce { - nonce_balance = nonce_balance.saturating_add(LAMPORTS_PER_SOL); + nonce_balance += LAMPORTS_PER_SOL; } let nonce_initial_hash = DurableNonce::from_blockhash(&Hash::new_unique()); @@ -716,10 +744,11 @@ fn simple_nonce(enable_fee_only_transactions: bool, fee_paying_nonce: bool) -> V (transaction, fee_payer, nonce_info) }; - // successful nonce transaction, regardless of features + // 0: successful nonce transaction, regardless of features { let (transaction, fee_payer, mut nonce_info) = - mk_nonce_transaction(&mut test_entry, real_program_id, false); + mk_nonce_transaction(&mut test_entry, real_program_id, false, false); + test_entry.push_nonce_transaction(transaction, nonce_info.clone()); test_entry.decrease_expected_lamports(&fee_payer, LAMPORTS_PER_SIGNATURE); @@ -739,10 +768,10 @@ fn simple_nonce(enable_fee_only_transactions: bool, fee_paying_nonce: bool) -> V .copy_from_slice(nonce_info.account().data()); } - // non-executing nonce transaction (fee payer doesnt exist) regardless of features + // 1: non-executing nonce transaction (fee payer doesnt exist) regardless of features { let (transaction, _fee_payer, nonce_info) = - mk_nonce_transaction(&mut test_entry, real_program_id, true); + mk_nonce_transaction(&mut test_entry, real_program_id, true, false); test_entry .final_accounts @@ -756,10 +785,11 @@ fn simple_nonce(enable_fee_only_transactions: bool, fee_paying_nonce: bool) -> V ); } - // failing nonce transaction (bad system instruction) regardless of features + // 2: failing nonce transaction (bad system instruction) regardless of features { let (transaction, fee_payer, mut nonce_info) = - mk_nonce_transaction(&mut test_entry, system_program::id(), false); + mk_nonce_transaction(&mut test_entry, system_program::id(), false, false); + test_entry.push_nonce_transaction_with_status( transaction, nonce_info.clone(), @@ -783,11 +813,10 @@ fn simple_nonce(enable_fee_only_transactions: bool, fee_paying_nonce: bool) -> V .copy_from_slice(nonce_info.account().data()); } - // and this (program doesnt exist) will be a non-executing transaction without the feature - // or a fee-only transaction with it. which is identical to failed *except* rent is not updated + // 3: processable non-executable nonce transaction with fee-only enabled, otherwise discarded { let (transaction, fee_payer, mut nonce_info) = - mk_nonce_transaction(&mut test_entry, Pubkey::new_unique(), false); + mk_nonce_transaction(&mut test_entry, Pubkey::new_unique(), false, false); if enable_fee_only_transactions { test_entry.push_nonce_transaction_with_status( @@ -841,9 +870,105 @@ fn simple_nonce(enable_fee_only_transactions: bool, fee_paying_nonce: bool) -> V } } + // 4: safety check that rent-paying nonce fee-payers are verboten (blockhash fee-payers may be below rent-exemption) + // if this situation is ever allowed in the future, the nonce account MUST be hidden for fee-only transactions + // as an aside, nonce accounts closed by WithdrawNonceAccount are safe because they are ordinary executed transactions + // we also dont care whether a non-fee nonce (or any account) pays rent because rent is charged on executed transactions + if fee_paying_nonce { + let (transaction, _, nonce_info) = + mk_nonce_transaction(&mut test_entry, real_program_id, false, true); + + test_entry + .final_accounts + .get_mut(nonce_info.address()) + .unwrap() + .set_rent_epoch(0); + + test_entry.push_nonce_transaction_with_status( + transaction, + nonce_info.clone(), + ExecutionStatus::Discarded, + ); + } + + // 5: rent-paying nonce fee-payers are also not charged for fee-only transactions + if enable_fee_only_transactions && fee_paying_nonce { + let (transaction, _, nonce_info) = + mk_nonce_transaction(&mut test_entry, Pubkey::new_unique(), false, true); + + test_entry + .final_accounts + .get_mut(nonce_info.address()) + .unwrap() + .set_rent_epoch(0); + + test_entry.push_nonce_transaction_with_status( + transaction, + nonce_info.clone(), + ExecutionStatus::Discarded, + ); + } + vec![test_entry] } +#[allow(unused)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum WriteProgramInstruction { + Print, + Set, + Dealloc, + Realloc(usize), +} +impl WriteProgramInstruction { + fn _create_transaction( + self, + program_id: Pubkey, + fee_payer: &Keypair, + target: Pubkey, + clamp_data_size: Option, + ) -> Transaction { + let instruction_data = match self { + Self::Print => vec![0], + Self::Set => vec![1], + Self::Dealloc => vec![2], + Self::Realloc(new_size) => { + let mut vec = vec![3]; + vec.extend_from_slice(&new_size.to_le_bytes()); + vec + } + }; + + let account_metas = match self { + Self::Dealloc => vec![ + AccountMeta::new(target, false), + AccountMeta::new(solana_sdk::incinerator::id(), false), + ], + Self::Print => vec![AccountMeta::new_readonly(target, false)], + _ => vec![AccountMeta::new(target, false)], + }; + + let mut instructions = vec![]; + + if let Some(size) = clamp_data_size { + instructions.push(ComputeBudgetInstruction::set_loaded_accounts_data_size_limit(size)); + } + + instructions.push(Instruction::new_with_bytes( + program_id, + &instruction_data, + account_metas, + )); + + Transaction::new_signed_with_payer( + &instructions, + Some(&fee_payer.pubkey()), + &[fee_payer], + Hash::default(), + ) + } +} + #[test_case(program_medley())] #[test_case(simple_transfer(false))] #[test_case(simple_transfer(true))] @@ -922,7 +1047,7 @@ fn execute_test_entry(test_entry: SvmTestEntry) { ); // build a hashmap of final account states incrementally, starting with all initial states, updating to all final states - // NOTE with SIMD-83 an account may appear multiple times in the same batch + // with SIMD83, an account might change multiple times in the same batch, but it might not exist on all transactions let mut final_accounts_actual = test_entry.initial_accounts.clone(); for (index, processed_transaction) in batch_output.processing_results.iter().enumerate() { @@ -957,6 +1082,20 @@ fn execute_test_entry(test_entry: SvmTestEntry) { } } + // first assert all transaction states together, it makes test-driven development much less of a headache + let (expected_statuses, actual_statuses): (Vec<_>, Vec<_>) = batch_output + .processing_results + .iter() + .zip(test_entry.asserts()) + .map(|(processing_result, test_item_assert)| { + ( + ExecutionStatus::from(processing_result), + test_item_assert.status, + ) + }) + .unzip(); + assert_eq!(expected_statuses, actual_statuses); + // check that all the account states we care about are present and correct for (pubkey, expected_account_data) in test_entry.final_accounts.iter() { let actual_account_data = final_accounts_actual.get(pubkey); diff --git a/svm/tests/mock_bank.rs b/svm/tests/mock_bank.rs index 58a1c155f226a9..b8fe4441124dbe 100644 --- a/svm/tests/mock_bank.rs +++ b/svm/tests/mock_bank.rs @@ -21,7 +21,7 @@ use { account::{AccountSharedData, ReadableAccount, WritableAccount}, bpf_loader_upgradeable::{self, UpgradeableLoaderState}, clock::{Clock, UnixTimestamp}, - native_loader, + compute_budget, native_loader, pubkey::Pubkey, rent::Rent, slot_hashes::Slot, @@ -138,6 +138,11 @@ pub fn program_address(program_name: &str) -> Pubkey { Pubkey::create_with_seed(&Pubkey::default(), program_name, &Pubkey::default()).unwrap() } +#[allow(unused)] +pub fn program_data_size(program_name: &str) -> usize { + load_program(program_name.to_string()).len() +} + #[allow(unused)] pub fn deploy_program(name: String, deployment_slot: Slot, mock_bank: &MockBankCallback) -> Pubkey { deploy_program_with_upgrade_authority(name, deployment_slot, mock_bank, None) @@ -294,6 +299,19 @@ pub fn register_builtins( solana_system_program::system_processor::Entrypoint::vm, ), ); + + // For testing realloc, we need the compute budget program + let compute_budget_program_name = "compute_budget_program"; + batch_processor.add_builtin( + mock_bank, + compute_budget::id(), + compute_budget_program_name, + ProgramCacheEntry::new_builtin( + DEPLOYMENT_SLOT, + compute_budget_program_name.len(), + solana_compute_budget_program::Entrypoint::vm, + ), + ); } #[allow(unused)] From ae6353090b29feafec0c9e38244c6e33d6f6f2ea Mon Sep 17 00:00:00 2001 From: hanako mumei <81144685+2501babe@users.noreply.github.com> Date: Thu, 17 Oct 2024 11:07:26 -0700 Subject: [PATCH 2/3] merge write program match expressions --- svm/tests/integration_test.rs | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/svm/tests/integration_test.rs b/svm/tests/integration_test.rs index fdd160b853441e..4b9b07dbde862b 100644 --- a/svm/tests/integration_test.rs +++ b/svm/tests/integration_test.rs @@ -928,26 +928,23 @@ impl WriteProgramInstruction { target: Pubkey, clamp_data_size: Option, ) -> Transaction { - let instruction_data = match self { - Self::Print => vec![0], - Self::Set => vec![1], - Self::Dealloc => vec![2], + let (instruction_data, account_metas) = match self { + Self::Print => (vec![0], vec![AccountMeta::new_readonly(target, false)]), + Self::Set => (vec![1], vec![AccountMeta::new(target, false)]), + Self::Dealloc => ( + vec![2], + vec![ + AccountMeta::new(target, false), + AccountMeta::new(solana_sdk::incinerator::id(), false), + ], + ), Self::Realloc(new_size) => { - let mut vec = vec![3]; - vec.extend_from_slice(&new_size.to_le_bytes()); - vec + let mut instruction_data = vec![3]; + instruction_data.extend_from_slice(&new_size.to_le_bytes()); + (instruction_data, vec![AccountMeta::new(target, false)]) } }; - let account_metas = match self { - Self::Dealloc => vec![ - AccountMeta::new(target, false), - AccountMeta::new(solana_sdk::incinerator::id(), false), - ], - Self::Print => vec![AccountMeta::new_readonly(target, false)], - _ => vec![AccountMeta::new(target, false)], - }; - let mut instructions = vec![]; if let Some(size) = clamp_data_size { From 3d92aa0133208cf32eb5bf761b2cae4f1d06032a Mon Sep 17 00:00:00 2001 From: hanako mumei <81144685+2501babe@users.noreply.github.com> Date: Thu, 17 Oct 2024 11:08:52 -0700 Subject: [PATCH 3/3] clarify nonce comment --- svm/tests/integration_test.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/svm/tests/integration_test.rs b/svm/tests/integration_test.rs index 4b9b07dbde862b..8ebe2342d0e398 100644 --- a/svm/tests/integration_test.rs +++ b/svm/tests/integration_test.rs @@ -870,7 +870,7 @@ fn simple_nonce(enable_fee_only_transactions: bool, fee_paying_nonce: bool) -> V } } - // 4: safety check that rent-paying nonce fee-payers are verboten (blockhash fee-payers may be below rent-exemption) + // 4: safety check that nonce fee-payers are required to be rent-exempt (blockhash fee-payers may be below rent-exemption) // if this situation is ever allowed in the future, the nonce account MUST be hidden for fee-only transactions // as an aside, nonce accounts closed by WithdrawNonceAccount are safe because they are ordinary executed transactions // we also dont care whether a non-fee nonce (or any account) pays rent because rent is charged on executed transactions