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
11 changed files with 350 additions and 248 deletions

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(),