solana/runtime/src/message_processor.rs

1307 lines
47 KiB
Rust

use crate::{
accounts::Accounts, ancestors::Ancestors, instruction_recorder::InstructionRecorder,
log_collector::LogCollector, rent_collector::RentCollector,
};
use log::*;
use serde::{Deserialize, Serialize};
use solana_measure::measure::Measure;
use solana_program_runtime::{ExecuteDetailsTimings, Executors, InstructionProcessor, PreAccount};
use solana_sdk::{
account::{AccountSharedData, ReadableAccount, WritableAccount},
compute_budget::ComputeBudget,
feature_set::{
demote_program_write_locks, neon_evm_compute_budget, tx_wide_compute_cap,
updated_verify_policy, FeatureSet,
},
fee_calculator::FeeCalculator,
hash::Hash,
ic_logger_msg,
instruction::{CompiledInstruction, Instruction, InstructionError},
keyed_account::{create_keyed_accounts_unified, KeyedAccount},
message::Message,
process_instruction::{
ComputeMeter, Executor, InvokeContext, InvokeContextStackFrame, Logger,
ProcessInstructionWithContext,
},
pubkey::Pubkey,
rent::Rent,
sysvar::instructions,
transaction::TransactionError,
};
use std::{cell::RefCell, rc::Rc, sync::Arc};
pub struct ThisComputeMeter {
remaining: u64,
}
impl ComputeMeter for ThisComputeMeter {
fn consume(&mut self, amount: u64) -> Result<(), InstructionError> {
let exceeded = self.remaining < amount;
self.remaining = self.remaining.saturating_sub(amount);
if exceeded {
return Err(InstructionError::ComputationalBudgetExceeded);
}
Ok(())
}
fn get_remaining(&self) -> u64 {
self.remaining
}
}
pub struct ThisInvokeContext<'a> {
invoke_stack: Vec<InvokeContextStackFrame<'a>>,
rent: Rent,
pre_accounts: Vec<PreAccount>,
accounts: &'a [(Pubkey, Rc<RefCell<AccountSharedData>>)],
programs: &'a [(Pubkey, ProcessInstructionWithContext)],
logger: Rc<RefCell<dyn Logger>>,
compute_budget: ComputeBudget,
#[allow(deprecated)]
bpf_compute_budget: solana_sdk::process_instruction::BpfComputeBudget,
compute_meter: Rc<RefCell<dyn ComputeMeter>>,
executors: Rc<RefCell<Executors>>,
instruction_recorder: Option<InstructionRecorder>,
feature_set: Arc<FeatureSet>,
pub timings: ExecuteDetailsTimings,
account_db: Arc<Accounts>,
ancestors: &'a Ancestors,
#[allow(clippy::type_complexity)]
sysvars: RefCell<Vec<(Pubkey, Option<Rc<Vec<u8>>>)>>,
blockhash: &'a Hash,
fee_calculator: &'a FeeCalculator,
}
impl<'a> ThisInvokeContext<'a> {
#[allow(clippy::too_many_arguments)]
pub fn new(
program_id: &Pubkey,
rent: Rent,
message: &'a Message,
instruction: &'a CompiledInstruction,
executable_accounts: &'a [(Pubkey, Rc<RefCell<AccountSharedData>>)],
accounts: &'a [(Pubkey, Rc<RefCell<AccountSharedData>>)],
programs: &'a [(Pubkey, ProcessInstructionWithContext)],
log_collector: Option<Rc<LogCollector>>,
compute_budget: ComputeBudget,
compute_meter: Rc<RefCell<dyn ComputeMeter>>,
executors: Rc<RefCell<Executors>>,
instruction_recorder: Option<InstructionRecorder>,
feature_set: Arc<FeatureSet>,
account_db: Arc<Accounts>,
ancestors: &'a Ancestors,
blockhash: &'a Hash,
fee_calculator: &'a FeeCalculator,
) -> Self {
let pre_accounts = MessageProcessor::create_pre_accounts(message, instruction, accounts);
let keyed_accounts = InstructionProcessor::create_keyed_accounts(
message,
instruction,
executable_accounts,
accounts,
feature_set.is_active(&demote_program_write_locks::id()),
);
let compute_meter = if feature_set.is_active(&tx_wide_compute_cap::id()) {
compute_meter
} else {
Rc::new(RefCell::new(ThisComputeMeter {
remaining: compute_budget.max_units,
}))
};
let mut invoke_context = Self {
invoke_stack: Vec::with_capacity(compute_budget.max_invoke_depth),
rent,
pre_accounts,
accounts,
programs,
logger: Rc::new(RefCell::new(ThisLogger { log_collector })),
compute_budget,
bpf_compute_budget: compute_budget.into(),
compute_meter,
executors,
instruction_recorder,
feature_set,
timings: ExecuteDetailsTimings::default(),
account_db,
ancestors,
sysvars: RefCell::new(vec![]),
blockhash,
fee_calculator,
};
invoke_context
.invoke_stack
.push(InvokeContextStackFrame::new(
*program_id,
create_keyed_accounts_unified(&keyed_accounts),
));
invoke_context
}
}
impl<'a> InvokeContext for ThisInvokeContext<'a> {
fn push(
&mut self,
key: &Pubkey,
keyed_accounts: &[(bool, bool, &Pubkey, &RefCell<AccountSharedData>)],
) -> Result<(), InstructionError> {
if self.invoke_stack.len() > self.compute_budget.max_invoke_depth {
return Err(InstructionError::CallDepth);
}
let contains = self.invoke_stack.iter().any(|frame| frame.key == *key);
let is_last = if let Some(last_frame) = self.invoke_stack.last() {
last_frame.key == *key
} else {
false
};
if contains && !is_last {
// Reentrancy not allowed unless caller is calling itself
return Err(InstructionError::ReentrancyNotAllowed);
}
// Alias the keys and account references in the provided keyed_accounts
// with the ones already existing in self, so that the lifetime 'a matches.
fn transmute_lifetime<'a, 'b, T: Sized>(value: &'a T) -> &'b T {
unsafe { std::mem::transmute(value) }
}
let keyed_accounts = keyed_accounts
.iter()
.map(|(is_signer, is_writable, search_key, account)| {
self.accounts
.iter()
.position(|(key, _account)| key == *search_key)
.map(|index| {
// TODO
// Currently we are constructing new accounts on the stack
// before calling MessageProcessor::process_cross_program_instruction
// Ideally we would recycle the existing accounts here.
(
*is_signer,
*is_writable,
&self.accounts[index].0,
// &self.accounts[index] as &RefCell<AccountSharedData>
transmute_lifetime(*account),
)
})
})
.collect::<Option<Vec<_>>>()
.ok_or(InstructionError::InvalidArgument)?;
self.invoke_stack.push(InvokeContextStackFrame::new(
*key,
create_keyed_accounts_unified(keyed_accounts.as_slice()),
));
Ok(())
}
fn pop(&mut self) {
self.invoke_stack.pop();
}
fn invoke_depth(&self) -> usize {
self.invoke_stack.len()
}
fn verify_and_update(
&mut self,
instruction: &CompiledInstruction,
accounts: &[(Pubkey, Rc<RefCell<AccountSharedData>>)],
write_privileges: &[bool],
) -> Result<(), InstructionError> {
let stack_frame = self
.invoke_stack
.last()
.ok_or(InstructionError::CallDepth)?;
let logger = self.get_logger();
InstructionProcessor::verify_and_update(
instruction,
&mut self.pre_accounts,
accounts,
&stack_frame.key,
&self.rent,
write_privileges,
&mut self.timings,
logger,
self.feature_set.is_active(&updated_verify_policy::id()),
)
}
fn get_caller(&self) -> Result<&Pubkey, InstructionError> {
self.invoke_stack
.last()
.map(|frame| &frame.key)
.ok_or(InstructionError::CallDepth)
}
fn remove_first_keyed_account(&mut self) -> Result<(), InstructionError> {
let stack_frame = &mut self
.invoke_stack
.last_mut()
.ok_or(InstructionError::CallDepth)?;
stack_frame.keyed_accounts_range.start =
stack_frame.keyed_accounts_range.start.saturating_add(1);
Ok(())
}
fn get_keyed_accounts(&self) -> Result<&[KeyedAccount], InstructionError> {
self.invoke_stack
.last()
.map(|frame| &frame.keyed_accounts[frame.keyed_accounts_range.clone()])
.ok_or(InstructionError::CallDepth)
}
fn get_programs(&self) -> &[(Pubkey, ProcessInstructionWithContext)] {
self.programs
}
fn get_logger(&self) -> Rc<RefCell<dyn Logger>> {
self.logger.clone()
}
#[allow(deprecated)]
fn get_bpf_compute_budget(&self) -> &solana_sdk::process_instruction::BpfComputeBudget {
&self.bpf_compute_budget
}
fn get_compute_meter(&self) -> Rc<RefCell<dyn ComputeMeter>> {
self.compute_meter.clone()
}
fn add_executor(&self, pubkey: &Pubkey, executor: Arc<dyn Executor>) {
self.executors.borrow_mut().insert(*pubkey, executor);
}
fn get_executor(&self, pubkey: &Pubkey) -> Option<Arc<dyn Executor>> {
self.executors.borrow().get(pubkey)
}
fn record_instruction(&self, instruction: &Instruction) {
if let Some(recorder) = &self.instruction_recorder {
recorder.record_instruction(instruction.clone());
}
}
fn is_feature_active(&self, feature_id: &Pubkey) -> bool {
self.feature_set.is_active(feature_id)
}
fn get_account(&self, pubkey: &Pubkey) -> Option<Rc<RefCell<AccountSharedData>>> {
self.accounts.iter().find_map(|(key, account)| {
if key == pubkey {
Some(account.clone())
} else {
None
}
})
}
fn update_timing(
&mut self,
serialize_us: u64,
create_vm_us: u64,
execute_us: u64,
deserialize_us: u64,
) {
self.timings.serialize_us += serialize_us;
self.timings.create_vm_us += create_vm_us;
self.timings.execute_us += execute_us;
self.timings.deserialize_us += deserialize_us;
}
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
let mut result = sysvars
.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()));
}
result
} else {
None
}
}
fn get_compute_budget(&self) -> &ComputeBudget {
&self.compute_budget
}
fn get_blockhash(&self) -> &Hash {
self.blockhash
}
fn get_fee_calculator(&self) -> &FeeCalculator {
self.fee_calculator
}
}
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);
}
}
}
#[derive(Debug, Default, Clone, Deserialize, Serialize)]
pub struct MessageProcessor {
#[serde(skip)]
instruction_processor: InstructionProcessor,
}
#[cfg(RUSTC_WITH_SPECIALIZATION)]
impl ::solana_frozen_abi::abi_example::AbiExample for MessageProcessor {
fn example() -> Self {
// MessageProcessor's fields are #[serde(skip)]-ed and not Serialize
// so, just rely on Default anyway.
MessageProcessor::default()
}
}
impl MessageProcessor {
/// Add a static entrypoint to intercept instructions before the dynamic loader.
pub fn add_program(
&mut self,
program_id: Pubkey,
process_instruction: ProcessInstructionWithContext,
) {
self.instruction_processor
.add_program(program_id, process_instruction);
}
/// Record the initial state of the accounts so that they can be compared
/// after the instruction is processed
pub fn create_pre_accounts(
message: &Message,
instruction: &CompiledInstruction,
accounts: &[(Pubkey, Rc<RefCell<AccountSharedData>>)],
) -> Vec<PreAccount> {
let mut pre_accounts = Vec::with_capacity(instruction.accounts.len());
{
let mut work = |_unique_index: usize, account_index: usize| {
if account_index < message.account_keys.len() && account_index < accounts.len() {
let account = accounts[account_index].1.borrow();
pre_accounts.push(PreAccount::new(&accounts[account_index].0, &account));
return Ok(());
}
Err(InstructionError::MissingAccount)
};
let _ = instruction.visit_each_account(&mut work);
}
pre_accounts
}
/// Verify there are no outstanding borrows
pub fn verify_account_references(
accounts: &[(Pubkey, Rc<RefCell<AccountSharedData>>)],
) -> Result<(), InstructionError> {
for (_, account) in accounts.iter() {
account
.try_borrow_mut()
.map_err(|_| InstructionError::AccountBorrowOutstanding)?;
}
Ok(())
}
/// Verify the results of an instruction
#[allow(clippy::too_many_arguments)]
pub fn verify(
message: &Message,
instruction: &CompiledInstruction,
pre_accounts: &[PreAccount],
executable_accounts: &[(Pubkey, Rc<RefCell<AccountSharedData>>)],
accounts: &[(Pubkey, Rc<RefCell<AccountSharedData>>)],
rent: &Rent,
timings: &mut ExecuteDetailsTimings,
logger: Rc<RefCell<dyn Logger>>,
updated_verify_policy: bool,
demote_program_write_locks: bool,
) -> Result<(), InstructionError> {
// Verify all executable accounts have zero outstanding refs
Self::verify_account_references(executable_accounts)?;
// Verify the per-account instruction results
let (mut pre_sum, mut post_sum) = (0_u128, 0_u128);
{
let program_id = instruction.program_id(&message.account_keys);
let mut work = |unique_index: usize, account_index: usize| {
{
// Verify account has no outstanding references
let _ = accounts[account_index]
.1
.try_borrow_mut()
.map_err(|_| InstructionError::AccountBorrowOutstanding)?;
}
let account = accounts[account_index].1.borrow();
pre_accounts[unique_index]
.verify(
program_id,
message.is_writable(account_index, demote_program_write_locks),
rent,
&account,
timings,
true,
updated_verify_policy,
)
.map_err(|err| {
ic_logger_msg!(
logger,
"failed to verify account {}: {}",
pre_accounts[unique_index].key(),
err
);
err
})?;
pre_sum += u128::from(pre_accounts[unique_index].lamports());
post_sum += u128::from(account.lamports());
Ok(())
};
instruction.visit_each_account(&mut work)?;
}
// Verify that the total sum of all the lamports did not change
if pre_sum != post_sum {
return Err(InstructionError::UnbalancedInstruction);
}
Ok(())
}
/// Execute an instruction
/// This method calls the instruction's program entrypoint method and verifies that the result of
/// the call does not violate the bank's accounting rules.
/// The accounts are committed back to the bank only if this function returns Ok(_).
#[allow(clippy::too_many_arguments)]
fn execute_instruction(
&self,
message: &Message,
instruction: &CompiledInstruction,
executable_accounts: &[(Pubkey, Rc<RefCell<AccountSharedData>>)],
accounts: &[(Pubkey, Rc<RefCell<AccountSharedData>>)],
rent_collector: &RentCollector,
log_collector: Option<Rc<LogCollector>>,
executors: Rc<RefCell<Executors>>,
instruction_recorder: Option<InstructionRecorder>,
instruction_index: usize,
feature_set: Arc<FeatureSet>,
compute_budget: ComputeBudget,
compute_meter: Rc<RefCell<dyn ComputeMeter>>,
timings: &mut ExecuteDetailsTimings,
account_db: Arc<Accounts>,
ancestors: &Ancestors,
blockhash: &Hash,
fee_calculator: &FeeCalculator,
) -> Result<(), InstructionError> {
// Fixup the special instructions key if present
// before the account pre-values are taken care of
for (pubkey, accont) in accounts.iter().take(message.account_keys.len()) {
if instructions::check_id(pubkey) {
let mut mut_account_ref = accont.borrow_mut();
instructions::store_current_index(
mut_account_ref.data_as_mut_slice(),
instruction_index as u16,
);
break;
}
}
let program_id = instruction.program_id(&message.account_keys);
let mut compute_budget = compute_budget;
if feature_set.is_active(&neon_evm_compute_budget::id())
&& *program_id == crate::neon_evm_program::id()
{
// Bump the compute budget for neon_evm
compute_budget.max_units = compute_budget.max_units.max(500_000);
compute_budget.heap_size = Some(256 * 1024);
}
let programs = self.instruction_processor.programs();
let mut invoke_context = ThisInvokeContext::new(
program_id,
rent_collector.rent,
message,
instruction,
executable_accounts,
accounts,
programs,
log_collector,
compute_budget,
compute_meter,
executors,
instruction_recorder,
feature_set,
account_db,
ancestors,
blockhash,
fee_calculator,
);
self.instruction_processor.process_instruction(
program_id,
&instruction.data,
&mut invoke_context,
)?;
Self::verify(
message,
instruction,
&invoke_context.pre_accounts,
executable_accounts,
accounts,
&rent_collector.rent,
timings,
invoke_context.get_logger(),
invoke_context.is_feature_active(&updated_verify_policy::id()),
invoke_context.is_feature_active(&demote_program_write_locks::id()),
)?;
timings.accumulate(&invoke_context.timings);
Ok(())
}
/// Process a message.
/// This method calls each instruction in the message over the set of loaded Accounts
/// The accounts are committed back to the bank only if every instruction succeeds
#[allow(clippy::too_many_arguments)]
#[allow(clippy::type_complexity)]
pub fn process_message(
&self,
message: &Message,
loaders: &[Vec<(Pubkey, Rc<RefCell<AccountSharedData>>)>],
accounts: &[(Pubkey, Rc<RefCell<AccountSharedData>>)],
rent_collector: &RentCollector,
log_collector: Option<Rc<LogCollector>>,
executors: Rc<RefCell<Executors>>,
instruction_recorders: Option<&[InstructionRecorder]>,
feature_set: Arc<FeatureSet>,
compute_budget: ComputeBudget,
compute_meter: Rc<RefCell<dyn ComputeMeter>>,
timings: &mut ExecuteDetailsTimings,
account_db: Arc<Accounts>,
ancestors: &Ancestors,
blockhash: Hash,
fee_calculator: FeeCalculator,
) -> Result<(), TransactionError> {
for (instruction_index, instruction) in message.instructions.iter().enumerate() {
let mut time = Measure::start("execute_instruction");
let pre_remaining_units = compute_meter.borrow().get_remaining();
let instruction_recorder = instruction_recorders
.as_ref()
.map(|recorders| recorders[instruction_index].clone());
let err = self
.execute_instruction(
message,
instruction,
&loaders[instruction_index],
accounts,
rent_collector,
log_collector.clone(),
executors.clone(),
instruction_recorder,
instruction_index,
feature_set.clone(),
compute_budget,
compute_meter.clone(),
timings,
account_db.clone(),
ancestors,
&blockhash,
&fee_calculator,
)
.map_err(|err| TransactionError::InstructionError(instruction_index as u8, err));
time.stop();
let post_remaining_units = compute_meter.borrow().get_remaining();
timings.accumulate_program(
instruction.program_id(&message.account_keys),
time.as_us(),
pre_remaining_units - post_remaining_units,
);
err?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use solana_sdk::{
instruction::{AccountMeta, Instruction, InstructionError},
message::Message,
native_loader::{self, create_loadable_account_for_test},
process_instruction::MockComputeMeter,
};
#[test]
fn test_invoke_context() {
const MAX_DEPTH: usize = 10;
let mut invoke_stack = vec![];
let mut accounts = vec![];
let mut metas = vec![];
for i in 0..MAX_DEPTH {
invoke_stack.push(solana_sdk::pubkey::new_rand());
accounts.push((
solana_sdk::pubkey::new_rand(),
Rc::new(RefCell::new(AccountSharedData::new(
i as u64,
1,
&invoke_stack[i],
))),
));
metas.push(AccountMeta::new(accounts[i].0, false));
}
for program_id in invoke_stack.iter() {
accounts.push((
*program_id,
Rc::new(RefCell::new(AccountSharedData::new(
1,
1,
&solana_sdk::pubkey::Pubkey::default(),
))),
));
metas.push(AccountMeta::new(*program_id, false));
}
let message = Message::new(
&[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(
&invoke_stack[0],
Rent::default(),
&message,
&message.instructions[0],
&[],
&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,
);
// Check call depth increases and has a limit
let mut depth_reached = 1;
for program_id in invoke_stack.iter().skip(1) {
if Err(InstructionError::CallDepth) == invoke_context.push(program_id, &[]) {
break;
}
depth_reached += 1;
}
assert_ne!(depth_reached, 0);
assert!(depth_reached < MAX_DEPTH);
// Mock each invocation
for owned_index in (1..depth_reached).rev() {
let not_owned_index = owned_index - 1;
let metas = vec![
AccountMeta::new(accounts[not_owned_index].0, false),
AccountMeta::new(accounts[owned_index].0, false),
];
let message = Message::new(
&[Instruction::new_with_bytes(
invoke_stack[owned_index],
&[0],
metas,
)],
None,
);
// modify account owned by the program
accounts[owned_index].1.borrow_mut().data_as_mut_slice()[0] =
(MAX_DEPTH + owned_index) as u8;
let mut these_accounts = accounts[not_owned_index..owned_index + 1].to_vec();
these_accounts.push((
message.account_keys[2],
Rc::new(RefCell::new(AccountSharedData::new(
1,
1,
&solana_sdk::pubkey::Pubkey::default(),
))),
));
let write_privileges: Vec<bool> = (0..message.account_keys.len())
.map(|i| message.is_writable(i, /*demote_program_write_locks=*/ true))
.collect();
invoke_context
.verify_and_update(&message.instructions[0], &these_accounts, &write_privileges)
.unwrap();
assert_eq!(
invoke_context.pre_accounts[owned_index].data()[0],
(MAX_DEPTH + owned_index) as u8
);
// modify account not owned by the program
let data = accounts[not_owned_index].1.borrow_mut().data()[0];
accounts[not_owned_index].1.borrow_mut().data_as_mut_slice()[0] =
(MAX_DEPTH + not_owned_index) as u8;
assert_eq!(
invoke_context.verify_and_update(
&message.instructions[0],
&accounts[not_owned_index..owned_index + 1],
&write_privileges,
),
Err(InstructionError::ExternalAccountDataModified)
);
assert_eq!(invoke_context.pre_accounts[not_owned_index].data()[0], data);
accounts[not_owned_index].1.borrow_mut().data_as_mut_slice()[0] = data;
invoke_context.pop();
}
}
#[test]
fn test_verify_account_references() {
let accounts = vec![(
solana_sdk::pubkey::new_rand(),
Rc::new(RefCell::new(AccountSharedData::default())),
)];
assert!(MessageProcessor::verify_account_references(&accounts).is_ok());
let mut _borrowed = accounts[0].1.borrow();
assert_eq!(
MessageProcessor::verify_account_references(&accounts),
Err(InstructionError::AccountBorrowOutstanding)
);
}
#[test]
fn test_process_message_readonly_handling() {
#[derive(Serialize, Deserialize)]
enum MockSystemInstruction {
Correct,
AttemptCredit { lamports: u64 },
AttemptDataChange { data: u8 },
}
fn mock_system_process_instruction(
_program_id: &Pubkey,
data: &[u8],
invoke_context: &mut dyn InvokeContext,
) -> Result<(), InstructionError> {
let keyed_accounts = invoke_context.get_keyed_accounts()?;
if let Ok(instruction) = bincode::deserialize(data) {
match instruction {
MockSystemInstruction::Correct => Ok(()),
MockSystemInstruction::AttemptCredit { lamports } => {
keyed_accounts[0]
.account
.borrow_mut()
.checked_sub_lamports(lamports)?;
keyed_accounts[1]
.account
.borrow_mut()
.checked_add_lamports(lamports)?;
Ok(())
}
// Change data in a read-only account
MockSystemInstruction::AttemptDataChange { data } => {
keyed_accounts[1].account.borrow_mut().set_data(vec![data]);
Ok(())
}
}
} else {
Err(InstructionError::InvalidInstructionData)
}
}
let mock_system_program_id = Pubkey::new(&[2u8; 32]);
let rent_collector = RentCollector::default();
let mut message_processor = MessageProcessor::default();
message_processor.add_program(mock_system_program_id, mock_system_process_instruction);
let accounts = vec![
(
solana_sdk::pubkey::new_rand(),
AccountSharedData::new_ref(100, 1, &mock_system_program_id),
),
(
solana_sdk::pubkey::new_rand(),
AccountSharedData::new_ref(0, 1, &mock_system_program_id),
),
];
let account = Rc::new(RefCell::new(create_loadable_account_for_test(
"mock_system_program",
)));
let loaders = vec![vec![(mock_system_program_id, account)]];
let executors = Rc::new(RefCell::new(Executors::default()));
let ancestors = Ancestors::default();
let account_metas = vec![
AccountMeta::new(accounts[0].0, true),
AccountMeta::new_readonly(accounts[1].0, false),
];
let message = Message::new(
&[Instruction::new_with_bincode(
mock_system_program_id,
&MockSystemInstruction::Correct,
account_metas.clone(),
)],
Some(&accounts[0].0),
);
let result = message_processor.process_message(
&message,
&loaders,
&accounts,
&rent_collector,
None,
executors.clone(),
None,
Arc::new(FeatureSet::all_enabled()),
ComputeBudget::new(),
Rc::new(RefCell::new(MockComputeMeter::default())),
&mut ExecuteDetailsTimings::default(),
Arc::new(Accounts::default_for_tests()),
&ancestors,
Hash::default(),
FeeCalculator::default(),
);
assert_eq!(result, Ok(()));
assert_eq!(accounts[0].1.borrow().lamports(), 100);
assert_eq!(accounts[1].1.borrow().lamports(), 0);
let message = Message::new(
&[Instruction::new_with_bincode(
mock_system_program_id,
&MockSystemInstruction::AttemptCredit { lamports: 50 },
account_metas.clone(),
)],
Some(&accounts[0].0),
);
let result = message_processor.process_message(
&message,
&loaders,
&accounts,
&rent_collector,
None,
executors.clone(),
None,
Arc::new(FeatureSet::all_enabled()),
ComputeBudget::new(),
Rc::new(RefCell::new(MockComputeMeter::default())),
&mut ExecuteDetailsTimings::default(),
Arc::new(Accounts::default_for_tests()),
&ancestors,
Hash::default(),
FeeCalculator::default(),
);
assert_eq!(
result,
Err(TransactionError::InstructionError(
0,
InstructionError::ReadonlyLamportChange
))
);
let message = Message::new(
&[Instruction::new_with_bincode(
mock_system_program_id,
&MockSystemInstruction::AttemptDataChange { data: 50 },
account_metas,
)],
Some(&accounts[0].0),
);
let result = message_processor.process_message(
&message,
&loaders,
&accounts,
&rent_collector,
None,
executors,
None,
Arc::new(FeatureSet::all_enabled()),
ComputeBudget::new(),
Rc::new(RefCell::new(MockComputeMeter::default())),
&mut ExecuteDetailsTimings::default(),
Arc::new(Accounts::default_for_tests()),
&ancestors,
Hash::default(),
FeeCalculator::default(),
);
assert_eq!(
result,
Err(TransactionError::InstructionError(
0,
InstructionError::ReadonlyDataModified
))
);
}
#[test]
fn test_process_message_duplicate_accounts() {
#[derive(Serialize, Deserialize)]
enum MockSystemInstruction {
BorrowFail,
MultiBorrowMut,
DoWork { lamports: u64, data: u8 },
}
fn mock_system_process_instruction(
_program_id: &Pubkey,
data: &[u8],
invoke_context: &mut dyn InvokeContext,
) -> Result<(), InstructionError> {
let keyed_accounts = invoke_context.get_keyed_accounts()?;
if let Ok(instruction) = bincode::deserialize(data) {
match instruction {
MockSystemInstruction::BorrowFail => {
let from_account = keyed_accounts[0].try_account_ref_mut()?;
let dup_account = keyed_accounts[2].try_account_ref_mut()?;
if from_account.lamports() != dup_account.lamports() {
return Err(InstructionError::InvalidArgument);
}
Ok(())
}
MockSystemInstruction::MultiBorrowMut => {
let from_lamports = {
let from_account = keyed_accounts[0].try_account_ref_mut()?;
from_account.lamports()
};
let dup_lamports = {
let dup_account = keyed_accounts[2].try_account_ref_mut()?;
dup_account.lamports()
};
if from_lamports != dup_lamports {
return Err(InstructionError::InvalidArgument);
}
Ok(())
}
MockSystemInstruction::DoWork { lamports, data } => {
{
let mut to_account = keyed_accounts[1].try_account_ref_mut()?;
let mut dup_account = keyed_accounts[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[0]
.try_account_ref_mut()?
.checked_sub_lamports(lamports)?;
keyed_accounts[1]
.try_account_ref_mut()?
.checked_add_lamports(lamports)?;
Ok(())
}
}
} else {
Err(InstructionError::InvalidInstructionData)
}
}
let mock_program_id = Pubkey::new(&[2u8; 32]);
let rent_collector = RentCollector::default();
let mut message_processor = MessageProcessor::default();
message_processor.add_program(mock_program_id, mock_system_process_instruction);
let accounts = vec![
(
solana_sdk::pubkey::new_rand(),
AccountSharedData::new_ref(100, 1, &mock_program_id),
),
(
solana_sdk::pubkey::new_rand(),
AccountSharedData::new_ref(0, 1, &mock_program_id),
),
];
let account = Rc::new(RefCell::new(create_loadable_account_for_test(
"mock_system_program",
)));
let loaders = vec![vec![(mock_program_id, account)]];
let executors = Rc::new(RefCell::new(Executors::default()));
let ancestors = Ancestors::default();
let account_metas = vec![
AccountMeta::new(accounts[0].0, true),
AccountMeta::new(accounts[1].0, false),
AccountMeta::new(accounts[0].0, false),
];
// Try to borrow mut the same account
let message = Message::new(
&[Instruction::new_with_bincode(
mock_program_id,
&MockSystemInstruction::BorrowFail,
account_metas.clone(),
)],
Some(&accounts[0].0),
);
let result = message_processor.process_message(
&message,
&loaders,
&accounts,
&rent_collector,
None,
executors.clone(),
None,
Arc::new(FeatureSet::all_enabled()),
ComputeBudget::new(),
Rc::new(RefCell::new(MockComputeMeter::default())),
&mut ExecuteDetailsTimings::default(),
Arc::new(Accounts::default_for_tests()),
&ancestors,
Hash::default(),
FeeCalculator::default(),
);
assert_eq!(
result,
Err(TransactionError::InstructionError(
0,
InstructionError::AccountBorrowFailed
))
);
// Try to borrow mut the same account in a safe way
let message = Message::new(
&[Instruction::new_with_bincode(
mock_program_id,
&MockSystemInstruction::MultiBorrowMut,
account_metas.clone(),
)],
Some(&accounts[0].0),
);
let result = message_processor.process_message(
&message,
&loaders,
&accounts,
&rent_collector,
None,
executors.clone(),
None,
Arc::new(FeatureSet::all_enabled()),
ComputeBudget::new(),
Rc::new(RefCell::new(MockComputeMeter::default())),
&mut ExecuteDetailsTimings::default(),
Arc::new(Accounts::default_for_tests()),
&ancestors,
Hash::default(),
FeeCalculator::default(),
);
assert_eq!(result, Ok(()));
// Do work on the same account but at different location in keyed_accounts[]
let message = Message::new(
&[Instruction::new_with_bincode(
mock_program_id,
&MockSystemInstruction::DoWork {
lamports: 10,
data: 42,
},
account_metas,
)],
Some(&accounts[0].0),
);
let ancestors = Ancestors::default();
let result = message_processor.process_message(
&message,
&loaders,
&accounts,
&rent_collector,
None,
executors,
None,
Arc::new(FeatureSet::all_enabled()),
ComputeBudget::new(),
Rc::new(RefCell::new(MockComputeMeter::default())),
&mut ExecuteDetailsTimings::default(),
Arc::new(Accounts::default_for_tests()),
&ancestors,
Hash::default(),
FeeCalculator::default(),
);
assert_eq!(result, Ok(()));
assert_eq!(accounts[0].1.borrow().lamports(), 80);
assert_eq!(accounts[1].1.borrow().lamports(), 20);
assert_eq!(accounts[0].1.borrow().data(), &vec![42]);
}
#[test]
fn test_process_cross_program() {
#[derive(Debug, Serialize, Deserialize)]
enum MockInstruction {
NoopSuccess,
NoopFail,
ModifyOwned,
ModifyNotOwned,
}
fn mock_process_instruction(
program_id: &Pubkey,
data: &[u8],
invoke_context: &mut dyn InvokeContext,
) -> Result<(), InstructionError> {
let keyed_accounts = invoke_context.get_keyed_accounts()?;
assert_eq!(*program_id, keyed_accounts[0].owner()?);
assert_ne!(
keyed_accounts[1].owner()?,
*keyed_accounts[0].unsigned_key()
);
if let Ok(instruction) = bincode::deserialize(data) {
match instruction {
MockInstruction::NoopSuccess => (),
MockInstruction::NoopFail => return Err(InstructionError::GenericError),
MockInstruction::ModifyOwned => {
keyed_accounts[0].try_account_ref_mut()?.data_as_mut_slice()[0] = 1
}
MockInstruction::ModifyNotOwned => {
keyed_accounts[1].try_account_ref_mut()?.data_as_mut_slice()[0] = 1
}
}
} else {
return Err(InstructionError::InvalidInstructionData);
}
Ok(())
}
let caller_program_id = solana_sdk::pubkey::new_rand();
let callee_program_id = solana_sdk::pubkey::new_rand();
let mut program_account = AccountSharedData::new(1, 0, &native_loader::id());
program_account.set_executable(true);
let executable_accounts = vec![(
callee_program_id,
Rc::new(RefCell::new(program_account.clone())),
)];
let owned_account = AccountSharedData::new(42, 1, &callee_program_id);
let not_owned_account = AccountSharedData::new(84, 1, &solana_sdk::pubkey::new_rand());
#[allow(unused_mut)]
let accounts = vec![
(
solana_sdk::pubkey::new_rand(),
Rc::new(RefCell::new(owned_account)),
),
(
solana_sdk::pubkey::new_rand(),
Rc::new(RefCell::new(not_owned_account)),
),
(callee_program_id, Rc::new(RefCell::new(program_account))),
];
let compiled_instruction = CompiledInstruction::new(2, &(), vec![0, 1, 2]);
let programs: Vec<(_, ProcessInstructionWithContext)> =
vec![(callee_program_id, mock_process_instruction)];
let metas = vec![
AccountMeta::new(accounts[0].0, false),
AccountMeta::new(accounts[1].0, false),
];
let instruction = Instruction::new_with_bincode(
callee_program_id,
&MockInstruction::NoopSuccess,
metas.clone(),
);
let message = Message::new(&[instruction], None);
let feature_set = FeatureSet::all_enabled();
let demote_program_write_locks = feature_set.is_active(&demote_program_write_locks::id());
let ancestors = Ancestors::default();
let blockhash = Hash::default();
let fee_calculator = FeeCalculator::default();
let mut invoke_context = ThisInvokeContext::new(
&caller_program_id,
Rent::default(),
&message,
&compiled_instruction,
&executable_accounts,
&accounts,
programs.as_slice(),
None,
ComputeBudget::default(),
Rc::new(RefCell::new(MockComputeMeter::default())),
Rc::new(RefCell::new(Executors::default())),
None,
Arc::new(feature_set),
Arc::new(Accounts::default_for_tests()),
&ancestors,
&blockhash,
&fee_calculator,
);
// not owned account modified by the caller (before the invoke)
let caller_write_privileges = message
.account_keys
.iter()
.enumerate()
.map(|(i, _)| message.is_writable(i, demote_program_write_locks))
.collect::<Vec<bool>>();
accounts[0].1.borrow_mut().data_as_mut_slice()[0] = 1;
assert_eq!(
InstructionProcessor::process_cross_program_instruction(
&message,
&executable_accounts,
&accounts,
&caller_write_privileges,
&mut invoke_context,
),
Err(InstructionError::ExternalAccountDataModified)
);
accounts[0].1.borrow_mut().data_as_mut_slice()[0] = 0;
let cases = vec![
(MockInstruction::NoopSuccess, Ok(())),
(
MockInstruction::NoopFail,
Err(InstructionError::GenericError),
),
(MockInstruction::ModifyOwned, Ok(())),
(
MockInstruction::ModifyNotOwned,
Err(InstructionError::ExternalAccountDataModified),
),
];
for case in cases {
let instruction =
Instruction::new_with_bincode(callee_program_id, &case.0, metas.clone());
let message = Message::new(&[instruction], None);
let ancestors = Ancestors::default();
let blockhash = Hash::default();
let fee_calculator = FeeCalculator::default();
let mut invoke_context = ThisInvokeContext::new(
&caller_program_id,
Rent::default(),
&message,
&compiled_instruction,
&executable_accounts,
&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 caller_write_privileges = message
.account_keys
.iter()
.enumerate()
.map(|(i, _)| message.is_writable(i, demote_program_write_locks))
.collect::<Vec<bool>>();
assert_eq!(
InstructionProcessor::process_cross_program_instruction(
&message,
&executable_accounts,
&accounts,
&caller_write_privileges,
&mut invoke_context,
),
case.1
);
}
}
}