light_client/interface/
tx_size.rs1use solana_instruction::Instruction;
4use solana_pubkey::Pubkey;
5
6pub const PACKET_DATA_SIZE: usize = 1232;
8
9#[derive(Debug, Clone, PartialEq, Eq)]
11pub struct InstructionTooLargeError {
12 pub instruction_index: usize,
14 pub estimated_size: usize,
16 pub max_size: usize,
18}
19
20impl std::fmt::Display for InstructionTooLargeError {
21 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
22 write!(
23 f,
24 "instruction at index {} exceeds max transaction size: {} > {}",
25 self.instruction_index, self.estimated_size, self.max_size
26 )
27 }
28}
29
30impl std::error::Error for InstructionTooLargeError {}
31
32pub fn split_by_tx_size(
44 instructions: Vec<Instruction>,
45 payer: &Pubkey,
46 max_size: Option<usize>,
47) -> Result<Vec<Vec<Instruction>>, InstructionTooLargeError> {
48 let max_size = max_size.unwrap_or(PACKET_DATA_SIZE);
49
50 if instructions.is_empty() {
51 return Ok(vec![]);
52 }
53
54 let mut batches = Vec::new();
55 let mut current_batch = Vec::new();
56
57 for (idx, ix) in instructions.into_iter().enumerate() {
58 let mut trial = current_batch.clone();
59 trial.push(ix.clone());
60
61 if estimate_tx_size(&trial, payer) > max_size {
62 let single_ix_size = estimate_tx_size(std::slice::from_ref(&ix), payer);
64 if single_ix_size > max_size {
65 return Err(InstructionTooLargeError {
66 instruction_index: idx,
67 estimated_size: single_ix_size,
68 max_size,
69 });
70 }
71
72 if !current_batch.is_empty() {
73 batches.push(current_batch);
74 }
75 current_batch = vec![ix];
76 } else {
77 current_batch.push(ix);
78 }
79 }
80
81 if !current_batch.is_empty() {
82 batches.push(current_batch);
83 }
84
85 Ok(batches)
86}
87
88fn count_signers(instructions: &[Instruction], payer: &Pubkey) -> usize {
90 let mut signers = vec![*payer];
91 for ix in instructions {
92 for meta in &ix.accounts {
93 if meta.is_signer && !signers.contains(&meta.pubkey) {
94 signers.push(meta.pubkey);
95 }
96 }
97 }
98 signers.len()
99}
100
101fn estimate_tx_size(instructions: &[Instruction], payer: &Pubkey) -> usize {
105 let num_signers = count_signers(instructions, payer);
106
107 let mut accounts = vec![*payer];
109 for ix in instructions {
110 if !accounts.contains(&ix.program_id) {
111 accounts.push(ix.program_id);
112 }
113 for meta in &ix.accounts {
114 if !accounts.contains(&meta.pubkey) {
115 accounts.push(meta.pubkey);
116 }
117 }
118 }
119
120 let mut size = 3;
122 size += compact_len(accounts.len()) + accounts.len() * 32;
124 size += 32;
126 size += compact_len(instructions.len());
128 for ix in instructions {
129 size += 1; size += compact_len(ix.accounts.len()) + ix.accounts.len();
131 size += compact_len(ix.data.len()) + ix.data.len();
132 }
133 size += compact_len(num_signers) + num_signers * 64;
135
136 size
137}
138
139#[inline]
140fn compact_len(val: usize) -> usize {
141 if val < 0x80 {
142 1
143 } else if val < 0x4000 {
144 2
145 } else {
146 3
147 }
148}
149
150#[cfg(test)]
151mod tests {
152 use solana_instruction::AccountMeta;
153
154 use super::*;
155
156 #[test]
157 fn test_split_by_tx_size() {
158 let payer = Pubkey::new_unique();
159 let instructions: Vec<Instruction> = (0..10)
160 .map(|_| Instruction {
161 program_id: Pubkey::new_unique(),
162 accounts: (0..10)
163 .map(|_| AccountMeta::new(Pubkey::new_unique(), false))
164 .collect(),
165 data: vec![0u8; 200],
166 })
167 .collect();
168
169 let batches = split_by_tx_size(instructions, &payer, None).unwrap();
170 assert!(batches.len() > 1);
171
172 for batch in &batches {
173 assert!(estimate_tx_size(batch, &payer) <= PACKET_DATA_SIZE);
174 }
175 }
176
177 #[test]
178 fn test_split_by_tx_size_oversized_instruction() {
179 let payer = Pubkey::new_unique();
180
181 let oversized_ix = Instruction {
183 program_id: Pubkey::new_unique(),
184 accounts: (0..5)
185 .map(|_| AccountMeta::new(Pubkey::new_unique(), false))
186 .collect(),
187 data: vec![0u8; 2000], };
189
190 let small_ix = Instruction {
191 program_id: Pubkey::new_unique(),
192 accounts: vec![AccountMeta::new(Pubkey::new_unique(), false)],
193 data: vec![0u8; 10],
194 };
195
196 let instructions = vec![small_ix.clone(), oversized_ix, small_ix];
198
199 let result = split_by_tx_size(instructions, &payer, None);
200 assert!(result.is_err());
201
202 let err = result.unwrap_err();
203 assert_eq!(err.instruction_index, 1);
204 assert!(err.estimated_size > err.max_size);
205 assert_eq!(err.max_size, PACKET_DATA_SIZE);
206 }
207
208 #[test]
209 fn test_signer_count_derived_from_metadata() {
210 let payer = Pubkey::new_unique();
211 let extra_signer = Pubkey::new_unique();
212
213 let ix_with_signer = Instruction {
215 program_id: Pubkey::new_unique(),
216 accounts: vec![
217 AccountMeta::new(Pubkey::new_unique(), false),
218 AccountMeta::new(extra_signer, true), ],
220 data: vec![0u8; 10],
221 };
222
223 let ix_no_signer = Instruction {
225 program_id: Pubkey::new_unique(),
226 accounts: vec![AccountMeta::new(Pubkey::new_unique(), false)],
227 data: vec![0u8; 10],
228 };
229
230 assert_eq!(
232 count_signers(std::slice::from_ref(&ix_no_signer), &payer),
233 1
234 );
235
236 assert_eq!(
238 count_signers(std::slice::from_ref(&ix_with_signer), &payer),
239 2
240 );
241
242 let ix_payer_signer = Instruction {
244 program_id: Pubkey::new_unique(),
245 accounts: vec![AccountMeta::new(payer, true)],
246 data: vec![0u8; 10],
247 };
248 assert_eq!(count_signers(&[ix_payer_signer], &payer), 1);
249 }
250}