From b7afc847d488e2f020e68ee77de2bb61328d11a9 Mon Sep 17 00:00:00 2001 From: hanako mumei <81144685+2501babe@users.noreply.github.com> Date: Fri, 4 Oct 2024 02:29:13 -0700 Subject: [PATCH] realloc tests --- svm/tests/integration_test.rs | 221 ++++++++++++++++++++++++++++------ svm/tests/mock_bank.rs | 20 ++- 2 files changed, 206 insertions(+), 35 deletions(-) diff --git a/svm/tests/integration_test.rs b/svm/tests/integration_test.rs index d242b779a669c4..f2532f6f2fc8a9 100644 --- a/svm/tests/integration_test.rs +++ b/svm/tests/integration_test.rs @@ -3,12 +3,14 @@ use { crate::mock_bank::{ - create_executable_environment, deploy_program, program_address, register_builtins, - MockBankCallback, MockForkGraph, WALLCLOCK_TIME, + create_executable_environment, deploy_program, program_address, program_data_size, + register_builtins, MockBankCallback, MockForkGraph, WALLCLOCK_TIME, }, solana_sdk::{ account::{AccountSharedData, ReadableAccount, WritableAccount}, clock::Slot, + compute_budget::ComputeBudgetInstruction, + entrypoint::MAX_PERMITTED_DATA_INCREASE, feature_set::{self, FeatureSet}, hash::Hash, instruction::{AccountMeta, Instruction}, @@ -1240,6 +1242,8 @@ fn nonce_reuse(enable_fee_only_transactions: bool, fee_paying_nonce: bool) -> Ve common_test_entry.decrease_expected_lamports(&fee_payer, LAMPORTS_PER_SIGNATURE); + let common_test_entry = common_test_entry; + // batch 0: one transaction that advances the nonce twice { let mut test_entry = common_test_entry.clone(); @@ -1602,6 +1606,62 @@ fn nonce_reuse(enable_fee_only_transactions: bool, fee_paying_nonce: bool) -> Ve test_entries } +#[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 = if self == Self::Dealloc { + vec![ + AccountMeta::new(target, false), + AccountMeta::new(solana_sdk::incinerator::id(), false), + ] + } else { + 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(), + ) + } +} + fn account_deallocate() -> Vec { let mut test_entries = vec![]; @@ -1634,15 +1694,11 @@ fn account_deallocate() -> Vec { ); test_entry.add_initial_account(target, &target_data); - let set_data_transaction = Transaction::new_signed_with_payer( - &[Instruction::new_with_bytes( - program_id, - &[1], - vec![AccountMeta::new(target, false)], - )], - Some(&fee_payer), - &[&fee_payer_keypair], - Hash::default(), + let set_data_transaction = WriteProgramInstruction::Set.create_transaction( + program_id, + &fee_payer_keypair, + target, + None, ); test_entry.push_transaction(set_data_transaction); @@ -1652,32 +1708,21 @@ fn account_deallocate() -> Vec { test_entry.update_expected_account_data(target, &target_data); if remove_lamports { - let dealloc_transaction = Transaction::new_signed_with_payer( - &[Instruction::new_with_bytes( - program_id, - &[2], - vec![ - AccountMeta::new(target, false), - AccountMeta::new(solana_sdk::incinerator::id(), false), - ], - )], - Some(&fee_payer), - &[&fee_payer_keypair], - Hash::default(), + let dealloc_transaction = WriteProgramInstruction::Dealloc.create_transaction( + program_id, + &fee_payer_keypair, + target, + None, ); test_entry.push_transaction(dealloc_transaction); - let check_transaction = Transaction::new_signed_with_payer( - &[Instruction::new_with_bytes( - program_id, - &[0], - vec![AccountMeta::new(target, false)], - )], - Some(&fee_payer), - &[&fee_payer_keypair], - Hash::default(), + let print_transaction = WriteProgramInstruction::Print.create_transaction( + program_id, + &fee_payer_keypair, + target, + None, ); - test_entry.push_transaction(check_transaction); + test_entry.push_transaction(print_transaction); test_entry.transaction_batch[2] .asserts .logs @@ -1850,6 +1895,112 @@ fn fee_payer_deallocate(enable_fee_only_transactions: bool) -> Vec vec![test_entry] } +fn account_reallocate(enable_fee_only_transactions: bool) -> Vec { + let mut test_entries = vec![]; + + let program_name = "write-to-account".to_string(); + let program_id = program_address(&program_name); + let program_size = program_data_size(&program_name); + + let mut common_test_entry = SvmTestEntry::default(); + + let fee_payer_keypair = Keypair::new(); + let fee_payer = fee_payer_keypair.pubkey(); + + let mut fee_payer_data = AccountSharedData::default(); + fee_payer_data.set_lamports(LAMPORTS_PER_SOL); + common_test_entry.add_initial_account(fee_payer, &fee_payer_data); + + let mk_target = |size| { + AccountSharedData::create( + LAMPORTS_PER_SOL * 10, + vec![0; size], + program_id, + false, + u64::MAX, + ) + }; + + let target = Pubkey::new_unique(); + let target_start_size = 100; + common_test_entry.add_initial_account(target, &mk_target(target_start_size)); + + let print_transaction = WriteProgramInstruction::Print.create_transaction( + program_id, + &fee_payer_keypair, + target, + Some( + (program_size + MAX_PERMITTED_DATA_INCREASE) + .try_into() + .unwrap(), + ), + ); + + common_test_entry.decrease_expected_lamports(&fee_payer, LAMPORTS_PER_SIGNATURE * 2); + + let common_test_entry = common_test_entry; + + // batch 0/1: + // * successful realloc up/down + // * change reflected in same batch + for new_target_size in [target_start_size + 1, target_start_size - 1] { + let mut test_entry = common_test_entry.clone(); + + let realloc_transaction = WriteProgramInstruction::Realloc(new_target_size) + .create_transaction(program_id, &fee_payer_keypair, target, None); + test_entry.push_transaction(realloc_transaction); + + test_entry.push_transaction(print_transaction.clone()); + test_entry.transaction_batch[1] + .asserts + .logs + .push(format!("Program log: account size {}", new_target_size)); + + test_entry.update_expected_account_data(target, &mk_target(new_target_size)); + + test_entries.push(test_entry); + } + + // batch 2: + // * successful large realloc up + // * transaction is aborted based on the new transaction data size post-realloc + { + let mut test_entry = common_test_entry.clone(); + + let new_target_size = target_start_size + MAX_PERMITTED_DATA_INCREASE; + let expected_print_status = if enable_fee_only_transactions { + ExecutionStatus::ProcessedFailed + } else { + test_entry.increase_expected_lamports(&fee_payer, LAMPORTS_PER_SIGNATURE); + ExecutionStatus::Discarded + }; + + let realloc_transaction = WriteProgramInstruction::Realloc(new_target_size) + .create_transaction(program_id, &fee_payer_keypair, target, None); + test_entry.push_transaction(realloc_transaction); + + test_entry.push_transaction_with_status(print_transaction.clone(), expected_print_status); + + test_entry.update_expected_account_data(target, &mk_target(new_target_size)); + + test_entries.push(test_entry); + } + + for test_entry in &mut test_entries { + test_entry + .initial_programs + .push((program_name.to_string(), DEPLOYMENT_SLOT)); + + if enable_fee_only_transactions { + test_entry + .enabled_features + .push(feature_set::enable_transaction_loading_failure_fees::id()); + } + } + + test_entries +} + #[test_case(program_medley())] #[test_case(simple_transfer(false))] #[test_case(simple_transfer(true))] @@ -1866,6 +2017,8 @@ fn fee_payer_deallocate(enable_fee_only_transactions: bool) -> Vec #[test_case(account_deallocate())] #[test_case(fee_payer_deallocate(false))] #[test_case(fee_payer_deallocate(true))] +#[test_case(account_reallocate(false))] +#[test_case(account_reallocate(true))] fn svm_integration(test_entries: Vec) { for test_entry in test_entries { execute_test_entry(test_entry); @@ -1937,7 +2090,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 + // we do it this way because an account may change multiple times in the same batch but may 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() { diff --git a/svm/tests/mock_bank.rs b/svm/tests/mock_bank.rs index 8adbd2a55bf13e..88196afb85747e 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, @@ -136,6 +136,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 { let program_account = program_address(&name); @@ -281,6 +286,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)]