1#![allow(dead_code)]
2
3use std::any::type_name;
4
5use base64::prelude::*;
6use bincode::Options;
7use jsonrpc_core::{Error, Result};
8use litesvm::types::TransactionMetadata;
9use solana_client::{
10 rpc_config::{RpcTokenAccountsFilter, RpcTransactionConfig},
11 rpc_filter::RpcFilterType,
12 rpc_request::{MAX_GET_CONFIRMED_SIGNATURES_FOR_ADDRESS2_LIMIT, TokenAccountsFilter},
13};
14use solana_commitment_config::CommitmentConfig;
15use solana_hash::Hash;
16use solana_message::{AccountKeys, VersionedMessage};
17use solana_packet::PACKET_DATA_SIZE;
18use solana_pubkey::{ParsePubkeyError, Pubkey};
19use solana_signature::Signature;
20use solana_transaction_status::{
21 InnerInstruction, InnerInstructions, TransactionBinaryEncoding, UiInnerInstructions,
22 UiTransactionEncoding, parse_ui_inner_instructions,
23};
24
25use crate::error::{SurfpoolError, SurfpoolResult};
26
27pub fn convert_transaction_metadata_from_canonical(
28 transaction_metadata: &TransactionMetadata,
29) -> surfpool_types::TransactionMetadata {
30 surfpool_types::TransactionMetadata {
31 signature: transaction_metadata.signature,
32 logs: transaction_metadata.logs.clone(),
33 inner_instructions: transaction_metadata.inner_instructions.clone(),
34 compute_units_consumed: transaction_metadata.compute_units_consumed,
35 return_data: transaction_metadata.return_data.clone(),
36 fee: transaction_metadata.fee,
37 }
38}
39
40fn optimize_filters(filters: &mut [RpcFilterType]) {
41 filters.iter_mut().for_each(|filter_type| {
42 if let RpcFilterType::Memcmp(compare) = filter_type {
43 if let Err(err) = compare.convert_to_raw_bytes() {
44 warn!("Invalid filter: bytes could not be decoded, {err}");
46 }
47 }
48 })
49}
50
51fn verify_filter(input: &RpcFilterType) -> Result<()> {
52 input
53 .verify()
54 .map_err(|e| Error::invalid_params(format!("Invalid param: {e:?}")))
55}
56
57pub fn verify_pubkey(input: &str) -> SurfpoolResult<Pubkey> {
58 input
59 .parse()
60 .map_err(|e: ParsePubkeyError| SurfpoolError::invalid_pubkey(input, e.to_string()))
61}
62
63pub fn verify_pubkeys(input: &[String]) -> SurfpoolResult<Vec<Pubkey>> {
64 input
65 .iter()
66 .enumerate()
67 .map(|(i, s)| {
68 verify_pubkey(s)
69 .map_err(|e| SurfpoolError::invalid_pubkey_at_index(s, i, e.to_string()))
70 })
71 .collect::<SurfpoolResult<Vec<_>>>()
72}
73
74fn verify_hash(input: &str) -> Result<Hash> {
75 input
76 .parse()
77 .map_err(|e| Error::invalid_params(format!("Invalid param: {e:?}")))
78}
79
80fn verify_signature(input: &str) -> Result<Signature> {
81 input
82 .parse()
83 .map_err(|e| Error::invalid_params(format!("Invalid param: {e:?}")))
84}
85
86fn verify_token_account_filter(
87 token_account_filter: RpcTokenAccountsFilter,
88) -> Result<TokenAccountsFilter> {
89 match token_account_filter {
90 RpcTokenAccountsFilter::Mint(mint_str) => {
91 let mint = verify_pubkey(&mint_str)?;
92 Ok(TokenAccountsFilter::Mint(mint))
93 }
94 RpcTokenAccountsFilter::ProgramId(program_id_str) => {
95 let program_id = verify_pubkey(&program_id_str)?;
96 Ok(TokenAccountsFilter::ProgramId(program_id))
97 }
98 }
99}
100
101fn verify_and_parse_signatures_for_address_params(
102 address: String,
103 before: Option<String>,
104 until: Option<String>,
105 limit: Option<usize>,
106) -> Result<(Pubkey, Option<Signature>, Option<Signature>, usize)> {
107 let address = verify_pubkey(&address)?;
108 let before = before
109 .map(|ref before| verify_signature(before))
110 .transpose()?;
111 let until = until.map(|ref until| verify_signature(until)).transpose()?;
112 let limit = limit.unwrap_or(MAX_GET_CONFIRMED_SIGNATURES_FOR_ADDRESS2_LIMIT);
113
114 if limit == 0 || limit > MAX_GET_CONFIRMED_SIGNATURES_FOR_ADDRESS2_LIMIT {
115 return Err(Error::invalid_params(format!(
116 "Invalid limit; max {MAX_GET_CONFIRMED_SIGNATURES_FOR_ADDRESS2_LIMIT}"
117 )));
118 }
119 Ok((address, before, until, limit))
120}
121
122const MAX_BASE58_SIZE: usize = 1683; const MAX_BASE64_SIZE: usize = 1644; pub fn decode_and_deserialize<T>(
125 encoded: String,
126 encoding: TransactionBinaryEncoding,
127) -> Result<(Vec<u8>, T)>
128where
129 T: serde::de::DeserializeOwned,
130{
131 let wire_output = match encoding {
132 TransactionBinaryEncoding::Base58 => {
133 if encoded.len() > MAX_BASE58_SIZE {
134 return Err(Error::invalid_params(format!(
135 "base58 encoded {} too large: {} bytes (max: encoded/raw {}/{})",
136 type_name::<T>(),
137 encoded.len(),
138 MAX_BASE58_SIZE,
139 PACKET_DATA_SIZE,
140 )));
141 }
142 bs58::decode(encoded)
143 .into_vec()
144 .map_err(|e| Error::invalid_params(format!("invalid base58 encoding: {e:?}")))?
145 }
146 TransactionBinaryEncoding::Base64 => {
147 if encoded.len() > MAX_BASE64_SIZE {
148 return Err(Error::invalid_params(format!(
149 "base64 encoded {} too large: {} bytes (max: encoded/raw {}/{})",
150 type_name::<T>(),
151 encoded.len(),
152 MAX_BASE64_SIZE,
153 PACKET_DATA_SIZE,
154 )));
155 }
156 BASE64_STANDARD
157 .decode(encoded)
158 .map_err(|e| Error::invalid_params(format!("invalid base64 encoding: {e:?}")))?
159 }
160 };
161 if wire_output.len() > PACKET_DATA_SIZE {
162 return Err(Error::invalid_params(format!(
163 "decoded {} too large: {} bytes (max: {} bytes)",
164 type_name::<T>(),
165 wire_output.len(),
166 PACKET_DATA_SIZE
167 )));
168 }
169 bincode::options()
170 .with_limit(PACKET_DATA_SIZE as u64)
171 .with_fixint_encoding()
172 .allow_trailing_bytes()
173 .deserialize_from(&wire_output[..])
174 .map_err(|err| {
175 Error::invalid_params(format!(
176 "failed to deserialize {}: {}",
177 type_name::<T>(),
178 &err.to_string()
179 ))
180 })
181 .map(|output| (wire_output, output))
182}
183
184pub fn decode_rpc_versioned_transaction(
193 data: String,
194 encoding: Option<UiTransactionEncoding>,
195) -> Result<solana_transaction::versioned::VersionedTransaction> {
196 let tx_encoding = encoding.unwrap_or(UiTransactionEncoding::Base58);
197 let binary_encoding = tx_encoding.into_binary_encoding().ok_or_else(|| {
198 Error::invalid_params(format!(
199 "unsupported encoding: {tx_encoding}. Supported encodings: base58, base64"
200 ))
201 })?;
202 let (_, unsanitized_tx) = decode_and_deserialize::<
203 solana_transaction::versioned::VersionedTransaction,
204 >(data, binary_encoding)?;
205 Ok(unsanitized_tx)
206}
207
208pub fn transform_tx_metadata_to_ui_accounts(
209 meta: TransactionMetadata,
210 message: &VersionedMessage,
211 loaded_addresses: Option<&solana_message::v0::LoadedAddresses>,
212) -> Vec<UiInnerInstructions> {
213 let account_keys = AccountKeys::new(message.static_account_keys(), loaded_addresses);
215
216 meta.inner_instructions
217 .into_iter()
218 .enumerate()
219 .filter_map(|(i, ixs)| {
220 let instructions: Vec<InnerInstruction> = ixs
221 .iter()
222 .map(|ix| InnerInstruction {
223 instruction: ix.instruction.clone(),
224 stack_height: Some(ix.stack_height as u32),
225 })
226 .collect();
227 if instructions.is_empty() {
228 None
229 } else {
230 let inner_instructions = InnerInstructions {
233 index: i as u8,
234 instructions,
235 };
236 Some(parse_ui_inner_instructions(
237 inner_instructions,
238 &account_keys,
239 ))
240 }
241 })
242 .collect()
243}
244
245const METHOD_NOT_SUPPORTED_NEEDLES: &[&str] = &[
249 "not supported",
250 "unsupported",
251 "unavailable",
252 "method blocked",
253 "invalid request",
254 "is blocked",
255 "if you need this method",
256 "client error 410",
257 "410 gone",
258 "(410 gone)",
259 " status 410",
260 "http 410",
261 "client error (410",
262];
263
264pub fn is_method_not_supported_error<E: std::fmt::Display>(err: &E) -> bool {
266 let msg = err.to_string().to_lowercase();
267 METHOD_NOT_SUPPORTED_NEEDLES
268 .iter()
269 .any(|needle| msg.contains(needle))
270}
271
272pub fn get_default_transaction_config() -> RpcTransactionConfig {
273 RpcTransactionConfig {
274 encoding: Some(UiTransactionEncoding::Json),
275 commitment: Some(CommitmentConfig::default()),
276 max_supported_transaction_version: Some(0),
277 }
278}
279
280pub fn adjust_default_transaction_config(config: &mut RpcTransactionConfig) {
281 if config.encoding.is_none() {
282 config.encoding = Some(UiTransactionEncoding::Json);
283 }
284 if config.max_supported_transaction_version.is_none() {
285 config.max_supported_transaction_version = Some(0);
286 }
287 if config.commitment.is_none() {
288 config.commitment = Some(CommitmentConfig::default());
289 }
290}