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