Record inner instruction stack height (#28430)

* Record inner instruction stack height

* fix sbf tests

* feedback
This commit is contained in:
Justin Starry 2022-10-26 10:37:44 +08:00 committed by GitHub
parent 74bd87d847
commit 2d8665d307
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 178 additions and 43 deletions

View File

@ -4406,7 +4406,9 @@ pub mod tests {
transaction_context::TransactionReturnData, transaction_context::TransactionReturnData,
}, },
solana_storage_proto::convert::generated, solana_storage_proto::convert::generated,
solana_transaction_status::{InnerInstructions, Reward, Rewards, TransactionTokenBalance}, solana_transaction_status::{
InnerInstruction, InnerInstructions, Reward, Rewards, TransactionTokenBalance,
},
std::{thread::Builder, time::Duration}, std::{thread::Builder, time::Duration},
}; };
@ -6838,7 +6840,10 @@ pub mod tests {
let post_balances_vec = vec![3, 2, 1]; let post_balances_vec = vec![3, 2, 1];
let inner_instructions_vec = vec![InnerInstructions { let inner_instructions_vec = vec![InnerInstructions {
index: 0, 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 log_messages_vec = vec![String::from("Test message\n")];
let pre_token_balances_vec = vec![]; let pre_token_balances_vec = vec![];
@ -7570,7 +7575,10 @@ pub mod tests {
} }
let inner_instructions = Some(vec![InnerInstructions { let inner_instructions = Some(vec![InnerInstructions {
index: 0, 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 log_messages = Some(vec![String::from("Test message\n")]);
let pre_token_balances = Some(vec![]); let pre_token_balances = Some(vec![]);
@ -7687,7 +7695,10 @@ pub mod tests {
} }
let inner_instructions = Some(vec![InnerInstructions { let inner_instructions = Some(vec![InnerInstructions {
index: 0, 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 log_messages = Some(vec![String::from("Test message\n")]);
let pre_token_balances = Some(vec![]); let pre_token_balances = Some(vec![]);

View File

@ -13,7 +13,7 @@ use {
solana_program_runtime::{compute_budget::ComputeBudget, timings::ExecuteTimings}, solana_program_runtime::{compute_budget::ComputeBudget, timings::ExecuteTimings},
solana_runtime::{ solana_runtime::{
bank::{ bank::{
DurableNonceFee, TransactionBalancesSet, TransactionExecutionDetails, DurableNonceFee, InnerInstruction, TransactionBalancesSet, TransactionExecutionDetails,
TransactionExecutionResult, TransactionResults, TransactionExecutionResult, TransactionResults,
}, },
loader_utils::{ loader_utils::{
@ -33,7 +33,6 @@ use {
feature_set::FeatureSet, feature_set::FeatureSet,
fee::FeeStructure, fee::FeeStructure,
fee_calculator::FeeRateGovernor, fee_calculator::FeeRateGovernor,
instruction::CompiledInstruction,
loader_instruction, loader_instruction,
message::{v0::LoadedAddresses, SanitizedMessage}, message::{v0::LoadedAddresses, SanitizedMessage},
rent::Rent, rent::Rent,
@ -374,7 +373,7 @@ fn process_transaction_and_record_inner(
tx: Transaction, tx: Transaction,
) -> ( ) -> (
Result<(), TransactionError>, Result<(), TransactionError>,
Vec<Vec<CompiledInstruction>>, Vec<Vec<InnerInstruction>>,
Vec<String>, Vec<String>,
) { ) {
let signature = tx.signatures.get(0).unwrap().clone(); let signature = tx.signatures.get(0).unwrap().clone();
@ -491,7 +490,13 @@ fn execute_transactions(
.enumerate() .enumerate()
.map(|(index, instructions)| InnerInstructions { .map(|(index, instructions)| InnerInstructions {
index: index as u8, index: index as u8,
instructions, 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()) .filter(|i| !i.instructions.is_empty())
.collect() .collect()
@ -1110,7 +1115,8 @@ fn test_program_sbf_invoke_sanity() {
let invoked_programs: Vec<Pubkey> = inner_instructions[0] let invoked_programs: Vec<Pubkey> = inner_instructions[0]
.iter() .iter()
.map(|ix| message.account_keys[ix.program_id_index as usize].clone()) .map(|ix| &message.account_keys[ix.instruction.program_id_index as usize])
.cloned()
.collect(); .collect();
let expected_invoked_programs = match program.0 { let expected_invoked_programs = match program.0 {
Languages::C => vec![ Languages::C => vec![
@ -1165,7 +1171,8 @@ fn test_program_sbf_invoke_sanity() {
assert_eq!(invoked_programs, expected_invoked_programs); assert_eq!(invoked_programs, expected_invoked_programs);
let no_invoked_programs: Vec<Pubkey> = inner_instructions[1] let no_invoked_programs: Vec<Pubkey> = inner_instructions[1]
.iter() .iter()
.map(|ix| message.account_keys[ix.program_id_index as usize].clone()) .map(|ix| &message.account_keys[ix.instruction.program_id_index as usize])
.cloned()
.collect(); .collect();
assert_eq!(no_invoked_programs.len(), 0); assert_eq!(no_invoked_programs.len(), 0);
@ -1195,7 +1202,8 @@ fn test_program_sbf_invoke_sanity() {
process_transaction_and_record_inner(&bank, tx); process_transaction_and_record_inner(&bank, tx);
let invoked_programs: Vec<Pubkey> = inner_instructions[0] let invoked_programs: Vec<Pubkey> = inner_instructions[0]
.iter() .iter()
.map(|ix| message.account_keys[ix.program_id_index as usize].clone()) .map(|ix| &message.account_keys[ix.instruction.program_id_index as usize])
.cloned()
.collect(); .collect();
assert_eq!(result, Err(expected_error)); assert_eq!(result, Err(expected_error));
assert_eq!(invoked_programs, expected_invoked_programs); assert_eq!(invoked_programs, expected_invoked_programs);
@ -1411,7 +1419,8 @@ fn test_program_sbf_invoke_sanity() {
process_transaction_and_record_inner(&bank, tx); process_transaction_and_record_inner(&bank, tx);
let invoked_programs: Vec<Pubkey> = inner_instructions[0] let invoked_programs: Vec<Pubkey> = inner_instructions[0]
.iter() .iter()
.map(|ix| message.account_keys[ix.program_id_index as usize].clone()) .map(|ix| &message.account_keys[ix.instruction.program_id_index as usize])
.cloned()
.collect(); .collect();
assert_eq!(invoked_programs, vec![system_program::id()]); assert_eq!(invoked_programs, vec![system_program::id()]);
assert_eq!( assert_eq!(

View File

@ -214,6 +214,7 @@ impl RpcSender for MockSender {
program_id_index: 2, program_id_index: 2,
accounts: vec![0, 1], accounts: vec![0, 1],
data: "3Bxs49DitAvXtoDR".to_string(), data: "3Bxs49DitAvXtoDR".to_string(),
stack_height: None,
}], }],
address_table_lookups: None, address_table_lookups: None,
}) })

View File

@ -8,7 +8,7 @@ use {
}, },
solana_runtime::bank::{DurableNonceFee, TransactionExecutionDetails}, solana_runtime::bank::{DurableNonceFee, TransactionExecutionDetails},
solana_transaction_status::{ solana_transaction_status::{
extract_and_fmt_memos, InnerInstructions, Reward, TransactionStatusMeta, extract_and_fmt_memos, InnerInstruction, InnerInstructions, Reward, TransactionStatusMeta,
}, },
std::{ std::{
sync::{ sync::{
@ -128,7 +128,13 @@ impl TransactionStatusService {
.enumerate() .enumerate()
.map(|(index, instructions)| InnerInstructions { .map(|(index, instructions)| InnerInstructions {
index: index as u8, index: index as u8,
instructions, 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()) .filter(|i| !i.instructions.is_empty())
.collect() .collect()

View File

@ -491,7 +491,15 @@ pub type TransactionBalances = Vec<Vec<u64>>;
/// An ordered list of compiled instructions that were invoked during a /// An ordered list of compiled instructions that were invoked during a
/// transaction instruction /// transaction instruction
pub type InnerInstructions = Vec<CompiledInstruction>; 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 list of compiled instructions that were invoked during each instruction of
/// a transaction /// a transaction
@ -511,10 +519,12 @@ pub fn inner_instructions_list_from_instruction_trace(
if let Ok(instruction_context) = if let Ok(instruction_context) =
transaction_context.get_instruction_context_at_index_in_trace(index_in_trace) 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()); outer_instructions.push(Vec::new());
} else if let Some(inner_instructions) = outer_instructions.last_mut() { } else if let Some(inner_instructions) = outer_instructions.last_mut() {
inner_instructions.push(CompiledInstruction::new_from_raw_parts( let stack_height = u8::try_from(stack_height).unwrap_or(u8::MAX);
let instruction = CompiledInstruction::new_from_raw_parts(
instruction_context instruction_context
.get_index_of_program_account_in_transaction( .get_index_of_program_account_in_transaction(
instruction_context instruction_context
@ -532,7 +542,11 @@ pub fn inner_instructions_list_from_instruction_trace(
.unwrap_or_default() as u8 .unwrap_or_default() as u8
}) })
.collect(), .collect(),
)); );
inner_instructions.push(InnerInstruction {
instruction,
stack_height,
});
} else { } else {
debug_assert!(false); debug_assert!(false);
} }
@ -19269,12 +19283,24 @@ pub(crate) mod tests {
assert_eq!( assert_eq!(
inner_instructions, inner_instructions,
vec![ 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: 2,
}],
vec![], vec![],
vec![ vec![
CompiledInstruction::new_from_raw_parts(0, vec![4], vec![]), InnerInstruction {
CompiledInstruction::new_from_raw_parts(0, vec![5], vec![]), instruction: CompiledInstruction::new_from_raw_parts(0, vec![4], vec![]),
CompiledInstruction::new_from_raw_parts(0, vec![6], vec![]) stack_height: 2,
},
InnerInstruction {
instruction: CompiledInstruction::new_from_raw_parts(0, vec![5], vec![]),
stack_height: 3,
},
InnerInstruction {
instruction: CompiledInstruction::new_from_raw_parts(0, vec![6], vec![]),
stack_height: 2,
},
] ]
] ]
); );

View File

@ -72,7 +72,18 @@ message TransactionError {
message InnerInstructions { message InnerInstructions {
uint32 index = 1; 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 { message CompiledInstruction {

View File

@ -15,9 +15,9 @@ use {
transaction_context::TransactionReturnData, transaction_context::TransactionReturnData,
}, },
solana_transaction_status::{ solana_transaction_status::{
ConfirmedBlock, InnerInstructions, Reward, RewardType, TransactionByAddrInfo, ConfirmedBlock, InnerInstruction, InnerInstructions, Reward, RewardType,
TransactionStatusMeta, TransactionTokenBalance, TransactionWithStatusMeta, TransactionByAddrInfo, TransactionStatusMeta, TransactionTokenBalance,
VersionedConfirmedBlock, VersionedTransactionWithStatusMeta, TransactionWithStatusMeta, VersionedConfirmedBlock, VersionedTransactionWithStatusMeta,
}, },
std::{ std::{
convert::{TryFrom, TryInto}, convert::{TryFrom, TryInto},
@ -647,6 +647,30 @@ impl From<generated::CompiledInstruction> for CompiledInstruction {
} }
} }
impl From<InnerInstruction> 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<generated::InnerInstruction> 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<tx_by_addr::TransactionError> for TransactionError { impl TryFrom<tx_by_addr::TransactionError> for TransactionError {
type Error = &'static str; type Error = &'static str;

View File

@ -139,13 +139,17 @@ pub enum UiInstruction {
} }
impl UiInstruction { impl UiInstruction {
fn parse(instruction: &CompiledInstruction, account_keys: &AccountKeys) -> Self { fn parse(
instruction: &CompiledInstruction,
account_keys: &AccountKeys,
stack_height: Option<u32>,
) -> Self {
let program_id = &account_keys[instruction.program_id_index as usize]; 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)) UiInstruction::Parsed(UiParsedInstruction::Parsed(parsed_instruction))
} else { } else {
UiInstruction::Parsed(UiParsedInstruction::PartiallyDecoded( 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 program_id_index: u8,
pub accounts: Vec<u8>, pub accounts: Vec<u8>,
pub data: String, pub data: String,
pub stack_height: Option<u32>,
} }
impl From<&CompiledInstruction> for UiCompiledInstruction { impl UiCompiledInstruction {
fn from(instruction: &CompiledInstruction) -> Self { fn from(instruction: &CompiledInstruction, stack_height: Option<u32>) -> Self {
Self { Self {
program_id_index: instruction.program_id_index, program_id_index: instruction.program_id_index,
accounts: instruction.accounts.clone(), 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 program_id: String,
pub accounts: Vec<String>, pub accounts: Vec<String>,
pub data: String, pub data: String,
pub stack_height: Option<u32>,
} }
impl UiPartiallyDecodedInstruction { impl UiPartiallyDecodedInstruction {
fn from(instruction: &CompiledInstruction, account_keys: &AccountKeys) -> Self { fn from(
instruction: &CompiledInstruction,
account_keys: &AccountKeys,
stack_height: Option<u32>,
) -> Self {
Self { Self {
program_id: account_keys[instruction.program_id_index as usize].to_string(), program_id: account_keys[instruction.program_id_index as usize].to_string(),
accounts: instruction accounts: instruction
@ -196,6 +207,7 @@ impl UiPartiallyDecodedInstruction {
.map(|&i| account_keys[i as usize].to_string()) .map(|&i| account_keys[i as usize].to_string())
.collect(), .collect(),
data: bs58::encode(instruction.data.clone()).into_string(), data: bs58::encode(instruction.data.clone()).into_string(),
stack_height,
} }
} }
} }
@ -205,7 +217,15 @@ pub struct InnerInstructions {
/// Transaction instruction index /// Transaction instruction index
pub index: u8, pub index: u8,
/// List of inner instructions /// List of inner instructions
pub instructions: Vec<CompiledInstruction>, pub instructions: Vec<InnerInstruction>,
}
#[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<u32>,
} }
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
@ -224,7 +244,14 @@ impl UiInnerInstructions {
instructions: inner_instructions instructions: inner_instructions
.instructions .instructions
.iter() .iter()
.map(|ix| UiInstruction::parse(ix, account_keys)) .map(
|InnerInstruction {
instruction: ix,
stack_height,
}| {
UiInstruction::parse(ix, account_keys, *stack_height)
},
)
.collect(), .collect(),
} }
} }
@ -237,7 +264,14 @@ impl From<InnerInstructions> for UiInnerInstructions {
instructions: inner_instructions instructions: inner_instructions
.instructions .instructions
.iter() .iter()
.map(|ix| UiInstruction::Compiled(ix.into())) .map(
|InnerInstruction {
instruction: ix,
stack_height,
}| {
UiInstruction::Compiled(UiCompiledInstruction::from(ix, *stack_height))
},
)
.collect(), .collect(),
} }
} }
@ -1091,7 +1125,7 @@ impl Encodable for Message {
instructions: self instructions: self
.instructions .instructions
.iter() .iter()
.map(|instruction| UiInstruction::parse(instruction, &account_keys)) .map(|instruction| UiInstruction::parse(instruction, &account_keys, None))
.collect(), .collect(),
address_table_lookups: None, address_table_lookups: None,
}) })
@ -1100,7 +1134,11 @@ impl Encodable for Message {
header: self.header, header: self.header,
account_keys: self.account_keys.iter().map(ToString::to_string).collect(), account_keys: self.account_keys.iter().map(ToString::to_string).collect(),
recent_blockhash: self.recent_blockhash.to_string(), 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, address_table_lookups: None,
}) })
} }
@ -1123,7 +1161,7 @@ impl EncodableWithMeta for v0::Message {
instructions: self instructions: self
.instructions .instructions
.iter() .iter()
.map(|instruction| UiInstruction::parse(instruction, &account_keys)) .map(|instruction| UiInstruction::parse(instruction, &account_keys, None))
.collect(), .collect(),
address_table_lookups: Some( address_table_lookups: Some(
self.address_table_lookups.iter().map(Into::into).collect(), self.address_table_lookups.iter().map(Into::into).collect(),
@ -1138,7 +1176,11 @@ impl EncodableWithMeta for v0::Message {
header: self.header, header: self.header,
account_keys: self.account_keys.iter().map(ToString::to_string).collect(), account_keys: self.account_keys.iter().map(ToString::to_string).collect(),
recent_blockhash: self.recent_blockhash.to_string(), 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( address_table_lookups: Some(
self.address_table_lookups.iter().map(Into::into).collect(), self.address_table_lookups.iter().map(Into::into).collect(),
), ),

View File

@ -81,6 +81,7 @@ pub struct ParsedInstruction {
pub program: String, pub program: String,
pub program_id: String, pub program_id: String,
pub parsed: Value, pub parsed: Value,
pub stack_height: Option<u32>,
} }
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] #[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
@ -110,6 +111,7 @@ pub fn parse(
program_id: &Pubkey, program_id: &Pubkey,
instruction: &CompiledInstruction, instruction: &CompiledInstruction,
account_keys: &AccountKeys, account_keys: &AccountKeys,
stack_height: Option<u32>,
) -> Result<ParsedInstruction, ParseInstructionError> { ) -> Result<ParsedInstruction, ParseInstructionError> {
let program_name = PARSABLE_PROGRAM_IDS let program_name = PARSABLE_PROGRAM_IDS
.get(program_id) .get(program_id)
@ -137,6 +139,7 @@ pub fn parse(
program: format!("{:?}", program_name).to_kebab_case(), program: format!("{:?}", program_name).to_kebab_case(),
program_id: program_id.to_string(), program_id: program_id.to_string(),
parsed: parsed_json, parsed: parsed_json,
stack_height,
}) })
} }
@ -177,24 +180,26 @@ mod test {
data: vec![240, 159, 166, 150], data: vec![240, 159, 166, 150],
}; };
assert_eq!( assert_eq!(
parse(&MEMO_V1_PROGRAM_ID, &memo_instruction, &no_keys).unwrap(), parse(&MEMO_V1_PROGRAM_ID, &memo_instruction, &no_keys, None).unwrap(),
ParsedInstruction { ParsedInstruction {
program: "spl-memo".to_string(), program: "spl-memo".to_string(),
program_id: MEMO_V1_PROGRAM_ID.to_string(), program_id: MEMO_V1_PROGRAM_ID.to_string(),
parsed: json!("🦖"), parsed: json!("🦖"),
stack_height: None,
} }
); );
assert_eq!( 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 { ParsedInstruction {
program: "spl-memo".to_string(), program: "spl-memo".to_string(),
program_id: MEMO_V3_PROGRAM_ID.to_string(), program_id: MEMO_V3_PROGRAM_ID.to_string(),
parsed: json!("🦖"), parsed: json!("🦖"),
stack_height: Some(1),
} }
); );
let non_parsable_program_id = Pubkey::new(&[1; 32]); 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] #[test]