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