surfpool_core/surfnet/
locker.rs

1use std::{
2    collections::{BTreeMap, HashSet},
3    sync::Arc,
4};
5
6use bincode::serialized_size;
7use crossbeam_channel::{Receiver, Sender};
8use itertools::Itertools;
9use litesvm::types::{FailedTransactionMetadata, SimulatedTransactionInfo, TransactionResult};
10use solana_account::{Account, ReadableAccount};
11use solana_account_decoder::{
12    UiAccount, UiAccountEncoding,
13    parse_bpf_loader::{BpfUpgradeableLoaderAccountType, UiProgram, parse_bpf_upgradeable_loader},
14    parse_token::UiTokenAmount,
15};
16use solana_address_lookup_table_interface::state::AddressLookupTable;
17use solana_client::{
18    rpc_config::{
19        RpcAccountInfoConfig, RpcBlockConfig, RpcLargestAccountsConfig, RpcLargestAccountsFilter,
20        RpcSignaturesForAddressConfig, RpcTransactionConfig, RpcTransactionLogsFilter,
21    },
22    rpc_filter::RpcFilterType,
23    rpc_request::TokenAccountsFilter,
24    rpc_response::{
25        RpcAccountBalance, RpcConfirmedTransactionStatusWithSignature, RpcKeyedAccount,
26        RpcLogsResponse, RpcTokenAccountBalance,
27    },
28};
29use solana_clock::Slot;
30use solana_commitment_config::{CommitmentConfig, CommitmentLevel};
31use solana_epoch_info::EpochInfo;
32use solana_hash::Hash;
33use solana_message::{
34    SimpleAddressLoader, VersionedMessage,
35    v0::{LoadedAddresses, MessageAddressTableLookup},
36};
37use solana_pubkey::Pubkey;
38use solana_rpc_client_api::response::SlotInfo;
39use solana_sdk::{
40    bpf_loader_upgradeable::{UpgradeableLoaderState, get_program_data_address},
41    transaction::{SanitizedTransaction, VersionedTransaction},
42};
43use solana_signature::Signature;
44use solana_transaction_error::TransactionError;
45use solana_transaction_status::{
46    EncodedConfirmedTransactionWithStatusMeta,
47    TransactionConfirmationStatus as SolanaTransactionConfirmationStatus, UiConfirmedBlock,
48    UiTransactionEncoding,
49};
50use surfpool_types::{
51    ComputeUnitsEstimationResult, ProfileResult, ProfileState, SimnetEvent,
52    TransactionConfirmationStatus, TransactionStatusEvent, UuidOrSignature,
53};
54use tokio::sync::RwLock;
55use uuid::Uuid;
56
57use super::{
58    AccountFactory, GetAccountResult, GetTransactionResult, GeyserEvent, SignatureSubscriptionType,
59    SurfnetSvm,
60    remote::{SomeRemoteCtx, SurfnetRemoteClient},
61};
62use crate::{
63    error::{SurfpoolError, SurfpoolResult},
64    rpc::utils::{convert_transaction_metadata_from_canonical, verify_pubkey},
65    surfnet::FINALIZATION_SLOT_THRESHOLD,
66    types::{
67        GeyserAccountUpdate, RemoteRpcResult, SurfnetTransactionStatus, TransactionWithStatusMeta,
68    },
69};
70
71pub struct SvmAccessContext<T> {
72    pub slot: Slot,
73    pub latest_epoch_info: EpochInfo,
74    pub latest_blockhash: Hash,
75    pub inner: T,
76}
77
78impl<T> SvmAccessContext<T> {
79    pub fn new(slot: Slot, latest_epoch_info: EpochInfo, latest_blockhash: Hash, inner: T) -> Self {
80        Self {
81            slot,
82            latest_blockhash,
83            latest_epoch_info,
84            inner,
85        }
86    }
87
88    pub fn inner(&self) -> &T {
89        &self.inner
90    }
91
92    pub fn with_new_value<N>(&self, inner: N) -> SvmAccessContext<N> {
93        SvmAccessContext {
94            slot: self.slot,
95            latest_blockhash: self.latest_blockhash,
96            latest_epoch_info: self.latest_epoch_info.clone(),
97            inner,
98        }
99    }
100}
101
102pub type SurfpoolContextualizedResult<T> = SurfpoolResult<SvmAccessContext<T>>;
103
104pub struct SurfnetSvmLocker(pub Arc<RwLock<SurfnetSvm>>);
105
106impl Clone for SurfnetSvmLocker {
107    fn clone(&self) -> Self {
108        Self(self.0.clone())
109    }
110}
111
112/// Functions for reading and writing to the underlying SurfnetSvm instance
113impl SurfnetSvmLocker {
114    /// Executes a read-only operation on the underlying `SurfnetSvm` by acquiring a blocking read lock.
115    /// Accepts a closure that receives a shared reference to `SurfnetSvm` and returns a value.
116    ///
117    /// # Returns
118    /// The result produced by the closure.
119    pub fn with_svm_reader<T, F>(&self, reader: F) -> T
120    where
121        F: Fn(&SurfnetSvm) -> T + Send + Sync,
122    {
123        let read_lock = self.0.clone();
124        tokio::task::block_in_place(move || {
125            let read_guard = read_lock.blocking_read();
126            reader(&read_guard)
127        })
128    }
129
130    /// Executes a read-only operation and wraps the result in `SvmAccessContext`, capturing
131    /// slot, epoch info, and blockhash along with the closure's result.
132    fn with_contextualized_svm_reader<T, F>(&self, reader: F) -> SvmAccessContext<T>
133    where
134        F: Fn(&SurfnetSvm) -> T + Send + Sync,
135        T: Send + 'static,
136    {
137        let read_lock = self.0.clone();
138        tokio::task::block_in_place(move || {
139            let read_guard = read_lock.blocking_read();
140            let res = reader(&read_guard);
141
142            SvmAccessContext::new(
143                read_guard.get_latest_absolute_slot(),
144                read_guard.latest_epoch_info(),
145                read_guard.latest_blockhash(),
146                res,
147            )
148        })
149    }
150
151    /// Executes a write operation on the underlying `SurfnetSvm` by acquiring a blocking write lock.
152    /// Accepts a closure that receives a mutable reference to `SurfnetSvm` and returns a value.
153    ///
154    /// # Returns
155    /// The result produced by the closure.
156    pub fn with_svm_writer<T, F>(&self, writer: F) -> T
157    where
158        F: Fn(&mut SurfnetSvm) -> T + Send + Sync,
159        T: Send + 'static,
160    {
161        let write_lock = self.0.clone();
162        tokio::task::block_in_place(move || {
163            let mut write_guard = write_lock.blocking_write();
164            writer(&mut write_guard)
165        })
166    }
167}
168
169/// Functions for creating and initializing the underlying SurfnetSvm instance
170impl SurfnetSvmLocker {
171    /// Constructs a new `SurfnetSvmLocker` wrapping the given `SurfnetSvm` instance.
172    pub fn new(svm: SurfnetSvm) -> Self {
173        Self(Arc::new(RwLock::new(svm)))
174    }
175
176    /// Initializes the locked `SurfnetSvm` by fetching or defaulting epoch info,
177    /// then calling its `initialize` method. Returns the epoch info on success.
178    pub async fn initialize(
179        &self,
180        remote_ctx: &Option<SurfnetRemoteClient>,
181    ) -> SurfpoolResult<EpochInfo> {
182        let epoch_info = if let Some(remote_client) = remote_ctx {
183            remote_client.get_epoch_info().await?
184        } else {
185            EpochInfo {
186                epoch: 0,
187                slot_index: 0,
188                slots_in_epoch: 0,
189                absolute_slot: 0,
190                block_height: 0,
191                transaction_count: None,
192            }
193        };
194
195        self.with_svm_writer(|svm_writer| {
196            svm_writer.initialize(epoch_info.clone(), remote_ctx);
197        });
198        Ok(epoch_info)
199    }
200}
201
202/// Functions for getting accounts from the underlying SurfnetSvm instance or remote client
203impl SurfnetSvmLocker {
204    /// Retrieves a local account from the SVM cache, returning a contextualized result.
205    pub fn get_account_local(&self, pubkey: &Pubkey) -> SvmAccessContext<GetAccountResult> {
206        self.with_contextualized_svm_reader(|svm_reader| {
207            match svm_reader.inner.get_account(pubkey) {
208                Some(account) => GetAccountResult::FoundAccount(
209                    *pubkey, account,
210                    // mark as not an account that should be updated in the SVM, since this is a local read and it already exists
211                    false,
212                ),
213                None => GetAccountResult::None(*pubkey),
214            }
215        })
216    }
217
218    /// Attempts local retrieval, then fetches from remote if missing, returning a contextualized result.
219    pub async fn get_account_local_then_remote(
220        &self,
221        client: &SurfnetRemoteClient,
222        pubkey: &Pubkey,
223        commitment_config: CommitmentConfig,
224    ) -> SurfpoolContextualizedResult<GetAccountResult> {
225        let result = self.get_account_local(pubkey);
226
227        if result.inner.is_none() {
228            let remote_account = client.get_account(pubkey, commitment_config).await?;
229            Ok(result.with_new_value(remote_account))
230        } else {
231            Ok(result)
232        }
233    }
234
235    /// Retrieves an account, using local or remote based on context, applying a default factory if provided.
236    pub async fn get_account(
237        &self,
238        remote_ctx: &Option<(SurfnetRemoteClient, CommitmentConfig)>,
239        pubkey: &Pubkey,
240        factory: Option<AccountFactory>,
241    ) -> SurfpoolContextualizedResult<GetAccountResult> {
242        let result = if let Some((remote_client, commitment_config)) = remote_ctx {
243            self.get_account_local_then_remote(remote_client, pubkey, *commitment_config)
244                .await?
245        } else {
246            self.get_account_local(pubkey)
247        };
248
249        match (&result.inner, factory) {
250            (&GetAccountResult::None(_), Some(factory)) => {
251                let default = factory(self.clone());
252                Ok(result.with_new_value(default))
253            }
254            _ => Ok(result),
255        }
256    }
257
258    /// Retrieves multiple accounts from local cache, returning a contextualized result.
259    pub fn get_multiple_accounts_local(
260        &self,
261        pubkeys: &[Pubkey],
262    ) -> SvmAccessContext<Vec<GetAccountResult>> {
263        self.with_contextualized_svm_reader(|svm_reader| {
264            let mut accounts = vec![];
265
266            for pubkey in pubkeys {
267                let res = match svm_reader.inner.get_account(pubkey) {
268                    Some(account) => GetAccountResult::FoundAccount(
269                        *pubkey, account,
270                        // mark as not an account that should be updated in the SVM, since this is a local read and it already exists
271                        false,
272                    ),
273                    None => GetAccountResult::None(*pubkey),
274                };
275                accounts.push(res);
276            }
277            accounts
278        })
279    }
280
281    /// Retrieves multiple accounts, fetching missing ones from remote, returning a contextualized result.
282    pub async fn get_multiple_accounts_local_then_remote(
283        &self,
284        client: &SurfnetRemoteClient,
285        pubkeys: &[Pubkey],
286        commitment_config: CommitmentConfig,
287    ) -> SurfpoolContextualizedResult<Vec<GetAccountResult>> {
288        let SvmAccessContext {
289            slot,
290            latest_epoch_info,
291            latest_blockhash,
292            inner: local_results,
293        } = self.get_multiple_accounts_local(pubkeys);
294
295        let mut missing_accounts = vec![];
296        let mut found_accounts = vec![];
297        for result in local_results.into_iter() {
298            if let GetAccountResult::None(pubkey) = result {
299                missing_accounts.push(pubkey)
300            } else {
301                found_accounts.push(result.clone());
302            }
303        }
304
305        if missing_accounts.is_empty() {
306            return Ok(SvmAccessContext::new(
307                slot,
308                latest_epoch_info,
309                latest_blockhash,
310                found_accounts,
311            ));
312        }
313
314        let mut remote_results = client
315            .get_multiple_accounts(&missing_accounts, commitment_config)
316            .await?;
317        let mut combined_results = found_accounts.clone();
318        combined_results.append(&mut remote_results);
319
320        Ok(SvmAccessContext::new(
321            slot,
322            latest_epoch_info,
323            latest_blockhash,
324            combined_results,
325        ))
326    }
327
328    /// Retrieves multiple accounts, using local or remote context and applying factory defaults if provided.
329    pub async fn get_multiple_accounts(
330        &self,
331        remote_ctx: &Option<(SurfnetRemoteClient, CommitmentConfig)>,
332        pubkeys: &[Pubkey],
333        factory: Option<AccountFactory>,
334    ) -> SurfpoolContextualizedResult<Vec<GetAccountResult>> {
335        let results = if let Some((remote_client, commitment_config)) = remote_ctx {
336            self.get_multiple_accounts_local_then_remote(remote_client, pubkeys, *commitment_config)
337                .await?
338        } else {
339            self.get_multiple_accounts_local(pubkeys)
340        };
341
342        let mut combined = Vec::with_capacity(results.inner.len());
343        for result in results.inner.clone() {
344            match (&result, &factory) {
345                (&GetAccountResult::None(_), Some(factory)) => {
346                    let default = factory(self.clone());
347                    combined.push(default);
348                }
349                _ => combined.push(result),
350            }
351        }
352        Ok(results.with_new_value(combined))
353    }
354
355    /// Retrieves largest accounts from local cache, returning a contextualized result.
356    pub fn get_largest_accounts_local(
357        &self,
358        config: RpcLargestAccountsConfig,
359    ) -> SvmAccessContext<Vec<RpcAccountBalance>> {
360        self.with_contextualized_svm_reader(|svm_reader| {
361            let non_circulating_accounts: Vec<_> = svm_reader
362                .non_circulating_accounts
363                .iter()
364                .flat_map(|acct| verify_pubkey(acct))
365                .collect();
366
367            let ordered_accounts = svm_reader
368                .accounts_registry
369                .iter()
370                .sorted_by(|a, b| b.1.lamports.cmp(&a.1.lamports))
371                .collect::<Vec<_>>();
372            let ordered_filtered_accounts = match config.filter {
373                Some(RpcLargestAccountsFilter::NonCirculating) => ordered_accounts
374                    .into_iter()
375                    .filter(|(pubkey, _)| non_circulating_accounts.contains(pubkey))
376                    .collect::<Vec<_>>(),
377                Some(RpcLargestAccountsFilter::Circulating) => ordered_accounts
378                    .into_iter()
379                    .filter(|(pubkey, _)| !non_circulating_accounts.contains(pubkey))
380                    .collect::<Vec<_>>(),
381                None => ordered_accounts,
382            };
383
384            ordered_filtered_accounts
385                .iter()
386                .take(20)
387                .map(|(pubkey, account)| RpcAccountBalance {
388                    address: pubkey.to_string(),
389                    lamports: account.lamports,
390                })
391                .collect()
392        })
393    }
394
395    pub async fn get_largest_accounts_local_then_remote(
396        &self,
397        client: &SurfnetRemoteClient,
398        config: RpcLargestAccountsConfig,
399        commitment_config: CommitmentConfig,
400    ) -> SurfpoolContextualizedResult<Vec<RpcAccountBalance>> {
401        // get all non-circulating and circulating pubkeys from the remote client first,
402        // and insert them locally
403        {
404            let remote_non_circulating_pubkeys_result = client
405                .get_largest_accounts(Some(RpcLargestAccountsConfig {
406                    filter: Some(RpcLargestAccountsFilter::NonCirculating),
407                    ..config.clone()
408                }))
409                .await?;
410
411            let (mut remote_non_circulating_pubkeys, mut remote_circulating_pubkeys) =
412                match remote_non_circulating_pubkeys_result {
413                    RemoteRpcResult::Ok(non_circulating_accounts) => {
414                        let remote_circulating_pubkeys_result = client
415                            .get_largest_accounts(Some(RpcLargestAccountsConfig {
416                                filter: Some(RpcLargestAccountsFilter::Circulating),
417                                ..config.clone()
418                            }))
419                            .await?;
420
421                        let remote_circulating_pubkeys = match remote_circulating_pubkeys_result {
422                            RemoteRpcResult::Ok(circulating_accounts) => circulating_accounts,
423                            RemoteRpcResult::MethodNotSupported => {
424                                unreachable!()
425                            }
426                        };
427                        (
428                            non_circulating_accounts
429                                .iter()
430                                .map(|account_balance| verify_pubkey(&account_balance.address))
431                                .collect::<SurfpoolResult<Vec<_>>>()?,
432                            remote_circulating_pubkeys
433                                .iter()
434                                .map(|account_balance| verify_pubkey(&account_balance.address))
435                                .collect::<SurfpoolResult<Vec<_>>>()?,
436                        )
437                    }
438                    RemoteRpcResult::MethodNotSupported => {
439                        let tx = self.simnet_events_tx();
440                        let _ = tx.send(SimnetEvent::warn("The `getLargestAccounts` method was sent to the remote RPC, but this method isn't supported by your RPC provider. Only local accounts will be returned."));
441                        (vec![], vec![])
442                    }
443                };
444
445            let mut combined = Vec::with_capacity(
446                remote_non_circulating_pubkeys.len() + remote_circulating_pubkeys.len(),
447            );
448            combined.append(&mut remote_non_circulating_pubkeys);
449            combined.append(&mut remote_circulating_pubkeys);
450
451            let get_account_results = self
452                .get_multiple_accounts_local_then_remote(client, &combined, commitment_config)
453                .await?
454                .inner;
455
456            self.write_multiple_account_updates(&get_account_results);
457        }
458
459        // now that our local cache is aware of all large remote accounts, we can get the largest accounts locally
460        // and filter according to the config
461        Ok(self.get_largest_accounts_local(config))
462    }
463
464    pub async fn get_largest_accounts(
465        &self,
466        remote_ctx: &Option<(SurfnetRemoteClient, CommitmentConfig)>,
467        config: RpcLargestAccountsConfig,
468    ) -> SurfpoolContextualizedResult<Vec<RpcAccountBalance>> {
469        let results = if let Some((remote_client, commitment_config)) = remote_ctx {
470            self.get_largest_accounts_local_then_remote(remote_client, config, *commitment_config)
471                .await?
472        } else {
473            self.get_largest_accounts_local(config)
474        };
475
476        Ok(results)
477    }
478
479    pub fn account_to_rpc_keyed_account<T: ReadableAccount + Send + Sync>(
480        &self,
481        pubkey: &Pubkey,
482        account: &T,
483        config: &RpcAccountInfoConfig,
484        token_mint: Option<Pubkey>,
485    ) -> RpcKeyedAccount {
486        self.with_svm_reader(|svm_reader| {
487            svm_reader.account_to_rpc_keyed_account(pubkey, account, config, token_mint)
488        })
489    }
490}
491
492/// Get signatures for Addresses
493impl SurfnetSvmLocker {
494    pub fn get_signatures_for_address_local(
495        &self,
496        pubkey: &Pubkey,
497        config: Option<RpcSignaturesForAddressConfig>,
498    ) -> SvmAccessContext<Vec<RpcConfirmedTransactionStatusWithSignature>> {
499        self.with_contextualized_svm_reader(|svm_reader| {
500            let current_slot = svm_reader.get_latest_absolute_slot();
501
502            let config = config.clone().unwrap_or_default();
503            let limit = config.limit.unwrap_or(1000);
504
505            let mut before_slot = None;
506            let mut until_slot = None;
507
508            let sigs: Vec<_> = svm_reader
509                .transactions
510                .iter()
511                .filter_map(|(sig, status)| {
512                    let TransactionWithStatusMeta {
513                        slot,
514                        transaction,
515                        meta,
516                    } = status.expect_processed();
517
518                    if *slot < config.clone().min_context_slot.unwrap_or_default() {
519                        return None;
520                    }
521
522                    if Some(sig.to_string()) == config.clone().before {
523                        before_slot = Some(*slot)
524                    }
525
526                    if Some(sig.to_string()) == config.clone().until {
527                        until_slot = Some(*slot)
528                    }
529
530                    // Check if the pubkey is a signer
531                    let is_signer = transaction
532                        .message
533                        .static_account_keys()
534                        .iter()
535                        .position(|pk| pk == pubkey)
536                        .map(|i| transaction.message.is_signer(i))
537                        .unwrap_or(false);
538
539                    if !is_signer {
540                        return None;
541                    }
542
543                    // Determine confirmation status
544                    let confirmation_status = match current_slot {
545                        cs if cs == *slot => SolanaTransactionConfirmationStatus::Processed,
546                        cs if cs < slot + FINALIZATION_SLOT_THRESHOLD => {
547                            SolanaTransactionConfirmationStatus::Confirmed
548                        }
549                        _ => SolanaTransactionConfirmationStatus::Finalized,
550                    };
551
552                    Some(RpcConfirmedTransactionStatusWithSignature {
553                        err: match &meta.status {
554                            Ok(_) => None,
555                            Err(e) => Some(e.clone()),
556                        },
557                        slot: *slot,
558                        memo: None,
559                        block_time: None,
560                        confirmation_status: Some(confirmation_status),
561                        signature: sig.to_string(),
562                    })
563                })
564                .collect();
565
566            sigs.into_iter()
567                .filter(|sig| {
568                    if config.before.is_none() && config.until.is_none() {
569                        return true;
570                    }
571
572                    if config.before.is_some() && before_slot >= Some(sig.slot) {
573                        return true;
574                    }
575
576                    if config.until.is_some() && until_slot <= Some(sig.slot) {
577                        return true;
578                    }
579
580                    false
581                })
582                .take(limit)
583                .collect()
584        })
585    }
586
587    pub async fn get_signatures_for_address_local_then_remote(
588        &self,
589        client: &SurfnetRemoteClient,
590        pubkey: &Pubkey,
591        config: Option<RpcSignaturesForAddressConfig>,
592    ) -> SurfpoolContextualizedResult<Vec<RpcConfirmedTransactionStatusWithSignature>> {
593        let results = self.get_signatures_for_address_local(pubkey, config.clone());
594        let limit = config.clone().and_then(|c| c.limit).unwrap_or(1000);
595
596        let mut combined_results = results.inner.clone();
597        if combined_results.len() < limit {
598            let mut remote_results = client.get_signatures_for_address(pubkey, config).await?;
599            combined_results.append(&mut remote_results);
600        }
601
602        Ok(results.with_new_value(combined_results))
603    }
604
605    pub async fn get_signatures_for_address(
606        &self,
607        remote_ctx: &Option<(SurfnetRemoteClient, ())>,
608        pubkey: &Pubkey,
609        config: Option<RpcSignaturesForAddressConfig>,
610    ) -> SurfpoolContextualizedResult<Vec<RpcConfirmedTransactionStatusWithSignature>> {
611        let results = if let Some((remote_client, _)) = remote_ctx {
612            self.get_signatures_for_address_local_then_remote(remote_client, pubkey, config.clone())
613                .await?
614        } else {
615            self.get_signatures_for_address_local(pubkey, config)
616        };
617
618        Ok(results)
619    }
620}
621
622/// Functions for getting transactions from the underlying SurfnetSvm instance or remote client
623impl SurfnetSvmLocker {
624    /// Retrieves a transaction by signature, using local or remote based on context.
625    pub async fn get_transaction(
626        &self,
627        remote_ctx: &Option<SurfnetRemoteClient>,
628        signature: &Signature,
629        config: RpcTransactionConfig,
630    ) -> SurfpoolResult<GetTransactionResult> {
631        if let Some(remote_client) = remote_ctx {
632            self.get_transaction_local_then_remote(remote_client, signature, config)
633                .await
634        } else {
635            self.get_transaction_local(signature, &config)
636        }
637    }
638
639    /// Retrieves a transaction from local cache, returning a contextualized result.
640    pub fn get_transaction_local(
641        &self,
642        signature: &Signature,
643        config: &RpcTransactionConfig,
644    ) -> SurfpoolResult<GetTransactionResult> {
645        self.with_svm_reader(|svm_reader| {
646            let latest_absolute_slot = svm_reader.get_latest_absolute_slot();
647
648            let Some(entry) = svm_reader.transactions.get(signature) else {
649                return Ok(GetTransactionResult::None(*signature));
650            };
651
652            let transaction_with_status_meta = entry.expect_processed();
653            let slot = transaction_with_status_meta.slot;
654            let block_time = svm_reader
655                .blocks
656                .get(&slot)
657                .map(|b| b.block_time)
658                .unwrap_or(0);
659            let encoded = transaction_with_status_meta.encode(
660                config.encoding.unwrap_or(UiTransactionEncoding::JsonParsed),
661                config.max_supported_transaction_version,
662                true,
663            )?;
664            Ok(GetTransactionResult::found_transaction(
665                *signature,
666                EncodedConfirmedTransactionWithStatusMeta {
667                    slot,
668                    transaction: encoded,
669                    block_time: Some(block_time),
670                },
671                latest_absolute_slot,
672            ))
673        })
674    }
675
676    /// Retrieves a transaction locally then from remote if missing, returning a contextualized result.
677    pub async fn get_transaction_local_then_remote(
678        &self,
679        client: &SurfnetRemoteClient,
680        signature: &Signature,
681        config: RpcTransactionConfig,
682    ) -> SurfpoolResult<GetTransactionResult> {
683        let local_result = self.get_transaction_local(signature, &config)?;
684        let latest_absolute_slot = self.get_latest_absolute_slot();
685        if local_result.is_none() {
686            Ok(client
687                .get_transaction(*signature, config, latest_absolute_slot)
688                .await)
689        } else {
690            Ok(local_result)
691        }
692    }
693}
694
695/// Functions for simulating and processing transactions in the underlying SurfnetSvm instance
696impl SurfnetSvmLocker {
697    /// Simulates a transaction on the SVM, returning detailed info or failure metadata.
698    pub fn simulate_transaction(
699        &self,
700        transaction: VersionedTransaction,
701        sigverify: bool,
702    ) -> Result<SimulatedTransactionInfo, FailedTransactionMetadata> {
703        self.with_svm_reader(|svm_reader| {
704            svm_reader.simulate_transaction(transaction.clone(), sigverify)
705        })
706    }
707
708    /// Processes a transaction: verifies signatures, preflight sim, sends to SVM, and enqueues status events.
709    pub async fn process_transaction(
710        &self,
711        remote_ctx: &Option<SurfnetRemoteClient>,
712        transaction: VersionedTransaction,
713        status_tx: Sender<TransactionStatusEvent>,
714        skip_preflight: bool,
715    ) -> SurfpoolContextualizedResult<()> {
716        let remote_ctx = &remote_ctx.get_remote_ctx(CommitmentConfig::confirmed());
717        let (latest_absolute_slot, latest_epoch_info, latest_blockhash) =
718            self.with_svm_writer(|svm_writer| {
719                let latest_absolute_slot = svm_writer.get_latest_absolute_slot();
720                svm_writer.notify_signature_subscribers(
721                    SignatureSubscriptionType::received(),
722                    &transaction.signatures[0],
723                    latest_absolute_slot,
724                    None,
725                );
726                (
727                    latest_absolute_slot,
728                    svm_writer.latest_epoch_info(),
729                    svm_writer.latest_blockhash(),
730                )
731            });
732
733        let signature = transaction.signatures[0];
734
735        // find accounts that are needed for this transaction but are missing from the local
736        // svm cache, fetch them from the RPC, and insert them locally
737        let loaded_addresses = self
738            .get_loaded_addresses(remote_ctx, &transaction.message)
739            .await?;
740        let pubkeys_from_message = self
741            .get_pubkeys_from_message(&transaction.message, loaded_addresses.clone())
742            .clone();
743
744        let account_updates = self
745            .get_multiple_accounts(remote_ctx, &pubkeys_from_message, None)
746            .await?
747            .inner;
748
749        let pre_execution_capture = {
750            let mut capture = BTreeMap::new();
751            for account_update in account_updates.iter() {
752                self.snapshot_get_account_result(
753                    &mut capture,
754                    account_update.clone(),
755                    Some(UiAccountEncoding::JsonParsed),
756                );
757            }
758            capture
759        };
760
761        self.with_svm_writer(|svm_writer| {
762            for update in &account_updates {
763                svm_writer.write_account_update(update.clone());
764            }
765
766            let accounts_before = pubkeys_from_message
767                .iter()
768                .map(|p| svm_writer.inner.get_account(p))
769                .collect::<Vec<Option<Account>>>();
770
771            let token_accounts_before = pubkeys_from_message
772                .iter()
773                .enumerate()
774                .filter_map(|(i, p)| {
775                    svm_writer
776                        .token_accounts
777                        .get(&p)
778                        .cloned()
779                        .map(|a| (i, a))
780                        .clone()
781                })
782                .collect::<Vec<_>>();
783
784            let token_programs = token_accounts_before
785                .iter()
786                .map(|(i, _)| {
787                    svm_writer
788                        .accounts_registry
789                        .get(&pubkeys_from_message[*i])
790                        .unwrap()
791                        .owner
792                })
793                .collect::<Vec<_>>()
794                .clone();
795            let mut logs = vec![];
796
797            // if not skipping preflight, simulate the transaction
798            if !skip_preflight {
799                match svm_writer.simulate_transaction(transaction.clone(), true) {
800                    Ok(_) => {}
801                    Err(res) => {
802                        let _ = svm_writer
803                            .simnet_events_tx
804                            .try_send(SimnetEvent::error(format!(
805                                "Transaction simulation failed: {}",
806                                res.err
807                            )));
808                        let meta = convert_transaction_metadata_from_canonical(&res.meta);
809                        logs = meta.logs.clone();
810                        let _ = status_tx.try_send(TransactionStatusEvent::SimulationFailure((
811                            res.err.clone(),
812                            meta,
813                        )));
814                        svm_writer.notify_signature_subscribers(
815                            SignatureSubscriptionType::processed(),
816                            &signature,
817                            latest_absolute_slot,
818                            Some(res.err.clone()),
819                        );
820                        svm_writer.notify_logs_subscribers(
821                            &signature,
822                            Some(res.err),
823                            logs,
824                            CommitmentLevel::Processed,
825                        );
826                        return Ok::<(), SurfpoolError>(());
827                    }
828                }
829            }
830            // send the transaction to the SVM
831            let err = match svm_writer
832                .send_transaction(transaction.clone(), false /* cu_analysis_enabled */)
833            {
834                Ok(res) => {
835                    logs = res.logs.clone();
836                    let accounts_after = pubkeys_from_message
837                        .iter()
838                        .map(|p| svm_writer.inner.get_account(p))
839                        .collect::<Vec<Option<Account>>>();
840
841                    let sanitized_transaction = SanitizedTransaction::try_create(
842                        transaction.clone(),
843                        transaction.message.hash(),
844                        Some(false),
845                        if let Some(loaded_addresses) = &loaded_addresses {
846                            SimpleAddressLoader::Enabled(loaded_addresses.clone())
847                        } else {
848                            SimpleAddressLoader::Disabled
849                        },
850                        &HashSet::new(), // todo: provide reserved account keys
851                    )
852                    .ok();
853
854                    for (pubkey, (before, after)) in pubkeys_from_message
855                        .iter()
856                        .zip(accounts_before.clone().iter().zip(accounts_after.clone()))
857                    {
858                        if before.ne(&after) {
859                            if let Some(after) = &after {
860                                svm_writer.update_account_registries(pubkey, after);
861                                let write_version = svm_writer.increment_write_version();
862
863                                if let Some(sanitized_transaction) = sanitized_transaction.clone() {
864                                    let _ = svm_writer.geyser_events_tx.send(
865                                        GeyserEvent::UpdateAccount(GeyserAccountUpdate::new(
866                                            *pubkey,
867                                            after.clone(),
868                                            svm_writer.get_latest_absolute_slot(),
869                                            sanitized_transaction.clone(),
870                                            write_version,
871                                        )),
872                                    );
873                                }
874                            }
875                            svm_writer
876                                .notify_account_subscribers(pubkey, &after.unwrap_or_default());
877                        }
878                    }
879
880                    let mut token_accounts_after = vec![];
881                    let mut post_execution_capture = BTreeMap::new();
882                    for (i, (pubkey, account)) in pubkeys_from_message
883                        .iter()
884                        .zip(accounts_after.iter())
885                        .enumerate()
886                    {
887                        let token_account = svm_writer.token_accounts.get(&pubkey).cloned();
888
889                        let ui_account = if let Some(account) = account {
890                            let ui_account = svm_writer
891                                .account_to_rpc_keyed_account(
892                                    &pubkey,
893                                    account,
894                                    &RpcAccountInfoConfig {
895                                        encoding: Some(UiAccountEncoding::JsonParsed),
896                                        ..Default::default()
897                                    },
898                                    token_account.map(|a| a.mint()),
899                                )
900                                .account;
901                            Some(ui_account)
902                        } else {
903                            None
904                        };
905                        post_execution_capture.insert(*pubkey, ui_account);
906
907                        if let Some(token_account) = token_account {
908                            token_accounts_after.push((i, token_account));
909                        }
910                    }
911
912                    let token_mints = token_accounts_after
913                        .iter()
914                        .map(|(_, a)| {
915                            svm_writer
916                                .token_mints
917                                .get(&a.mint())
918                                .ok_or(SurfpoolError::token_mint_not_found(a.mint()))
919                                .cloned()
920                        })
921                        .collect::<Result<Vec<_>, SurfpoolError>>()?;
922
923                    let profile_result = ProfileResult::success(
924                        res.compute_units_consumed,
925                        res.logs.clone(),
926                        pre_execution_capture.clone(),
927                        post_execution_capture,
928                        latest_absolute_slot,
929                        Some(Uuid::new_v4()),
930                    );
931                    svm_writer.write_executed_profile_result(signature, profile_result);
932
933                    let transaction_meta = convert_transaction_metadata_from_canonical(&res);
934
935                    let transaction_with_status_meta = TransactionWithStatusMeta::new(
936                        svm_writer.get_latest_absolute_slot(),
937                        transaction.clone(),
938                        res,
939                        accounts_before,
940                        accounts_after,
941                        token_accounts_before,
942                        token_accounts_after,
943                        token_mints,
944                        token_programs,
945                        loaded_addresses.clone().unwrap_or_default(),
946                    );
947                    svm_writer.transactions.insert(
948                        transaction_meta.signature,
949                        SurfnetTransactionStatus::Processed(Box::new(
950                            transaction_with_status_meta.clone(),
951                        )),
952                    );
953
954                    let _ = svm_writer
955                        .simnet_events_tx
956                        .try_send(SimnetEvent::transaction_processed(transaction_meta, None));
957
958                    let _ = svm_writer
959                        .geyser_events_tx
960                        .send(GeyserEvent::NotifyTransaction(transaction_with_status_meta));
961
962                    let _ = status_tx.try_send(TransactionStatusEvent::Success(
963                        TransactionConfirmationStatus::Processed,
964                    ));
965                    svm_writer
966                        .transactions_queued_for_confirmation
967                        .push_back((transaction.clone(), status_tx.clone()));
968                    None
969                }
970                Err(res) => {
971                    let transaction_meta = convert_transaction_metadata_from_canonical(&res.meta);
972                    let _ = svm_writer
973                        .simnet_events_tx
974                        .try_send(SimnetEvent::error(format!(
975                            "Transaction execution failed: {}",
976                            res.err
977                        )));
978                    let _ = status_tx.try_send(TransactionStatusEvent::ExecutionFailure((
979                        res.err.clone(),
980                        transaction_meta,
981                    )));
982                    Some(res.err)
983                }
984            };
985
986            svm_writer.notify_signature_subscribers(
987                SignatureSubscriptionType::processed(),
988                &signature,
989                latest_absolute_slot,
990                err.clone(),
991            );
992            svm_writer.notify_logs_subscribers(&signature, err, logs, CommitmentLevel::Processed);
993            Ok(())
994        })?;
995
996        Ok(SvmAccessContext::new(
997            latest_absolute_slot,
998            latest_epoch_info,
999            latest_blockhash,
1000            (),
1001        ))
1002    }
1003}
1004
1005/// Functions for writing account updates to the underlying SurfnetSvm instance
1006impl SurfnetSvmLocker {
1007    /// Writes a single account update into the SVM state if present.
1008    pub fn write_account_update(&self, account_update: GetAccountResult) {
1009        if !account_update.requires_update() {
1010            return;
1011        }
1012
1013        self.with_svm_writer(move |svm_writer| {
1014            svm_writer.write_account_update(account_update.clone())
1015        })
1016    }
1017
1018    /// Writes multiple account updates into the SVM state when any are present.
1019    pub fn write_multiple_account_updates(&self, account_updates: &[GetAccountResult]) {
1020        if account_updates
1021            .iter()
1022            .all(|update| !update.requires_update())
1023        {
1024            return;
1025        }
1026
1027        self.with_svm_writer(move |svm_writer| {
1028            for update in account_updates {
1029                svm_writer.write_account_update(update.clone());
1030            }
1031        });
1032    }
1033}
1034
1035/// Token account related functions
1036impl SurfnetSvmLocker {
1037    /// Fetches all token accounts for an owner, returning remote results and missing pubkeys contexts.
1038    pub async fn get_token_accounts_by_owner(
1039        &self,
1040        remote_ctx: &Option<SurfnetRemoteClient>,
1041        owner: Pubkey,
1042        filter: &TokenAccountsFilter,
1043        config: &RpcAccountInfoConfig,
1044    ) -> SurfpoolContextualizedResult<Vec<RpcKeyedAccount>> {
1045        if let Some(remote_client) = remote_ctx {
1046            self.get_token_accounts_by_owner_local_then_remote(owner, filter, remote_client, config)
1047                .await
1048        } else {
1049            Ok(self.get_token_accounts_by_owner_local(owner, filter, config))
1050        }
1051    }
1052
1053    pub fn get_token_accounts_by_owner_local(
1054        &self,
1055        owner: Pubkey,
1056        filter: &TokenAccountsFilter,
1057        config: &RpcAccountInfoConfig,
1058    ) -> SvmAccessContext<Vec<RpcKeyedAccount>> {
1059        self.with_contextualized_svm_reader(|svm_reader| {
1060            svm_reader
1061                .get_parsed_token_accounts_by_owner(&owner)
1062                .iter()
1063                .filter_map(|(pubkey, token_account)| {
1064                    let account = svm_reader.accounts_registry.get(pubkey)?;
1065                    if match filter {
1066                        TokenAccountsFilter::Mint(mint) => token_account.mint().eq(mint),
1067                        TokenAccountsFilter::ProgramId(program_id) => account.owner.eq(program_id),
1068                    } {
1069                        Some(svm_reader.account_to_rpc_keyed_account(
1070                            pubkey,
1071                            account,
1072                            config,
1073                            Some(token_account.mint()),
1074                        ))
1075                    } else {
1076                        None
1077                    }
1078                })
1079                .collect::<Vec<_>>()
1080        })
1081    }
1082
1083    pub async fn get_token_accounts_by_owner_local_then_remote(
1084        &self,
1085        owner: Pubkey,
1086        filter: &TokenAccountsFilter,
1087        remote_client: &SurfnetRemoteClient,
1088        config: &RpcAccountInfoConfig,
1089    ) -> SurfpoolContextualizedResult<Vec<RpcKeyedAccount>> {
1090        let SvmAccessContext {
1091            slot,
1092            latest_epoch_info,
1093            latest_blockhash,
1094            inner: local_accounts,
1095        } = self.get_token_accounts_by_owner_local(owner, filter, config);
1096
1097        let remote_accounts = remote_client
1098            .get_token_accounts_by_owner(owner, filter, config)
1099            .await?;
1100
1101        let mut combined_accounts = remote_accounts;
1102
1103        for local_account in local_accounts {
1104            if let Some((pos, _)) = combined_accounts
1105                .iter()
1106                .find_position(|RpcKeyedAccount { pubkey, .. }| pubkey.eq(&local_account.pubkey))
1107            {
1108                combined_accounts[pos] = local_account;
1109            } else {
1110                combined_accounts.push(local_account);
1111            }
1112        }
1113
1114        Ok(SvmAccessContext::new(
1115            slot,
1116            latest_epoch_info,
1117            latest_blockhash,
1118            combined_accounts,
1119        ))
1120    }
1121
1122    pub async fn get_token_accounts_by_delegate(
1123        &self,
1124        remote_ctx: &Option<SurfnetRemoteClient>,
1125        delegate: Pubkey,
1126        filter: &TokenAccountsFilter,
1127        config: &RpcAccountInfoConfig,
1128    ) -> SurfpoolContextualizedResult<Vec<RpcKeyedAccount>> {
1129        // Validate that the program is supported if using ProgramId filter
1130        if let TokenAccountsFilter::ProgramId(program_id) = filter {
1131            if !is_supported_token_program(program_id) {
1132                return Err(SurfpoolError::unsupported_token_program(*program_id));
1133            }
1134        }
1135
1136        if let Some(remote_client) = remote_ctx {
1137            self.get_token_accounts_by_delegate_local_then_remote(
1138                delegate,
1139                filter,
1140                remote_client,
1141                config,
1142            )
1143            .await
1144        } else {
1145            Ok(self.get_token_accounts_by_delegate_local(delegate, filter, config))
1146        }
1147    }
1148}
1149
1150/// Token account by delegate related functions
1151impl SurfnetSvmLocker {
1152    pub fn get_token_accounts_by_delegate_local(
1153        &self,
1154        delegate: Pubkey,
1155        filter: &TokenAccountsFilter,
1156        config: &RpcAccountInfoConfig,
1157    ) -> SvmAccessContext<Vec<RpcKeyedAccount>> {
1158        self.with_contextualized_svm_reader(|svm_reader| {
1159            svm_reader
1160                .get_token_accounts_by_delegate(&delegate)
1161                .iter()
1162                .filter_map(|(pubkey, token_account)| {
1163                    let account = svm_reader.accounts_registry.get(pubkey)?;
1164                    let include = match filter {
1165                        TokenAccountsFilter::Mint(mint) => token_account.mint() == *mint,
1166                        TokenAccountsFilter::ProgramId(program_id) => {
1167                            account.owner == *program_id && is_supported_token_program(program_id)
1168                        }
1169                    };
1170
1171                    if include {
1172                        Some(svm_reader.account_to_rpc_keyed_account(
1173                            pubkey,
1174                            account,
1175                            config,
1176                            Some(token_account.mint()),
1177                        ))
1178                    } else {
1179                        None
1180                    }
1181                })
1182                .collect::<Vec<_>>()
1183        })
1184    }
1185
1186    pub async fn get_token_accounts_by_delegate_local_then_remote(
1187        &self,
1188        delegate: Pubkey,
1189        filter: &TokenAccountsFilter,
1190        remote_client: &SurfnetRemoteClient,
1191        config: &RpcAccountInfoConfig,
1192    ) -> SurfpoolContextualizedResult<Vec<RpcKeyedAccount>> {
1193        let SvmAccessContext {
1194            slot,
1195            latest_epoch_info,
1196            latest_blockhash,
1197            inner: local_accounts,
1198        } = self.get_token_accounts_by_delegate_local(delegate, filter, config);
1199
1200        let remote_accounts = remote_client
1201            .get_token_accounts_by_delegate(delegate, filter, config)
1202            .await?;
1203
1204        let mut combined_accounts = remote_accounts;
1205
1206        for local_account in local_accounts {
1207            if let Some((pos, _)) = combined_accounts
1208                .iter()
1209                .find_position(|RpcKeyedAccount { pubkey, .. }| pubkey.eq(&local_account.pubkey))
1210            {
1211                // Replace remote account with local one (local takes precedence)
1212                combined_accounts[pos] = local_account;
1213            } else {
1214                // Add local account that wasn't found in remote results
1215                combined_accounts.push(local_account);
1216            }
1217        }
1218
1219        Ok(SvmAccessContext::new(
1220            slot,
1221            latest_epoch_info,
1222            latest_blockhash,
1223            combined_accounts,
1224        ))
1225    }
1226}
1227
1228/// Get largest account related account
1229impl SurfnetSvmLocker {
1230    pub fn get_token_largest_accounts_local(
1231        &self,
1232        mint: &Pubkey,
1233    ) -> SvmAccessContext<Vec<RpcTokenAccountBalance>> {
1234        self.with_contextualized_svm_reader(|svm_reader| {
1235            let token_accounts = svm_reader.get_token_accounts_by_mint(mint);
1236
1237            // get mint information to determine decimals
1238            let mint_decimals = if let Some(mint_account) = svm_reader.token_mints.get(mint) {
1239                mint_account.decimals()
1240            } else {
1241                0
1242            };
1243
1244            // convert to RpcTokenAccountBalance and sort by balance
1245            let mut balances: Vec<RpcTokenAccountBalance> = token_accounts
1246                .into_iter()
1247                .map(|(pubkey, token_account)| RpcTokenAccountBalance {
1248                    address: pubkey.to_string(),
1249                    amount: UiTokenAmount {
1250                        amount: token_account.amount().to_string(),
1251                        decimals: mint_decimals,
1252                        ui_amount: Some(format_ui_amount(token_account.amount(), mint_decimals)),
1253                        ui_amount_string: format_ui_amount_string(
1254                            token_account.amount(),
1255                            mint_decimals,
1256                        ),
1257                    },
1258                })
1259                .collect();
1260
1261            // sort by amount in descending order
1262            balances.sort_by(|a, b| {
1263                let amount_a: u64 = a.amount.amount.parse().unwrap_or(0);
1264                let amount_b: u64 = b.amount.amount.parse().unwrap_or(0);
1265                amount_b.cmp(&amount_a)
1266            });
1267
1268            // limit to top 20 accounts
1269            balances.truncate(20);
1270
1271            balances
1272        })
1273    }
1274
1275    pub async fn get_token_largest_accounts_local_then_remote(
1276        &self,
1277        client: &SurfnetRemoteClient,
1278        mint: &Pubkey,
1279        commitment_config: CommitmentConfig,
1280    ) -> SurfpoolContextualizedResult<Vec<RpcTokenAccountBalance>> {
1281        let SvmAccessContext {
1282            slot,
1283            latest_epoch_info,
1284            latest_blockhash,
1285            inner: local_accounts,
1286        } = self.get_token_largest_accounts_local(mint);
1287
1288        let remote_accounts = client
1289            .get_token_largest_accounts(mint, commitment_config)
1290            .await?;
1291
1292        let mut combined_accounts = remote_accounts;
1293
1294        // if the account is in both the local and remote list, add the local one and not the remote
1295        for local_account in local_accounts {
1296            if let Some((pos, _)) = combined_accounts
1297                .iter()
1298                .find_position(|remote_account| remote_account.address == local_account.address)
1299            {
1300                combined_accounts[pos] = local_account;
1301            } else {
1302                combined_accounts.push(local_account);
1303            }
1304        }
1305
1306        // re-sort and limit after combining
1307        combined_accounts.sort_by(|a, b| {
1308            let amount_a: u64 = a.amount.amount.parse().unwrap_or(0);
1309            let amount_b: u64 = b.amount.amount.parse().unwrap_or(0);
1310            amount_b.cmp(&amount_a)
1311        });
1312        combined_accounts.truncate(20);
1313
1314        Ok(SvmAccessContext::new(
1315            slot,
1316            latest_epoch_info,
1317            latest_blockhash,
1318            combined_accounts,
1319        ))
1320    }
1321
1322    /// Fetches the largest token accounts for a specific mint, returning contextualized results.
1323    pub async fn get_token_largest_accounts(
1324        &self,
1325        remote_ctx: &Option<(SurfnetRemoteClient, CommitmentConfig)>,
1326        mint: &Pubkey,
1327    ) -> SurfpoolContextualizedResult<Vec<RpcTokenAccountBalance>> {
1328        if let Some((remote_client, commitment_config)) = remote_ctx {
1329            self.get_token_largest_accounts_local_then_remote(
1330                remote_client,
1331                mint,
1332                *commitment_config,
1333            )
1334            .await
1335        } else {
1336            Ok(self.get_token_largest_accounts_local(mint))
1337        }
1338    }
1339}
1340
1341/// Address lookup table related functions
1342impl SurfnetSvmLocker {
1343    /// Extracts pubkeys from a VersionedMessage, resolving address lookup tables as needed.
1344    pub fn get_pubkeys_from_message(
1345        &self,
1346        message: &VersionedMessage,
1347        loaded_addresses: Option<LoadedAddresses>,
1348    ) -> Vec<Pubkey> {
1349        match message {
1350            VersionedMessage::Legacy(message) => message.account_keys.clone(),
1351            VersionedMessage::V0(message) => {
1352                let alts = message.address_table_lookups.clone();
1353                let mut acc_keys = message.account_keys.clone();
1354                let mut alt_pubkeys = alts.iter().map(|msg| msg.account_key).collect::<Vec<_>>();
1355
1356                acc_keys.append(&mut alt_pubkeys);
1357                if let Some(mut loaded_addresses) = loaded_addresses {
1358                    acc_keys.append(&mut loaded_addresses.readonly);
1359                    acc_keys.append(&mut loaded_addresses.writable);
1360                }
1361                acc_keys
1362            }
1363        }
1364    }
1365
1366    /// Gets addresses loaded from on-chain lookup tables from a VersionedMessage.
1367    pub async fn get_loaded_addresses(
1368        &self,
1369        remote_ctx: &Option<(SurfnetRemoteClient, CommitmentConfig)>,
1370        message: &VersionedMessage,
1371    ) -> SurfpoolResult<Option<LoadedAddresses>> {
1372        match message {
1373            VersionedMessage::Legacy(_) => Ok(None),
1374            VersionedMessage::V0(message) => {
1375                let alts = message.address_table_lookups.clone();
1376                if alts.is_empty() {
1377                    return Ok(None);
1378                }
1379                let mut combined = LoadedAddresses::default();
1380                for alt in alts {
1381                    let mut loaded_addresses = self
1382                        .get_lookup_table_addresses(remote_ctx, &alt)
1383                        .await?
1384                        .inner;
1385                    combined.readonly.append(&mut loaded_addresses.readonly);
1386                    combined.writable.append(&mut loaded_addresses.writable);
1387                }
1388
1389                Ok(Some(combined))
1390            }
1391        }
1392    }
1393
1394    /// Retrieves loaded addresses from a lookup table account, validating owner and indices.
1395    pub async fn get_lookup_table_addresses(
1396        &self,
1397        remote_ctx: &Option<(SurfnetRemoteClient, CommitmentConfig)>,
1398        address_table_lookup: &MessageAddressTableLookup,
1399    ) -> SurfpoolContextualizedResult<LoadedAddresses> {
1400        let result = self
1401            .get_account(remote_ctx, &address_table_lookup.account_key, None)
1402            .await?;
1403        let table_account = result.inner.clone().map_account()?;
1404
1405        if table_account.owner == solana_sdk_ids::address_lookup_table::id() {
1406            let SvmAccessContext {
1407                slot: current_slot,
1408                inner: slot_hashes,
1409                ..
1410            } = self.with_contextualized_svm_reader(|svm_reader| {
1411                svm_reader
1412                    .inner
1413                    .get_sysvar::<solana_sdk::sysvar::slot_hashes::SlotHashes>()
1414            });
1415
1416            //let current_slot = self.get_latest_absolute_slot(); // or should i use this?
1417            let data = &table_account.data.clone();
1418            let lookup_table = AddressLookupTable::deserialize(data).map_err(|_ix_err| {
1419                SurfpoolError::invalid_account_data(
1420                    address_table_lookup.account_key,
1421                    table_account.data,
1422                    Some("Attempted to lookup addresses from an invalid account"),
1423                )
1424            })?;
1425
1426            let loaded_addresses = LoadedAddresses {
1427                writable: lookup_table
1428                    .lookup(
1429                        current_slot,
1430                        &address_table_lookup.writable_indexes,
1431                        &slot_hashes,
1432                    )
1433                    .map_err(|_ix_err| {
1434                        SurfpoolError::invalid_lookup_index(address_table_lookup.account_key)
1435                    })?,
1436                readonly: lookup_table
1437                    .lookup(
1438                        current_slot,
1439                        &address_table_lookup.readonly_indexes,
1440                        &slot_hashes,
1441                    )
1442                    .map_err(|_ix_err| {
1443                        SurfpoolError::invalid_lookup_index(address_table_lookup.account_key)
1444                    })?,
1445            };
1446            Ok(result.with_new_value(loaded_addresses))
1447        } else {
1448            Err(SurfpoolError::invalid_account_owner(
1449                table_account.owner,
1450                Some("Attempted to lookup addresses from an account owned by the wrong program"),
1451            ))
1452        }
1453    }
1454}
1455
1456/// Profiling helper functions
1457impl SurfnetSvmLocker {
1458    /// Estimates compute units for a transaction via contextualized simulation.
1459    pub fn estimate_compute_units(
1460        &self,
1461        transaction: &VersionedTransaction,
1462    ) -> SvmAccessContext<ComputeUnitsEstimationResult> {
1463        self.with_contextualized_svm_reader(|svm_reader| {
1464            svm_reader.estimate_compute_units(transaction)
1465        })
1466    }
1467    pub async fn profile_transaction(
1468        &self,
1469        remote_ctx: &Option<SurfnetRemoteClient>,
1470        transaction: VersionedTransaction,
1471        encoding: Option<UiAccountEncoding>,
1472        tag: Option<String>,
1473    ) -> SurfpoolContextualizedResult<ProfileResult> {
1474        let SvmAccessContext {
1475            slot,
1476            latest_epoch_info,
1477            latest_blockhash,
1478            inner: mut svm_clone,
1479        } = self.with_contextualized_svm_reader(|svm_reader| svm_reader.clone());
1480
1481        let (dummy_simnet_tx, _) = crossbeam_channel::bounded(1);
1482        let (dummy_geyser_tx, _) = crossbeam_channel::bounded(1);
1483        svm_clone.simnet_events_tx = dummy_simnet_tx;
1484        svm_clone.geyser_events_tx = dummy_geyser_tx;
1485
1486        let svm_locker = SurfnetSvmLocker::new(svm_clone);
1487
1488        let remote_ctx_with_config = remote_ctx
1489            .clone()
1490            .map(|client| (client, CommitmentConfig::confirmed()));
1491
1492        let loaded_addresses = svm_locker
1493            .get_loaded_addresses(&remote_ctx_with_config, &transaction.message)
1494            .await?;
1495        let account_keys =
1496            svm_locker.get_pubkeys_from_message(&transaction.message, loaded_addresses);
1497
1498        let pre_execution_capture = {
1499            let mut capture = BTreeMap::new();
1500            for pubkey in &account_keys {
1501                let account = svm_locker
1502                    .get_account(&remote_ctx_with_config, pubkey, None)
1503                    .await?
1504                    .inner;
1505
1506                svm_locker.snapshot_get_account_result(&mut capture, account, encoding);
1507            }
1508            capture
1509        };
1510
1511        let compute_units_estimation_result = svm_locker.estimate_compute_units(&transaction).inner;
1512
1513        let (status_tx, status_rx) = crossbeam_channel::unbounded();
1514        let signature = transaction.signatures[0];
1515        let _ = svm_locker
1516            .process_transaction(remote_ctx, transaction, status_tx, true)
1517            .await?;
1518
1519        let simnet_events_tx = self.simnet_events_tx();
1520        loop {
1521            if let Ok(status) = status_rx.try_recv() {
1522                match status {
1523                    TransactionStatusEvent::Success(_) => break,
1524                    TransactionStatusEvent::ExecutionFailure((err, _)) => {
1525                        let _ = simnet_events_tx.try_send(SimnetEvent::WarnLog(
1526                            chrono::Local::now(),
1527                            format!(
1528                                "Transaction {} failed during snapshot simulation: {}",
1529                                signature, err
1530                            ),
1531                        ));
1532                        return Err(SurfpoolError::internal(format!(
1533                            "Transaction {} failed during snapshot simulation: {}",
1534                            signature, err
1535                        )));
1536                    }
1537                    TransactionStatusEvent::SimulationFailure(_) => unreachable!(),
1538                    TransactionStatusEvent::VerificationFailure(_) => {
1539                        let _ = simnet_events_tx.try_send(SimnetEvent::WarnLog(
1540                            chrono::Local::now(),
1541                            format!(
1542                                "Transaction {} verification failed during snapshot simulation",
1543                                signature
1544                            ),
1545                        ));
1546                        return Err(SurfpoolError::internal(format!(
1547                            "Transaction {} verification failed during snapshot simulation",
1548                            signature
1549                        )));
1550                    }
1551                }
1552            }
1553        }
1554
1555        let post_execution_capture = {
1556            let mut capture = BTreeMap::new();
1557            for pubkey in &account_keys {
1558                // get the account locally after execution, because execution should have inserted from
1559                // the remote if not found locally
1560                let account = svm_locker.get_account_local(pubkey).inner;
1561
1562                svm_locker.snapshot_get_account_result(&mut capture, account, encoding);
1563            }
1564            capture
1565        };
1566
1567        let uuid = Uuid::new_v4();
1568        let profile_result = ProfileResult {
1569            compute_units: compute_units_estimation_result,
1570            state: ProfileState::new(pre_execution_capture, post_execution_capture),
1571            slot,
1572            uuid: Some(uuid),
1573        };
1574
1575        self.with_svm_writer(|svm| {
1576            svm.simulated_transaction_profiles
1577                .insert(uuid, profile_result.clone());
1578            svm.profile_tag_map
1579                .entry(tag.clone().unwrap_or(uuid.to_string()))
1580                .or_default()
1581                .push(UuidOrSignature::Uuid(uuid));
1582        });
1583
1584        Ok(SvmAccessContext::new(
1585            slot,
1586            latest_epoch_info,
1587            latest_blockhash,
1588            profile_result,
1589        ))
1590    }
1591
1592    /// Returns the profile result for a given signature or UUID, and whether it exists in the SVM.
1593    pub fn get_profile_result(
1594        &self,
1595        signature_or_uuid: UuidOrSignature,
1596    ) -> SurfpoolResult<Option<ProfileResult>> {
1597        match &signature_or_uuid {
1598            UuidOrSignature::Signature(signature) => {
1599                let profile = self.with_svm_reader(|svm| {
1600                    svm.executed_transaction_profiles.get(signature).cloned()
1601                });
1602                let transaction_exists =
1603                    self.with_svm_reader(|svm| svm.transactions.contains_key(signature));
1604                if profile.is_none() && transaction_exists {
1605                    Err(SurfpoolError::transaction_not_found_in_svm(signature))
1606                } else {
1607                    Ok(profile)
1608                }
1609            }
1610            UuidOrSignature::Uuid(uuid) => {
1611                let profile = self
1612                    .with_svm_reader(|svm| svm.simulated_transaction_profiles.get(uuid).cloned());
1613                Ok(profile)
1614            }
1615        }
1616    }
1617
1618    /// Returns the profile results for a given tag.
1619    pub fn get_profile_results_by_tag(
1620        &self,
1621        tag: String,
1622    ) -> SurfpoolResult<Option<Vec<ProfileResult>>> {
1623        let tag_map = self.with_svm_reader(|svm| svm.profile_tag_map.get(&tag).cloned());
1624        match tag_map {
1625            None => Ok(None),
1626            Some(uuids_or_sigs) => {
1627                let mut profiles = Vec::new();
1628                for id in uuids_or_sigs {
1629                    let profile = self.get_profile_result(id.clone())?;
1630                    if profile.is_none() {
1631                        return Err(SurfpoolError::tag_not_found(&tag));
1632                    }
1633                    profiles.push(profile.unwrap());
1634                }
1635                Ok(Some(profiles))
1636            }
1637        }
1638    }
1639}
1640/// Program account related functions
1641impl SurfnetSvmLocker {
1642    /// Clones a program account from source to destination, handling upgradeable loader state.
1643    pub async fn clone_program_account(
1644        &self,
1645        remote_ctx: &Option<(SurfnetRemoteClient, CommitmentConfig)>,
1646        source_program_id: &Pubkey,
1647        destination_program_id: &Pubkey,
1648    ) -> SurfpoolContextualizedResult<()> {
1649        let expected_source_program_data_address = get_program_data_address(source_program_id);
1650
1651        let result = self
1652            .get_multiple_accounts(
1653                remote_ctx,
1654                &[*source_program_id, expected_source_program_data_address],
1655                None,
1656            )
1657            .await?;
1658
1659        let mut accounts = result
1660            .inner
1661            .clone()
1662            .into_iter()
1663            .map(|a| a.map_account())
1664            .collect::<SurfpoolResult<Vec<Account>>>()?;
1665
1666        let source_program_data_account = accounts.remove(1);
1667        let source_program_account = accounts.remove(0);
1668
1669        let BpfUpgradeableLoaderAccountType::Program(UiProgram {
1670            program_data: source_program_data_address,
1671        }) = parse_bpf_upgradeable_loader(&source_program_account.data).map_err(|e| {
1672            SurfpoolError::invalid_program_account(source_program_id, e.to_string())
1673        })?
1674        else {
1675            return Err(SurfpoolError::expected_program_account(source_program_id));
1676        };
1677
1678        if source_program_data_address.ne(&expected_source_program_data_address.to_string()) {
1679            return Err(SurfpoolError::invalid_program_account(
1680                source_program_id,
1681                format!(
1682                    "Program data address mismatch: expected {}, found {}",
1683                    expected_source_program_data_address, source_program_data_address
1684                ),
1685            ));
1686        }
1687
1688        let destination_program_data_address = get_program_data_address(destination_program_id);
1689
1690        // create a new program account that has the `program_data` field set to the
1691        // destination program data address
1692        let mut new_program_account = source_program_account;
1693        new_program_account.data = bincode::serialize(&UpgradeableLoaderState::Program {
1694            programdata_address: destination_program_data_address,
1695        })
1696        .map_err(|e| SurfpoolError::internal(format!("Failed to serialize program data: {}", e)))?;
1697
1698        self.with_svm_writer(|svm_writer| {
1699            svm_writer.set_account(
1700                &destination_program_data_address,
1701                source_program_data_account.clone(),
1702            )?;
1703
1704            svm_writer.set_account(destination_program_id, new_program_account.clone())?;
1705            Ok::<(), SurfpoolError>(())
1706        })?;
1707
1708        Ok(result.with_new_value(()))
1709    }
1710
1711    pub async fn set_program_authority(
1712        &self,
1713        remote_ctx: &Option<(SurfnetRemoteClient, CommitmentConfig)>,
1714        program_id: Pubkey,
1715        new_authority: Option<Pubkey>,
1716    ) -> SurfpoolContextualizedResult<()> {
1717        let SvmAccessContext {
1718            slot,
1719            latest_epoch_info,
1720            latest_blockhash,
1721            inner: mut get_account_result,
1722        } = self.get_account(remote_ctx, &program_id, None).await?;
1723
1724        let original_authority = match &mut get_account_result {
1725            GetAccountResult::None(pubkey) => {
1726                return Err(SurfpoolError::invalid_program_account(
1727                    pubkey,
1728                    "Account not found",
1729                ));
1730            }
1731            GetAccountResult::FoundAccount(pubkey, program_account, _) => {
1732                let programdata_address = get_program_data_address(pubkey);
1733                let mut programdata_account_result = self
1734                    .get_account(remote_ctx, &programdata_address, None)
1735                    .await?
1736                    .inner;
1737                match &mut programdata_account_result {
1738                    GetAccountResult::None(pubkey) => {
1739                        return Err(SurfpoolError::invalid_program_account(
1740                            pubkey,
1741                            "Program data account does not exist",
1742                        ));
1743                    }
1744                    GetAccountResult::FoundAccount(_, programdata_account, _) => {
1745                        let original_authority = update_programdata_account(
1746                            &program_id,
1747                            programdata_account,
1748                            new_authority,
1749                        )?;
1750
1751                        get_account_result = GetAccountResult::FoundProgramAccount(
1752                            (pubkey.clone(), program_account.clone()),
1753                            (programdata_address, Some(programdata_account.clone())),
1754                        );
1755
1756                        original_authority
1757                    }
1758                    GetAccountResult::FoundProgramAccount(_, _)
1759                    | GetAccountResult::FoundTokenAccount(_, _) => {
1760                        return Err(SurfpoolError::invalid_program_account(
1761                            pubkey,
1762                            "Not a program account",
1763                        ));
1764                    }
1765                }
1766            }
1767            GetAccountResult::FoundProgramAccount(_, (_, None)) => {
1768                return Err(SurfpoolError::invalid_program_account(
1769                    &program_id,
1770                    "Program data account does not exist",
1771                ));
1772            }
1773            GetAccountResult::FoundProgramAccount(_, (_, Some(programdata_account))) => {
1774                update_programdata_account(&program_id, programdata_account, new_authority)?
1775            }
1776            GetAccountResult::FoundTokenAccount(_, _) => {
1777                return Err(SurfpoolError::invalid_program_account(
1778                    program_id,
1779                    "Not a program account",
1780                ));
1781            }
1782        };
1783
1784        let simnet_events_tx = self.simnet_events_tx();
1785        match (original_authority, new_authority) {
1786            (Some(original), Some(new)) => {
1787                if original != new {
1788                    let _ = simnet_events_tx.send(SimnetEvent::info(format!(
1789                        "Setting new authority for program {}",
1790                        program_id
1791                    )));
1792                    let _ = simnet_events_tx
1793                        .send(SimnetEvent::info(format!("Old Authority: {}", original)));
1794                    let _ =
1795                        simnet_events_tx.send(SimnetEvent::info(format!("New Authority: {}", new)));
1796                } else {
1797                    let _ = simnet_events_tx.send(SimnetEvent::info(format!(
1798                        "No authority change for program {}",
1799                        program_id
1800                    )));
1801                }
1802            }
1803            (Some(original), None) => {
1804                let _ = simnet_events_tx.send(SimnetEvent::info(format!(
1805                    "Removing authority for program {}",
1806                    program_id
1807                )));
1808                let _ = simnet_events_tx
1809                    .send(SimnetEvent::info(format!("Old Authority: {}", original)));
1810            }
1811            (None, Some(new)) => {
1812                let _ = simnet_events_tx.send(SimnetEvent::info(format!(
1813                    "Setting new authority for program {}",
1814                    program_id
1815                )));
1816                let _ = simnet_events_tx.send(SimnetEvent::info(format!("Old Authority: None")));
1817                let _ = simnet_events_tx.send(SimnetEvent::info(format!("New Authority: {}", new)));
1818            }
1819            (None, None) => {
1820                let _ = simnet_events_tx.send(SimnetEvent::info(format!(
1821                    "No authority change for program {}",
1822                    program_id
1823                )));
1824            }
1825        };
1826
1827        self.write_account_update(get_account_result);
1828
1829        Ok(SvmAccessContext::new(
1830            slot,
1831            latest_epoch_info,
1832            latest_blockhash,
1833            (),
1834        ))
1835    }
1836
1837    pub async fn get_program_accounts(
1838        &self,
1839        remote_ctx: &Option<SurfnetRemoteClient>,
1840        program_id: &Pubkey,
1841        account_config: RpcAccountInfoConfig,
1842        filters: Option<Vec<RpcFilterType>>,
1843    ) -> SurfpoolContextualizedResult<Vec<RpcKeyedAccount>> {
1844        if let Some(remote_client) = remote_ctx {
1845            self.get_program_accounts_local_then_remote(
1846                remote_client,
1847                program_id,
1848                account_config,
1849                filters,
1850            )
1851            .await
1852        } else {
1853            self.get_program_accounts_local(program_id, account_config, filters)
1854        }
1855    }
1856
1857    /// Retrieves program accounts from the local SVM cache, returning a contextualized result.
1858    pub fn get_program_accounts_local(
1859        &self,
1860        program_id: &Pubkey,
1861        account_config: RpcAccountInfoConfig,
1862        filters: Option<Vec<RpcFilterType>>,
1863    ) -> SurfpoolContextualizedResult<Vec<RpcKeyedAccount>> {
1864        let res = self.with_svm_reader(|svm_reader| {
1865            let res = svm_reader.get_account_owned_by(*program_id);
1866
1867            let mut filtered = vec![];
1868            for (pubkey, account) in &res {
1869                if let Some(ref active_filters) = filters {
1870                    match apply_rpc_filters(&account.data, active_filters) {
1871                        Ok(true) => {}           // Account matches all filters
1872                        Ok(false) => continue,   // Filtered out
1873                        Err(e) => return Err(e), // Error applying filter, already JsonRpcError
1874                    }
1875                }
1876
1877                filtered.push(svm_reader.account_to_rpc_keyed_account(
1878                    pubkey,
1879                    account,
1880                    &account_config,
1881                    None,
1882                ));
1883            }
1884            Ok(filtered)
1885        })?;
1886
1887        Ok(self.with_contextualized_svm_reader(|_| res.clone()))
1888    }
1889
1890    /// Retrieves program accounts from the local cache and remote client, combining results.
1891    pub async fn get_program_accounts_local_then_remote(
1892        &self,
1893        client: &SurfnetRemoteClient,
1894        program_id: &Pubkey,
1895        account_config: RpcAccountInfoConfig,
1896        filters: Option<Vec<RpcFilterType>>,
1897    ) -> SurfpoolContextualizedResult<Vec<RpcKeyedAccount>> {
1898        let SvmAccessContext {
1899            slot,
1900            latest_epoch_info,
1901            latest_blockhash,
1902            inner: local_accounts,
1903        } = self.get_program_accounts_local(program_id, account_config.clone(), filters.clone())?;
1904        let remote_accounts = client
1905            .get_program_accounts(program_id, account_config, filters)
1906            .await?;
1907
1908        let mut combined_accounts = remote_accounts;
1909
1910        for local_account in local_accounts {
1911            // if the local account is in the remote set, replace it with the local one
1912            if let Some((pos, _)) = combined_accounts.iter().find_position(
1913                |RpcKeyedAccount {
1914                     pubkey: remote_pubkey,
1915                     ..
1916                 }| remote_pubkey.eq(&local_account.pubkey),
1917            ) {
1918                combined_accounts[pos] = local_account;
1919            } else {
1920                // otherwise, add the local account to the combined list
1921                combined_accounts.push(local_account);
1922            };
1923        }
1924
1925        Ok(SvmAccessContext {
1926            slot,
1927            latest_epoch_info,
1928            latest_blockhash,
1929            inner: combined_accounts,
1930        })
1931    }
1932}
1933
1934impl SurfnetSvmLocker {
1935    pub fn get_first_local_slot(&self) -> Option<Slot> {
1936        self.with_svm_reader(|svm_reader| svm_reader.blocks.keys().min().copied())
1937    }
1938
1939    pub async fn get_block(
1940        &self,
1941        remote_ctx: &Option<SurfnetRemoteClient>,
1942        slot: &Slot,
1943        config: &RpcBlockConfig,
1944    ) -> SurfpoolContextualizedResult<Option<UiConfirmedBlock>> {
1945        let first_local_slot = self.get_first_local_slot();
1946
1947        let result = if first_local_slot.is_some() && first_local_slot.unwrap() > *slot {
1948            match remote_ctx {
1949                Some(remote_client) => Some(remote_client.get_block(slot, *config).await?),
1950                None => return Err(SurfpoolError::slot_too_old(*slot)),
1951            }
1952        } else {
1953            self.get_block_local(slot, config)?
1954        };
1955
1956        Ok(SvmAccessContext {
1957            slot: *slot,
1958            latest_epoch_info: self.get_epoch_info(),
1959            latest_blockhash: self
1960                .get_latest_blockhash(&CommitmentConfig::processed())
1961                .unwrap_or_default(),
1962            inner: result,
1963        })
1964    }
1965
1966    pub fn get_block_local(
1967        &self,
1968        slot: &Slot,
1969        config: &RpcBlockConfig,
1970    ) -> SurfpoolResult<Option<UiConfirmedBlock>> {
1971        self.with_svm_reader(|svm_reader| svm_reader.get_block_at_slot(*slot, config))
1972    }
1973
1974    pub fn get_genesis_hash_local(&self) -> SvmAccessContext<Hash> {
1975        self.with_contextualized_svm_reader(|svm_reader| svm_reader.genesis_config.hash())
1976    }
1977
1978    pub async fn get_genesis_hash(
1979        &self,
1980        remote_ctx: &Option<SurfnetRemoteClient>,
1981    ) -> SurfpoolContextualizedResult<Hash> {
1982        if let Some(client) = remote_ctx {
1983            let remote_hash = client.get_genesis_hash().await?;
1984            Ok(self.with_contextualized_svm_reader(|_| remote_hash))
1985        } else {
1986            Ok(self.get_genesis_hash_local())
1987        }
1988    }
1989}
1990
1991/// Pass through functions for accessing the underlying SurfnetSvm instance
1992impl SurfnetSvmLocker {
1993    /// Returns a sender for simulation events from the underlying SVM.
1994    pub fn simnet_events_tx(&self) -> Sender<SimnetEvent> {
1995        self.with_svm_reader(|svm_reader| svm_reader.simnet_events_tx.clone())
1996    }
1997
1998    /// Retrieves the latest epoch info from the underlying SVM.
1999    pub fn get_epoch_info(&self) -> EpochInfo {
2000        self.with_svm_reader(|svm_reader| svm_reader.latest_epoch_info.clone())
2001    }
2002
2003    /// Retrieves the latest absolute slot from the underlying SVM.
2004    pub fn get_latest_absolute_slot(&self) -> Slot {
2005        self.with_svm_reader(|svm_reader| svm_reader.get_latest_absolute_slot())
2006    }
2007
2008    /// Retrieves the latest blockhash for the given commitment config from the underlying SVM.
2009    pub fn get_latest_blockhash(&self, config: &CommitmentConfig) -> Option<Hash> {
2010        let slot = self.get_slot_for_commitment(config);
2011        self.with_svm_reader(|svm_reader| svm_reader.blockhash_for_slot(slot))
2012    }
2013
2014    pub fn get_slot_for_commitment(&self, commitment: &CommitmentConfig) -> Slot {
2015        self.with_svm_reader(|svm_reader| {
2016            let slot = svm_reader.get_latest_absolute_slot();
2017            match commitment.commitment {
2018                CommitmentLevel::Processed => slot,
2019                CommitmentLevel::Confirmed => slot.saturating_sub(1),
2020                CommitmentLevel::Finalized => slot.saturating_sub(FINALIZATION_SLOT_THRESHOLD),
2021            }
2022        })
2023    }
2024
2025    /// Executes an airdrop via the underlying SVM.
2026    pub fn airdrop(&self, pubkey: &Pubkey, lamports: u64) -> TransactionResult {
2027        self.with_svm_writer(|svm_writer| svm_writer.airdrop(pubkey, lamports))
2028    }
2029
2030    /// Executes a batch airdrop via the underlying SVM.
2031    pub fn airdrop_pubkeys(&self, lamports: u64, addresses: &[Pubkey]) {
2032        self.with_svm_writer(|svm_writer| svm_writer.airdrop_pubkeys(lamports, addresses))
2033    }
2034
2035    /// Confirms the current block on the underlying SVM, returning `Ok(())` or an error.
2036    pub fn confirm_current_block(&self) -> SurfpoolResult<()> {
2037        self.with_svm_writer(|svm_writer| svm_writer.confirm_current_block())
2038    }
2039
2040    /// Subscribes for signature updates (confirmed/finalized) and returns a receiver of events.
2041    pub fn subscribe_for_signature_updates(
2042        &self,
2043        signature: &Signature,
2044        subscription_type: SignatureSubscriptionType,
2045    ) -> Receiver<(Slot, Option<TransactionError>)> {
2046        self.with_svm_writer(|svm_writer| {
2047            svm_writer.subscribe_for_signature_updates(signature, subscription_type.clone())
2048        })
2049    }
2050
2051    /// Subscribes for account updates and returns a receiver of account updates.
2052    pub fn subscribe_for_account_updates(
2053        &self,
2054        account_pubkey: &Pubkey,
2055        encoding: Option<UiAccountEncoding>,
2056    ) -> Receiver<UiAccount> {
2057        // Handles the locking/unlocking safely
2058        self.with_svm_writer(|svm_writer| {
2059            svm_writer.subscribe_for_account_updates(account_pubkey, encoding)
2060        })
2061    }
2062
2063    /// Subscribes for slot updates and returns a receiver of slot updates.
2064    pub fn subscribe_for_slot_updates(&self) -> Receiver<SlotInfo> {
2065        self.with_svm_writer(|svm_writer| svm_writer.subscribe_for_slot_updates())
2066    }
2067
2068    /// Subscribes for logs updates and returns a receiver of logs updates.
2069    pub fn subscribe_for_logs_updates(
2070        &self,
2071        commitment_level: &CommitmentLevel,
2072        filter: &RpcTransactionLogsFilter,
2073    ) -> Receiver<(Slot, RpcLogsResponse)> {
2074        self.with_svm_writer(|svm_writer| {
2075            svm_writer.subscribe_for_logs_updates(commitment_level, filter)
2076        })
2077    }
2078
2079    fn snapshot_get_account_result(
2080        &self,
2081        capture: &mut BTreeMap<Pubkey, Option<UiAccount>>,
2082        result: GetAccountResult,
2083        encoding: Option<UiAccountEncoding>,
2084    ) {
2085        let config = RpcAccountInfoConfig {
2086            encoding,
2087            ..Default::default()
2088        };
2089        match result {
2090            GetAccountResult::None(pubkey) => {
2091                capture.insert(pubkey, None);
2092            }
2093            GetAccountResult::FoundAccount(pubkey, account, _) => {
2094                let rpc_keyed_account = self.with_svm_reader(|svm_reader| {
2095                    svm_reader.account_to_rpc_keyed_account(&pubkey, &account, &config, None)
2096                });
2097                capture.insert(pubkey, Some(rpc_keyed_account.account));
2098            }
2099            GetAccountResult::FoundTokenAccount((pubkey, account), (mint_pubkey, mint_account)) => {
2100                let rpc_keyed_account = self.with_svm_reader(|svm_reader| {
2101                    svm_reader.account_to_rpc_keyed_account(
2102                        &pubkey,
2103                        &account,
2104                        &config,
2105                        Some(mint_pubkey),
2106                    )
2107                });
2108                capture.insert(pubkey, Some(rpc_keyed_account.account));
2109                if let Some(mint_account) = mint_account {
2110                    let rpc_keyed_account = self.with_svm_reader(|svm_reader| {
2111                        svm_reader.account_to_rpc_keyed_account(
2112                            &mint_pubkey,
2113                            &mint_account,
2114                            &config,
2115                            None,
2116                        )
2117                    });
2118                    capture.insert(mint_pubkey, Some(rpc_keyed_account.account));
2119                }
2120            }
2121            GetAccountResult::FoundProgramAccount(
2122                (pubkey, account),
2123                (data_pubkey, data_account),
2124            ) => {
2125                let rpc_keyed_account = self.with_svm_reader(|svm_reader| {
2126                    svm_reader.account_to_rpc_keyed_account(&pubkey, &account, &config, None)
2127                });
2128                capture.insert(pubkey, Some(rpc_keyed_account.account));
2129                if let Some(data_account) = data_account {
2130                    let rpc_keyed_account = self.with_svm_reader(|svm_reader| {
2131                        svm_reader.account_to_rpc_keyed_account(
2132                            &data_pubkey,
2133                            &data_account,
2134                            &config,
2135                            None,
2136                        )
2137                    });
2138                    capture.insert(data_pubkey, Some(rpc_keyed_account.account));
2139                }
2140            }
2141        }
2142    }
2143}
2144
2145// Helper function to apply filters
2146fn apply_rpc_filters(account_data: &[u8], filters: &[RpcFilterType]) -> SurfpoolResult<bool> {
2147    for filter in filters {
2148        match filter {
2149            RpcFilterType::DataSize(size) => {
2150                if account_data.len() as u64 != *size {
2151                    return Ok(false);
2152                }
2153            }
2154            RpcFilterType::Memcmp(memcmp_filter) => {
2155                // Use the public bytes_match method from solana_client::rpc_filter::Memcmp
2156                if !memcmp_filter.bytes_match(account_data) {
2157                    return Ok(false); // Content mismatch or out of bounds handled by bytes_match
2158                }
2159            }
2160            RpcFilterType::TokenAccountState => {
2161                return Err(SurfpoolError::internal(
2162                    "TokenAccountState filter is not supported",
2163                ));
2164            }
2165        }
2166    }
2167    Ok(true)
2168}
2169
2170// used in the remote.rs
2171pub fn is_supported_token_program(program_id: &Pubkey) -> bool {
2172    *program_id == spl_token::ID || *program_id == spl_token_2022::ID
2173}
2174
2175fn update_programdata_account(
2176    program_id: &Pubkey,
2177    programdata_account: &mut Account,
2178    new_authority: Option<Pubkey>,
2179) -> SurfpoolResult<Option<Pubkey>> {
2180    let upgradeable_loader_state =
2181        bincode::deserialize::<UpgradeableLoaderState>(&programdata_account.data).map_err(|e| {
2182            SurfpoolError::invalid_program_account(
2183                &program_id,
2184                format!("Failed to serialize program data: {}", e),
2185            )
2186        })?;
2187    if let UpgradeableLoaderState::ProgramData {
2188        upgrade_authority_address,
2189        slot,
2190    } = upgradeable_loader_state
2191    {
2192        let offset = if upgrade_authority_address.is_some() {
2193            UpgradeableLoaderState::size_of_programdata_metadata()
2194        } else {
2195            UpgradeableLoaderState::size_of_programdata_metadata()
2196                - serialized_size(&Pubkey::default()).unwrap() as usize
2197        };
2198
2199        let mut data = bincode::serialize(&UpgradeableLoaderState::ProgramData {
2200            upgrade_authority_address: new_authority,
2201            slot,
2202        })
2203        .map_err(|e| {
2204            SurfpoolError::invalid_program_account(
2205                &program_id,
2206                format!("Failed to serialize program data: {}", e),
2207            )
2208        })?;
2209
2210        data.append(&mut programdata_account.data[offset..].to_vec());
2211
2212        programdata_account.data = data;
2213
2214        Ok(upgrade_authority_address)
2215    } else {
2216        return Err(SurfpoolError::invalid_program_account(
2217            &program_id,
2218            "Invalid program data account",
2219        ));
2220    }
2221}
2222
2223pub fn format_ui_amount_string(amount: u64, decimals: u8) -> String {
2224    let formatted_amount = if decimals > 0 {
2225        let divisor = 10u64.pow(decimals as u32);
2226        format!(
2227            "{:.decimals$}",
2228            amount as f64 / divisor as f64,
2229            decimals = decimals as usize
2230        )
2231    } else {
2232        amount.to_string()
2233    };
2234    formatted_amount
2235}
2236
2237pub fn format_ui_amount(amount: u64, decimals: u8) -> f64 {
2238    if decimals > 0 {
2239        let divisor = 10u64.pow(decimals as u32);
2240        amount as f64 / divisor as f64
2241    } else {
2242        amount as f64
2243    }
2244}