1use itertools::Itertools;
2use serde::{Deserialize, Serialize};
3use solana_sdk::{
4 clock::UnixTimestamp, instruction::CompiledInstruction, message::VersionedMessage,
5};
6use solana_transaction_status::{
7 option_serializer::OptionSerializer, EncodedConfirmedTransactionWithStatusMeta,
8 EncodedTransaction, EncodedTransactionWithStatusMeta, UiCompiledInstruction, UiInstruction,
9 UiMessage, UiParsedInstruction, VersionedTransactionWithStatusMeta,
10};
11
12#[derive(Clone, Debug, Deserialize, Serialize)]
13pub struct ParsedTransaction {
14 pub slot: u64,
15 pub block_time: Option<UnixTimestamp>,
16 pub instructions: Vec<ParsedInstruction>,
17 pub inner_instructions: Vec<Vec<ParsedInnerInstruction>>,
18 pub logs: Vec<String>,
19 pub is_err: bool,
20 pub signature: String,
21 pub fee_payer: String,
22}
23
24#[derive(Clone, Debug, Serialize, Deserialize)]
25pub struct ParsedInstruction {
26 pub program_id: String,
27 pub accounts: Vec<String>,
28 pub data: Vec<u8>,
29 pub stack_height: Option<u32>,
30}
31
32#[derive(Clone, Debug, Serialize, Deserialize)]
33pub struct ParsedInnerInstruction {
34 pub parent_index: usize,
35 pub instruction: ParsedInstruction,
36}
37
38pub fn parse_ui_compiled_instruction(
39 c: &UiCompiledInstruction,
40 account_keys: &[String],
41) -> ParsedInstruction {
42 ParsedInstruction {
43 program_id: account_keys[c.program_id_index as usize].clone(),
44 accounts: c
45 .accounts
46 .iter()
47 .map(|i| account_keys[*i as usize].clone())
48 .collect(),
49 data: bs58::decode(c.data.clone()).into_vec().unwrap(),
50 stack_height: c.stack_height,
51 }
52}
53
54pub fn parse_ui_instruction(
55 ui_instruction: &UiInstruction,
56 account_keys: &[String],
57) -> ParsedInstruction {
58 match ui_instruction {
59 UiInstruction::Compiled(c) => parse_ui_compiled_instruction(c, account_keys),
60 UiInstruction::Parsed(p) => match p {
61 UiParsedInstruction::PartiallyDecoded(pd) => ParsedInstruction {
62 program_id: pd.program_id.clone(),
63 accounts: pd.accounts.clone(),
64 data: bs58::decode(&pd.data.clone()).into_vec().unwrap(),
65 stack_height: pd.stack_height,
66 },
67 _ => panic!("Unsupported instruction encoding"),
68 },
69 }
70}
71
72pub fn parse_compiled_instruction(
73 instruction: &CompiledInstruction,
74 account_keys: &[String],
75) -> ParsedInstruction {
76 ParsedInstruction {
77 program_id: account_keys[instruction.program_id_index as usize].clone(),
78 accounts: instruction
79 .accounts
80 .iter()
81 .map(|i| account_keys[*i as usize].clone())
82 .collect(),
83 data: instruction.data.clone(),
84 stack_height: Some(1),
85 }
86}
87
88pub fn parse_ui_message(
89 message: UiMessage,
90 loaded_addresses: &[String],
91) -> (Vec<String>, Vec<ParsedInstruction>) {
92 match message {
93 UiMessage::Parsed(p) => {
94 let mut keys = p
95 .account_keys
96 .iter()
97 .map(|k| k.pubkey.to_string())
98 .collect::<Vec<String>>();
99 keys.extend_from_slice(loaded_addresses);
100 (
101 keys.clone(),
102 p.instructions
103 .iter()
104 .map(|i| parse_ui_instruction(i, &keys))
105 .collect::<Vec<ParsedInstruction>>(),
106 )
107 }
108 UiMessage::Raw(r) => {
109 let mut keys = r.account_keys.clone();
110 keys.extend_from_slice(loaded_addresses);
111 (
112 keys.clone(),
113 r.instructions
114 .iter()
115 .map(|i| parse_ui_compiled_instruction(i, &keys))
116 .collect::<Vec<ParsedInstruction>>(),
117 )
118 }
119 }
120}
121
122pub fn parse_versioned_message(
123 message: VersionedMessage,
124 loaded_addresses: &[String],
125) -> (Vec<String>, Vec<ParsedInstruction>) {
126 let mut keys = message
127 .static_account_keys()
128 .into_iter()
129 .map(|pk| pk.to_string())
130 .collect_vec();
131 keys.extend_from_slice(loaded_addresses);
132 let instructions = message
133 .instructions()
134 .iter()
135 .map(|i| parse_compiled_instruction(i, &keys))
136 .collect_vec();
137 (keys, instructions)
138}
139
140pub fn parse_transaction(tx: EncodedConfirmedTransactionWithStatusMeta) -> ParsedTransaction {
141 let slot = tx.slot;
142 let block_time = tx.block_time;
143
144 let tx_meta = tx.transaction.meta.unwrap();
145 let loaded_addresses = match tx_meta.loaded_addresses {
146 OptionSerializer::Some(l) => [l.writable, l.readonly].concat(),
147 _ => vec![],
148 };
149
150 let (keys, instructions, signature) = match tx.transaction.transaction {
151 EncodedTransaction::Json(t) => {
152 let (keys, instructions) = parse_ui_message(t.message, &loaded_addresses);
153 let signature = t.signatures[0].to_string();
154 (keys, instructions, signature)
155 }
156 _ => {
157 let versioned_tx = tx
158 .transaction
159 .transaction
160 .decode()
161 .expect("Failed to decode transaction");
162 let (keys, instructions) =
163 parse_versioned_message(versioned_tx.message, &loaded_addresses);
164 (keys, instructions, versioned_tx.signatures[0].to_string())
165 }
166 };
167
168 let is_err = tx_meta.err.is_some();
169 let logs = match tx_meta.log_messages {
170 OptionSerializer::Some(l) => l,
171 _ => vec![],
172 };
173 let inner_instructions = match tx_meta.inner_instructions {
174 OptionSerializer::Some(inner) => inner
175 .iter()
176 .map(|ii| {
177 ii.instructions
178 .iter()
179 .map(|i| ParsedInnerInstruction {
180 parent_index: ii.index as usize,
181 instruction: parse_ui_instruction(i, &keys),
182 })
183 .collect::<Vec<ParsedInnerInstruction>>()
184 })
185 .collect::<Vec<Vec<ParsedInnerInstruction>>>(),
186 _ => vec![],
187 };
188 ParsedTransaction {
189 slot,
190 block_time,
191 instructions,
192 inner_instructions,
193 logs,
194 is_err,
195 signature,
196 fee_payer: keys[0].clone(),
197 }
198}
199
200pub fn parse_versioned_transaction(
201 slot: u64,
202 block_time: Option<i64>,
203 tx: VersionedTransactionWithStatusMeta,
204) -> Option<ParsedTransaction> {
205 let tx_meta = tx.meta;
206 let is_err = tx_meta.status.is_err();
207 if is_err {
208 return None;
209 }
210 let loaded_addresses = [
211 tx_meta.loaded_addresses.writable,
212 tx_meta.loaded_addresses.readonly,
213 ]
214 .concat()
215 .iter()
216 .map(|x| x.to_string())
217 .collect::<Vec<String>>();
218
219 let (keys, instructions) =
220 { parse_versioned_message(tx.transaction.message, loaded_addresses.as_slice()) };
221
222 let logs = tx_meta.log_messages.unwrap_or_default();
223 let inner_instructions = tx_meta
224 .inner_instructions
225 .unwrap_or_default()
226 .iter()
227 .map(|ii| {
228 ii.instructions
229 .iter()
230 .map(|i| ParsedInnerInstruction {
231 parent_index: ii.index as usize,
232 instruction: parse_compiled_instruction(&i.instruction, &keys),
233 })
234 .collect::<Vec<ParsedInnerInstruction>>()
235 })
236 .collect::<Vec<Vec<ParsedInnerInstruction>>>();
237 Some(ParsedTransaction {
238 slot,
239 signature: tx.transaction.signatures[0].to_string(),
240 block_time,
241 instructions,
242 inner_instructions,
243 logs,
244 is_err,
245 fee_payer: keys[0].clone(),
246 })
247}
248
249pub fn parse_encoded_transaction_with_status_meta(
250 slot: u64,
251 block_time: Option<i64>,
252 tx: EncodedTransactionWithStatusMeta,
253) -> Option<ParsedTransaction> {
254 let tx_meta = tx.meta?;
255 let loaded_addresses = match tx_meta.loaded_addresses {
256 OptionSerializer::Some(la) => [la.writable, la.readonly].concat(),
257 _ => vec![],
258 };
259
260 let versioned_tx = tx.transaction.decode()?;
261 let (keys, instructions) =
262 { parse_versioned_message(versioned_tx.message, loaded_addresses.as_slice()) };
263
264 let is_err = tx_meta.status.is_err();
265 let logs = match tx_meta.log_messages {
266 OptionSerializer::Some(lm) => lm,
267 _ => vec![],
268 };
269 let inner_instructions = match tx_meta.inner_instructions {
270 OptionSerializer::Some(inner_instructions) => inner_instructions
271 .iter()
272 .map(|ii| {
273 ii.instructions
274 .iter()
275 .map(|i| ParsedInnerInstruction {
276 parent_index: ii.index as usize,
277 instruction: parse_ui_instruction(i, &keys),
278 })
279 .collect::<Vec<ParsedInnerInstruction>>()
280 })
281 .collect::<Vec<Vec<ParsedInnerInstruction>>>(),
282 _ => vec![],
283 };
284 Some(ParsedTransaction {
285 slot,
286 signature: versioned_tx.signatures[0].to_string(),
287 block_time,
288 instructions,
289 inner_instructions,
290 logs,
291 is_err,
292 fee_payer: keys[0].clone(),
293 })
294}
295
296#[derive(Debug, Clone)]
297pub struct InstructionStack {
298 pub instruction: ParsedInstruction,
299 pub stack_height: u32,
300 pub instructions_in_stack: Vec<InstructionStack>,
301 pub flatten_index: usize,
302}
303
304impl InstructionStack {
305 pub fn from_parsed_transaction(tx: &ParsedTransaction) -> Vec<InstructionStack> {
306 let mut instruction_stacks = tx
308 .instructions
309 .iter()
310 .map(|instruction| InstructionStack {
311 instruction: instruction.clone(),
312 stack_height: 1,
314 instructions_in_stack: vec![],
315 flatten_index: 0, })
317 .collect::<Vec<_>>();
318
319 for ii in tx.inner_instructions.iter() {
321 if let Some(first_ii) = ii.first() {
322 let current_instruction_stack =
323 &mut instruction_stacks[first_ii.parent_index as usize];
324 parse_inner_instuction_stack(
325 current_instruction_stack,
326 2,
328 0,
329 &ii,
330 &mut 0, );
332 }
333 }
334
335 let mut current_index = 0;
337 for stack in instruction_stacks.iter_mut() {
338 assign_indices_dfs(stack, &mut current_index);
339 }
340
341 instruction_stacks
342 }
343}
344
345fn assign_indices_dfs(stack: &mut InstructionStack, current_index: &mut usize) {
346 stack.flatten_index = *current_index;
347 *current_index += 1;
348
349 for inner in stack.instructions_in_stack.iter_mut() {
350 assign_indices_dfs(inner, current_index);
351 }
352}
353
354fn parse_inner_instuction_stack(
355 current_stack: &mut InstructionStack,
356 current_stack_height: u32,
357 inner_inst_cursor: usize,
358 inner_instructions: &Vec<ParsedInnerInstruction>,
359 total_instructions: &mut usize,
360) -> usize {
361 if inner_inst_cursor >= inner_instructions.len() {
362 return inner_inst_cursor;
363 }
364
365 let current_instruction = &inner_instructions[inner_inst_cursor];
366 let stack_height = current_instruction
367 .instruction
368 .stack_height
369 .expect("Inner instruction must have stack height");
370
371 if stack_height < current_stack_height {
372 return inner_inst_cursor;
373 }
374
375 if stack_height == current_stack_height {
376 current_stack.instructions_in_stack.push(InstructionStack {
377 instruction: current_instruction.instruction.clone(),
378 stack_height: current_stack_height,
379 instructions_in_stack: vec![],
380 flatten_index: 0, });
382 *total_instructions += 1;
383
384 return parse_inner_instuction_stack(
386 current_stack,
387 current_stack_height,
388 inner_inst_cursor + 1,
389 inner_instructions,
390 total_instructions,
391 );
392 }
393
394 assert_eq!(
396 stack_height,
397 current_stack_height + 1,
398 "Stack height can only increase by 1"
399 );
400
401 let last_stack = current_stack
403 .instructions_in_stack
404 .last_mut()
405 .expect("Must have a parent instruction before going deeper");
406
407 let new_cursor = parse_inner_instuction_stack(
409 last_stack,
410 stack_height,
411 inner_inst_cursor,
412 inner_instructions,
413 total_instructions,
414 );
415
416 parse_inner_instuction_stack(
417 current_stack,
418 current_stack_height,
419 new_cursor,
420 inner_instructions,
421 total_instructions,
422 )
423}