1#![allow(dead_code)]
2
3use std::any::type_name;
4
5use base64::prelude::*;
6use jsonrpc_core::{Error, Result};
7use litesvm::types::TransactionMetadata;
8use solana_client::{
9 rpc_config::{RpcTokenAccountsFilter, RpcTransactionConfig},
10 rpc_filter::RpcFilterType,
11 rpc_request::{MAX_GET_CONFIRMED_SIGNATURES_FOR_ADDRESS2_LIMIT, TokenAccountsFilter},
12};
13use solana_commitment_config::CommitmentConfig;
14use solana_hash::Hash;
15use solana_message::{
16 AccountKeys, VersionedMessage,
17 v1::{MAX_TRANSACTION_SIZE, V1_PREFIX},
18};
19use solana_packet::PACKET_DATA_SIZE;
20use solana_pubkey::{ParsePubkeyError, Pubkey};
21use solana_signature::Signature;
22use solana_transaction_status::{
23 InnerInstruction, InnerInstructions, TransactionBinaryEncoding, UiInnerInstructions,
24 UiTransactionEncoding, parse_ui_inner_instructions,
25};
26
27use crate::error::{SurfpoolError, SurfpoolResult};
28
29pub fn convert_transaction_metadata_from_canonical(
30 transaction_metadata: &TransactionMetadata,
31) -> surfpool_types::TransactionMetadata {
32 surfpool_types::TransactionMetadata {
33 signature: transaction_metadata.signature,
34 logs: transaction_metadata.logs.clone(),
35 inner_instructions: transaction_metadata.inner_instructions.clone(),
36 compute_units_consumed: transaction_metadata.compute_units_consumed,
37 return_data: transaction_metadata.return_data.clone(),
38 fee: transaction_metadata.fee,
39 }
40}
41
42fn optimize_filters(filters: &mut [RpcFilterType]) {
43 filters.iter_mut().for_each(|filter_type| {
44 if let RpcFilterType::Memcmp(compare) = filter_type {
45 if let Err(err) = compare.convert_to_raw_bytes() {
46 warn!("Invalid filter: bytes could not be decoded, {err}");
48 }
49 }
50 })
51}
52
53fn verify_filter(input: &RpcFilterType) -> Result<()> {
54 input
55 .verify()
56 .map_err(|e| Error::invalid_params(format!("Invalid param: {e:?}")))
57}
58
59pub fn verify_pubkey(input: &str) -> SurfpoolResult<Pubkey> {
60 input
61 .parse()
62 .map_err(|e: ParsePubkeyError| SurfpoolError::invalid_pubkey(input, e.to_string()))
63}
64
65pub fn verify_pubkeys(input: &[String]) -> SurfpoolResult<Vec<Pubkey>> {
66 input
67 .iter()
68 .enumerate()
69 .map(|(i, s)| {
70 verify_pubkey(s)
71 .map_err(|e| SurfpoolError::invalid_pubkey_at_index(s, i, e.to_string()))
72 })
73 .collect::<SurfpoolResult<Vec<_>>>()
74}
75
76fn verify_hash(input: &str) -> Result<Hash> {
77 input
78 .parse()
79 .map_err(|e| Error::invalid_params(format!("Invalid param: {e:?}")))
80}
81
82fn verify_signature(input: &str) -> Result<Signature> {
83 input
84 .parse()
85 .map_err(|e| Error::invalid_params(format!("Invalid param: {e:?}")))
86}
87
88fn verify_token_account_filter(
89 token_account_filter: RpcTokenAccountsFilter,
90) -> Result<TokenAccountsFilter> {
91 match token_account_filter {
92 RpcTokenAccountsFilter::Mint(mint_str) => {
93 let mint = verify_pubkey(&mint_str)?;
94 Ok(TokenAccountsFilter::Mint(mint))
95 }
96 RpcTokenAccountsFilter::ProgramId(program_id_str) => {
97 let program_id = verify_pubkey(&program_id_str)?;
98 Ok(TokenAccountsFilter::ProgramId(program_id))
99 }
100 }
101}
102
103fn verify_and_parse_signatures_for_address_params(
104 address: String,
105 before: Option<String>,
106 until: Option<String>,
107 limit: Option<usize>,
108) -> Result<(Pubkey, Option<Signature>, Option<Signature>, usize)> {
109 let address = verify_pubkey(&address)?;
110 let before = before
111 .map(|ref before| verify_signature(before))
112 .transpose()?;
113 let until = until.map(|ref until| verify_signature(until)).transpose()?;
114 let limit = limit.unwrap_or(MAX_GET_CONFIRMED_SIGNATURES_FOR_ADDRESS2_LIMIT);
115
116 if limit == 0 || limit > MAX_GET_CONFIRMED_SIGNATURES_FOR_ADDRESS2_LIMIT {
117 return Err(Error::invalid_params(format!(
118 "Invalid limit; max {MAX_GET_CONFIRMED_SIGNATURES_FOR_ADDRESS2_LIMIT}"
119 )));
120 }
121 Ok((address, before, until, limit))
122}
123
124const MAX_BASE58_SIZE: usize = 5594; const MAX_BASE64_SIZE: usize = 5464; pub const MAX_SUPPORTED_TRANSACTION_VERSION: u8 = 1;
129
130fn wire_size_limit(wire_output: &[u8]) -> usize {
131 if wire_output.first().copied() == Some(V1_PREFIX) {
134 MAX_TRANSACTION_SIZE
135 } else {
136 PACKET_DATA_SIZE
137 }
138}
139
140pub fn decode_and_deserialize<T>(
141 encoded: String,
142 encoding: TransactionBinaryEncoding,
143) -> Result<(Vec<u8>, T)>
144where
145 T: wincode::DeserializeOwned<Dst = T>,
146{
147 let wire_output = match encoding {
148 TransactionBinaryEncoding::Base58 => {
149 if encoded.len() > MAX_BASE58_SIZE {
150 return Err(Error::invalid_params(format!(
151 "base58 encoded {} too large: {} bytes (max: encoded/raw {}/{})",
152 type_name::<T>(),
153 encoded.len(),
154 MAX_BASE58_SIZE,
155 MAX_TRANSACTION_SIZE,
156 )));
157 }
158 bs58::decode(encoded)
159 .into_vec()
160 .map_err(|e| Error::invalid_params(format!("invalid base58 encoding: {e:?}")))?
161 }
162 TransactionBinaryEncoding::Base64 => {
163 if encoded.len() > MAX_BASE64_SIZE {
164 return Err(Error::invalid_params(format!(
165 "base64 encoded {} too large: {} bytes (max: encoded/raw {}/{})",
166 type_name::<T>(),
167 encoded.len(),
168 MAX_BASE64_SIZE,
169 MAX_TRANSACTION_SIZE,
170 )));
171 }
172 BASE64_STANDARD
173 .decode(encoded)
174 .map_err(|e| Error::invalid_params(format!("invalid base64 encoding: {e:?}")))?
175 }
176 };
177 let size_limit = wire_size_limit(&wire_output);
178 if wire_output.len() > size_limit {
179 return Err(Error::invalid_params(format!(
180 "decoded {} too large: {} bytes (max: {} bytes)",
181 type_name::<T>(),
182 wire_output.len(),
183 size_limit
184 )));
185 }
186 wincode::deserialize(&wire_output)
187 .map_err(|err| {
188 Error::invalid_params(format!(
189 "failed to deserialize {}: {}",
190 type_name::<T>(),
191 &err.to_string()
192 ))
193 })
194 .map(|output| (wire_output, output))
195}
196
197pub fn decode_rpc_versioned_transaction(
206 data: String,
207 encoding: Option<UiTransactionEncoding>,
208) -> Result<solana_transaction::versioned::VersionedTransaction> {
209 let tx_encoding = encoding.unwrap_or(UiTransactionEncoding::Base58);
210 let binary_encoding = tx_encoding.into_binary_encoding().ok_or_else(|| {
211 Error::invalid_params(format!(
212 "unsupported encoding: {tx_encoding}. Supported encodings: base58, base64"
213 ))
214 })?;
215 let (_, unsanitized_tx) = decode_and_deserialize::<
216 solana_transaction::versioned::VersionedTransaction,
217 >(data, binary_encoding)?;
218 Ok(unsanitized_tx)
219}
220
221pub fn transform_tx_metadata_to_ui_accounts(
222 meta: TransactionMetadata,
223 message: &VersionedMessage,
224 loaded_addresses: Option<&solana_message::v0::LoadedAddresses>,
225) -> Vec<UiInnerInstructions> {
226 let account_keys = AccountKeys::new(message.static_account_keys(), loaded_addresses);
228
229 meta.inner_instructions
230 .into_iter()
231 .enumerate()
232 .filter_map(|(i, ixs)| {
233 let instructions: Vec<InnerInstruction> = ixs
234 .iter()
235 .map(|ix| InnerInstruction {
236 instruction: ix.instruction.clone(),
237 stack_height: Some(ix.stack_height as u32),
238 })
239 .collect();
240 if instructions.is_empty() {
241 None
242 } else {
243 let inner_instructions = InnerInstructions {
246 index: i as u8,
247 instructions,
248 };
249 Some(parse_ui_inner_instructions(
250 inner_instructions,
251 &account_keys,
252 ))
253 }
254 })
255 .collect()
256}
257
258const METHOD_NOT_SUPPORTED_NEEDLES: &[&str] = &[
262 "not supported",
263 "unsupported",
264 "unavailable",
265 "method blocked",
266 "invalid request",
267 "is blocked",
268 "if you need this method",
269 "client error 410",
270 "410 gone",
271 "(410 gone)",
272 " status 410",
273 "http 410",
274 "client error (410",
275];
276
277pub fn is_method_not_supported_error<E: std::fmt::Display>(err: &E) -> bool {
279 let msg = err.to_string().to_lowercase();
280 METHOD_NOT_SUPPORTED_NEEDLES
281 .iter()
282 .any(|needle| msg.contains(needle))
283}
284
285pub fn get_default_transaction_config() -> RpcTransactionConfig {
286 RpcTransactionConfig {
287 encoding: Some(UiTransactionEncoding::Json),
288 commitment: Some(CommitmentConfig::default()),
289 max_supported_transaction_version: Some(MAX_SUPPORTED_TRANSACTION_VERSION),
290 }
291}
292
293pub fn adjust_default_transaction_config(config: &mut RpcTransactionConfig) {
294 if config.encoding.is_none() {
295 config.encoding = Some(UiTransactionEncoding::Json);
296 }
297 if config.max_supported_transaction_version.is_none() {
298 config.max_supported_transaction_version = Some(MAX_SUPPORTED_TRANSACTION_VERSION);
299 }
300 if config.commitment.is_none() {
301 config.commitment = Some(CommitmentConfig::default());
302 }
303}
304
305#[cfg(test)]
306mod tests {
307 use solana_keypair::Keypair;
308 use solana_message::{MessageHeader, compiled_instruction::CompiledInstruction, v1};
309 use solana_signer::Signer;
310 use solana_transaction::versioned::VersionedTransaction;
311
312 use super::*;
313
314 fn signed_v1_transaction(data_len: usize) -> VersionedTransaction {
315 let payer = Keypair::new();
316 let message = VersionedMessage::V1(v1::Message::new(
317 MessageHeader {
318 num_required_signatures: 1,
319 num_readonly_signed_accounts: 0,
320 num_readonly_unsigned_accounts: 1,
321 },
322 v1::TransactionConfig::empty(),
323 Hash::default(),
324 vec![payer.pubkey(), Pubkey::new_unique()],
325 vec![CompiledInstruction {
326 program_id_index: 1,
327 accounts: vec![0],
328 data: vec![0; data_len],
329 }],
330 ));
331 let transaction = VersionedTransaction::try_new(message, &[&payer]).unwrap();
332 assert!(
333 transaction.signatures[0]
334 .as_ref()
335 .iter()
336 .any(|byte| *byte != 0)
337 );
338 transaction
339 }
340
341 #[test]
342 fn decodes_signed_v1_transaction_larger_than_legacy_packet() {
343 let transaction = signed_v1_transaction(PACKET_DATA_SIZE);
344 let wire = wincode::serialize(&transaction).unwrap();
345 assert!(wire.len() > PACKET_DATA_SIZE);
346 assert!(wire.len() <= MAX_TRANSACTION_SIZE);
347 assert_eq!(wire[0], V1_PREFIX);
348
349 let encoded = BASE64_STANDARD.encode(&wire);
350 let (_, decoded) = decode_and_deserialize::<VersionedTransaction>(
351 encoded,
352 TransactionBinaryEncoding::Base64,
353 )
354 .unwrap();
355
356 assert_eq!(decoded, transaction);
357 }
358
359 #[test]
360 fn rejects_oversized_v1_and_legacy_wire_payloads() {
361 let mut oversized_v1 = vec![0; MAX_TRANSACTION_SIZE + 1];
362 oversized_v1[0] = V1_PREFIX;
363 let v1_error = decode_and_deserialize::<VersionedTransaction>(
364 BASE64_STANDARD.encode(oversized_v1),
365 TransactionBinaryEncoding::Base64,
366 )
367 .unwrap_err();
368 assert!(v1_error.message.contains("max: 4096 bytes"));
369
370 let oversized_legacy = vec![0; PACKET_DATA_SIZE + 1];
371 let legacy_error = decode_and_deserialize::<VersionedTransaction>(
372 BASE64_STANDARD.encode(oversized_legacy),
373 TransactionBinaryEncoding::Base64,
374 )
375 .unwrap_err();
376 assert!(legacy_error.message.contains("max: 1232 bytes"));
377 }
378
379 #[test]
380 fn worst_case_v1_encoded_size_goldens() {
381 let wire = vec![0xff; MAX_TRANSACTION_SIZE];
382 assert_eq!(bs58::encode(&wire).into_string().len(), MAX_BASE58_SIZE);
383 assert_eq!(BASE64_STANDARD.encode(&wire).len(), MAX_BASE64_SIZE);
384 }
385
386 #[test]
387 fn transaction_config_defaults_support_v1() {
388 assert_eq!(
389 get_default_transaction_config().max_supported_transaction_version,
390 Some(MAX_SUPPORTED_TRANSACTION_VERSION)
391 );
392
393 let mut config = RpcTransactionConfig::default();
394 adjust_default_transaction_config(&mut config);
395 assert_eq!(
396 config.max_supported_transaction_version,
397 Some(MAX_SUPPORTED_TRANSACTION_VERSION)
398 );
399 }
400}