227 lines
8.3 KiB
Rust
227 lines
8.3 KiB
Rust
use {
|
|
crate::{
|
|
nonce_info::{NonceFull, NonceInfo, NoncePartial},
|
|
rent_debits::RentDebits,
|
|
},
|
|
solana_program_runtime::loaded_programs::LoadedProgramsForTxBatch,
|
|
solana_sdk::{
|
|
instruction::{CompiledInstruction, TRANSACTION_LEVEL_STACK_HEIGHT},
|
|
transaction::{self, TransactionError},
|
|
transaction_context::{TransactionContext, TransactionReturnData},
|
|
},
|
|
};
|
|
|
|
pub type TransactionCheckResult = (transaction::Result<()>, Option<NoncePartial>);
|
|
|
|
pub struct TransactionResults {
|
|
pub fee_collection_results: Vec<transaction::Result<()>>,
|
|
pub execution_results: Vec<TransactionExecutionResult>,
|
|
pub rent_debits: Vec<RentDebits>,
|
|
}
|
|
|
|
/// Type safe representation of a transaction execution attempt which
|
|
/// differentiates between a transaction that was executed (will be
|
|
/// committed to the ledger) and a transaction which wasn't executed
|
|
/// and will be dropped.
|
|
///
|
|
/// Note: `Result<TransactionExecutionDetails, TransactionError>` is not
|
|
/// used because it's easy to forget that the inner `details.status` field
|
|
/// is what should be checked to detect a successful transaction. This
|
|
/// enum provides a convenience method `Self::was_executed_successfully` to
|
|
/// make such checks hard to do incorrectly.
|
|
#[derive(Debug, Clone)]
|
|
pub enum TransactionExecutionResult {
|
|
Executed {
|
|
details: TransactionExecutionDetails,
|
|
programs_modified_by_tx: Box<LoadedProgramsForTxBatch>,
|
|
programs_updated_only_for_global_cache: Box<LoadedProgramsForTxBatch>,
|
|
},
|
|
NotExecuted(TransactionError),
|
|
}
|
|
|
|
impl TransactionExecutionResult {
|
|
pub fn was_executed_successfully(&self) -> bool {
|
|
match self {
|
|
Self::Executed { details, .. } => details.status.is_ok(),
|
|
Self::NotExecuted { .. } => false,
|
|
}
|
|
}
|
|
|
|
pub fn was_executed(&self) -> bool {
|
|
match self {
|
|
Self::Executed { .. } => true,
|
|
Self::NotExecuted(_) => false,
|
|
}
|
|
}
|
|
|
|
pub fn details(&self) -> Option<&TransactionExecutionDetails> {
|
|
match self {
|
|
Self::Executed { details, .. } => Some(details),
|
|
Self::NotExecuted(_) => None,
|
|
}
|
|
}
|
|
|
|
pub fn flattened_result(&self) -> transaction::Result<()> {
|
|
match self {
|
|
Self::Executed { details, .. } => details.status.clone(),
|
|
Self::NotExecuted(err) => Err(err.clone()),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct TransactionExecutionDetails {
|
|
pub status: transaction::Result<()>,
|
|
pub log_messages: Option<Vec<String>>,
|
|
pub inner_instructions: Option<InnerInstructionsList>,
|
|
pub durable_nonce_fee: Option<DurableNonceFee>,
|
|
pub return_data: Option<TransactionReturnData>,
|
|
pub executed_units: u64,
|
|
/// The change in accounts data len for this transaction.
|
|
/// NOTE: This value is valid IFF `status` is `Ok`.
|
|
pub accounts_data_len_delta: i64,
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub enum DurableNonceFee {
|
|
Valid(u64),
|
|
Invalid,
|
|
}
|
|
|
|
impl From<&NonceFull> for DurableNonceFee {
|
|
fn from(nonce: &NonceFull) -> Self {
|
|
match nonce.lamports_per_signature() {
|
|
Some(lamports_per_signature) => Self::Valid(lamports_per_signature),
|
|
None => Self::Invalid,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl DurableNonceFee {
|
|
pub fn lamports_per_signature(&self) -> Option<u64> {
|
|
match self {
|
|
Self::Valid(lamports_per_signature) => Some(*lamports_per_signature),
|
|
Self::Invalid => None,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// An ordered list of compiled instructions that were invoked during a
|
|
/// transaction instruction
|
|
pub type InnerInstructions = Vec<InnerInstruction>;
|
|
|
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
pub struct InnerInstruction {
|
|
pub instruction: CompiledInstruction,
|
|
/// Invocation stack height of this instruction. Instruction stack height
|
|
/// starts at 1 for transaction instructions.
|
|
pub stack_height: u8,
|
|
}
|
|
|
|
/// A list of compiled instructions that were invoked during each instruction of
|
|
/// a transaction
|
|
pub type InnerInstructionsList = Vec<InnerInstructions>;
|
|
|
|
/// Extract the InnerInstructionsList from a TransactionContext
|
|
pub fn inner_instructions_list_from_instruction_trace(
|
|
transaction_context: &TransactionContext,
|
|
) -> InnerInstructionsList {
|
|
debug_assert!(transaction_context
|
|
.get_instruction_context_at_index_in_trace(0)
|
|
.map(|instruction_context| instruction_context.get_stack_height()
|
|
== TRANSACTION_LEVEL_STACK_HEIGHT)
|
|
.unwrap_or(true));
|
|
let mut outer_instructions = Vec::new();
|
|
for index_in_trace in 0..transaction_context.get_instruction_trace_length() {
|
|
if let Ok(instruction_context) =
|
|
transaction_context.get_instruction_context_at_index_in_trace(index_in_trace)
|
|
{
|
|
let stack_height = instruction_context.get_stack_height();
|
|
if stack_height == TRANSACTION_LEVEL_STACK_HEIGHT {
|
|
outer_instructions.push(Vec::new());
|
|
} else if let Some(inner_instructions) = outer_instructions.last_mut() {
|
|
let stack_height = u8::try_from(stack_height).unwrap_or(u8::MAX);
|
|
let instruction = CompiledInstruction::new_from_raw_parts(
|
|
instruction_context
|
|
.get_index_of_program_account_in_transaction(
|
|
instruction_context
|
|
.get_number_of_program_accounts()
|
|
.saturating_sub(1),
|
|
)
|
|
.unwrap_or_default() as u8,
|
|
instruction_context.get_instruction_data().to_vec(),
|
|
(0..instruction_context.get_number_of_instruction_accounts())
|
|
.map(|instruction_account_index| {
|
|
instruction_context
|
|
.get_index_of_instruction_account_in_transaction(
|
|
instruction_account_index,
|
|
)
|
|
.unwrap_or_default() as u8
|
|
})
|
|
.collect(),
|
|
);
|
|
inner_instructions.push(InnerInstruction {
|
|
instruction,
|
|
stack_height,
|
|
});
|
|
} else {
|
|
debug_assert!(false);
|
|
}
|
|
} else {
|
|
debug_assert!(false);
|
|
}
|
|
}
|
|
outer_instructions
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use {super::*, solana_sdk::transaction_context::TransactionContext};
|
|
|
|
#[test]
|
|
fn test_inner_instructions_list_from_instruction_trace() {
|
|
let instruction_trace = [1, 2, 1, 1, 2, 3, 2];
|
|
let mut transaction_context =
|
|
TransactionContext::new(vec![], None, 3, instruction_trace.len());
|
|
for (index_in_trace, stack_height) in instruction_trace.into_iter().enumerate() {
|
|
while stack_height <= transaction_context.get_instruction_context_stack_height() {
|
|
transaction_context.pop().unwrap();
|
|
}
|
|
if stack_height > transaction_context.get_instruction_context_stack_height() {
|
|
transaction_context
|
|
.get_next_instruction_context()
|
|
.unwrap()
|
|
.configure(&[], &[], &[index_in_trace as u8]);
|
|
transaction_context.push().unwrap();
|
|
}
|
|
}
|
|
let inner_instructions =
|
|
inner_instructions_list_from_instruction_trace(&transaction_context);
|
|
|
|
assert_eq!(
|
|
inner_instructions,
|
|
vec![
|
|
vec![InnerInstruction {
|
|
instruction: CompiledInstruction::new_from_raw_parts(0, vec![1], vec![]),
|
|
stack_height: 2,
|
|
}],
|
|
vec![],
|
|
vec![
|
|
InnerInstruction {
|
|
instruction: CompiledInstruction::new_from_raw_parts(0, vec![4], vec![]),
|
|
stack_height: 2,
|
|
},
|
|
InnerInstruction {
|
|
instruction: CompiledInstruction::new_from_raw_parts(0, vec![5], vec![]),
|
|
stack_height: 3,
|
|
},
|
|
InnerInstruction {
|
|
instruction: CompiledInstruction::new_from_raw_parts(0, vec![6], vec![]),
|
|
stack_height: 2,
|
|
},
|
|
]
|
|
]
|
|
);
|
|
}
|
|
}
|