diff --git a/accounts-db/src/transaction_results.rs b/accounts-db/src/transaction_results.rs index 46e59cd4c..bcfe18585 100644 --- a/accounts-db/src/transaction_results.rs +++ b/accounts-db/src/transaction_results.rs @@ -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; - -#[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; - /// Extract the InnerInstructionsList from a TransactionContext pub fn inner_instructions_list_from_instruction_trace( transaction_context: &TransactionContext, diff --git a/banks-interface/src/lib.rs b/banks-interface/src/lib.rs index 93538e00c..9e2e50922 100644 --- a/banks-interface/src/lib.rs +++ b/banks-interface/src/lib.rs @@ -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, pub units_consumed: u64, pub return_data: Option, + pub inner_instructions: Option>, } #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] diff --git a/banks-server/src/banks_server.rs b/banks-server/src/banks_server.rs index a97019a12..1fcdce1ad 100644 --- a/banks-server/src/banks_server.rs +++ b/banks-server/src/banks_server.rs @@ -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), diff --git a/programs/sbf/tests/programs.rs b/programs/sbf/tests/programs.rs index 6285cee35..6476aa842 100644 --- a/programs/sbf/tests/programs.rs +++ b/programs/sbf/tests/programs.rs @@ -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()); diff --git a/rpc-client-api/src/config.rs b/rpc-client-api/src/config.rs index 9ecff334c..cecc0b64b 100644 --- a/rpc-client-api/src/config.rs +++ b/rpc-client-api/src/config.rs @@ -44,6 +44,8 @@ pub struct RpcSimulateTransactionConfig { pub encoding: Option, pub accounts: Option, pub min_context_slot: Option, + #[serde(default)] + pub inner_instructions: bool, } #[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)] diff --git a/rpc-client-api/src/response.rs b/rpc-client-api/src/response.rs index 7591c58c0..fa70e89b6 100644 --- a/rpc-client-api/src/response.rs +++ b/rpc-client-api/src/response.rs @@ -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>>, pub units_consumed: Option, pub return_data: Option, + pub inner_instructions: Option>, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] diff --git a/rpc-client/src/mock_sender.rs b/rpc-client/src/mock_sender.rs index 654f45d02..de8f8cddd 100644 --- a/rpc-client/src/mock_sender.rs +++ b/rpc-client/src/mock_sender.rs @@ -350,6 +350,7 @@ impl RpcSender for MockSender { accounts: None, units_consumed: None, return_data: None, + inner_instructions: None, }, })?, "getMinimumBalanceForRentExemption" => json![20], diff --git a/rpc/src/rpc.rs b/rpc/src/rpc.rs index 98051b0d5..c5b1bc3a9 100644 --- a/rpc/src/rpc.rs +++ b/rpc/src/rpc.rs @@ -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(), ) ); diff --git a/rpc/src/transaction_status_service.rs b/rpc/src/transaction_status_service.rs index d36efbc6c..eca53c666 100644 --- a/rpc/src/transaction_status_service.rs +++ b/rpc/src/transaction_status_service.rs @@ -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); diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index 77cb74d32..f1d941dae 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -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, pub units_consumed: u64, pub return_data: Option, + pub inner_instructions: Option>, } 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, } } diff --git a/runtime/src/bank/tests.rs b/runtime/src/bank/tests.rs index 318f5416d..a849101fd 100644 --- a/runtime/src/bank/tests.rs +++ b/runtime/src/bank/tests.rs @@ -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); } diff --git a/sdk/src/inner_instruction.rs b/sdk/src/inner_instruction.rs new file mode 100644 index 000000000..1a715979e --- /dev/null +++ b/sdk/src/inner_instruction.rs @@ -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; + +/// A list of compiled instructions that were invoked during each instruction of +/// a transaction +pub type InnerInstructionsList = Vec; diff --git a/sdk/src/lib.rs b/sdk/src/lib.rs index 1b6763f8e..49fc7c045 100644 --- a/sdk/src/lib.rs +++ b/sdk/src/lib.rs @@ -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; diff --git a/transaction-status/src/lib.rs b/transaction-status/src/lib.rs index fac20d985..0eb13d368 100644 --- a/transaction-status/src/lib.rs +++ b/transaction-status/src/lib.rs @@ -230,6 +230,27 @@ pub struct InnerInstruction { pub stack_height: Option, } +/// 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 { + 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