* Record invoked instructions and store in transaction meta
* Enable cpi recording if transaction sender is some
* Rename invoked to innerInstructions
(cherry picked from commit 6601ec8f26
)
Co-authored-by: Justin Starry <justin@solana.com>
This commit is contained in:
@@ -12,6 +12,7 @@ use crate::{
|
||||
blockhash_queue::BlockhashQueue,
|
||||
builtins::get_builtins,
|
||||
epoch_stakes::{EpochStakes, NodeVoteAccounts},
|
||||
instruction_recorder::InstructionRecorder,
|
||||
log_collector::LogCollector,
|
||||
message_processor::{Executors, MessageProcessor},
|
||||
nonce_utils,
|
||||
@@ -46,6 +47,7 @@ use solana_sdk::{
|
||||
hash::{extend_and_hash, hashv, Hash},
|
||||
incinerator,
|
||||
inflation::Inflation,
|
||||
instruction::CompiledInstruction,
|
||||
message::Message,
|
||||
native_loader,
|
||||
native_token::sol_to_lamports,
|
||||
@@ -299,6 +301,12 @@ impl TransactionBalancesSet {
|
||||
}
|
||||
pub type TransactionBalances = Vec<Vec<u64>>;
|
||||
|
||||
/// An ordered list of instructions that were invoked during a transaction instruction
|
||||
pub type InnerInstructions = Vec<CompiledInstruction>;
|
||||
|
||||
/// A list of instructions that were invoked during each instruction of a transaction
|
||||
pub type InnerInstructionsList = Vec<InnerInstructions>;
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub enum HashAgeKind {
|
||||
Extant,
|
||||
@@ -1529,6 +1537,7 @@ impl Bank {
|
||||
let (
|
||||
_loaded_accounts,
|
||||
executed,
|
||||
_inner_instructions,
|
||||
_retryable_transactions,
|
||||
_transaction_count,
|
||||
_signature_count,
|
||||
@@ -1536,6 +1545,7 @@ impl Bank {
|
||||
&batch,
|
||||
MAX_PROCESSING_AGE,
|
||||
Some(log_collector.clone()),
|
||||
false,
|
||||
);
|
||||
let transaction_result = executed[0].0.clone().map(|_| ());
|
||||
let log_messages = Rc::try_unwrap(log_collector).unwrap_or_default().into();
|
||||
@@ -1871,6 +1881,19 @@ impl Bank {
|
||||
});
|
||||
}
|
||||
|
||||
fn compile_recorded_instructions(
|
||||
inner_instructions: &mut Vec<Option<InnerInstructionsList>>,
|
||||
instruction_recorders: Option<Vec<InstructionRecorder>>,
|
||||
message: &Message,
|
||||
) {
|
||||
inner_instructions.push(instruction_recorders.map(|instruction_recorders| {
|
||||
instruction_recorders
|
||||
.into_iter()
|
||||
.map(|r| r.compile_instructions(message))
|
||||
.collect()
|
||||
}));
|
||||
}
|
||||
|
||||
/// Get any cached executors needed by the transaction
|
||||
fn get_executors(
|
||||
&self,
|
||||
@@ -1920,9 +1943,11 @@ impl Bank {
|
||||
batch: &TransactionBatch,
|
||||
max_age: usize,
|
||||
log_collector: Option<Rc<LogCollector>>,
|
||||
enable_cpi_recording: bool,
|
||||
) -> (
|
||||
Vec<(Result<TransactionLoadResult>, Option<HashAgeKind>)>,
|
||||
Vec<TransactionProcessResult>,
|
||||
Vec<Option<InnerInstructionsList>>,
|
||||
Vec<usize>,
|
||||
u64,
|
||||
u64,
|
||||
@@ -1963,6 +1988,8 @@ impl Bank {
|
||||
|
||||
let mut execution_time = Measure::start("execution_time");
|
||||
let mut signature_count: u64 = 0;
|
||||
let mut inner_instructions: Vec<Option<InnerInstructionsList>> =
|
||||
Vec::with_capacity(txs.len());
|
||||
let executed: Vec<TransactionProcessResult> = loaded_accounts
|
||||
.iter_mut()
|
||||
.zip(OrderedIterator::new(txs, batch.iteration_order()))
|
||||
@@ -1976,6 +2003,11 @@ impl Bank {
|
||||
let (account_refcells, loader_refcells) =
|
||||
Self::accounts_to_refcells(accounts, loaders);
|
||||
|
||||
let mut instruction_recorders = if enable_cpi_recording {
|
||||
Some(Vec::new())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let process_result = self.message_processor.process_message(
|
||||
tx.message(),
|
||||
&loader_refcells,
|
||||
@@ -1983,10 +2015,17 @@ impl Bank {
|
||||
&self.rent_collector,
|
||||
log_collector.clone(),
|
||||
executors.clone(),
|
||||
instruction_recorders.as_mut(),
|
||||
self.cluster_type(),
|
||||
self.epoch(),
|
||||
);
|
||||
|
||||
Self::compile_recorded_instructions(
|
||||
&mut inner_instructions,
|
||||
instruction_recorders,
|
||||
&tx.message,
|
||||
);
|
||||
|
||||
Self::refcells_to_accounts(
|
||||
accounts,
|
||||
loaders,
|
||||
@@ -2036,6 +2075,7 @@ impl Bank {
|
||||
(
|
||||
loaded_accounts,
|
||||
executed,
|
||||
inner_instructions,
|
||||
retryable_txs,
|
||||
tx_count,
|
||||
signature_count,
|
||||
@@ -2664,14 +2704,19 @@ impl Bank {
|
||||
batch: &TransactionBatch,
|
||||
max_age: usize,
|
||||
collect_balances: bool,
|
||||
) -> (TransactionResults, TransactionBalancesSet) {
|
||||
enable_cpi_recording: bool,
|
||||
) -> (
|
||||
TransactionResults,
|
||||
TransactionBalancesSet,
|
||||
Vec<Option<InnerInstructionsList>>,
|
||||
) {
|
||||
let pre_balances = if collect_balances {
|
||||
self.collect_balances(batch)
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
let (mut loaded_accounts, executed, _, tx_count, signature_count) =
|
||||
self.load_and_execute_transactions(batch, max_age, None);
|
||||
let (mut loaded_accounts, executed, inner_instructions, _, tx_count, signature_count) =
|
||||
self.load_and_execute_transactions(batch, max_age, None, enable_cpi_recording);
|
||||
|
||||
let results = self.commit_transactions(
|
||||
batch.transactions(),
|
||||
@@ -2689,13 +2734,14 @@ impl Bank {
|
||||
(
|
||||
results,
|
||||
TransactionBalancesSet::new(pre_balances, post_balances),
|
||||
inner_instructions,
|
||||
)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn process_transactions(&self, txs: &[Transaction]) -> Vec<Result<()>> {
|
||||
let batch = self.prepare_batch(txs, None);
|
||||
self.load_execute_and_commit_transactions(&batch, MAX_PROCESSING_AGE, false)
|
||||
self.load_execute_and_commit_transactions(&batch, MAX_PROCESSING_AGE, false, false)
|
||||
.0
|
||||
.fee_collection_results
|
||||
}
|
||||
@@ -6018,7 +6064,7 @@ mod tests {
|
||||
|
||||
let lock_result = bank.prepare_batch(&pay_alice, None);
|
||||
let results_alice = bank
|
||||
.load_execute_and_commit_transactions(&lock_result, MAX_PROCESSING_AGE, false)
|
||||
.load_execute_and_commit_transactions(&lock_result, MAX_PROCESSING_AGE, false, false)
|
||||
.0
|
||||
.fee_collection_results;
|
||||
assert_eq!(results_alice[0], Ok(()));
|
||||
@@ -7831,9 +7877,10 @@ mod tests {
|
||||
let txs = vec![tx0, tx1, tx2];
|
||||
|
||||
let lock_result = bank0.prepare_batch(&txs, None);
|
||||
let (transaction_results, transaction_balances_set) =
|
||||
bank0.load_execute_and_commit_transactions(&lock_result, MAX_PROCESSING_AGE, true);
|
||||
let (transaction_results, transaction_balances_set, inner_instructions) = bank0
|
||||
.load_execute_and_commit_transactions(&lock_result, MAX_PROCESSING_AGE, true, false);
|
||||
|
||||
assert!(inner_instructions[0].iter().all(|ix| ix.is_empty()));
|
||||
assert_eq!(transaction_balances_set.pre_balances.len(), 3);
|
||||
assert_eq!(transaction_balances_set.post_balances.len(), 3);
|
||||
|
||||
|
26
runtime/src/instruction_recorder.rs
Normal file
26
runtime/src/instruction_recorder.rs
Normal file
@@ -0,0 +1,26 @@
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
|
||||
use solana_sdk::{
|
||||
instruction::{CompiledInstruction, Instruction},
|
||||
message::Message,
|
||||
};
|
||||
|
||||
/// Records and compiles cross-program invoked instructions
|
||||
#[derive(Clone, Default)]
|
||||
pub struct InstructionRecorder {
|
||||
inner: Rc<RefCell<Vec<Instruction>>>,
|
||||
}
|
||||
|
||||
impl InstructionRecorder {
|
||||
pub fn compile_instructions(&self, message: &Message) -> Vec<CompiledInstruction> {
|
||||
self.inner
|
||||
.borrow()
|
||||
.iter()
|
||||
.map(|ix| message.compile_instruction(ix))
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn record_instruction(&self, instruction: Instruction) {
|
||||
self.inner.borrow_mut().push(instruction);
|
||||
}
|
||||
}
|
@@ -14,6 +14,7 @@ pub mod commitment;
|
||||
pub mod epoch_stakes;
|
||||
pub mod genesis_utils;
|
||||
pub mod hardened_unpack;
|
||||
pub mod instruction_recorder;
|
||||
pub mod loader_utils;
|
||||
pub mod log_collector;
|
||||
pub mod message_processor;
|
||||
|
@@ -1,5 +1,6 @@
|
||||
use crate::{
|
||||
log_collector::LogCollector, native_loader::NativeLoader, rent_collector::RentCollector,
|
||||
instruction_recorder::InstructionRecorder, log_collector::LogCollector,
|
||||
native_loader::NativeLoader, rent_collector::RentCollector,
|
||||
};
|
||||
use log::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -11,7 +12,7 @@ use solana_sdk::{
|
||||
Executor, InvokeContext, Logger, ProcessInstruction, ProcessInstructionWithContext,
|
||||
},
|
||||
genesis_config::ClusterType,
|
||||
instruction::{CompiledInstruction, InstructionError},
|
||||
instruction::{CompiledInstruction, Instruction, InstructionError},
|
||||
message::Message,
|
||||
native_loader,
|
||||
pubkey::Pubkey,
|
||||
@@ -206,6 +207,7 @@ pub struct ThisInvokeContext {
|
||||
compute_budget: ComputeBudget,
|
||||
compute_meter: Rc<RefCell<dyn ComputeMeter>>,
|
||||
executors: Rc<RefCell<Executors>>,
|
||||
instruction_recorder: Option<InstructionRecorder>,
|
||||
}
|
||||
impl ThisInvokeContext {
|
||||
pub fn new(
|
||||
@@ -217,6 +219,7 @@ impl ThisInvokeContext {
|
||||
is_cross_program_supported: bool,
|
||||
compute_budget: ComputeBudget,
|
||||
executors: Rc<RefCell<Executors>>,
|
||||
instruction_recorder: Option<InstructionRecorder>,
|
||||
) -> Self {
|
||||
let mut program_ids = Vec::with_capacity(compute_budget.max_invoke_depth);
|
||||
program_ids.push(*program_id);
|
||||
@@ -232,6 +235,7 @@ impl ThisInvokeContext {
|
||||
remaining: compute_budget.max_units,
|
||||
})),
|
||||
executors,
|
||||
instruction_recorder,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -294,6 +298,11 @@ impl InvokeContext for ThisInvokeContext {
|
||||
fn get_executor(&mut 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
pub struct ThisLogger {
|
||||
log_collector: Option<Rc<LogCollector>>,
|
||||
@@ -667,6 +676,7 @@ impl MessageProcessor {
|
||||
rent_collector: &RentCollector,
|
||||
log_collector: Option<Rc<LogCollector>>,
|
||||
executors: Rc<RefCell<Executors>>,
|
||||
instruction_recorder: Option<InstructionRecorder>,
|
||||
instruction_index: usize,
|
||||
cluster_type: ClusterType,
|
||||
epoch: Epoch,
|
||||
@@ -696,6 +706,7 @@ impl MessageProcessor {
|
||||
self.is_cross_program_supported,
|
||||
self.compute_budget,
|
||||
executors,
|
||||
instruction_recorder,
|
||||
);
|
||||
let keyed_accounts =
|
||||
Self::create_keyed_accounts(message, instruction, executable_accounts, accounts)?;
|
||||
@@ -714,6 +725,7 @@ impl MessageProcessor {
|
||||
/// 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)]
|
||||
pub fn process_message(
|
||||
&self,
|
||||
message: &Message,
|
||||
@@ -722,10 +734,16 @@ impl MessageProcessor {
|
||||
rent_collector: &RentCollector,
|
||||
log_collector: Option<Rc<LogCollector>>,
|
||||
executors: Rc<RefCell<Executors>>,
|
||||
mut instruction_recorders: Option<&mut Vec<InstructionRecorder>>,
|
||||
cluster_type: ClusterType,
|
||||
epoch: Epoch,
|
||||
) -> Result<(), TransactionError> {
|
||||
for (instruction_index, instruction) in message.instructions.iter().enumerate() {
|
||||
let instruction_recorder = instruction_recorders.as_mut().map(|recorders| {
|
||||
let instruction_recorder = InstructionRecorder::default();
|
||||
recorders.push(instruction_recorder.clone());
|
||||
instruction_recorder
|
||||
});
|
||||
self.execute_instruction(
|
||||
message,
|
||||
instruction,
|
||||
@@ -734,6 +752,7 @@ impl MessageProcessor {
|
||||
rent_collector,
|
||||
log_collector.clone(),
|
||||
executors.clone(),
|
||||
instruction_recorder,
|
||||
instruction_index,
|
||||
cluster_type,
|
||||
epoch,
|
||||
@@ -794,6 +813,7 @@ mod tests {
|
||||
true,
|
||||
ComputeBudget::default(),
|
||||
Rc::new(RefCell::new(Executors::default())),
|
||||
None,
|
||||
);
|
||||
|
||||
// Check call depth increases and has a limit
|
||||
@@ -1329,6 +1349,7 @@ mod tests {
|
||||
&rent_collector,
|
||||
None,
|
||||
executors.clone(),
|
||||
None,
|
||||
ClusterType::Development,
|
||||
0,
|
||||
);
|
||||
@@ -1352,6 +1373,7 @@ mod tests {
|
||||
&rent_collector,
|
||||
None,
|
||||
executors.clone(),
|
||||
None,
|
||||
ClusterType::Development,
|
||||
0,
|
||||
);
|
||||
@@ -1379,6 +1401,7 @@ mod tests {
|
||||
&rent_collector,
|
||||
None,
|
||||
executors,
|
||||
None,
|
||||
ClusterType::Development,
|
||||
0,
|
||||
);
|
||||
@@ -1489,6 +1512,7 @@ mod tests {
|
||||
&rent_collector,
|
||||
None,
|
||||
executors.clone(),
|
||||
None,
|
||||
ClusterType::Development,
|
||||
0,
|
||||
);
|
||||
@@ -1516,6 +1540,7 @@ mod tests {
|
||||
&rent_collector,
|
||||
None,
|
||||
executors.clone(),
|
||||
None,
|
||||
ClusterType::Development,
|
||||
0,
|
||||
);
|
||||
@@ -1540,6 +1565,7 @@ mod tests {
|
||||
&rent_collector,
|
||||
None,
|
||||
executors,
|
||||
None,
|
||||
ClusterType::Development,
|
||||
0,
|
||||
);
|
||||
@@ -1618,6 +1644,7 @@ mod tests {
|
||||
true,
|
||||
ComputeBudget::default(),
|
||||
Rc::new(RefCell::new(Executors::default())),
|
||||
None,
|
||||
);
|
||||
let metas = vec![
|
||||
AccountMeta::new(owned_key, false),
|
||||
|
Reference in New Issue
Block a user