1use crate::constants::SOLANA_MAX_TX_SIZE;
2use solana_instruction::Instruction;
3use solana_pubkey::Pubkey;
4use solana_sdk::address_lookup_table::AddressLookupTableAccount;
5use solana_sdk::transaction::VersionedTransaction;
6use std::collections::HashSet;
7
8#[derive(Debug)]
9pub struct TransactionSizeInfo {
10 pub size: usize,
11 pub max_size: usize,
12 pub is_oversized: bool,
13}
14
15#[derive(Debug)]
16pub struct AccountsNotInLuts {
17 pub accounts: Vec<Pubkey>,
18 pub total_accounts: usize,
19 pub accounts_in_luts: usize,
20}
21
22pub struct TransactionAnalysis;
23
24impl TransactionAnalysis {
25 pub fn analyze_transaction_size(tx: &VersionedTransaction) -> TransactionSizeInfo {
26 let size = bincode::serialize(tx).map_or(0, |s| s.len());
27 TransactionSizeInfo {
28 size,
29 max_size: SOLANA_MAX_TX_SIZE,
30 is_oversized: size > SOLANA_MAX_TX_SIZE,
31 }
32 }
33
34 pub fn get_accounts_not_in_luts(
35 instructions: &[Instruction],
36 lookup_tables: &[AddressLookupTableAccount],
37 ) -> AccountsNotInLuts {
38 let all_accounts: HashSet<Pubkey> = instructions
39 .iter()
40 .flat_map(|ix| {
41 std::iter::once(ix.program_id).chain(ix.accounts.iter().map(|m| m.pubkey))
42 })
43 .collect();
44
45 let lut_accounts: HashSet<Pubkey> = lookup_tables
46 .iter()
47 .flat_map(|lut| lut.addresses.iter().copied())
48 .collect();
49
50 let accounts: Vec<Pubkey> = all_accounts.difference(&lut_accounts).copied().collect();
51 let total_accounts = all_accounts.len();
52 let accounts_in_luts = total_accounts.saturating_sub(accounts.len());
53
54 AccountsNotInLuts {
55 accounts,
56 total_accounts,
57 accounts_in_luts,
58 }
59 }
60
61 pub fn log_transaction_size_warning(tx: &VersionedTransaction, tx_index: usize) {
62 let size_info = Self::analyze_transaction_size(tx);
63 if size_info.is_oversized {
64 tracing::error!(
65 "transaction {tx_index} is OVERSIZED: {size}/{max} bytes (exceeds by {excess} bytes)",
66 size = size_info.size,
67 max = size_info.max_size,
68 excess = size_info.size - size_info.max_size
69 );
70 } else {
71 tracing::info!(
72 "transaction {tx_index} size: {size}/{max} bytes ({remaining} bytes remaining)",
73 size = size_info.size,
74 max = size_info.max_size,
75 remaining = size_info.max_size - size_info.size
76 );
77 }
78 }
79
80 pub fn log_accounts_not_in_luts(
81 instructions: &[Instruction],
82 lookup_tables: &[AddressLookupTableAccount],
83 context: &str,
84 ) {
85 let analysis = Self::get_accounts_not_in_luts(instructions, lookup_tables);
86
87 if analysis.accounts.is_empty() {
88 tracing::info!(
89 "[{context}] all {total} accounts are in LUTs",
90 total = analysis.total_accounts
91 );
92 return;
93 }
94
95 tracing::warn!(
96 "[{context}] {missing_count}/{total} accounts NOT in LUTs ({in_luts} in LUTs)",
97 missing_count = analysis.accounts.len(),
98 total = analysis.total_accounts,
99 in_luts = analysis.accounts_in_luts
100 );
101
102 for (i, account) in analysis.accounts.iter().enumerate() {
103 tracing::warn!("[{context}] missing LUT account [{i}]: {account}");
104 }
105 }
106
107 pub fn log_bundle_failure_analysis(
108 transactions: &[VersionedTransaction],
109 all_instructions: &[Option<Vec<Instruction>>],
110 lookup_tables: &[AddressLookupTableAccount],
111 error: &str,
112 ) {
113 tracing::error!("=== BUNDLE FAILURE ANALYSIS ===");
114 tracing::error!("error: {error}");
115
116 for (i, tx) in transactions.iter().enumerate() {
117 tracing::error!("--- transaction {i} ---");
118 Self::log_transaction_size_warning(tx, i);
119
120 if let Some(Some(ixs)) = all_instructions.get(i) {
121 Self::log_accounts_not_in_luts(ixs, lookup_tables, &format!("TX{i}"));
122 }
123 }
124
125 tracing::error!("=== END ANALYSIS ===");
126 }
127}
128
129#[cfg(test)]
130mod tests {
131 use super::*;
132 use solana_instruction::AccountMeta;
133 use solana_pubkey::Pubkey;
134
135 #[test]
136 fn log_bundle_failure_analysis_handles_option_slots() {
137 let ix = Instruction {
138 program_id: Pubkey::new_unique(),
139 accounts: vec![AccountMeta::new(Pubkey::new_unique(), true)],
140 data: vec![1, 2, 3],
141 };
142 let slots: [Option<Vec<Instruction>>; 5] = [Some(vec![ix]), None, None, None, None];
143 TransactionAnalysis::log_bundle_failure_analysis(&[], &slots, &[], "test error");
144 }
145}