1use std::sync::Arc;
2
3use crossbeam_channel::{Receiver, Sender};
4use jsonrpc_core::futures::future::join_all;
5use litesvm::types::{FailedTransactionMetadata, SimulatedTransactionInfo, TransactionResult};
6use solana_account::Account;
7use solana_account_decoder::parse_bpf_loader::{
8 parse_bpf_upgradeable_loader, BpfUpgradeableLoaderAccountType, UiProgram,
9};
10use solana_address_lookup_table_interface::state::AddressLookupTable;
11use solana_client::rpc_response::RpcKeyedAccount;
12use solana_clock::Slot;
13use solana_commitment_config::CommitmentConfig;
14use solana_epoch_info::EpochInfo;
15use solana_hash::Hash;
16use solana_message::{
17 v0::{LoadedAddresses, MessageAddressTableLookup},
18 VersionedMessage,
19};
20use solana_pubkey::Pubkey;
21use solana_sdk::{
22 bpf_loader_upgradeable::{get_program_data_address, UpgradeableLoaderState},
23 transaction::VersionedTransaction,
24};
25use solana_signature::Signature;
26use solana_transaction_error::TransactionError;
27use solana_transaction_status::{EncodedConfirmedTransactionWithStatusMeta, UiTransactionEncoding};
28use surfpool_types::{
29 ComputeUnitsEstimationResult, ProfileResult, SimnetEvent, TransactionConfirmationStatus,
30 TransactionStatusEvent,
31};
32use tokio::sync::RwLock;
33
34use super::{
35 remote::SurfnetRemoteClient, AccountFactory, GetAccountResult, GetTransactionResult,
36 GeyserEvent, SignatureSubscriptionType, SurfnetSvm,
37};
38use crate::{
39 error::{SurfpoolError, SurfpoolResult},
40 rpc::utils::convert_transaction_metadata_from_canonical,
41};
42
43pub struct SvmAccessContext<T> {
44 pub slot: Slot,
45 pub latest_epoch_info: EpochInfo,
46 pub latest_blockhash: Hash,
47 pub inner: T,
48}
49
50impl<T> SvmAccessContext<T> {
51 pub fn new(slot: Slot, latest_epoch_info: EpochInfo, latest_blockhash: Hash, inner: T) -> Self {
52 Self {
53 slot,
54 latest_blockhash,
55 latest_epoch_info,
56 inner,
57 }
58 }
59
60 pub fn inner(&self) -> &T {
61 &self.inner
62 }
63
64 pub fn with_new_value<N>(&self, inner: N) -> SvmAccessContext<N> {
65 SvmAccessContext {
66 slot: self.slot,
67 latest_blockhash: self.latest_blockhash,
68 latest_epoch_info: self.latest_epoch_info.clone(),
69 inner,
70 }
71 }
72}
73
74pub type SurfpoolContextualizedResult<T> = SurfpoolResult<SvmAccessContext<T>>;
75
76pub struct SurfnetSvmLocker(pub Arc<RwLock<SurfnetSvm>>);
77
78impl Clone for SurfnetSvmLocker {
79 fn clone(&self) -> Self {
80 Self(self.0.clone())
81 }
82}
83
84impl SurfnetSvmLocker {
86 pub fn with_svm_reader<T, F>(&self, reader: F) -> T
92 where
93 F: Fn(&SurfnetSvm) -> T + Send + Sync,
94 T: Send + 'static,
95 {
96 let read_lock = self.0.clone();
97 tokio::task::block_in_place(move || {
98 let read_guard = read_lock.blocking_read();
99 reader(&read_guard)
100 })
101 }
102
103 fn with_contextualized_svm_reader<T, F>(&self, reader: F) -> SvmAccessContext<T>
106 where
107 F: Fn(&SurfnetSvm) -> T + Send + Sync,
108 T: Send + 'static,
109 {
110 let read_lock = self.0.clone();
111 tokio::task::block_in_place(move || {
112 let read_guard = read_lock.blocking_read();
113 let res = reader(&read_guard);
114
115 SvmAccessContext::new(
116 read_guard.get_latest_absolute_slot(),
117 read_guard.latest_epoch_info(),
118 read_guard.latest_blockhash(),
119 res,
120 )
121 })
122 }
123
124 pub fn with_svm_writer<T, F>(&self, writer: F) -> T
130 where
131 F: Fn(&mut SurfnetSvm) -> T + Send + Sync,
132 T: Send + 'static,
133 {
134 let write_lock = self.0.clone();
135 tokio::task::block_in_place(move || {
136 let mut write_guard = write_lock.blocking_write();
137 writer(&mut write_guard)
138 })
139 }
140}
141
142impl SurfnetSvmLocker {
144 pub fn new(svm: SurfnetSvm) -> Self {
146 Self(Arc::new(RwLock::new(svm)))
147 }
148
149 pub async fn initialize(
152 &self,
153 remote_ctx: &Option<SurfnetRemoteClient>,
154 ) -> SurfpoolResult<EpochInfo> {
155 let epoch_info = if let Some(remote_client) = remote_ctx {
156 remote_client.get_epoch_info().await?
157 } else {
158 EpochInfo {
159 epoch: 0,
160 slot_index: 0,
161 slots_in_epoch: 0,
162 absolute_slot: 0,
163 block_height: 0,
164 transaction_count: None,
165 }
166 };
167
168 self.with_svm_writer(|svm_writer| {
169 svm_writer.initialize(epoch_info.clone(), remote_ctx);
170 });
171 Ok(epoch_info)
172 }
173}
174
175impl SurfnetSvmLocker {
177 pub fn get_account_local(&self, pubkey: &Pubkey) -> SvmAccessContext<GetAccountResult> {
179 self.with_contextualized_svm_reader(|svm_reader| {
180 match svm_reader.inner.get_account(pubkey) {
181 Some(account) => GetAccountResult::FoundAccount(
182 *pubkey, account,
183 false,
185 ),
186 None => GetAccountResult::None(*pubkey),
187 }
188 })
189 }
190
191 pub async fn get_account_local_then_remote(
193 &self,
194 client: &SurfnetRemoteClient,
195 pubkey: &Pubkey,
196 commitment_config: CommitmentConfig,
197 ) -> SurfpoolContextualizedResult<GetAccountResult> {
198 let result = self.get_account_local(pubkey);
199
200 if result.inner.is_none() {
201 let remote_account = client.get_account(pubkey, commitment_config).await?;
202 Ok(result.with_new_value(remote_account))
203 } else {
204 Ok(result)
205 }
206 }
207
208 pub async fn get_account(
210 &self,
211 remote_ctx: &Option<(SurfnetRemoteClient, CommitmentConfig)>,
212 pubkey: &Pubkey,
213 factory: Option<AccountFactory>,
214 ) -> SurfpoolContextualizedResult<GetAccountResult> {
215 let result = if let Some((remote_client, commitment_config)) = remote_ctx {
216 self.get_account_local_then_remote(remote_client, pubkey, *commitment_config)
217 .await?
218 } else {
219 self.get_account_local(pubkey)
220 };
221
222 match (&result.inner, factory) {
223 (&GetAccountResult::None(_), Some(factory)) => {
224 let default = factory(self.clone());
225 Ok(result.with_new_value(default))
226 }
227 _ => Ok(result),
228 }
229 }
230
231 pub fn get_multiple_accounts_local(
233 &self,
234 pubkeys: &[Pubkey],
235 ) -> SvmAccessContext<Vec<GetAccountResult>> {
236 self.with_contextualized_svm_reader(|svm_reader| {
237 let mut accounts = vec![];
238
239 for pubkey in pubkeys.iter() {
240 let res = match svm_reader.inner.get_account(pubkey) {
241 Some(account) => GetAccountResult::FoundAccount(
242 *pubkey, account,
243 false,
245 ),
246 None => GetAccountResult::None(*pubkey),
247 };
248 accounts.push(res);
249 }
250 accounts
251 })
252 }
253
254 pub async fn get_multiple_accounts_local_then_remote(
256 &self,
257 client: &SurfnetRemoteClient,
258 pubkeys: &[Pubkey],
259 commitment_config: CommitmentConfig,
260 ) -> SurfpoolContextualizedResult<Vec<GetAccountResult>> {
261 let results = self.get_multiple_accounts_local(pubkeys);
262
263 let mut missing_accounts = vec![];
264 for result in &results.inner {
265 if let GetAccountResult::None(pubkey) = result {
266 missing_accounts.push(*pubkey)
267 }
268 }
269
270 if missing_accounts.is_empty() {
271 return Ok(results);
272 }
273
274 let mut remote_results = client
275 .get_multiple_accounts(&missing_accounts, commitment_config)
276 .await?;
277 let mut combined_results = results.inner.clone();
278 combined_results.append(&mut remote_results);
279
280 Ok(results.with_new_value(combined_results))
281 }
282
283 pub async fn get_multiple_accounts(
285 &self,
286 remote_ctx: &Option<(SurfnetRemoteClient, CommitmentConfig)>,
287 pubkeys: &[Pubkey],
288 factory: Option<AccountFactory>,
289 ) -> SurfpoolContextualizedResult<Vec<GetAccountResult>> {
290 let results = if let Some((remote_client, commitment_config)) = remote_ctx {
291 self.get_multiple_accounts_local_then_remote(remote_client, pubkeys, *commitment_config)
292 .await?
293 } else {
294 self.get_multiple_accounts_local(pubkeys)
295 };
296
297 let mut combined = Vec::with_capacity(results.inner.len());
298 for result in results.inner.clone() {
299 match (&result, &factory) {
300 (&GetAccountResult::None(_), Some(factory)) => {
301 let default = factory(self.clone());
302 combined.push(default);
303 }
304 _ => combined.push(result),
305 }
306 }
307 Ok(results.with_new_value(combined))
308 }
309}
310
311impl SurfnetSvmLocker {
313 pub async fn get_transaction(
315 &self,
316 remote_ctx: &Option<(SurfnetRemoteClient, Option<UiTransactionEncoding>)>,
317 signature: &Signature,
318 ) -> SvmAccessContext<GetTransactionResult> {
319 if let Some((remote_client, encoding)) = remote_ctx {
320 self.get_transaction_local_then_remote(remote_client, signature, *encoding)
321 .await
322 } else {
323 self.get_transaction_local(signature)
324 }
325 }
326
327 pub fn get_transaction_local(
329 &self,
330 signature: &Signature,
331 ) -> SvmAccessContext<GetTransactionResult> {
332 self.with_contextualized_svm_reader(|svm_reader| {
333 let latest_absolute_slot = svm_reader.get_latest_absolute_slot();
334
335 match svm_reader.transactions.get(signature).map(|entry| {
336 Into::<EncodedConfirmedTransactionWithStatusMeta>::into(
337 entry.expect_processed().clone(),
338 )
339 }) {
340 Some(tx) => {
341 GetTransactionResult::found_transaction(*signature, tx, latest_absolute_slot)
342 }
343 None => GetTransactionResult::None(*signature),
344 }
345 })
346 }
347
348 pub async fn get_transaction_local_then_remote(
350 &self,
351 client: &SurfnetRemoteClient,
352 signature: &Signature,
353 encoding: Option<UiTransactionEncoding>,
354 ) -> SvmAccessContext<GetTransactionResult> {
355 let local_result = self.get_transaction_local(signature);
356 if local_result.inner.is_none() {
357 let remote_result = client
358 .get_transaction(*signature, encoding, local_result.slot)
359 .await;
360 local_result.with_new_value(remote_result)
361 } else {
362 local_result
363 }
364 }
365}
366
367impl SurfnetSvmLocker {
369 pub fn simulate_transaction(
371 &self,
372 transaction: VersionedTransaction,
373 ) -> Result<SimulatedTransactionInfo, FailedTransactionMetadata> {
374 self.with_svm_reader(|svm_reader| svm_reader.simulate_transaction(transaction.clone()))
375 }
376
377 pub async fn process_transaction(
379 &self,
380 remote_ctx: &Option<(SurfnetRemoteClient, CommitmentConfig)>,
381 transaction: VersionedTransaction,
382 status_tx: Sender<TransactionStatusEvent>,
383 skip_preflight: bool,
384 ) -> SurfpoolContextualizedResult<()> {
385 let (latest_absolute_slot, latest_epoch_info, latest_blockhash) =
386 self.with_svm_writer(|svm_writer| {
387 let latest_absolute_slot = svm_writer.get_latest_absolute_slot();
388 svm_writer.notify_signature_subscribers(
389 SignatureSubscriptionType::received(),
390 &transaction.signatures[0],
391 latest_absolute_slot,
392 None,
393 );
394 (
395 latest_absolute_slot,
396 svm_writer.latest_epoch_info(),
397 svm_writer.latest_blockhash(),
398 )
399 });
400
401 {
403 if transaction
404 .verify_with_results()
405 .iter()
406 .any(|valid| !*valid)
407 {
408 return Ok(self.with_contextualized_svm_reader(|svm_reader| {
409 svm_reader
410 .notify_invalid_transaction(transaction.signatures[0], status_tx.clone());
411 }));
412 }
413 }
414
415 let signature = transaction.signatures[0];
416
417 let accounts = self
420 .get_pubkeys_from_message(remote_ctx, &transaction.message)
421 .await?;
422
423 let account_updates = self
424 .get_multiple_accounts(remote_ctx, &accounts, None)
425 .await?
426 .inner;
427
428 self.with_svm_writer(|svm_writer| {
429 for update in &account_updates {
430 svm_writer.write_account_update(update.clone());
431 }
432 if !skip_preflight {
434 match svm_writer.simulate_transaction(transaction.clone()) {
435 Ok(_) => {}
436 Err(res) => {
437 let _ = svm_writer
438 .simnet_events_tx
439 .try_send(SimnetEvent::error(format!(
440 "Transaction simulation failed: {}",
441 res.err
442 )));
443 let meta = convert_transaction_metadata_from_canonical(&res.meta);
444 let _ = status_tx.try_send(TransactionStatusEvent::SimulationFailure((
445 res.err.clone(),
446 meta,
447 )));
448 svm_writer.notify_signature_subscribers(
449 SignatureSubscriptionType::processed(),
450 &signature,
451 latest_absolute_slot,
452 Some(res.err),
453 );
454 return;
455 }
456 }
457 }
458 let err = match svm_writer
460 .send_transaction(transaction.clone(), false )
461 {
462 Ok(res) => {
463 let transaction_meta = convert_transaction_metadata_from_canonical(&res);
464 let _ = svm_writer
465 .geyser_events_tx
466 .send(GeyserEvent::NewTransaction(
467 transaction.clone(),
468 transaction_meta.clone(),
469 latest_absolute_slot,
470 ));
471 let _ = status_tx.try_send(TransactionStatusEvent::Success(
472 TransactionConfirmationStatus::Processed,
473 ));
474 svm_writer
475 .transactions_queued_for_confirmation
476 .push_back((transaction.clone(), status_tx.clone()));
477 None
478 }
479 Err(res) => {
480 let transaction_meta = convert_transaction_metadata_from_canonical(&res.meta);
481 let _ = svm_writer
482 .simnet_events_tx
483 .try_send(SimnetEvent::error(format!(
484 "Transaction execution failed: {}",
485 res.err
486 )));
487 let _ = status_tx.try_send(TransactionStatusEvent::ExecutionFailure((
488 res.err.clone(),
489 transaction_meta,
490 )));
491 Some(res.err)
492 }
493 };
494
495 svm_writer.notify_signature_subscribers(
496 SignatureSubscriptionType::processed(),
497 &signature,
498 latest_absolute_slot,
499 err,
500 );
501 });
502
503 Ok(SvmAccessContext::new(
504 latest_absolute_slot,
505 latest_epoch_info,
506 latest_blockhash,
507 (),
508 ))
509 }
510}
511
512impl SurfnetSvmLocker {
514 pub fn write_account_update(&self, account_update: GetAccountResult) {
516 if !account_update.requires_update() {
517 return;
518 }
519
520 self.with_svm_writer(move |svm_writer| {
521 svm_writer.write_account_update(account_update.clone())
522 })
523 }
524
525 pub fn write_multiple_account_updates(&self, account_updates: &[GetAccountResult]) {
527 if account_updates
528 .iter()
529 .all(|update| !update.requires_update())
530 {
531 return;
532 }
533
534 self.with_svm_writer(move |svm_writer| {
535 for update in account_updates {
536 svm_writer.write_account_update(update.clone());
537 }
538 });
539 }
540}
541
542impl SurfnetSvmLocker {
544 pub async fn get_all_token_accounts(
546 &self,
547 remote_ctx: &Option<SurfnetRemoteClient>,
548 owner: Pubkey,
549 token_program: Pubkey,
550 ) -> SurfpoolContextualizedResult<(Vec<RpcKeyedAccount>, Vec<Pubkey>)> {
551 let keyed_accounts = if let Some(remote_client) = remote_ctx {
552 remote_client
553 .get_token_accounts_by_owner(owner, token_program)
554 .await?
555 } else {
556 vec![]
557 };
558
559 let token_account_pubkeys = keyed_accounts
560 .iter()
561 .map(|a| Pubkey::from_str_const(&a.pubkey))
562 .collect::<Vec<_>>();
563
564 let local_accounts = self.get_multiple_accounts_local(&token_account_pubkeys);
566
567 let missing_pubkeys = local_accounts
568 .inner
569 .iter()
570 .filter_map(|some_account_result| match &some_account_result {
571 GetAccountResult::None(pubkey) => Some(*pubkey),
572 _ => None,
573 })
574 .collect::<Vec<_>>();
575
576 Ok(local_accounts.with_new_value((keyed_accounts, missing_pubkeys)))
579 }
580}
581
582impl SurfnetSvmLocker {
584 pub async fn get_pubkeys_from_message(
586 &self,
587 remote_ctx: &Option<(SurfnetRemoteClient, CommitmentConfig)>,
588 message: &VersionedMessage,
589 ) -> SurfpoolResult<Vec<Pubkey>> {
590 match message {
591 VersionedMessage::Legacy(message) => Ok(message.account_keys.clone()),
592 VersionedMessage::V0(message) => {
593 let alts = message.address_table_lookups.clone();
594 let mut acc_keys = message.account_keys.clone();
595 let mut alt_pubkeys = alts.iter().map(|msg| msg.account_key).collect::<Vec<_>>();
596
597 let mut table_entries = join_all(alts.iter().map(|msg| async {
598 let loaded_addresses = self
599 .get_lookup_table_addresses(remote_ctx, msg)
600 .await?
601 .inner;
602 let mut combined = loaded_addresses.writable;
603 combined.extend(loaded_addresses.readonly);
604 Ok::<_, SurfpoolError>(combined)
605 }))
606 .await
607 .into_iter()
608 .collect::<Result<Vec<Vec<Pubkey>>, SurfpoolError>>()?
609 .into_iter()
610 .flatten()
611 .collect();
612
613 acc_keys.append(&mut alt_pubkeys);
614 acc_keys.append(&mut table_entries);
615 Ok(acc_keys)
616 }
617 }
618 }
619
620 pub async fn get_lookup_table_addresses(
622 &self,
623 remote_ctx: &Option<(SurfnetRemoteClient, CommitmentConfig)>,
624 address_table_lookup: &MessageAddressTableLookup,
625 ) -> SurfpoolContextualizedResult<LoadedAddresses> {
626 let result = self
627 .get_account(remote_ctx, &address_table_lookup.account_key, None)
628 .await?;
629 let table_account = result.inner.clone().map_account()?;
630
631 if table_account.owner == solana_sdk_ids::address_lookup_table::id() {
632 let SvmAccessContext {
633 slot: current_slot,
634 inner: slot_hashes,
635 ..
636 } = self.with_contextualized_svm_reader(|svm_reader| {
637 svm_reader
638 .inner
639 .get_sysvar::<solana_sdk::sysvar::slot_hashes::SlotHashes>()
640 });
641
642 let data = &table_account.data.clone();
644 let lookup_table = AddressLookupTable::deserialize(data).map_err(|_ix_err| {
645 SurfpoolError::invalid_account_data(
646 address_table_lookup.account_key,
647 table_account.data,
648 Some("Attempted to lookup addresses from an invalid account"),
649 )
650 })?;
651
652 let loaded_addresses = LoadedAddresses {
653 writable: lookup_table
654 .lookup(
655 current_slot,
656 &address_table_lookup.writable_indexes,
657 &slot_hashes,
658 )
659 .map_err(|_ix_err| {
660 SurfpoolError::invalid_lookup_index(address_table_lookup.account_key)
661 })?,
662 readonly: lookup_table
663 .lookup(
664 current_slot,
665 &address_table_lookup.readonly_indexes,
666 &slot_hashes,
667 )
668 .map_err(|_ix_err| {
669 SurfpoolError::invalid_lookup_index(address_table_lookup.account_key)
670 })?,
671 };
672 Ok(result.with_new_value(loaded_addresses))
673 } else {
674 Err(SurfpoolError::invalid_account_owner(
675 table_account.owner,
676 Some("Attempted to lookup addresses from an account owned by the wrong program"),
677 ))
678 }
679 }
680}
681
682impl SurfnetSvmLocker {
684 pub fn estimate_compute_units(
686 &self,
687 transaction: &VersionedTransaction,
688 ) -> SvmAccessContext<ComputeUnitsEstimationResult> {
689 self.with_contextualized_svm_reader(|svm_reader| {
690 svm_reader.estimate_compute_units(transaction)
691 })
692 }
693
694 pub fn write_profiling_results(&self, tag: String, profile_result: ProfileResult) {
696 self.with_svm_writer(|svm_writer| {
697 svm_writer
698 .tagged_profiling_results
699 .entry(tag.clone())
700 .or_default()
701 .push(profile_result.clone());
702 let _ = svm_writer
703 .simnet_events_tx
704 .try_send(SimnetEvent::tagged_profile(
705 profile_result.clone(),
706 tag.clone(),
707 ));
708 });
709 }
710}
711
712impl SurfnetSvmLocker {
714 pub async fn clone_program_account(
716 &self,
717 remote_ctx: &Option<(SurfnetRemoteClient, CommitmentConfig)>,
718 source_program_id: &Pubkey,
719 destination_program_id: &Pubkey,
720 ) -> SurfpoolContextualizedResult<()> {
721 let expected_source_program_data_address = get_program_data_address(source_program_id);
722
723 let result = self
724 .get_multiple_accounts(
725 remote_ctx,
726 &[*source_program_id, expected_source_program_data_address],
727 None,
728 )
729 .await?;
730
731 let mut accounts = result
732 .inner
733 .clone()
734 .into_iter()
735 .map(|a| a.map_account())
736 .collect::<SurfpoolResult<Vec<Account>>>()?;
737
738 let source_program_data_account = accounts.remove(1);
739 let source_program_account = accounts.remove(0);
740
741 let BpfUpgradeableLoaderAccountType::Program(UiProgram {
742 program_data: source_program_data_address,
743 }) = parse_bpf_upgradeable_loader(&source_program_account.data).map_err(|e| {
744 SurfpoolError::invalid_program_account(source_program_id, e.to_string())
745 })?
746 else {
747 return Err(SurfpoolError::expected_program_account(source_program_id));
748 };
749
750 if source_program_data_address.ne(&expected_source_program_data_address.to_string()) {
751 return Err(SurfpoolError::invalid_program_account(
752 source_program_id,
753 format!(
754 "Program data address mismatch: expected {}, found {}",
755 expected_source_program_data_address, source_program_data_address
756 ),
757 ));
758 }
759
760 let destination_program_data_address = get_program_data_address(destination_program_id);
761
762 let mut new_program_account = source_program_account;
765 new_program_account.data = bincode::serialize(&UpgradeableLoaderState::Program {
766 programdata_address: destination_program_data_address,
767 })
768 .map_err(|e| SurfpoolError::internal(format!("Failed to serialize program data: {}", e)))?;
769
770 self.with_svm_writer(|svm_writer| {
771 svm_writer.set_account(
772 &destination_program_data_address,
773 source_program_data_account.clone(),
774 )?;
775
776 svm_writer.set_account(destination_program_id, new_program_account.clone())?;
777 Ok::<(), SurfpoolError>(())
778 })?;
779
780 Ok(result.with_new_value(()))
781 }
782}
783
784impl SurfnetSvmLocker {
786 pub fn simnet_events_tx(&self) -> Sender<SimnetEvent> {
788 self.with_svm_reader(|svm_reader| svm_reader.simnet_events_tx.clone())
789 }
790
791 pub fn get_epoch_info(&self) -> EpochInfo {
793 self.with_svm_reader(|svm_reader| svm_reader.latest_epoch_info.clone())
794 }
795
796 pub fn get_latest_absolute_slot(&self) -> Slot {
798 self.with_svm_reader(|svm_reader| svm_reader.get_latest_absolute_slot())
799 }
800
801 pub fn airdrop(&self, pubkey: &Pubkey, lamports: u64) -> TransactionResult {
803 self.with_svm_writer(|svm_writer| svm_writer.airdrop(pubkey, lamports))
804 }
805
806 pub fn airdrop_pubkeys(&self, lamports: u64, addresses: &[Pubkey]) {
808 self.with_svm_writer(|svm_writer| svm_writer.airdrop_pubkeys(lamports, addresses))
809 }
810
811 pub fn confirm_current_block(&self) -> SurfpoolResult<()> {
813 self.with_svm_writer(|svm_writer| svm_writer.confirm_current_block())
814 }
815
816 pub fn subscribe_for_signature_updates(
818 &self,
819 signature: &Signature,
820 subscription_type: SignatureSubscriptionType,
821 ) -> Receiver<(Slot, Option<TransactionError>)> {
822 self.with_svm_writer(|svm_writer| {
823 svm_writer.subscribe_for_signature_updates(signature, subscription_type.clone())
824 })
825 }
826}