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().
This commit is contained in:
Alexander Meißner 2021-10-21 20:57:42 +02:00 committed by GitHub
parent 0ac89841bf
commit 97c2732d02
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 350 additions and 248 deletions

View File

@ -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());

View File

@ -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<T: Default + Sysvar + Sized + serde::de::DeserializeOwned>(
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<T: Default + Sysvar + Sized + serde::de::DeserializeOwned>(
panic!("Exceeded compute budget");
}
SUCCESS
match process_instruction::get_sysvar::<T>(invoke_context, id) {
Ok(sysvar_data) => unsafe {
*(var_addr as *mut _ as *mut T) = sysvar_data;
SUCCESS
},
Err(_) => UNSUPPORTED_SYSVAR,
}
}
struct SyscallStubs {}

View File

@ -94,8 +94,28 @@ fn bench_program_alu(bencher: &mut Bencher) {
.write_u64::<LittleEndian>(ARMSTRONG_LIMIT)
.unwrap();
inner_iter.write_u64::<LittleEndian>(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 = <dyn Executable<BpfError, ThisInstructionMeter>>::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();

View File

@ -192,24 +192,23 @@ fn upgrade_bpf_program(
fn run_program(
name: &str,
loader_id: &Pubkey,
program_id: &Pubkey,
parameter_accounts: Vec<KeyedAccount>,
instruction_data: &[u8],
) -> Result<u64, InstructionError> {
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,
&parameter_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}%)",

View File

@ -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 {

View File

@ -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,

View File

@ -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!(

View File

@ -322,7 +322,7 @@ pub fn process_instruction(
return Err(InstructionError::InvalidAccountOwner);
}
let signers: HashSet<Pubkey> = get_signers(&keyed_accounts[1..]);
let signers: HashSet<Pubkey> = get_signers(&keyed_accounts[first_instruction_account..]);
match limited_deserialize(data)? {
VoteInstruction::InitializeAccount(vote_init) => {
verify_rent_exemption(

View File

@ -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(())
}

View File

@ -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<RefCell<Self>> {
Rc::new(RefCell::new(Self { remaining }))
}
}
pub struct ThisLogger {
log_collector: Option<Rc<LogCollector>>,
}
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<LogCollector>>) -> Rc<RefCell<Self>> {
Rc::new(RefCell::new(Self { log_collector }))
}
}
pub struct ThisInvokeContext<'a> {
instruction_index: usize,
invoke_stack: Vec<InvokeContextStackFrame<'a>>,
@ -63,11 +89,11 @@ pub struct ThisInvokeContext<'a> {
feature_set: Arc<FeatureSet>,
pub timings: ExecuteDetailsTimings,
account_db: Arc<Accounts>,
ancestors: &'a Ancestors,
ancestors: Option<&'a Ancestors>,
#[allow(clippy::type_complexity)]
sysvars: RefCell<Vec<(Pubkey, Option<Rc<Vec<u8>>>)>>,
blockhash: &'a Hash,
fee_calculator: &'a FeeCalculator,
blockhash: Hash,
fee_calculator: FeeCalculator,
return_data: (Pubkey, Vec<u8>),
}
impl<'a> ThisInvokeContext<'a> {
@ -83,9 +109,9 @@ impl<'a> ThisInvokeContext<'a> {
instruction_recorders: Option<&'a [InstructionRecorder]>,
feature_set: Arc<FeatureSet>,
account_db: Arc<Accounts>,
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<RefCell<AccountSharedData>>)],
programs: &'a [(Pubkey, ProcessInstructionWithContext)],
feature_set: Arc<FeatureSet>,
) -> 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<RefCell<AccountSharedData>>)],
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<Vec<(Pubkey, Option<Rc<Vec<u8>>>)>> {
&self.sysvars
}
fn get_sysvar_data(&self, id: &Pubkey) -> Option<Rc<Vec<u8>>> {
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<u8>) -> 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<Rc<LogCollector>>,
pub struct MockInvokeContextPreparation {
pub accounts: Vec<(Pubkey, Rc<RefCell<AccountSharedData>>)>,
pub message: Message,
pub account_indices: Vec<usize>,
}
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<RefCell<AccountSharedData>>)],
) -> MockInvokeContextPreparation {
#[allow(clippy::type_complexity)]
let (accounts, mut metas): (
Vec<(Pubkey, Rc<RefCell<AccountSharedData>>)>,
Vec<AccountMeta>,
) = 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<usize> = 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<usize>,
instruction_data: &[u8],
keyed_accounts: &[(bool, bool, Pubkey, Rc<RefCell<AccountSharedData>>)],
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(),

View File

@ -121,12 +121,19 @@ pub trait InvokeContext {
execute_us: u64,
deserialize_us: u64,
);
/// Get sysvars
#[allow(clippy::type_complexity)]
fn get_sysvars(&self) -> &RefCell<Vec<(Pubkey, Option<Rc<Vec<u8>>>)>>;
/// Get sysvar data
fn get_sysvar_data(&self, id: &Pubkey) -> Option<Rc<Vec<u8>>>;
/// 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<RefCell<AccountSharedData>>)>,
pub sysvars: Vec<(Pubkey, Option<Rc<Vec<u8>>>)>,
#[allow(clippy::type_complexity)]
pub sysvars: RefCell<Vec<(Pubkey, Option<Rc<Vec<u8>>>)>>,
pub disabled_features: HashSet<Pubkey>,
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<T: 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<Vec<(Pubkey, Option<Rc<Vec<u8>>>)>> {
&self.sysvars
}
fn get_sysvar_data(&self, id: &Pubkey) -> Option<Rc<Vec<u8>>> {
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
}