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