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