Skip to content

Commit

Permalink
RPC: Add inner instructions to simulate transaction response (solana-…
Browse files Browse the repository at this point in the history
…labs#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 <[email protected]>

---------

Co-authored-by: Tyera <[email protected]>
  • Loading branch information
Joe C and CriesofCarrots authored Dec 16, 2023
1 parent 1f2b72b commit 171c58c
Show file tree
Hide file tree
Showing 14 changed files with 111 additions and 71 deletions.
22 changes: 6 additions & 16 deletions accounts-db/src/transaction_results.rs
Original file line number Diff line number Diff line change
@@ -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},
Expand Down Expand Up @@ -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,
Expand Down
2 changes: 2 additions & 0 deletions banks-interface/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use {
commitment_config::CommitmentLevel,
fee_calculator::FeeCalculator,
hash::Hash,
inner_instruction::InnerInstructions,
message::Message,
pubkey::Pubkey,
signature::Signature,
Expand Down Expand Up @@ -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)]
Expand Down
5 changes: 4 additions & 1 deletion banks-server/src/banks_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
20 changes: 3 additions & 17 deletions programs/sbf/tests/programs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ use {
transaction::VersionedTransaction,
},
solana_transaction_status::{
ConfirmedTransactionWithStatusMeta, InnerInstructions, TransactionStatusMeta,
map_inner_instructions, ConfirmedTransactionWithStatusMeta, TransactionStatusMeta,
TransactionWithStatusMeta, VersionedTransactionWithStatusMeta,
},
std::collections::HashMap,
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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());

Expand Down
2 changes: 2 additions & 0 deletions rpc-client-api/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down
3 changes: 2 additions & 1 deletion rpc-client-api/src/response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use {
},
solana_transaction_status::{
ConfirmedTransactionStatusWithSignature, TransactionConfirmationStatus, UiConfirmedBlock,
UiTransactionReturnData,
UiInnerInstructions, UiTransactionReturnData,
},
std::{collections::HashMap, fmt, net::SocketAddr, str::FromStr},
thiserror::Error,
Expand Down Expand Up @@ -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)]
Expand Down
1 change: 1 addition & 0 deletions rpc-client/src/mock_sender.rs
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,7 @@ impl RpcSender for MockSender {
accounts: None,
units_consumed: None,
return_data: None,
inner_instructions: None,
},
})?,
"getMinimumBalanceForRentExemption" => json![20],
Expand Down
35 changes: 27 additions & 8 deletions rpc/src/rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand Down Expand Up @@ -3266,6 +3266,7 @@ pub mod rpc_full {
use {
super::*,
solana_sdk::message::{SanitizedVersionedMessage, VersionedMessage},
solana_transaction_status::UiInnerInstructions,
};
#[rpc]
pub trait Full {
Expand Down Expand Up @@ -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 => {
Expand All @@ -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());
Expand Down Expand Up @@ -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(|| {
Expand Down Expand Up @@ -3753,15 +3757,18 @@ 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,
logs,
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
Expand Down Expand Up @@ -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 {
Expand All @@ -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,
},
))
}
Expand Down Expand Up @@ -5913,6 +5927,7 @@ pub mod tests {
}
],
"err":null,
"innerInstructions": null,
"logs":[
"Program 11111111111111111111111111111111 invoke [1]",
"Program 11111111111111111111111111111111 success"
Expand Down Expand Up @@ -5997,6 +6012,7 @@ pub mod tests {
"value":{
"accounts":null,
"err":null,
"innerInstructions":null,
"logs":[
"Program 11111111111111111111111111111111 invoke [1]",
"Program 11111111111111111111111111111111 success"
Expand Down Expand Up @@ -6025,6 +6041,7 @@ pub mod tests {
"value":{
"accounts":null,
"err":null,
"innerInstructions":null,
"logs":[
"Program 11111111111111111111111111111111 invoke [1]",
"Program 11111111111111111111111111111111 success"
Expand Down Expand Up @@ -6077,6 +6094,7 @@ pub mod tests {
"value":{
"err":"BlockhashNotFound",
"accounts":null,
"innerInstructions":null,
"logs":[],
"returnData":null,
"unitsConsumed":0,
Expand All @@ -6103,6 +6121,7 @@ pub mod tests {
"value":{
"accounts":null,
"err":null,
"innerInstructions":null,
"logs":[
"Program 11111111111111111111111111111111 invoke [1]",
"Program 11111111111111111111111111111111 success"
Expand Down Expand Up @@ -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(),
)
);

Expand Down
18 changes: 2 additions & 16 deletions rpc/src/transaction_status_service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand Down Expand Up @@ -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);
Expand Down
27 changes: 17 additions & 10 deletions runtime/src/bank.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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 {
Expand All @@ -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,
Expand Down Expand Up @@ -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();

Expand All @@ -4388,6 +4394,7 @@ impl Bank {
post_simulation_accounts,
units_consumed,
return_data,
inner_instructions,
}
}

Expand Down
Loading

0 comments on commit 171c58c

Please sign in to comment.