RPC: Add inner instructions to simulate transaction response (#34313)
* rpc: add optional `innerInstructions: bool` arg to `simulateTransaction` * bank: enable cpi recording in simulate * sdk: move `InnerInstructions` into SDK from accounts DB * bank: return inner instructions from simulate tx * rpc: return inner instructions from simulate tx * rpc: simulate tx: add `jsonParsed` support for inner instructions * accounts db: add deprecated attribute to re-exported inner instructions * rpc: de-dupe inner instruction mapping * update deprecated comment Co-authored-by: Tyera <teulberg@gmail.com> --------- Co-authored-by: Tyera <teulberg@gmail.com>
This commit is contained in:
parent
1f2b72b6e3
commit
171c58c5c0
|
@ -1,3 +1,9 @@
|
|||
// Re-exported since these have moved to `solana_sdk`.
|
||||
#[deprecated(
|
||||
since = "1.18.0",
|
||||
note = "Please use `solana_sdk::inner_instruction` types instead"
|
||||
)]
|
||||
pub use solana_sdk::inner_instruction::{InnerInstruction, InnerInstructionsList};
|
||||
use {
|
||||
crate::{
|
||||
nonce_info::{NonceFull, NonceInfo, NoncePartial},
|
||||
|
@ -105,22 +111,6 @@ impl DurableNonceFee {
|
|||
}
|
||||
}
|
||||
|
||||
/// 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,
|
||||
|
|
|
@ -8,6 +8,7 @@ use {
|
|||
commitment_config::CommitmentLevel,
|
||||
fee_calculator::FeeCalculator,
|
||||
hash::Hash,
|
||||
inner_instruction::InnerInstructions,
|
||||
message::Message,
|
||||
pubkey::Pubkey,
|
||||
signature::Signature,
|
||||
|
@ -37,6 +38,7 @@ pub struct TransactionSimulationDetails {
|
|||
pub logs: Vec<String>,
|
||||
pub units_consumed: u64,
|
||||
pub return_data: Option<TransactionReturnData>,
|
||||
pub inner_instructions: Option<Vec<InnerInstructions>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
|
|
|
@ -194,11 +194,14 @@ fn simulate_transaction(
|
|||
post_simulation_accounts: _,
|
||||
units_consumed,
|
||||
return_data,
|
||||
} = bank.simulate_transaction_unchecked(sanitized_transaction);
|
||||
inner_instructions,
|
||||
} = bank.simulate_transaction_unchecked(&sanitized_transaction, false);
|
||||
|
||||
let simulation_details = TransactionSimulationDetails {
|
||||
logs,
|
||||
units_consumed,
|
||||
return_data,
|
||||
inner_instructions,
|
||||
};
|
||||
BanksTransactionResultWithSimulation {
|
||||
result: Some(result),
|
||||
|
|
|
@ -54,7 +54,7 @@ use {
|
|||
transaction::VersionedTransaction,
|
||||
},
|
||||
solana_transaction_status::{
|
||||
ConfirmedTransactionWithStatusMeta, InnerInstructions, TransactionStatusMeta,
|
||||
map_inner_instructions, ConfirmedTransactionWithStatusMeta, TransactionStatusMeta,
|
||||
TransactionWithStatusMeta, VersionedTransactionWithStatusMeta,
|
||||
},
|
||||
std::collections::HashMap,
|
||||
|
@ -212,21 +212,7 @@ fn execute_transactions(
|
|||
);
|
||||
|
||||
let inner_instructions = inner_instructions.map(|inner_instructions| {
|
||||
inner_instructions
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(index, instructions)| InnerInstructions {
|
||||
index: index as u8,
|
||||
instructions: instructions
|
||||
.into_iter()
|
||||
.map(|ix| solana_transaction_status::InnerInstruction {
|
||||
instruction: ix.instruction,
|
||||
stack_height: Some(u32::from(ix.stack_height)),
|
||||
})
|
||||
.collect(),
|
||||
})
|
||||
.filter(|i| !i.instructions.is_empty())
|
||||
.collect()
|
||||
map_inner_instructions(inner_instructions).collect()
|
||||
});
|
||||
|
||||
let tx_status_meta = TransactionStatusMeta {
|
||||
|
@ -766,7 +752,7 @@ fn test_return_data_and_log_data_syscall() {
|
|||
let transaction = Transaction::new(&[&mint_keypair], message, blockhash);
|
||||
let sanitized_tx = SanitizedTransaction::from_transaction_for_tests(transaction);
|
||||
|
||||
let result = bank.simulate_transaction(sanitized_tx);
|
||||
let result = bank.simulate_transaction(&sanitized_tx, false);
|
||||
|
||||
assert!(result.result.is_ok());
|
||||
|
||||
|
|
|
@ -44,6 +44,8 @@ pub struct RpcSimulateTransactionConfig {
|
|||
pub encoding: Option<UiTransactionEncoding>,
|
||||
pub accounts: Option<RpcSimulateTransactionAccountsConfig>,
|
||||
pub min_context_slot: Option<Slot>,
|
||||
#[serde(default)]
|
||||
pub inner_instructions: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
|
|
|
@ -11,7 +11,7 @@ use {
|
|||
},
|
||||
solana_transaction_status::{
|
||||
ConfirmedTransactionStatusWithSignature, TransactionConfirmationStatus, UiConfirmedBlock,
|
||||
UiTransactionReturnData,
|
||||
UiInnerInstructions, UiTransactionReturnData,
|
||||
},
|
||||
std::{collections::HashMap, fmt, net::SocketAddr, str::FromStr},
|
||||
thiserror::Error,
|
||||
|
@ -423,6 +423,7 @@ pub struct RpcSimulateTransactionResult {
|
|||
pub accounts: Option<Vec<Option<UiAccount>>>,
|
||||
pub units_consumed: Option<u64>,
|
||||
pub return_data: Option<UiTransactionReturnData>,
|
||||
pub inner_instructions: Option<Vec<UiInnerInstructions>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
|
||||
|
|
|
@ -350,6 +350,7 @@ impl RpcSender for MockSender {
|
|||
accounts: None,
|
||||
units_consumed: None,
|
||||
return_data: None,
|
||||
inner_instructions: None,
|
||||
},
|
||||
})?,
|
||||
"getMinimumBalanceForRentExemption" => json![20],
|
||||
|
|
|
@ -87,10 +87,10 @@ use {
|
|||
solana_storage_bigtable::Error as StorageError,
|
||||
solana_streamer::socket::SocketAddrSpace,
|
||||
solana_transaction_status::{
|
||||
BlockEncodingOptions, ConfirmedBlock, ConfirmedTransactionStatusWithSignature,
|
||||
ConfirmedTransactionWithStatusMeta, EncodedConfirmedTransactionWithStatusMeta, Reward,
|
||||
RewardType, TransactionBinaryEncoding, TransactionConfirmationStatus, TransactionStatus,
|
||||
UiConfirmedBlock, UiTransactionEncoding,
|
||||
map_inner_instructions, BlockEncodingOptions, ConfirmedBlock,
|
||||
ConfirmedTransactionStatusWithSignature, ConfirmedTransactionWithStatusMeta,
|
||||
EncodedConfirmedTransactionWithStatusMeta, Reward, RewardType, TransactionBinaryEncoding,
|
||||
TransactionConfirmationStatus, TransactionStatus, UiConfirmedBlock, UiTransactionEncoding,
|
||||
},
|
||||
solana_vote_program::vote_state::{VoteState, MAX_LOCKOUT_HISTORY},
|
||||
spl_token_2022::{
|
||||
|
@ -3266,6 +3266,7 @@ pub mod rpc_full {
|
|||
use {
|
||||
super::*,
|
||||
solana_sdk::message::{SanitizedVersionedMessage, VersionedMessage},
|
||||
solana_transaction_status::UiInnerInstructions,
|
||||
};
|
||||
#[rpc]
|
||||
pub trait Full {
|
||||
|
@ -3676,7 +3677,8 @@ pub mod rpc_full {
|
|||
post_simulation_accounts: _,
|
||||
units_consumed,
|
||||
return_data,
|
||||
} = preflight_bank.simulate_transaction(transaction)
|
||||
inner_instructions: _, // Always `None` due to `enable_cpi_recording = false`
|
||||
} = preflight_bank.simulate_transaction(&transaction, false)
|
||||
{
|
||||
match err {
|
||||
TransactionError::BlockhashNotFound => {
|
||||
|
@ -3694,6 +3696,7 @@ pub mod rpc_full {
|
|||
accounts: None,
|
||||
units_consumed: Some(units_consumed),
|
||||
return_data: return_data.map(|return_data| return_data.into()),
|
||||
inner_instructions: None,
|
||||
},
|
||||
}
|
||||
.into());
|
||||
|
@ -3724,6 +3727,7 @@ pub mod rpc_full {
|
|||
encoding,
|
||||
accounts: config_accounts,
|
||||
min_context_slot,
|
||||
inner_instructions: enable_cpi_recording,
|
||||
} = config.unwrap_or_default();
|
||||
let tx_encoding = encoding.unwrap_or(UiTransactionEncoding::Base58);
|
||||
let binary_encoding = tx_encoding.into_binary_encoding().ok_or_else(|| {
|
||||
|
@ -3753,7 +3757,6 @@ pub mod rpc_full {
|
|||
if sig_verify {
|
||||
verify_transaction(&transaction, &bank.feature_set)?;
|
||||
}
|
||||
let number_of_accounts = transaction.message().account_keys().len();
|
||||
|
||||
let TransactionSimulationResult {
|
||||
result,
|
||||
|
@ -3761,7 +3764,11 @@ pub mod rpc_full {
|
|||
post_simulation_accounts,
|
||||
units_consumed,
|
||||
return_data,
|
||||
} = bank.simulate_transaction(transaction);
|
||||
inner_instructions,
|
||||
} = bank.simulate_transaction(&transaction, enable_cpi_recording);
|
||||
|
||||
let account_keys = transaction.message().account_keys();
|
||||
let number_of_accounts = account_keys.len();
|
||||
|
||||
let accounts = if let Some(config_accounts) = config_accounts {
|
||||
let accounts_encoding = config_accounts
|
||||
|
@ -3804,6 +3811,12 @@ pub mod rpc_full {
|
|||
None
|
||||
};
|
||||
|
||||
let inner_instructions = inner_instructions.map(|info| {
|
||||
map_inner_instructions(info)
|
||||
.map(|converted| UiInnerInstructions::parse(converted, &account_keys))
|
||||
.collect()
|
||||
});
|
||||
|
||||
Ok(new_response(
|
||||
bank,
|
||||
RpcSimulateTransactionResult {
|
||||
|
@ -3812,6 +3825,7 @@ pub mod rpc_full {
|
|||
accounts,
|
||||
units_consumed: Some(units_consumed),
|
||||
return_data: return_data.map(|return_data| return_data.into()),
|
||||
inner_instructions,
|
||||
},
|
||||
))
|
||||
}
|
||||
|
@ -5913,6 +5927,7 @@ pub mod tests {
|
|||
}
|
||||
],
|
||||
"err":null,
|
||||
"innerInstructions": null,
|
||||
"logs":[
|
||||
"Program 11111111111111111111111111111111 invoke [1]",
|
||||
"Program 11111111111111111111111111111111 success"
|
||||
|
@ -5997,6 +6012,7 @@ pub mod tests {
|
|||
"value":{
|
||||
"accounts":null,
|
||||
"err":null,
|
||||
"innerInstructions":null,
|
||||
"logs":[
|
||||
"Program 11111111111111111111111111111111 invoke [1]",
|
||||
"Program 11111111111111111111111111111111 success"
|
||||
|
@ -6025,6 +6041,7 @@ pub mod tests {
|
|||
"value":{
|
||||
"accounts":null,
|
||||
"err":null,
|
||||
"innerInstructions":null,
|
||||
"logs":[
|
||||
"Program 11111111111111111111111111111111 invoke [1]",
|
||||
"Program 11111111111111111111111111111111 success"
|
||||
|
@ -6077,6 +6094,7 @@ pub mod tests {
|
|||
"value":{
|
||||
"err":"BlockhashNotFound",
|
||||
"accounts":null,
|
||||
"innerInstructions":null,
|
||||
"logs":[],
|
||||
"returnData":null,
|
||||
"unitsConsumed":0,
|
||||
|
@ -6103,6 +6121,7 @@ pub mod tests {
|
|||
"value":{
|
||||
"accounts":null,
|
||||
"err":null,
|
||||
"innerInstructions":null,
|
||||
"logs":[
|
||||
"Program 11111111111111111111111111111111 invoke [1]",
|
||||
"Program 11111111111111111111111111111111 success"
|
||||
|
@ -6483,7 +6502,7 @@ pub mod tests {
|
|||
assert_eq!(
|
||||
res,
|
||||
Some(
|
||||
r#"{"jsonrpc":"2.0","error":{"code":-32002,"message":"Transaction simulation failed: Blockhash not found","data":{"accounts":null,"err":"BlockhashNotFound","logs":[],"returnData":null,"unitsConsumed":0}},"id":1}"#.to_string(),
|
||||
r#"{"jsonrpc":"2.0","error":{"code":-32002,"message":"Transaction simulation failed: Blockhash not found","data":{"accounts":null,"err":"BlockhashNotFound","innerInstructions":null,"logs":[],"returnData":null,"unitsConsumed":0}},"id":1}"#.to_string(),
|
||||
)
|
||||
);
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ use {
|
|||
blockstore_processor::{TransactionStatusBatch, TransactionStatusMessage},
|
||||
},
|
||||
solana_transaction_status::{
|
||||
extract_and_fmt_memos, InnerInstruction, InnerInstructions, Reward, TransactionStatusMeta,
|
||||
extract_and_fmt_memos, map_inner_instructions, Reward, TransactionStatusMeta,
|
||||
},
|
||||
std::{
|
||||
sync::{
|
||||
|
@ -121,21 +121,7 @@ impl TransactionStatusService {
|
|||
let tx_account_locks = transaction.get_account_locks_unchecked();
|
||||
|
||||
let inner_instructions = inner_instructions.map(|inner_instructions| {
|
||||
inner_instructions
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(index, instructions)| InnerInstructions {
|
||||
index: index as u8,
|
||||
instructions: instructions
|
||||
.into_iter()
|
||||
.map(|info| InnerInstruction {
|
||||
instruction: info.instruction,
|
||||
stack_height: Some(u32::from(info.stack_height)),
|
||||
})
|
||||
.collect(),
|
||||
})
|
||||
.filter(|i| !i.instructions.is_empty())
|
||||
.collect()
|
||||
map_inner_instructions(inner_instructions).collect()
|
||||
});
|
||||
|
||||
let pre_token_balances = Some(pre_token_balances);
|
||||
|
|
|
@ -150,6 +150,7 @@ use {
|
|||
hash::{extend_and_hash, hashv, Hash},
|
||||
incinerator,
|
||||
inflation::Inflation,
|
||||
inner_instruction::InnerInstructions,
|
||||
instruction::InstructionError,
|
||||
loader_v4::{self, LoaderV4State, LoaderV4Status},
|
||||
message::{AccountKeys, SanitizedMessage},
|
||||
|
@ -338,6 +339,7 @@ pub struct TransactionSimulationResult {
|
|||
pub post_simulation_accounts: Vec<TransactionAccount>,
|
||||
pub units_consumed: u64,
|
||||
pub return_data: Option<TransactionReturnData>,
|
||||
pub inner_instructions: Option<Vec<InnerInstructions>>,
|
||||
}
|
||||
pub struct TransactionBalancesSet {
|
||||
pub pre_balances: TransactionBalances,
|
||||
|
@ -4308,23 +4310,25 @@ impl Bank {
|
|||
/// Run transactions against a frozen bank without committing the results
|
||||
pub fn simulate_transaction(
|
||||
&self,
|
||||
transaction: SanitizedTransaction,
|
||||
transaction: &SanitizedTransaction,
|
||||
enable_cpi_recording: bool,
|
||||
) -> TransactionSimulationResult {
|
||||
assert!(self.is_frozen(), "simulation bank must be frozen");
|
||||
|
||||
self.simulate_transaction_unchecked(transaction)
|
||||
self.simulate_transaction_unchecked(transaction, enable_cpi_recording)
|
||||
}
|
||||
|
||||
/// Run transactions against a bank without committing the results; does not check if the bank
|
||||
/// is frozen, enabling use in single-Bank test frameworks
|
||||
pub fn simulate_transaction_unchecked(
|
||||
&self,
|
||||
transaction: SanitizedTransaction,
|
||||
transaction: &SanitizedTransaction,
|
||||
enable_cpi_recording: bool,
|
||||
) -> TransactionSimulationResult {
|
||||
let account_keys = transaction.message().account_keys();
|
||||
let number_of_accounts = account_keys.len();
|
||||
let account_overrides = self.get_account_overrides_for_simulation(&account_keys);
|
||||
let batch = self.prepare_unlocked_batch_from_single_tx(&transaction);
|
||||
let batch = self.prepare_unlocked_batch_from_single_tx(transaction);
|
||||
let mut timings = ExecuteTimings::default();
|
||||
|
||||
let LoadAndExecuteTransactionsOutput {
|
||||
|
@ -4337,7 +4341,7 @@ impl Bank {
|
|||
// for processing. During forwarding, the transaction could expire if the
|
||||
// delay is not accounted for.
|
||||
MAX_PROCESSING_AGE - MAX_TRANSACTION_FORWARDING_DELAY,
|
||||
false,
|
||||
enable_cpi_recording,
|
||||
true,
|
||||
true,
|
||||
&mut timings,
|
||||
|
@ -4374,11 +4378,13 @@ impl Bank {
|
|||
|
||||
let execution_result = execution_results.pop().unwrap();
|
||||
let flattened_result = execution_result.flattened_result();
|
||||
let (logs, return_data) = match execution_result {
|
||||
TransactionExecutionResult::Executed { details, .. } => {
|
||||
(details.log_messages, details.return_data)
|
||||
}
|
||||
TransactionExecutionResult::NotExecuted(_) => (None, None),
|
||||
let (logs, return_data, inner_instructions) = match execution_result {
|
||||
TransactionExecutionResult::Executed { details, .. } => (
|
||||
details.log_messages,
|
||||
details.return_data,
|
||||
details.inner_instructions,
|
||||
),
|
||||
TransactionExecutionResult::NotExecuted(_) => (None, None, None),
|
||||
};
|
||||
let logs = logs.unwrap_or_default();
|
||||
|
||||
|
@ -4388,6 +4394,7 @@ impl Bank {
|
|||
post_simulation_accounts,
|
||||
units_consumed,
|
||||
return_data,
|
||||
inner_instructions,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -14136,6 +14136,6 @@ fn test_failed_simulation_compute_units() {
|
|||
|
||||
bank.freeze();
|
||||
let sanitized = SanitizedTransaction::from_transaction_for_tests(transaction);
|
||||
let simulation = bank.simulate_transaction(sanitized);
|
||||
let simulation = bank.simulate_transaction(&sanitized, false);
|
||||
assert_eq!(TEST_UNITS, simulation.units_consumed);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
use {
|
||||
crate::instruction::CompiledInstruction,
|
||||
serde::{Deserialize, Serialize},
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
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,
|
||||
}
|
||||
|
||||
/// An ordered list of compiled instructions that were invoked during a
|
||||
/// transaction instruction
|
||||
pub type InnerInstructions = Vec<InnerInstruction>;
|
||||
|
||||
/// A list of compiled instructions that were invoked during each instruction of
|
||||
/// a transaction
|
||||
pub type InnerInstructionsList = Vec<InnerInstructions>;
|
|
@ -78,6 +78,7 @@ pub mod genesis_config;
|
|||
pub mod hard_forks;
|
||||
pub mod hash;
|
||||
pub mod inflation;
|
||||
pub mod inner_instruction;
|
||||
pub mod log;
|
||||
pub mod native_loader;
|
||||
pub mod net;
|
||||
|
|
|
@ -230,6 +230,27 @@ pub struct InnerInstruction {
|
|||
pub stack_height: Option<u32>,
|
||||
}
|
||||
|
||||
/// Maps a list of inner instructions from `solana_sdk` into a list of this
|
||||
/// crate's representation of inner instructions (with instruction indices).
|
||||
pub fn map_inner_instructions(
|
||||
inner_instructions: solana_sdk::inner_instruction::InnerInstructionsList,
|
||||
) -> impl Iterator<Item = InnerInstructions> {
|
||||
inner_instructions
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(index, instructions)| InnerInstructions {
|
||||
index: index as u8,
|
||||
instructions: instructions
|
||||
.into_iter()
|
||||
.map(|info| InnerInstruction {
|
||||
stack_height: Some(u32::from(info.stack_height)),
|
||||
instruction: info.instruction,
|
||||
})
|
||||
.collect(),
|
||||
})
|
||||
.filter(|i| !i.instructions.is_empty())
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UiInnerInstructions {
|
||||
|
@ -240,7 +261,7 @@ pub struct UiInnerInstructions {
|
|||
}
|
||||
|
||||
impl UiInnerInstructions {
|
||||
fn parse(inner_instructions: InnerInstructions, account_keys: &AccountKeys) -> Self {
|
||||
pub fn parse(inner_instructions: InnerInstructions, account_keys: &AccountKeys) -> Self {
|
||||
Self {
|
||||
index: inner_instructions.index,
|
||||
instructions: inner_instructions
|
||||
|
|
Loading…
Reference in New Issue