1use {
24 crate::{
25 collection::InstructionDecoderCollection,
26 datasource::TransactionUpdate,
27 error::{CarbonResult, Error},
28 instruction::{DecodedInstruction, InstructionMetadata},
29 schema::ParsedInstruction,
30 transaction::TransactionMetadata,
31 },
32 solana_sdk::{
33 instruction::{AccountMeta, CompiledInstruction},
34 message::{
35 v0::{LoadedAddresses, LoadedMessage},
36 VersionedMessage,
37 },
38 pubkey::Pubkey,
39 reserved_account_keys::ReservedAccountKeys,
40 transaction_context::TransactionReturnData,
41 },
42 solana_transaction_status::{
43 option_serializer::OptionSerializer, InnerInstruction, InnerInstructions, Reward,
44 TransactionStatusMeta, TransactionTokenBalance, UiInstruction, UiLoadedAddresses,
45 UiTransactionStatusMeta,
46 },
47 std::{collections::HashSet, str::FromStr},
48};
49
50pub fn extract_instructions_with_metadata(
74 transaction_metadata: &TransactionMetadata,
75 transaction_update: &TransactionUpdate,
76) -> CarbonResult<Vec<(InstructionMetadata, solana_sdk::instruction::Instruction)>> {
77 log::trace!(
78 "extract_instructions_with_metadata(transaction_metadata: {:?}, transaction_update: {:?})",
79 transaction_metadata,
80 transaction_update
81 );
82 let message = transaction_update.transaction.message.clone();
83 let meta = transaction_update.meta.clone();
84
85 let mut instructions_with_metadata =
86 Vec::<(InstructionMetadata, solana_sdk::instruction::Instruction)>::new();
87
88 match message {
89 VersionedMessage::Legacy(legacy) => {
90 for (i, compiled_instruction) in legacy.instructions.iter().enumerate() {
91 let program_id = *legacy
92 .account_keys
93 .get(compiled_instruction.program_id_index as usize)
94 .unwrap_or(&Pubkey::default());
95
96 let accounts: Vec<_> = compiled_instruction
97 .accounts
98 .iter()
99 .filter_map(|account_index| {
100 let account_pubkey = legacy.account_keys.get(*account_index as usize)?;
101 Some(AccountMeta {
102 pubkey: *account_pubkey,
103 is_writable: legacy.is_maybe_writable(*account_index as usize, None),
104 is_signer: legacy.is_signer(*account_index as usize),
105 })
106 })
107 .collect();
108
109 instructions_with_metadata.push((
110 InstructionMetadata {
111 transaction_metadata: transaction_metadata.clone(),
112 stack_height: 1,
113 },
114 solana_sdk::instruction::Instruction {
115 program_id,
116 accounts,
117 data: compiled_instruction.data.clone(),
118 },
119 ));
120
121 if let Some(inner_instructions) = &meta.inner_instructions {
122 for inner_instructions_per_tx in inner_instructions {
123 if inner_instructions_per_tx.index == i as u8 {
124 for inner_instruction in inner_instructions_per_tx.instructions.iter() {
125 let program_id = *legacy
126 .account_keys
127 .get(inner_instruction.instruction.program_id_index as usize)
128 .unwrap_or(&Pubkey::default());
129
130 let accounts: Vec<_> = inner_instruction
131 .instruction
132 .accounts
133 .iter()
134 .filter_map(|account_index| {
135 let account_pubkey =
136 legacy.account_keys.get(*account_index as usize)?;
137
138 Some(AccountMeta {
139 pubkey: *account_pubkey,
140 is_writable: legacy
141 .is_maybe_writable(*account_index as usize, None),
142 is_signer: legacy.is_signer(*account_index as usize),
143 })
144 })
145 .collect();
146
147 instructions_with_metadata.push((
148 InstructionMetadata {
149 transaction_metadata: transaction_metadata.clone(),
150 stack_height: inner_instruction.stack_height.unwrap_or(1),
151 },
152 solana_sdk::instruction::Instruction {
153 program_id,
154 accounts,
155 data: inner_instruction.instruction.data.clone(),
156 },
157 ));
158 }
159 }
160 }
161 }
162 }
163 }
164 VersionedMessage::V0(v0) => {
165 let loaded_addresses = LoadedAddresses {
166 writable: meta.loaded_addresses.writable.to_vec(),
167 readonly: meta.loaded_addresses.readonly.to_vec(),
168 };
169
170 let loaded_message = LoadedMessage::new(
171 v0.clone(),
172 loaded_addresses,
173 &ReservedAccountKeys::empty_key_set(),
174 );
175
176 for (i, compiled_instruction) in v0.instructions.iter().enumerate() {
177 let program_id = *loaded_message
178 .account_keys()
179 .get(compiled_instruction.program_id_index as usize)
180 .unwrap_or(&Pubkey::default());
181
182 let accounts: Vec<AccountMeta> = compiled_instruction
183 .accounts
184 .iter()
185 .map(|account_index| {
186 let account_pubkey =
187 loaded_message.account_keys().get(*account_index as usize);
188
189 AccountMeta {
190 pubkey: account_pubkey.copied().unwrap_or_default(),
191 is_writable: loaded_message.is_writable(*account_index as usize),
192 is_signer: loaded_message.is_signer(*account_index as usize),
193 }
194 })
195 .collect();
196
197 instructions_with_metadata.push((
198 InstructionMetadata {
199 transaction_metadata: transaction_metadata.clone(),
200 stack_height: 1,
201 },
202 solana_sdk::instruction::Instruction {
203 program_id,
204 accounts,
205 data: compiled_instruction.data.clone(),
206 },
207 ));
208
209 if let Some(inner_instructions) = &meta.inner_instructions {
210 for inner_instructions_per_tx in inner_instructions {
211 if inner_instructions_per_tx.index == i as u8 {
212 for inner_instruction in inner_instructions_per_tx.instructions.iter() {
213 let program_id = *loaded_message
214 .account_keys()
215 .get(inner_instruction.instruction.program_id_index as usize)
216 .unwrap_or(&Pubkey::default());
217
218 let accounts: Vec<AccountMeta> = inner_instruction
219 .instruction
220 .accounts
221 .iter()
222 .map(|account_index| {
223 let account_pubkey = loaded_message
224 .account_keys()
225 .get(*account_index as usize)
226 .copied()
227 .unwrap_or_default();
228
229 AccountMeta {
230 pubkey: account_pubkey,
231 is_writable: loaded_message
232 .is_writable(*account_index as usize),
233 is_signer: loaded_message
234 .is_signer(*account_index as usize),
235 }
236 })
237 .collect();
238
239 instructions_with_metadata.push((
240 InstructionMetadata {
241 transaction_metadata: transaction_metadata.clone(),
242 stack_height: inner_instruction.stack_height.unwrap_or(1),
243 },
244 solana_sdk::instruction::Instruction {
245 program_id,
246 accounts,
247 data: inner_instruction.instruction.data.clone(),
248 },
249 ));
250 }
251 }
252 }
253 }
254 }
255 }
256 }
257
258 Ok(instructions_with_metadata)
259}
260
261pub fn extract_account_metas(
283 compiled_instruction: &solana_sdk::instruction::CompiledInstruction,
284 message: &solana_sdk::message::VersionedMessage,
285) -> CarbonResult<Vec<solana_sdk::instruction::AccountMeta>> {
286 log::trace!(
287 "extract_account_metas(compiled_instruction: {:?}, message: {:?})",
288 compiled_instruction,
289 message
290 );
291 let mut accounts = Vec::<solana_sdk::instruction::AccountMeta>::with_capacity(
292 compiled_instruction.accounts.len(),
293 );
294
295 for account_index in compiled_instruction.accounts.iter() {
296 accounts.push(solana_sdk::instruction::AccountMeta {
297 pubkey: *message
298 .static_account_keys()
299 .get(*account_index as usize)
300 .ok_or(Error::MissingAccountInTransaction)?,
301 is_signer: message.is_signer(*account_index as usize),
302 is_writable: message.is_maybe_writable(
303 *account_index as usize,
304 Some(
305 &message
306 .static_account_keys()
307 .iter()
308 .copied()
309 .collect::<HashSet<_>>(),
310 ),
311 ),
312 });
313 }
314
315 Ok(accounts)
316}
317
318pub fn unnest_parsed_instructions<T: InstructionDecoderCollection>(
338 transaction_metadata: TransactionMetadata,
339 instructions: Vec<ParsedInstruction<T>>,
340 stack_height: u32,
341) -> Vec<(InstructionMetadata, DecodedInstruction<T>)> {
342 log::trace!(
343 "unnest_parsed_instructions(instructions: {:?})",
344 instructions
345 );
346
347 let mut result = Vec::new();
348
349 for parsed_instruction in instructions.into_iter() {
350 result.push((
351 InstructionMetadata {
352 transaction_metadata: transaction_metadata.clone(),
353 stack_height,
354 },
355 parsed_instruction.instruction,
356 ));
357 result.extend(unnest_parsed_instructions(
358 transaction_metadata.clone(),
359 parsed_instruction.inner_instructions,
360 stack_height + 1,
361 ));
362 }
363
364 result
365}
366
367pub fn transaction_metadata_from_original_meta(
388 meta_original: UiTransactionStatusMeta,
389) -> CarbonResult<TransactionStatusMeta> {
390 log::trace!(
391 "transaction_metadata_from_original_meta(meta_original: {:?})",
392 meta_original
393 );
394 Ok(TransactionStatusMeta {
395 status: meta_original.status,
396 fee: meta_original.fee,
397 pre_balances: meta_original.pre_balances,
398 post_balances: meta_original.post_balances,
399 inner_instructions: Some(
400 meta_original
401 .inner_instructions
402 .unwrap_or_else(std::vec::Vec::new)
403 .iter()
404 .map(|inner_instruction_group| InnerInstructions {
405 index: inner_instruction_group.index,
406 instructions: inner_instruction_group
407 .instructions
408 .iter()
409 .map(|ui_instruction| match ui_instruction {
410 UiInstruction::Compiled(compiled_ui_instruction) => {
411 let decoded_data =
412 bs58::decode(compiled_ui_instruction.data.clone())
413 .into_vec()
414 .unwrap_or_else(|_| vec![]);
415 InnerInstruction {
416 instruction: CompiledInstruction {
417 program_id_index: compiled_ui_instruction.program_id_index,
418 accounts: compiled_ui_instruction.accounts.clone(),
419 data: decoded_data,
420 },
421 stack_height: compiled_ui_instruction.stack_height,
422 }
423 }
424 _ => {
425 log::error!("Unsupported instruction type encountered");
426 InnerInstruction {
427 instruction: CompiledInstruction {
428 program_id_index: 0,
429 accounts: vec![],
430 data: vec![],
431 },
432 stack_height: None,
433 }
434 }
435 })
436 .collect::<Vec<InnerInstruction>>(),
437 })
438 .collect::<Vec<InnerInstructions>>(),
439 ),
440 log_messages: Some(
441 meta_original
442 .log_messages
443 .unwrap_or_else(std::vec::Vec::new),
444 ),
445 pre_token_balances: Some(
446 meta_original
447 .pre_token_balances
448 .unwrap_or_else(std::vec::Vec::new)
449 .iter()
450 .filter_map(|transaction_token_balance| {
451 if let (OptionSerializer::Some(owner), OptionSerializer::Some(program_id)) = (
452 transaction_token_balance.owner.as_ref(),
453 transaction_token_balance.program_id.as_ref(),
454 ) {
455 Some(TransactionTokenBalance {
456 account_index: transaction_token_balance.account_index,
457 mint: transaction_token_balance.mint.clone(),
458 ui_token_amount: transaction_token_balance.ui_token_amount.clone(),
459 owner: owner.to_string(),
460 program_id: program_id.to_string(),
461 })
462 } else {
463 None
464 }
465 })
466 .collect::<Vec<TransactionTokenBalance>>(),
467 ),
468 post_token_balances: Some(
469 meta_original
470 .post_token_balances
471 .unwrap_or_else(std::vec::Vec::new)
472 .iter()
473 .filter_map(|transaction_token_balance| {
474 if let (OptionSerializer::Some(owner), OptionSerializer::Some(program_id)) = (
475 transaction_token_balance.owner.as_ref(),
476 transaction_token_balance.program_id.as_ref(),
477 ) {
478 Some(TransactionTokenBalance {
479 account_index: transaction_token_balance.account_index,
480 mint: transaction_token_balance.mint.clone(),
481 ui_token_amount: transaction_token_balance.ui_token_amount.clone(),
482 owner: owner.to_string(),
483 program_id: program_id.to_string(),
484 })
485 } else {
486 None
487 }
488 })
489 .collect::<Vec<TransactionTokenBalance>>(),
490 ),
491 rewards: Some(
492 meta_original
493 .rewards
494 .unwrap_or_else(std::vec::Vec::new)
495 .iter()
496 .map(|rewards| Reward {
497 pubkey: rewards.pubkey.clone(),
498 lamports: rewards.lamports,
499 post_balance: rewards.post_balance,
500 reward_type: rewards.reward_type,
501 commission: rewards.commission,
502 })
503 .collect::<Vec<Reward>>(),
504 ),
505 loaded_addresses: {
506 let loaded = meta_original
507 .loaded_addresses
508 .unwrap_or_else(|| UiLoadedAddresses {
509 writable: vec![],
510 readonly: vec![],
511 });
512 LoadedAddresses {
513 writable: loaded
514 .writable
515 .iter()
516 .map(|w| Pubkey::from_str(w).unwrap_or_default())
517 .collect::<Vec<Pubkey>>(),
518 readonly: loaded
519 .readonly
520 .iter()
521 .map(|r| Pubkey::from_str(r).unwrap_or_default())
522 .collect::<Vec<Pubkey>>(),
523 }
524 },
525 return_data: meta_original
526 .return_data
527 .map(|return_data| TransactionReturnData {
528 program_id: return_data.program_id.parse().unwrap_or_default(),
529 data: return_data.data.0.as_bytes().to_vec(),
530 }),
531 compute_units_consumed: meta_original
532 .compute_units_consumed
533 .map(|compute_unit_consumed| compute_unit_consumed)
534 .or(None),
535 })
536}