Record and store invoked instructions in transaction meta (#12311) (#12449)

* 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:
mergify[bot]
2020-09-24 15:42:34 +00:00
committed by GitHub
parent 9ff2378948
commit 7212bb12ea
19 changed files with 429 additions and 98 deletions

View File

@@ -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);

View 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);
}
}

View File

@@ -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;

View File

@@ -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),