surfpool_core/surfnet/
remote.rs

1use std::str::FromStr;
2
3use serde_json::json;
4use solana_account_decoder::{encode_ui_account, UiAccountEncoding};
5use solana_client::{
6    nonblocking::rpc_client::RpcClient,
7    rpc_client::GetConfirmedSignaturesForAddress2Config,
8    rpc_config::{
9        RpcAccountInfoConfig, RpcLargestAccountsConfig, RpcProgramAccountsConfig,
10        RpcSignaturesForAddressConfig, RpcTokenAccountsFilter,
11    },
12    rpc_filter::RpcFilterType,
13    rpc_request::{RpcRequest, TokenAccountsFilter},
14    rpc_response::{
15        RpcAccountBalance, RpcConfirmedTransactionStatusWithSignature, RpcKeyedAccount, RpcResult,
16        RpcTokenAccountBalance,
17    },
18};
19use solana_commitment_config::CommitmentConfig;
20use solana_epoch_info::EpochInfo;
21use solana_hash::Hash;
22use solana_pubkey::Pubkey;
23use solana_sdk::bpf_loader_upgradeable::get_program_data_address;
24use solana_signature::Signature;
25use solana_transaction_status::UiTransactionEncoding;
26
27use super::GetTransactionResult;
28use crate::{
29    error::{SurfpoolError, SurfpoolResult},
30    surfnet::{locker::is_supported_token_program, GetAccountResult},
31};
32
33pub struct SurfnetRemoteClient {
34    pub client: RpcClient,
35}
36impl Clone for SurfnetRemoteClient {
37    fn clone(&self) -> Self {
38        let remote_rpc_url = self.client.url();
39        SurfnetRemoteClient {
40            client: RpcClient::new(remote_rpc_url),
41        }
42    }
43}
44
45pub trait SomeRemoteCtx {
46    fn get_remote_ctx<T>(&self, input: T) -> Option<(SurfnetRemoteClient, T)>;
47}
48
49impl SomeRemoteCtx for Option<SurfnetRemoteClient> {
50    fn get_remote_ctx<T>(&self, input: T) -> Option<(SurfnetRemoteClient, T)> {
51        self.as_ref()
52            .map(|remote_rpc_client| (remote_rpc_client.clone(), input))
53    }
54}
55
56impl SurfnetRemoteClient {
57    pub fn new(remote_rpc_url: &str) -> Self {
58        SurfnetRemoteClient {
59            client: RpcClient::new(remote_rpc_url.to_string()),
60        }
61    }
62
63    pub async fn get_epoch_info(&self) -> SurfpoolResult<EpochInfo> {
64        self.client.get_epoch_info().await.map_err(Into::into)
65    }
66
67    pub async fn get_account(
68        &self,
69        pubkey: &Pubkey,
70        commitment_config: CommitmentConfig,
71    ) -> SurfpoolResult<GetAccountResult> {
72        let res = self
73            .client
74            .get_account_with_commitment(pubkey, commitment_config)
75            .await
76            .map_err(|e| SurfpoolError::get_account(*pubkey, e))?;
77
78        let result = match res.value {
79            Some(account) => {
80                if !account.executable {
81                    GetAccountResult::FoundAccount(
82                        *pubkey, account,
83                        // Mark this account as needing to be updated in the SVM, since we fetched it
84                        true,
85                    )
86                } else {
87                    let program_data_address = get_program_data_address(pubkey);
88
89                    let program_data = self
90                        .client
91                        .get_account_with_commitment(&program_data_address, commitment_config)
92                        .await
93                        .map_err(|e| SurfpoolError::get_account(*pubkey, e))?;
94
95                    GetAccountResult::FoundProgramAccount(
96                        (*pubkey, account),
97                        (program_data_address, program_data.value),
98                    )
99                }
100            }
101            None => GetAccountResult::None(*pubkey),
102        };
103        Ok(result)
104    }
105
106    pub async fn get_multiple_accounts(
107        &self,
108        pubkeys: &[Pubkey],
109        commitment_config: CommitmentConfig,
110    ) -> SurfpoolResult<Vec<GetAccountResult>> {
111        let remote_accounts = self
112            .client
113            .get_multiple_accounts(pubkeys)
114            .await
115            .map_err(SurfpoolError::get_multiple_accounts)?;
116
117        let mut accounts_result = vec![];
118        for (pubkey, remote_account) in pubkeys.iter().zip(remote_accounts) {
119            if let Some(remote_account) = remote_account {
120                if !remote_account.executable {
121                    accounts_result.push(GetAccountResult::FoundAccount(
122                        *pubkey,
123                        remote_account,
124                        // Mark this account as needing to be updated in the SVM, since we fetched it
125                        true,
126                    ));
127                } else {
128                    let program_data_address = get_program_data_address(pubkey);
129
130                    let program_data = self
131                        .client
132                        .get_account_with_commitment(&program_data_address, commitment_config)
133                        .await
134                        .map_err(|e| SurfpoolError::get_account(*pubkey, e))?;
135
136                    accounts_result.push(GetAccountResult::FoundProgramAccount(
137                        (*pubkey, remote_account),
138                        (program_data_address, program_data.value),
139                    ));
140                }
141            } else {
142                accounts_result.push(GetAccountResult::None(*pubkey));
143            }
144        }
145        Ok(accounts_result)
146    }
147
148    pub async fn get_transaction(
149        &self,
150        signature: Signature,
151        encoding: Option<UiTransactionEncoding>,
152        latest_absolute_slot: u64,
153    ) -> GetTransactionResult {
154        match self
155            .client
156            .get_transaction(
157                &signature,
158                encoding.unwrap_or(UiTransactionEncoding::Base64),
159            )
160            .await
161        {
162            Ok(tx) => GetTransactionResult::found_transaction(signature, tx, latest_absolute_slot),
163            Err(_) => GetTransactionResult::None(signature),
164        }
165    }
166
167    pub async fn get_token_accounts_by_owner(
168        &self,
169        owner: Pubkey,
170        filter: &TokenAccountsFilter,
171        config: &RpcAccountInfoConfig,
172    ) -> SurfpoolResult<Vec<RpcKeyedAccount>> {
173        let token_account_filter = match filter {
174            TokenAccountsFilter::Mint(mint) => RpcTokenAccountsFilter::Mint(mint.to_string()),
175            TokenAccountsFilter::ProgramId(program_id) => {
176                RpcTokenAccountsFilter::ProgramId(program_id.to_string())
177            }
178        };
179
180        // the RPC client's default implementation of get_token_accounts_by_owner doesn't allow providing the config,
181        // so we need to use the send method directly
182        let res: RpcResult<Vec<RpcKeyedAccount>> = self
183            .client
184            .send(
185                RpcRequest::GetTokenAccountsByOwner,
186                json!([owner.to_string(), token_account_filter, config]),
187            )
188            .await;
189        res.map_err(|e| SurfpoolError::get_token_accounts(owner, filter, e))
190            .map(|res| res.value)
191    }
192
193    pub async fn get_token_largest_accounts(
194        &self,
195        mint: &Pubkey,
196        commitment_config: CommitmentConfig,
197    ) -> SurfpoolResult<Vec<RpcTokenAccountBalance>> {
198        self.client
199            .get_token_largest_accounts_with_commitment(mint, commitment_config)
200            .await
201            .map(|response| response.value)
202            .map_err(|e| SurfpoolError::get_token_largest_accounts(*mint, e))
203    }
204
205    pub async fn get_token_accounts_by_delegate(
206        &self,
207        delegate: Pubkey,
208        filter: &TokenAccountsFilter,
209        config: &RpcAccountInfoConfig,
210    ) -> SurfpoolResult<Vec<RpcKeyedAccount>> {
211        // validate that the program is supported if using ProgramId filter
212        if let TokenAccountsFilter::ProgramId(program_id) = &filter {
213            if !is_supported_token_program(program_id) {
214                return Err(SurfpoolError::unsupported_token_program(*program_id));
215            }
216        }
217
218        let token_account_filter = match &filter {
219            TokenAccountsFilter::Mint(mint) => RpcTokenAccountsFilter::Mint(mint.to_string()),
220            TokenAccountsFilter::ProgramId(program_id) => {
221                RpcTokenAccountsFilter::ProgramId(program_id.to_string())
222            }
223        };
224
225        let res: RpcResult<Vec<RpcKeyedAccount>> = self
226            .client
227            .send(
228                RpcRequest::GetTokenAccountsByDelegate,
229                json!([delegate.to_string(), token_account_filter, config]),
230            )
231            .await;
232
233        res.map_err(|e| SurfpoolError::get_token_accounts_by_delegate_error(delegate, filter, e))
234            .map(|res| res.value)
235    }
236
237    pub async fn get_program_accounts(
238        &self,
239        program_id: &Pubkey,
240        account_config: RpcAccountInfoConfig,
241        filters: Option<Vec<RpcFilterType>>,
242    ) -> SurfpoolResult<Vec<RpcKeyedAccount>> {
243        let encoding = account_config.encoding.unwrap_or(UiAccountEncoding::Base64);
244        let data_slice = account_config.data_slice;
245        self.client
246            .get_program_accounts_with_config(
247                program_id,
248                RpcProgramAccountsConfig {
249                    filters,
250                    with_context: Some(false),
251                    account_config,
252                    ..Default::default()
253                },
254            )
255            .await
256            .map(|accounts| {
257                accounts
258                    .iter()
259                    .map(|(pubkey, account)| RpcKeyedAccount {
260                        pubkey: pubkey.to_string(),
261                        account: encode_ui_account(pubkey, account, encoding, None, data_slice),
262                    })
263                    .collect()
264            })
265            .map_err(|e| SurfpoolError::get_program_accounts(*program_id, e))
266    }
267
268    pub async fn get_largest_accounts(
269        &self,
270        config: Option<RpcLargestAccountsConfig>,
271    ) -> SurfpoolResult<Vec<RpcAccountBalance>> {
272        self.client
273            .get_largest_accounts_with_config(config.unwrap_or_default())
274            .await
275            .map(|res| res.value)
276            .map_err(SurfpoolError::get_largest_accounts)
277    }
278
279    pub async fn get_genesis_hash(&self) -> SurfpoolResult<Hash> {
280        self.client.get_genesis_hash().await.map_err(Into::into)
281    }
282
283    pub async fn get_signatures_for_address(
284        &self,
285        pubkey: &Pubkey,
286        config: Option<RpcSignaturesForAddressConfig>,
287    ) -> SurfpoolResult<Vec<RpcConfirmedTransactionStatusWithSignature>> {
288        let c = match config {
289            Some(c) => GetConfirmedSignaturesForAddress2Config {
290                before: c.before.and_then(|s| Signature::from_str(&s).ok()),
291                commitment: c.commitment,
292                limit: c.limit,
293                until: c.until.and_then(|s| Signature::from_str(&s).ok()),
294            },
295            _ => GetConfirmedSignaturesForAddress2Config::default(),
296        };
297        self.client
298            .get_signatures_for_address_with_config(pubkey, c)
299            .await
300            .map_err(SurfpoolError::get_signatures_for_address)
301    }
302}