1use std::{collections::HashMap, str::FromStr};
2
3use serde_json::json;
4use solana_account::Account;
5use solana_account_decoder::UiAccount;
6use solana_client::{
7 nonblocking::rpc_client::RpcClient,
8 rpc_client::{GetConfirmedSignaturesForAddress2Config, RpcClientConfig},
9 rpc_config::{
10 RpcAccountInfoConfig, RpcBlockConfig, RpcLargestAccountsConfig, RpcProgramAccountsConfig,
11 RpcSignaturesForAddressConfig, RpcTokenAccountsFilter, RpcTransactionConfig,
12 },
13 rpc_filter::RpcFilterType,
14 rpc_request::{RpcRequest, TokenAccountsFilter},
15 rpc_response::{
16 RpcAccountBalance, RpcConfirmedTransactionStatusWithSignature, RpcKeyedAccount, RpcResult,
17 RpcTokenAccountBalance,
18 },
19};
20use solana_clock::Slot;
21use solana_commitment_config::CommitmentConfig;
22use solana_epoch_info::EpochInfo;
23use solana_epoch_schedule::EpochSchedule;
24use solana_hash::Hash;
25use solana_loader_v3_interface::get_program_data_address;
26use solana_pubkey::Pubkey;
27use solana_signature::Signature;
28use solana_transaction_status::UiConfirmedBlock;
29
30use super::GetTransactionResult;
31use crate::{
32 error::{SurfpoolError, SurfpoolResult},
33 rpc::utils::is_method_not_supported_error,
34 surfnet::{GetAccountResult, locker::is_supported_token_program},
35 types::{RemoteRpcResult, TokenAccount},
36};
37
38pub struct SurfnetRemoteClient {
39 pub client: RpcClient,
40}
41impl Clone for SurfnetRemoteClient {
42 fn clone(&self) -> Self {
43 let remote_rpc_url = self.client.url();
44 SurfnetRemoteClient::new_unsafe(remote_rpc_url)
45 .expect("unable to clone SurfnetRemoteClient")
46 }
47}
48
49pub trait SomeRemoteCtx {
50 fn get_remote_ctx<T>(&self, input: T) -> Option<(SurfnetRemoteClient, T)>;
51}
52
53impl SomeRemoteCtx for Option<SurfnetRemoteClient> {
54 fn get_remote_ctx<T>(&self, input: T) -> Option<(SurfnetRemoteClient, T)> {
55 self.as_ref()
56 .map(|remote_rpc_client| (remote_rpc_client.clone(), input))
57 }
58}
59
60impl SurfnetRemoteClient {
61 pub fn new<U: ToString>(remote_rpc_url: U) -> Self {
62 let client = RpcClient::new(remote_rpc_url.to_string());
63 SurfnetRemoteClient { client }
64 }
65
66 pub fn new_unsafe<U: ToString>(remote_rpc_url: U) -> Option<Self> {
67 use reqwest;
68 use solana_rpc_client::http_sender::HttpSender;
69
70 let client = match reqwest::Client::builder()
72 .danger_accept_invalid_certs(true)
73 .tls_built_in_root_certs(false)
74 .tls_built_in_webpki_certs(false)
75 .timeout(std::time::Duration::from_secs(30))
76 .build()
77 {
78 Ok(client) => client,
79 Err(e) => {
80 error!(
81 "unable to initialize datasource client after retries: {}",
82 e
83 );
84 return None;
85 }
86 };
87 let http_sender = HttpSender::new_with_client(remote_rpc_url, client);
88 let client = RpcClient::new_sender(http_sender, RpcClientConfig::default());
89 Some(SurfnetRemoteClient { client })
90 }
91
92 pub async fn get_epoch_info(&self) -> SurfpoolResult<EpochInfo> {
93 self.client.get_epoch_info().await.map_err(Into::into)
94 }
95
96 pub async fn get_epoch_schedule(&self) -> SurfpoolResult<EpochSchedule> {
97 self.client.get_epoch_schedule().await.map_err(Into::into)
98 }
99
100 pub async fn get_account(
101 &self,
102 pubkey: &Pubkey,
103 commitment_config: CommitmentConfig,
104 ) -> SurfpoolResult<GetAccountResult> {
105 #[cfg(feature = "prometheus")]
106 let fetch_start = std::time::Instant::now();
107
108 let res = self
109 .client
110 .get_account_with_commitment(pubkey, commitment_config)
111 .await
112 .map_err(|e| SurfpoolError::get_account(*pubkey, e))?;
113
114 let result = match res.value {
115 Some(account) => {
116 let mut result = None;
117 if is_supported_token_program(&account.owner) {
118 if let Ok(token_account) = TokenAccount::unpack(&account.data) {
119 let mint = self
120 .client
121 .get_account_with_commitment(&token_account.mint(), commitment_config)
122 .await
123 .map_err(|e| SurfpoolError::get_account(*pubkey, e))?;
124
125 result = Some(GetAccountResult::FoundTokenAccount(
126 (*pubkey, account.clone()),
127 (token_account.mint(), mint.value),
128 ));
129 };
130 } else if account.executable {
131 let program_data_address = get_program_data_address(pubkey);
132
133 let program_data = self
134 .client
135 .get_account_with_commitment(&program_data_address, commitment_config)
136 .await
137 .map_err(|e| SurfpoolError::get_account(*pubkey, e))?;
138
139 result = Some(GetAccountResult::FoundProgramAccount(
140 (*pubkey, account.clone()),
141 (program_data_address, program_data.value),
142 ));
143 }
144
145 result.unwrap_or(GetAccountResult::FoundAccount(
146 *pubkey, account,
147 true,
149 ))
150 }
151 None => GetAccountResult::None(*pubkey),
152 };
153 #[cfg(feature = "prometheus")]
154 if let Some(m) = crate::telemetry::metrics() {
155 m.record_remote_fetch(fetch_start.elapsed().as_millis() as u64);
156 }
157 Ok(result)
158 }
159
160 pub async fn get_multiple_accounts(
161 &self,
162 pubkeys: &[Pubkey],
163 commitment_config: CommitmentConfig,
164 ) -> SurfpoolResult<Vec<GetAccountResult>> {
165 #[cfg(feature = "prometheus")]
166 let fetch_start = std::time::Instant::now();
167
168 let remote_accounts = self
169 .client
170 .get_multiple_accounts(pubkeys)
171 .await
172 .map_err(SurfpoolError::get_multiple_accounts)?;
173 debug!("Fetched {:?} accounts from remote", pubkeys);
174 debug!(
175 "Found accounts for pubkeys: {:#?}",
176 remote_accounts
177 .iter()
178 .zip(pubkeys)
179 .filter_map(|(account, pubkey)| if account.is_some() {
180 Some(pubkey)
181 } else {
182 None
183 })
184 .collect::<Vec<&Pubkey>>()
185 );
186 let mut results_map: HashMap<Pubkey, GetAccountResult> = HashMap::new();
187 let mut mint_accounts_src: Vec<(Pubkey, Account, Pubkey)> = vec![];
188 let mut program_accounts_src: Vec<(Pubkey, Account, Pubkey)> = vec![];
189 for (pubkey, remote_account) in pubkeys.iter().zip(remote_accounts) {
190 if let Some(remote_account) = remote_account {
191 if is_supported_token_program(&remote_account.owner) {
192 if let Ok(token_account) = TokenAccount::unpack(&remote_account.data) {
193 mint_accounts_src.push((*pubkey, remote_account, token_account.mint()));
194 } else {
195 results_map.insert(
196 *pubkey,
197 GetAccountResult::FoundAccount(
198 *pubkey,
199 remote_account,
200 true,
202 ),
203 );
204 }
205 } else if remote_account.executable {
206 let program_data_address = get_program_data_address(pubkey);
207 program_accounts_src.push((*pubkey, remote_account, program_data_address));
208 } else {
209 results_map.insert(
210 *pubkey,
211 GetAccountResult::FoundAccount(
212 *pubkey,
213 remote_account,
214 true,
216 ),
217 );
218 }
219 } else {
220 results_map.insert(*pubkey, GetAccountResult::None(*pubkey));
221 }
222 }
223
224 debug!(
225 "Identified {} mint accounts and {} program accounts to fetch for remote accounts",
226 mint_accounts_src.len(),
227 program_accounts_src.len()
228 );
229
230 if !(mint_accounts_src.is_empty() && program_accounts_src.is_empty()) {
231 let mint_acc_src_len = mint_accounts_src.len();
232 let mut account_buffer = mint_accounts_src.clone();
233 account_buffer.extend_from_slice(&program_accounts_src);
234
235 let account_pubkeys: Vec<Pubkey> = account_buffer.iter().map(|p| p.2).collect();
236
237 let binding_remote_accounts = self
238 .client
239 .get_multiple_accounts_with_commitment(&account_pubkeys, commitment_config)
240 .await
241 .map_err(SurfpoolError::get_multiple_accounts)?
242 .value;
243
244 debug!(
245 "Fetched {} additional accounts from remote",
246 binding_remote_accounts.len()
247 );
248 debug!(
249 "Found additional accounts for pubkeys: {:#?}",
250 binding_remote_accounts
251 .iter()
252 .zip(account_pubkeys)
253 .filter_map(|(account, pubkey)| if account.is_some() {
254 Some(pubkey)
255 } else {
256 None
257 })
258 .collect::<Vec<Pubkey>>()
259 );
260
261 for (index, remote_account) in binding_remote_accounts.iter().enumerate() {
262 if index < mint_acc_src_len {
263 results_map.insert(
265 account_buffer[index].0,
266 GetAccountResult::FoundTokenAccount(
267 (account_buffer[index].0, account_buffer[index].1.clone()),
268 (account_buffer[index].2, remote_account.clone()),
269 ),
270 );
271 } else {
272 results_map.insert(
273 account_buffer[index].0,
274 GetAccountResult::FoundProgramAccount(
275 (account_buffer[index].0, account_buffer[index].1.clone()),
276 (account_buffer[index].2, remote_account.clone()),
277 ),
278 );
279 }
280 }
281 }
282 #[cfg(feature = "prometheus")]
283 if let Some(m) = crate::telemetry::metrics() {
284 m.record_remote_fetch(fetch_start.elapsed().as_millis() as u64);
285 }
286 Ok(pubkeys
287 .iter()
288 .map(|pk| {
289 results_map
290 .remove(pk)
291 .unwrap_or(GetAccountResult::None(*pk))
292 })
293 .collect())
294 }
295
296 pub async fn get_transaction(
297 &self,
298 signature: Signature,
299 config: RpcTransactionConfig,
300 latest_absolute_slot: u64,
301 ) -> GetTransactionResult {
302 match self
303 .client
304 .get_transaction_with_config(&signature, config)
305 .await
306 {
307 Ok(tx) => GetTransactionResult::found_transaction(signature, tx, latest_absolute_slot),
308 Err(_) => GetTransactionResult::None(signature),
309 }
310 }
311
312 pub async fn get_token_accounts_by_owner(
313 &self,
314 owner: Pubkey,
315 filter: &TokenAccountsFilter,
316 config: &RpcAccountInfoConfig,
317 ) -> SurfpoolResult<Vec<RpcKeyedAccount>> {
318 let token_account_filter = match filter {
319 TokenAccountsFilter::Mint(mint) => RpcTokenAccountsFilter::Mint(mint.to_string()),
320 TokenAccountsFilter::ProgramId(program_id) => {
321 RpcTokenAccountsFilter::ProgramId(program_id.to_string())
322 }
323 };
324
325 let res: RpcResult<Vec<RpcKeyedAccount>> = self
328 .client
329 .send(
330 RpcRequest::GetTokenAccountsByOwner,
331 json!([owner.to_string(), token_account_filter, config]),
332 )
333 .await;
334 res.map_err(|e| SurfpoolError::get_token_accounts(owner, filter, e))
335 .map(|res| res.value)
336 }
337
338 pub async fn get_token_largest_accounts(
339 &self,
340 mint: &Pubkey,
341 commitment_config: CommitmentConfig,
342 ) -> SurfpoolResult<Vec<RpcTokenAccountBalance>> {
343 self.client
344 .get_token_largest_accounts_with_commitment(mint, commitment_config)
345 .await
346 .map(|response| response.value)
347 .map_err(|e| SurfpoolError::get_token_largest_accounts(*mint, e))
348 }
349
350 pub async fn get_token_accounts_by_delegate(
351 &self,
352 delegate: Pubkey,
353 filter: &TokenAccountsFilter,
354 config: &RpcAccountInfoConfig,
355 ) -> SurfpoolResult<Vec<RpcKeyedAccount>> {
356 if let TokenAccountsFilter::ProgramId(program_id) = &filter {
358 if !is_supported_token_program(program_id) {
359 return Err(SurfpoolError::unsupported_token_program(*program_id));
360 }
361 }
362
363 let token_account_filter = match &filter {
364 TokenAccountsFilter::Mint(mint) => RpcTokenAccountsFilter::Mint(mint.to_string()),
365 TokenAccountsFilter::ProgramId(program_id) => {
366 RpcTokenAccountsFilter::ProgramId(program_id.to_string())
367 }
368 };
369
370 let res: RpcResult<Vec<RpcKeyedAccount>> = self
371 .client
372 .send(
373 RpcRequest::GetTokenAccountsByDelegate,
374 json!([delegate.to_string(), token_account_filter, config]),
375 )
376 .await;
377
378 res.map_err(|e| SurfpoolError::get_token_accounts_by_delegate_error(delegate, filter, e))
379 .map(|res| res.value)
380 }
381
382 pub async fn get_program_accounts(
383 &self,
384 program_id: &Pubkey,
385 account_config: RpcAccountInfoConfig,
386 filters: Option<Vec<RpcFilterType>>,
387 ) -> SurfpoolResult<RemoteRpcResult<Vec<(Pubkey, UiAccount)>>> {
388 handle_remote_rpc(|| async {
389 self.client
390 .get_program_ui_accounts_with_config(
391 program_id,
392 RpcProgramAccountsConfig {
393 filters,
394 with_context: Some(false),
395 account_config,
396 ..Default::default()
397 },
398 )
399 .await
400 .map_err(|e| SurfpoolError::get_program_accounts(*program_id, e))
401 })
402 .await
403 }
404
405 pub async fn get_largest_accounts(
406 &self,
407 config: Option<RpcLargestAccountsConfig>,
408 ) -> SurfpoolResult<RemoteRpcResult<Vec<RpcAccountBalance>>> {
409 handle_remote_rpc(|| async {
410 self.client
411 .get_largest_accounts_with_config(config.unwrap_or_default())
412 .await
413 .map(|res| res.value)
414 .map_err(SurfpoolError::get_largest_accounts)
415 })
416 .await
417 }
418
419 pub async fn get_genesis_hash(&self) -> SurfpoolResult<Hash> {
420 self.client.get_genesis_hash().await.map_err(Into::into)
421 }
422
423 pub async fn get_signatures_for_address(
424 &self,
425 pubkey: &Pubkey,
426 config: Option<&RpcSignaturesForAddressConfig>,
427 ) -> SurfpoolResult<Vec<RpcConfirmedTransactionStatusWithSignature>> {
428 let c = match config {
429 Some(c) => GetConfirmedSignaturesForAddress2Config {
430 before: c
431 .before
432 .as_deref()
433 .and_then(|s| Signature::from_str(&s).ok()),
434 commitment: c.commitment,
435 limit: c.limit,
436 until: c
437 .until
438 .as_deref()
439 .and_then(|s| Signature::from_str(&s).ok()),
440 },
441 _ => GetConfirmedSignaturesForAddress2Config::default(),
442 };
443 self.client
444 .get_signatures_for_address_with_config(pubkey, c)
445 .await
446 .map_err(SurfpoolError::get_signatures_for_address)
447 }
448
449 pub async fn get_block(
450 &self,
451 slot: &Slot,
452 config: RpcBlockConfig,
453 ) -> SurfpoolResult<UiConfirmedBlock> {
454 self.client
455 .get_block_with_config(*slot, config)
456 .await
457 .map_err(|e| SurfpoolError::get_block(e, *slot))
458 }
459}
460
461pub async fn handle_remote_rpc<T, E, F, Fut>(fut: F) -> Result<RemoteRpcResult<T>, E>
466where
467 F: FnOnce() -> Fut,
468 Fut: std::future::Future<Output = Result<T, E>>,
469 E: std::fmt::Display,
470{
471 match fut().await {
472 Ok(val) => Ok(RemoteRpcResult::Ok(val)),
473 Err(e) if is_method_not_supported_error(&e) => Ok(RemoteRpcResult::MethodNotSupported),
474 Err(e) => Err(e),
475 }
476}