Refactor: Use instruction_trace instead of instruction_context_stack (#22988)

* Moves stack_height of the instruction_trace into InstructionContext.

* Uses nesting_level instead of stack_height internally in transaction_context.rs

* Uses the instruction_trace instead of the instruction_context_stack internally.

* Adds feature gate: record_instruction_in_transaction_context_push.
This commit is contained in:
Alexander Meißner
2022-02-09 20:04:49 +01:00
committed by GitHub
parent e545828fc5
commit 226a71f073
6 changed files with 120 additions and 79 deletions

View File

@@ -307,6 +307,10 @@ pub mod disable_bpf_unresolved_symbols_at_runtime {
solana_sdk::declare_id!("4yuaYAj2jGMGTh1sSmi4G2eFscsDq8qjugJXZoBN6YEa");
}
pub mod record_instruction_in_transaction_context_push {
solana_sdk::declare_id!("3aJdcZqxoLpSBxgeYGjPwaYS1zzcByxUDqJkbzWAH1Zb");
}
lazy_static! {
/// Map of feature identifiers to user-visible description
pub static ref FEATURE_NAMES: HashMap<Pubkey, &'static str> = [
@@ -378,6 +382,7 @@ lazy_static! {
(bank_tranaction_count_fix::id(), "Fixes Bank::transaction_count to include all committed transactions, not just successful ones"),
(disable_bpf_deprecated_load_instructions::id(), "Disable ldabs* and ldind* BPF instructions"),
(disable_bpf_unresolved_symbols_at_runtime::id(), "Disable reporting of unresolved BPF symbols at runtime"),
(record_instruction_in_transaction_context_push::id(), "Move the CPI stack overflow check to the end of push"),
/*************** ADD NEW FEATURES HERE ***************/
]
.iter()

View File

@@ -2,7 +2,7 @@
use crate::{
account::{AccountSharedData, ReadableAccount, WritableAccount},
instruction::{InstructionError, TRANSACTION_LEVEL_STACK_HEIGHT},
instruction::InstructionError,
lamports::LamportsError,
pubkey::Pubkey,
sysvar::Sysvar,
@@ -31,7 +31,7 @@ pub struct TransactionContext {
account_keys: Pin<Box<[Pubkey]>>,
accounts: Pin<Box<[RefCell<AccountSharedData>]>>,
instruction_context_capacity: usize,
instruction_context_stack: Vec<InstructionContext>,
instruction_stack: Vec<usize>,
number_of_instructions_at_transaction_level: usize,
instruction_trace: InstructionTrace,
return_data: (Pubkey, Vec<u8>),
@@ -53,7 +53,7 @@ impl TransactionContext {
account_keys: Pin::new(account_keys.into_boxed_slice()),
accounts: Pin::new(accounts.into_boxed_slice()),
instruction_context_capacity,
instruction_context_stack: Vec::with_capacity(instruction_context_capacity),
instruction_stack: Vec::with_capacity(instruction_context_capacity),
number_of_instructions_at_transaction_level,
instruction_trace: Vec::with_capacity(number_of_instructions_at_transaction_level),
return_data: (Pubkey::default(), Vec::new()),
@@ -61,12 +61,7 @@ impl TransactionContext {
}
/// Used by the bank in the runtime to write back the processed accounts and recorded instructions
pub fn deconstruct(
self,
) -> (
Vec<TransactionAccount>,
Vec<Vec<(usize, InstructionContext)>>,
) {
pub fn deconstruct(self) -> (Vec<TransactionAccount>, Vec<Vec<InstructionContext>>) {
(
Vec::from(Pin::into_inner(self.account_keys))
.into_iter()
@@ -82,7 +77,7 @@ impl TransactionContext {
/// Used in mock_process_instruction
pub fn deconstruct_without_keys(self) -> Result<Vec<AccountSharedData>, InstructionError> {
if !self.instruction_context_stack.is_empty() {
if !self.instruction_stack.is_empty() {
return Err(InstructionError::CallDepth);
}
Ok(Vec::from(Pin::into_inner(self.accounts))
@@ -137,15 +132,30 @@ impl TransactionContext {
self.account_keys.iter().rposition(|key| key == pubkey)
}
/// Gets an InstructionContext by its height in the stack
/// Gets an InstructionContext by its nesting level in the stack
pub fn get_instruction_context_at(
&self,
level: usize,
) -> Result<&InstructionContext, InstructionError> {
if level >= self.instruction_context_stack.len() {
return Err(InstructionError::CallDepth);
}
Ok(&self.instruction_context_stack[level])
let top_level_index = *self
.instruction_stack
.get(0)
.ok_or(InstructionError::CallDepth)?;
let cpi_index = if level == 0 {
0
} else {
*self
.instruction_stack
.get(level)
.ok_or(InstructionError::CallDepth)?
};
let instruction_context = self
.instruction_trace
.get(top_level_index)
.and_then(|instruction_trace| instruction_trace.get(cpi_index))
.ok_or(InstructionError::CallDepth)?;
debug_assert_eq!(instruction_context.nesting_level, level);
Ok(instruction_context)
}
/// Gets the max height of the InstructionContext stack
@@ -156,14 +166,13 @@ impl TransactionContext {
/// Gets instruction stack height, top-level instructions are height
/// `solana_sdk::instruction::TRANSACTION_LEVEL_STACK_HEIGHT`
pub fn get_instruction_context_stack_height(&self) -> usize {
self.instruction_context_stack.len()
self.instruction_stack.len()
}
/// Returns the current InstructionContext
pub fn get_current_instruction_context(&self) -> Result<&InstructionContext, InstructionError> {
let level = self
.instruction_context_stack
.len()
.get_instruction_context_stack_height()
.checked_sub(1)
.ok_or(InstructionError::CallDepth)?;
self.get_instruction_context_at(level)
@@ -175,36 +184,47 @@ impl TransactionContext {
program_accounts: &[usize],
instruction_accounts: &[InstructionAccount],
instruction_data: &[u8],
record_instruction_in_transaction_context_push: bool,
) -> Result<(), InstructionError> {
if self.instruction_context_stack.len() >= self.instruction_context_capacity {
return Err(InstructionError::CallDepth);
}
let instruction_context = InstructionContext {
program_accounts: program_accounts.to_vec(),
instruction_accounts: instruction_accounts.to_vec(),
instruction_data: instruction_data.to_vec(),
};
if self.instruction_context_stack.is_empty() {
let index_in_trace = if self.instruction_stack.is_empty() {
debug_assert!(
self.instruction_trace.len() < self.number_of_instructions_at_transaction_level
);
self.instruction_trace.push(vec![(
TRANSACTION_LEVEL_STACK_HEIGHT,
instruction_context.clone(),
)]);
let instruction_context = InstructionContext {
nesting_level: self.instruction_stack.len(),
program_accounts: program_accounts.to_vec(),
instruction_accounts: instruction_accounts.to_vec(),
instruction_data: instruction_data.to_vec(),
};
self.instruction_trace.push(vec![instruction_context]);
self.instruction_trace.len().saturating_sub(1)
} else if let Some(instruction_trace) = self.instruction_trace.last_mut() {
if record_instruction_in_transaction_context_push {
let instruction_context = InstructionContext {
nesting_level: self.instruction_stack.len(),
program_accounts: program_accounts.to_vec(),
instruction_accounts: instruction_accounts.to_vec(),
instruction_data: instruction_data.to_vec(),
};
instruction_trace.push(instruction_context);
}
instruction_trace.len().saturating_sub(1)
} else {
return Err(InstructionError::CallDepth);
};
if self.instruction_stack.len() >= self.instruction_context_capacity {
return Err(InstructionError::CallDepth);
}
self.instruction_context_stack.push(instruction_context);
self.instruction_stack.push(index_in_trace);
Ok(())
}
/// Pops the current InstructionContext
pub fn pop(&mut self) -> Result<(), InstructionError> {
if self.instruction_context_stack.is_empty() {
if self.instruction_stack.is_empty() {
return Err(InstructionError::CallDepth);
}
self.instruction_context_stack.pop();
self.instruction_stack.pop();
Ok(())
}
@@ -238,9 +258,12 @@ impl TransactionContext {
}
/// Used by the runtime when a new CPI instruction begins
pub fn record_instruction(&mut self, stack_height: usize, instruction: InstructionContext) {
///
/// Deprecated, automatically done in push()
/// once record_instruction_in_transaction_context_push is activated.
pub fn record_instruction(&mut self, instruction: InstructionContext) {
if let Some(records) = self.instruction_trace.last_mut() {
records.push((stack_height, instruction));
records.push(instruction);
}
}
@@ -251,13 +274,14 @@ impl TransactionContext {
}
/// List of (stack height, instruction) for each top-level instruction
pub type InstructionTrace = Vec<Vec<(usize, InstructionContext)>>;
pub type InstructionTrace = Vec<Vec<InstructionContext>>;
/// Loaded instruction shared between runtime and programs.
///
/// This context is valid for the entire duration of a (possibly cross program) instruction being processed.
#[derive(Debug, Clone)]
pub struct InstructionContext {
nesting_level: usize,
program_accounts: Vec<usize>,
instruction_accounts: Vec<InstructionAccount>,
instruction_data: Vec<u8>,
@@ -266,17 +290,26 @@ pub struct InstructionContext {
impl InstructionContext {
/// New
pub fn new(
nesting_level: usize,
program_accounts: &[usize],
instruction_accounts: &[InstructionAccount],
instruction_data: &[u8],
) -> Self {
InstructionContext {
nesting_level,
program_accounts: program_accounts.to_vec(),
instruction_accounts: instruction_accounts.to_vec(),
instruction_data: instruction_data.to_vec(),
}
}
/// How many Instructions were on the stack after this one was pushed
///
/// That is the number of nested parent Instructions plus one (itself).
pub fn get_stack_height(&self) -> usize {
self.nesting_level.saturating_add(1)
}
/// Number of program accounts
pub fn get_number_of_program_accounts(&self) -> usize {
self.program_accounts.len()