1use std::{
2 collections::{BTreeMap, HashMap, HashSet},
3 sync::Arc,
4 time::SystemTime,
5};
6
7use bincode::serialized_size;
8use crossbeam_channel::{Receiver, Sender};
9use itertools::Itertools;
10use litesvm::types::{
11 FailedTransactionMetadata, SimulatedTransactionInfo, TransactionMetadata, TransactionResult,
12};
13use solana_account::{Account, ReadableAccount};
14use solana_account_decoder::{
15 UiAccount, UiAccountEncoding, UiDataSliceConfig,
16 parse_account_data::AccountAdditionalDataV3,
17 parse_bpf_loader::{BpfUpgradeableLoaderAccountType, UiProgram, parse_bpf_upgradeable_loader},
18 parse_token::UiTokenAmount,
19};
20use solana_address_lookup_table_interface::state::AddressLookupTable;
21use solana_client::{
22 rpc_client::SerializableTransaction,
23 rpc_config::{
24 RpcAccountInfoConfig, RpcBlockConfig, RpcLargestAccountsConfig, RpcLargestAccountsFilter,
25 RpcSignaturesForAddressConfig, RpcTransactionConfig, RpcTransactionLogsFilter,
26 },
27 rpc_filter::RpcFilterType,
28 rpc_request::TokenAccountsFilter,
29 rpc_response::{
30 RpcAccountBalance, RpcConfirmedTransactionStatusWithSignature, RpcKeyedAccount,
31 RpcLogsResponse, RpcTokenAccountBalance,
32 },
33};
34use solana_clock::{Clock, Slot, UnixTimestamp};
35use solana_commitment_config::{CommitmentConfig, CommitmentLevel};
36use solana_epoch_info::EpochInfo;
37use solana_hash::Hash;
38use solana_loader_v3_interface::{get_program_data_address, state::UpgradeableLoaderState};
39use solana_message::{
40 Message, SimpleAddressLoader, VersionedMessage,
41 compiled_instruction::CompiledInstruction,
42 v0::{LoadedAddresses, MessageAddressTableLookup},
43};
44use solana_pubkey::Pubkey;
45use solana_rpc_client_api::response::{SlotInfo, SlotUpdate};
46use solana_signature::Signature;
47use solana_transaction::{sanitized::SanitizedTransaction, versioned::VersionedTransaction};
48use solana_transaction_error::TransactionError;
49use solana_transaction_status::{
50 EncodedConfirmedTransactionWithStatusMeta,
51 TransactionConfirmationStatus as SolanaTransactionConfirmationStatus, UiConfirmedBlock,
52 UiTransactionEncoding, VersionedTransactionWithStatusMeta, extract_and_fmt_memos,
53};
54use surfpool_types::{
55 AccountSnapshot, ComputeUnitsEstimationResult, ExecutionCapture, ExportSnapshotConfig, Idl,
56 KeyedProfileResult, ProfileResult, RpcProfileResultConfig, RunbookExecutionStatusReport,
57 SimnetCommand, SimnetEvent, TransactionConfirmationStatus, TransactionStatusEvent,
58 UiKeyedProfileResult, UuidOrSignature, VersionedIdl,
59};
60use tokio::sync::RwLock;
61use txtx_addon_kit::indexmap::IndexSet;
62use uuid::Uuid;
63
64use super::{
65 AccountFactory, GetAccountResult, GetTransactionResult, GeyserEvent, SignatureSubscriptionType,
66 SurfnetSvm, remote::SurfnetRemoteClient,
67};
68use crate::{
69 error::{AirdropError, SurfpoolError, SurfpoolResult},
70 helpers::time_travel::calculate_time_travel_clock,
71 rpc::utils::{convert_transaction_metadata_from_canonical, verify_pubkey},
72 storage::StorageResult,
73 surfnet::FINALIZATION_SLOT_THRESHOLD,
74 types::{
75 GeyserAccountUpdate, OfflineAccountConfig, RemoteRpcResult, SurfnetTransactionStatus,
76 TimeTravelConfig, TokenAccount, TransactionLoadedAddresses, TransactionWithStatusMeta,
77 },
78};
79
80enum ProcessTransactionResult {
81 Success(TransactionMetadata),
82 SimulationFailure(FailedTransactionMetadata),
83 ExecutionFailure(FailedTransactionMetadata),
84}
85
86pub struct SvmAccessContext<T> {
87 pub slot: Slot,
88 pub latest_epoch_info: EpochInfo,
89 pub latest_blockhash: Hash,
90 pub inner: T,
91}
92
93impl<T> SvmAccessContext<T> {
94 pub fn new(slot: Slot, latest_epoch_info: EpochInfo, latest_blockhash: Hash, inner: T) -> Self {
95 Self {
96 slot,
97 latest_blockhash,
98 latest_epoch_info,
99 inner,
100 }
101 }
102
103 pub fn inner(&self) -> &T {
104 &self.inner
105 }
106
107 pub fn with_new_value<N>(self, inner: N) -> SvmAccessContext<N> {
108 SvmAccessContext {
109 slot: self.slot,
110 latest_blockhash: self.latest_blockhash,
111 latest_epoch_info: self.latest_epoch_info,
112 inner,
113 }
114 }
115}
116
117pub type SurfpoolContextualizedResult<T> = SurfpoolResult<SvmAccessContext<T>>;
118
119fn snapshot_load_priority(account: &Account) -> u8 {
126 if account.owner != solana_sdk_ids::bpf_loader_upgradeable::id() {
127 return 1;
128 }
129
130 let programdata_metadata_size = UpgradeableLoaderState::size_of_programdata_metadata();
131 if !account.executable
132 && account.data.len() >= programdata_metadata_size
133 && matches!(
134 bincode::deserialize::<UpgradeableLoaderState>(
135 &account.data[..programdata_metadata_size]
136 ),
137 Ok(UpgradeableLoaderState::ProgramData { .. })
138 )
139 {
140 return 0;
141 }
142
143 if account.executable
144 && matches!(
145 bincode::deserialize::<UpgradeableLoaderState>(&account.data),
146 Ok(UpgradeableLoaderState::Program { .. })
147 )
148 {
149 return 2;
150 }
151
152 1
153}
154
155pub struct SurfnetSvmLocker(pub Arc<RwLock<SurfnetSvm>>);
165
166impl Clone for SurfnetSvmLocker {
167 fn clone(&self) -> Self {
168 Self(self.0.clone())
169 }
170}
171
172impl SurfnetSvmLocker {
174 pub fn shutdown(&self) {
177 let read_lock = self.0.clone();
178 tokio::task::block_in_place(move || {
179 let read_guard = read_lock.blocking_read();
180 read_guard.shutdown();
181 });
182 }
183
184 pub fn with_svm_reader<T, F>(&self, reader: F) -> T
190 where
191 F: FnOnce(&SurfnetSvm) -> T + Send + Sync,
192 {
193 let read_lock = self.0.clone();
194 tokio::task::block_in_place(move || {
195 let read_guard = read_lock.blocking_read();
196 reader(&read_guard)
197 })
198 }
199
200 fn with_contextualized_svm_reader<T, F>(&self, reader: F) -> SvmAccessContext<T>
203 where
204 F: Fn(&SurfnetSvm) -> T + Send + Sync,
205 T: Send + 'static,
206 {
207 let read_lock = self.0.clone();
208 tokio::task::block_in_place(move || {
209 let read_guard = read_lock.blocking_read();
210 let res = reader(&read_guard);
211
212 SvmAccessContext::new(
213 read_guard.get_latest_absolute_slot(),
214 read_guard.latest_epoch_info(),
215 read_guard.latest_blockhash(),
216 res,
217 )
218 })
219 }
220
221 pub fn with_svm_writer<T, F>(&self, writer: F) -> T
227 where
228 F: FnOnce(&mut SurfnetSvm) -> T + Send + Sync,
229 T: Send + 'static,
230 {
231 let write_lock = self.0.clone();
232 tokio::task::block_in_place(move || {
233 let mut write_guard = write_lock.blocking_write();
234 writer(&mut write_guard)
235 })
236 }
237}
238
239impl SurfnetSvmLocker {
241 pub fn new(svm: SurfnetSvm) -> Self {
243 Self(Arc::new(RwLock::new(svm)))
244 }
245
246 pub async fn initialize(&self, remote_ctx: &Option<SurfnetRemoteClient>) -> SurfpoolResult<()> {
248 let Some(remote_client) = remote_ctx else {
249 return Ok(());
250 };
251
252 let (mut epoch_info, epoch_schedule) = {
253 let epoch_info = remote_client.get_epoch_info().await?;
254 let epoch_schedule = remote_client.get_epoch_schedule().await?;
255 (epoch_info, epoch_schedule)
256 };
257 epoch_info.transaction_count = None;
258
259 self.with_svm_writer(move |svm_writer| {
260 svm_writer.initialize(epoch_info, epoch_schedule);
261 });
262 Ok(())
263 }
264}
265
266impl SurfnetSvmLocker {
268 fn filter_downloaded_account_result(
270 requested_pubkey: &Pubkey,
271 result: GetAccountResult,
272 offline_owners: &[Pubkey],
273 ) -> GetAccountResult {
274 match result {
275 GetAccountResult::FoundAccount(_, account, _)
276 | GetAccountResult::FoundProgramAccount((_, account), _)
277 | GetAccountResult::FoundTokenAccount((_, account), _)
278 if offline_owners.contains(&account.owner) =>
279 {
280 GetAccountResult::None(*requested_pubkey)
281 }
282 other => other,
283 }
284 }
285
286 pub fn get_account_local(&self, pubkey: &Pubkey) -> SvmAccessContext<GetAccountResult> {
288 self.with_contextualized_svm_reader(|svm_reader| {
289 return svm_reader.inner.get_account_result(pubkey).unwrap();
290 })
291 }
292
293 pub async fn get_account_local_then_remote(
297 &self,
298 client: &SurfnetRemoteClient,
299 pubkey: &Pubkey,
300 commitment_config: CommitmentConfig,
301 ) -> SurfpoolContextualizedResult<GetAccountResult> {
302 let result = self.get_account_local(pubkey);
303
304 if result.inner.is_none() {
305 let is_offline = self.is_account_offline(pubkey);
306
307 if !is_offline {
308 let offline_owners = self.get_offline_account_owners();
309 let remote_account = client.get_account(pubkey, commitment_config).await?;
310 Ok(
311 result.with_new_value(Self::filter_downloaded_account_result(
312 pubkey,
313 remote_account,
314 &offline_owners,
315 )),
316 )
317 } else {
318 Ok(result)
319 }
320 } else {
321 Ok(result)
322 }
323 }
324
325 pub async fn get_account(
327 &self,
328 remote_ctx: &Option<(SurfnetRemoteClient, CommitmentConfig)>,
329 pubkey: &Pubkey,
330 factory: Option<AccountFactory>,
331 ) -> SurfpoolContextualizedResult<GetAccountResult> {
332 let result = if let Some((remote_client, commitment_config)) = remote_ctx {
333 self.get_account_local_then_remote(remote_client, pubkey, *commitment_config)
334 .await?
335 } else {
336 self.get_account_local(pubkey)
337 };
338
339 match (&result.inner, factory) {
340 (&GetAccountResult::None(_), Some(factory)) => {
341 let default = factory(self.clone());
342 Ok(result.with_new_value(default))
343 }
344 _ => Ok(result),
345 }
346 }
347 pub fn get_multiple_accounts_local(
349 &self,
350 pubkeys: &[Pubkey],
351 ) -> SvmAccessContext<Vec<GetAccountResult>> {
352 self.with_contextualized_svm_reader(|svm_reader| {
353 let mut accounts = vec![];
354
355 for pubkey in pubkeys {
356 let result = svm_reader.inner.get_account_result(pubkey).unwrap();
357 if result.is_none() {};
358 accounts.push(result);
359 }
360 accounts
361 })
362 }
363
364 pub async fn get_multiple_accounts_with_remote_fallback(
370 &self,
371 client: &SurfnetRemoteClient,
372 pubkeys: &[Pubkey],
373 commitment_config: CommitmentConfig,
374 ) -> SurfpoolContextualizedResult<Vec<GetAccountResult>> {
375 let SvmAccessContext {
376 slot,
377 latest_epoch_info,
378 latest_blockhash,
379 inner: local_results,
380 } = self.get_multiple_accounts_local(pubkeys);
381
382 let mut missing_accounts = Vec::new();
384 for result in &local_results {
385 let GetAccountResult::None(pubkey) = result else {
386 continue;
387 };
388 if self.is_account_offline(pubkey) {
389 continue;
390 }
391 missing_accounts.push(*pubkey);
392 }
393
394 if missing_accounts.is_empty() {
395 return Ok(SvmAccessContext::new(
397 slot,
398 latest_epoch_info,
399 latest_blockhash,
400 local_results,
401 ));
402 }
403 debug!(
404 "Missing accounts will be fetched: {}",
405 missing_accounts.iter().join(", ")
406 );
407 let remote_results = client
409 .get_multiple_accounts(&missing_accounts, commitment_config)
410 .await?;
411
412 let offline_owners = self.get_offline_account_owners();
414 let remote_map: HashMap<Pubkey, GetAccountResult> = missing_accounts
415 .iter()
416 .copied()
417 .zip(remote_results.into_iter())
418 .map(|(requested_pubkey, result)| {
419 (
420 requested_pubkey,
421 Self::filter_downloaded_account_result(
422 &requested_pubkey,
423 result,
424 &offline_owners,
425 ),
426 )
427 })
428 .collect();
429
430 let combined_results: Vec<GetAccountResult> = pubkeys
433 .iter()
434 .zip(local_results.into_iter())
435 .map(|(pubkey, local_result)| {
436 match local_result {
437 GetAccountResult::None(_) => remote_map
438 .get(pubkey)
439 .cloned()
440 .unwrap_or(GetAccountResult::None(*pubkey)),
441 found => {
442 debug!("Keeping local account: {}", pubkey);
443 found
444 } }
446 })
447 .collect();
448
449 Ok(SvmAccessContext::new(
450 slot,
451 latest_epoch_info,
452 latest_blockhash,
453 combined_results,
454 ))
455 }
456
457 pub async fn get_multiple_accounts(
459 &self,
460 remote_ctx: &Option<(SurfnetRemoteClient, CommitmentConfig)>,
461 pubkeys: &[Pubkey],
462 factory: Option<AccountFactory>,
463 ) -> SurfpoolContextualizedResult<Vec<GetAccountResult>> {
464 let results = if let Some((remote_client, commitment_config)) = remote_ctx {
465 self.get_multiple_accounts_with_remote_fallback(
466 remote_client,
467 pubkeys,
468 *commitment_config,
469 )
470 .await?
471 } else {
472 self.get_multiple_accounts_local(pubkeys)
473 };
474
475 let mut combined = Vec::with_capacity(results.inner.len());
476 for result in results.inner.clone() {
477 match (&result, &factory) {
478 (&GetAccountResult::None(_), Some(factory)) => {
479 let default = factory(self.clone());
480 combined.push(default);
481 }
482 _ => combined.push(result),
483 }
484 }
485 Ok(results.with_new_value(combined))
486 }
487
488 pub async fn load_snapshot(
503 &self,
504 snapshot: &BTreeMap<String, Option<AccountSnapshot>>,
505 remote_client: Option<&SurfnetRemoteClient>,
506 commitment_config: CommitmentConfig,
507 ) -> SurfpoolResult<usize> {
508 use std::str::FromStr;
509
510 use base64::{Engine, prelude::BASE64_STANDARD};
511
512 let mut loaded_count = 0;
513
514 let mut accounts_to_load: Vec<(Pubkey, Account)> = Vec::new();
516 let mut pubkeys_to_fetch: Vec<Pubkey> = Vec::new();
517
518 for (pubkey_str, account_snapshot_opt) in snapshot.iter() {
519 let pubkey = match Pubkey::from_str(pubkey_str) {
520 Ok(pk) => pk,
521 Err(e) => {
522 self.with_svm_reader(|svm| {
523 let _ = svm.simnet_events_tx.send(SimnetEvent::warn(format!(
524 "Skipping invalid pubkey '{}' in snapshot: {}",
525 pubkey_str, e
526 )));
527 });
528 continue;
529 }
530 };
531
532 match account_snapshot_opt {
533 Some(account_snapshot) => {
534 let data = match BASE64_STANDARD.decode(&account_snapshot.data) {
536 Ok(d) => d,
537 Err(e) => {
538 self.with_svm_reader(|svm| {
539 let _ = svm.simnet_events_tx.send(SimnetEvent::warn(format!(
540 "Skipping account '{}': failed to decode base64 data: {}",
541 pubkey_str, e
542 )));
543 });
544 continue;
545 }
546 };
547
548 let owner = match Pubkey::from_str(&account_snapshot.owner) {
550 Ok(pk) => pk,
551 Err(e) => {
552 self.with_svm_reader(|svm| {
553 let _ = svm.simnet_events_tx.send(SimnetEvent::warn(format!(
554 "Skipping account '{}': invalid owner pubkey: {}",
555 pubkey_str, e
556 )));
557 });
558 continue;
559 }
560 };
561
562 let account = Account {
564 lamports: account_snapshot.lamports,
565 data,
566 owner,
567 executable: account_snapshot.executable,
568 rent_epoch: account_snapshot.rent_epoch,
569 };
570
571 accounts_to_load.push((pubkey, account));
572 }
573 None => {
574 if remote_client.is_some() {
576 pubkeys_to_fetch.push(pubkey);
577 }
578 }
579 }
580 }
581
582 if let Some(client) = remote_client {
584 if !pubkeys_to_fetch.is_empty() {
585 self.with_svm_reader(|svm| {
586 let _ = svm.simnet_events_tx.send(SimnetEvent::info(format!(
587 "Fetching {} accounts from remote RPC for snapshot",
588 pubkeys_to_fetch.len()
589 )));
590 });
591
592 match client
593 .get_multiple_accounts(&pubkeys_to_fetch, commitment_config)
594 .await
595 {
596 Ok(remote_results) => {
597 for (pubkey, result) in pubkeys_to_fetch.iter().zip(remote_results) {
598 match result {
599 GetAccountResult::FoundAccount(_, account, _) => {
600 accounts_to_load.push((*pubkey, account));
601 }
602 GetAccountResult::FoundProgramAccount(
603 (program_pubkey, program_account),
604 (data_pubkey, data_account_opt),
605 ) => {
606 accounts_to_load.push((program_pubkey, program_account));
607 if let Some(data_account) = data_account_opt {
608 accounts_to_load.push((data_pubkey, data_account));
609 }
610 }
611 GetAccountResult::FoundTokenAccount(
612 (token_pubkey, token_account),
613 (mint_pubkey, mint_account_opt),
614 ) => {
615 accounts_to_load.push((token_pubkey, token_account));
616 if let Some(mint_account) = mint_account_opt {
617 accounts_to_load.push((mint_pubkey, mint_account));
618 }
619 }
620 GetAccountResult::None(_) => {
621 }
623 }
624 }
625 }
626 Err(e) => {
627 self.with_svm_reader(|svm| {
628 let _ = svm.simnet_events_tx.send(SimnetEvent::warn(format!(
629 "Failed to fetch some accounts from remote: {}",
630 e
631 )));
632 });
633 }
634 }
635 }
636 }
637
638 accounts_to_load.sort_by_key(|(_, account)| snapshot_load_priority(account));
639
640 self.with_svm_writer(|svm| {
642 let slot = svm.get_latest_absolute_slot();
643
644 for (pubkey, account) in accounts_to_load {
645 if let Err(e) = svm.set_account(&pubkey, account.clone()) {
646 let _ = svm.simnet_events_tx.send(SimnetEvent::warn(format!(
647 "Failed to set account '{}': {}",
648 pubkey, e
649 )));
650 continue;
651 }
652
653 let write_version = svm.increment_write_version();
655 let _ = svm.geyser_events_tx.send(GeyserEvent::StartupAccountUpdate(
656 GeyserAccountUpdate::startup_update(pubkey, account, slot, write_version),
657 ));
658
659 loaded_count += 1;
660 }
661 });
662
663 Ok(loaded_count)
664 }
665
666 pub fn get_largest_accounts_local(
668 &self,
669 config: RpcLargestAccountsConfig,
670 ) -> SurfpoolContextualizedResult<Vec<RpcAccountBalance>> {
671 let res: Vec<RpcAccountBalance> = self.with_svm_reader(|svm_reader| {
672 let non_circulating_accounts: Vec<_> = svm_reader
673 .non_circulating_accounts
674 .iter()
675 .flat_map(|acct| verify_pubkey(acct))
676 .collect();
677
678 let ordered_accounts = svm_reader
679 .get_all_accounts()?
680 .into_iter()
681 .sorted_by(|a, b| b.1.lamports().cmp(&a.1.lamports()))
682 .collect::<Vec<_>>();
683 let ordered_filtered_accounts = match config.filter {
684 Some(RpcLargestAccountsFilter::NonCirculating) => ordered_accounts
685 .into_iter()
686 .filter(|(pubkey, _)| non_circulating_accounts.contains(pubkey))
687 .collect::<Vec<_>>(),
688 Some(RpcLargestAccountsFilter::Circulating) => ordered_accounts
689 .into_iter()
690 .filter(|(pubkey, _)| !non_circulating_accounts.contains(pubkey))
691 .collect::<Vec<_>>(),
692 None => ordered_accounts,
693 };
694
695 Ok::<Vec<RpcAccountBalance>, SurfpoolError>(
696 ordered_filtered_accounts
697 .iter()
698 .take(20)
699 .map(|(pubkey, account)| RpcAccountBalance {
700 address: pubkey.to_string(),
701 lamports: account.lamports(),
702 })
703 .collect(),
704 )
705 })?;
706 Ok(self.with_contextualized_svm_reader(|_| res.to_owned()))
707 }
708
709 pub async fn get_largest_accounts_local_then_remote(
710 &self,
711 client: &SurfnetRemoteClient,
712 config: RpcLargestAccountsConfig,
713 commitment_config: CommitmentConfig,
714 ) -> SurfpoolContextualizedResult<Vec<RpcAccountBalance>> {
715 {
718 let remote_non_circulating_pubkeys_result = client
719 .get_largest_accounts(Some(RpcLargestAccountsConfig {
720 filter: Some(RpcLargestAccountsFilter::NonCirculating),
721 ..config
722 }))
723 .await?;
724
725 let (mut remote_non_circulating_pubkeys, mut remote_circulating_pubkeys) =
726 match remote_non_circulating_pubkeys_result {
727 RemoteRpcResult::Ok(non_circulating_accounts) => {
728 let remote_circulating_pubkeys_result = client
729 .get_largest_accounts(Some(RpcLargestAccountsConfig {
730 filter: Some(RpcLargestAccountsFilter::Circulating),
731 ..config
732 }))
733 .await?;
734
735 let remote_circulating_pubkeys = match remote_circulating_pubkeys_result {
736 RemoteRpcResult::Ok(circulating_accounts) => circulating_accounts,
737 RemoteRpcResult::MethodNotSupported => {
738 unreachable!()
739 }
740 };
741 (
742 non_circulating_accounts
743 .iter()
744 .map(|account_balance| verify_pubkey(&account_balance.address))
745 .collect::<SurfpoolResult<Vec<_>>>()?,
746 remote_circulating_pubkeys
747 .iter()
748 .map(|account_balance| verify_pubkey(&account_balance.address))
749 .collect::<SurfpoolResult<Vec<_>>>()?,
750 )
751 }
752 RemoteRpcResult::MethodNotSupported => {
753 let tx = self.simnet_events_tx();
754 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."));
755 (vec![], vec![])
756 }
757 };
758
759 let mut combined = Vec::with_capacity(
760 remote_non_circulating_pubkeys.len() + remote_circulating_pubkeys.len(),
761 );
762 combined.append(&mut remote_non_circulating_pubkeys);
763 combined.append(&mut remote_circulating_pubkeys);
764
765 let get_account_results = self
766 .get_multiple_accounts_with_remote_fallback(client, &combined, commitment_config)
767 .await?
768 .inner;
769
770 self.write_multiple_account_updates(&get_account_results);
771 }
772
773 self.get_largest_accounts_local(config)
776 }
777
778 pub async fn get_largest_accounts(
779 &self,
780 remote_ctx: &Option<(SurfnetRemoteClient, CommitmentConfig)>,
781 config: RpcLargestAccountsConfig,
782 ) -> SurfpoolContextualizedResult<Vec<RpcAccountBalance>> {
783 if let Some((remote_client, commitment_config)) = remote_ctx {
784 self.get_largest_accounts_local_then_remote(remote_client, config, *commitment_config)
785 .await
786 } else {
787 self.get_largest_accounts_local(config)
788 }
789 }
790
791 pub fn account_to_rpc_keyed_account<T: ReadableAccount + Send + Sync>(
792 &self,
793 pubkey: &Pubkey,
794 account: &T,
795 config: &RpcAccountInfoConfig,
796 token_mint: Option<Pubkey>,
797 ) -> RpcKeyedAccount {
798 self.with_svm_reader(|svm_reader| {
799 svm_reader.account_to_rpc_keyed_account(pubkey, account, config, token_mint)
800 })
801 }
802}
803
804fn signatures_for_address_remote_config(
818 config: Option<&RpcSignaturesForAddressConfig>,
819 before_is_local: bool,
820 until_is_local: bool,
821 remaining_limit: usize,
822) -> Option<RpcSignaturesForAddressConfig> {
823 if until_is_local {
824 return None;
825 }
826 let base = config.cloned().unwrap_or_default();
827 Some(RpcSignaturesForAddressConfig {
828 before: if before_is_local { None } else { base.before },
829 until: base.until,
830 limit: Some(remaining_limit),
831 commitment: base.commitment,
832 min_context_slot: base.min_context_slot,
833 })
834}
835
836impl SurfnetSvmLocker {
838 pub fn get_signatures_for_address_local(
850 &self,
851 pubkey: &Pubkey,
852 config: Option<&RpcSignaturesForAddressConfig>,
853 ) -> SvmAccessContext<Vec<RpcConfirmedTransactionStatusWithSignature>> {
854 let before = config.and_then(|c| c.before.as_ref());
855 let until = config.and_then(|c| c.until.as_ref());
856 let limit = config.and_then(|c| c.limit).unwrap_or(1000);
857 let min_context_slot = config.and_then(|c| c.min_context_slot).unwrap_or_default();
858
859 self.with_contextualized_svm_reader(move |svm_reader| {
860 let current_slot = svm_reader.get_latest_absolute_slot();
861
862 let sigs: Vec<_> = svm_reader
863 .transactions
864 .into_iter()
865 .map(|iter| {
866 iter.filter_map(|(sig, status)| {
867 let (
868 TransactionWithStatusMeta {
869 slot,
870 transaction,
871 meta,
872 },
873 _,
874 ) = status
875 .as_processed()
876 .expect("expected processed transaction");
877
878 if slot < min_context_slot {
879 return None;
880 }
881
882 if !transaction.message.static_account_keys().contains(pubkey) {
885 return None;
886 }
887
888 let confirmation_status = match current_slot {
890 cs if cs == slot => SolanaTransactionConfirmationStatus::Processed,
891 cs if cs < slot + FINALIZATION_SLOT_THRESHOLD => {
892 SolanaTransactionConfirmationStatus::Confirmed
893 }
894 _ => SolanaTransactionConfirmationStatus::Finalized,
895 };
896
897 let tx_with_meta = VersionedTransactionWithStatusMeta { transaction, meta };
902 let memo = extract_and_fmt_memos(&tx_with_meta);
903 let err = match &tx_with_meta.meta.status {
904 Ok(_) => None,
905 Err(e) => Some(e.clone().into()),
906 };
907
908 let block_time =
912 Some((svm_reader.calculate_block_time_for_slot(slot) / 1_000) as i64);
913
914 Some(RpcConfirmedTransactionStatusWithSignature {
915 err,
916 slot,
917 memo,
918 block_time,
919 confirmation_status: Some(confirmation_status),
920 signature: sig,
921 transaction_index: None,
922 })
923 })
924 .collect()
925 })
926 .unwrap_or_default();
927
928 let unique_slots: HashSet<u64> = sigs.iter().map(|s| s.slot).collect();
931 let mut sig_position: HashMap<String, usize> = HashMap::new();
932 for slot in unique_slots {
933 if let Ok(Some(block_header)) = svm_reader.blocks.get(&slot) {
934 for (idx, block_sig) in block_header.signatures.iter().enumerate() {
935 sig_position.insert(block_sig.to_string(), idx);
936 }
937 }
938 }
939
940 let sigs: Vec<_> = sigs
941 .into_iter()
942 .sorted_by(|a, b| {
945 b.slot.cmp(&a.slot).then_with(|| {
946 let a_pos = sig_position.get(&a.signature).unwrap_or(&usize::MAX);
947 let b_pos = sig_position.get(&b.signature).unwrap_or(&usize::MAX);
948 b_pos.cmp(&a_pos)
949 })
950 })
951 .collect();
952
953 let window = {
954 let start = match before {
958 Some(before) => match sigs.iter().position(|sig| sig.signature.eq(before)) {
961 Some(idx) => idx + 1,
962 None => sigs.len(),
963 },
964 None => 0,
965 };
966
967 let end = match until {
968 Some(until) => {
972 match sigs[start..].iter().position(|sig| sig.signature.eq(until)) {
973 Some(offset) => start + offset,
974 None => sigs.len(),
975 }
976 }
977 None => sigs.len(),
978 };
979 start..end
980 };
981
982 sigs[window].iter().take(limit).cloned().collect()
984 })
985 }
986
987 pub async fn get_signatures_for_address_local_then_remote(
988 &self,
989 client: &SurfnetRemoteClient,
990 pubkey: &Pubkey,
991 config: Option<&RpcSignaturesForAddressConfig>,
992 ) -> SurfpoolContextualizedResult<Vec<RpcConfirmedTransactionStatusWithSignature>> {
993 let results = self.get_signatures_for_address_local(pubkey, config);
994 let limit = config.and_then(|c| c.limit).unwrap_or(1000);
995
996 let SvmAccessContext {
997 slot,
998 latest_blockhash,
999 latest_epoch_info,
1000 inner: mut combined_results,
1001 } = results;
1002 if combined_results.len() < limit {
1003 let (before_is_local, until_is_local) =
1004 self.with_svm_reader(|svm_reader| -> StorageResult<(bool, bool)> {
1005 let is_local = |sig: &String| -> StorageResult<bool> {
1006 Ok(svm_reader.transactions.get(sig)?.is_some())
1007 };
1008 let before_local = match config.and_then(|c| c.before.as_ref()) {
1009 Some(sig) => is_local(sig)?,
1010 None => false,
1011 };
1012 let until_local = match config.and_then(|c| c.until.as_ref()) {
1013 Some(sig) => is_local(sig)?,
1014 None => false,
1015 };
1016 Ok((before_local, until_local))
1017 })?;
1018
1019 let remaining_limit = limit - combined_results.len();
1020 if let Some(remote_config) = signatures_for_address_remote_config(
1021 config,
1022 before_is_local,
1023 until_is_local,
1024 remaining_limit,
1025 ) {
1026 let mut remote_results = client
1027 .get_signatures_for_address(pubkey, Some(&remote_config))
1028 .await?;
1029 combined_results.append(&mut remote_results);
1030 combined_results.truncate(limit);
1033 }
1034 }
1035
1036 Ok(SvmAccessContext::new(
1037 slot,
1038 latest_epoch_info,
1039 latest_blockhash,
1040 combined_results,
1041 ))
1042 }
1043
1044 pub async fn get_signatures_for_address(
1045 &self,
1046 remote_ctx: &Option<(SurfnetRemoteClient, ())>,
1047 pubkey: &Pubkey,
1048 config: Option<&RpcSignaturesForAddressConfig>,
1049 ) -> SurfpoolContextualizedResult<Vec<RpcConfirmedTransactionStatusWithSignature>> {
1050 let results = if let Some((remote_client, _)) = remote_ctx {
1051 self.get_signatures_for_address_local_then_remote(remote_client, pubkey, config)
1052 .await?
1053 } else {
1054 self.get_signatures_for_address_local(pubkey, config)
1055 };
1056
1057 Ok(results)
1058 }
1059}
1060
1061impl SurfnetSvmLocker {
1063 pub async fn get_transaction(
1065 &self,
1066 remote_ctx: &Option<SurfnetRemoteClient>,
1067 signature: &Signature,
1068 config: RpcTransactionConfig,
1069 ) -> SurfpoolResult<GetTransactionResult> {
1070 if let Some(remote_client) = remote_ctx {
1071 self.get_transaction_local_then_remote(remote_client, signature, config)
1072 .await
1073 } else {
1074 self.get_transaction_local(signature, &config)
1075 }
1076 }
1077
1078 pub fn store_bundle(&self, bundle_id: String, signatures: Vec<String>) -> SurfpoolResult<()> {
1080 self.with_svm_writer(|svm_writer| svm_writer.jito_bundles.store(bundle_id, signatures))?;
1081 Ok(())
1082 }
1083
1084 pub fn get_bundle(&self, bundle_id: &str) -> Option<Vec<String>> {
1090 self.with_svm_reader(|svm_reader| {
1091 svm_reader
1092 .jito_bundles
1093 .get(&bundle_id.to_string())
1094 .unwrap_or_default()
1095 })
1096 }
1097
1098 pub fn get_transaction_local(
1100 &self,
1101 signature: &Signature,
1102 config: &RpcTransactionConfig,
1103 ) -> SurfpoolResult<GetTransactionResult> {
1104 self.with_svm_reader(|svm_reader| {
1105 let latest_absolute_slot = svm_reader.get_latest_absolute_slot();
1106
1107 let Some(entry) = svm_reader.transactions.get(&signature.to_string())? else {
1108 return Ok(GetTransactionResult::None(*signature));
1109 };
1110
1111 let (transaction_with_status_meta, _) = entry.expect_processed();
1112 let slot = transaction_with_status_meta.slot;
1113 let block_time = svm_reader
1114 .blocks
1115 .get(&slot)?
1116 .map(|b| (b.block_time / 1_000) as UnixTimestamp)
1117 .unwrap_or(0);
1118 let encoded = transaction_with_status_meta.encode(
1119 config.encoding.unwrap_or(UiTransactionEncoding::JsonParsed),
1120 config.max_supported_transaction_version,
1121 true,
1122 )?;
1123 Ok(GetTransactionResult::found_transaction(
1124 *signature,
1125 EncodedConfirmedTransactionWithStatusMeta {
1126 slot,
1127 transaction: encoded,
1128 block_time: Some(block_time),
1129 transaction_index: None,
1130 },
1131 latest_absolute_slot,
1132 ))
1133 })
1134 }
1135
1136 pub async fn get_transaction_local_then_remote(
1138 &self,
1139 client: &SurfnetRemoteClient,
1140 signature: &Signature,
1141 config: RpcTransactionConfig,
1142 ) -> SurfpoolResult<GetTransactionResult> {
1143 let local_result = self.get_transaction_local(signature, &config)?;
1144 let latest_absolute_slot = self.get_latest_absolute_slot();
1145 if local_result.is_none() {
1146 Ok(client
1147 .get_transaction(*signature, config, latest_absolute_slot)
1148 .await)
1149 } else {
1150 Ok(local_result)
1151 }
1152 }
1153}
1154
1155impl SurfnetSvmLocker {
1157 #[allow(clippy::result_large_err)]
1159 pub fn simulate_transaction(
1160 &self,
1161 transaction: VersionedTransaction,
1162 sigverify: bool,
1163 ) -> Result<SimulatedTransactionInfo, FailedTransactionMetadata> {
1164 self.with_svm_reader(move |svm_reader| {
1165 svm_reader.simulate_transaction(transaction, sigverify)
1166 })
1167 }
1168
1169 pub fn is_instruction_profiling_enabled(&self) -> bool {
1170 self.with_svm_reader(|svm_reader| svm_reader.instruction_profiling_enabled)
1171 }
1172
1173 pub fn get_profiling_map_capacity(&self) -> usize {
1174 self.with_svm_reader(|svm_reader| svm_reader.max_profiles)
1175 }
1176
1177 pub async fn process_transaction(
1178 &self,
1179 remote_ctx: &Option<(SurfnetRemoteClient, CommitmentConfig)>,
1180 transaction: VersionedTransaction,
1181 status_tx: Sender<TransactionStatusEvent>,
1182 skip_preflight: bool,
1183 sigverify: bool,
1184 ) -> SurfpoolResult<()> {
1185 let do_propagate_status_updates = true;
1186 let signature = transaction.signatures[0];
1187 let profile_result = match self
1188 .fetch_all_tx_accounts_then_process_tx_returning_profile_res(
1189 remote_ctx,
1190 transaction,
1191 &status_tx,
1192 skip_preflight,
1193 sigverify,
1194 do_propagate_status_updates,
1195 )
1196 .await
1197 {
1198 Ok(result) => result,
1199 Err(e) => {
1200 let err_str = e.to_string();
1208 if err_str.contains("Account loaded twice") {
1209 let _ = status_tx.try_send(TransactionStatusEvent::SimulationFailure((
1210 TransactionError::AccountLoadedTwice,
1211 surfpool_types::TransactionMetadata::default(),
1212 )));
1213 } else {
1214 let _ =
1215 status_tx.try_send(TransactionStatusEvent::VerificationFailure(err_str));
1216 }
1217 return Err(e);
1218 }
1219 };
1220
1221 self.with_svm_writer(|svm_writer| {
1222 svm_writer.write_executed_profile_result(signature, profile_result)
1223 })?;
1224
1225 Ok(())
1226 }
1227
1228 pub async fn profile_transaction(
1229 &self,
1230 remote_ctx: &Option<(SurfnetRemoteClient, CommitmentConfig)>,
1231 transaction: VersionedTransaction,
1232 tag: Option<String>,
1233 ) -> SurfpoolContextualizedResult<Uuid> {
1234 let svm_clone = self.with_svm_reader(|svm_reader| svm_reader.clone_for_profiling());
1237
1238 let svm_locker = SurfnetSvmLocker::new(svm_clone);
1239
1240 let (status_tx, _) = crossbeam_channel::unbounded();
1241
1242 let skip_preflight = true; let sigverify = true; let do_propagate_status_updates = false; let mut profile_result = svm_locker
1246 .fetch_all_tx_accounts_then_process_tx_returning_profile_res(
1247 remote_ctx,
1248 transaction,
1249 &status_tx,
1250 skip_preflight,
1251 sigverify,
1252 do_propagate_status_updates,
1253 )
1254 .await?;
1255
1256 let uuid = Uuid::new_v4();
1257 profile_result.key = UuidOrSignature::Uuid(uuid);
1258
1259 self.with_svm_writer(|svm_writer| {
1260 svm_writer.write_simulated_profile_result(uuid, tag, profile_result)
1261 })?;
1262
1263 Ok(self.with_contextualized_svm_reader(|_| uuid))
1264 }
1265
1266 pub(crate) async fn fetch_all_tx_accounts_then_process_tx_returning_profile_res(
1267 &self,
1268 remote_ctx: &Option<(SurfnetRemoteClient, CommitmentConfig)>,
1269 transaction: VersionedTransaction,
1270 status_tx: &Sender<TransactionStatusEvent>,
1271 skip_preflight: bool,
1272 sigverify: bool,
1273 do_propagate: bool,
1274 ) -> SurfpoolResult<KeyedProfileResult> {
1275 let signature = transaction.signatures[0];
1276
1277 if sigverify {
1279 self.with_svm_reader(|svm_reader| svm_reader.sigverify(&transaction))
1280 .map_err(|e| Into::<SurfpoolError>::into(e.err))?;
1281 }
1282
1283 let latest_absolute_slot = self.with_svm_writer(|svm_writer| {
1284 let latest_absolute_slot = svm_writer.get_latest_absolute_slot();
1285 svm_writer.notify_signature_subscribers(
1286 SignatureSubscriptionType::received(),
1287 &signature,
1288 latest_absolute_slot,
1289 None,
1290 );
1291
1292 latest_absolute_slot
1293 });
1294
1295 let tx_loaded_addresses = self
1298 .get_loaded_addresses(remote_ctx, &transaction.message)
1299 .await?;
1300
1301 if let Some(ref loaded) = tx_loaded_addresses {
1304 let static_keys: HashSet<&Pubkey> =
1305 transaction.message.static_account_keys().iter().collect();
1306 for loaded_key in loaded.all_loaded_addresses() {
1307 if static_keys.contains(loaded_key) {
1308 return Err(TransactionError::AccountLoadedTwice.into());
1309 }
1310 }
1311 }
1312
1313 let transaction_accounts = self.get_pubkeys_from_message(
1316 &transaction.message,
1317 tx_loaded_addresses
1318 .as_ref()
1319 .map(|l| l.all_loaded_addresses()),
1320 );
1321 debug!(
1322 "Transaction {} accounts inputs: {}",
1323 transaction.get_signature(),
1324 transaction_accounts.iter().join(", ")
1325 );
1326
1327 let account_updates = self
1328 .get_multiple_accounts(remote_ctx, &transaction_accounts, None)
1329 .await?
1330 .inner;
1331
1332 let alt_account_updates = self
1334 .get_multiple_accounts(
1335 remote_ctx,
1336 &tx_loaded_addresses
1337 .as_ref()
1338 .map(|l| l.alt_addresses())
1339 .unwrap_or_default(),
1340 None,
1341 )
1342 .await?
1343 .inner;
1344
1345 let readonly_account_states = transaction_accounts
1346 .iter()
1347 .enumerate()
1348 .filter_map(|(i, pubkey)| {
1349 if transaction.message.is_maybe_writable(i, None) {
1350 None
1351 } else {
1352 self.get_account_local(pubkey)
1353 .inner
1354 .map_account()
1355 .ok()
1356 .map(|a| (*pubkey, a))
1357 }
1358 })
1359 .collect::<HashMap<_, _>>();
1360
1361 self.with_svm_writer(|svm_writer| {
1362 for update in &account_updates {
1363 svm_writer.write_account_update(update.clone());
1364 }
1365 for update in alt_account_updates {
1366 svm_writer.write_account_update(update);
1367 }
1368 });
1369
1370 let pre_execution_capture = {
1371 let mut capture = ExecutionCapture::new();
1372 for account_update in account_updates.into_iter() {
1373 match account_update {
1374 GetAccountResult::None(pubkey) => {
1375 capture.insert(pubkey, None);
1376 }
1377 GetAccountResult::FoundAccount(pubkey, account, _)
1378 | GetAccountResult::FoundProgramAccount((pubkey, account), _)
1379 | GetAccountResult::FoundTokenAccount((pubkey, account), _) => {
1380 capture.insert(pubkey, Some(account));
1381 }
1382 }
1383 }
1384 capture
1385 };
1386
1387 let (accounts_before, token_accounts_before, token_programs) =
1388 self.with_svm_reader(|svm_reader| {
1389 let accounts_before = transaction_accounts
1390 .iter()
1391 .map(|p| svm_reader.inner.get_account(p))
1392 .collect::<Result<Vec<Option<Account>>, SurfpoolError>>()?;
1393
1394 let token_accounts_before = transaction_accounts
1395 .iter()
1396 .enumerate()
1397 .filter_map(|(i, p)| {
1398 svm_reader
1399 .token_accounts
1400 .get(&p.to_string())
1401 .ok()
1402 .flatten()
1403 .map(|a| (i, a))
1404 })
1405 .collect::<Vec<_>>();
1406
1407 let token_programs = token_accounts_before
1408 .iter()
1409 .map(|(i, ta)| {
1410 svm_reader
1411 .get_account(&transaction_accounts[*i])
1412 .map(|res| res.map(|a| a.owner).unwrap_or(ta.token_program_id()))
1413 })
1414 .collect::<Result<Vec<_>, SurfpoolError>>()?;
1415
1416 Ok::<
1417 (
1418 Vec<Option<Account>>,
1419 Vec<(usize, TokenAccount)>,
1420 Vec<Pubkey>,
1421 ),
1422 SurfpoolError,
1423 >((accounts_before, token_accounts_before, token_programs))
1424 })?;
1425
1426 let loaded_addresses = tx_loaded_addresses.as_ref().map(|l| l.loaded_addresses());
1427
1428 let ix_profiles = if self.is_instruction_profiling_enabled() {
1429 match self
1430 .generate_instruction_profiles(
1431 &transaction,
1432 &transaction_accounts,
1433 &tx_loaded_addresses,
1434 &accounts_before,
1435 &token_accounts_before,
1436 &token_programs,
1437 pre_execution_capture.clone(),
1438 &status_tx,
1439 )
1440 .await
1441 {
1442 Ok(profiles) => profiles,
1443 Err(e) => {
1444 let _ = self.simnet_events_tx().try_send(SimnetEvent::error(format!(
1445 "Failed to generate instruction profiles: {}",
1446 e
1447 )));
1448 None
1449 }
1450 }
1451 } else {
1452 None
1453 };
1454
1455 let profile_result = self
1456 .process_transaction_internal(
1457 transaction,
1458 skip_preflight,
1459 sigverify,
1460 &transaction_accounts,
1461 &loaded_addresses,
1462 &accounts_before,
1463 &token_accounts_before,
1464 &token_programs,
1465 pre_execution_capture,
1466 &status_tx,
1467 do_propagate,
1468 )
1469 .await?;
1470
1471 Ok(KeyedProfileResult::new(
1472 latest_absolute_slot,
1473 UuidOrSignature::Signature(signature),
1474 ix_profiles,
1475 profile_result,
1476 readonly_account_states,
1477 ))
1478 }
1479
1480 #[allow(clippy::too_many_arguments)]
1481 async fn generate_instruction_profiles(
1482 &self,
1483 transaction: &VersionedTransaction,
1484 transaction_accounts: &[Pubkey],
1485 loaded_addresses: &Option<TransactionLoadedAddresses>,
1486 accounts_before: &[Option<Account>],
1487 token_accounts_before: &[(usize, TokenAccount)],
1488 token_programs: &[Pubkey],
1489 pre_execution_capture: ExecutionCapture,
1490 status_tx: &Sender<TransactionStatusEvent>,
1491 ) -> SurfpoolResult<Option<Vec<ProfileResult>>> {
1492 let instructions = transaction.message.instructions();
1493 let ix_count = instructions.len();
1494 if ix_count == 0 {
1495 return Ok(None);
1496 }
1497 let mut ix_profile_results: Vec<ProfileResult> = vec![];
1500
1501 for idx in 1..=ix_count {
1502 let partial_transaction_res = self.create_partial_transaction(
1503 instructions,
1504 transaction_accounts,
1505 transaction,
1506 idx,
1507 loaded_addresses,
1508 );
1509
1510 let mut ix_required_accounts = IndexSet::new();
1511 for &account_idx in &instructions[idx - 1].accounts {
1512 ix_required_accounts.insert(transaction_accounts[account_idx as usize]);
1513 }
1514 ix_required_accounts
1515 .insert(transaction_accounts[instructions[idx - 1].program_id_index as usize]);
1516
1517 let Some(partial_tx) = partial_transaction_res else {
1518 debug!("Unable to create partial transaction");
1519 return Ok(None);
1520 };
1521
1522 let mut previous_execution_captures = ExecutionCapture::new();
1523 let mut previous_cus = 0;
1524 let mut previous_log_count = 0;
1525 for result in ix_profile_results.iter() {
1526 previous_execution_captures.extend(result.post_execution_capture.clone());
1527 previous_cus += result.compute_units_consumed;
1528 previous_log_count += result.log_messages.as_ref().map(|m| m.len()).unwrap_or(0);
1529 }
1530
1531 let skip_preflight = true;
1532 let sigverify = false;
1533 let do_propagate = false;
1534
1535 let mut pre_execution_capture_cursor = pre_execution_capture.clone();
1536 let capture_keys: Vec<_> = pre_execution_capture_cursor.keys().cloned().collect();
1539 for pubkey in capture_keys.into_iter() {
1540 if let Some(pre_account) = previous_execution_captures.remove(&pubkey) {
1541 pre_execution_capture_cursor.insert(pubkey, pre_account);
1543 }
1544 }
1545 let mut svm_clone = self.with_svm_reader(|svm_reader| svm_reader.clone_for_profiling());
1546
1547 let (dummy_simnet_tx, _) = crossbeam_channel::bounded(1);
1548 let (dummy_geyser_tx, _) = crossbeam_channel::bounded(1);
1549 svm_clone.simnet_events_tx = dummy_simnet_tx;
1550 svm_clone.geyser_events_tx = dummy_geyser_tx;
1551
1552 let svm_locker = SurfnetSvmLocker::new(svm_clone);
1553 let mut profile_result = svm_locker
1554 .process_transaction_internal(
1555 partial_tx,
1556 skip_preflight,
1557 sigverify,
1558 transaction_accounts,
1559 &loaded_addresses.as_ref().map(|l| l.loaded_addresses()),
1560 accounts_before,
1561 token_accounts_before,
1562 token_programs,
1563 pre_execution_capture_cursor,
1564 status_tx,
1565 do_propagate,
1566 )
1567 .await?;
1568
1569 profile_result
1570 .pre_execution_capture
1571 .retain(|pubkey, _| ix_required_accounts.contains(pubkey));
1572 profile_result
1573 .post_execution_capture
1574 .retain(|pubkey, _| ix_required_accounts.contains(pubkey));
1575
1576 profile_result.compute_units_consumed = profile_result
1577 .compute_units_consumed
1578 .saturating_sub(previous_cus);
1579 profile_result.log_messages = profile_result.log_messages.map(|logs| {
1580 logs.into_iter()
1581 .skip(previous_log_count)
1582 .collect::<Vec<_>>()
1583 });
1584
1585 ix_profile_results.push(profile_result);
1586 }
1587
1588 Ok(Some(ix_profile_results))
1589 }
1590
1591 fn handle_simulation_failure(
1592 &self,
1593 signature: Signature,
1594 failed_transaction_metadata: FailedTransactionMetadata,
1595 pre_execution_capture: ExecutionCapture,
1596 simulated_slot: Slot,
1597 status_tx: Sender<TransactionStatusEvent>,
1598 do_propagate: bool,
1599 ) -> ProfileResult {
1600 let FailedTransactionMetadata { err, meta } = failed_transaction_metadata;
1601
1602 let cus = meta.compute_units_consumed;
1603 let log_messages = meta.logs.clone();
1604 let err_string = err.to_string();
1605
1606 if do_propagate {
1607 let meta = convert_transaction_metadata_from_canonical(&meta);
1608 let simnet_events_tx = self.simnet_events_tx();
1609 let _ = simnet_events_tx.try_send(SimnetEvent::error(format!(
1610 "Transaction simulation failed: {}",
1611 err
1612 )));
1613
1614 self.with_svm_writer(|svm_writer| {
1615 svm_writer.notify_signature_subscribers(
1616 SignatureSubscriptionType::processed(),
1617 &signature,
1618 simulated_slot,
1619 Some(err.clone()),
1620 );
1621 svm_writer.notify_logs_subscribers(
1622 &signature,
1623 Some(err.clone()),
1624 log_messages.clone(),
1625 CommitmentLevel::Processed,
1626 );
1627 });
1628 let _ = status_tx.try_send(TransactionStatusEvent::SimulationFailure((err, meta)));
1629 }
1630 ProfileResult::new(
1631 pre_execution_capture,
1632 BTreeMap::new(),
1633 cus,
1634 Some(log_messages),
1635 Some(err_string),
1636 )
1637 }
1638
1639 #[allow(clippy::too_many_arguments)]
1640 fn handle_execution_failure(
1641 &self,
1642 failed_transaction_metadata: FailedTransactionMetadata,
1643 transaction: VersionedTransaction,
1644 simulated_slot: Slot,
1645 pubkeys_from_message: &[Pubkey],
1646 accounts_before: &[Option<Account>],
1647 token_accounts_before: &[(usize, TokenAccount)],
1648 token_programs: &[Pubkey],
1649 loaded_addresses: &Option<LoadedAddresses>,
1650 pre_execution_capture: ExecutionCapture,
1651 status_tx: Sender<TransactionStatusEvent>,
1652 do_propagate: bool,
1653 ) -> SurfpoolResult<ProfileResult> {
1654 let FailedTransactionMetadata { err, meta } = failed_transaction_metadata;
1655
1656 let cus = meta.compute_units_consumed;
1657 let log_messages = meta.logs.clone();
1658 let err_string = err.to_string();
1659 let signature = meta.signature;
1660
1661 let accounts_after = pubkeys_from_message
1662 .iter()
1663 .map(|p| self.with_svm_reader(|svm_reader| svm_reader.inner.get_account(p)))
1664 .collect::<SurfpoolResult<Vec<Option<Account>>>>()?;
1665
1666 for (pubkey, (before, after)) in pubkeys_from_message
1667 .iter()
1668 .zip(accounts_before.iter().zip(accounts_after.iter()))
1669 {
1670 if before.ne(&after) {
1671 self.with_svm_writer(|svm_writer| {
1672 if let Some(after) = &after {
1673 let _ = svm_writer.update_account_registries(pubkey, after);
1674 svm_writer.notify_account_subscribers(pubkey, &after);
1675 svm_writer.notify_program_subscribers(pubkey, &after);
1676 } else {
1677 svm_writer.notify_account_subscribers(pubkey, &Account::default());
1678 svm_writer.notify_program_subscribers(pubkey, &Account::default());
1679 }
1680 });
1681 }
1682 }
1683
1684 let token_mints = self
1685 .with_svm_reader(|svm_reader| {
1686 token_accounts_before
1687 .iter()
1688 .map(|(_, a)| {
1689 svm_reader
1690 .token_mints
1691 .get(&a.mint().to_string())
1692 .ok()
1693 .flatten()
1694 .ok_or(SurfpoolError::token_mint_not_found(a.mint()))
1695 })
1696 .collect::<Result<Vec<_>, SurfpoolError>>()
1697 })
1698 .unwrap_or_default();
1699
1700 if do_propagate {
1701 let meta_canonical = convert_transaction_metadata_from_canonical(&meta);
1702 let simnet_events_tx = self.simnet_events_tx();
1703 let _ = simnet_events_tx.try_send(SimnetEvent::error(format!(
1704 "Transaction execution failed: {}",
1705 err
1706 )));
1707 let _ = status_tx.try_send(TransactionStatusEvent::ExecutionFailure((
1708 err.clone(),
1709 meta_canonical.clone(),
1710 )));
1711
1712 self.with_svm_writer(|svm_writer| {
1713 let transaction_with_status_meta = TransactionWithStatusMeta::from_failure(
1714 simulated_slot,
1715 transaction.clone(),
1716 &FailedTransactionMetadata {
1717 err: err.clone(),
1718 meta: meta.clone(),
1719 },
1720 accounts_before,
1721 &accounts_after,
1722 token_accounts_before,
1723 token_mints,
1724 token_programs,
1725 loaded_addresses.clone().unwrap_or_default(),
1726 );
1727 svm_writer.transactions.store(
1728 signature.to_string(),
1729 SurfnetTransactionStatus::processed(
1730 transaction_with_status_meta.clone(),
1731 HashSet::new(),
1732 ),
1733 )?;
1734
1735 let _ = svm_writer
1736 .geyser_events_tx
1737 .send(GeyserEvent::NotifyTransaction(
1738 transaction_with_status_meta,
1739 Some(transaction.clone()),
1740 ));
1741
1742 svm_writer.transactions_queued_for_confirmation.push_back((
1743 transaction.clone(),
1744 status_tx.clone(),
1745 Some(err.clone()),
1746 ));
1747
1748 svm_writer.notify_signature_subscribers(
1749 SignatureSubscriptionType::processed(),
1750 &signature,
1751 simulated_slot,
1752 Some(err.clone()),
1753 );
1754 svm_writer.notify_logs_subscribers(
1755 &signature,
1756 Some(err.clone()),
1757 log_messages.clone(),
1758 CommitmentLevel::Processed,
1759 );
1760 let _ = svm_writer
1761 .simnet_events_tx
1762 .try_send(SimnetEvent::transaction_processed(
1763 meta_canonical,
1764 Some(err),
1765 ));
1766 Ok::<(), SurfpoolError>(())
1767 })?;
1768 }
1769 Ok(ProfileResult::new(
1770 pre_execution_capture,
1771 BTreeMap::new(),
1772 cus,
1773 Some(log_messages),
1774 Some(err_string),
1775 ))
1776 }
1777
1778 #[allow(clippy::too_many_arguments)]
1779 fn handle_execution_success(
1780 &self,
1781 transaction_metadata: TransactionMetadata,
1782 transaction: VersionedTransaction,
1783 simulated_slot: Slot,
1784 pubkeys_from_message: &[Pubkey],
1785 loaded_addresses: &Option<LoadedAddresses>,
1786 accounts_before: &[Option<Account>],
1787 token_accounts_before: &[(usize, TokenAccount)],
1788 token_programs: &[Pubkey],
1789 pre_execution_capture: ExecutionCapture,
1790 status_tx: &Sender<TransactionStatusEvent>,
1791 do_propagate: bool,
1792 ) -> SurfpoolResult<ProfileResult> {
1793 let cus = transaction_metadata.compute_units_consumed;
1794 let logs = transaction_metadata.logs.clone();
1795 let signature = transaction.signatures[0];
1796
1797 let post_execution_capture = self.with_svm_writer(|svm_writer| {
1798 let accounts_after = pubkeys_from_message
1799 .iter()
1800 .map(|p| svm_writer.inner.get_account_no_db(p))
1801 .collect::<Vec<Option<Account>>>();
1802 let (sanitized_transaction, versioned_transaction) = if do_propagate {
1803 let address_loader = match (&transaction.message, &loaded_addresses) {
1804 (VersionedMessage::V0(_), Some(loaded_addresses)) => {
1805 SimpleAddressLoader::Enabled(loaded_addresses.clone())
1806 }
1807 (VersionedMessage::V0(_), None) => {
1809 SimpleAddressLoader::Enabled(LoadedAddresses::default())
1810 }
1811 (VersionedMessage::Legacy(_) | VersionedMessage::V1(_), _) => {
1812 SimpleAddressLoader::Disabled
1813 }
1814 };
1815
1816 (
1817 SanitizedTransaction::try_create(
1818 transaction.clone(),
1819 transaction.message.hash(),
1820 Some(false),
1821 address_loader,
1822 &HashSet::new(), )
1824 .map_err(|error| {
1825 debug!(
1826 "Failed to sanitize transaction {} for Geyser account updates: {:?}",
1827 signature, error
1828 );
1829 error
1830 })
1831 .ok(),
1832 Some(transaction.clone()),
1833 )
1834 } else {
1835 (None, None)
1836 };
1837
1838 let mut mutated_account_pubkeys = HashSet::new();
1839 for (pubkey, (before, after)) in pubkeys_from_message
1840 .iter()
1841 .zip(accounts_before.iter().zip(accounts_after.clone()))
1842 {
1843 if before.ne(&after) {
1844 mutated_account_pubkeys.insert(*pubkey);
1845 let after = after.unwrap_or_default();
1846 svm_writer.update_account_registries(pubkey, &after)?;
1847 let write_version = svm_writer.increment_write_version();
1848
1849 if let Some(sanitized_transaction) = sanitized_transaction.clone() {
1850 let _ = svm_writer.geyser_events_tx.send(GeyserEvent::UpdateAccount(
1851 GeyserAccountUpdate::transaction_update(
1852 *pubkey,
1853 after.clone(),
1854 svm_writer.get_latest_absolute_slot(),
1855 sanitized_transaction.clone(),
1856 write_version,
1857 ),
1858 ));
1859 }
1860 svm_writer.notify_account_subscribers(pubkey, &after);
1861 svm_writer.notify_program_subscribers(pubkey, &after);
1862 }
1863 }
1864
1865 let mut token_accounts_after = vec![];
1866 let mut post_execution_capture = BTreeMap::new();
1867 let mut post_token_program_ids = vec![];
1868
1869 for (i, (pubkey, account)) in pubkeys_from_message
1870 .iter()
1871 .zip(accounts_after.iter())
1872 .enumerate()
1873 {
1874 let token_account = svm_writer
1875 .token_accounts
1876 .get(&pubkey.to_string())
1877 .ok()
1878 .flatten();
1879 post_execution_capture.insert(*pubkey, account.clone());
1880
1881 if let Some(token_account) = token_account {
1882 token_accounts_after.push((i, token_account));
1883 post_token_program_ids.push(
1884 account
1885 .as_ref()
1886 .map(|a| a.owner)
1887 .unwrap_or(spl_token_interface::id()),
1888 );
1889 }
1890 }
1891
1892 let token_mints = token_accounts_after
1893 .iter()
1894 .map(|(_, a)| {
1895 svm_writer
1896 .token_mints
1897 .get(&a.mint().to_string())
1898 .ok()
1899 .flatten()
1900 .ok_or(SurfpoolError::token_mint_not_found(a.mint()))
1901 })
1902 .collect::<Result<Vec<_>, SurfpoolError>>()?;
1903
1904 if do_propagate {
1905 let transaction_meta =
1906 convert_transaction_metadata_from_canonical(&transaction_metadata);
1907 let transaction_with_status_meta = TransactionWithStatusMeta::new(
1908 svm_writer.get_latest_absolute_slot(),
1909 transaction.clone(),
1910 transaction_metadata,
1911 accounts_before,
1912 &accounts_after,
1913 token_accounts_before,
1914 &token_accounts_after,
1915 token_mints,
1916 token_programs,
1917 &post_token_program_ids,
1918 loaded_addresses.clone().unwrap_or_default(),
1919 );
1920 svm_writer.transactions.store(
1921 transaction_meta.signature.to_string(),
1922 SurfnetTransactionStatus::processed(
1923 transaction_with_status_meta.clone(),
1924 mutated_account_pubkeys,
1925 ),
1926 )?;
1927
1928 let _ = svm_writer
1929 .simnet_events_tx
1930 .try_send(SimnetEvent::transaction_processed(transaction_meta, None));
1931
1932 let _ = svm_writer
1933 .geyser_events_tx
1934 .send(GeyserEvent::NotifyTransaction(
1935 transaction_with_status_meta,
1936 versioned_transaction,
1937 ));
1938
1939 svm_writer.transactions_queued_for_confirmation.push_back((
1940 transaction.clone(),
1941 status_tx.clone(),
1942 None,
1943 ));
1944
1945 svm_writer.notify_signature_subscribers(
1946 SignatureSubscriptionType::processed(),
1947 &signature,
1948 simulated_slot,
1949 None,
1950 );
1951 svm_writer.notify_logs_subscribers(
1952 &signature,
1953 None,
1954 logs.clone(),
1955 CommitmentLevel::Processed,
1956 );
1957 let _ = status_tx.try_send(TransactionStatusEvent::Success(
1958 TransactionConfirmationStatus::Processed,
1959 ));
1960 }
1961
1962 Ok::<ExecutionCapture, SurfpoolError>(post_execution_capture)
1963 })?;
1964
1965 Ok(ProfileResult::new(
1966 pre_execution_capture,
1967 post_execution_capture,
1968 cus,
1969 Some(logs),
1970 None,
1971 ))
1972 }
1973
1974 #[allow(clippy::too_many_arguments)]
1975 async fn process_transaction_internal(
1976 &self,
1977 transaction: VersionedTransaction,
1978 skip_preflight: bool,
1979 sigverify: bool,
1980 transaction_accounts: &[Pubkey],
1981 loaded_addresses: &Option<LoadedAddresses>,
1982 accounts_before: &[Option<Account>],
1983 token_accounts_before: &[(usize, TokenAccount)],
1984 token_programs: &[Pubkey],
1985 pre_execution_capture: ExecutionCapture,
1986 status_tx: &Sender<TransactionStatusEvent>,
1987 do_propagate: bool,
1988 ) -> SurfpoolResult<ProfileResult> {
1989 let res = match self
1990 .do_process_transaction_internal(transaction.clone(), skip_preflight, sigverify)
1991 .await
1992 {
1993 ProcessTransactionResult::Success(transaction_metadata) => self
1994 .handle_execution_success(
1995 transaction_metadata,
1996 transaction,
1997 self.get_latest_absolute_slot(),
1998 transaction_accounts,
1999 loaded_addresses,
2000 accounts_before,
2001 token_accounts_before,
2002 token_programs,
2003 pre_execution_capture,
2004 status_tx,
2005 do_propagate,
2006 )?,
2007 ProcessTransactionResult::SimulationFailure(failed_transaction_metadata) => self
2008 .handle_simulation_failure(
2009 transaction.signatures[0],
2010 failed_transaction_metadata,
2011 pre_execution_capture,
2012 self.get_latest_absolute_slot(),
2013 status_tx.clone(),
2014 do_propagate,
2015 ),
2016 ProcessTransactionResult::ExecutionFailure(failed) => self.handle_execution_failure(
2017 failed,
2018 transaction,
2019 self.get_latest_absolute_slot(),
2020 transaction_accounts,
2021 accounts_before,
2022 token_accounts_before,
2023 token_programs,
2024 loaded_addresses,
2025 pre_execution_capture,
2026 status_tx.clone(),
2027 do_propagate,
2028 )?,
2029 };
2030 Ok(res)
2031 }
2032
2033 async fn do_process_transaction_internal(
2034 &self,
2035 transaction: VersionedTransaction,
2036 skip_preflight: bool,
2037 sigverify: bool,
2038 ) -> ProcessTransactionResult {
2039 if !skip_preflight {
2041 if let Err(e) = self.with_svm_reader(|svm_reader| {
2042 svm_reader
2043 .simulate_transaction(transaction.clone(), sigverify)
2044 .map_err(ProcessTransactionResult::SimulationFailure)
2045 }) {
2046 return e;
2047 }
2048 }
2049
2050 match self.with_svm_writer(|svm_writer| {
2051 svm_writer
2052 .send_transaction(transaction, false , sigverify)
2053 .map_err(|e| {
2054 debug!("Transaction execution failure: {:?}", e.meta);
2055 ProcessTransactionResult::ExecutionFailure(e)
2056 })
2057 .map(ProcessTransactionResult::Success)
2058 }) {
2059 Ok(res) => res,
2060 Err(res) => res,
2061 }
2062 }
2063}
2064
2065impl SurfnetSvmLocker {
2067 pub fn write_account_update(&self, account_update: GetAccountResult) {
2069 if !account_update.requires_update() {
2070 return;
2071 }
2072
2073 self.with_svm_writer(move |svm_writer| {
2074 svm_writer.write_account_update(account_update.clone())
2075 })
2076 }
2077
2078 pub fn write_multiple_account_updates(&self, account_updates: &[GetAccountResult]) {
2080 if account_updates
2081 .iter()
2082 .all(|update| !update.requires_update())
2083 {
2084 return;
2085 }
2086
2087 self.with_svm_writer(move |svm_writer| {
2088 for update in account_updates {
2089 svm_writer.write_account_update(update.clone());
2090 }
2091 });
2092 }
2093
2094 pub fn reset_account(
2101 &self,
2102 pubkey: Pubkey,
2103 include_owned_accounts: bool,
2104 ) -> SurfpoolResult<()> {
2105 let simnet_events_tx = self.simnet_events_tx();
2106 let _ = simnet_events_tx.send(SimnetEvent::info(format!(
2107 "Account {} will be reset",
2108 pubkey
2109 )));
2110 self.remove_offline_account(pubkey, include_owned_accounts)?;
2112
2113 self.with_svm_writer(move |svm_writer| {
2114 svm_writer.reset_account(&pubkey, include_owned_accounts)
2115 })
2116 }
2117
2118 pub async fn reset_network(
2123 &self,
2124 remote_ctx: &Option<SurfnetRemoteClient>,
2125 ) -> SurfpoolResult<()> {
2126 let simnet_events_tx = self.simnet_events_tx();
2127 let _ = simnet_events_tx.send(SimnetEvent::info("Resetting network..."));
2128
2129 let (mut epoch_info, epoch_schedule) = if let Some(remote_client) = remote_ctx {
2131 (
2132 remote_client.get_epoch_info().await?,
2133 remote_client.get_epoch_schedule().await?,
2134 )
2135 } else {
2136 let epoch_schedule = SurfnetSvm::default_epoch_schedule();
2137 let epoch_info = SurfnetSvm::default_epoch_info(&epoch_schedule);
2138 (epoch_info, epoch_schedule)
2139 };
2140 epoch_info.transaction_count = None;
2141
2142 self.with_svm_writer(move |svm_writer| {
2143 let _ = svm_writer.reset_network(epoch_info, epoch_schedule);
2144 let _ = svm_writer.offline_accounts.clear();
2145 });
2146 Ok(())
2147 }
2148
2149 pub async fn insert_offline_account(
2155 &self,
2156 pubkey: Pubkey,
2157 include_owned_accounts: bool,
2158 ) -> SurfpoolResult<()> {
2159 let simnet_events_tx = self.simnet_events_tx();
2160 let _ = simnet_events_tx.send(SimnetEvent::info(format!(
2161 "Account {} will be marked offline (excluded from remote downloads)",
2162 pubkey
2163 )));
2164
2165 self.with_svm_writer(move |svm_writer| {
2166 if let Err(e) = svm_writer.offline_accounts.store(
2167 pubkey.to_string(),
2168 OfflineAccountConfig {
2169 include_owned_accounts,
2170 },
2171 ) {
2172 warn!("Failed to store offline account {}: {}", pubkey, e);
2173 }
2174 });
2175
2176 Ok(())
2177 }
2178
2179 pub fn stream_account(
2181 &self,
2182 pubkey: Pubkey,
2183 include_owned_accounts: bool,
2184 ) -> SurfpoolResult<()> {
2185 let simnet_events_tx = self.simnet_events_tx();
2186 let _ = simnet_events_tx.send(SimnetEvent::info(format!(
2187 "Account {} changes will be streamed",
2188 pubkey
2189 )));
2190 self.with_svm_writer(|svm_writer| {
2191 svm_writer
2192 .streamed_accounts
2193 .store(pubkey.to_string(), include_owned_accounts)
2194 })?;
2195 Ok(())
2196 }
2197
2198 pub fn get_streamed_accounts(&self) -> Vec<(String, bool)> {
2199 self.with_svm_reader(|svm_reader| {
2200 svm_reader
2201 .streamed_accounts
2202 .into_iter()
2203 .map(|iter| iter.collect())
2204 .unwrap_or_default()
2205 })
2206 }
2207
2208 pub fn remove_offline_account(
2213 &self,
2214 pubkey: Pubkey,
2215 include_owned_accounts: bool,
2216 ) -> SurfpoolResult<()> {
2217 self.with_svm_writer(move |svm_writer| {
2218 if let Err(e) = svm_writer.offline_accounts.take(&pubkey.to_string()) {
2219 warn!("Failed to set account online {}: {}", pubkey, e);
2220 }
2221
2222 if include_owned_accounts {
2223 let owned_pubkeys: Vec<Pubkey> = svm_writer
2226 .accounts_by_owner
2227 .get(&pubkey.to_string())
2228 .ok()
2229 .flatten()
2230 .unwrap_or_default()
2231 .iter()
2232 .filter_map(|pk_str| pk_str.parse().ok())
2233 .collect();
2234 for owned_pk in owned_pubkeys {
2235 if let Err(e) = svm_writer.offline_accounts.take(&owned_pk.to_string()) {
2236 warn!("Failed to set account online {}: {}", owned_pk, e);
2237 }
2238 }
2239 }
2240 });
2241 Ok(())
2242 }
2243
2244 pub fn is_account_offline(&self, pubkey: &Pubkey) -> bool {
2246 self.with_svm_reader(|svm_reader| {
2247 svm_reader
2248 .offline_accounts
2249 .contains_key(&pubkey.to_string())
2250 .unwrap_or(false)
2251 })
2252 }
2253
2254 pub fn get_offline_account_owners(&self) -> Vec<Pubkey> {
2256 self.with_svm_reader(|svm_reader| {
2257 svm_reader
2258 .offline_accounts
2259 .into_iter()
2260 .unwrap_or_else(|e| {
2261 warn!("Failed to iterate offline_accounts: {}", e);
2262 Box::new(std::iter::empty())
2263 })
2264 .filter(|(_, config)| config.include_owned_accounts)
2265 .filter_map(|(k, _)| match k.parse() {
2266 Ok(pk) => Some(pk),
2267 Err(e) => {
2268 warn!("Invalid pubkey in offline_accounts: {}: {}", k, e);
2269 None
2270 }
2271 })
2272 .collect()
2273 })
2274 }
2275
2276 pub fn register_scenario(
2278 &self,
2279 scenario: surfpool_types::Scenario,
2280 slot: Option<Slot>,
2281 ) -> SurfpoolResult<()> {
2282 self.with_svm_writer(move |svm_writer| svm_writer.register_scenario(scenario, slot))
2283 }
2284
2285 pub async fn materialize_overrides_for_slot(
2287 &self,
2288 remote_ctx: &Option<(SurfnetRemoteClient, CommitmentConfig)>,
2289 slot: Slot,
2290 ) -> SurfpoolResult<()> {
2291 let mut svm_writer = self.0.write().await;
2292 svm_writer
2293 .materialize_overrides_for_slot(remote_ctx, slot)
2294 .await
2295 }
2296}
2297
2298impl SurfnetSvmLocker {
2300 pub async fn get_token_accounts_by_owner(
2302 &self,
2303 remote_ctx: &Option<SurfnetRemoteClient>,
2304 owner: Pubkey,
2305 filter: &TokenAccountsFilter,
2306 config: &RpcAccountInfoConfig,
2307 ) -> SurfpoolContextualizedResult<Vec<RpcKeyedAccount>> {
2308 if let Some(remote_client) = remote_ctx {
2309 self.get_token_accounts_by_owner_local_then_remote(owner, filter, remote_client, config)
2310 .await
2311 } else {
2312 self.get_token_accounts_by_owner_local(owner, filter, config)
2313 }
2314 }
2315
2316 pub fn get_token_accounts_by_owner_local(
2317 &self,
2318 owner: Pubkey,
2319 filter: &TokenAccountsFilter,
2320 config: &RpcAccountInfoConfig,
2321 ) -> SurfpoolContextualizedResult<Vec<RpcKeyedAccount>> {
2322 let result = self.with_contextualized_svm_reader(|svm_reader| {
2323 svm_reader
2324 .get_parsed_token_accounts_by_owner(&owner)
2325 .iter()
2326 .filter_map(|(pubkey, token_account)| {
2327 svm_reader
2328 .get_account(pubkey)
2329 .map(|res| {
2330 let Some(account) = res else {
2331 return None;
2332 };
2333 if match filter {
2334 TokenAccountsFilter::Mint(mint) => token_account.mint().eq(mint),
2335 TokenAccountsFilter::ProgramId(program_id) => {
2336 account.owner.eq(program_id)
2337 }
2338 } {
2339 Some(svm_reader.account_to_rpc_keyed_account(
2340 pubkey,
2341 &account,
2342 config,
2343 Some(token_account.mint()),
2344 ))
2345 } else {
2346 None
2347 }
2348 })
2349 .transpose()
2350 })
2351 .collect::<SurfpoolResult<Vec<_>>>()
2352 });
2353 let SvmAccessContext {
2354 slot,
2355 latest_epoch_info,
2356 latest_blockhash,
2357 inner: accounts,
2358 } = result;
2359 Ok(SvmAccessContext::new(
2360 slot,
2361 latest_epoch_info,
2362 latest_blockhash,
2363 accounts?,
2364 ))
2365 }
2366
2367 pub async fn get_token_accounts_by_owner_local_then_remote(
2368 &self,
2369 owner: Pubkey,
2370 filter: &TokenAccountsFilter,
2371 remote_client: &SurfnetRemoteClient,
2372 config: &RpcAccountInfoConfig,
2373 ) -> SurfpoolContextualizedResult<Vec<RpcKeyedAccount>> {
2374 let SvmAccessContext {
2375 slot,
2376 latest_epoch_info,
2377 latest_blockhash,
2378 inner: local_accounts,
2379 } = self.get_token_accounts_by_owner_local(owner, filter, config)?;
2380
2381 let remote_accounts = remote_client
2382 .get_token_accounts_by_owner(owner, filter, config)
2383 .await?;
2384
2385 let mut combined_accounts = remote_accounts;
2386
2387 for local_account in local_accounts {
2388 if let Some((pos, _)) = combined_accounts
2389 .iter()
2390 .find_position(|RpcKeyedAccount { pubkey, .. }| pubkey.eq(&local_account.pubkey))
2391 {
2392 combined_accounts[pos] = local_account;
2393 } else {
2394 combined_accounts.push(local_account);
2395 }
2396 }
2397
2398 Ok(SvmAccessContext::new(
2399 slot,
2400 latest_epoch_info,
2401 latest_blockhash,
2402 combined_accounts,
2403 ))
2404 }
2405
2406 pub async fn get_token_accounts_by_delegate(
2407 &self,
2408 remote_ctx: &Option<SurfnetRemoteClient>,
2409 delegate: Pubkey,
2410 filter: &TokenAccountsFilter,
2411 config: &RpcAccountInfoConfig,
2412 ) -> SurfpoolContextualizedResult<Vec<RpcKeyedAccount>> {
2413 if let TokenAccountsFilter::ProgramId(program_id) = filter {
2415 if !is_supported_token_program(program_id) {
2416 return Err(SurfpoolError::unsupported_token_program(*program_id));
2417 }
2418 }
2419
2420 if let Some(remote_client) = remote_ctx {
2421 self.get_token_accounts_by_delegate_local_then_remote(
2422 delegate,
2423 filter,
2424 remote_client,
2425 config,
2426 )
2427 .await
2428 } else {
2429 self.get_token_accounts_by_delegate_local(delegate, filter, config)
2430 }
2431 }
2432}
2433
2434impl SurfnetSvmLocker {
2436 pub fn get_token_accounts_by_delegate_local(
2437 &self,
2438 delegate: Pubkey,
2439 filter: &TokenAccountsFilter,
2440 config: &RpcAccountInfoConfig,
2441 ) -> SurfpoolContextualizedResult<Vec<RpcKeyedAccount>> {
2442 let result = self.with_contextualized_svm_reader(|svm_reader| {
2443 svm_reader
2444 .get_token_accounts_by_delegate(&delegate)
2445 .iter()
2446 .filter_map(|(pubkey, token_account)| {
2447 svm_reader
2448 .get_account(pubkey)
2449 .map(|res| {
2450 let Some(account) = res else {
2451 return None;
2452 };
2453 let include = match filter {
2454 TokenAccountsFilter::Mint(mint) => token_account.mint() == *mint,
2455 TokenAccountsFilter::ProgramId(program_id) => {
2456 account.owner == *program_id
2457 && is_supported_token_program(program_id)
2458 }
2459 };
2460
2461 if include {
2462 Some(svm_reader.account_to_rpc_keyed_account(
2463 pubkey,
2464 &account,
2465 config,
2466 Some(token_account.mint()),
2467 ))
2468 } else {
2469 None
2470 }
2471 })
2472 .transpose()
2473 })
2474 .collect::<SurfpoolResult<Vec<_>>>()
2475 });
2476 let SvmAccessContext {
2477 slot,
2478 latest_epoch_info,
2479 latest_blockhash,
2480 inner: accounts,
2481 } = result;
2482 Ok(SvmAccessContext::new(
2483 slot,
2484 latest_epoch_info,
2485 latest_blockhash,
2486 accounts?,
2487 ))
2488 }
2489
2490 pub async fn get_token_accounts_by_delegate_local_then_remote(
2491 &self,
2492 delegate: Pubkey,
2493 filter: &TokenAccountsFilter,
2494 remote_client: &SurfnetRemoteClient,
2495 config: &RpcAccountInfoConfig,
2496 ) -> SurfpoolContextualizedResult<Vec<RpcKeyedAccount>> {
2497 let SvmAccessContext {
2498 slot,
2499 latest_epoch_info,
2500 latest_blockhash,
2501 inner: local_accounts,
2502 } = self.get_token_accounts_by_delegate_local(delegate, filter, config)?;
2503
2504 let remote_accounts = remote_client
2505 .get_token_accounts_by_delegate(delegate, filter, config)
2506 .await?;
2507
2508 let mut combined_accounts = remote_accounts;
2509
2510 for local_account in local_accounts {
2511 if let Some((pos, _)) = combined_accounts
2512 .iter()
2513 .find_position(|RpcKeyedAccount { pubkey, .. }| pubkey.eq(&local_account.pubkey))
2514 {
2515 combined_accounts[pos] = local_account;
2517 } else {
2518 combined_accounts.push(local_account);
2520 }
2521 }
2522
2523 Ok(SvmAccessContext::new(
2524 slot,
2525 latest_epoch_info,
2526 latest_blockhash,
2527 combined_accounts,
2528 ))
2529 }
2530}
2531
2532impl SurfnetSvmLocker {
2534 pub fn get_token_largest_accounts_local(
2535 &self,
2536 mint: &Pubkey,
2537 ) -> SvmAccessContext<Vec<RpcTokenAccountBalance>> {
2538 self.with_contextualized_svm_reader(|svm_reader| {
2539 let token_accounts = svm_reader.get_token_accounts_by_mint(mint);
2540
2541 let mint_decimals = if let Some(mint_account) =
2543 svm_reader.token_mints.get(&mint.to_string()).ok().flatten()
2544 {
2545 mint_account.decimals()
2546 } else {
2547 0
2548 };
2549
2550 let mut balances: Vec<RpcTokenAccountBalance> = token_accounts
2552 .into_iter()
2553 .map(|(pubkey, token_account)| RpcTokenAccountBalance {
2554 address: pubkey.to_string(),
2555 amount: UiTokenAmount {
2556 amount: token_account.amount().to_string(),
2557 decimals: mint_decimals,
2558 ui_amount: Some(format_ui_amount(token_account.amount(), mint_decimals)),
2559 ui_amount_string: format_ui_amount_string(
2560 token_account.amount(),
2561 mint_decimals,
2562 ),
2563 },
2564 })
2565 .collect();
2566
2567 balances.sort_by(|a, b| {
2569 let amount_a: u64 = a.amount.amount.parse().unwrap_or(0);
2570 let amount_b: u64 = b.amount.amount.parse().unwrap_or(0);
2571 amount_b.cmp(&amount_a)
2572 });
2573
2574 balances.truncate(20);
2576
2577 balances
2578 })
2579 }
2580
2581 pub async fn get_token_largest_accounts_local_then_remote(
2582 &self,
2583 client: &SurfnetRemoteClient,
2584 mint: &Pubkey,
2585 commitment_config: CommitmentConfig,
2586 ) -> SurfpoolContextualizedResult<Vec<RpcTokenAccountBalance>> {
2587 let SvmAccessContext {
2588 slot,
2589 latest_epoch_info,
2590 latest_blockhash,
2591 inner: local_accounts,
2592 } = self.get_token_largest_accounts_local(mint);
2593
2594 let remote_accounts = client
2595 .get_token_largest_accounts(mint, commitment_config)
2596 .await?;
2597
2598 let mut combined_accounts = remote_accounts;
2599
2600 for local_account in local_accounts {
2602 if let Some((pos, _)) = combined_accounts
2603 .iter()
2604 .find_position(|remote_account| remote_account.address == local_account.address)
2605 {
2606 combined_accounts[pos] = local_account;
2607 } else {
2608 combined_accounts.push(local_account);
2609 }
2610 }
2611
2612 combined_accounts.sort_by(|a, b| {
2614 let amount_a: u64 = a.amount.amount.parse().unwrap_or(0);
2615 let amount_b: u64 = b.amount.amount.parse().unwrap_or(0);
2616 amount_b.cmp(&amount_a)
2617 });
2618 combined_accounts.truncate(20);
2619
2620 Ok(SvmAccessContext::new(
2621 slot,
2622 latest_epoch_info,
2623 latest_blockhash,
2624 combined_accounts,
2625 ))
2626 }
2627
2628 pub async fn get_token_largest_accounts(
2630 &self,
2631 remote_ctx: &Option<(SurfnetRemoteClient, CommitmentConfig)>,
2632 mint: &Pubkey,
2633 ) -> SurfpoolContextualizedResult<Vec<RpcTokenAccountBalance>> {
2634 if let Some((remote_client, commitment_config)) = remote_ctx {
2635 self.get_token_largest_accounts_local_then_remote(
2636 remote_client,
2637 mint,
2638 *commitment_config,
2639 )
2640 .await
2641 } else {
2642 Ok(self.get_token_largest_accounts_local(mint))
2643 }
2644 }
2645}
2646
2647impl SurfnetSvmLocker {
2649 pub fn get_pubkeys_from_message(
2651 &self,
2652 message: &VersionedMessage,
2653 all_transaction_lookup_table_addresses: Option<Vec<&Pubkey>>,
2654 ) -> Vec<Pubkey> {
2655 match message {
2656 VersionedMessage::Legacy(message) => message.account_keys.clone(),
2657 VersionedMessage::V0(message) => {
2658 let mut acc_keys = message.account_keys.clone();
2659
2660 if let Some(loaded_addresses) = all_transaction_lookup_table_addresses {
2661 acc_keys.extend(loaded_addresses);
2662 }
2663 acc_keys
2664 }
2665 VersionedMessage::V1(message) => message.account_keys.clone(),
2666 }
2667 }
2668
2669 pub async fn get_loaded_addresses(
2671 &self,
2672 remote_ctx: &Option<(SurfnetRemoteClient, CommitmentConfig)>,
2673 message: &VersionedMessage,
2674 ) -> SurfpoolResult<Option<TransactionLoadedAddresses>> {
2675 match message {
2676 VersionedMessage::Legacy(_) | VersionedMessage::V1(_) => Ok(None),
2677 VersionedMessage::V0(message) => {
2678 if message.address_table_lookups.is_empty() {
2679 return Ok(None);
2680 }
2681 let mut loaded = TransactionLoadedAddresses::new();
2682 for alt in message.address_table_lookups.iter() {
2683 self.get_lookup_table_addresses(remote_ctx, alt, &mut loaded)
2684 .await?;
2685 }
2686
2687 Ok(Some(loaded))
2688 }
2689 }
2690 }
2691
2692 pub async fn get_lookup_table_addresses(
2694 &self,
2695 remote_ctx: &Option<(SurfnetRemoteClient, CommitmentConfig)>,
2696 address_table_lookup: &MessageAddressTableLookup,
2697 transaction_loaded_addresses: &mut TransactionLoadedAddresses,
2698 ) -> SurfpoolResult<()> {
2699 let table_account = self
2700 .get_account(remote_ctx, &address_table_lookup.account_key, None)
2701 .await?
2702 .inner
2703 .map_account()?;
2704
2705 if table_account.owner == solana_sdk_ids::address_lookup_table::id() {
2706 let SvmAccessContext {
2707 slot: current_slot,
2708 inner: slot_hashes,
2709 ..
2710 } = self.with_contextualized_svm_reader(|svm_reader| {
2711 svm_reader
2712 .inner
2713 .get_sysvar::<solana_slot_hashes::SlotHashes>()
2714 });
2715
2716 let data = &table_account.data.clone();
2718 let lookup_table = AddressLookupTable::deserialize(data).map_err(|_ix_err| {
2719 SurfpoolError::invalid_account_data(
2720 address_table_lookup.account_key,
2721 table_account.data,
2722 Some("Attempted to lookup addresses from an invalid account"),
2723 )
2724 })?;
2725
2726 let writable = lookup_table
2727 .lookup(
2728 current_slot,
2729 &address_table_lookup.writable_indexes,
2730 &slot_hashes,
2731 )
2732 .map_err(|_ix_err| {
2733 SurfpoolError::invalid_lookup_index(address_table_lookup.account_key)
2734 })?;
2735
2736 let readable = lookup_table
2737 .lookup(
2738 current_slot,
2739 &address_table_lookup.readonly_indexes,
2740 &slot_hashes,
2741 )
2742 .map_err(|_ix_err| {
2743 SurfpoolError::invalid_lookup_index(address_table_lookup.account_key)
2744 })?;
2745
2746 let MessageAddressTableLookup {
2747 account_key,
2748 writable_indexes,
2749 readonly_indexes,
2750 } = address_table_lookup.to_owned();
2751
2752 transaction_loaded_addresses.insert_members(
2753 account_key,
2754 writable_indexes
2755 .into_iter()
2756 .zip(writable.into_iter())
2757 .collect(),
2758 readonly_indexes
2759 .into_iter()
2760 .zip(readable.into_iter())
2761 .collect(),
2762 );
2763
2764 Ok(())
2765 } else {
2766 Err(SurfpoolError::invalid_account_owner(
2767 table_account.owner,
2768 Some("Attempted to lookup addresses from an account owned by the wrong program"),
2769 ))
2770 }
2771 }
2772}
2773
2774impl SurfnetSvmLocker {
2776 pub fn estimate_compute_units(
2778 &self,
2779 transaction: &VersionedTransaction,
2780 ) -> SvmAccessContext<ComputeUnitsEstimationResult> {
2781 self.with_contextualized_svm_reader(|svm_reader| {
2782 svm_reader.estimate_compute_units(transaction)
2783 })
2784 }
2785
2786 #[allow(clippy::too_many_arguments)]
2809 fn create_partial_transaction(
2810 &self,
2811 instructions: &[CompiledInstruction],
2812 message_accounts: &[Pubkey],
2813 transaction: &VersionedTransaction,
2814 idx: usize,
2815 loaded_addresses: &Option<TransactionLoadedAddresses>,
2816 ) -> Option<VersionedTransaction> {
2817 let ixs_for_tx = instructions[0..idx].to_vec();
2821
2822 let new_message = match transaction.message {
2825 VersionedMessage::Legacy(ref message) => VersionedMessage::Legacy(Message {
2826 account_keys: message_accounts[..message.account_keys.len()].to_vec(),
2827 header: message.header,
2828 recent_blockhash: *transaction.message.recent_blockhash(),
2829 instructions: ixs_for_tx.clone(),
2830 }),
2831 VersionedMessage::V0(ref message) => {
2832 VersionedMessage::V0(solana_message::v0::Message {
2833 account_keys: message_accounts[..message.account_keys.len()].to_vec(),
2834 header: message.header,
2835 recent_blockhash: *transaction.message.recent_blockhash(),
2836 instructions: ixs_for_tx.clone(),
2837 address_table_lookups: loaded_addresses
2839 .as_ref()
2840 .map(|l| l.to_address_table_lookups())
2841 .unwrap_or_default(),
2842 })
2843 }
2844 VersionedMessage::V1(ref message) => {
2845 let mut message = message.clone();
2846 message.account_keys = message_accounts[..message.account_keys.len()].to_vec();
2847 message.instructions = ixs_for_tx;
2848 VersionedMessage::V1(message)
2849 }
2850 };
2851
2852 let tx = VersionedTransaction {
2853 signatures: transaction.signatures.clone(),
2854 message: new_message,
2855 };
2856
2857 Some(tx)
2858 }
2859
2860 pub fn get_profile_result(
2862 &self,
2863 signature_or_uuid: UuidOrSignature,
2864 config: &RpcProfileResultConfig,
2865 ) -> SurfpoolResult<Option<UiKeyedProfileResult>> {
2866 let result = match &signature_or_uuid {
2867 UuidOrSignature::Signature(signature) => self.with_svm_reader(|svm| {
2868 svm.executed_transaction_profiles
2869 .get(&signature.to_string())
2870 .ok()
2871 .flatten()
2872 }),
2873 UuidOrSignature::Uuid(uuid) => self.with_svm_reader(|svm| {
2874 svm.simulated_transaction_profiles
2875 .get(&uuid.to_string())
2876 .ok()
2877 .flatten()
2878 }),
2879 };
2880 Ok(result.map(|profile| self.encode_ui_keyed_profile_result(profile, config)))
2881 }
2882
2883 pub fn encode_ui_keyed_profile_result(
2884 &self,
2885 profile: KeyedProfileResult,
2886 config: &RpcProfileResultConfig,
2887 ) -> UiKeyedProfileResult {
2888 self.with_svm_reader(|svm_reader| {
2889 svm_reader.encode_ui_keyed_profile_result(profile, config)
2890 })
2891 }
2892
2893 pub fn get_profile_results_by_tag(
2895 &self,
2896 tag: String,
2897 config: &RpcProfileResultConfig,
2898 ) -> SurfpoolResult<Option<Vec<UiKeyedProfileResult>>> {
2899 let tag_map = self.with_svm_reader(|svm| svm.profile_tag_map.get(&tag).ok().flatten());
2900 match tag_map {
2901 None => Ok(None),
2902 Some(uuids_or_sigs) => {
2903 let mut profiles = Vec::new();
2904 for id in uuids_or_sigs {
2905 let profile = self.get_profile_result(id, config)?;
2906 if profile.is_none() {
2907 return Err(SurfpoolError::tag_not_found(&tag));
2908 }
2909 profiles.push(profile.unwrap());
2910 }
2911 Ok(Some(profiles))
2912 }
2913 }
2914 }
2915
2916 pub fn register_idl(&self, idl: Idl, slot: Option<Slot>) -> SurfpoolResult<()> {
2917 self.with_svm_writer(|svm_writer| svm_writer.register_idl(idl, slot))
2918 }
2919
2920 pub fn get_idl(&self, address: &Pubkey, slot: Option<Slot>) -> Option<Idl> {
2921 self.with_svm_reader(|svm_reader| {
2922 let query_slot = slot.unwrap_or_else(|| svm_reader.get_latest_absolute_slot());
2923 svm_reader
2925 .registered_idls
2926 .get(&address.to_string())
2927 .ok()
2928 .flatten()
2929 .and_then(|idl_versions| {
2930 idl_versions
2931 .iter()
2932 .filter(|VersionedIdl(s, _)| *s <= query_slot)
2933 .max()
2934 .map(|VersionedIdl(_, idl)| idl.clone())
2935 })
2936 })
2937 }
2938
2939 pub fn get_forged_account_data(
2962 &self,
2963 account_pubkey: &Pubkey,
2964 account_data: &[u8],
2965 idl: &Idl,
2966 overrides: &HashMap<String, serde_json::Value>,
2967 ) -> SurfpoolResult<Vec<u8>> {
2968 self.with_svm_reader(|svm_reader| {
2969 svm_reader.get_forged_account_data(account_pubkey, account_data, idl, overrides)
2970 })
2971 }
2972}
2973impl SurfnetSvmLocker {
2975 pub async fn clone_program_account(
2977 &self,
2978 remote_ctx: &Option<(SurfnetRemoteClient, CommitmentConfig)>,
2979 source_program_id: &Pubkey,
2980 destination_program_id: &Pubkey,
2981 ) -> SurfpoolContextualizedResult<()> {
2982 let expected_source_program_data_address = get_program_data_address(source_program_id);
2983
2984 let result = self
2985 .get_multiple_accounts(
2986 remote_ctx,
2987 &[*source_program_id, expected_source_program_data_address],
2988 None,
2989 )
2990 .await?;
2991
2992 let mut accounts = result
2993 .inner
2994 .clone()
2995 .into_iter()
2996 .map(|a| a.map_account())
2997 .collect::<SurfpoolResult<Vec<Account>>>()?;
2998
2999 let source_program_data_account = accounts.remove(1);
3000 let source_program_account = accounts.remove(0);
3001
3002 let BpfUpgradeableLoaderAccountType::Program(UiProgram {
3003 program_data: source_program_data_address,
3004 }) = parse_bpf_upgradeable_loader(&source_program_account.data).map_err(|e| {
3005 SurfpoolError::invalid_program_account(source_program_id, e.to_string())
3006 })?
3007 else {
3008 return Err(SurfpoolError::expected_program_account(source_program_id));
3009 };
3010
3011 if source_program_data_address.ne(&expected_source_program_data_address.to_string()) {
3012 return Err(SurfpoolError::invalid_program_account(
3013 source_program_id,
3014 format!(
3015 "Program data address mismatch: expected {}, found {}",
3016 expected_source_program_data_address, source_program_data_address
3017 ),
3018 ));
3019 }
3020
3021 let destination_program_data_address = get_program_data_address(destination_program_id);
3022
3023 let mut new_program_account = source_program_account;
3026 new_program_account.data = bincode::serialize(&UpgradeableLoaderState::Program {
3027 programdata_address: destination_program_data_address,
3028 })
3029 .map_err(|e| SurfpoolError::internal(format!("Failed to serialize program data: {}", e)))?;
3030
3031 self.with_svm_writer(|svm_writer| {
3032 svm_writer.set_account(
3033 &destination_program_data_address,
3034 source_program_data_account.clone(),
3035 )?;
3036
3037 svm_writer.set_account(destination_program_id, new_program_account.clone())?;
3038 Ok::<(), SurfpoolError>(())
3039 })?;
3040
3041 Ok(result.with_new_value(()))
3042 }
3043
3044 pub async fn set_program_authority(
3045 &self,
3046 remote_ctx: &Option<(SurfnetRemoteClient, CommitmentConfig)>,
3047 program_id: Pubkey,
3048 new_authority: Option<Pubkey>,
3049 ) -> SurfpoolContextualizedResult<()> {
3050 let SvmAccessContext {
3051 slot,
3052 latest_epoch_info,
3053 latest_blockhash,
3054 inner: mut get_account_result,
3055 } = self.get_account(remote_ctx, &program_id, None).await?;
3056
3057 let original_authority = match &mut get_account_result {
3058 GetAccountResult::None(pubkey) => {
3059 return Err(SurfpoolError::invalid_program_account(
3060 pubkey,
3061 "Account not found",
3062 ));
3063 }
3064 GetAccountResult::FoundAccount(pubkey, program_account, _) => {
3065 let programdata_address = get_program_data_address(pubkey);
3066 let mut programdata_account_result = self
3067 .get_account(remote_ctx, &programdata_address, None)
3068 .await?
3069 .inner;
3070 match &mut programdata_account_result {
3071 GetAccountResult::None(pubkey) => {
3072 return Err(SurfpoolError::invalid_program_account(
3073 pubkey,
3074 "Program data account does not exist",
3075 ));
3076 }
3077 GetAccountResult::FoundAccount(_, programdata_account, _) => {
3078 let original_authority = update_programdata_account(
3079 &program_id,
3080 programdata_account,
3081 new_authority,
3082 )?;
3083
3084 get_account_result = GetAccountResult::FoundProgramAccount(
3085 (*pubkey, program_account.clone()),
3086 (programdata_address, Some(programdata_account.clone())),
3087 );
3088
3089 original_authority
3090 }
3091 GetAccountResult::FoundProgramAccount(_, _)
3092 | GetAccountResult::FoundTokenAccount(_, _) => {
3093 return Err(SurfpoolError::invalid_program_account(
3094 pubkey,
3095 "Not a program account",
3096 ));
3097 }
3098 }
3099 }
3100 GetAccountResult::FoundProgramAccount(_, (_, None)) => {
3101 return Err(SurfpoolError::invalid_program_account(
3102 program_id,
3103 "Program data account does not exist",
3104 ));
3105 }
3106 GetAccountResult::FoundProgramAccount(_, (_, Some(programdata_account))) => {
3107 update_programdata_account(&program_id, programdata_account, new_authority)?
3108 }
3109 GetAccountResult::FoundTokenAccount(_, _) => {
3110 return Err(SurfpoolError::invalid_program_account(
3111 program_id,
3112 "Not a program account",
3113 ));
3114 }
3115 };
3116
3117 let simnet_events_tx = self.simnet_events_tx();
3118 match (original_authority, new_authority) {
3119 (Some(original), Some(new)) => {
3120 if original != new {
3121 let _ = simnet_events_tx.send(SimnetEvent::info(format!(
3122 "Setting new authority for program {}",
3123 program_id
3124 )));
3125 let _ = simnet_events_tx
3126 .send(SimnetEvent::info(format!("Old Authority: {}", original)));
3127 let _ =
3128 simnet_events_tx.send(SimnetEvent::info(format!("New Authority: {}", new)));
3129 } else {
3130 let _ = simnet_events_tx.send(SimnetEvent::info(format!(
3131 "No authority change for program {}",
3132 program_id
3133 )));
3134 }
3135 }
3136 (Some(original), None) => {
3137 let _ = simnet_events_tx.send(SimnetEvent::info(format!(
3138 "Removing authority for program {}",
3139 program_id
3140 )));
3141 let _ = simnet_events_tx
3142 .send(SimnetEvent::info(format!("Old Authority: {}", original)));
3143 }
3144 (None, Some(new)) => {
3145 let _ = simnet_events_tx.send(SimnetEvent::info(format!(
3146 "Setting new authority for program {}",
3147 program_id
3148 )));
3149 let _ = simnet_events_tx.send(SimnetEvent::info("Old Authority: None".to_string()));
3150 let _ = simnet_events_tx.send(SimnetEvent::info(format!("New Authority: {}", new)));
3151 }
3152 (None, None) => {
3153 let _ = simnet_events_tx.send(SimnetEvent::info(format!(
3154 "No authority change for program {}",
3155 program_id
3156 )));
3157 }
3158 };
3159
3160 self.write_account_update(get_account_result);
3161
3162 Ok(SvmAccessContext::new(
3163 slot,
3164 latest_epoch_info,
3165 latest_blockhash,
3166 (),
3167 ))
3168 }
3169
3170 pub async fn get_program_accounts(
3171 &self,
3172 remote_ctx: &Option<SurfnetRemoteClient>,
3173 program_id: &Pubkey,
3174 account_config: RpcAccountInfoConfig,
3175 filters: Option<Vec<RpcFilterType>>,
3176 ) -> SurfpoolContextualizedResult<Vec<RpcKeyedAccount>> {
3177 if let Some(remote_client) = remote_ctx {
3178 self.get_program_accounts_local_then_remote(
3179 remote_client,
3180 program_id,
3181 account_config,
3182 filters,
3183 )
3184 .await
3185 } else {
3186 self.get_program_accounts_local(program_id, account_config, filters)
3187 }
3188 }
3189
3190 pub fn get_program_accounts_local(
3192 &self,
3193 program_id: &Pubkey,
3194 account_config: RpcAccountInfoConfig,
3195 filters: Option<Vec<RpcFilterType>>,
3196 ) -> SurfpoolContextualizedResult<Vec<RpcKeyedAccount>> {
3197 let res = self.with_svm_reader(|svm_reader| {
3198 let res = svm_reader.get_account_owned_by(program_id)?;
3199
3200 let mut filtered = vec![];
3201 for (pubkey, account) in &res {
3202 if let Some(ref active_filters) = filters {
3203 match apply_rpc_filters(&account.data, active_filters) {
3204 Ok(true) => {} Ok(false) => continue, Err(e) => return Err(e), }
3208 }
3209
3210 filtered.push(svm_reader.account_to_rpc_keyed_account(
3211 pubkey,
3212 account,
3213 &account_config,
3214 None,
3215 ));
3216 }
3217 Ok(filtered)
3218 })?;
3219
3220 Ok(self.with_contextualized_svm_reader(|_| res.clone()))
3221 }
3222
3223 pub fn encode_ui_account(
3224 &self,
3225 pubkey: &Pubkey,
3226 account: &Account,
3227 encoding: UiAccountEncoding,
3228 additional_data: Option<AccountAdditionalDataV3>,
3229 data_slice: Option<UiDataSliceConfig>,
3230 ) -> UiAccount {
3231 self.with_svm_reader(|svm_reader| {
3232 svm_reader.encode_ui_account(pubkey, account, encoding, additional_data, data_slice)
3233 })
3234 }
3235
3236 pub async fn get_program_accounts_local_then_remote(
3238 &self,
3239 client: &SurfnetRemoteClient,
3240 program_id: &Pubkey,
3241 account_config: RpcAccountInfoConfig,
3242 filters: Option<Vec<RpcFilterType>>,
3243 ) -> SurfpoolContextualizedResult<Vec<RpcKeyedAccount>> {
3244 let SvmAccessContext {
3245 slot,
3246 latest_epoch_info,
3247 latest_blockhash,
3248 inner: local_accounts,
3249 } = self.get_program_accounts_local(program_id, account_config.clone(), filters.clone())?;
3250
3251 let remote_accounts_result = client
3252 .get_program_accounts(program_id, account_config, filters)
3253 .await?;
3254
3255 let remote_accounts = remote_accounts_result.handle_method_not_supported(|| {
3256 let tx = self.simnet_events_tx();
3257 let _ = tx.send(SimnetEvent::warn("The `getProgramAccounts` method was sent to the remote RPC, but this method isn't supported by your RPC provider. If you need this method, please use a different RPC provider."));
3258 vec![]
3259 });
3260
3261 let mut combined_accounts = remote_accounts
3262 .into_iter()
3263 .map(|(pubkey, account)| RpcKeyedAccount {
3264 pubkey: pubkey.to_string(),
3265 account,
3266 })
3267 .collect::<Vec<RpcKeyedAccount>>();
3268
3269 for local_account in local_accounts {
3270 if let Some((pos, _)) = combined_accounts.iter().find_position(
3272 |RpcKeyedAccount {
3273 pubkey: remote_pubkey,
3274 ..
3275 }| remote_pubkey.eq(&local_account.pubkey),
3276 ) {
3277 combined_accounts[pos] = local_account;
3278 } else {
3279 combined_accounts.push(local_account);
3281 };
3282 }
3283
3284 Ok(SvmAccessContext {
3285 slot,
3286 latest_epoch_info,
3287 latest_blockhash,
3288 inner: combined_accounts,
3289 })
3290 }
3291}
3292
3293impl SurfnetSvmLocker {
3294 pub fn get_first_local_slot(&self) -> Option<Slot> {
3297 self.with_svm_reader(|svm| Some(svm.genesis_slot))
3298 }
3299
3300 pub async fn get_block(
3301 &self,
3302 remote_ctx: &Option<SurfnetRemoteClient>,
3303 slot: &Slot,
3304 config: &RpcBlockConfig,
3305 ) -> SurfpoolContextualizedResult<Option<UiConfirmedBlock>> {
3306 let committed_slot = self.get_slot_for_commitment(&config.commitment.unwrap_or_default());
3307 if *slot > committed_slot {
3308 return Ok(SvmAccessContext {
3309 slot: committed_slot,
3310 latest_epoch_info: self.get_epoch_info(),
3311 latest_blockhash: self
3312 .get_latest_blockhash(&CommitmentConfig::processed())
3313 .unwrap_or_default(),
3314 inner: None,
3315 });
3316 }
3317
3318 let first_local_slot = self.get_first_local_slot();
3319
3320 let result = if first_local_slot.is_some() && first_local_slot.unwrap() > *slot {
3321 match remote_ctx {
3322 Some(remote_client) => Some(remote_client.get_block(slot, *config).await?),
3323 None => return Err(SurfpoolError::slot_too_old(*slot)),
3324 }
3325 } else {
3326 self.get_block_local(slot, config)?
3327 };
3328
3329 Ok(SvmAccessContext {
3330 slot: *slot,
3331 latest_epoch_info: self.get_epoch_info(),
3332 latest_blockhash: self
3333 .get_latest_blockhash(&CommitmentConfig::processed())
3334 .unwrap_or_default(),
3335 inner: result,
3336 })
3337 }
3338
3339 pub fn get_block_local(
3340 &self,
3341 slot: &Slot,
3342 config: &RpcBlockConfig,
3343 ) -> SurfpoolResult<Option<UiConfirmedBlock>> {
3344 self.with_svm_reader(|svm_reader| svm_reader.get_block_at_slot(*slot, config))
3345 }
3346
3347 pub fn get_genesis_hash_local(&self) -> SvmAccessContext<Hash> {
3348 self.with_contextualized_svm_reader(|svm_reader| svm_reader.genesis_config.hash())
3349 }
3350
3351 pub async fn get_genesis_hash(
3352 &self,
3353 remote_ctx: &Option<SurfnetRemoteClient>,
3354 ) -> SurfpoolContextualizedResult<Hash> {
3355 if let Some(client) = remote_ctx {
3356 let remote_hash = client.get_genesis_hash().await?;
3357 Ok(self.with_contextualized_svm_reader(|_| remote_hash))
3358 } else {
3359 Ok(self.get_genesis_hash_local())
3360 }
3361 }
3362}
3363
3364impl SurfnetSvmLocker {
3366 pub fn simnet_events_tx(&self) -> Sender<SimnetEvent> {
3368 self.with_svm_reader(|svm_reader| svm_reader.simnet_events_tx.clone())
3369 }
3370
3371 pub fn get_epoch_info(&self) -> EpochInfo {
3373 self.with_svm_reader(|svm_reader| svm_reader.latest_epoch_info.clone())
3374 }
3375
3376 pub fn time_travel(
3377 &self,
3378 key: Option<(blake3::Hash, String)>,
3379 simnet_command_tx: Sender<SimnetCommand>,
3380 config: TimeTravelConfig,
3381 ) -> SurfpoolResult<EpochInfo> {
3382 let (epoch_info, slot_time, updated_at) = self.with_svm_reader(|svm_reader| {
3383 (
3384 svm_reader.latest_epoch_info.clone(),
3385 svm_reader.slot_time,
3386 svm_reader.updated_at,
3387 )
3388 });
3389
3390 let clock_update: Clock =
3391 calculate_time_travel_clock(&config, updated_at, slot_time, &epoch_info)
3392 .map_err(|e| SurfpoolError::internal(e.to_string()))?;
3393
3394 let formated_time = chrono::DateTime::from_timestamp(clock_update.unix_timestamp, 0)
3395 .unwrap_or_else(|| chrono::DateTime::from_timestamp(0, 0).unwrap())
3396 .format("%Y-%m-%d %H:%M:%S")
3397 .to_string();
3398
3399 let (response_tx, response_rx) = crossbeam_channel::bounded(1);
3401
3402 let _ = simnet_command_tx.send(SimnetCommand::UpdateInternalClockWithConfirmation(
3404 key,
3405 clock_update,
3406 response_tx,
3407 ));
3408
3409 let updated_epoch_info = response_rx
3411 .recv_timeout(std::time::Duration::from_secs(2))
3412 .map_err(|e| {
3413 SurfpoolError::internal(format!("Failed to confirm clock update: {}", e))
3414 })?;
3415
3416 let _ = self.simnet_events_tx().send(SimnetEvent::info(format!(
3417 "Time travel to {} successful (epoch {} / slot {})",
3418 formated_time, updated_epoch_info.epoch, updated_epoch_info.absolute_slot
3419 )));
3420
3421 Ok(updated_epoch_info)
3422 }
3423
3424 pub fn get_latest_absolute_slot(&self) -> Slot {
3426 self.with_svm_reader(|svm_reader| svm_reader.get_latest_absolute_slot())
3427 }
3428
3429 pub fn get_latest_blockhash(&self, config: &CommitmentConfig) -> Option<Hash> {
3431 let slot = self.get_slot_for_commitment(config);
3432 self.with_svm_reader(|svm_reader| svm_reader.blockhash_for_slot(slot))
3433 }
3434
3435 pub fn latest_absolute_blockhash(&self) -> Hash {
3436 self.with_svm_reader(|svm_reader| svm_reader.latest_blockhash())
3437 }
3438
3439 pub fn get_slot_for_commitment(&self, commitment: &CommitmentConfig) -> Slot {
3440 self.with_svm_reader(|svm_reader| {
3441 let slot = svm_reader.get_latest_absolute_slot();
3442 match commitment.commitment {
3443 CommitmentLevel::Processed => slot,
3444 CommitmentLevel::Confirmed => slot.saturating_sub(1),
3445 CommitmentLevel::Finalized => slot.saturating_sub(FINALIZATION_SLOT_THRESHOLD),
3446 }
3447 })
3448 }
3449
3450 pub fn airdrop(
3452 &self,
3453 pubkey: &Pubkey,
3454 lamports: u64,
3455 ) -> Result<TransactionResult, AirdropError> {
3456 self.with_svm_writer(|svm_writer| svm_writer.airdrop(pubkey, lamports))
3457 }
3458
3459 pub fn airdrop_pubkeys(&self, lamports: u64, addresses: &[Pubkey]) {
3461 self.with_svm_writer(|svm_writer| svm_writer.airdrop_pubkeys(lamports, addresses))
3462 }
3463
3464 pub async fn confirm_current_block(
3466 &self,
3467 remote_ctx: &Option<(SurfnetRemoteClient, CommitmentConfig)>,
3468 ) -> SurfpoolResult<()> {
3469 let mut svm_writer = self.0.write().await;
3472 svm_writer.confirm_current_block()?;
3473 svm_writer.materialize_overrides(remote_ctx).await
3474 }
3475
3476 pub fn subscribe_for_signature_updates(
3478 &self,
3479 signature: &Signature,
3480 subscription_type: SignatureSubscriptionType,
3481 ) -> Receiver<(Slot, Option<TransactionError>)> {
3482 self.with_svm_writer(|svm_writer| {
3483 svm_writer.subscribe_for_signature_updates(signature, subscription_type.clone())
3484 })
3485 }
3486
3487 pub fn subscribe_for_account_updates(
3489 &self,
3490 account_pubkey: &Pubkey,
3491 encoding: Option<UiAccountEncoding>,
3492 ) -> Receiver<UiAccount> {
3493 self.with_svm_writer(|svm_writer| {
3495 svm_writer.subscribe_for_account_updates(account_pubkey, encoding)
3496 })
3497 }
3498
3499 pub fn subscribe_for_program_updates(
3501 &self,
3502 program_id: &Pubkey,
3503 encoding: Option<UiAccountEncoding>,
3504 filters: Option<Vec<RpcFilterType>>,
3505 ) -> Receiver<RpcKeyedAccount> {
3506 self.with_svm_writer(|svm_writer| {
3507 svm_writer.subscribe_for_program_updates(program_id, encoding, filters)
3508 })
3509 }
3510
3511 pub fn subscribe_for_slot_updates(&self) -> Receiver<SlotInfo> {
3513 self.with_svm_writer(|svm_writer| svm_writer.subscribe_for_slot_updates())
3514 }
3515
3516 pub fn subscribe_for_slots_updates(&self) -> Receiver<Arc<SlotUpdate>> {
3519 self.with_svm_writer(|svm_writer| svm_writer.subscribe_for_slots_updates())
3520 }
3521
3522 pub fn subscribe_for_logs_updates(
3524 &self,
3525 commitment_level: &CommitmentLevel,
3526 filter: &RpcTransactionLogsFilter,
3527 ) -> Receiver<(Slot, RpcLogsResponse)> {
3528 self.with_svm_writer(|svm_writer| {
3529 svm_writer.subscribe_for_logs_updates(commitment_level, filter)
3530 })
3531 }
3532
3533 pub fn subscribe_for_snapshot_import_updates(
3536 &self,
3537 snapshot_url: &str,
3538 snapshot_id: &str,
3539 ) -> Receiver<super::SnapshotImportNotification> {
3540 let (tx, rx) =
3542 self.with_svm_writer(|svm_writer| svm_writer.register_snapshot_subscription());
3543
3544 let locker = self.clone();
3546 let snapshot_url = snapshot_url.to_string();
3547 let snapshot_id = snapshot_id.to_string();
3548
3549 tokio::spawn(async move {
3550 let _ = tx.send(super::SnapshotImportNotification {
3552 snapshot_id: snapshot_id.clone(),
3553 status: super::SnapshotImportStatus::Started,
3554 accounts_loaded: 0,
3555 total_accounts: 0,
3556 error: None,
3557 });
3558
3559 let snapshot_data = match SurfnetSvm::fetch_snapshot_from_url(&snapshot_url).await {
3561 Ok(data) => data,
3562 Err(e) => {
3563 let _ = tx.send(super::SnapshotImportNotification {
3564 snapshot_id,
3565 status: super::SnapshotImportStatus::Failed,
3566 accounts_loaded: 0,
3567 total_accounts: 0,
3568 error: Some(format!("Failed to fetch snapshot: {}", e)),
3569 });
3570 return;
3571 }
3572 };
3573
3574 let total_accounts = snapshot_data.len() as u64;
3575
3576 let _ = tx.send(super::SnapshotImportNotification {
3578 snapshot_id: snapshot_id.clone(),
3579 status: super::SnapshotImportStatus::InProgress,
3580 accounts_loaded: 0,
3581 total_accounts,
3582 error: None,
3583 });
3584
3585 match locker
3587 .load_snapshot(&snapshot_data, None, CommitmentConfig::processed())
3588 .await
3589 {
3590 Ok(loaded_count) => {
3591 let _ = tx.send(super::SnapshotImportNotification {
3592 snapshot_id,
3593 status: super::SnapshotImportStatus::Completed,
3594 accounts_loaded: loaded_count as u64,
3595 total_accounts,
3596 error: None,
3597 });
3598 }
3599 Err(e) => {
3600 let _ = tx.send(super::SnapshotImportNotification {
3601 snapshot_id,
3602 status: super::SnapshotImportStatus::Failed,
3603 accounts_loaded: 0,
3604 total_accounts,
3605 error: Some(format!("Failed to load snapshot: {}", e)),
3606 });
3607 }
3608 }
3609 });
3610
3611 rx
3612 }
3613
3614 pub fn runbook_executions(&self) -> Vec<RunbookExecutionStatusReport> {
3615 self.with_svm_reader(|svm_reader| svm_reader.runbook_executions.clone())
3616 }
3617
3618 pub fn start_runbook_execution(&self, runbook_id: String) {
3619 self.with_svm_writer(|svm_writer| {
3620 svm_writer.instruction_profiling_enabled = false;
3621 svm_writer.start_runbook_execution(runbook_id);
3622 });
3623 }
3624
3625 pub fn complete_runbook_execution(
3626 &self,
3627 runbook_id: String,
3628 error: Option<Vec<String>>,
3629 re_enable_ix_profiling: bool,
3630 ) {
3631 self.with_svm_writer(|svm_writer| {
3632 svm_writer.complete_runbook_execution(&runbook_id, error);
3633 let some_runbook_executing = svm_writer
3634 .runbook_executions
3635 .iter()
3636 .any(|e| e.completed_at.is_none());
3637 if !some_runbook_executing && re_enable_ix_profiling {
3638 svm_writer.instruction_profiling_enabled = true;
3639 }
3640 });
3641 }
3642
3643 pub fn export_snapshot(
3644 &self,
3645 config: ExportSnapshotConfig,
3646 ) -> SurfpoolResult<BTreeMap<String, AccountSnapshot>> {
3647 self.with_svm_reader(|svm_reader| svm_reader.export_snapshot(config))
3648 }
3649
3650 pub fn get_start_time(&self) -> SystemTime {
3651 self.with_svm_reader(|svm_reader| svm_reader.start_time)
3652 }
3653}
3654
3655impl SurfnetSvmLocker {
3657 pub async fn write_program(
3658 &self,
3659 program_id: Pubkey,
3660 authority: Option<Pubkey>,
3661 offset: usize,
3662 data: &[u8],
3663 remote_ctx: &Option<(SurfnetRemoteClient, CommitmentConfig)>,
3664 ) -> SurfpoolResult<()> {
3665 let program_data_address = get_program_data_address(&program_id);
3666
3667 let program_account = self
3668 .get_or_create_program_account(program_id, program_data_address, remote_ctx)
3669 .await?;
3670
3671 let _ = self
3672 .write_program_data_account_with_offset(
3673 program_id,
3674 authority,
3675 program_data_address,
3676 offset,
3677 data,
3678 remote_ctx,
3679 )
3680 .await?;
3681
3682 let set_result = self.with_svm_writer(|svm_writer| {
3688 svm_writer.set_account(&program_id, program_account.clone())
3689 });
3690 if let Err(e) = set_result {
3691 let _ = self.simnet_events_tx().send(SimnetEvent::info(format!(
3692 "Program cache update deferred for {}: {}",
3693 program_id, e
3694 )));
3695 }
3696
3697 Ok(())
3698 }
3699
3700 pub async fn get_or_create_program_account(
3701 &self,
3702 program_id: Pubkey,
3703 program_data_address: Pubkey,
3704 remote_ctx: &Option<(SurfnetRemoteClient, CommitmentConfig)>,
3705 ) -> SurfpoolResult<Account> {
3706 let SvmAccessContext {
3708 inner: program_account_result,
3709 ..
3710 } = self
3711 .get_account(
3712 remote_ctx,
3713 &program_id,
3714 Some(Box::new(move |svm_locker| {
3715 let program_state =
3717 solana_loader_v3_interface::state::UpgradeableLoaderState::Program {
3718 programdata_address: program_data_address,
3719 };
3720 let program_data = bincode::serialize(&program_state)
3721 .expect("Failed to serialize program state");
3722 let program_lamports = svm_locker.with_svm_reader(|svm_reader| {
3723 svm_reader
3724 .inner
3725 .minimum_balance_for_rent_exemption(program_data.len())
3726 });
3727
3728 let _ = svm_locker
3729 .simnet_events_tx()
3730 .send(SimnetEvent::info(format!(
3731 "Creating program account {} with program data address {}",
3732 program_id, program_data_address
3733 )));
3734
3735 GetAccountResult::FoundAccount(
3736 program_id,
3737 solana_account::Account {
3738 lamports: program_lamports,
3739 data: program_data,
3740 owner: solana_sdk_ids::bpf_loader_upgradeable::id(),
3741 executable: true,
3742 rent_epoch: 0,
3743 },
3744 true,
3745 )
3746 })),
3747 )
3748 .await?;
3749
3750 let was_program_created = matches!(
3752 program_account_result,
3753 GetAccountResult::FoundAccount(_, _, true)
3754 );
3755
3756 let program_account = program_account_result.map_account()?;
3758
3759 if program_account.owner != solana_sdk_ids::bpf_loader_upgradeable::id() {
3761 return Err(SurfpoolError::invalid_program_account(
3762 &program_id,
3763 "Account not owned by the BPF Upgradeable Loader",
3764 ));
3765 }
3766
3767 if !program_account.executable {
3769 return Err(SurfpoolError::invalid_program_account(
3770 &program_id,
3771 "Account not executable",
3772 ));
3773 }
3774
3775 if was_program_created {
3777 self.write_account_update(GetAccountResult::FoundAccount(
3778 program_id,
3779 program_account.clone(),
3780 true,
3781 ));
3782 }
3783 Ok(program_account)
3784 }
3785
3786 pub async fn write_program_data_account_with_offset(
3787 &self,
3788 program_id: Pubkey,
3789 authority: Option<Pubkey>,
3790 program_data_address: Pubkey,
3791 offset: usize,
3792 data: &[u8],
3793 remote_ctx: &Option<(SurfnetRemoteClient, CommitmentConfig)>,
3794 ) -> SurfpoolResult<Account> {
3795 let SvmAccessContext {
3797 inner: program_data_result,
3798 slot,
3799 ..
3800 } = self
3801 .get_account(
3802 &remote_ctx,
3803 &program_data_address,
3804 Some(Box::new(move |svm_locker| {
3805 let programdata_state =
3807 solana_loader_v3_interface::state::UpgradeableLoaderState::ProgramData {
3808 slot: svm_locker.get_latest_absolute_slot(),
3809 upgrade_authority_address: authority
3812 .or(Some(solana_system_interface::program::id())),
3813 };
3814 let mut programdata_data = bincode::serialize(&programdata_state)
3815 .expect("Failed to serialize program data state");
3816
3817 programdata_data.extend(vec![0u8; 0]);
3819
3820 let programdata_lamports = svm_locker.with_svm_reader(|svm_reader| {
3821 svm_reader
3822 .inner
3823 .minimum_balance_for_rent_exemption(programdata_data.len())
3824 });
3825
3826 let _ = svm_locker
3827 .simnet_events_tx()
3828 .send(SimnetEvent::info(format!(
3829 "Creating program data account {} for program {}",
3830 program_data_address, program_id
3831 )));
3832
3833 GetAccountResult::FoundAccount(
3834 program_data_address,
3835 solana_account::Account {
3836 lamports: programdata_lamports,
3837 data: programdata_data,
3838 owner: solana_sdk_ids::bpf_loader_upgradeable::id(),
3839 executable: false,
3840 rent_epoch: 0,
3841 },
3842 true,
3843 )
3844 })),
3845 )
3846 .await?;
3847
3848 let mut program_data_account = program_data_result.map_account()?;
3850
3851 let metadata_size =
3853 solana_loader_v3_interface::state::UpgradeableLoaderState::size_of_programdata_metadata(
3854 );
3855
3856 let upgrade_authority_address = match bincode::deserialize::<
3858 solana_loader_v3_interface::state::UpgradeableLoaderState,
3859 >(&program_data_account.data[..metadata_size])
3860 {
3861 Ok(solana_loader_v3_interface::state::UpgradeableLoaderState::ProgramData {
3862 upgrade_authority_address,
3863 ..
3864 }) => upgrade_authority_address,
3865 Ok(_) => {
3866 return Err(SurfpoolError::invalid_program_data_account(
3867 program_data_address,
3868 "Account is not a program data account",
3869 ));
3870 }
3871 Err(e) => {
3872 return Err(SurfpoolError::invalid_program_data_account(
3873 program_data_address,
3874 format!("Invalid program data account state: {}", e),
3875 ));
3876 }
3877 };
3878
3879 let new_metadata = if upgrade_authority_address.ne(&authority) {
3880 let _ = self.simnet_events_tx().send(SimnetEvent::info(format!(
3881 "Updating program authority of program {} to {}",
3882 program_id,
3883 authority.unwrap_or(solana_system_interface::program::id())
3884 )));
3885 solana_loader_v3_interface::state::UpgradeableLoaderState::ProgramData {
3886 slot,
3887 upgrade_authority_address: authority
3888 .or(Some(solana_system_interface::program::id())),
3889 }
3890 } else {
3891 solana_loader_v3_interface::state::UpgradeableLoaderState::ProgramData {
3892 slot,
3893 upgrade_authority_address,
3894 }
3895 };
3896
3897 let metadata_bytes = bincode::serialize(&new_metadata).map_err(|e| {
3898 SurfpoolError::internal(format!("Failed to serialize program data metadata: {}", e))
3899 })?;
3900
3901 let minimum_program_bytes = crate::surfnet::noop_program::NOOP_PROGRAM_ELF;
3905 if program_data_account.data.len() == metadata_size + minimum_program_bytes.len()
3906 && program_data_account.data[metadata_size..] == *minimum_program_bytes
3907 {
3908 program_data_account.data.truncate(metadata_size);
3909 }
3910
3911 let absolute_offset = metadata_size + offset;
3913 let end_offset = absolute_offset + data.len();
3914
3915 if end_offset > program_data_account.data.len() {
3917 let new_size = end_offset;
3918 program_data_account.data.resize(new_size, 0);
3919
3920 let new_lamports = self.with_svm_reader(|svm_reader| {
3922 svm_reader
3923 .inner
3924 .minimum_balance_for_rent_exemption(new_size)
3925 });
3926 program_data_account.lamports = new_lamports;
3927
3928 let _ = self.simnet_events_tx().send(SimnetEvent::info(format!(
3929 "Expanding program data account to {} bytes",
3930 new_size
3931 )));
3932 }
3933
3934 program_data_account.data[..metadata_size].copy_from_slice(&metadata_bytes);
3936 program_data_account.data[absolute_offset..end_offset].copy_from_slice(&data);
3938
3939 self.with_svm_writer(|svm_writer| {
3941 svm_writer.set_account(&program_data_address, program_data_account.clone())?;
3942 Ok::<(), SurfpoolError>(())
3943 })?;
3944
3945 let _ = self.simnet_events_tx().send(SimnetEvent::info(format!(
3946 "Wrote {} bytes to program {} at offset {}",
3947 data.len(),
3948 program_id,
3949 offset
3950 )));
3951
3952 Ok(program_data_account)
3953 }
3954}
3955
3956pub(crate) fn apply_rpc_filters(
3958 account_data: &[u8],
3959 filters: &[RpcFilterType],
3960) -> SurfpoolResult<bool> {
3961 for filter in filters {
3962 match filter {
3963 RpcFilterType::DataSize(size) => {
3964 if account_data.len() as u64 != *size {
3965 return Ok(false);
3966 }
3967 }
3968 RpcFilterType::Memcmp(memcmp_filter) => {
3969 if !memcmp_filter.bytes_match(account_data) {
3971 return Ok(false); }
3973 }
3974 RpcFilterType::TokenAccountState => {
3975 return Err(SurfpoolError::internal(
3976 "TokenAccountState filter is not supported",
3977 ));
3978 }
3979 }
3980 }
3981 Ok(true)
3982}
3983
3984pub fn is_supported_token_program(program_id: &Pubkey) -> bool {
3986 *program_id == spl_token_interface::ID || *program_id == spl_token_2022_interface::ID
3987}
3988
3989fn update_programdata_account(
3990 program_id: &Pubkey,
3991 programdata_account: &mut Account,
3992 new_authority: Option<Pubkey>,
3993) -> SurfpoolResult<Option<Pubkey>> {
3994 let upgradeable_loader_state =
3995 bincode::deserialize::<UpgradeableLoaderState>(&programdata_account.data).map_err(|e| {
3996 SurfpoolError::invalid_program_account(
3997 program_id,
3998 format!("Failed to serialize program data: {}", e),
3999 )
4000 })?;
4001 if let UpgradeableLoaderState::ProgramData {
4002 upgrade_authority_address,
4003 slot,
4004 } = upgradeable_loader_state
4005 {
4006 let offset = if upgrade_authority_address.is_some() {
4007 UpgradeableLoaderState::size_of_programdata_metadata()
4008 } else {
4009 UpgradeableLoaderState::size_of_programdata_metadata()
4010 - serialized_size(&Pubkey::default()).unwrap() as usize
4011 };
4012
4013 let mut data = bincode::serialize(&UpgradeableLoaderState::ProgramData {
4014 upgrade_authority_address: new_authority,
4015 slot,
4016 })
4017 .map_err(|e| {
4018 SurfpoolError::invalid_program_account(
4019 program_id,
4020 format!("Failed to serialize program data: {}", e),
4021 )
4022 })?;
4023
4024 data.append(&mut programdata_account.data[offset..].to_vec());
4025
4026 programdata_account.data = data;
4027
4028 Ok(upgrade_authority_address)
4029 } else {
4030 Err(SurfpoolError::invalid_program_account(
4031 program_id,
4032 "Invalid program data account",
4033 ))
4034 }
4035}
4036
4037pub fn format_ui_amount_string(amount: u64, decimals: u8) -> String {
4038 if decimals > 0 {
4039 let divisor = 10u64.pow(decimals as u32);
4040 format!(
4041 "{:.decimals$}",
4042 amount as f64 / divisor as f64,
4043 decimals = decimals as usize
4044 )
4045 } else {
4046 amount.to_string()
4047 }
4048}
4049
4050pub fn format_ui_amount(amount: u64, decimals: u8) -> f64 {
4051 if decimals > 0 {
4052 let divisor = 10u64.pow(decimals as u32);
4053 amount as f64 / divisor as f64
4054 } else {
4055 amount as f64
4056 }
4057}
4058
4059#[cfg(test)]
4060mod tests {
4061 use std::collections::HashMap;
4062
4063 use solana_account::Account;
4064 use solana_account_decoder::UiAccountEncoding;
4065 use solana_epoch_schedule::EpochSchedule;
4066 use solana_transaction_status::TransactionStatusMeta;
4067
4068 use super::*;
4069 use crate::{
4070 scenarios::registry::PYTH_V2_IDL_CONTENT,
4071 surfnet::{BlockHeader, SurfnetSvm, svm::apply_override_to_decoded_account},
4072 };
4073
4074 #[test]
4075 fn test_get_forged_account_data_with_pyth_fixture() {
4076 use borsh::{BorshDeserialize, BorshSerialize};
4077
4078 #[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq)]
4080 pub enum VerificationLevel {
4081 Partial { num_signatures: u8 },
4082 Full,
4083 }
4084
4085 #[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq)]
4086 pub struct PriceFeedMessage {
4087 pub feed_id: [u8; 32],
4088 pub price: i64,
4089 pub conf: u64,
4090 pub exponent: i32,
4091 pub publish_time: i64,
4092 pub prev_publish_time: i64,
4093 pub ema_price: i64,
4094 pub ema_conf: u64,
4095 }
4096
4097 #[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq)]
4098 pub struct PriceUpdateV2 {
4099 pub write_authority: Pubkey,
4100 pub verification_level: VerificationLevel,
4101 pub price_message: PriceFeedMessage,
4102 pub posted_slot: u64,
4103 }
4104
4105 let account_data_hex = vec![
4107 0x22, 0xf1, 0x23, 0x63, 0x9d, 0x7e, 0xf4, 0xcd, 0x35, 0xa7, 0x0c, 0x11, 0x16, 0x2f, 0xbf, 0x5a, 0x0e, 0x7f, 0x7d, 0x2f, 0x96, 0xe1,
4109 0x9f, 0x97, 0xb0, 0x22, 0x46, 0xa1, 0x56, 0x87, 0xee, 0x67, 0x27, 0x94, 0x89, 0x74,
4110 0x48, 0xe6, 0x58, 0xde, 0x01, 0xe6, 0x2d, 0xf6, 0xc8, 0xb4, 0xa8, 0x5f, 0xe1, 0xa6,
4111 0x7d, 0xb4, 0x4d, 0xc1, 0x2d, 0xe5, 0xdb, 0x33, 0x0f, 0x7a, 0xc6, 0x6b, 0x72, 0xdc,
4112 0x65, 0x8a, 0xfe, 0xdf, 0x0f, 0x4a, 0x41, 0x5b, 0x43, 0xd7, 0x1f, 0x18, 0x64, 0x5f,
4113 0x0a, 0x00, 0x00, 0x96, 0x67, 0xea, 0xc5, 0x00, 0x00, 0x00, 0x00, 0xf8, 0xff, 0xff,
4114 0xff, 0x5f, 0x2b, 0x00, 0x69, 0x00, 0x00, 0x00, 0x00, 0x5e, 0x2b, 0x00, 0x69, 0x00,
4115 0x00, 0x00, 0x00, 0xa0, 0x7c, 0x1a, 0x38, 0x63, 0x0a, 0x00, 0x00, 0x94, 0xa6, 0xb9,
4116 0xb5, 0x00, 0x00, 0x00, 0x00, 0x8c, 0x5e, 0x6d, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00,
4117 ];
4118
4119 let idl: Idl = serde_json::from_str(PYTH_V2_IDL_CONTENT).expect("Failed to load IDL");
4121
4122 let mut overrides: HashMap<String, serde_json::Value> = HashMap::new();
4125
4126 let account_def = idl
4128 .accounts
4129 .iter()
4130 .find(|acc| acc.discriminator.eq(&account_data_hex[..8]));
4131
4132 assert!(
4133 account_def.is_some(),
4134 "Should find PriceUpdateV2 account by discriminator"
4135 );
4136 assert_eq!(account_def.unwrap().name, "PriceUpdateV2");
4137
4138 let (surfnet_svm, _simnet_events_rx, _geyser_events_rx) = SurfnetSvm::default();
4140 let svm_locker = SurfnetSvmLocker::new(surfnet_svm);
4141
4142 let account_pubkey = Pubkey::from_str_const("rec5EKMGg6MxZYaMdyBfgwp4d5rB9T1VQH5pJv5LtFJ");
4144 svm_locker.register_idl(idl.clone(), None).unwrap();
4145
4146 let pyth_account = Account {
4148 lamports: 1_000_000,
4149 data: account_data_hex.clone(),
4150 owner: account_pubkey,
4151 executable: false,
4152 rent_epoch: 0,
4153 };
4154
4155 let ui_account = svm_locker.encode_ui_account(
4157 &account_pubkey,
4158 &pyth_account,
4159 UiAccountEncoding::JsonParsed,
4160 None,
4161 None, );
4163
4164 println!("UI Account lamports: {}", ui_account.lamports);
4166 println!("UI Account owner: {}", ui_account.owner);
4167
4168 use solana_account_decoder::UiAccountData;
4170 match &ui_account.data {
4171 UiAccountData::Json(parsed_account) => {
4172 let parsed_obj = &parsed_account.parsed;
4173
4174 let price_message = parsed_obj
4176 .get("price_message")
4177 .expect("Should have price_message field")
4178 .as_object()
4179 .expect("price_message should be an object");
4180
4181 let price = price_message
4183 .get("price")
4184 .expect("Should have price field")
4185 .as_i64()
4186 .expect("price should be a number");
4187 assert_eq!(price, 11404817473495, "Price should match expected value");
4188
4189 let exponent = price_message
4191 .get("exponent")
4192 .expect("Should have exponent field")
4193 .as_i64()
4194 .expect("exponent should be a number");
4195 assert_eq!(exponent, -8, "Exponent should be -8");
4196
4197 let ema_price = price_message
4199 .get("ema_price")
4200 .expect("Should have ema_price field")
4201 .as_i64()
4202 .expect("ema_price should be a number");
4203 assert_eq!(
4204 ema_price, 11421259300000,
4205 "EMA price should match expected value"
4206 );
4207
4208 let publish_time = price_message
4210 .get("publish_time")
4211 .expect("Should have publish_time field")
4212 .as_i64()
4213 .expect("publish_time should be a number");
4214 assert_eq!(
4215 publish_time, 1761618783,
4216 "Publish time should match expected value"
4217 );
4218
4219 println!("✓ All price assertions passed!");
4220 }
4221 _ => panic!("Expected JSON parsed account data"),
4222 }
4223
4224 println!("\n--- Testing get_forged_account_data without overrides ---");
4226 let forged_data_no_overrides = svm_locker.get_forged_account_data(
4227 &account_pubkey,
4228 &account_data_hex,
4229 &idl,
4230 &overrides,
4231 );
4232
4233 match forged_data_no_overrides {
4234 Ok(data) => {
4235 assert_eq!(
4237 data, account_data_hex,
4238 "Data without overrides should match original"
4239 );
4240 println!("✓ Forged data without overrides matches original!");
4241 }
4242 Err(e) => {
4243 println!("Expected error (Borsh vs JSON): {:?}", e);
4245 println!("Note: This documents the need for proper Borsh implementation");
4246 }
4247 }
4248
4249 println!("\n--- Testing get_forged_account_data with overrides ---");
4251
4252 let new_price = 999999999999i64;
4254 let new_publish_time = 1234567890i64;
4255 let new_ema_price = 888888888888i64;
4256
4257 overrides.insert("price_message.price".into(), json!(new_price));
4258 overrides.insert("price_message.publish_time".into(), json!(new_publish_time));
4259 overrides.insert("price_message.ema_price".into(), json!(new_ema_price));
4260
4261 let forged_data_with_overrides = svm_locker.get_forged_account_data(
4262 &account_pubkey,
4263 &account_data_hex,
4264 &idl,
4265 &overrides,
4266 );
4267
4268 match forged_data_with_overrides {
4269 Ok(modified_data) => {
4270 assert_ne!(
4272 modified_data, account_data_hex,
4273 "Modified data should be different from original"
4274 );
4275 println!("✓ Modified data is different from original!");
4276
4277 let modified_account = Account {
4279 lamports: 1_000_000,
4280 data: modified_data.clone(),
4281 owner: account_pubkey,
4282 executable: false,
4283 rent_epoch: 0,
4284 };
4285
4286 let modified_ui_account = svm_locker.encode_ui_account(
4288 &account_pubkey,
4289 &modified_account,
4290 UiAccountEncoding::JsonParsed,
4291 None,
4292 None,
4293 );
4294
4295 match &modified_ui_account.data {
4297 UiAccountData::Json(parsed_account) => {
4298 let parsed_obj = &parsed_account.parsed;
4299 let price_message = parsed_obj
4300 .get("price_message")
4301 .expect("Should have price_message field")
4302 .as_object()
4303 .expect("price_message should be an object");
4304
4305 let modified_price = price_message
4307 .get("price")
4308 .expect("Should have price field")
4309 .as_i64()
4310 .expect("price should be a number");
4311 assert_eq!(
4312 modified_price, new_price,
4313 "Modified price should match override value"
4314 );
4315
4316 let modified_publish_time = price_message
4318 .get("publish_time")
4319 .expect("Should have publish_time field")
4320 .as_i64()
4321 .expect("publish_time should be a number");
4322 assert_eq!(
4323 modified_publish_time, new_publish_time,
4324 "Modified publish_time should match override value"
4325 );
4326
4327 let modified_ema_price = price_message
4329 .get("ema_price")
4330 .expect("Should have ema_price field")
4331 .as_i64()
4332 .expect("ema_price should be a number");
4333 assert_eq!(
4334 modified_ema_price, new_ema_price,
4335 "Modified ema_price should match override value"
4336 );
4337
4338 let exponent = price_message
4340 .get("exponent")
4341 .expect("Should have exponent field")
4342 .as_i64()
4343 .expect("exponent should be a number");
4344 assert_eq!(exponent, -8, "Exponent should remain unchanged");
4345
4346 println!("✓ All override assertions passed!");
4347 println!(" - Price changed: 11404817473495 → {}", new_price);
4348 println!(
4349 " - Publish time changed: 1761618783 → {}",
4350 new_publish_time
4351 );
4352 println!(" - EMA price changed: 11421259300000 → {}", new_ema_price);
4353 println!(" - Exponent unchanged: -8");
4354 }
4355 _ => panic!("Expected JSON parsed account data for modified account"),
4356 }
4357 }
4358 Err(e) => {
4359 println!("Expected error (Borsh vs JSON): {:?}", e);
4361 println!("Note: Once Borsh serialization is implemented, this test will:");
4362 println!(" 1. Successfully modify the account data");
4363 println!(" 2. Verify price changed to: {}", new_price);
4364 println!(" 3. Verify publish_time changed to: {}", new_publish_time);
4365 println!(" 4. Verify ema_price changed to: {}", new_ema_price);
4366 println!(" 5. Verify other fields remain unchanged");
4367 }
4368 }
4369
4370 println!("\n--- Step 8: Testing with Borsh structures ---");
4372
4373 let account_bytes = &account_data_hex[8..];
4375 println!(
4376 "Account data length (without discriminator): {} bytes",
4377 account_bytes.len()
4378 );
4379
4380 let mut reader = std::io::Cursor::new(account_bytes);
4381 let original_price_update = PriceUpdateV2::deserialize_reader(&mut reader)
4382 .expect("Should deserialize Pyth account data with Borsh");
4383
4384 let bytes_read = reader.position() as usize;
4385 println!("Bytes read by Borsh: {}", bytes_read);
4386 if bytes_read < account_bytes.len() {
4387 println!(
4388 "Note: {} extra bytes at end (likely padding)",
4389 account_bytes.len() - bytes_read
4390 );
4391 }
4392
4393 println!("Original Borsh-deserialized data:");
4394 println!(" - Price: {}", original_price_update.price_message.price);
4395 println!(
4396 " - Exponent: {}",
4397 original_price_update.price_message.exponent
4398 );
4399 println!(
4400 " - EMA Price: {}",
4401 original_price_update.price_message.ema_price
4402 );
4403 println!(
4404 " - Publish time: {}",
4405 original_price_update.price_message.publish_time
4406 );
4407
4408 assert_eq!(
4410 original_price_update.price_message.price, 11404817473495,
4411 "Borsh price should match JSON parsed value"
4412 );
4413 assert_eq!(
4414 original_price_update.price_message.exponent, -8,
4415 "Borsh exponent should match JSON parsed value"
4416 );
4417 assert_eq!(
4418 original_price_update.price_message.ema_price, 11421259300000,
4419 "Borsh ema_price should match JSON parsed value"
4420 );
4421 assert_eq!(
4422 original_price_update.price_message.publish_time, 1761618783,
4423 "Borsh publish_time should match JSON parsed value"
4424 );
4425
4426 println!("✓ Borsh deserialization matches JSON parsing!");
4427
4428 println!("\n--- Step 9: Modifying account data with Borsh ---");
4430
4431 let mut modified_price_update = original_price_update.clone();
4432 modified_price_update.price_message.price = new_price;
4433 modified_price_update.price_message.publish_time = new_publish_time;
4434 modified_price_update.price_message.ema_price = new_ema_price;
4435
4436 let modified_account_data =
4438 borsh::to_vec(&modified_price_update).expect("Should serialize modified data");
4439
4440 let mut full_modified_data = account_data_hex[..8].to_vec();
4442 full_modified_data.extend_from_slice(&modified_account_data);
4443
4444 println!("Modified Borsh-serialized data:");
4445 println!(
4446 " - Price: {} → {}",
4447 original_price_update.price_message.price, new_price
4448 );
4449 println!(
4450 " - Publish time: {} → {}",
4451 original_price_update.price_message.publish_time, new_publish_time
4452 );
4453 println!(
4454 " - EMA Price: {} → {}",
4455 original_price_update.price_message.ema_price, new_ema_price
4456 );
4457 println!(
4458 " - Exponent: {} (unchanged)",
4459 modified_price_update.price_message.exponent
4460 );
4461
4462 assert_ne!(
4464 full_modified_data, account_data_hex,
4465 "Modified data should differ from original"
4466 );
4467
4468 let mut modified_reader = std::io::Cursor::new(&full_modified_data[8..]);
4470 let reloaded_price_update = PriceUpdateV2::deserialize_reader(&mut modified_reader)
4471 .expect("Should deserialize modified data");
4472
4473 assert_eq!(
4474 reloaded_price_update.price_message.price, new_price,
4475 "Reloaded price should match modified value"
4476 );
4477 assert_eq!(
4478 reloaded_price_update.price_message.publish_time, new_publish_time,
4479 "Reloaded publish_time should match modified value"
4480 );
4481 assert_eq!(
4482 reloaded_price_update.price_message.ema_price, new_ema_price,
4483 "Reloaded ema_price should match modified value"
4484 );
4485 assert_eq!(
4486 reloaded_price_update.price_message.exponent,
4487 original_price_update.price_message.exponent,
4488 "Exponent should remain unchanged"
4489 );
4490
4491 println!("✓ Borsh round-trip successful!");
4492
4493 println!("\n--- Step 10: Verify modified data with encode_ui_account ---");
4495
4496 let modified_test_account = Account {
4497 lamports: 1_000_000,
4498 data: full_modified_data,
4499 owner: account_pubkey,
4500 executable: false,
4501 rent_epoch: 0,
4502 };
4503
4504 let modified_ui_account = svm_locker.encode_ui_account(
4505 &account_pubkey,
4506 &modified_test_account,
4507 UiAccountEncoding::JsonParsed,
4508 None,
4509 None,
4510 );
4511
4512 match &modified_ui_account.data {
4514 UiAccountData::Json(parsed_account) => {
4515 let parsed_obj = &parsed_account.parsed;
4516 let price_message = parsed_obj
4517 .get("price_message")
4518 .expect("Should have price_message")
4519 .as_object()
4520 .expect("Should be object");
4521
4522 let final_price = price_message
4523 .get("price")
4524 .expect("Should have price")
4525 .as_i64()
4526 .expect("Should be i64");
4527 let final_publish_time = price_message
4528 .get("publish_time")
4529 .expect("Should have publish_time")
4530 .as_i64()
4531 .expect("Should be i64");
4532 let final_ema_price = price_message
4533 .get("ema_price")
4534 .expect("Should have ema_price")
4535 .as_i64()
4536 .expect("Should be i64");
4537
4538 assert_eq!(
4539 final_price, new_price,
4540 "JSON-parsed price should match Borsh value"
4541 );
4542 assert_eq!(
4543 final_publish_time, new_publish_time,
4544 "JSON-parsed publish_time should match Borsh value"
4545 );
4546 assert_eq!(
4547 final_ema_price, new_ema_price,
4548 "JSON-parsed ema_price should match Borsh value"
4549 );
4550 }
4551 _ => panic!("Expected JSON parsed data"),
4552 }
4553 }
4554
4555 #[test]
4556 fn test_apply_override_to_decoded_account() {
4557 use txtx_addon_kit::{indexmap::IndexMap, types::types::Value};
4558
4559 let mut price_message_obj = IndexMap::new();
4561 price_message_obj.insert("price".to_string(), Value::Integer(100));
4562 price_message_obj.insert("publish_time".to_string(), Value::Integer(1234567890));
4563
4564 let mut decoded_value = IndexMap::new();
4565 decoded_value.insert(
4566 "price_message".to_string(),
4567 Value::Object(price_message_obj),
4568 );
4569 decoded_value.insert("expo".to_string(), Value::Integer(-8));
4570
4571 let mut decoded_value = Value::Object(decoded_value);
4572
4573 let result =
4575 apply_override_to_decoded_account(&mut decoded_value, "expo", &serde_json::json!(-6));
4576 assert!(result.is_ok());
4577 match &decoded_value {
4578 Value::Object(map) => {
4579 assert_eq!(map.get("expo"), Some(&Value::Integer(-6)));
4580 }
4581 _ => panic!("Expected Object"),
4582 }
4583
4584 let result = apply_override_to_decoded_account(
4586 &mut decoded_value,
4587 "price_message.price",
4588 &serde_json::json!(200),
4589 );
4590 assert!(result.is_ok());
4591 match &decoded_value {
4592 Value::Object(map) => match map.get("price_message") {
4593 Some(Value::Object(price_msg)) => {
4594 assert_eq!(price_msg.get("price"), Some(&Value::Integer(200)));
4595 }
4596 _ => panic!("Expected price_message to be Object"),
4597 },
4598 _ => panic!("Expected Object"),
4599 }
4600
4601 let result = apply_override_to_decoded_account(
4603 &mut decoded_value,
4604 "nonexistent.field",
4605 &serde_json::json!(999),
4606 );
4607 assert!(result.is_err());
4608 }
4609
4610 #[tokio::test(flavor = "multi_thread")]
4611 async fn test_v0_transaction_without_alt_emits_geyser_account_updates() {
4612 use std::time::Duration;
4613
4614 use crossbeam_channel::{RecvTimeoutError, unbounded};
4615 use solana_keypair::Keypair;
4616 use solana_message::{VersionedMessage, v0};
4617 use solana_signer::Signer;
4618 use solana_system_interface::instruction as system_instruction;
4619 use solana_transaction::versioned::VersionedTransaction;
4620
4621 let (svm, _events_rx, geyser_rx) = SurfnetSvm::default();
4622 let locker = SurfnetSvmLocker::new(svm);
4623
4624 let payer = Keypair::new();
4625 let payer_pubkey = payer.pubkey();
4626 let recipient = Pubkey::new_unique();
4627
4628 let _ = locker
4629 .airdrop(&payer_pubkey, 1_000_000_000)
4630 .expect("airdrop should succeed");
4631
4632 let recent_blockhash = locker.latest_absolute_blockhash();
4633 let message = v0::Message::try_compile(
4634 &payer_pubkey,
4635 &[system_instruction::transfer(
4636 &payer_pubkey,
4637 &recipient,
4638 1_000_000,
4639 )],
4640 &[],
4641 recent_blockhash,
4642 )
4643 .expect("v0 message should compile");
4644
4645 let tx =
4646 VersionedTransaction::try_new(VersionedMessage::V0(message), &[payer.insecure_clone()])
4647 .expect("v0 transaction should sign");
4648
4649 let tx_signature = tx.signatures[0];
4650 let (status_tx, _status_rx) = unbounded();
4651 locker
4652 .process_transaction(&None, tx, status_tx, true, true)
4653 .await
4654 .expect("transaction processing should succeed");
4655
4656 let mut account_updates = vec![];
4657 let mut got_transaction_notify = false;
4658
4659 for _ in 0..32 {
4660 match geyser_rx.recv_timeout(Duration::from_millis(50)) {
4661 Ok(crate::surfnet::GeyserEvent::UpdateAccount(update)) => {
4662 account_updates.push(update);
4663 }
4664 Ok(crate::surfnet::GeyserEvent::NotifyTransaction(_, _)) => {
4665 got_transaction_notify = true;
4666 }
4667 Ok(_) => {}
4668 Err(RecvTimeoutError::Timeout) | Err(RecvTimeoutError::Disconnected) => break,
4669 }
4670 }
4671
4672 assert!(
4673 got_transaction_notify,
4674 "Expected NotifyTransaction geyser event"
4675 );
4676 assert!(
4677 !account_updates.is_empty(),
4678 "Expected account update geyser events for v0 transaction without ALTs"
4679 );
4680 assert!(
4681 account_updates.iter().any(|u| u.pubkey == payer_pubkey),
4682 "Expected payer account update"
4683 );
4684 assert!(
4685 account_updates.iter().any(|u| u.pubkey == recipient),
4686 "Expected recipient account update"
4687 );
4688
4689 for update in account_updates {
4690 let sanitized_transaction = update
4691 .sanitized_transaction
4692 .expect("Expected sanitized transaction on account update");
4693 assert_eq!(
4694 *sanitized_transaction.signature(),
4695 tx_signature,
4696 "Account update should carry transaction signature"
4697 );
4698 }
4699 }
4700
4701 #[tokio::test(flavor = "multi_thread")]
4702 async fn test_get_signatures_for_address_local_populates_memo_and_block_time() {
4703 use std::str::FromStr;
4704
4705 use crossbeam_channel::unbounded;
4706 use solana_instruction::Instruction;
4707 use solana_keypair::Keypair;
4708 use solana_message::{VersionedMessage, v0};
4709 use solana_signer::Signer;
4710 use solana_system_interface::instruction as system_instruction;
4711 use solana_transaction::versioned::VersionedTransaction;
4712
4713 let (svm, _events_rx, _geyser_rx) = SurfnetSvm::default();
4714 let locker = SurfnetSvmLocker::new(svm);
4715
4716 let payer = Keypair::new();
4717 let payer_pubkey = payer.pubkey();
4718 let recipient = Pubkey::new_unique();
4719
4720 let _ = locker
4721 .airdrop(&payer_pubkey, 1_000_000_000)
4722 .expect("airdrop should succeed");
4723
4724 let memo_program = Pubkey::from_str("MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr").unwrap();
4727
4728 async fn process(
4730 locker: &SurfnetSvmLocker,
4731 payer: &Keypair,
4732 instructions: &[Instruction],
4733 ) -> String {
4734 let blockhash = locker.latest_absolute_blockhash();
4735 let message = v0::Message::try_compile(&payer.pubkey(), instructions, &[], blockhash)
4736 .expect("v0 message should compile");
4737 let tx = VersionedTransaction::try_new(
4738 VersionedMessage::V0(message),
4739 &[payer.insecure_clone()],
4740 )
4741 .expect("v0 transaction should sign");
4742 let signature = tx.signatures[0].to_string();
4743 let (status_tx, _status_rx) = unbounded();
4744 locker
4745 .process_transaction(&None, tx, status_tx, true, true)
4746 .await
4747 .expect("transaction processing should succeed");
4748 signature
4749 }
4750
4751 let sig_with_memo = process(
4753 &locker,
4754 &payer,
4755 &[
4756 system_instruction::transfer(&payer_pubkey, &recipient, 1_000_000),
4757 Instruction {
4758 program_id: memo_program,
4759 accounts: vec![],
4760 data: b"hello-memo".to_vec(),
4761 },
4762 ],
4763 )
4764 .await;
4765
4766 let sig_without_memo = process(
4768 &locker,
4769 &payer,
4770 &[system_instruction::transfer(
4771 &payer_pubkey,
4772 &recipient,
4773 1_000_000,
4774 )],
4775 )
4776 .await;
4777
4778 let SvmAccessContext { inner: sigs, .. } =
4779 locker.get_signatures_for_address_local(&payer_pubkey, None);
4780
4781 let row_with_memo = sigs
4782 .iter()
4783 .find(|s| s.signature == sig_with_memo)
4784 .expect("memo-bearing tx should appear in getSignaturesForAddress");
4785 let row_without_memo = sigs
4786 .iter()
4787 .find(|s| s.signature == sig_without_memo)
4788 .expect("plain tx should appear in getSignaturesForAddress");
4789
4790 assert_eq!(
4791 row_with_memo.memo,
4792 Some("[10] hello-memo".to_string()),
4793 "memo-bearing tx should expose the Agave-formatted memo summary"
4794 );
4795 assert_eq!(
4796 row_without_memo.memo, None,
4797 "plain transfer must still yield a null memo"
4798 );
4799
4800 for row in [row_with_memo, row_without_memo] {
4804 let expected = (locker.with_svm_reader(|r| r.calculate_block_time_for_slot(row.slot))
4805 / 1_000) as i64;
4806 assert_eq!(
4807 row.block_time,
4808 Some(expected),
4809 "block_time should be the slot's synthetic unix-seconds time"
4810 );
4811 }
4812 }
4813
4814 #[tokio::test(flavor = "multi_thread")]
4817 async fn test_load_snapshot_basic() {
4818 use base64::{Engine, engine::general_purpose};
4819
4820 let (svm, _events_rx, _geyser_rx) = SurfnetSvm::default();
4821 let locker = SurfnetSvmLocker::new(svm);
4822
4823 let pubkey = Pubkey::new_unique();
4824 let owner = Pubkey::new_unique();
4825 let data = vec![1, 2, 3, 4, 5];
4826 let data_base64 = general_purpose::STANDARD.encode(&data);
4827
4828 let mut snapshot = BTreeMap::new();
4829 snapshot.insert(
4830 pubkey.to_string(),
4831 Some(AccountSnapshot {
4832 lamports: 1_000_000,
4833 owner: owner.to_string(),
4834 executable: false,
4835 rent_epoch: 0,
4836 data: data_base64,
4837 parsed_data: None,
4838 }),
4839 );
4840
4841 let loaded = locker
4842 .load_snapshot(&snapshot, None, CommitmentConfig::confirmed())
4843 .await
4844 .unwrap();
4845 assert_eq!(loaded, 1);
4846
4847 let account = locker
4848 .with_svm_reader(|svm| svm.get_account(&pubkey))
4849 .unwrap();
4850 assert!(account.is_some());
4851 let account = account.unwrap();
4852 assert_eq!(account.lamports, 1_000_000);
4853 assert_eq!(account.owner, owner);
4854 assert_eq!(account.data, data);
4855 assert!(!account.executable);
4856 }
4857
4858 #[tokio::test(flavor = "multi_thread")]
4859 async fn test_load_snapshot_multiple_accounts() {
4860 use base64::{Engine, engine::general_purpose};
4861
4862 let (svm, _events_rx, _geyser_rx) = SurfnetSvm::default();
4863 let locker = SurfnetSvmLocker::new(svm);
4864
4865 let owner = Pubkey::new_unique();
4866 let mut snapshot = BTreeMap::new();
4867
4868 let pubkeys: Vec<Pubkey> = (0..5).map(|_| Pubkey::new_unique()).collect();
4870 for (i, pubkey) in pubkeys.iter().enumerate() {
4871 let data = vec![i as u8; 10];
4872 snapshot.insert(
4873 pubkey.to_string(),
4874 Some(AccountSnapshot {
4875 lamports: (i as u64 + 1) * 1_000_000,
4876 owner: owner.to_string(),
4877 executable: false,
4878 rent_epoch: 0,
4879 data: general_purpose::STANDARD.encode(&data),
4880 parsed_data: None,
4881 }),
4882 );
4883 }
4884
4885 let loaded = locker
4886 .load_snapshot(&snapshot, None, CommitmentConfig::confirmed())
4887 .await
4888 .unwrap();
4889 assert_eq!(loaded, 5);
4890
4891 for (i, pubkey) in pubkeys.iter().enumerate() {
4893 let account = locker
4894 .with_svm_reader(|svm| svm.get_account(pubkey))
4895 .unwrap()
4896 .unwrap();
4897 assert_eq!(account.lamports, (i as u64 + 1) * 1_000_000);
4898 assert_eq!(account.owner, owner);
4899 assert_eq!(account.data, vec![i as u8; 10]);
4900 }
4901 }
4902
4903 #[tokio::test(flavor = "multi_thread")]
4904 async fn test_load_snapshot_skips_none_without_remote() {
4905 use base64::{Engine, engine::general_purpose};
4906
4907 let (svm, _events_rx, _geyser_rx) = SurfnetSvm::default();
4908 let locker = SurfnetSvmLocker::new(svm);
4909
4910 let pubkey1 = Pubkey::new_unique();
4911 let pubkey2 = Pubkey::new_unique();
4912 let owner = Pubkey::new_unique();
4913
4914 let mut snapshot = BTreeMap::new();
4915
4916 snapshot.insert(
4918 pubkey1.to_string(),
4919 Some(AccountSnapshot {
4920 lamports: 1_000_000,
4921 owner: owner.to_string(),
4922 executable: false,
4923 rent_epoch: 0,
4924 data: general_purpose::STANDARD.encode(&[1, 2, 3]),
4925 parsed_data: None,
4926 }),
4927 );
4928
4929 snapshot.insert(pubkey2.to_string(), None);
4931
4932 let loaded = locker
4933 .load_snapshot(&snapshot, None, CommitmentConfig::confirmed())
4934 .await
4935 .unwrap();
4936 assert_eq!(loaded, 1);
4937
4938 assert!(
4940 locker
4941 .with_svm_reader(|svm| svm.get_account(&pubkey1))
4942 .unwrap()
4943 .is_some()
4944 );
4945
4946 assert!(
4948 locker
4949 .with_svm_reader(|svm| svm.get_account(&pubkey2))
4950 .unwrap()
4951 .is_none()
4952 );
4953 }
4954
4955 #[tokio::test(flavor = "multi_thread")]
4956 async fn test_load_snapshot_invalid_pubkey() {
4957 use base64::{Engine, engine::general_purpose};
4958
4959 let (svm, _events_rx, _geyser_rx) = SurfnetSvm::default();
4960 let locker = SurfnetSvmLocker::new(svm);
4961
4962 let owner = Pubkey::new_unique();
4963 let mut snapshot = BTreeMap::new();
4964
4965 snapshot.insert(
4967 "invalid_pubkey".to_string(),
4968 Some(AccountSnapshot {
4969 lamports: 1_000_000,
4970 owner: owner.to_string(),
4971 executable: false,
4972 rent_epoch: 0,
4973 data: general_purpose::STANDARD.encode(&[1, 2, 3]),
4974 parsed_data: None,
4975 }),
4976 );
4977
4978 let loaded = locker
4980 .load_snapshot(&snapshot, None, CommitmentConfig::confirmed())
4981 .await
4982 .unwrap();
4983 assert_eq!(loaded, 0);
4984 }
4985
4986 #[tokio::test(flavor = "multi_thread")]
4987 async fn test_load_snapshot_invalid_base64_data() {
4988 let (svm, _events_rx, _geyser_rx) = SurfnetSvm::default();
4989 let locker = SurfnetSvmLocker::new(svm);
4990
4991 let pubkey = Pubkey::new_unique();
4992 let owner = Pubkey::new_unique();
4993 let mut snapshot = BTreeMap::new();
4994
4995 snapshot.insert(
4997 pubkey.to_string(),
4998 Some(AccountSnapshot {
4999 lamports: 1_000_000,
5000 owner: owner.to_string(),
5001 executable: false,
5002 rent_epoch: 0,
5003 data: "not_valid_base64!!!".to_string(),
5004 parsed_data: None,
5005 }),
5006 );
5007
5008 let loaded = locker
5010 .load_snapshot(&snapshot, None, CommitmentConfig::confirmed())
5011 .await
5012 .unwrap();
5013 assert_eq!(loaded, 0);
5014
5015 assert!(
5017 locker
5018 .with_svm_reader(|svm| svm.get_account(&pubkey))
5019 .unwrap()
5020 .is_none()
5021 );
5022 }
5023
5024 #[tokio::test(flavor = "multi_thread")]
5025 async fn test_load_snapshot_invalid_owner() {
5026 use base64::{Engine, engine::general_purpose};
5027
5028 let (svm, _events_rx, _geyser_rx) = SurfnetSvm::default();
5029 let locker = SurfnetSvmLocker::new(svm);
5030
5031 let pubkey = Pubkey::new_unique();
5032 let mut snapshot = BTreeMap::new();
5033
5034 snapshot.insert(
5036 pubkey.to_string(),
5037 Some(AccountSnapshot {
5038 lamports: 1_000_000,
5039 owner: "invalid_owner".to_string(),
5040 executable: false,
5041 rent_epoch: 0,
5042 data: general_purpose::STANDARD.encode(&[1, 2, 3]),
5043 parsed_data: None,
5044 }),
5045 );
5046
5047 let loaded = locker
5049 .load_snapshot(&snapshot, None, CommitmentConfig::confirmed())
5050 .await
5051 .unwrap();
5052 assert_eq!(loaded, 0);
5053 }
5054
5055 #[tokio::test(flavor = "multi_thread")]
5056 async fn test_load_snapshot_empty() {
5057 let (svm, _events_rx, _geyser_rx) = SurfnetSvm::default();
5058 let locker = SurfnetSvmLocker::new(svm);
5059
5060 let snapshot = BTreeMap::new();
5061 let loaded = locker
5062 .load_snapshot(&snapshot, None, CommitmentConfig::confirmed())
5063 .await
5064 .unwrap();
5065 assert_eq!(loaded, 0);
5066 }
5067
5068 #[tokio::test(flavor = "multi_thread")]
5069 async fn test_load_snapshot_updates_account_registries() {
5070 use base64::{Engine, engine::general_purpose};
5071
5072 let (svm, _events_rx, _geyser_rx) = SurfnetSvm::default();
5073 let locker = SurfnetSvmLocker::new(svm);
5074
5075 let pubkey = Pubkey::new_unique();
5076 let owner = Pubkey::new_unique();
5077
5078 let mut snapshot = BTreeMap::new();
5079 snapshot.insert(
5080 pubkey.to_string(),
5081 Some(AccountSnapshot {
5082 lamports: 1_000_000,
5083 owner: owner.to_string(),
5084 executable: false,
5085 rent_epoch: 0,
5086 data: general_purpose::STANDARD.encode(&[1, 2, 3]),
5087 parsed_data: None,
5088 }),
5089 );
5090
5091 locker
5092 .load_snapshot(&snapshot, None, CommitmentConfig::confirmed())
5093 .await
5094 .unwrap();
5095
5096 let owned_accounts = locker
5098 .with_svm_reader(|svm| svm.get_account_owned_by(&owner))
5099 .unwrap();
5100 assert_eq!(owned_accounts.len(), 1);
5101 assert_eq!(owned_accounts[0].0, pubkey);
5102 }
5103
5104 #[tokio::test(flavor = "multi_thread")]
5105 async fn test_load_snapshot_mixed_valid_invalid() {
5106 use base64::{Engine, engine::general_purpose};
5107
5108 let (svm, _events_rx, _geyser_rx) = SurfnetSvm::default();
5109 let locker = SurfnetSvmLocker::new(svm);
5110
5111 let valid_pubkey = Pubkey::new_unique();
5112 let owner = Pubkey::new_unique();
5113
5114 let mut snapshot = BTreeMap::new();
5115
5116 snapshot.insert(
5118 valid_pubkey.to_string(),
5119 Some(AccountSnapshot {
5120 lamports: 1_000_000,
5121 owner: owner.to_string(),
5122 executable: false,
5123 rent_epoch: 0,
5124 data: general_purpose::STANDARD.encode(&[1, 2, 3]),
5125 parsed_data: None,
5126 }),
5127 );
5128
5129 snapshot.insert(
5131 "bad_pubkey".to_string(),
5132 Some(AccountSnapshot {
5133 lamports: 2_000_000,
5134 owner: owner.to_string(),
5135 executable: false,
5136 rent_epoch: 0,
5137 data: general_purpose::STANDARD.encode(&[4, 5, 6]),
5138 parsed_data: None,
5139 }),
5140 );
5141
5142 snapshot.insert(Pubkey::new_unique().to_string(), None);
5144
5145 let loaded = locker
5146 .load_snapshot(&snapshot, None, CommitmentConfig::confirmed())
5147 .await
5148 .unwrap();
5149 assert_eq!(loaded, 1);
5150
5151 assert!(
5153 locker
5154 .with_svm_reader(|svm| svm.get_account(&valid_pubkey))
5155 .unwrap()
5156 .is_some()
5157 );
5158 }
5159
5160 fn make_test_tx(sig: Signature, pubkey: &Pubkey) -> VersionedTransaction {
5162 use solana_system_interface::instruction as system_instruction;
5163 VersionedTransaction {
5164 signatures: vec![sig],
5165 message: VersionedMessage::Legacy(Message::new(
5166 &[system_instruction::transfer(pubkey, pubkey, 1)],
5167 Some(pubkey),
5168 )),
5169 }
5170 }
5171
5172 fn store_test_tx(svm: &mut SurfnetSvm, sig: Signature, pubkey: &Pubkey, slot: u64) {
5174 let tx = make_test_tx(sig, pubkey);
5175 svm.transactions
5176 .store(
5177 sig.to_string(),
5178 SurfnetTransactionStatus::processed(
5179 TransactionWithStatusMeta {
5180 slot,
5181 transaction: tx,
5182 meta: TransactionStatusMeta {
5183 status: Ok(()),
5184 fee: 5000,
5185 pre_balances: vec![0; 3],
5186 post_balances: vec![0; 3],
5187 inner_instructions: Some(vec![]),
5188 log_messages: Some(vec![]),
5189 pre_token_balances: Some(vec![]),
5190 post_token_balances: Some(vec![]),
5191 rewards: Some(vec![]),
5192 loaded_addresses: LoadedAddresses::default(),
5193 return_data: None,
5194 compute_units_consumed: Some(0),
5195 cost_units: None,
5196 },
5197 },
5198 HashSet::new(),
5199 ),
5200 )
5201 .unwrap();
5202 }
5203
5204 fn seed_signature_history(
5205 locker: &SurfnetSvmLocker,
5206 pubkey: &Pubkey,
5207 blocks: &[(u64, Vec<Signature>)],
5208 ) {
5209 locker.with_svm_writer(|svm| {
5210 for (slot, signatures) in blocks {
5211 for sig in signatures {
5212 store_test_tx(svm, *sig, pubkey, *slot);
5213 }
5214
5215 svm.blocks
5216 .store(
5217 *slot,
5218 BlockHeader {
5219 hash: String::new(),
5220 previous_blockhash: String::new(),
5221 parent_slot: 0,
5222 block_time: 0,
5223 block_height: 0,
5224 signatures: signatures.clone(),
5225 },
5226 )
5227 .unwrap();
5228 }
5229 });
5230 }
5231
5232 fn fetch_signature_strings(
5233 locker: &SurfnetSvmLocker,
5234 pubkey: &Pubkey,
5235 config: Option<&RpcSignaturesForAddressConfig>,
5236 ) -> Vec<String> {
5237 locker
5238 .get_signatures_for_address_local(pubkey, config)
5239 .inner
5240 .iter()
5241 .map(|s| s.signature.clone())
5242 .collect()
5243 }
5244
5245 #[tokio::test(flavor = "multi_thread")]
5246 async fn test_get_signatures_for_address_ordering_within_block() {
5247 let (svm, _events_rx, _geyser_rx) = SurfnetSvm::default();
5248 let locker = SurfnetSvmLocker::new(svm);
5249
5250 let pubkey = Pubkey::new_unique();
5251 let sig_a = Signature::new_unique();
5252 let sig_b = Signature::new_unique();
5253 let sig_c = Signature::new_unique();
5254 let slot = 5;
5255
5256 seed_signature_history(&locker, &pubkey, &[(slot, vec![sig_a, sig_b, sig_c])]);
5257 let sigs = fetch_signature_strings(&locker, &pubkey, None);
5258
5259 assert_eq!(sigs.len(), 3);
5261 assert_eq!(sigs[0], sig_c.to_string());
5262 assert_eq!(sigs[1], sig_b.to_string());
5263 assert_eq!(sigs[2], sig_a.to_string());
5264 }
5265
5266 #[tokio::test(flavor = "multi_thread")]
5267 async fn test_get_signatures_for_address_until_excludes_boundary_signature() {
5268 let (svm, _events_rx, _geyser_rx) = SurfnetSvm::default();
5269 let locker = SurfnetSvmLocker::new(svm);
5270
5271 let pubkey = Pubkey::new_unique();
5272 let sig_a = Signature::new_unique();
5273 let sig_b = Signature::new_unique();
5274 let sig_c = Signature::new_unique();
5275 let slot = 5;
5276
5277 seed_signature_history(&locker, &pubkey, &[(slot, vec![sig_a, sig_b, sig_c])]);
5278 let sigs = fetch_signature_strings(
5279 &locker,
5280 &pubkey,
5281 Some(&RpcSignaturesForAddressConfig {
5282 until: Some(sig_b.to_string()),
5283 ..RpcSignaturesForAddressConfig::default()
5284 }),
5285 );
5286
5287 assert_eq!(sigs, vec![sig_c.to_string()]);
5288 }
5289
5290 #[tokio::test(flavor = "multi_thread")]
5291 async fn test_get_signatures_for_address_before_excludes_boundary_signature() {
5292 let (svm, _events_rx, _geyser_rx) = SurfnetSvm::default();
5293 let locker = SurfnetSvmLocker::new(svm);
5294
5295 let pubkey = Pubkey::new_unique();
5296 let sig_a = Signature::new_unique();
5297 let sig_b = Signature::new_unique();
5298 let sig_c = Signature::new_unique();
5299 let slot = 5;
5300
5301 seed_signature_history(&locker, &pubkey, &[(slot, vec![sig_a, sig_b, sig_c])]);
5302 let sigs = fetch_signature_strings(
5303 &locker,
5304 &pubkey,
5305 Some(&RpcSignaturesForAddressConfig {
5306 before: Some(sig_b.to_string()),
5307 ..RpcSignaturesForAddressConfig::default()
5308 }),
5309 );
5310
5311 assert_eq!(sigs, vec![sig_a.to_string()]);
5312 }
5313
5314 #[tokio::test(flavor = "multi_thread")]
5315 async fn test_get_signatures_for_address_before_and_until_form_window() {
5316 let (svm, _events_rx, _geyser_rx) = SurfnetSvm::default();
5317 let locker = SurfnetSvmLocker::new(svm);
5318
5319 let pubkey = Pubkey::new_unique();
5320 let sig_a = Signature::new_unique();
5321 let sig_b = Signature::new_unique();
5322 let sig_c = Signature::new_unique();
5323 let sig_d = Signature::new_unique();
5324 let slot = 5;
5325
5326 seed_signature_history(
5327 &locker,
5328 &pubkey,
5329 &[(slot, vec![sig_a, sig_b, sig_c, sig_d])],
5330 );
5331 let sigs = fetch_signature_strings(
5332 &locker,
5333 &pubkey,
5334 Some(&RpcSignaturesForAddressConfig {
5335 before: Some(sig_d.to_string()),
5336 until: Some(sig_b.to_string()),
5337 ..RpcSignaturesForAddressConfig::default()
5338 }),
5339 );
5340
5341 assert_eq!(sigs, vec![sig_c.to_string()]);
5342 }
5343
5344 #[tokio::test(flavor = "multi_thread")]
5345 async fn test_get_signatures_for_address_before_missing_returns_empty() {
5346 let (svm, _events_rx, _geyser_rx) = SurfnetSvm::default();
5347 let locker = SurfnetSvmLocker::new(svm);
5348
5349 let pubkey = Pubkey::new_unique();
5350 let sig_a = Signature::new_unique();
5351 let sig_b = Signature::new_unique();
5352 let missing_sig = Signature::new_unique();
5353 let slot = 5;
5354
5355 seed_signature_history(&locker, &pubkey, &[(slot, vec![sig_a, sig_b])]);
5356 let sigs = fetch_signature_strings(
5357 &locker,
5358 &pubkey,
5359 Some(&RpcSignaturesForAddressConfig {
5360 before: Some(missing_sig.to_string()),
5361 ..RpcSignaturesForAddressConfig::default()
5362 }),
5363 );
5364
5365 assert!(sigs.is_empty());
5366 }
5367
5368 #[tokio::test(flavor = "multi_thread")]
5369 async fn test_get_signatures_for_address_until_missing_returns_all_results() {
5370 let (svm, _events_rx, _geyser_rx) = SurfnetSvm::default();
5371 let locker = SurfnetSvmLocker::new(svm);
5372
5373 let pubkey = Pubkey::new_unique();
5374 let sig_a = Signature::new_unique();
5375 let sig_b = Signature::new_unique();
5376 let missing_sig = Signature::new_unique();
5377 let slot = 5;
5378
5379 seed_signature_history(&locker, &pubkey, &[(slot, vec![sig_a, sig_b])]);
5380 let sigs = fetch_signature_strings(
5381 &locker,
5382 &pubkey,
5383 Some(&RpcSignaturesForAddressConfig {
5384 until: Some(missing_sig.to_string()),
5385 ..RpcSignaturesForAddressConfig::default()
5386 }),
5387 );
5388
5389 assert_eq!(sigs, vec![sig_b.to_string(), sig_a.to_string()]);
5390 }
5391
5392 #[tokio::test(flavor = "multi_thread")]
5393 async fn test_get_signatures_for_address_limit_applies_after_windowing() {
5394 let (svm, _events_rx, _geyser_rx) = SurfnetSvm::default();
5395 let locker = SurfnetSvmLocker::new(svm);
5396
5397 let pubkey = Pubkey::new_unique();
5398 let sig_a = Signature::new_unique();
5399 let sig_b = Signature::new_unique();
5400 let sig_c = Signature::new_unique();
5401 let sig_d = Signature::new_unique();
5402 let sig_e = Signature::new_unique();
5403 let slot = 5;
5404
5405 seed_signature_history(
5406 &locker,
5407 &pubkey,
5408 &[(slot, vec![sig_a, sig_b, sig_c, sig_d, sig_e])],
5409 );
5410 let sigs = fetch_signature_strings(
5411 &locker,
5412 &pubkey,
5413 Some(&RpcSignaturesForAddressConfig {
5414 before: Some(sig_e.to_string()),
5415 until: Some(sig_a.to_string()),
5416 limit: Some(2),
5417 ..RpcSignaturesForAddressConfig::default()
5418 }),
5419 );
5420
5421 assert_eq!(sigs, vec![sig_d.to_string(), sig_c.to_string()]);
5422 }
5423
5424 #[tokio::test(flavor = "multi_thread")]
5425 async fn test_get_signatures_for_address_until_excludes_boundary_across_slots() {
5426 let (svm, _events_rx, _geyser_rx) = SurfnetSvm::default();
5427 let locker = SurfnetSvmLocker::new(svm);
5428
5429 let pubkey = Pubkey::new_unique();
5430 let sig_s5 = Signature::new_unique();
5431 let sig_s10_a = Signature::new_unique();
5432 let sig_s10_b = Signature::new_unique();
5433 let sig_s15 = Signature::new_unique();
5434
5435 seed_signature_history(
5436 &locker,
5437 &pubkey,
5438 &[
5439 (5, vec![sig_s5]),
5440 (10, vec![sig_s10_a, sig_s10_b]),
5441 (15, vec![sig_s15]),
5442 ],
5443 );
5444 let sigs = fetch_signature_strings(
5445 &locker,
5446 &pubkey,
5447 Some(&RpcSignaturesForAddressConfig {
5448 until: Some(sig_s10_b.to_string()),
5449 ..RpcSignaturesForAddressConfig::default()
5450 }),
5451 );
5452
5453 assert_eq!(sigs, vec![sig_s15.to_string()]);
5454 }
5455
5456 #[tokio::test(flavor = "multi_thread")]
5457 async fn test_get_signatures_for_address_ordering_across_slots() {
5458 let (svm, _events_rx, _geyser_rx) = SurfnetSvm::default();
5459 let locker = SurfnetSvmLocker::new(svm);
5460
5461 let pubkey = Pubkey::new_unique();
5462 let sig_s5_a = Signature::new_unique();
5463 let sig_s5_b = Signature::new_unique();
5464 let sig_s10_a = Signature::new_unique();
5465 let sig_s10_b = Signature::new_unique();
5466
5467 seed_signature_history(
5468 &locker,
5469 &pubkey,
5470 &[
5471 (5, vec![sig_s5_a, sig_s5_b]),
5472 (10, vec![sig_s10_a, sig_s10_b]),
5473 ],
5474 );
5475 let sigs = fetch_signature_strings(&locker, &pubkey, None);
5476
5477 assert_eq!(sigs.len(), 4);
5480 assert_eq!(sigs[0], sig_s10_b.to_string());
5481 assert_eq!(sigs[1], sig_s10_a.to_string());
5482 assert_eq!(sigs[2], sig_s5_b.to_string());
5483 assert_eq!(sigs[3], sig_s5_a.to_string());
5484 }
5485
5486 #[tokio::test(flavor = "multi_thread")]
5487 async fn test_get_signatures_for_address_ordering_missing_block_header() {
5488 let (svm, _events_rx, _geyser_rx) = SurfnetSvm::default();
5489 let locker = SurfnetSvmLocker::new(svm);
5490
5491 let pubkey = Pubkey::new_unique();
5492 let sig_a = Signature::new_unique();
5493 let sig_b = Signature::new_unique();
5494 let slot = 5;
5495
5496 locker.with_svm_writer(|svm| {
5497 store_test_tx(svm, sig_a, &pubkey, slot);
5498 store_test_tx(svm, sig_b, &pubkey, slot);
5499 });
5501
5502 let result = locker.get_signatures_for_address_local(&pubkey, None);
5503
5504 assert_eq!(result.inner.len(), 2);
5506
5507 let sigs: HashSet<String> = result.inner.iter().map(|s| s.signature.clone()).collect();
5509 assert!(sigs.contains(&sig_a.to_string()));
5510 assert!(sigs.contains(&sig_b.to_string()));
5511 }
5512
5513 #[test]
5514 fn remote_config_passes_through_when_no_boundary_is_local() {
5515 let sig_before = Signature::new_unique().to_string();
5516 let sig_until = Signature::new_unique().to_string();
5517 let config = RpcSignaturesForAddressConfig {
5518 before: Some(sig_before.clone()),
5519 until: Some(sig_until.clone()),
5520 limit: Some(42),
5521 commitment: None,
5522 min_context_slot: Some(7),
5523 };
5524
5525 let translated = signatures_for_address_remote_config(Some(&config), false, false, 42)
5526 .expect("remote call should not be skipped");
5527
5528 assert_eq!(translated.before.as_deref(), Some(sig_before.as_str()));
5529 assert_eq!(translated.until.as_deref(), Some(sig_until.as_str()));
5530 assert_eq!(translated.limit, Some(42));
5531 assert_eq!(translated.min_context_slot, Some(7));
5532 }
5533
5534 #[test]
5535 fn remote_config_drops_local_before_boundary() {
5536 let sig_before = Signature::new_unique().to_string();
5537 let sig_until = Signature::new_unique().to_string();
5538 let config = RpcSignaturesForAddressConfig {
5539 before: Some(sig_before.clone()),
5540 until: Some(sig_until.clone()),
5541 limit: Some(100),
5542 commitment: None,
5543 min_context_slot: None,
5544 };
5545
5546 let translated = signatures_for_address_remote_config(Some(&config), true, false, 100)
5547 .expect("remote call should not be skipped");
5548
5549 assert!(
5550 translated.before.is_none(),
5551 "local `before` must not leak to the remote"
5552 );
5553 assert_eq!(translated.until.as_deref(), Some(sig_until.as_str()));
5554 assert_eq!(translated.limit, Some(100));
5555 }
5556
5557 #[test]
5558 fn remote_config_skipped_when_until_is_local() {
5559 let config = RpcSignaturesForAddressConfig {
5560 before: None,
5561 until: Some(Signature::new_unique().to_string()),
5562 limit: Some(100),
5563 commitment: None,
5564 min_context_slot: None,
5565 };
5566
5567 assert!(
5568 signatures_for_address_remote_config(Some(&config), false, true, 100).is_none(),
5569 "a local `until` boundary excludes the entire remote stream — the call must be skipped"
5570 );
5571 assert!(signatures_for_address_remote_config(Some(&config), true, true, 100).is_none());
5573 }
5574
5575 #[test]
5576 fn remote_config_pins_remote_limit_to_remaining_slots() {
5577 let config = RpcSignaturesForAddressConfig {
5580 before: None,
5581 until: None,
5582 limit: Some(100),
5583 commitment: None,
5584 min_context_slot: None,
5585 };
5586
5587 let translated = signatures_for_address_remote_config(Some(&config), false, false, 70)
5588 .expect("remote call should not be skipped");
5589 assert_eq!(translated.limit, Some(70));
5590 }
5591
5592 #[test]
5593 fn remote_config_handles_missing_caller_config() {
5594 let translated = signatures_for_address_remote_config(None, false, false, 250)
5595 .expect("remote call should not be skipped");
5596 assert_eq!(
5597 translated.limit,
5598 Some(250),
5599 "even without a caller config, the remote must respect the remaining slot budget"
5600 );
5601 assert!(translated.before.is_none());
5602 assert!(translated.until.is_none());
5603 }
5604
5605 #[tokio::test(flavor = "multi_thread")]
5606 async fn initializes_epoch_schedule_without_warmup_when_offline() {
5607 let (surfnet_svm, _simnet_events_rx, _geyser_events_rx) = SurfnetSvm::default();
5608 let svm_locker = SurfnetSvmLocker::new(surfnet_svm);
5609
5610 svm_locker
5611 .initialize(&None)
5612 .await
5613 .expect("initialize should succeed");
5614
5615 let epoch_schedule =
5616 svm_locker.with_svm_reader(|svm_reader| svm_reader.inner.get_sysvar::<EpochSchedule>());
5617
5618 assert!(
5619 !epoch_schedule.warmup,
5620 "offline initialization should disable warmup to match mainnet"
5621 );
5622 assert_eq!(
5623 epoch_schedule.get_first_slot_in_epoch(886),
5624 886_u64 * 432_000,
5625 "first slot should align with mainnet epoch boundaries when warmup is disabled"
5626 );
5627 }
5628}