From 97c2732d02f57e4588bb5f62af922dd0f10947c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Mei=C3=9Fner?= Date: Thu, 21 Oct 2021 20:57:42 +0200 Subject: [PATCH] Refactor: Cleanup InvokeContext (#20785) * Move blockhash and fee_calculator in ThisInvokeContext instead of using a reference. * Moves tx_wide_compute_cap into InvokeContext::push(). * Adds ThisInvokeContext::new_mock() constructor. * Adds missing loader account in uses of MockInvokeContext. * Use keyed_account_at_index() when accessing keyed_accounts. * Makes sysvar interface consistent between ThisInvokeContext and MockInvokeContext, in order to add InvokeContext::get_sysvars(). * Adds InvokeContext::set_blockhash() and InvokeContext ::set_fee_calculator(). * Adds new_mock_with_features. * Makes ancestors optional in ThisInvokeContext. * Adds prepare_mock_invoke_context() and mock_process_instruction(). --- program-runtime/src/instruction_processor.rs | 7 +- program-test/src/lib.rs | 28 +- programs/bpf/benches/bpf_loader.rs | 54 ++- programs/bpf/tests/programs.rs | 21 +- programs/bpf_loader/src/syscalls.rs | 12 +- programs/config/src/config_processor.rs | 21 +- programs/stake/src/stake_instruction.rs | 26 +- programs/vote/src/vote_instruction.rs | 2 +- runtime/src/bank.rs | 38 +- runtime/src/message_processor.rs | 351 ++++++++++++------- sdk/src/process_instruction.rs | 38 +- 11 files changed, 350 insertions(+), 248 deletions(-) diff --git a/program-runtime/src/instruction_processor.rs b/program-runtime/src/instruction_processor.rs index 5d26d41dc4..4883efd723 100644 --- a/program-runtime/src/instruction_processor.rs +++ b/program-runtime/src/instruction_processor.rs @@ -9,6 +9,7 @@ use solana_sdk::{ }, ic_msg, instruction::{Instruction, InstructionError}, + keyed_account::keyed_account_at_index, message::Message, process_instruction::{Executor, InvokeContext, ProcessInstructionWithContext}, pubkey::Pubkey, @@ -355,7 +356,8 @@ impl InstructionProcessor { instruction_data: &[u8], invoke_context: &mut dyn InvokeContext, ) -> Result<(), InstructionError> { - if let Some(root_account) = invoke_context.get_keyed_accounts()?.iter().next() { + let keyed_accounts = invoke_context.get_keyed_accounts()?; + if let Ok(root_account) = keyed_account_at_index(keyed_accounts, 0) { let root_id = root_account.unsigned_key(); let owner_id = &root_account.owner()?; if solana_sdk::native_loader::check_id(owner_id) { @@ -536,7 +538,8 @@ impl InstructionProcessor { caller_write_privileges = Vec::with_capacity(1 + keyed_account_indices_obsolete.len()); caller_write_privileges.push(false); for index in keyed_account_indices_obsolete.iter() { - caller_write_privileges.push(caller_keyed_accounts[*index].is_writable()); + caller_write_privileges + .push(keyed_account_at_index(caller_keyed_accounts, *index)?.is_writable()); } }; let mut account_indices = Vec::with_capacity(message.account_keys.len()); diff --git a/program-test/src/lib.rs b/program-test/src/lib.rs index ad17c37504..cbf61db931 100644 --- a/program-test/src/lib.rs +++ b/program-test/src/lib.rs @@ -33,7 +33,7 @@ use { message::Message, native_token::sol_to_lamports, poh_config::PohConfig, - process_instruction::{stable_log, InvokeContext, ProcessInstructionWithContext}, + process_instruction::{self, stable_log, InvokeContext, ProcessInstructionWithContext}, program_error::{ProgramError, ACCOUNT_BORROW_FAILED, UNSUPPORTED_SYSVAR}, pubkey::Pubkey, rent::Rent, @@ -204,24 +204,6 @@ fn get_sysvar( var_addr: *mut u8, ) -> u64 { let invoke_context = get_invoke_context(); - - let sysvar_data = match invoke_context.get_sysvar_data(id).ok_or_else(|| { - ic_msg!(invoke_context, "Unable to get Sysvar {}", id); - UNSUPPORTED_SYSVAR - }) { - Ok(sysvar_data) => sysvar_data, - Err(err) => return err, - }; - - let var: T = match bincode::deserialize(&sysvar_data) { - Ok(sysvar_data) => sysvar_data, - Err(_) => return UNSUPPORTED_SYSVAR, - }; - - unsafe { - *(var_addr as *mut _ as *mut T) = var; - } - if invoke_context .get_compute_meter() .try_borrow_mut() @@ -233,7 +215,13 @@ fn get_sysvar( panic!("Exceeded compute budget"); } - SUCCESS + match process_instruction::get_sysvar::(invoke_context, id) { + Ok(sysvar_data) => unsafe { + *(var_addr as *mut _ as *mut T) = sysvar_data; + SUCCESS + }, + Err(_) => UNSUPPORTED_SYSVAR, + } } struct SyscallStubs {} diff --git a/programs/bpf/benches/bpf_loader.rs b/programs/bpf/benches/bpf_loader.rs index 673bd7c81d..15a958e4e0 100644 --- a/programs/bpf/benches/bpf_loader.rs +++ b/programs/bpf/benches/bpf_loader.rs @@ -94,8 +94,28 @@ fn bench_program_alu(bencher: &mut Bencher) { .write_u64::(ARMSTRONG_LIMIT) .unwrap(); inner_iter.write_u64::(0).unwrap(); + let loader_id = bpf_loader::id(); - let mut invoke_context = MockInvokeContext::new(&Pubkey::default(), vec![]); + let program_id = solana_sdk::pubkey::new_rand(); + let accounts = [ + ( + program_id, + RefCell::new(AccountSharedData::new(0, 0, &loader_id)), + ), + ( + solana_sdk::pubkey::new_rand(), + RefCell::new(AccountSharedData::new( + 1, + 10000001, + &solana_sdk::pubkey::new_rand(), + )), + ), + ]; + let keyed_accounts: Vec<_> = accounts + .iter() + .map(|(key, account)| solana_sdk::keyed_account::KeyedAccount::new(&key, false, &account)) + .collect(); + let mut invoke_context = MockInvokeContext::new(&program_id, keyed_accounts); let elf = load_elf("bench_alu").unwrap(); let mut executable = >::from_elf( @@ -254,29 +274,35 @@ fn bench_create_vm(bencher: &mut Bencher) { fn bench_instruction_count_tuner(_bencher: &mut Bencher) { const BUDGET: u64 = 200_000; let loader_id = bpf_loader::id(); + let program_id = solana_sdk::pubkey::new_rand(); - let accounts = [RefCell::new(AccountSharedData::new( - 1, - 10000001, - &solana_sdk::pubkey::new_rand(), - ))]; - let keys = [solana_sdk::pubkey::new_rand()]; - let keyed_accounts: Vec<_> = keys + let accounts = [ + ( + program_id, + RefCell::new(AccountSharedData::new(0, 0, &loader_id)), + ), + ( + solana_sdk::pubkey::new_rand(), + RefCell::new(AccountSharedData::new( + 1, + 10000001, + &solana_sdk::pubkey::new_rand(), + )), + ), + ]; + let keyed_accounts: Vec<_> = accounts .iter() - .zip(&accounts) .map(|(key, account)| solana_sdk::keyed_account::KeyedAccount::new(&key, false, &account)) .collect(); let instruction_data = vec![0u8]; - - let mut invoke_context = MockInvokeContext::new(&loader_id, keyed_accounts); + let mut invoke_context = MockInvokeContext::new(&program_id, keyed_accounts); invoke_context.compute_meter.remaining = BUDGET; // Serialize account data - let keyed_accounts = invoke_context.get_keyed_accounts().unwrap(); let (mut serialized, account_lengths) = serialize_parameters( &loader_id, - &solana_sdk::pubkey::new_rand(), - keyed_accounts, + &program_id, + &invoke_context.get_keyed_accounts().unwrap()[1..], &instruction_data, ) .unwrap(); diff --git a/programs/bpf/tests/programs.rs b/programs/bpf/tests/programs.rs index 17d496de3e..087621e9fc 100644 --- a/programs/bpf/tests/programs.rs +++ b/programs/bpf/tests/programs.rs @@ -192,24 +192,23 @@ fn upgrade_bpf_program( fn run_program( name: &str, + loader_id: &Pubkey, program_id: &Pubkey, parameter_accounts: Vec, instruction_data: &[u8], ) -> Result { - let path = create_bpf_path(name); - let mut file = File::open(path).unwrap(); - + let mut file = File::open(create_bpf_path(name)).unwrap(); let mut data = vec![]; file.read_to_end(&mut data).unwrap(); - let loader_id = bpf_loader::id(); + + let mut invoke_context = MockInvokeContext::new(&program_id, parameter_accounts); let (parameter_bytes, account_lengths) = serialize_parameters( &loader_id, program_id, - ¶meter_accounts, + &invoke_context.get_keyed_accounts().unwrap()[1..], &instruction_data, ) .unwrap(); - let mut invoke_context = MockInvokeContext::new(&loader_id, parameter_accounts); let compute_meter = invoke_context.get_compute_meter(); let mut instruction_meter = ThisInstructionMeter { compute_meter }; @@ -1405,11 +1404,17 @@ fn assert_instruction_count() { let mut passed = true; println!("\n {:36} expected actual diff", "BPF program"); for program in programs.iter() { + let loader_id = bpf_loader::id(); let program_id = Pubkey::new_unique(); let key = Pubkey::new_unique(); + let mut program_account = RefCell::new(AccountSharedData::new(0, 0, &loader_id)); let mut account = RefCell::new(AccountSharedData::default()); - let parameter_accounts = vec![KeyedAccount::new(&key, false, &mut account)]; - let count = run_program(program.0, &program_id, parameter_accounts, &[]).unwrap(); + let parameter_accounts = vec![ + KeyedAccount::new(&program_id, false, &mut program_account), + KeyedAccount::new(&key, false, &mut account), + ]; + let count = + run_program(program.0, &loader_id, &program_id, parameter_accounts, &[]).unwrap(); let diff: i64 = count as i64 - program.1 as i64; println!( " {:36} {:8} {:6} {:+5} ({:+3.0}%)", diff --git a/programs/bpf_loader/src/syscalls.rs b/programs/bpf_loader/src/syscalls.rs index 2e6160f966..892c17c03e 100644 --- a/programs/bpf_loader/src/syscalls.rs +++ b/programs/bpf_loader/src/syscalls.rs @@ -3335,7 +3335,8 @@ mod tests { let mut data = vec![]; bincode::serialize_into(&mut data, &src_clock).unwrap(); invoke_context - .sysvars + .get_sysvars() + .borrow_mut() .push((sysvar::clock::id(), Some(Rc::new(data)))); let mut syscall = SyscallGetClockSysvar { @@ -3380,7 +3381,8 @@ mod tests { let mut data = vec![]; bincode::serialize_into(&mut data, &src_epochschedule).unwrap(); invoke_context - .sysvars + .get_sysvars() + .borrow_mut() .push((sysvar::epoch_schedule::id(), Some(Rc::new(data)))); let mut syscall = SyscallGetEpochScheduleSysvar { @@ -3432,7 +3434,8 @@ mod tests { let mut data = vec![]; bincode::serialize_into(&mut data, &src_fees).unwrap(); invoke_context - .sysvars + .get_sysvars() + .borrow_mut() .push((sysvar::fees::id(), Some(Rc::new(data)))); let mut syscall = SyscallGetFeesSysvar { @@ -3475,7 +3478,8 @@ mod tests { let mut data = vec![]; bincode::serialize_into(&mut data, &src_rent).unwrap(); invoke_context - .sysvars + .get_sysvars() + .borrow_mut() .push((sysvar::rent::id(), Some(Rc::new(data)))); let mut syscall = SyscallGetRentSysvar { diff --git a/programs/config/src/config_processor.rs b/programs/config/src/config_processor.rs index 17ecc30b8c..545dc5f0fc 100644 --- a/programs/config/src/config_processor.rs +++ b/programs/config/src/config_processor.rs @@ -54,20 +54,19 @@ pub fn process_instruction( } let mut counter = 0; - let mut keyed_accounts_iter = keyed_accounts.iter().skip(2); for (signer, _) in key_list.keys.iter().filter(|(_, is_signer)| *is_signer) { counter += 1; if signer != config_keyed_account.unsigned_key() { - let signer_account = keyed_accounts_iter.next(); - if signer_account.is_none() { - ic_msg!( - invoke_context, - "account {:?} is not in account list", - signer - ); - return Err(InstructionError::MissingRequiredSignature); - } - let signer_key = signer_account.unwrap().signer_key(); + let signer_account = + keyed_account_at_index(keyed_accounts, counter + 1).map_err(|_| { + ic_msg!( + invoke_context, + "account {:?} is not in account list", + signer, + ); + InstructionError::MissingRequiredSignature + })?; + let signer_key = signer_account.signer_key(); if signer_key.is_none() { ic_msg!( invoke_context, diff --git a/programs/stake/src/stake_instruction.rs b/programs/stake/src/stake_instruction.rs index 4a49caffed..f962a69eb8 100644 --- a/programs/stake/src/stake_instruction.rs +++ b/programs/stake/src/stake_instruction.rs @@ -37,7 +37,7 @@ pub fn process_instruction( return Err(InstructionError::InvalidAccountOwner); } - let signers = get_signers(&keyed_accounts[1..]); + let signers = get_signers(&keyed_accounts[first_instruction_account..]); match limited_deserialize(data)? { StakeInstruction::Initialize(authorized, lockup) => me.initialize( &authorized, @@ -330,7 +330,7 @@ mod tests { account::{self, Account, AccountSharedData, WritableAccount}, instruction::{AccountMeta, Instruction}, keyed_account::create_keyed_accounts_unified, - process_instruction::{mock_set_sysvar, MockInvokeContext}, + process_instruction::MockInvokeContext, pubkey::Pubkey, rent::Rent, stake::{ @@ -338,7 +338,7 @@ mod tests { instruction::{self, LockupArgs}, state::{Authorized, Lockup, StakeAuthorize}, }, - sysvar::stake_history::StakeHistory, + sysvar::{stake_history::StakeHistory, Sysvar}, }; use std::{cell::RefCell, rc::Rc, str::FromStr}; @@ -442,12 +442,12 @@ mod tests { &processor_id, create_keyed_accounts_unified(&keyed_accounts), ); - mock_set_sysvar( - &mut invoke_context, - sysvar::clock::id(), - sysvar::clock::Clock::default(), - ) - .unwrap(); + let mut data = Vec::with_capacity(sysvar::clock::Clock::size_of()); + bincode::serialize_into(&mut data, &sysvar::clock::Clock::default()).unwrap(); + invoke_context + .get_sysvars() + .borrow_mut() + .push((sysvar::clock::id(), Some(Rc::new(data)))); super::process_instruction(1, &instruction.data, &mut invoke_context) } } @@ -1100,11 +1100,11 @@ mod tests { ]; let mut invoke_context = MockInvokeContext::new(&id(), create_keyed_accounts_unified(&keyed_accounts)); - let clock = Clock::default(); - let mut data = vec![]; - bincode::serialize_into(&mut data, &clock).unwrap(); + let mut data = Vec::with_capacity(sysvar::clock::Clock::size_of()); + bincode::serialize_into(&mut data, &sysvar::clock::Clock::default()).unwrap(); invoke_context - .sysvars + .get_sysvars() + .borrow_mut() .push((sysvar::clock::id(), Some(Rc::new(data)))); assert_eq!( diff --git a/programs/vote/src/vote_instruction.rs b/programs/vote/src/vote_instruction.rs index 300c9146de..b1410fe479 100644 --- a/programs/vote/src/vote_instruction.rs +++ b/programs/vote/src/vote_instruction.rs @@ -322,7 +322,7 @@ pub fn process_instruction( return Err(InstructionError::InvalidAccountOwner); } - let signers: HashSet = get_signers(&keyed_accounts[1..]); + let signers: HashSet = get_signers(&keyed_accounts[first_instruction_account..]); match limited_deserialize(data)? { VoteInstruction::InitializeAccount(vote_init) => { verify_rent_exemption( diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index 3557fc16e8..62e23d13c0 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -6332,6 +6332,7 @@ pub(crate) mod tests { genesis_config::create_genesis_config, hash, instruction::{AccountMeta, CompiledInstruction, Instruction, InstructionError}, + keyed_account::keyed_account_at_index, message::{Message, MessageHeader}, nonce, poh_config::PohConfig, @@ -6713,11 +6714,11 @@ pub(crate) mod tests { if let Ok(instruction) = bincode::deserialize(data) { match instruction { MockInstruction::Deduction => { - keyed_accounts[first_instruction_account + 1] + keyed_account_at_index(keyed_accounts, first_instruction_account + 1)? .account .borrow_mut() .checked_add_lamports(1)?; - keyed_accounts[first_instruction_account + 2] + keyed_account_at_index(keyed_accounts, first_instruction_account + 2)? .account .borrow_mut() .checked_sub_lamports(1)?; @@ -11295,18 +11296,16 @@ pub(crate) mod tests { ) -> result::Result<(), InstructionError> { let keyed_accounts = invoke_context.get_keyed_accounts()?; let lamports = data[0] as u64; - { - let mut to_account = - keyed_accounts[first_instruction_account + 1].try_account_ref_mut()?; - let mut dup_account = - keyed_accounts[first_instruction_account + 2].try_account_ref_mut()?; - dup_account.checked_sub_lamports(lamports)?; - to_account.checked_add_lamports(lamports)?; - } - keyed_accounts[first_instruction_account] + keyed_account_at_index(keyed_accounts, first_instruction_account + 2)? .try_account_ref_mut()? .checked_sub_lamports(lamports)?; - keyed_accounts[first_instruction_account + 1] + keyed_account_at_index(keyed_accounts, first_instruction_account + 1)? + .try_account_ref_mut()? + .checked_add_lamports(lamports)?; + keyed_account_at_index(keyed_accounts, first_instruction_account)? + .try_account_ref_mut()? + .checked_sub_lamports(lamports)?; + keyed_account_at_index(keyed_accounts, first_instruction_account + 1)? .try_account_ref_mut()? .checked_add_lamports(lamports)?; Ok(()) @@ -11790,14 +11789,9 @@ pub(crate) mod tests { invoke_context: &mut dyn InvokeContext, ) -> result::Result<(), InstructionError> { let keyed_accounts = invoke_context.get_keyed_accounts()?; - assert_eq!( - 42, - keyed_accounts[first_instruction_account] - .lamports() - .unwrap() - ); - let mut account = keyed_accounts[first_instruction_account].try_account_ref_mut()?; - account.checked_add_lamports(1)?; + let account = keyed_account_at_index(keyed_accounts, first_instruction_account)?; + assert_eq!(42, account.lamports().unwrap()); + account.try_account_ref_mut()?.checked_add_lamports(1)?; Ok(()) } @@ -14598,8 +14592,8 @@ pub(crate) mod tests { ) -> std::result::Result<(), InstructionError> { use solana_sdk::account::WritableAccount; let keyed_accounts = invoke_context.get_keyed_accounts()?; - let mut data = keyed_accounts[first_instruction_account + 1].try_account_ref_mut()?; - data.data_as_mut_slice()[0] = 5; + let data = keyed_account_at_index(keyed_accounts, first_instruction_account + 1)?; + data.try_account_ref_mut()?.data_as_mut_slice()[0] = 5; Ok(()) } diff --git a/runtime/src/message_processor.rs b/runtime/src/message_processor.rs index 3b9b977314..2385cc6a0a 100644 --- a/runtime/src/message_processor.rs +++ b/runtime/src/message_processor.rs @@ -17,7 +17,7 @@ use solana_sdk::{ fee_calculator::FeeCalculator, hash::Hash, ic_logger_msg, - instruction::{CompiledInstruction, Instruction, InstructionError}, + instruction::{AccountMeta, CompiledInstruction, Instruction, InstructionError}, keyed_account::{create_keyed_accounts_unified, KeyedAccount}, message::Message, precompiles::is_precompile, @@ -48,6 +48,32 @@ impl ComputeMeter for ThisComputeMeter { self.remaining } } +impl ThisComputeMeter { + pub fn new_ref(remaining: u64) -> Rc> { + Rc::new(RefCell::new(Self { remaining })) + } +} + +pub struct ThisLogger { + log_collector: Option>, +} +impl Logger for ThisLogger { + fn log_enabled(&self) -> bool { + log_enabled!(log::Level::Info) || self.log_collector.is_some() + } + fn log(&self, message: &str) { + debug!("{}", message); + if let Some(log_collector) = &self.log_collector { + log_collector.log(message); + } + } +} +impl ThisLogger { + pub fn new_ref(log_collector: Option>) -> Rc> { + Rc::new(RefCell::new(Self { log_collector })) + } +} + pub struct ThisInvokeContext<'a> { instruction_index: usize, invoke_stack: Vec>, @@ -63,11 +89,11 @@ pub struct ThisInvokeContext<'a> { feature_set: Arc, pub timings: ExecuteDetailsTimings, account_db: Arc, - ancestors: &'a Ancestors, + ancestors: Option<&'a Ancestors>, #[allow(clippy::type_complexity)] sysvars: RefCell>>)>>, - blockhash: &'a Hash, - fee_calculator: &'a FeeCalculator, + blockhash: Hash, + fee_calculator: FeeCalculator, return_data: (Pubkey, Vec), } impl<'a> ThisInvokeContext<'a> { @@ -83,9 +109,9 @@ impl<'a> ThisInvokeContext<'a> { instruction_recorders: Option<&'a [InstructionRecorder]>, feature_set: Arc, account_db: Arc, - ancestors: &'a Ancestors, - blockhash: &'a Hash, - fee_calculator: &'a FeeCalculator, + ancestors: Option<&'a Ancestors>, + blockhash: Hash, + fee_calculator: FeeCalculator, ) -> Self { Self { instruction_index: 0, @@ -94,7 +120,7 @@ impl<'a> ThisInvokeContext<'a> { pre_accounts: Vec::new(), accounts, programs, - logger: Rc::new(RefCell::new(ThisLogger { log_collector })), + logger: ThisLogger::new_ref(log_collector), compute_budget, compute_meter, executors, @@ -103,12 +129,41 @@ impl<'a> ThisInvokeContext<'a> { timings: ExecuteDetailsTimings::default(), account_db, ancestors, - sysvars: RefCell::new(vec![]), + sysvars: RefCell::new(Vec::new()), blockhash, fee_calculator, return_data: (Pubkey::default(), Vec::new()), } } + + pub fn new_mock_with_features( + accounts: &'a [(Pubkey, Rc>)], + programs: &'a [(Pubkey, ProcessInstructionWithContext)], + feature_set: Arc, + ) -> Self { + Self::new( + Rent::default(), + accounts, + programs, + None, + ComputeBudget::default(), + ThisComputeMeter::new_ref(std::i64::MAX as u64), + Rc::new(RefCell::new(Executors::default())), + None, + feature_set, + Arc::new(Accounts::default_for_tests()), + None, + Hash::default(), + FeeCalculator::default(), + ) + } + + pub fn new_mock( + accounts: &'a [(Pubkey, Rc>)], + programs: &'a [(Pubkey, ProcessInstructionWithContext)], + ) -> Self { + Self::new_mock_with_features(accounts, programs, Arc::new(FeatureSet::all_enabled())) + } } impl<'a> InvokeContext for ThisInvokeContext<'a> { fn push( @@ -140,6 +195,10 @@ impl<'a> InvokeContext for ThisInvokeContext<'a> { } if self.invoke_stack.is_empty() { + if !self.feature_set.is_active(&tx_wide_compute_cap::id()) { + self.compute_meter = ThisComputeMeter::new_ref(self.compute_budget.max_units); + } + self.pre_accounts = Vec::with_capacity(instruction.accounts.len()); let mut work = |_unique_index: usize, account_index: usize| { if account_index < self.accounts.len() { @@ -364,11 +423,6 @@ impl<'a> InvokeContext for ThisInvokeContext<'a> { self.executors.borrow().get(pubkey) } fn set_instruction_index(&mut self, instruction_index: usize) { - if !self.feature_set.is_active(&tx_wide_compute_cap::id()) { - self.compute_meter = Rc::new(RefCell::new(ThisComputeMeter { - remaining: self.compute_budget.max_units, - })); - } self.instruction_index = instruction_index; } fn record_instruction(&self, instruction: &Instruction) { @@ -399,6 +453,10 @@ impl<'a> InvokeContext for ThisInvokeContext<'a> { self.timings.execute_us += execute_us; self.timings.deserialize_us += deserialize_us; } + #[allow(clippy::type_complexity)] + fn get_sysvars(&self) -> &RefCell>>)>> { + &self.sysvars + } fn get_sysvar_data(&self, id: &Pubkey) -> Option>> { if let Ok(mut sysvars) = self.sysvars.try_borrow_mut() { // Try share from cache @@ -406,13 +464,15 @@ impl<'a> InvokeContext for ThisInvokeContext<'a> { .iter() .find_map(|(key, sysvar)| if id == key { sysvar.clone() } else { None }); if result.is_none() { - // Load it - result = self - .account_db - .load_with_fixed_root(self.ancestors, id) - .map(|(account, _)| Rc::new(account.data().to_vec())); - // Cache it - sysvars.push((*id, result.clone())); + if let Some(ancestors) = self.ancestors { + // Load it + result = self + .account_db + .load_with_fixed_root(ancestors, id) + .map(|(account, _)| Rc::new(account.data().to_vec())); + // Cache it + sysvars.push((*id, result.clone())); + } } result } else { @@ -422,11 +482,17 @@ impl<'a> InvokeContext for ThisInvokeContext<'a> { fn get_compute_budget(&self) -> &ComputeBudget { &self.compute_budget } + fn set_blockhash(&mut self, hash: Hash) { + self.blockhash = hash; + } fn get_blockhash(&self) -> &Hash { - self.blockhash + &self.blockhash + } + fn set_fee_calculator(&mut self, fee_calculator: FeeCalculator) { + self.fee_calculator = fee_calculator; } fn get_fee_calculator(&self) -> &FeeCalculator { - self.fee_calculator + &self.fee_calculator } fn set_return_data(&mut self, data: Vec) -> Result<(), InstructionError> { self.return_data = (*self.get_caller()?, data); @@ -436,21 +502,90 @@ impl<'a> InvokeContext for ThisInvokeContext<'a> { (self.return_data.0, &self.return_data.1) } } -pub struct ThisLogger { - log_collector: Option>, + +pub struct MockInvokeContextPreparation { + pub accounts: Vec<(Pubkey, Rc>)>, + pub message: Message, + pub account_indices: Vec, } -impl Logger for ThisLogger { - fn log_enabled(&self) -> bool { - log_enabled!(log::Level::Info) || self.log_collector.is_some() + +pub fn prepare_mock_invoke_context( + program_indices: &[usize], + instruction_data: &[u8], + keyed_accounts: &[(bool, bool, Pubkey, Rc>)], +) -> MockInvokeContextPreparation { + #[allow(clippy::type_complexity)] + let (accounts, mut metas): ( + Vec<(Pubkey, Rc>)>, + Vec, + ) = keyed_accounts + .iter() + .map(|(is_signer, is_writable, pubkey, account)| { + ( + (*pubkey, account.clone()), + AccountMeta { + pubkey: *pubkey, + is_signer: *is_signer, + is_writable: *is_writable, + }, + ) + }) + .unzip(); + let program_id = if let Some(program_index) = program_indices.last() { + accounts[*program_index].0 + } else { + Pubkey::default() + }; + for program_index in program_indices.iter().rev() { + metas.remove(*program_index); } - fn log(&self, message: &str) { - debug!("{}", message); - if let Some(log_collector) = &self.log_collector { - log_collector.log(message); - } + let message = Message::new( + &[Instruction::new_with_bytes( + program_id, + instruction_data, + metas, + )], + None, + ); + let account_indices: Vec = message + .account_keys + .iter() + .map(|search_key| { + accounts + .iter() + .position(|(key, _account)| key == search_key) + .unwrap_or(accounts.len()) + }) + .collect(); + MockInvokeContextPreparation { + accounts, + message, + account_indices, } } +pub fn mock_process_instruction( + loader_id: &Pubkey, + mut program_indices: Vec, + instruction_data: &[u8], + keyed_accounts: &[(bool, bool, Pubkey, Rc>)], + process_instruction: ProcessInstructionWithContext, +) -> Result<(), InstructionError> { + let mut preparation = + prepare_mock_invoke_context(&program_indices, instruction_data, keyed_accounts); + let processor_account = AccountSharedData::new_ref(0, 0, &solana_sdk::native_loader::id()); + program_indices.insert(0, preparation.accounts.len()); + preparation.accounts.push((*loader_id, processor_account)); + let mut invoke_context = ThisInvokeContext::new_mock(&preparation.accounts, &[]); + invoke_context.push( + &preparation.message, + &preparation.message.instructions[0], + &program_indices, + Some(&preparation.account_indices), + )?; + process_instruction(1, instruction_data, &mut invoke_context) +} + #[derive(Debug, Default, Clone, Deserialize, Serialize)] pub struct MessageProcessor {} @@ -500,9 +635,9 @@ impl MessageProcessor { instruction_recorders, feature_set, account_db, - ancestors, - &blockhash, - &fee_calculator, + Some(ancestors), + blockhash, + fee_calculator, ); let compute_meter = invoke_context.get_compute_meter(); @@ -585,9 +720,9 @@ mod tests { use super::*; use solana_sdk::{ instruction::{AccountMeta, Instruction, InstructionError}, + keyed_account::keyed_account_at_index, message::Message, native_loader::{self, create_loadable_account_for_test}, - process_instruction::MockComputeMeter, secp256k1_instruction::new_secp256k1_instruction, secp256k1_program, }; @@ -610,11 +745,11 @@ mod tests { let keyed_accounts = invoke_context.get_keyed_accounts()?; assert_eq!( *program_id, - keyed_accounts[first_instruction_account].owner()? + keyed_account_at_index(keyed_accounts, first_instruction_account)?.owner()? ); assert_ne!( - keyed_accounts[first_instruction_account + 1].owner()?, - *keyed_accounts[first_instruction_account].unsigned_key() + keyed_account_at_index(keyed_accounts, first_instruction_account + 1)?.owner()?, + *keyed_account_at_index(keyed_accounts, first_instruction_account)?.unsigned_key() ); if let Ok(instruction) = bincode::deserialize(data) { @@ -622,17 +757,17 @@ mod tests { MockInstruction::NoopSuccess => (), MockInstruction::NoopFail => return Err(InstructionError::GenericError), MockInstruction::ModifyOwned => { - keyed_accounts[first_instruction_account] + keyed_account_at_index(keyed_accounts, first_instruction_account)? .try_account_ref_mut()? .data_as_mut_slice()[0] = 1 } MockInstruction::ModifyNotOwned => { - keyed_accounts[first_instruction_account + 1] + keyed_account_at_index(keyed_accounts, first_instruction_account + 1)? .try_account_ref_mut()? .data_as_mut_slice()[0] = 1 } MockInstruction::ModifyReadonly => { - keyed_accounts[first_instruction_account + 2] + keyed_account_at_index(keyed_accounts, first_instruction_account + 2)? .try_account_ref_mut()? .data_as_mut_slice()[0] = 1 } @@ -678,24 +813,7 @@ mod tests { &[Instruction::new_with_bytes(invoke_stack[0], &[0], metas)], None, ); - let ancestors = Ancestors::default(); - let blockhash = Hash::default(); - let fee_calculator = FeeCalculator::default(); - let mut invoke_context = ThisInvokeContext::new( - Rent::default(), - &accounts, - &[], - None, - ComputeBudget::default(), - Rc::new(RefCell::new(MockComputeMeter::default())), - Rc::new(RefCell::new(Executors::default())), - None, - Arc::new(FeatureSet::all_enabled()), - Arc::new(Accounts::default_for_tests()), - &ancestors, - &blockhash, - &fee_calculator, - ); + let mut invoke_context = ThisInvokeContext::new_mock(&accounts, &[]); // Check call depth increases and has a limit let mut depth_reached = 0; @@ -782,24 +900,7 @@ mod tests { )], None, ); - let ancestors = Ancestors::default(); - let blockhash = Hash::default(); - let fee_calculator = FeeCalculator::default(); - let mut invoke_context = ThisInvokeContext::new( - Rent::default(), - &accounts, - &[], - None, - ComputeBudget::default(), - Rc::new(RefCell::new(MockComputeMeter::default())), - Rc::new(RefCell::new(Executors::default())), - None, - Arc::new(FeatureSet::all_enabled()), - Arc::new(Accounts::default_for_tests()), - &ancestors, - &blockhash, - &fee_calculator, - ); + let mut invoke_context = ThisInvokeContext::new_mock(&accounts, &[]); invoke_context .push(&message, &message.instructions[0], &[0], None) .unwrap(); @@ -833,11 +934,11 @@ mod tests { match instruction { MockSystemInstruction::Correct => Ok(()), MockSystemInstruction::AttemptCredit { lamports } => { - keyed_accounts[first_instruction_account] + keyed_account_at_index(keyed_accounts, first_instruction_account)? .account .borrow_mut() .checked_sub_lamports(lamports)?; - keyed_accounts[first_instruction_account + 1] + keyed_account_at_index(keyed_accounts, first_instruction_account + 1)? .account .borrow_mut() .checked_add_lamports(lamports)?; @@ -845,7 +946,7 @@ mod tests { } // Change data in a read-only account MockSystemInstruction::AttemptDataChange { data } => { - keyed_accounts[first_instruction_account + 1] + keyed_account_at_index(keyed_accounts, first_instruction_account + 1)? .account .borrow_mut() .set_data(vec![data]); @@ -905,7 +1006,7 @@ mod tests { None, Arc::new(FeatureSet::all_enabled()), ComputeBudget::new(), - Rc::new(RefCell::new(MockComputeMeter::default())), + ThisComputeMeter::new_ref(std::i64::MAX as u64), &mut ExecuteDetailsTimings::default(), Arc::new(Accounts::default_for_tests()), &ancestors, @@ -936,7 +1037,7 @@ mod tests { None, Arc::new(FeatureSet::all_enabled()), ComputeBudget::new(), - Rc::new(RefCell::new(MockComputeMeter::default())), + ThisComputeMeter::new_ref(std::i64::MAX as u64), &mut ExecuteDetailsTimings::default(), Arc::new(Accounts::default_for_tests()), &ancestors, @@ -971,7 +1072,7 @@ mod tests { None, Arc::new(FeatureSet::all_enabled()), ComputeBudget::new(), - Rc::new(RefCell::new(MockComputeMeter::default())), + ThisComputeMeter::new_ref(std::i64::MAX as u64), &mut ExecuteDetailsTimings::default(), Arc::new(Accounts::default_for_tests()), &ancestors, @@ -1006,9 +1107,11 @@ mod tests { match instruction { MockSystemInstruction::BorrowFail => { let from_account = - keyed_accounts[first_instruction_account].try_account_ref_mut()?; + keyed_account_at_index(keyed_accounts, first_instruction_account)? + .try_account_ref_mut()?; let dup_account = - keyed_accounts[first_instruction_account + 2].try_account_ref_mut()?; + keyed_account_at_index(keyed_accounts, first_instruction_account + 2)? + .try_account_ref_mut()?; if from_account.lamports() != dup_account.lamports() { return Err(InstructionError::InvalidArgument); } @@ -1017,12 +1120,16 @@ mod tests { MockSystemInstruction::MultiBorrowMut => { let from_lamports = { let from_account = - keyed_accounts[first_instruction_account].try_account_ref_mut()?; + keyed_account_at_index(keyed_accounts, first_instruction_account)? + .try_account_ref_mut()?; from_account.lamports() }; let dup_lamports = { - let dup_account = keyed_accounts[first_instruction_account + 2] - .try_account_ref_mut()?; + let dup_account = keyed_account_at_index( + keyed_accounts, + first_instruction_account + 2, + )? + .try_account_ref_mut()?; dup_account.lamports() }; if from_lamports != dup_lamports { @@ -1032,18 +1139,24 @@ mod tests { } MockSystemInstruction::DoWork { lamports, data } => { { - let mut to_account = keyed_accounts[first_instruction_account + 1] - .try_account_ref_mut()?; - let mut dup_account = keyed_accounts[first_instruction_account + 2] - .try_account_ref_mut()?; + let mut to_account = keyed_account_at_index( + keyed_accounts, + first_instruction_account + 1, + )? + .try_account_ref_mut()?; + let mut dup_account = keyed_account_at_index( + keyed_accounts, + first_instruction_account + 2, + )? + .try_account_ref_mut()?; dup_account.checked_sub_lamports(lamports)?; to_account.checked_add_lamports(lamports)?; dup_account.set_data(vec![data]); } - keyed_accounts[first_instruction_account] + keyed_account_at_index(keyed_accounts, first_instruction_account)? .try_account_ref_mut()? .checked_sub_lamports(lamports)?; - keyed_accounts[first_instruction_account + 1] + keyed_account_at_index(keyed_accounts, first_instruction_account + 1)? .try_account_ref_mut()? .checked_add_lamports(lamports)?; Ok(()) @@ -1104,7 +1217,7 @@ mod tests { None, Arc::new(FeatureSet::all_enabled()), ComputeBudget::new(), - Rc::new(RefCell::new(MockComputeMeter::default())), + ThisComputeMeter::new_ref(std::i64::MAX as u64), &mut ExecuteDetailsTimings::default(), Arc::new(Accounts::default_for_tests()), &ancestors, @@ -1139,7 +1252,7 @@ mod tests { None, Arc::new(FeatureSet::all_enabled()), ComputeBudget::new(), - Rc::new(RefCell::new(MockComputeMeter::default())), + ThisComputeMeter::new_ref(std::i64::MAX as u64), &mut ExecuteDetailsTimings::default(), Arc::new(Accounts::default_for_tests()), &ancestors, @@ -1172,7 +1285,7 @@ mod tests { None, Arc::new(FeatureSet::all_enabled()), ComputeBudget::new(), - Rc::new(RefCell::new(MockComputeMeter::default())), + ThisComputeMeter::new_ref(std::i64::MAX as u64), &mut ExecuteDetailsTimings::default(), Arc::new(Accounts::default_for_tests()), &ancestors, @@ -1233,24 +1346,7 @@ mod tests { ); let message = Message::new(&[callee_instruction], None); - let ancestors = Ancestors::default(); - let blockhash = Hash::default(); - let fee_calculator = FeeCalculator::default(); - let mut invoke_context = ThisInvokeContext::new( - Rent::default(), - &accounts, - programs.as_slice(), - None, - ComputeBudget::default(), - Rc::new(RefCell::new(MockComputeMeter::default())), - Rc::new(RefCell::new(Executors::default())), - None, - Arc::new(FeatureSet::all_enabled()), - Arc::new(Accounts::default_for_tests()), - &ancestors, - &blockhash, - &fee_calculator, - ); + let mut invoke_context = ThisInvokeContext::new_mock(&accounts, programs.as_slice()); invoke_context .push(&message, &caller_instruction, &program_indices[..1], None) .unwrap(); @@ -1378,24 +1474,7 @@ mod tests { ); let message = Message::new(&[callee_instruction.clone()], None); - let ancestors = Ancestors::default(); - let blockhash = Hash::default(); - let fee_calculator = FeeCalculator::default(); - let mut invoke_context = ThisInvokeContext::new( - Rent::default(), - &accounts, - programs.as_slice(), - None, - ComputeBudget::default(), - Rc::new(RefCell::new(MockComputeMeter::default())), - Rc::new(RefCell::new(Executors::default())), - None, - Arc::new(FeatureSet::all_enabled()), - Arc::new(Accounts::default_for_tests()), - &ancestors, - &blockhash, - &fee_calculator, - ); + let mut invoke_context = ThisInvokeContext::new_mock(&accounts, programs.as_slice()); invoke_context .push(&message, &caller_instruction, &program_indices, None) .unwrap(); @@ -1509,7 +1588,7 @@ mod tests { None, Arc::new(FeatureSet::all_enabled()), ComputeBudget::new(), - Rc::new(RefCell::new(MockComputeMeter::default())), + ThisComputeMeter::new_ref(std::i64::MAX as u64), &mut ExecuteDetailsTimings::default(), Arc::new(Accounts::default_for_tests()), &Ancestors::default(), diff --git a/sdk/src/process_instruction.rs b/sdk/src/process_instruction.rs index e45da37d47..7ff3090840 100644 --- a/sdk/src/process_instruction.rs +++ b/sdk/src/process_instruction.rs @@ -121,12 +121,19 @@ pub trait InvokeContext { execute_us: u64, deserialize_us: u64, ); + /// Get sysvars + #[allow(clippy::type_complexity)] + fn get_sysvars(&self) -> &RefCell>>)>>; /// Get sysvar data fn get_sysvar_data(&self, id: &Pubkey) -> Option>>; /// Get this invocation's compute budget fn get_compute_budget(&self) -> &ComputeBudget; + /// Set this invocation's blockhash + fn set_blockhash(&mut self, hash: Hash); /// Get this invocation's blockhash fn get_blockhash(&self) -> &Hash; + /// Set this invocation's `FeeCalculator` + fn set_fee_calculator(&mut self, fee_calculator: FeeCalculator); /// Get this invocation's `FeeCalculator` fn get_fee_calculator(&self) -> &FeeCalculator; /// Set the return data @@ -350,7 +357,8 @@ pub struct MockInvokeContext<'a> { pub compute_meter: MockComputeMeter, pub programs: Vec<(Pubkey, ProcessInstructionWithContext)>, pub accounts: Vec<(Pubkey, Rc>)>, - pub sysvars: Vec<(Pubkey, Option>>)>, + #[allow(clippy::type_complexity)] + pub sysvars: RefCell>>)>>, pub disabled_features: HashSet, pub blockhash: Hash, pub fee_calculator: FeeCalculator, @@ -369,7 +377,7 @@ impl<'a> MockInvokeContext<'a> { }, programs: vec![], accounts: vec![], - sysvars: vec![], + sysvars: RefCell::new(Vec::new()), disabled_features: HashSet::default(), blockhash: Hash::default(), fee_calculator: FeeCalculator::default(), @@ -390,21 +398,6 @@ impl<'a> MockInvokeContext<'a> { } } -pub fn mock_set_sysvar( - mock_invoke_context: &mut MockInvokeContext, - id: Pubkey, - sysvar: T, -) -> Result<(), InstructionError> { - let mut data = Vec::with_capacity(T::size_of()); - - bincode::serialize_into(&mut data, &sysvar).map_err(|err| { - ic_msg!(mock_invoke_context, "Unable to serialize sysvar: {:?}", err); - InstructionError::GenericError - })?; - mock_invoke_context.sysvars.push((id, Some(Rc::new(data)))); - Ok(()) -} - impl<'a> InvokeContext for MockInvokeContext<'a> { fn push( &mut self, @@ -498,17 +491,28 @@ impl<'a> InvokeContext for MockInvokeContext<'a> { _deserialize_us: u64, ) { } + #[allow(clippy::type_complexity)] + fn get_sysvars(&self) -> &RefCell>>)>> { + &self.sysvars + } fn get_sysvar_data(&self, id: &Pubkey) -> Option>> { self.sysvars + .borrow() .iter() .find_map(|(key, sysvar)| if id == key { sysvar.clone() } else { None }) } fn get_compute_budget(&self) -> &ComputeBudget { &self.compute_budget } + fn set_blockhash(&mut self, hash: Hash) { + self.blockhash = hash; + } fn get_blockhash(&self) -> &Hash { &self.blockhash } + fn set_fee_calculator(&mut self, fee_calculator: FeeCalculator) { + self.fee_calculator = fee_calculator; + } fn get_fee_calculator(&self) -> &FeeCalculator { &self.fee_calculator }