surfpool_core/rpc/
utils.rs

1#![allow(dead_code)]
2
3use std::{any::type_name, sync::Arc};
4
5use base64::prelude::*;
6use bincode::Options;
7use jsonrpc_core::{Error, Result};
8use litesvm::types::TransactionMetadata;
9use solana_client::{
10    rpc_config::RpcTokenAccountsFilter,
11    rpc_custom_error::RpcCustomError,
12    rpc_filter::RpcFilterType,
13    rpc_request::{TokenAccountsFilter, MAX_GET_CONFIRMED_SIGNATURES_FOR_ADDRESS2_LIMIT},
14};
15use solana_hash::Hash;
16use solana_packet::PACKET_DATA_SIZE;
17use solana_pubkey::{ParsePubkeyError, Pubkey};
18use solana_runtime::verify_precompiles::verify_precompiles;
19use solana_signature::Signature;
20use solana_transaction::sanitized::SanitizedTransaction;
21use solana_transaction_status::{
22    InnerInstruction, InnerInstructions, TransactionBinaryEncoding, UiInnerInstructions,
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    }
37}
38
39fn optimize_filters(filters: &mut [RpcFilterType]) {
40    filters.iter_mut().for_each(|filter_type| {
41        if let RpcFilterType::Memcmp(compare) = filter_type {
42            if let Err(err) = compare.convert_to_raw_bytes() {
43                // All filters should have been previously verified
44                warn!("Invalid filter: bytes could not be decoded, {err}");
45            }
46        }
47    })
48}
49
50fn verify_transaction(
51    transaction: &SanitizedTransaction,
52    feature_set: &Arc<solana_feature_set::FeatureSet>,
53) -> Result<()> {
54    #[allow(clippy::question_mark)]
55    if transaction.verify().is_err() {
56        return Err(RpcCustomError::TransactionSignatureVerificationFailure.into());
57    }
58
59    let move_precompile_verification_to_svm =
60        feature_set.is_active(&solana_feature_set::move_precompile_verification_to_svm::id());
61    if !move_precompile_verification_to_svm {
62        if let Err(e) = verify_precompiles(transaction, feature_set) {
63            return Err(RpcCustomError::TransactionPrecompileVerificationFailure(e).into());
64        }
65    }
66
67    Ok(())
68}
69
70fn verify_filter(input: &RpcFilterType) -> Result<()> {
71    input
72        .verify()
73        .map_err(|e| Error::invalid_params(format!("Invalid param: {e:?}")))
74}
75
76pub fn verify_pubkey(input: &str) -> SurfpoolResult<Pubkey> {
77    input
78        .parse()
79        .map_err(|e: ParsePubkeyError| SurfpoolError::invalid_pubkey(input, e.to_string()))
80}
81
82fn verify_hash(input: &str) -> Result<Hash> {
83    input
84        .parse()
85        .map_err(|e| Error::invalid_params(format!("Invalid param: {e:?}")))
86}
87
88fn verify_signature(input: &str) -> Result<Signature> {
89    input
90        .parse()
91        .map_err(|e| Error::invalid_params(format!("Invalid param: {e:?}")))
92}
93
94fn verify_token_account_filter(
95    token_account_filter: RpcTokenAccountsFilter,
96) -> Result<TokenAccountsFilter> {
97    match token_account_filter {
98        RpcTokenAccountsFilter::Mint(mint_str) => {
99            let mint = verify_pubkey(&mint_str)?;
100            Ok(TokenAccountsFilter::Mint(mint))
101        }
102        RpcTokenAccountsFilter::ProgramId(program_id_str) => {
103            let program_id = verify_pubkey(&program_id_str)?;
104            Ok(TokenAccountsFilter::ProgramId(program_id))
105        }
106    }
107}
108
109fn verify_and_parse_signatures_for_address_params(
110    address: String,
111    before: Option<String>,
112    until: Option<String>,
113    limit: Option<usize>,
114) -> Result<(Pubkey, Option<Signature>, Option<Signature>, usize)> {
115    let address = verify_pubkey(&address)?;
116    let before = before
117        .map(|ref before| verify_signature(before))
118        .transpose()?;
119    let until = until.map(|ref until| verify_signature(until)).transpose()?;
120    let limit = limit.unwrap_or(MAX_GET_CONFIRMED_SIGNATURES_FOR_ADDRESS2_LIMIT);
121
122    if limit == 0 || limit > MAX_GET_CONFIRMED_SIGNATURES_FOR_ADDRESS2_LIMIT {
123        return Err(Error::invalid_params(format!(
124            "Invalid limit; max {MAX_GET_CONFIRMED_SIGNATURES_FOR_ADDRESS2_LIMIT}"
125        )));
126    }
127    Ok((address, before, until, limit))
128}
129
130const MAX_BASE58_SIZE: usize = 1683; // Golden, bump if PACKET_DATA_SIZE changes
131const MAX_BASE64_SIZE: usize = 1644; // Golden, bump if PACKET_DATA_SIZE changes
132pub fn decode_and_deserialize<T>(
133    encoded: String,
134    encoding: TransactionBinaryEncoding,
135) -> Result<(Vec<u8>, T)>
136where
137    T: serde::de::DeserializeOwned,
138{
139    let wire_output = match encoding {
140        TransactionBinaryEncoding::Base58 => {
141            if encoded.len() > MAX_BASE58_SIZE {
142                return Err(Error::invalid_params(format!(
143                    "base58 encoded {} too large: {} bytes (max: encoded/raw {}/{})",
144                    type_name::<T>(),
145                    encoded.len(),
146                    MAX_BASE58_SIZE,
147                    PACKET_DATA_SIZE,
148                )));
149            }
150            bs58::decode(encoded)
151                .into_vec()
152                .map_err(|e| Error::invalid_params(format!("invalid base58 encoding: {e:?}")))?
153        }
154        TransactionBinaryEncoding::Base64 => {
155            if encoded.len() > MAX_BASE64_SIZE {
156                return Err(Error::invalid_params(format!(
157                    "base64 encoded {} too large: {} bytes (max: encoded/raw {}/{})",
158                    type_name::<T>(),
159                    encoded.len(),
160                    MAX_BASE64_SIZE,
161                    PACKET_DATA_SIZE,
162                )));
163            }
164            BASE64_STANDARD
165                .decode(encoded)
166                .map_err(|e| Error::invalid_params(format!("invalid base64 encoding: {e:?}")))?
167        }
168    };
169    if wire_output.len() > PACKET_DATA_SIZE {
170        return Err(Error::invalid_params(format!(
171            "decoded {} too large: {} bytes (max: {} bytes)",
172            type_name::<T>(),
173            wire_output.len(),
174            PACKET_DATA_SIZE
175        )));
176    }
177    bincode::options()
178        .with_limit(PACKET_DATA_SIZE as u64)
179        .with_fixint_encoding()
180        .allow_trailing_bytes()
181        .deserialize_from(&wire_output[..])
182        .map_err(|err| {
183            Error::invalid_params(format!(
184                "failed to deserialize {}: {}",
185                type_name::<T>(),
186                &err.to_string()
187            ))
188        })
189        .map(|output| (wire_output, output))
190}
191
192pub fn transform_tx_metadata_to_ui_accounts(
193    meta: &TransactionMetadata,
194) -> Vec<UiInnerInstructions> {
195    meta.inner_instructions
196        .iter()
197        .enumerate()
198        .map(|(i, ixs)| {
199            InnerInstructions {
200                index: i as u8,
201                instructions: ixs
202                    .iter()
203                    .map(|ix| InnerInstruction {
204                        instruction: ix.instruction.clone(),
205                        stack_height: Some(ix.stack_height as u32),
206                    })
207                    .collect(),
208            }
209            .into()
210        })
211        .collect()
212}