solana_tx_decoding/tx/
analyze_tx.rs

1use crate::tx::top_level_instructions_loop::top_level_instructions_loop;
2use crate::types::tx_format::TxFormat;
3use bumpalo::Bump;
4use solana_central::Instruction;
5use solana_central::SwapTx;
6use solana_central::TokenCreation;
7use solana_sdk::pubkey::Pubkey;
8use solana_sdk::signature::Signature;
9use solana_transaction_status_client_types::UiInstruction;
10use std::collections::HashMap;
11use std::collections::HashSet;
12use tokio::sync::broadcast::Sender;
13
14/// Analyze raw Solana transactions and extract swaps and token creations. This is the main entry
15/// point for transaction decoding. It accepts transactions from multiple sources (Archive, gRPC,
16/// JSON RPC) using the `TxFormat` enum and normalizes them into a common format before processing,
17/// writing stadardized output to channels `swap_tx_sender` and `token_create_sender`. Failed
18/// transactions are skipped and not analyzed. TODO support can be added for add/remove liquidity
19/// and bubblemapping with links.
20pub fn analyze_tx(
21  tx: &TxFormat,
22  swap_tx_sender: &Sender<SwapTx>,
23  token_create_sender: &Sender<TokenCreation>,
24  block_time: u64,
25  slot: u64,
26  index: u64,
27) {
28  let mut account_keys;
29  let mut top_level_instructions: Vec<Instruction> = Vec::new();
30  // Key is the top level instruction index, value is the list of inner instructions yielded by that top level instruction
31  let mut inner_instructions: HashMap<u8, Vec<Instruction>> = HashMap::new();
32  // Maps a token account address index to the token address (mint) that that token account is for
33  let mut ta_mint: HashMap<u8, Pubkey> = HashMap::new();
34  // Maps a token account address index to the token balance of that token account
35  let mut running_token_balances: HashMap<u8, u64> = HashMap::new();
36  let num_required_signatures;
37  let signature;
38  let arena;
39
40  match tx {
41    // Standardize data from rpc txs
42    TxFormat::Archive(tx) => {
43      // Do not analyze failed txs
44      if tx.meta.err.is_some() {
45        return;
46      }
47      let account_keys_length = tx.tx.message.static_account_keys().len()
48        + tx.meta.loaded_writable_addresses.len()
49        + tx.meta.loaded_readonly_addresses.len();
50      account_keys = Vec::with_capacity(account_keys_length);
51      account_keys.extend_from_slice(tx.tx.message.static_account_keys());
52
53      // Add writable and readable addresses loaded from lookup tables for v0 txs if they exist
54      for bytes in &tx.meta.loaded_writable_addresses {
55        account_keys.push(
56          bytes
57            .as_slice()
58            .try_into()
59            .map(Pubkey::new_from_array)
60            .unwrap(),
61        );
62      }
63      // Add loaded readonly addresses
64      for bytes in &tx.meta.loaded_readonly_addresses {
65        account_keys.push(
66          bytes
67            .as_slice()
68            .try_into()
69            .map(Pubkey::new_from_array)
70            .unwrap(),
71        );
72      }
73      let mut atomic_instruction_index = 0;
74      for raw_inst in tx.tx.message.instructions() {
75        let inst = Instruction {
76          tx_account_keys: &account_keys,
77          accounts: &raw_inst.accounts,
78          data: &raw_inst.data,
79          program_id_index: raw_inst.program_id_index,
80        };
81        top_level_instructions.push(inst);
82        atomic_instruction_index += 1;
83      }
84
85      for inner_inst_set in &tx.meta.inner_instructions {
86        for inner_inst_raw in &inner_inst_set.instructions {
87          let inner_inst = Instruction {
88            tx_account_keys: &account_keys,
89            accounts: &inner_inst_raw.accounts,
90            data: &inner_inst_raw.data,
91            program_id_index: inner_inst_raw.program_id_index as u8,
92          };
93          inner_instructions
94            .entry(inner_inst_set.index as u8)
95            .or_insert(Vec::new())
96            .push(inner_inst);
97          atomic_instruction_index += 1;
98        }
99      }
100
101      for pre_token_balance in &tx.meta.pre_token_balances {
102        ta_mint.insert(
103          pre_token_balance.account_index as u8,
104          Pubkey::from_str_const(&pre_token_balance.mint),
105        );
106        running_token_balances.insert(
107          pre_token_balance.account_index as u8,
108          pre_token_balance
109            .ui_token_amount
110            .as_ref()
111            .unwrap()
112            .amount
113            .parse::<u64>()
114            .unwrap(),
115        );
116      }
117      for post_token_balance in &tx.meta.post_token_balances {
118        ta_mint.insert(
119          post_token_balance.account_index as u8,
120          Pubkey::from_str_const(&post_token_balance.mint),
121        );
122      }
123      num_required_signatures = tx.tx.message.header().num_required_signatures;
124      signature = Signature::from(tx.tx.signatures[0]);
125    }
126
127    // Standardize data from grpc txs
128    TxFormat::Grpc(tx) => {
129      // Do not analyze failed txs
130      if tx.meta.err.is_some() {
131        return;
132      }
133      let message = tx.tx.message.as_ref().unwrap();
134      let account_keys_length = message.account_keys.len()
135        + tx.meta.loaded_writable_addresses.len()
136        + tx.meta.loaded_readonly_addresses.len();
137      account_keys = Vec::with_capacity(account_keys_length);
138      for bytes in &message.account_keys {
139        account_keys.push(
140          bytes
141            .as_slice()
142            .try_into()
143            .map(Pubkey::new_from_array)
144            .unwrap(),
145        );
146      }
147
148      // Add writable and readable addresses loaded from lookup tables for v0 txs if they exist
149      for bytes in &tx.meta.loaded_writable_addresses {
150        account_keys.push(
151          bytes
152            .as_slice()
153            .try_into()
154            .map(Pubkey::new_from_array)
155            .unwrap(),
156        );
157      }
158      // Add loaded readonly addresses
159      for bytes in &tx.meta.loaded_readonly_addresses {
160        account_keys.push(
161          bytes
162            .as_slice()
163            .try_into()
164            .map(Pubkey::new_from_array)
165            .unwrap(),
166        );
167      }
168      let mut atomic_instruction_index = 0;
169      for raw_inst in &message.instructions {
170        let inst = Instruction {
171          tx_account_keys: &account_keys,
172          accounts: &raw_inst.accounts,
173          data: &raw_inst.data,
174          program_id_index: raw_inst.program_id_index as u8,
175        };
176        top_level_instructions.push(inst);
177        atomic_instruction_index += 1;
178      }
179
180      for inner_inst_set in &tx.meta.inner_instructions {
181        for inner_inst_raw in &inner_inst_set.instructions {
182          let inner_inst = Instruction {
183            tx_account_keys: &account_keys,
184            accounts: &inner_inst_raw.accounts,
185            data: &inner_inst_raw.data,
186            program_id_index: inner_inst_raw.program_id_index as u8,
187          };
188          inner_instructions
189            .entry(inner_inst_set.index as u8)
190            .or_insert(Vec::new())
191            .push(inner_inst);
192          atomic_instruction_index += 1;
193        }
194      }
195
196      for pre_token_balance in &tx.meta.pre_token_balances {
197        ta_mint.insert(
198          pre_token_balance.account_index as u8,
199          Pubkey::from_str_const(&pre_token_balance.mint),
200        );
201        running_token_balances.insert(
202          pre_token_balance.account_index as u8,
203          pre_token_balance
204            .ui_token_amount
205            .as_ref()
206            .unwrap()
207            .amount
208            .parse::<u64>()
209            .unwrap(),
210        );
211      }
212      for post_token_balance in &tx.meta.post_token_balances {
213        ta_mint.insert(
214          post_token_balance.account_index as u8,
215          Pubkey::from_str_const(&post_token_balance.mint),
216        );
217      }
218      num_required_signatures = message.header.unwrap().num_required_signatures as u8;
219      signature = Signature::from(
220        <[u8; 64]>::try_from(tx.tx.signatures[0].as_slice())
221          .expect("analyze_tx: Signature should be 64 bytes"),
222      );
223    }
224
225    TxFormat::JsonRpc(tx) => {
226      // Do not analyze failed txs
227      if tx.meta.err.is_some() {
228        // println!("Failed tx: {:?}", tx.meta.err.as_ref().unwrap());
229        return;
230      }
231      arena = Bump::new();
232
233      let loaded_addresses = tx.meta.loaded_addresses.as_ref().unwrap();
234
235      let account_keys_length = tx.tx.message.static_account_keys().len()
236        + loaded_addresses.writable.len()
237        + loaded_addresses.readonly.len();
238      account_keys = Vec::with_capacity(account_keys_length);
239      account_keys.extend_from_slice(tx.tx.message.static_account_keys());
240
241      // Add writable and readable addresses loaded from lookup tables for v0 txs if they exist
242      for base58_string in &loaded_addresses.writable {
243        account_keys.push(Pubkey::from_str_const(base58_string));
244      }
245      // Add loaded readonly addresses
246      for base58_string in &loaded_addresses.readonly {
247        account_keys.push(Pubkey::from_str_const(base58_string));
248      }
249
250      let mut atomic_instruction_index = 0;
251      for raw_inst in tx.tx.message.instructions() {
252        let inst = Instruction {
253          tx_account_keys: &account_keys,
254          accounts: &raw_inst.accounts,
255          data: &raw_inst.data,
256          program_id_index: raw_inst.program_id_index,
257        };
258        top_level_instructions.push(inst);
259        atomic_instruction_index += 1;
260      }
261
262      for inner_inst_set in tx.meta.inner_instructions.as_ref().unwrap() {
263        for inner_inst_raw in &inner_inst_set.instructions {
264          let inner_inst;
265          match inner_inst_raw {
266            UiInstruction::Compiled(inner_inst_raw) => {
267              // inner instruction raw data is a base58 encoded string
268              let data = bs58::decode(&inner_inst_raw.data).into_vec().unwrap();
269              let data = arena.alloc_slice_copy(&data);
270              inner_inst = Instruction {
271                tx_account_keys: &account_keys,
272                accounts: &inner_inst_raw.accounts,
273                data,
274                program_id_index: inner_inst_raw.program_id_index as u8,
275              };
276            }
277            UiInstruction::Parsed(_) => {
278              panic!("We should not be getting parsed instructions here");
279            }
280          }
281          inner_instructions
282            .entry(inner_inst_set.index as u8)
283            .or_insert(Vec::new())
284            .push(inner_inst);
285          atomic_instruction_index += 1;
286        }
287      }
288
289      let pre_token_balances = tx.meta.pre_token_balances.as_ref().unwrap();
290      for pre_token_balance in pre_token_balances {
291        ta_mint.insert(
292          pre_token_balance.account_index as u8,
293          Pubkey::from_str_const(&pre_token_balance.mint),
294        );
295        running_token_balances.insert(
296          pre_token_balance.account_index as u8,
297          pre_token_balance
298            .ui_token_amount
299            .amount
300            .parse::<u64>()
301            .unwrap(),
302        );
303      }
304      for post_token_balance in tx.meta.post_token_balances.as_ref().unwrap() {
305        ta_mint.insert(
306          post_token_balance.account_index as u8,
307          Pubkey::from_str_const(&post_token_balance.mint),
308        );
309      }
310      num_required_signatures = tx.tx.message.header().num_required_signatures;
311      signature = Signature::from(tx.tx.signatures[0]);
312    }
313  }
314
315  let mut signers = HashSet::new();
316  for i in 0..num_required_signatures {
317    signers.insert(account_keys[i as usize]);
318  }
319
320  top_level_instructions_loop(
321    &top_level_instructions,
322    &inner_instructions,
323    &account_keys,
324    &ta_mint,
325    &mut running_token_balances,
326    swap_tx_sender,
327    token_create_sender,
328    block_time,
329    slot,
330    index,
331    &signers,
332    &signature,
333  );
334}