diff --git a/ledger/src/blockstore.rs b/ledger/src/blockstore.rs index f3f3f520f0ac6a..0540ea5d2d0f99 100644 --- a/ledger/src/blockstore.rs +++ b/ledger/src/blockstore.rs @@ -4394,7 +4394,7 @@ pub mod tests { rand::{seq::SliceRandom, thread_rng}, solana_account_decoder::parse_token::UiTokenAmount, solana_entry::entry::{next_entry, next_entry_mut}, - solana_runtime::bank::{Bank, RewardType}, + solana_runtime::bank::{Bank, InnerInstruction, RewardType}, solana_sdk::{ hash::{self, hash, Hash}, instruction::CompiledInstruction, @@ -6838,7 +6838,10 @@ pub mod tests { let post_balances_vec = vec![3, 2, 1]; let inner_instructions_vec = vec![InnerInstructions { index: 0, - instructions: vec![CompiledInstruction::new(1, &(), vec![0])], + instructions: vec![InnerInstruction { + instruction: CompiledInstruction::new(1, &(), vec![0]), + stack_height: Some(2), + }], }]; let log_messages_vec = vec![String::from("Test message\n")]; let pre_token_balances_vec = vec![]; @@ -7570,7 +7573,10 @@ pub mod tests { } let inner_instructions = Some(vec![InnerInstructions { index: 0, - instructions: vec![CompiledInstruction::new(1, &(), vec![0])], + instructions: vec![InnerInstruction { + instruction: CompiledInstruction::new(1, &(), vec![0]), + stack_height: Some(2), + }], }]); let log_messages = Some(vec![String::from("Test message\n")]); let pre_token_balances = Some(vec![]); @@ -7687,7 +7693,10 @@ pub mod tests { } let inner_instructions = Some(vec![InnerInstructions { index: 0, - instructions: vec![CompiledInstruction::new(1, &(), vec![0])], + instructions: vec![InnerInstruction { + instruction: CompiledInstruction::new(1, &(), vec![0]), + stack_height: Some(2), + }], }]); let log_messages = Some(vec![String::from("Test message\n")]); let pre_token_balances = Some(vec![]); diff --git a/rpc-client/src/mock_sender.rs b/rpc-client/src/mock_sender.rs index 346c75278e461c..63198e735944a7 100644 --- a/rpc-client/src/mock_sender.rs +++ b/rpc-client/src/mock_sender.rs @@ -214,6 +214,7 @@ impl RpcSender for MockSender { program_id_index: 2, accounts: vec![0, 1], data: "3Bxs49DitAvXtoDR".to_string(), + stack_height: None, }], address_table_lookups: None, }) diff --git a/rpc/src/transaction_status_service.rs b/rpc/src/transaction_status_service.rs index d0a3fde0cb2ec0..da1528a47d1f84 100644 --- a/rpc/src/transaction_status_service.rs +++ b/rpc/src/transaction_status_service.rs @@ -8,7 +8,7 @@ use { }, solana_runtime::bank::{DurableNonceFee, TransactionExecutionDetails}, solana_transaction_status::{ - extract_and_fmt_memos, InnerInstructions, Reward, TransactionStatusMeta, + extract_and_fmt_memos, InnerInstruction, InnerInstructions, Reward, TransactionStatusMeta, }, std::{ sync::{ @@ -128,7 +128,13 @@ impl TransactionStatusService { .enumerate() .map(|(index, instructions)| InnerInstructions { index: index as u8, - instructions, + instructions: instructions + .into_iter() + .map(|info| InnerInstruction { + instruction: info.instruction, + stack_height: info.stack_height, + }) + .collect(), }) .filter(|i| !i.instructions.is_empty()) .collect() diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index d87d7e63a4914c..a62c9838ff5fdc 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -490,7 +490,16 @@ pub type TransactionBalances = Vec>; /// An ordered list of compiled instructions that were invoked during a /// transaction instruction -pub type InnerInstructions = Vec; +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. This field is set to `None` + /// for transactions that were processed prior to stack height tracking. + pub stack_height: Option, +} /// A list of compiled instructions that were invoked during each instruction of /// a transaction @@ -510,10 +519,12 @@ pub fn inner_instructions_list_from_instruction_trace( if let Ok(instruction_context) = transaction_context.get_instruction_context_at_index_in_trace(index_in_trace) { - if instruction_context.get_stack_height() == TRANSACTION_LEVEL_STACK_HEIGHT { + let stack_height = instruction_context.get_stack_height(); + if stack_height == TRANSACTION_LEVEL_STACK_HEIGHT { outer_instructions.push(Vec::new()); } else if let Some(inner_instructions) = outer_instructions.last_mut() { - inner_instructions.push(CompiledInstruction::new_from_raw_parts( + let stack_height = u32::try_from(stack_height).unwrap_or(u32::MAX); + let instruction = CompiledInstruction::new_from_raw_parts( instruction_context .get_index_of_program_account_in_transaction( instruction_context @@ -531,7 +542,11 @@ pub fn inner_instructions_list_from_instruction_trace( .unwrap_or_default() as u8 }) .collect(), - )); + ); + inner_instructions.push(InnerInstruction { + instruction, + stack_height: Some(stack_height), + }); } else { debug_assert!(false); } @@ -18466,12 +18481,24 @@ pub(crate) mod tests { assert_eq!( inner_instructions, vec![ - vec![CompiledInstruction::new_from_raw_parts(0, vec![1], vec![])], + vec![InnerInstruction { + instruction: CompiledInstruction::new_from_raw_parts(0, vec![1], vec![]), + stack_height: Some(2), + }], vec![], vec![ - CompiledInstruction::new_from_raw_parts(0, vec![4], vec![]), - CompiledInstruction::new_from_raw_parts(0, vec![5], vec![]), - CompiledInstruction::new_from_raw_parts(0, vec![6], vec![]) + InnerInstruction { + instruction: CompiledInstruction::new_from_raw_parts(0, vec![4], vec![]), + stack_height: Some(2), + }, + InnerInstruction { + instruction: CompiledInstruction::new_from_raw_parts(0, vec![5], vec![]), + stack_height: Some(3), + }, + InnerInstruction { + instruction: CompiledInstruction::new_from_raw_parts(0, vec![6], vec![]), + stack_height: Some(2), + }, ] ] ); diff --git a/storage-proto/proto/confirmed_block.proto b/storage-proto/proto/confirmed_block.proto index ef8dc0fb215f54..47548ea13bc6a4 100644 --- a/storage-proto/proto/confirmed_block.proto +++ b/storage-proto/proto/confirmed_block.proto @@ -72,7 +72,18 @@ message TransactionError { message InnerInstructions { uint32 index = 1; - repeated CompiledInstruction instructions = 2; + repeated InnerInstruction instructions = 2; +} + +message InnerInstruction { + uint32 program_id_index = 1; + bytes accounts = 2; + bytes data = 3; + + // Invocation stack height of an inner instruction. + // Available since Solana v1.14.6 + // Set to `None` for txs executed on earlier versions. + optional uint32 stack_height = 4; } message CompiledInstruction { diff --git a/storage-proto/src/convert.rs b/storage-proto/src/convert.rs index 4e2d6ef3df2767..17fbfd641a39c1 100644 --- a/storage-proto/src/convert.rs +++ b/storage-proto/src/convert.rs @@ -15,9 +15,9 @@ use { transaction_context::TransactionReturnData, }, solana_transaction_status::{ - ConfirmedBlock, InnerInstructions, Reward, RewardType, TransactionByAddrInfo, - TransactionStatusMeta, TransactionTokenBalance, TransactionWithStatusMeta, - VersionedConfirmedBlock, VersionedTransactionWithStatusMeta, + ConfirmedBlock, InnerInstruction, InnerInstructions, Reward, RewardType, + TransactionByAddrInfo, TransactionStatusMeta, TransactionTokenBalance, + TransactionWithStatusMeta, VersionedConfirmedBlock, VersionedTransactionWithStatusMeta, }, std::{ convert::{TryFrom, TryInto}, @@ -647,6 +647,30 @@ impl From for CompiledInstruction { } } +impl From for generated::InnerInstruction { + fn from(value: InnerInstruction) -> Self { + Self { + program_id_index: value.instruction.program_id_index as u32, + accounts: value.instruction.accounts, + data: value.instruction.data, + stack_height: value.stack_height, + } + } +} + +impl From for InnerInstruction { + fn from(value: generated::InnerInstruction) -> Self { + Self { + instruction: CompiledInstruction { + program_id_index: value.program_id_index as u8, + accounts: value.accounts, + data: value.data, + }, + stack_height: value.stack_height, + } + } +} + impl TryFrom for TransactionError { type Error = &'static str; diff --git a/transaction-status/src/lib.rs b/transaction-status/src/lib.rs index d907ebcf69ebfb..4811c5b6b48f78 100644 --- a/transaction-status/src/lib.rs +++ b/transaction-status/src/lib.rs @@ -139,13 +139,17 @@ pub enum UiInstruction { } impl UiInstruction { - fn parse(instruction: &CompiledInstruction, account_keys: &AccountKeys) -> Self { + fn parse( + instruction: &CompiledInstruction, + account_keys: &AccountKeys, + stack_height: Option, + ) -> Self { let program_id = &account_keys[instruction.program_id_index as usize]; - if let Ok(parsed_instruction) = parse(program_id, instruction, account_keys) { + if let Ok(parsed_instruction) = parse(program_id, instruction, account_keys, stack_height) { UiInstruction::Parsed(UiParsedInstruction::Parsed(parsed_instruction)) } else { UiInstruction::Parsed(UiParsedInstruction::PartiallyDecoded( - UiPartiallyDecodedInstruction::from(instruction, account_keys), + UiPartiallyDecodedInstruction::from(instruction, account_keys, stack_height), )) } } @@ -165,14 +169,16 @@ pub struct UiCompiledInstruction { pub program_id_index: u8, pub accounts: Vec, pub data: String, + pub stack_height: Option, } -impl From<&CompiledInstruction> for UiCompiledInstruction { - fn from(instruction: &CompiledInstruction) -> Self { +impl UiCompiledInstruction { + fn from(instruction: &CompiledInstruction, stack_height: Option) -> Self { Self { program_id_index: instruction.program_id_index, accounts: instruction.accounts.clone(), - data: bs58::encode(instruction.data.clone()).into_string(), + data: bs58::encode(&instruction.data).into_string(), + stack_height, } } } @@ -184,10 +190,15 @@ pub struct UiPartiallyDecodedInstruction { pub program_id: String, pub accounts: Vec, pub data: String, + pub stack_height: Option, } impl UiPartiallyDecodedInstruction { - fn from(instruction: &CompiledInstruction, account_keys: &AccountKeys) -> Self { + fn from( + instruction: &CompiledInstruction, + account_keys: &AccountKeys, + stack_height: Option, + ) -> Self { Self { program_id: account_keys[instruction.program_id_index as usize].to_string(), accounts: instruction @@ -196,6 +207,7 @@ impl UiPartiallyDecodedInstruction { .map(|&i| account_keys[i as usize].to_string()) .collect(), data: bs58::encode(instruction.data.clone()).into_string(), + stack_height, } } } @@ -205,7 +217,15 @@ pub struct InnerInstructions { /// Transaction instruction index pub index: u8, /// List of inner instructions - pub instructions: Vec, + pub instructions: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct InnerInstruction { + /// Compiled instruction + pub instruction: CompiledInstruction, + /// Invocation stack height of the instruction, + pub stack_height: Option, } #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] @@ -224,7 +244,14 @@ impl UiInnerInstructions { instructions: inner_instructions .instructions .iter() - .map(|ix| UiInstruction::parse(ix, account_keys)) + .map( + |InnerInstruction { + instruction: ix, + stack_height, + }| { + UiInstruction::parse(ix, account_keys, *stack_height) + }, + ) .collect(), } } @@ -237,7 +264,14 @@ impl From for UiInnerInstructions { instructions: inner_instructions .instructions .iter() - .map(|ix| UiInstruction::Compiled(ix.into())) + .map( + |InnerInstruction { + instruction: ix, + stack_height, + }| { + UiInstruction::Compiled(UiCompiledInstruction::from(ix, *stack_height)) + }, + ) .collect(), } } @@ -1091,7 +1125,7 @@ impl Encodable for Message { instructions: self .instructions .iter() - .map(|instruction| UiInstruction::parse(instruction, &account_keys)) + .map(|instruction| UiInstruction::parse(instruction, &account_keys, None)) .collect(), address_table_lookups: None, }) @@ -1100,7 +1134,11 @@ impl Encodable for Message { header: self.header, account_keys: self.account_keys.iter().map(ToString::to_string).collect(), recent_blockhash: self.recent_blockhash.to_string(), - instructions: self.instructions.iter().map(Into::into).collect(), + instructions: self + .instructions + .iter() + .map(|ix| UiCompiledInstruction::from(ix, None)) + .collect(), address_table_lookups: None, }) } @@ -1123,7 +1161,7 @@ impl EncodableWithMeta for v0::Message { instructions: self .instructions .iter() - .map(|instruction| UiInstruction::parse(instruction, &account_keys)) + .map(|instruction| UiInstruction::parse(instruction, &account_keys, None)) .collect(), address_table_lookups: Some( self.address_table_lookups.iter().map(Into::into).collect(), @@ -1138,7 +1176,11 @@ impl EncodableWithMeta for v0::Message { header: self.header, account_keys: self.account_keys.iter().map(ToString::to_string).collect(), recent_blockhash: self.recent_blockhash.to_string(), - instructions: self.instructions.iter().map(Into::into).collect(), + instructions: self + .instructions + .iter() + .map(|ix| UiCompiledInstruction::from(ix, None)) + .collect(), address_table_lookups: Some( self.address_table_lookups.iter().map(Into::into).collect(), ), diff --git a/transaction-status/src/parse_instruction.rs b/transaction-status/src/parse_instruction.rs index e64f4f4493141f..f4a2a9748f355a 100644 --- a/transaction-status/src/parse_instruction.rs +++ b/transaction-status/src/parse_instruction.rs @@ -81,6 +81,7 @@ pub struct ParsedInstruction { pub program: String, pub program_id: String, pub parsed: Value, + pub stack_height: Option, } #[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] @@ -110,6 +111,7 @@ pub fn parse( program_id: &Pubkey, instruction: &CompiledInstruction, account_keys: &AccountKeys, + stack_height: Option, ) -> Result { let program_name = PARSABLE_PROGRAM_IDS .get(program_id) @@ -137,6 +139,7 @@ pub fn parse( program: format!("{:?}", program_name).to_kebab_case(), program_id: program_id.to_string(), parsed: parsed_json, + stack_height, }) } @@ -177,24 +180,26 @@ mod test { data: vec![240, 159, 166, 150], }; assert_eq!( - parse(&MEMO_V1_PROGRAM_ID, &memo_instruction, &no_keys).unwrap(), + parse(&MEMO_V1_PROGRAM_ID, &memo_instruction, &no_keys, None).unwrap(), ParsedInstruction { program: "spl-memo".to_string(), program_id: MEMO_V1_PROGRAM_ID.to_string(), parsed: json!("🦖"), + stack_height: None, } ); assert_eq!( - parse(&MEMO_V3_PROGRAM_ID, &memo_instruction, &no_keys).unwrap(), + parse(&MEMO_V3_PROGRAM_ID, &memo_instruction, &no_keys, Some(1)).unwrap(), ParsedInstruction { program: "spl-memo".to_string(), program_id: MEMO_V3_PROGRAM_ID.to_string(), parsed: json!("🦖"), + stack_height: Some(1), } ); let non_parsable_program_id = Pubkey::new(&[1; 32]); - assert!(parse(&non_parsable_program_id, &memo_instruction, &no_keys).is_err()); + assert!(parse(&non_parsable_program_id, &memo_instruction, &no_keys, None).is_err()); } #[test]