Refactor: Cleanup InstructionProcessor (#21404)
* Moves create_message(), native_invoke() and process_cross_program_instruction() from the InstructionProcessor to the InvokeContext so that they can have a useful "self" parameter. * Moves InstructionProcessor into InvokeContext and Bank. * Moves ExecuteDetailsTimings into its own file. * Moves Executor into invoke_context.rs * Moves PreAccount into its own file. * impl AbiExample for BuiltinPrograms
This commit is contained in:
committed by
GitHub
parent
e922c2da9d
commit
b78f5b6032
@ -1,11 +1,11 @@
|
||||
use crate::{
|
||||
ic_logger_msg, ic_msg,
|
||||
instruction_processor::{ExecuteDetailsTimings, Executor, Executors, PreAccount},
|
||||
instruction_recorder::InstructionRecorder,
|
||||
log_collector::LogCollector,
|
||||
ic_logger_msg, ic_msg, instruction_recorder::InstructionRecorder, log_collector::LogCollector,
|
||||
native_loader::NativeLoader, pre_account::PreAccount, timings::ExecuteDetailsTimings,
|
||||
};
|
||||
use solana_sdk::{
|
||||
account::{AccountSharedData, ReadableAccount},
|
||||
account_utils::StateMut,
|
||||
bpf_loader_upgradeable::{self, UpgradeableLoaderState},
|
||||
compute_budget::ComputeBudget,
|
||||
feature_set::{
|
||||
demote_program_write_locks, do_support_realloc, neon_evm_compute_budget,
|
||||
@ -13,13 +13,66 @@ use solana_sdk::{
|
||||
},
|
||||
hash::Hash,
|
||||
instruction::{AccountMeta, CompiledInstruction, Instruction, InstructionError},
|
||||
keyed_account::{create_keyed_accounts_unified, KeyedAccount},
|
||||
keyed_account::{create_keyed_accounts_unified, keyed_account_at_index, KeyedAccount},
|
||||
message::Message,
|
||||
pubkey::Pubkey,
|
||||
rent::Rent,
|
||||
sysvar::Sysvar,
|
||||
};
|
||||
use std::{cell::RefCell, rc::Rc, sync::Arc};
|
||||
use std::{cell::RefCell, collections::HashMap, fmt::Debug, rc::Rc, sync::Arc};
|
||||
|
||||
pub type ProcessInstructionWithContext =
|
||||
fn(usize, &[u8], &mut dyn InvokeContext) -> Result<(), InstructionError>;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct BuiltinProgram {
|
||||
pub program_id: Pubkey,
|
||||
pub process_instruction: ProcessInstructionWithContext,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for BuiltinProgram {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
// These are just type aliases for work around of Debug-ing above pointers
|
||||
type ErasedProcessInstructionWithContext = fn(
|
||||
usize,
|
||||
&'static [u8],
|
||||
&'static mut dyn InvokeContext,
|
||||
) -> Result<(), InstructionError>;
|
||||
|
||||
// rustc doesn't compile due to bug without this work around
|
||||
// https://github.com/rust-lang/rust/issues/50280
|
||||
// https://users.rust-lang.org/t/display-function-pointer/17073/2
|
||||
let erased_instruction: ErasedProcessInstructionWithContext = self.process_instruction;
|
||||
write!(f, "{}: {:p}", self.program_id, erased_instruction)
|
||||
}
|
||||
}
|
||||
|
||||
/// Program executor
|
||||
pub trait Executor: Debug + Send + Sync {
|
||||
/// Execute the program
|
||||
fn execute(
|
||||
&self,
|
||||
first_instruction_account: usize,
|
||||
instruction_data: &[u8],
|
||||
invoke_context: &mut dyn InvokeContext,
|
||||
use_jit: bool,
|
||||
) -> Result<(), InstructionError>;
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Executors {
|
||||
pub executors: HashMap<Pubkey, Arc<dyn Executor>>,
|
||||
pub is_dirty: bool,
|
||||
}
|
||||
impl Executors {
|
||||
pub fn insert(&mut self, key: Pubkey, executor: Arc<dyn Executor>) {
|
||||
let _ = self.executors.insert(key, executor);
|
||||
self.is_dirty = true;
|
||||
}
|
||||
pub fn get(&self, key: &Pubkey) -> Option<Arc<dyn Executor>> {
|
||||
self.executors.get(key).cloned()
|
||||
}
|
||||
}
|
||||
|
||||
/// Compute meter
|
||||
pub struct ComputeMeter {
|
||||
@ -77,7 +130,7 @@ pub struct ThisInvokeContext<'a> {
|
||||
rent: Rent,
|
||||
pre_accounts: Vec<PreAccount>,
|
||||
accounts: &'a [(Pubkey, Rc<RefCell<AccountSharedData>>)],
|
||||
programs: &'a [(Pubkey, ProcessInstructionWithContext)],
|
||||
builtin_programs: &'a [BuiltinProgram],
|
||||
sysvars: &'a [(Pubkey, Vec<u8>)],
|
||||
log_collector: Option<Rc<RefCell<LogCollector>>>,
|
||||
compute_budget: ComputeBudget,
|
||||
@ -96,7 +149,7 @@ impl<'a> ThisInvokeContext<'a> {
|
||||
pub fn new(
|
||||
rent: Rent,
|
||||
accounts: &'a [(Pubkey, Rc<RefCell<AccountSharedData>>)],
|
||||
programs: &'a [(Pubkey, ProcessInstructionWithContext)],
|
||||
builtin_programs: &'a [BuiltinProgram],
|
||||
sysvars: &'a [(Pubkey, Vec<u8>)],
|
||||
log_collector: Option<Rc<RefCell<LogCollector>>>,
|
||||
compute_budget: ComputeBudget,
|
||||
@ -113,7 +166,7 @@ impl<'a> ThisInvokeContext<'a> {
|
||||
rent,
|
||||
pre_accounts: Vec::new(),
|
||||
accounts,
|
||||
programs,
|
||||
builtin_programs,
|
||||
sysvars,
|
||||
log_collector,
|
||||
current_compute_budget: compute_budget,
|
||||
@ -131,14 +184,14 @@ impl<'a> ThisInvokeContext<'a> {
|
||||
|
||||
pub fn new_mock_with_sysvars_and_features(
|
||||
accounts: &'a [(Pubkey, Rc<RefCell<AccountSharedData>>)],
|
||||
programs: &'a [(Pubkey, ProcessInstructionWithContext)],
|
||||
builtin_programs: &'a [BuiltinProgram],
|
||||
sysvars: &'a [(Pubkey, Vec<u8>)],
|
||||
feature_set: Arc<FeatureSet>,
|
||||
) -> Self {
|
||||
Self::new(
|
||||
Rent::default(),
|
||||
accounts,
|
||||
programs,
|
||||
builtin_programs,
|
||||
sysvars,
|
||||
None,
|
||||
ComputeBudget::default(),
|
||||
@ -153,11 +206,11 @@ impl<'a> ThisInvokeContext<'a> {
|
||||
|
||||
pub fn new_mock(
|
||||
accounts: &'a [(Pubkey, Rc<RefCell<AccountSharedData>>)],
|
||||
programs: &'a [(Pubkey, ProcessInstructionWithContext)],
|
||||
builtin_programs: &'a [BuiltinProgram],
|
||||
) -> Self {
|
||||
Self::new_mock_with_sysvars_and_features(
|
||||
accounts,
|
||||
programs,
|
||||
builtin_programs,
|
||||
&[],
|
||||
Arc::new(FeatureSet::all_enabled()),
|
||||
)
|
||||
@ -192,6 +245,28 @@ pub trait InvokeContext {
|
||||
account_indices: &[usize],
|
||||
write_privileges: &[bool],
|
||||
) -> Result<(), InstructionError>;
|
||||
/// Entrypoint for a cross-program invocation from a native program
|
||||
fn native_invoke(
|
||||
&mut self,
|
||||
instruction: Instruction,
|
||||
signers: &[Pubkey],
|
||||
) -> Result<(), InstructionError>;
|
||||
/// Helper to prepare for process_cross_program_instruction()
|
||||
fn create_message(
|
||||
&mut self,
|
||||
instruction: &Instruction,
|
||||
signers: &[Pubkey],
|
||||
) -> Result<(Message, Vec<bool>, Vec<usize>), InstructionError>;
|
||||
/// Process a cross-program instruction
|
||||
fn process_cross_program_instruction(
|
||||
&mut self,
|
||||
message: &Message,
|
||||
program_indices: &[usize],
|
||||
account_indices: &[usize],
|
||||
caller_write_privileges: &[bool],
|
||||
) -> Result<(), InstructionError>;
|
||||
/// Calls the instruction's program entrypoint method
|
||||
fn process_instruction(&mut self, instruction_data: &[u8]) -> Result<(), InstructionError>;
|
||||
/// Get the program ID of the currently executing program
|
||||
fn get_caller(&self) -> Result<&Pubkey, InstructionError>;
|
||||
/// Removes the first keyed account
|
||||
@ -202,8 +277,6 @@ pub trait InvokeContext {
|
||||
fn remove_first_keyed_account(&mut self) -> Result<(), InstructionError>;
|
||||
/// Get the list of keyed accounts
|
||||
fn get_keyed_accounts(&self) -> Result<&[KeyedAccount], InstructionError>;
|
||||
/// Get a list of built-in programs
|
||||
fn get_programs(&self) -> &[(Pubkey, ProcessInstructionWithContext)];
|
||||
/// Get this invocation's LogCollector
|
||||
fn get_log_collector(&self) -> Option<Rc<RefCell<LogCollector>>>;
|
||||
/// Get this invocation's ComputeMeter
|
||||
@ -497,6 +570,228 @@ impl<'a> InvokeContext for ThisInvokeContext<'a> {
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
fn native_invoke(
|
||||
&mut self,
|
||||
instruction: Instruction,
|
||||
signers: &[Pubkey],
|
||||
) -> Result<(), InstructionError> {
|
||||
let (message, caller_write_privileges, program_indices) =
|
||||
self.create_message(&instruction, signers)?;
|
||||
let mut account_indices = Vec::with_capacity(message.account_keys.len());
|
||||
let mut prev_account_sizes = Vec::with_capacity(message.account_keys.len());
|
||||
for account_key in message.account_keys.iter() {
|
||||
let (account_index, account) = self
|
||||
.get_account(account_key)
|
||||
.ok_or(InstructionError::MissingAccount)?;
|
||||
let account_length = account.borrow().data().len();
|
||||
account_indices.push(account_index);
|
||||
prev_account_sizes.push((account, account_length));
|
||||
}
|
||||
|
||||
self.record_instruction(&instruction);
|
||||
self.process_cross_program_instruction(
|
||||
&message,
|
||||
&program_indices,
|
||||
&account_indices,
|
||||
&caller_write_privileges,
|
||||
)?;
|
||||
|
||||
// Verify the called program has not misbehaved
|
||||
let do_support_realloc = self.is_feature_active(&do_support_realloc::id());
|
||||
for (account, prev_size) in prev_account_sizes.iter() {
|
||||
if !do_support_realloc && *prev_size != account.borrow().data().len() && *prev_size != 0
|
||||
{
|
||||
// Only support for `CreateAccount` at this time.
|
||||
// Need a way to limit total realloc size across multiple CPI calls
|
||||
ic_msg!(
|
||||
self,
|
||||
"Inner instructions do not support realloc, only SystemProgram::CreateAccount",
|
||||
);
|
||||
return Err(InstructionError::InvalidRealloc);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
fn create_message(
|
||||
&mut self,
|
||||
instruction: &Instruction,
|
||||
signers: &[Pubkey],
|
||||
) -> Result<(Message, Vec<bool>, Vec<usize>), InstructionError> {
|
||||
let message = Message::new(&[instruction.clone()], None);
|
||||
|
||||
// Gather keyed_accounts in the order of message.account_keys
|
||||
let caller_keyed_accounts = self.get_keyed_accounts()?;
|
||||
let callee_keyed_accounts = message
|
||||
.account_keys
|
||||
.iter()
|
||||
.map(|account_key| {
|
||||
caller_keyed_accounts
|
||||
.iter()
|
||||
.find(|keyed_account| keyed_account.unsigned_key() == account_key)
|
||||
.ok_or_else(|| {
|
||||
ic_msg!(
|
||||
self,
|
||||
"Instruction references an unknown account {}",
|
||||
account_key
|
||||
);
|
||||
InstructionError::MissingAccount
|
||||
})
|
||||
})
|
||||
.collect::<Result<Vec<_>, InstructionError>>()?;
|
||||
|
||||
// Check for privilege escalation
|
||||
for account in instruction.accounts.iter() {
|
||||
let keyed_account = callee_keyed_accounts
|
||||
.iter()
|
||||
.find_map(|keyed_account| {
|
||||
if &account.pubkey == keyed_account.unsigned_key() {
|
||||
Some(keyed_account)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.ok_or_else(|| {
|
||||
ic_msg!(
|
||||
self,
|
||||
"Instruction references an unknown account {}",
|
||||
account.pubkey
|
||||
);
|
||||
InstructionError::MissingAccount
|
||||
})?;
|
||||
// Readonly account cannot become writable
|
||||
if account.is_writable && !keyed_account.is_writable() {
|
||||
ic_msg!(self, "{}'s writable privilege escalated", account.pubkey);
|
||||
return Err(InstructionError::PrivilegeEscalation);
|
||||
}
|
||||
|
||||
if account.is_signer && // If message indicates account is signed
|
||||
!( // one of the following needs to be true:
|
||||
keyed_account.signer_key().is_some() // Signed in the parent instruction
|
||||
|| signers.contains(&account.pubkey) // Signed by the program
|
||||
) {
|
||||
ic_msg!(self, "{}'s signer privilege escalated", account.pubkey);
|
||||
return Err(InstructionError::PrivilegeEscalation);
|
||||
}
|
||||
}
|
||||
let caller_write_privileges = callee_keyed_accounts
|
||||
.iter()
|
||||
.map(|keyed_account| keyed_account.is_writable())
|
||||
.collect::<Vec<bool>>();
|
||||
|
||||
// Find and validate executables / program accounts
|
||||
let callee_program_id = instruction.program_id;
|
||||
let (program_account_index, program_account) = callee_keyed_accounts
|
||||
.iter()
|
||||
.find(|keyed_account| &callee_program_id == keyed_account.unsigned_key())
|
||||
.and_then(|_keyed_account| self.get_account(&callee_program_id))
|
||||
.ok_or_else(|| {
|
||||
ic_msg!(self, "Unknown program {}", callee_program_id);
|
||||
InstructionError::MissingAccount
|
||||
})?;
|
||||
if !program_account.borrow().executable() {
|
||||
ic_msg!(self, "Account {} is not executable", callee_program_id);
|
||||
return Err(InstructionError::AccountNotExecutable);
|
||||
}
|
||||
let mut program_indices = vec![];
|
||||
if program_account.borrow().owner() == &bpf_loader_upgradeable::id() {
|
||||
if let UpgradeableLoaderState::Program {
|
||||
programdata_address,
|
||||
} = program_account.borrow().state()?
|
||||
{
|
||||
if let Some((programdata_account_index, _programdata_account)) =
|
||||
self.get_account(&programdata_address)
|
||||
{
|
||||
program_indices.push(programdata_account_index);
|
||||
} else {
|
||||
ic_msg!(
|
||||
self,
|
||||
"Unknown upgradeable programdata account {}",
|
||||
programdata_address,
|
||||
);
|
||||
return Err(InstructionError::MissingAccount);
|
||||
}
|
||||
} else {
|
||||
ic_msg!(
|
||||
self,
|
||||
"Invalid upgradeable program account {}",
|
||||
callee_program_id,
|
||||
);
|
||||
return Err(InstructionError::MissingAccount);
|
||||
}
|
||||
}
|
||||
program_indices.push(program_account_index);
|
||||
|
||||
Ok((message, caller_write_privileges, program_indices))
|
||||
}
|
||||
fn process_cross_program_instruction(
|
||||
&mut self,
|
||||
message: &Message,
|
||||
program_indices: &[usize],
|
||||
account_indices: &[usize],
|
||||
caller_write_privileges: &[bool],
|
||||
) -> Result<(), InstructionError> {
|
||||
// This function is always called with a valid instruction, if that changes return an error
|
||||
let instruction = message
|
||||
.instructions
|
||||
.get(0)
|
||||
.ok_or(InstructionError::GenericError)?;
|
||||
|
||||
// Verify the calling program hasn't misbehaved
|
||||
self.verify_and_update(instruction, account_indices, caller_write_privileges)?;
|
||||
|
||||
self.set_return_data(Vec::new())?;
|
||||
self.push(message, instruction, program_indices, Some(account_indices))?;
|
||||
let result = self.process_instruction(&instruction.data).and_then(|_| {
|
||||
// Verify the called program has not misbehaved
|
||||
let demote_program_write_locks =
|
||||
self.is_feature_active(&demote_program_write_locks::id());
|
||||
let write_privileges: Vec<bool> = (0..message.account_keys.len())
|
||||
.map(|i| message.is_writable(i, demote_program_write_locks))
|
||||
.collect();
|
||||
self.verify_and_update(instruction, account_indices, &write_privileges)
|
||||
});
|
||||
|
||||
// Restore previous state
|
||||
self.pop();
|
||||
result
|
||||
}
|
||||
fn process_instruction(&mut self, instruction_data: &[u8]) -> Result<(), InstructionError> {
|
||||
let keyed_accounts = self.get_keyed_accounts()?;
|
||||
let root_account = keyed_account_at_index(keyed_accounts, 0)
|
||||
.map_err(|_| InstructionError::UnsupportedProgramId)?;
|
||||
let root_id = root_account.unsigned_key();
|
||||
let owner_id = &root_account.owner()?;
|
||||
if solana_sdk::native_loader::check_id(owner_id) {
|
||||
for entry in self.builtin_programs {
|
||||
if entry.program_id == *root_id {
|
||||
// Call the builtin program
|
||||
return (entry.process_instruction)(
|
||||
1, // root_id to be skipped
|
||||
instruction_data,
|
||||
self,
|
||||
);
|
||||
}
|
||||
}
|
||||
if !self.is_feature_active(&remove_native_loader::id()) {
|
||||
let native_loader = NativeLoader::default();
|
||||
// Call the program via the native loader
|
||||
return native_loader.process_instruction(0, instruction_data, self);
|
||||
}
|
||||
} else {
|
||||
for entry in self.builtin_programs {
|
||||
if entry.program_id == *owner_id {
|
||||
// Call the program via a builtin loader
|
||||
return (entry.process_instruction)(
|
||||
0, // no root_id was provided
|
||||
instruction_data,
|
||||
self,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(InstructionError::UnsupportedProgramId)
|
||||
}
|
||||
fn get_caller(&self) -> Result<&Pubkey, InstructionError> {
|
||||
self.invoke_stack
|
||||
.last()
|
||||
@ -520,9 +815,6 @@ impl<'a> InvokeContext for ThisInvokeContext<'a> {
|
||||
.map(|frame| &frame.keyed_accounts[frame.keyed_accounts_range.clone()])
|
||||
.ok_or(InstructionError::CallDepth)
|
||||
}
|
||||
fn get_programs(&self) -> &[(Pubkey, ProcessInstructionWithContext)] {
|
||||
self.programs
|
||||
}
|
||||
fn get_log_collector(&self) -> Option<Rc<RefCell<LogCollector>>> {
|
||||
self.log_collector.clone()
|
||||
}
|
||||
@ -615,9 +907,6 @@ pub fn get_sysvar<T: Sysvar>(
|
||||
})
|
||||
}
|
||||
|
||||
pub type ProcessInstructionWithContext =
|
||||
fn(usize, &[u8], &mut dyn InvokeContext) -> Result<(), InstructionError>;
|
||||
|
||||
pub struct MockInvokeContextPreparation {
|
||||
pub accounts: Vec<(Pubkey, Rc<RefCell<AccountSharedData>>)>,
|
||||
pub message: Message,
|
||||
@ -743,12 +1032,10 @@ pub fn mock_process_instruction(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::instruction_processor::InstructionProcessor;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use solana_sdk::{
|
||||
account::{ReadableAccount, WritableAccount},
|
||||
instruction::{AccountMeta, Instruction, InstructionError},
|
||||
keyed_account::keyed_account_at_index,
|
||||
message::Message,
|
||||
native_loader,
|
||||
};
|
||||
@ -762,6 +1049,37 @@ mod tests {
|
||||
ModifyReadonly,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_program_entry_debug() {
|
||||
#[allow(clippy::unnecessary_wraps)]
|
||||
fn mock_process_instruction(
|
||||
_first_instruction_account: usize,
|
||||
_data: &[u8],
|
||||
_invoke_context: &mut dyn InvokeContext,
|
||||
) -> Result<(), InstructionError> {
|
||||
Ok(())
|
||||
}
|
||||
#[allow(clippy::unnecessary_wraps)]
|
||||
fn mock_ix_processor(
|
||||
_first_instruction_account: usize,
|
||||
_data: &[u8],
|
||||
_context: &mut dyn InvokeContext,
|
||||
) -> Result<(), InstructionError> {
|
||||
Ok(())
|
||||
}
|
||||
let builtin_programs = &[
|
||||
BuiltinProgram {
|
||||
program_id: solana_sdk::pubkey::new_rand(),
|
||||
process_instruction: mock_process_instruction,
|
||||
},
|
||||
BuiltinProgram {
|
||||
program_id: solana_sdk::pubkey::new_rand(),
|
||||
process_instruction: mock_ix_processor,
|
||||
},
|
||||
];
|
||||
assert!(!format!("{:?}", builtin_programs).is_empty());
|
||||
}
|
||||
|
||||
#[allow(clippy::integer_arithmetic)]
|
||||
fn mock_process_instruction(
|
||||
first_instruction_account: usize,
|
||||
@ -973,8 +1291,6 @@ mod tests {
|
||||
let account_indices = [0, 1, 2];
|
||||
let program_indices = [3, 4];
|
||||
|
||||
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),
|
||||
@ -990,7 +1306,11 @@ mod tests {
|
||||
);
|
||||
let message = Message::new(&[callee_instruction], None);
|
||||
|
||||
let mut invoke_context = ThisInvokeContext::new_mock(&accounts, programs.as_slice());
|
||||
let builtin_programs = &[BuiltinProgram {
|
||||
program_id: callee_program_id,
|
||||
process_instruction: mock_process_instruction,
|
||||
}];
|
||||
let mut invoke_context = ThisInvokeContext::new_mock(&accounts, builtin_programs);
|
||||
invoke_context
|
||||
.push(&message, &caller_instruction, &program_indices[..1], None)
|
||||
.unwrap();
|
||||
@ -1006,12 +1326,11 @@ mod tests {
|
||||
.collect::<Vec<bool>>();
|
||||
accounts[0].1.borrow_mut().data_as_mut_slice()[0] = 1;
|
||||
assert_eq!(
|
||||
InstructionProcessor::process_cross_program_instruction(
|
||||
invoke_context.process_cross_program_instruction(
|
||||
&message,
|
||||
&program_indices[1..],
|
||||
&account_indices,
|
||||
&caller_write_privileges,
|
||||
&mut invoke_context,
|
||||
),
|
||||
Err(InstructionError::ExternalAccountDataModified)
|
||||
);
|
||||
@ -1020,12 +1339,11 @@ mod tests {
|
||||
// readonly account modified by the invoker
|
||||
accounts[2].1.borrow_mut().data_as_mut_slice()[0] = 1;
|
||||
assert_eq!(
|
||||
InstructionProcessor::process_cross_program_instruction(
|
||||
invoke_context.process_cross_program_instruction(
|
||||
&message,
|
||||
&program_indices[1..],
|
||||
&account_indices,
|
||||
&caller_write_privileges,
|
||||
&mut invoke_context,
|
||||
),
|
||||
Err(InstructionError::ReadonlyDataModified)
|
||||
);
|
||||
@ -1059,12 +1377,11 @@ mod tests {
|
||||
.map(|(i, _)| message.is_writable(i, demote_program_write_locks))
|
||||
.collect::<Vec<bool>>();
|
||||
assert_eq!(
|
||||
InstructionProcessor::process_cross_program_instruction(
|
||||
invoke_context.process_cross_program_instruction(
|
||||
&message,
|
||||
&program_indices[1..],
|
||||
&account_indices,
|
||||
&caller_write_privileges,
|
||||
&mut invoke_context,
|
||||
),
|
||||
case.1
|
||||
);
|
||||
@ -1101,8 +1418,6 @@ mod tests {
|
||||
(callee_program_id, Rc::new(RefCell::new(program_account))),
|
||||
];
|
||||
let program_indices = [3];
|
||||
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),
|
||||
@ -1118,7 +1433,11 @@ mod tests {
|
||||
);
|
||||
let message = Message::new(&[callee_instruction.clone()], None);
|
||||
|
||||
let mut invoke_context = ThisInvokeContext::new_mock(&accounts, programs.as_slice());
|
||||
let builtin_programs = &[BuiltinProgram {
|
||||
program_id: callee_program_id,
|
||||
process_instruction: mock_process_instruction,
|
||||
}];
|
||||
let mut invoke_context = ThisInvokeContext::new_mock(&accounts, builtin_programs);
|
||||
invoke_context
|
||||
.push(&message, &caller_instruction, &program_indices, None)
|
||||
.unwrap();
|
||||
@ -1126,11 +1445,7 @@ mod tests {
|
||||
// not owned account modified by the invoker
|
||||
accounts[0].1.borrow_mut().data_as_mut_slice()[0] = 1;
|
||||
assert_eq!(
|
||||
InstructionProcessor::native_invoke(
|
||||
&mut invoke_context,
|
||||
callee_instruction.clone(),
|
||||
&[]
|
||||
),
|
||||
invoke_context.native_invoke(callee_instruction.clone(), &[]),
|
||||
Err(InstructionError::ExternalAccountDataModified)
|
||||
);
|
||||
accounts[0].1.borrow_mut().data_as_mut_slice()[0] = 0;
|
||||
@ -1138,7 +1453,7 @@ mod tests {
|
||||
// readonly account modified by the invoker
|
||||
accounts[2].1.borrow_mut().data_as_mut_slice()[0] = 1;
|
||||
assert_eq!(
|
||||
InstructionProcessor::native_invoke(&mut invoke_context, callee_instruction, &[]),
|
||||
invoke_context.native_invoke(callee_instruction, &[]),
|
||||
Err(InstructionError::ReadonlyDataModified)
|
||||
);
|
||||
accounts[2].1.borrow_mut().data_as_mut_slice()[0] = 0;
|
||||
@ -1170,7 +1485,7 @@ mod tests {
|
||||
.push(&message, &caller_instruction, &program_indices, None)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
InstructionProcessor::native_invoke(&mut invoke_context, callee_instruction, &[]),
|
||||
invoke_context.native_invoke(callee_instruction, &[]),
|
||||
case.1
|
||||
);
|
||||
invoke_context.pop();
|
||||
|
@ -1,9 +1,10 @@
|
||||
#![cfg_attr(RUSTC_WITH_SPECIALIZATION, feature(min_specialization))]
|
||||
|
||||
pub mod instruction_processor;
|
||||
pub mod instruction_recorder;
|
||||
pub mod invoke_context;
|
||||
pub mod log_collector;
|
||||
pub mod native_loader;
|
||||
pub mod neon_evm_program;
|
||||
pub mod pre_account;
|
||||
pub mod stable_log;
|
||||
pub mod timings;
|
||||
|
@ -1,111 +1,18 @@
|
||||
use crate::{
|
||||
ic_msg,
|
||||
invoke_context::{InvokeContext, ProcessInstructionWithContext},
|
||||
native_loader::NativeLoader,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use crate::timings::ExecuteDetailsTimings;
|
||||
use solana_sdk::{
|
||||
account::{AccountSharedData, ReadableAccount, WritableAccount},
|
||||
account_utils::StateMut,
|
||||
bpf_loader_upgradeable::{self, UpgradeableLoaderState},
|
||||
feature_set::{demote_program_write_locks, do_support_realloc, remove_native_loader},
|
||||
instruction::{Instruction, InstructionError},
|
||||
keyed_account::keyed_account_at_index,
|
||||
message::Message,
|
||||
instruction::InstructionError,
|
||||
pubkey::Pubkey,
|
||||
rent::Rent,
|
||||
system_instruction::MAX_PERMITTED_DATA_LENGTH,
|
||||
system_program,
|
||||
};
|
||||
use std::{
|
||||
cell::{Ref, RefCell, RefMut},
|
||||
collections::HashMap,
|
||||
cell::{Ref, RefCell},
|
||||
fmt::Debug,
|
||||
rc::Rc,
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
/// Program executor
|
||||
pub trait Executor: Debug + Send + Sync {
|
||||
/// Execute the program
|
||||
fn execute(
|
||||
&self,
|
||||
first_instruction_account: usize,
|
||||
instruction_data: &[u8],
|
||||
invoke_context: &mut dyn InvokeContext,
|
||||
use_jit: bool,
|
||||
) -> Result<(), InstructionError>;
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Executors {
|
||||
pub executors: HashMap<Pubkey, Arc<dyn Executor>>,
|
||||
pub is_dirty: bool,
|
||||
}
|
||||
impl Executors {
|
||||
pub fn insert(&mut self, key: Pubkey, executor: Arc<dyn Executor>) {
|
||||
let _ = self.executors.insert(key, executor);
|
||||
self.is_dirty = true;
|
||||
}
|
||||
pub fn get(&self, key: &Pubkey) -> Option<Arc<dyn Executor>> {
|
||||
self.executors.get(key).cloned()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct ProgramTiming {
|
||||
pub accumulated_us: u64,
|
||||
pub accumulated_units: u64,
|
||||
pub count: u32,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct ExecuteDetailsTimings {
|
||||
pub serialize_us: u64,
|
||||
pub create_vm_us: u64,
|
||||
pub execute_us: u64,
|
||||
pub deserialize_us: u64,
|
||||
pub changed_account_count: u64,
|
||||
pub total_account_count: u64,
|
||||
pub total_data_size: usize,
|
||||
pub data_size_changed: usize,
|
||||
pub per_program_timings: HashMap<Pubkey, ProgramTiming>,
|
||||
}
|
||||
impl ExecuteDetailsTimings {
|
||||
pub fn accumulate(&mut self, other: &ExecuteDetailsTimings) {
|
||||
self.serialize_us = self.serialize_us.saturating_add(other.serialize_us);
|
||||
self.create_vm_us = self.create_vm_us.saturating_add(other.create_vm_us);
|
||||
self.execute_us = self.execute_us.saturating_add(other.execute_us);
|
||||
self.deserialize_us = self.deserialize_us.saturating_add(other.deserialize_us);
|
||||
self.changed_account_count = self
|
||||
.changed_account_count
|
||||
.saturating_add(other.changed_account_count);
|
||||
self.total_account_count = self
|
||||
.total_account_count
|
||||
.saturating_add(other.total_account_count);
|
||||
self.total_data_size = self.total_data_size.saturating_add(other.total_data_size);
|
||||
self.data_size_changed = self
|
||||
.data_size_changed
|
||||
.saturating_add(other.data_size_changed);
|
||||
for (id, other) in &other.per_program_timings {
|
||||
let program_timing = self.per_program_timings.entry(*id).or_default();
|
||||
program_timing.accumulated_us = program_timing
|
||||
.accumulated_us
|
||||
.saturating_add(other.accumulated_us);
|
||||
program_timing.accumulated_units = program_timing
|
||||
.accumulated_units
|
||||
.saturating_add(other.accumulated_units);
|
||||
program_timing.count = program_timing.count.saturating_add(other.count);
|
||||
}
|
||||
}
|
||||
pub fn accumulate_program(&mut self, program_id: &Pubkey, us: u64, units: u64) {
|
||||
let program_timing = self.per_program_timings.entry(*program_id).or_default();
|
||||
program_timing.accumulated_us = program_timing.accumulated_us.saturating_add(us);
|
||||
program_timing.accumulated_units = program_timing.accumulated_units.saturating_add(units);
|
||||
program_timing.count = program_timing.count.saturating_add(1);
|
||||
}
|
||||
}
|
||||
|
||||
// The relevant state of an account before an Instruction executes, used
|
||||
// to verify account integrity after the Instruction completes
|
||||
#[derive(Clone, Debug, Default)]
|
||||
@ -283,361 +190,6 @@ impl PreAccount {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Deserialize, Serialize)]
|
||||
pub struct InstructionProcessor {
|
||||
#[serde(skip)]
|
||||
programs: Vec<(Pubkey, ProcessInstructionWithContext)>,
|
||||
#[serde(skip)]
|
||||
native_loader: NativeLoader,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for InstructionProcessor {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
#[derive(Debug)]
|
||||
struct MessageProcessor<'a> {
|
||||
#[allow(dead_code)]
|
||||
programs: Vec<String>,
|
||||
#[allow(dead_code)]
|
||||
native_loader: &'a NativeLoader,
|
||||
}
|
||||
|
||||
// These are just type aliases for work around of Debug-ing above pointers
|
||||
type ErasedProcessInstructionWithContext = fn(
|
||||
usize,
|
||||
&'static [u8],
|
||||
&'static mut dyn InvokeContext,
|
||||
) -> Result<(), InstructionError>;
|
||||
|
||||
// rustc doesn't compile due to bug without this work around
|
||||
// https://github.com/rust-lang/rust/issues/50280
|
||||
// https://users.rust-lang.org/t/display-function-pointer/17073/2
|
||||
let processor = MessageProcessor {
|
||||
programs: self
|
||||
.programs
|
||||
.iter()
|
||||
.map(|(pubkey, instruction)| {
|
||||
let erased_instruction: ErasedProcessInstructionWithContext = *instruction;
|
||||
format!("{}: {:p}", pubkey, erased_instruction)
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
native_loader: &self.native_loader,
|
||||
};
|
||||
|
||||
write!(f, "{:?}", processor)
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for InstructionProcessor {
|
||||
fn clone(&self) -> Self {
|
||||
InstructionProcessor {
|
||||
programs: self.programs.clone(),
|
||||
native_loader: NativeLoader::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(RUSTC_WITH_SPECIALIZATION)]
|
||||
impl ::solana_frozen_abi::abi_example::AbiExample for InstructionProcessor {
|
||||
fn example() -> Self {
|
||||
// MessageProcessor's fields are #[serde(skip)]-ed and not Serialize
|
||||
// so, just rely on Default anyway.
|
||||
InstructionProcessor::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl InstructionProcessor {
|
||||
pub fn programs(&self) -> &[(Pubkey, ProcessInstructionWithContext)] {
|
||||
&self.programs
|
||||
}
|
||||
|
||||
/// Add a static entrypoint to intercept instructions before the dynamic loader.
|
||||
pub fn add_program(
|
||||
&mut self,
|
||||
program_id: &Pubkey,
|
||||
process_instruction: ProcessInstructionWithContext,
|
||||
) {
|
||||
match self.programs.iter_mut().find(|(key, _)| program_id == key) {
|
||||
Some((_, processor)) => *processor = process_instruction,
|
||||
None => self.programs.push((*program_id, process_instruction)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove a program
|
||||
pub fn remove_program(&mut self, program_id: &Pubkey) {
|
||||
if let Some(position) = self.programs.iter().position(|(key, _)| program_id == key) {
|
||||
self.programs.remove(position);
|
||||
}
|
||||
}
|
||||
|
||||
/// Process an instruction
|
||||
/// This method calls the instruction's program entrypoint method
|
||||
pub fn process_instruction(
|
||||
&self,
|
||||
instruction_data: &[u8],
|
||||
invoke_context: &mut dyn InvokeContext,
|
||||
) -> Result<(), InstructionError> {
|
||||
let keyed_accounts = invoke_context.get_keyed_accounts()?;
|
||||
let root_account = keyed_account_at_index(keyed_accounts, 0)
|
||||
.map_err(|_| InstructionError::UnsupportedProgramId)?;
|
||||
let root_id = root_account.unsigned_key();
|
||||
let owner_id = &root_account.owner()?;
|
||||
if solana_sdk::native_loader::check_id(owner_id) {
|
||||
for (id, process_instruction) in &self.programs {
|
||||
if id == root_id {
|
||||
// Call the builtin program
|
||||
return process_instruction(
|
||||
1, // root_id to be skipped
|
||||
instruction_data,
|
||||
invoke_context,
|
||||
);
|
||||
}
|
||||
}
|
||||
if !invoke_context.is_feature_active(&remove_native_loader::id()) {
|
||||
// Call the program via the native loader
|
||||
return self
|
||||
.native_loader
|
||||
.process_instruction(0, instruction_data, invoke_context);
|
||||
}
|
||||
} else {
|
||||
for (id, process_instruction) in &self.programs {
|
||||
if id == owner_id {
|
||||
// Call the program via a builtin loader
|
||||
return process_instruction(
|
||||
0, // no root_id was provided
|
||||
instruction_data,
|
||||
invoke_context,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(InstructionError::UnsupportedProgramId)
|
||||
}
|
||||
|
||||
pub fn create_message(
|
||||
instruction: &Instruction,
|
||||
signers: &[Pubkey],
|
||||
invoke_context: &RefMut<&mut dyn InvokeContext>,
|
||||
) -> Result<(Message, Vec<bool>, Vec<usize>), InstructionError> {
|
||||
let message = Message::new(&[instruction.clone()], None);
|
||||
|
||||
// Gather keyed_accounts in the order of message.account_keys
|
||||
let caller_keyed_accounts = invoke_context.get_keyed_accounts()?;
|
||||
let callee_keyed_accounts = message
|
||||
.account_keys
|
||||
.iter()
|
||||
.map(|account_key| {
|
||||
caller_keyed_accounts
|
||||
.iter()
|
||||
.find(|keyed_account| keyed_account.unsigned_key() == account_key)
|
||||
.ok_or_else(|| {
|
||||
ic_msg!(
|
||||
*invoke_context,
|
||||
"Instruction references an unknown account {}",
|
||||
account_key
|
||||
);
|
||||
InstructionError::MissingAccount
|
||||
})
|
||||
})
|
||||
.collect::<Result<Vec<_>, InstructionError>>()?;
|
||||
|
||||
// Check for privilege escalation
|
||||
for account in instruction.accounts.iter() {
|
||||
let keyed_account = callee_keyed_accounts
|
||||
.iter()
|
||||
.find_map(|keyed_account| {
|
||||
if &account.pubkey == keyed_account.unsigned_key() {
|
||||
Some(keyed_account)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.ok_or_else(|| {
|
||||
ic_msg!(
|
||||
invoke_context,
|
||||
"Instruction references an unknown account {}",
|
||||
account.pubkey
|
||||
);
|
||||
InstructionError::MissingAccount
|
||||
})?;
|
||||
// Readonly account cannot become writable
|
||||
if account.is_writable && !keyed_account.is_writable() {
|
||||
ic_msg!(
|
||||
invoke_context,
|
||||
"{}'s writable privilege escalated",
|
||||
account.pubkey
|
||||
);
|
||||
return Err(InstructionError::PrivilegeEscalation);
|
||||
}
|
||||
|
||||
if account.is_signer && // If message indicates account is signed
|
||||
!( // one of the following needs to be true:
|
||||
keyed_account.signer_key().is_some() // Signed in the parent instruction
|
||||
|| signers.contains(&account.pubkey) // Signed by the program
|
||||
) {
|
||||
ic_msg!(
|
||||
invoke_context,
|
||||
"{}'s signer privilege escalated",
|
||||
account.pubkey
|
||||
);
|
||||
return Err(InstructionError::PrivilegeEscalation);
|
||||
}
|
||||
}
|
||||
let caller_write_privileges = callee_keyed_accounts
|
||||
.iter()
|
||||
.map(|keyed_account| keyed_account.is_writable())
|
||||
.collect::<Vec<bool>>();
|
||||
|
||||
// Find and validate executables / program accounts
|
||||
let callee_program_id = instruction.program_id;
|
||||
let (program_account_index, program_account) = callee_keyed_accounts
|
||||
.iter()
|
||||
.find(|keyed_account| &callee_program_id == keyed_account.unsigned_key())
|
||||
.and_then(|_keyed_account| invoke_context.get_account(&callee_program_id))
|
||||
.ok_or_else(|| {
|
||||
ic_msg!(invoke_context, "Unknown program {}", callee_program_id);
|
||||
InstructionError::MissingAccount
|
||||
})?;
|
||||
if !program_account.borrow().executable() {
|
||||
ic_msg!(
|
||||
invoke_context,
|
||||
"Account {} is not executable",
|
||||
callee_program_id
|
||||
);
|
||||
return Err(InstructionError::AccountNotExecutable);
|
||||
}
|
||||
let mut program_indices = vec![];
|
||||
if program_account.borrow().owner() == &bpf_loader_upgradeable::id() {
|
||||
if let UpgradeableLoaderState::Program {
|
||||
programdata_address,
|
||||
} = program_account.borrow().state()?
|
||||
{
|
||||
if let Some((programdata_account_index, _programdata_account)) =
|
||||
invoke_context.get_account(&programdata_address)
|
||||
{
|
||||
program_indices.push(programdata_account_index);
|
||||
} else {
|
||||
ic_msg!(
|
||||
invoke_context,
|
||||
"Unknown upgradeable programdata account {}",
|
||||
programdata_address,
|
||||
);
|
||||
return Err(InstructionError::MissingAccount);
|
||||
}
|
||||
} else {
|
||||
ic_msg!(
|
||||
invoke_context,
|
||||
"Invalid upgradeable program account {}",
|
||||
callee_program_id,
|
||||
);
|
||||
return Err(InstructionError::MissingAccount);
|
||||
}
|
||||
}
|
||||
program_indices.push(program_account_index);
|
||||
|
||||
Ok((message, caller_write_privileges, program_indices))
|
||||
}
|
||||
|
||||
/// Entrypoint for a cross-program invocation from a native program
|
||||
pub fn native_invoke(
|
||||
invoke_context: &mut dyn InvokeContext,
|
||||
instruction: Instruction,
|
||||
signers: &[Pubkey],
|
||||
) -> Result<(), InstructionError> {
|
||||
let do_support_realloc = invoke_context.is_feature_active(&do_support_realloc::id());
|
||||
let invoke_context = RefCell::new(invoke_context);
|
||||
let mut invoke_context = invoke_context.borrow_mut();
|
||||
|
||||
// Translate and verify caller's data
|
||||
let (message, caller_write_privileges, program_indices) =
|
||||
Self::create_message(&instruction, signers, &invoke_context)?;
|
||||
let mut account_indices = Vec::with_capacity(message.account_keys.len());
|
||||
let mut accounts = Vec::with_capacity(message.account_keys.len());
|
||||
for account_key in message.account_keys.iter() {
|
||||
let (account_index, account) = invoke_context
|
||||
.get_account(account_key)
|
||||
.ok_or(InstructionError::MissingAccount)?;
|
||||
let account_length = account.borrow().data().len();
|
||||
account_indices.push(account_index);
|
||||
accounts.push((account, account_length));
|
||||
}
|
||||
|
||||
// Record the instruction
|
||||
invoke_context.record_instruction(&instruction);
|
||||
|
||||
// Process instruction
|
||||
InstructionProcessor::process_cross_program_instruction(
|
||||
&message,
|
||||
&program_indices,
|
||||
&account_indices,
|
||||
&caller_write_privileges,
|
||||
*invoke_context,
|
||||
)?;
|
||||
|
||||
// Verify the called program has not misbehaved
|
||||
for (account, prev_size) in accounts.iter() {
|
||||
if !do_support_realloc && *prev_size != account.borrow().data().len() && *prev_size != 0
|
||||
{
|
||||
// Only support for `CreateAccount` at this time.
|
||||
// Need a way to limit total realloc size across multiple CPI calls
|
||||
ic_msg!(
|
||||
invoke_context,
|
||||
"Inner instructions do not support realloc, only SystemProgram::CreateAccount",
|
||||
);
|
||||
return Err(InstructionError::InvalidRealloc);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Process a cross-program instruction
|
||||
/// This method calls the instruction's program entrypoint function
|
||||
pub fn process_cross_program_instruction(
|
||||
message: &Message,
|
||||
program_indices: &[usize],
|
||||
account_indices: &[usize],
|
||||
caller_write_privileges: &[bool],
|
||||
invoke_context: &mut dyn InvokeContext,
|
||||
) -> Result<(), InstructionError> {
|
||||
// This function is always called with a valid instruction, if that changes return an error
|
||||
let instruction = message
|
||||
.instructions
|
||||
.get(0)
|
||||
.ok_or(InstructionError::GenericError)?;
|
||||
|
||||
// Verify the calling program hasn't misbehaved
|
||||
invoke_context.verify_and_update(instruction, account_indices, caller_write_privileges)?;
|
||||
|
||||
// clear the return data
|
||||
invoke_context.set_return_data(Vec::new())?;
|
||||
|
||||
// Invoke callee
|
||||
invoke_context.push(message, instruction, program_indices, Some(account_indices))?;
|
||||
|
||||
let mut instruction_processor = InstructionProcessor::default();
|
||||
for (program_id, process_instruction) in invoke_context.get_programs().iter() {
|
||||
instruction_processor.add_program(program_id, *process_instruction);
|
||||
}
|
||||
|
||||
let mut result =
|
||||
instruction_processor.process_instruction(&instruction.data, invoke_context);
|
||||
if result.is_ok() {
|
||||
// Verify the called program has not misbehaved
|
||||
let demote_program_write_locks =
|
||||
invoke_context.is_feature_active(&demote_program_write_locks::id());
|
||||
let write_privileges: Vec<bool> = (0..message.account_keys.len())
|
||||
.map(|i| message.is_writable(i, demote_program_write_locks))
|
||||
.collect();
|
||||
result =
|
||||
invoke_context.verify_and_update(instruction, account_indices, &write_privileges);
|
||||
}
|
||||
|
||||
// Restore previous state
|
||||
invoke_context.pop();
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@ -1074,30 +626,4 @@ mod tests {
|
||||
"program should not be able to change owner and executable at the same time"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_debug() {
|
||||
let mut instruction_processor = InstructionProcessor::default();
|
||||
#[allow(clippy::unnecessary_wraps)]
|
||||
fn mock_process_instruction(
|
||||
_first_instruction_account: usize,
|
||||
_data: &[u8],
|
||||
_invoke_context: &mut dyn InvokeContext,
|
||||
) -> Result<(), InstructionError> {
|
||||
Ok(())
|
||||
}
|
||||
#[allow(clippy::unnecessary_wraps)]
|
||||
fn mock_ix_processor(
|
||||
_first_instruction_account: usize,
|
||||
_data: &[u8],
|
||||
_context: &mut dyn InvokeContext,
|
||||
) -> Result<(), InstructionError> {
|
||||
Ok(())
|
||||
}
|
||||
let program_id = solana_sdk::pubkey::new_rand();
|
||||
instruction_processor.add_program(&program_id, mock_process_instruction);
|
||||
instruction_processor.add_program(&program_id, mock_ix_processor);
|
||||
|
||||
assert!(!format!("{:?}", instruction_processor).is_empty());
|
||||
}
|
||||
}
|
56
program-runtime/src/timings.rs
Normal file
56
program-runtime/src/timings.rs
Normal file
@ -0,0 +1,56 @@
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct ProgramTiming {
|
||||
pub accumulated_us: u64,
|
||||
pub accumulated_units: u64,
|
||||
pub count: u32,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct ExecuteDetailsTimings {
|
||||
pub serialize_us: u64,
|
||||
pub create_vm_us: u64,
|
||||
pub execute_us: u64,
|
||||
pub deserialize_us: u64,
|
||||
pub changed_account_count: u64,
|
||||
pub total_account_count: u64,
|
||||
pub total_data_size: usize,
|
||||
pub data_size_changed: usize,
|
||||
pub per_program_timings: HashMap<Pubkey, ProgramTiming>,
|
||||
}
|
||||
impl ExecuteDetailsTimings {
|
||||
pub fn accumulate(&mut self, other: &ExecuteDetailsTimings) {
|
||||
self.serialize_us = self.serialize_us.saturating_add(other.serialize_us);
|
||||
self.create_vm_us = self.create_vm_us.saturating_add(other.create_vm_us);
|
||||
self.execute_us = self.execute_us.saturating_add(other.execute_us);
|
||||
self.deserialize_us = self.deserialize_us.saturating_add(other.deserialize_us);
|
||||
self.changed_account_count = self
|
||||
.changed_account_count
|
||||
.saturating_add(other.changed_account_count);
|
||||
self.total_account_count = self
|
||||
.total_account_count
|
||||
.saturating_add(other.total_account_count);
|
||||
self.total_data_size = self.total_data_size.saturating_add(other.total_data_size);
|
||||
self.data_size_changed = self
|
||||
.data_size_changed
|
||||
.saturating_add(other.data_size_changed);
|
||||
for (id, other) in &other.per_program_timings {
|
||||
let program_timing = self.per_program_timings.entry(*id).or_default();
|
||||
program_timing.accumulated_us = program_timing
|
||||
.accumulated_us
|
||||
.saturating_add(other.accumulated_us);
|
||||
program_timing.accumulated_units = program_timing
|
||||
.accumulated_units
|
||||
.saturating_add(other.accumulated_units);
|
||||
program_timing.count = program_timing.count.saturating_add(other.count);
|
||||
}
|
||||
}
|
||||
pub fn accumulate_program(&mut self, program_id: &Pubkey, us: u64, units: u64) {
|
||||
let program_timing = self.per_program_timings.entry(*program_id).or_default();
|
||||
program_timing.accumulated_us = program_timing.accumulated_us.saturating_add(us);
|
||||
program_timing.accumulated_units = program_timing.accumulated_units.saturating_add(units);
|
||||
program_timing.count = program_timing.count.saturating_add(1);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user