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,
},
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},
};
@ -6838,7 +6840,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 +7575,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 +7695,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![]);

View File

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

View File

@ -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,
})

View File

@ -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: Some(u32::from(info.stack_height)),
})
.collect(),
})
.filter(|i| !i.instructions.is_empty())
.collect()

View File

@ -491,7 +491,15 @@ pub type TransactionBalances = Vec<Vec<u64>>;
/// An ordered list of compiled instructions that were invoked during a
/// 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 transaction
@ -511,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 = u8::try_from(stack_height).unwrap_or(u8::MAX);
let instruction = CompiledInstruction::new_from_raw_parts(
instruction_context
.get_index_of_program_account_in_transaction(
instruction_context
@ -532,7 +542,11 @@ pub fn inner_instructions_list_from_instruction_trace(
.unwrap_or_default() as u8
})
.collect(),
));
);
inner_instructions.push(InnerInstruction {
instruction,
stack_height,
});
} else {
debug_assert!(false);
}
@ -19269,12 +19283,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: 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: 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 {
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 {

View File

@ -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<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 {
type Error = &'static str;

View File

@ -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<u32>,
) -> 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<u8>,
pub data: String,
pub stack_height: Option<u32>,
}
impl From<&CompiledInstruction> for UiCompiledInstruction {
fn from(instruction: &CompiledInstruction) -> Self {
impl UiCompiledInstruction {
fn from(instruction: &CompiledInstruction, stack_height: Option<u32>) -> 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<String>,
pub data: String,
pub stack_height: Option<u32>,
}
impl UiPartiallyDecodedInstruction {
fn from(instruction: &CompiledInstruction, account_keys: &AccountKeys) -> Self {
fn from(
instruction: &CompiledInstruction,
account_keys: &AccountKeys,
stack_height: Option<u32>,
) -> 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<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)]
@ -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<InnerInstructions> 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(),
),

View File

@ -81,6 +81,7 @@ pub struct ParsedInstruction {
pub program: String,
pub program_id: String,
pub parsed: Value,
pub stack_height: Option<u32>,
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
@ -110,6 +111,7 @@ pub fn parse(
program_id: &Pubkey,
instruction: &CompiledInstruction,
account_keys: &AccountKeys,
stack_height: Option<u32>,
) -> Result<ParsedInstruction, ParseInstructionError> {
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]