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
82pub fn verify_pubkeys(input: &[String]) -> SurfpoolResult<Vec<Pubkey>> {
83    input
84        .iter()
85        .enumerate()
86        .map(|(i, s)| {
87            verify_pubkey(s)
88                .map_err(|e| SurfpoolError::invalid_pubkey_at_index(s, i, e.to_string()))
89        })
90        .collect::<SurfpoolResult<Vec<_>>>()
91}
92
93fn verify_hash(input: &str) -> Result<Hash> {
94    input
95        .parse()
96        .map_err(|e| Error::invalid_params(format!("Invalid param: {e:?}")))
97}
98
99fn verify_signature(input: &str) -> Result<Signature> {
100    input
101        .parse()
102        .map_err(|e| Error::invalid_params(format!("Invalid param: {e:?}")))
103}
104
105fn verify_token_account_filter(
106    token_account_filter: RpcTokenAccountsFilter,
107) -> Result<TokenAccountsFilter> {
108    match token_account_filter {
109        RpcTokenAccountsFilter::Mint(mint_str) => {
110            let mint = verify_pubkey(&mint_str)?;
111            Ok(TokenAccountsFilter::Mint(mint))
112        }
113        RpcTokenAccountsFilter::ProgramId(program_id_str) => {
114            let program_id = verify_pubkey(&program_id_str)?;
115            Ok(TokenAccountsFilter::ProgramId(program_id))
116        }
117    }
118}
119
120fn verify_and_parse_signatures_for_address_params(
121    address: String,
122    before: Option<String>,
123    until: Option<String>,
124    limit: Option<usize>,
125) -> Result<(Pubkey, Option<Signature>, Option<Signature>, usize)> {
126    let address = verify_pubkey(&address)?;
127    let before = before
128        .map(|ref before| verify_signature(before))
129        .transpose()?;
130    let until = until.map(|ref until| verify_signature(until)).transpose()?;
131    let limit = limit.unwrap_or(MAX_GET_CONFIRMED_SIGNATURES_FOR_ADDRESS2_LIMIT);
132
133    if limit == 0 || limit > MAX_GET_CONFIRMED_SIGNATURES_FOR_ADDRESS2_LIMIT {
134        return Err(Error::invalid_params(format!(
135            "Invalid limit; max {MAX_GET_CONFIRMED_SIGNATURES_FOR_ADDRESS2_LIMIT}"
136        )));
137    }
138    Ok((address, before, until, limit))
139}
140
141const MAX_BASE58_SIZE: usize = 1683; // Golden, bump if PACKET_DATA_SIZE changes
142const MAX_BASE64_SIZE: usize = 1644; // Golden, bump if PACKET_DATA_SIZE changes
143pub fn decode_and_deserialize<T>(
144    encoded: String,
145    encoding: TransactionBinaryEncoding,
146) -> Result<(Vec<u8>, T)>
147where
148    T: serde::de::DeserializeOwned,
149{
150    let wire_output = match encoding {
151        TransactionBinaryEncoding::Base58 => {
152            if encoded.len() > MAX_BASE58_SIZE {
153                return Err(Error::invalid_params(format!(
154                    "base58 encoded {} too large: {} bytes (max: encoded/raw {}/{})",
155                    type_name::<T>(),
156                    encoded.len(),
157                    MAX_BASE58_SIZE,
158                    PACKET_DATA_SIZE,
159                )));
160            }
161            bs58::decode(encoded)
162                .into_vec()
163                .map_err(|e| Error::invalid_params(format!("invalid base58 encoding: {e:?}")))?
164        }
165        TransactionBinaryEncoding::Base64 => {
166            if encoded.len() > MAX_BASE64_SIZE {
167                return Err(Error::invalid_params(format!(
168                    "base64 encoded {} too large: {} bytes (max: encoded/raw {}/{})",
169                    type_name::<T>(),
170                    encoded.len(),
171                    MAX_BASE64_SIZE,
172                    PACKET_DATA_SIZE,
173                )));
174            }
175            BASE64_STANDARD
176                .decode(encoded)
177                .map_err(|e| Error::invalid_params(format!("invalid base64 encoding: {e:?}")))?
178        }
179    };
180    if wire_output.len() > PACKET_DATA_SIZE {
181        return Err(Error::invalid_params(format!(
182            "decoded {} too large: {} bytes (max: {} bytes)",
183            type_name::<T>(),
184            wire_output.len(),
185            PACKET_DATA_SIZE
186        )));
187    }
188    bincode::options()
189        .with_limit(PACKET_DATA_SIZE as u64)
190        .with_fixint_encoding()
191        .allow_trailing_bytes()
192        .deserialize_from(&wire_output[..])
193        .map_err(|err| {
194            Error::invalid_params(format!(
195                "failed to deserialize {}: {}",
196                type_name::<T>(),
197                &err.to_string()
198            ))
199        })
200        .map(|output| (wire_output, output))
201}
202
203pub fn transform_tx_metadata_to_ui_accounts(
204    meta: &TransactionMetadata,
205) -> Vec<UiInnerInstructions> {
206    meta.inner_instructions
207        .iter()
208        .enumerate()
209        .map(|(i, ixs)| {
210            InnerInstructions {
211                index: i as u8,
212                instructions: ixs
213                    .iter()
214                    .map(|ix| InnerInstruction {
215                        instruction: ix.instruction.clone(),
216                        stack_height: Some(ix.stack_height as u32),
217                    })
218                    .collect(),
219            }
220            .into()
221        })
222        .collect()
223}