1use solana_sdk::{
4 inner_instruction::InnerInstruction, instruction::AccountMeta, pubkey::Pubkey,
5 signature::Signature, system_program,
6};
7
8use super::config::EnhancedLoggingConfig;
9
10#[derive(Debug, Clone)]
12pub struct EnhancedTransactionLog {
13 pub signature: Signature,
14 pub slot: u64,
15 pub status: TransactionStatus,
16 pub fee: u64,
17 pub compute_used: u64,
18 pub compute_total: u64,
19 pub instructions: Vec<EnhancedInstructionLog>,
20 pub account_changes: Vec<AccountChange>,
21 pub program_logs_pretty: String,
22 pub light_events: Vec<LightProtocolEvent>,
23}
24
25#[derive(Debug, Clone)]
27pub enum TransactionStatus {
28 Success,
29 Failed(String),
30 Unknown,
31}
32
33impl TransactionStatus {
34 pub fn text(&self) -> String {
35 match self {
36 TransactionStatus::Success => "Success".to_string(),
37 TransactionStatus::Failed(err) => format!("Failed: {}", err),
38 TransactionStatus::Unknown => "Unknown".to_string(),
39 }
40 }
41}
42
43#[derive(Debug, Clone)]
45pub struct EnhancedInstructionLog {
46 pub index: usize,
47 pub program_id: Pubkey,
48 pub program_name: String,
49 pub instruction_name: Option<String>,
50 pub accounts: Vec<AccountMeta>,
51 pub data: Vec<u8>,
52 pub parsed_data: Option<ParsedInstructionData>,
53 pub inner_instructions: Vec<EnhancedInstructionLog>,
54 pub compute_consumed: Option<u64>,
55 pub success: bool,
56 pub depth: usize,
57}
58
59#[derive(Debug, Clone)]
61pub enum ParsedInstructionData {
62 LightSystemProgram {
63 instruction_type: String,
64 compressed_accounts: Option<CompressedAccountSummary>,
65 proof_info: Option<ProofSummary>,
66 address_params: Option<Vec<AddressParam>>,
67 fee_info: Option<FeeSummary>,
68 input_account_data: Option<Vec<InputAccountData>>,
69 output_account_data: Option<Vec<OutputAccountData>>,
70 },
71 ComputeBudget {
72 instruction_type: String,
73 value: Option<u64>,
74 },
75 System {
76 instruction_type: String,
77 lamports: Option<u64>,
78 space: Option<u64>,
79 new_account: Option<Pubkey>,
80 },
81 Unknown {
82 program_name: String,
83 data_preview: String,
84 },
85}
86
87#[derive(Debug, Clone)]
89pub struct CompressedAccountSummary {
90 pub input_accounts: usize,
91 pub output_accounts: usize,
92 pub lamports_change: Option<i64>,
93}
94
95#[derive(Debug, Clone)]
97pub struct ProofSummary {
98 pub proof_type: String,
99 pub has_validity_proof: bool,
100}
101
102#[derive(Debug, Clone)]
104pub struct FeeSummary {
105 pub relay_fee: Option<u64>,
106 pub compression_fee: Option<u64>,
107}
108
109#[derive(Debug, Clone)]
111pub enum AddressAssignment {
112 V1,
114 None,
116 AssignedIndex(u8),
118}
119
120#[derive(Debug, Clone)]
122pub struct AddressParam {
123 pub seed: [u8; 32],
124 pub address_queue_index: Option<u8>,
125 pub address_queue_pubkey: Option<solana_sdk::pubkey::Pubkey>,
126 pub merkle_tree_index: Option<u8>,
127 pub address_merkle_tree_pubkey: Option<solana_sdk::pubkey::Pubkey>,
128 pub root_index: Option<u16>,
129 pub derived_address: Option<[u8; 32]>,
130 pub assigned_account_index: AddressAssignment,
131}
132
133#[derive(Debug, Clone)]
135pub struct InputAccountData {
136 pub lamports: u64,
137 pub owner: Option<solana_sdk::pubkey::Pubkey>,
138 pub merkle_tree_index: Option<u8>,
139 pub merkle_tree_pubkey: Option<solana_sdk::pubkey::Pubkey>,
140 pub queue_index: Option<u8>,
141 pub queue_pubkey: Option<solana_sdk::pubkey::Pubkey>,
142 pub address: Option<[u8; 32]>,
143 pub data_hash: Vec<u8>,
144 pub discriminator: Vec<u8>,
145 pub leaf_index: Option<u32>,
146 pub root_index: Option<u16>,
147}
148
149#[derive(Debug, Clone)]
151pub struct OutputAccountData {
152 pub lamports: u64,
153 pub data: Option<Vec<u8>>,
154 pub owner: Option<solana_sdk::pubkey::Pubkey>,
155 pub merkle_tree_index: Option<u8>,
156 pub merkle_tree_pubkey: Option<solana_sdk::pubkey::Pubkey>,
157 pub queue_index: Option<u8>,
158 pub queue_pubkey: Option<solana_sdk::pubkey::Pubkey>,
159 pub address: Option<[u8; 32]>,
160 pub data_hash: Vec<u8>,
161 pub discriminator: Vec<u8>,
162}
163
164#[derive(Debug, Clone)]
166pub struct AccountChange {
167 pub pubkey: Pubkey,
168 pub account_type: String,
169 pub access: AccountAccess,
170 pub account_index: usize,
171 pub lamports_before: u64,
172 pub lamports_after: u64,
173 pub data_len_before: usize,
174 pub data_len_after: usize,
175 pub owner: Pubkey,
176 pub executable: bool,
177 pub rent_epoch: u64,
178}
179
180#[derive(Debug, Clone, Copy, PartialEq, Eq)]
182pub enum AccountAccess {
183 Readonly,
184 Writable,
185 Signer,
186 SignerWritable,
187}
188
189impl AccountAccess {
190 pub fn symbol(&self, index: usize) -> String {
191 format!("#{}", index)
192 }
193
194 pub fn text(&self) -> &'static str {
195 match self {
196 AccountAccess::Readonly => "readonly",
197 AccountAccess::Writable => "writable",
198 AccountAccess::Signer => "signer",
199 AccountAccess::SignerWritable => "signer+writable",
200 }
201 }
202}
203
204#[derive(Debug, Clone)]
206pub struct LightProtocolEvent {
207 pub event_type: String,
208 pub compressed_accounts: Vec<CompressedAccountInfo>,
209 pub merkle_tree_changes: Vec<MerkleTreeChange>,
210 pub nullifiers: Vec<String>,
211}
212
213#[derive(Debug, Clone)]
215pub struct CompressedAccountInfo {
216 pub hash: String,
217 pub owner: Pubkey,
218 pub lamports: u64,
219 pub data: Option<Vec<u8>>,
220 pub address: Option<String>,
221}
222
223#[derive(Debug, Clone)]
225pub struct MerkleTreeChange {
226 pub tree_pubkey: Pubkey,
227 pub tree_type: String,
228 pub sequence_number: u64,
229 pub leaf_index: u64,
230}
231
232impl EnhancedTransactionLog {
233 fn get_pretty_logs_string(result: &litesvm::types::TransactionResult) -> String {
235 match result {
236 Ok(meta) => meta.pretty_logs(),
237 Err(failed) => failed.meta.pretty_logs(),
238 }
239 }
240
241 pub fn from_transaction_result(
243 transaction: &solana_sdk::transaction::Transaction,
244 result: &litesvm::types::TransactionResult,
245 signature: &Signature,
246 slot: u64,
247 config: &EnhancedLoggingConfig,
248 ) -> Self {
249 let (status, compute_consumed) = match result {
250 Ok(meta) => (TransactionStatus::Success, meta.compute_units_consumed),
251 Err(failed) => (
252 TransactionStatus::Failed(format!("{:?}", failed.err)),
253 failed.meta.compute_units_consumed,
254 ),
255 };
256
257 let estimated_fee = (transaction.signatures.len() as u64) * 5000;
260
261 let instructions: Vec<EnhancedInstructionLog> = transaction
263 .message
264 .instructions
265 .iter()
266 .enumerate()
267 .map(|(index, ix)| EnhancedInstructionLog {
268 index,
269 program_id: transaction.message.account_keys[ix.program_id_index as usize],
270 program_name: get_program_name(
271 &transaction.message.account_keys[ix.program_id_index as usize],
272 ),
273 instruction_name: None, accounts: ix
275 .accounts
276 .iter()
277 .map(|&idx| AccountMeta {
278 pubkey: transaction.message.account_keys[idx as usize],
279 is_signer: transaction.message.is_signer(idx as usize),
280 is_writable: transaction.message.is_maybe_writable(idx as usize, None),
281 })
282 .collect(),
283 data: ix.data.clone(),
284 parsed_data: None, inner_instructions: Vec::new(), compute_consumed: None,
287 success: true,
288 depth: 0,
289 })
290 .collect();
291
292 let inner_instructions_list = match result {
294 Ok(meta) => &meta.inner_instructions,
295 Err(failed) => &failed.meta.inner_instructions,
296 };
297
298 let mut instructions = instructions;
300 if config.decode_light_instructions {
301 for instruction in instructions.iter_mut() {
303 instruction.parsed_data = super::decoder::decode_instruction(
304 &instruction.program_id,
305 &instruction.data,
306 &instruction.accounts,
307 );
308 if let Some(ref parsed) = instruction.parsed_data {
309 instruction.instruction_name = match parsed {
310 ParsedInstructionData::LightSystemProgram {
311 instruction_type, ..
312 } => Some(instruction_type.clone()),
313 ParsedInstructionData::ComputeBudget {
314 instruction_type, ..
315 } => Some(instruction_type.clone()),
316 ParsedInstructionData::System {
317 instruction_type, ..
318 } => Some(instruction_type.clone()),
319 _ => None,
320 };
321 }
322 }
323
324 for (instruction_index, inner_list) in inner_instructions_list.iter().enumerate() {
326 if let Some(instruction) = instructions.get_mut(instruction_index) {
327 instruction.inner_instructions = Self::parse_inner_instructions(
328 inner_list, &transaction.message.account_keys,
330 &transaction.message, 1, config,
333 );
334 }
335 }
336 }
337
338 let pretty_logs_string = Self::get_pretty_logs_string(result);
340
341 Self {
342 signature: *signature,
343 slot,
344 status,
345 fee: estimated_fee,
346 compute_used: compute_consumed,
347 compute_total: 1_400_000, instructions,
349 account_changes: Vec::new(), program_logs_pretty: pretty_logs_string,
351 light_events: Vec::new(),
352 }
353 }
354
355 fn parse_inner_instructions(
357 inner_instructions: &[InnerInstruction],
358 account_keys: &[Pubkey],
359 message: &solana_sdk::message::Message,
360 base_depth: usize,
361 config: &EnhancedLoggingConfig,
362 ) -> Vec<EnhancedInstructionLog> {
363 let mut result = Vec::new();
364
365 for (index, inner_ix) in inner_instructions.iter().enumerate() {
366 let program_id = account_keys[inner_ix.instruction.program_id_index as usize];
367 let program_name = get_program_name(&program_id);
368
369 let accounts: Vec<AccountMeta> = inner_ix
370 .instruction
371 .accounts
372 .iter()
373 .map(|&idx| {
374 let account_index = idx as usize;
375 let pubkey = account_keys[account_index];
376
377 let is_signer = message.is_signer(account_index);
379 let is_writable = message.is_maybe_writable(account_index, None);
380
381 AccountMeta {
382 pubkey,
383 is_signer,
384 is_writable,
385 }
386 })
387 .collect();
388
389 let parsed_data = if config.decode_light_instructions {
390 super::decoder::decode_instruction(
391 &program_id,
392 &inner_ix.instruction.data,
393 &accounts,
394 )
395 } else {
396 None
397 };
398
399 let instruction_name = parsed_data.as_ref().and_then(|parsed| match parsed {
400 ParsedInstructionData::LightSystemProgram {
401 instruction_type, ..
402 } => Some(instruction_type.clone()),
403 ParsedInstructionData::ComputeBudget {
404 instruction_type, ..
405 } => Some(instruction_type.clone()),
406 ParsedInstructionData::System {
407 instruction_type, ..
408 } => Some(instruction_type.clone()),
409 _ => None,
410 });
411
412 let instruction_depth = base_depth + (inner_ix.stack_height as usize).saturating_sub(1);
416
417 let instruction_log = EnhancedInstructionLog {
418 index,
419 program_id,
420 program_name,
421 instruction_name,
422 accounts,
423 data: inner_ix.instruction.data.clone(),
424 parsed_data,
425 inner_instructions: Vec::new(),
426 compute_consumed: None,
427 success: true, depth: instruction_depth,
429 };
430
431 if inner_ix.stack_height <= 2 {
435 result.push(instruction_log);
437 } else {
438 let target_parent_depth = instruction_depth - 1;
441 if let Some(parent) =
442 Self::find_parent_for_instruction(&mut result, target_parent_depth)
443 {
444 parent.inner_instructions.push(instruction_log);
445 } else {
446 result.push(instruction_log);
448 }
449 }
450 }
451
452 result
453 }
454
455 fn find_parent_for_instruction(
457 instructions: &mut [EnhancedInstructionLog],
458 target_depth: usize,
459 ) -> Option<&mut EnhancedInstructionLog> {
460 for instruction in instructions.iter_mut().rev() {
461 if instruction.depth == target_depth {
462 return Some(instruction);
463 }
464 if let Some(parent) =
466 Self::find_parent_for_instruction(&mut instruction.inner_instructions, target_depth)
467 {
468 return Some(parent);
469 }
470 }
471 None
472 }
473}
474fn get_program_name(program_id: &Pubkey) -> String {
476 match program_id.to_string().as_str() {
477 id if id == system_program::ID.to_string() => "System Program".to_string(),
478 "ComputeBudget111111111111111111111111111111" => "Compute Budget".to_string(),
479 "SySTEM1eSU2p4BGQfQpimFEWWSC1XDFeun3Nqzz3rT7" => "Light System Program".to_string(),
480 "compr6CUsB5m2jS4Y3831ztGSTnDpnKJTKS95d64XVq" => "Account Compression".to_string(),
481 "cTokenmWW8bLPjZEBAUgYy3zKxQZW6VKi7bqNFEVv3m" => "Compressed Token Program".to_string(),
482 _ => format!("Unknown Program ({})", program_id),
483 }
484}