1#![cfg_attr(
2 not(feature = "agave-unstable-api"),
3 deprecated(
4 since = "3.1.0",
5 note = "This crate has been marked for formal inclusion in the Agave Unstable API. From \
6 v4.0.0 onward, the `agave-unstable-api` crate feature must be specified to \
7 acknowledge use of an interface that may break without warning."
8 )
9)]
10pub use {
18 crate::error::BanksClientError,
19 solana_banks_interface::{BanksClient as TarpcClient, TransactionStatus},
20};
21use {
22 borsh::BorshDeserialize,
23 futures::future::join_all,
24 solana_account::{from_account, Account},
25 solana_banks_interface::{
26 BanksRequest, BanksResponse, BanksTransactionResultWithMetadata,
27 BanksTransactionResultWithSimulation,
28 },
29 solana_clock::Slot,
30 solana_commitment_config::CommitmentLevel,
31 solana_hash::Hash,
32 solana_message::Message,
33 solana_program_pack::Pack,
34 solana_pubkey::Pubkey,
35 solana_rent::Rent,
36 solana_signature::Signature,
37 solana_sysvar::SysvarSerialize,
38 solana_transaction::versioned::VersionedTransaction,
39 tarpc::{
40 client::{self, NewClient, RequestDispatch},
41 context::{self, Context},
42 serde_transport::tcp,
43 ClientMessage, Response, Transport,
44 },
45 tokio::net::ToSocketAddrs,
46 tokio_serde::formats::Bincode,
47};
48
49mod error;
50
51mod transaction {
52 pub use solana_transaction_error::TransactionResult as Result;
53}
54
55pub trait BanksClientExt {}
57
58#[derive(Clone)]
59pub struct BanksClient {
60 inner: TarpcClient,
61}
62
63impl BanksClient {
64 #[allow(clippy::new_ret_no_self)]
65 pub fn new<C>(
66 config: client::Config,
67 transport: C,
68 ) -> NewClient<TarpcClient, RequestDispatch<BanksRequest, BanksResponse, C>>
69 where
70 C: Transport<ClientMessage<BanksRequest>, Response<BanksResponse>>,
71 {
72 TarpcClient::new(config, transport)
73 }
74
75 pub async fn send_transaction_with_context(
76 &self,
77 ctx: Context,
78 transaction: impl Into<VersionedTransaction>,
79 ) -> Result<(), BanksClientError> {
80 self.inner
81 .send_transaction_with_context(ctx, transaction.into())
82 .await
83 .map_err(Into::into)
84 }
85
86 pub async fn get_transaction_status_with_context(
87 &self,
88 ctx: Context,
89 signature: Signature,
90 ) -> Result<Option<TransactionStatus>, BanksClientError> {
91 self.inner
92 .get_transaction_status_with_context(ctx, signature)
93 .await
94 .map_err(Into::into)
95 }
96
97 pub async fn get_slot_with_context(
98 &self,
99 ctx: Context,
100 commitment: CommitmentLevel,
101 ) -> Result<Slot, BanksClientError> {
102 self.inner
103 .get_slot_with_context(ctx, commitment)
104 .await
105 .map_err(Into::into)
106 }
107
108 pub async fn get_block_height_with_context(
109 &self,
110 ctx: Context,
111 commitment: CommitmentLevel,
112 ) -> Result<Slot, BanksClientError> {
113 self.inner
114 .get_block_height_with_context(ctx, commitment)
115 .await
116 .map_err(Into::into)
117 }
118
119 pub async fn process_transaction_with_commitment_and_context(
120 &self,
121 ctx: Context,
122 transaction: impl Into<VersionedTransaction>,
123 commitment: CommitmentLevel,
124 ) -> Result<Option<transaction::Result<()>>, BanksClientError> {
125 self.inner
126 .process_transaction_with_commitment_and_context(ctx, transaction.into(), commitment)
127 .await
128 .map_err(Into::into)
129 }
130
131 pub async fn process_transaction_with_preflight_and_commitment_and_context(
132 &self,
133 ctx: Context,
134 transaction: impl Into<VersionedTransaction>,
135 commitment: CommitmentLevel,
136 ) -> Result<BanksTransactionResultWithSimulation, BanksClientError> {
137 self.inner
138 .process_transaction_with_preflight_and_commitment_and_context(
139 ctx,
140 transaction.into(),
141 commitment,
142 )
143 .await
144 .map_err(Into::into)
145 }
146
147 pub async fn process_transaction_with_metadata_and_context(
148 &self,
149 ctx: Context,
150 transaction: impl Into<VersionedTransaction>,
151 ) -> Result<BanksTransactionResultWithMetadata, BanksClientError> {
152 self.inner
153 .process_transaction_with_metadata_and_context(ctx, transaction.into())
154 .await
155 .map_err(Into::into)
156 }
157
158 pub async fn simulate_transaction_with_commitment_and_context(
159 &self,
160 ctx: Context,
161 transaction: impl Into<VersionedTransaction>,
162 commitment: CommitmentLevel,
163 ) -> Result<BanksTransactionResultWithSimulation, BanksClientError> {
164 self.inner
165 .simulate_transaction_with_commitment_and_context(ctx, transaction.into(), commitment)
166 .await
167 .map_err(Into::into)
168 }
169
170 pub async fn get_account_with_commitment_and_context(
171 &self,
172 ctx: Context,
173 address: Pubkey,
174 commitment: CommitmentLevel,
175 ) -> Result<Option<Account>, BanksClientError> {
176 self.inner
177 .get_account_with_commitment_and_context(ctx, address, commitment)
178 .await
179 .map_err(Into::into)
180 }
181
182 pub async fn send_transaction(
186 &self,
187 transaction: impl Into<VersionedTransaction>,
188 ) -> Result<(), BanksClientError> {
189 self.send_transaction_with_context(context::current(), transaction.into())
190 .await
191 }
192
193 pub async fn get_sysvar<T: SysvarSerialize>(&self) -> Result<T, BanksClientError> {
195 let sysvar = self
196 .get_account(T::id())
197 .await?
198 .ok_or(BanksClientError::ClientError("Sysvar not present"))?;
199 from_account::<T, _>(&sysvar).ok_or(BanksClientError::ClientError(
200 "Failed to deserialize sysvar",
201 ))
202 }
203
204 pub async fn get_rent(&self) -> Result<Rent, BanksClientError> {
206 self.get_sysvar::<Rent>().await
207 }
208
209 pub async fn process_transaction_with_commitment(
212 &self,
213 transaction: impl Into<VersionedTransaction>,
214 commitment: CommitmentLevel,
215 ) -> Result<(), BanksClientError> {
216 let ctx = context::current();
217 match self
218 .process_transaction_with_commitment_and_context(ctx, transaction, commitment)
219 .await?
220 {
221 None => Err(BanksClientError::ClientError(
222 "invalid blockhash or fee-payer",
223 )),
224 Some(transaction_result) => Ok(transaction_result?),
225 }
226 }
227
228 pub async fn process_transaction_with_metadata(
230 &self,
231 transaction: impl Into<VersionedTransaction>,
232 ) -> Result<BanksTransactionResultWithMetadata, BanksClientError> {
233 let ctx = context::current();
234 self.process_transaction_with_metadata_and_context(ctx, transaction.into())
235 .await
236 }
237
238 pub async fn process_transaction_with_preflight_and_commitment(
241 &self,
242 transaction: impl Into<VersionedTransaction>,
243 commitment: CommitmentLevel,
244 ) -> Result<(), BanksClientError> {
245 let ctx = context::current();
246 match self
247 .process_transaction_with_preflight_and_commitment_and_context(
248 ctx,
249 transaction,
250 commitment,
251 )
252 .await?
253 {
254 BanksTransactionResultWithSimulation {
255 result: None,
256 simulation_details: _,
257 } => Err(BanksClientError::ClientError(
258 "invalid blockhash or fee-payer",
259 )),
260 BanksTransactionResultWithSimulation {
261 result: Some(Err(err)),
262 simulation_details: Some(simulation_details),
263 } => Err(BanksClientError::SimulationError {
264 err,
265 logs: simulation_details.logs,
266 units_consumed: simulation_details.units_consumed,
267 return_data: simulation_details.return_data,
268 }),
269 BanksTransactionResultWithSimulation {
270 result: Some(result),
271 simulation_details: _,
272 } => result.map_err(Into::into),
273 }
274 }
275
276 pub async fn process_transaction_with_preflight(
279 &self,
280 transaction: impl Into<VersionedTransaction>,
281 ) -> Result<(), BanksClientError> {
282 self.process_transaction_with_preflight_and_commitment(
283 transaction,
284 CommitmentLevel::default(),
285 )
286 .await
287 }
288
289 pub async fn process_transaction(
291 &self,
292 transaction: impl Into<VersionedTransaction>,
293 ) -> Result<(), BanksClientError> {
294 self.process_transaction_with_commitment(transaction, CommitmentLevel::default())
295 .await
296 }
297
298 pub async fn process_transactions_with_commitment<T: Into<VersionedTransaction>>(
299 &self,
300 transactions: Vec<T>,
301 commitment: CommitmentLevel,
302 ) -> Result<(), BanksClientError> {
303 let mut clients: Vec<_> = transactions.iter().map(|_| self.clone()).collect();
304 let futures = clients
305 .iter_mut()
306 .zip(transactions)
307 .map(|(client, transaction)| {
308 client.process_transaction_with_commitment(transaction, commitment)
309 });
310 let statuses = join_all(futures).await;
311 statuses.into_iter().collect() }
313
314 pub async fn process_transactions<'a, T: Into<VersionedTransaction> + 'a>(
316 &'a self,
317 transactions: Vec<T>,
318 ) -> Result<(), BanksClientError> {
319 self.process_transactions_with_commitment(transactions, CommitmentLevel::default())
320 .await
321 }
322
323 pub async fn simulate_transaction_with_commitment(
325 &self,
326 transaction: impl Into<VersionedTransaction>,
327 commitment: CommitmentLevel,
328 ) -> Result<BanksTransactionResultWithSimulation, BanksClientError> {
329 self.simulate_transaction_with_commitment_and_context(
330 context::current(),
331 transaction,
332 commitment,
333 )
334 .await
335 }
336
337 pub async fn simulate_transaction(
339 &self,
340 transaction: impl Into<VersionedTransaction>,
341 ) -> Result<BanksTransactionResultWithSimulation, BanksClientError> {
342 self.simulate_transaction_with_commitment(transaction, CommitmentLevel::default())
343 .await
344 }
345
346 pub async fn get_root_slot(&self) -> Result<Slot, BanksClientError> {
349 self.get_slot_with_context(context::current(), CommitmentLevel::default())
350 .await
351 }
352
353 pub async fn get_root_block_height(&self) -> Result<Slot, BanksClientError> {
356 self.get_block_height_with_context(context::current(), CommitmentLevel::default())
357 .await
358 }
359
360 pub async fn get_account_with_commitment(
363 &self,
364 address: Pubkey,
365 commitment: CommitmentLevel,
366 ) -> Result<Option<Account>, BanksClientError> {
367 self.get_account_with_commitment_and_context(context::current(), address, commitment)
368 .await
369 }
370
371 pub async fn get_account(&self, address: Pubkey) -> Result<Option<Account>, BanksClientError> {
374 self.get_account_with_commitment(address, CommitmentLevel::default())
375 .await
376 }
377
378 pub async fn get_packed_account_data<T: Pack>(
381 &self,
382 address: Pubkey,
383 ) -> Result<T, BanksClientError> {
384 let account = self
385 .get_account(address)
386 .await?
387 .ok_or(BanksClientError::ClientError("Account not found"))?;
388 T::unpack_from_slice(&account.data)
389 .map_err(|_| BanksClientError::ClientError("Failed to deserialize account"))
390 }
391
392 pub async fn get_account_data_with_borsh<T: BorshDeserialize>(
395 &self,
396 address: Pubkey,
397 ) -> Result<T, BanksClientError> {
398 let account = self
399 .get_account(address)
400 .await?
401 .ok_or(BanksClientError::ClientError("Account not found"))?;
402 T::try_from_slice(&account.data).map_err(Into::into)
403 }
404
405 pub async fn get_balance_with_commitment(
408 &self,
409 address: Pubkey,
410 commitment: CommitmentLevel,
411 ) -> Result<u64, BanksClientError> {
412 Ok(self
413 .get_account_with_commitment_and_context(context::current(), address, commitment)
414 .await?
415 .map(|x| x.lamports)
416 .unwrap_or(0))
417 }
418
419 pub async fn get_balance(&self, address: Pubkey) -> Result<u64, BanksClientError> {
422 self.get_balance_with_commitment(address, CommitmentLevel::default())
423 .await
424 }
425
426 pub async fn get_transaction_status(
432 &self,
433 signature: Signature,
434 ) -> Result<Option<TransactionStatus>, BanksClientError> {
435 self.get_transaction_status_with_context(context::current(), signature)
436 .await
437 }
438
439 pub async fn get_transaction_statuses(
441 &self,
442 signatures: Vec<Signature>,
443 ) -> Result<Vec<Option<TransactionStatus>>, BanksClientError> {
444 let mut clients_and_signatures: Vec<_> = signatures
446 .into_iter()
447 .map(|signature| (self.clone(), signature))
448 .collect();
449
450 let futs = clients_and_signatures
451 .iter_mut()
452 .map(|(client, signature)| client.get_transaction_status(*signature));
453
454 let statuses = join_all(futs).await;
455
456 statuses.into_iter().collect()
458 }
459
460 pub async fn get_latest_blockhash(&self) -> Result<Hash, BanksClientError> {
461 self.get_latest_blockhash_with_commitment(CommitmentLevel::default())
462 .await?
463 .map(|x| x.0)
464 .ok_or(BanksClientError::ClientError("valid blockhash not found"))
465 }
466
467 pub async fn get_latest_blockhash_with_commitment(
468 &self,
469 commitment: CommitmentLevel,
470 ) -> Result<Option<(Hash, u64)>, BanksClientError> {
471 self.get_latest_blockhash_with_commitment_and_context(context::current(), commitment)
472 .await
473 }
474
475 pub async fn get_latest_blockhash_with_commitment_and_context(
476 &self,
477 ctx: Context,
478 commitment: CommitmentLevel,
479 ) -> Result<Option<(Hash, u64)>, BanksClientError> {
480 self.inner
481 .get_latest_blockhash_with_commitment_and_context(ctx, commitment)
482 .await
483 .map_err(Into::into)
484 }
485
486 pub async fn get_fee_for_message(
487 &self,
488 message: Message,
489 ) -> Result<Option<u64>, BanksClientError> {
490 self.get_fee_for_message_with_commitment_and_context(
491 context::current(),
492 message,
493 CommitmentLevel::default(),
494 )
495 .await
496 }
497
498 pub async fn get_fee_for_message_with_commitment(
499 &self,
500 message: Message,
501 commitment: CommitmentLevel,
502 ) -> Result<Option<u64>, BanksClientError> {
503 self.get_fee_for_message_with_commitment_and_context(
504 context::current(),
505 message,
506 commitment,
507 )
508 .await
509 }
510
511 pub async fn get_fee_for_message_with_commitment_and_context(
512 &self,
513 ctx: Context,
514 message: Message,
515 commitment: CommitmentLevel,
516 ) -> Result<Option<u64>, BanksClientError> {
517 self.inner
518 .get_fee_for_message_with_commitment_and_context(ctx, message, commitment)
519 .await
520 .map_err(Into::into)
521 }
522}
523
524pub async fn start_client<C>(transport: C) -> Result<BanksClient, BanksClientError>
525where
526 C: Transport<ClientMessage<BanksRequest>, Response<BanksResponse>> + Send + 'static,
527{
528 Ok(BanksClient {
529 inner: TarpcClient::new(client::Config::default(), transport).spawn(),
530 })
531}
532
533pub async fn start_tcp_client<T: ToSocketAddrs>(addr: T) -> Result<BanksClient, BanksClientError> {
534 let transport = tcp::connect(addr, Bincode::default).await?;
535 Ok(BanksClient {
536 inner: TarpcClient::new(client::Config::default(), transport).spawn(),
537 })
538}
539
540#[cfg(test)]
541mod tests {
542 use {
543 super::*,
544 solana_banks_server::banks_server::start_local_server,
545 solana_runtime::{
546 bank::Bank, bank_forks::BankForks, commitment::BlockCommitmentCache,
547 genesis_utils::create_genesis_config,
548 },
549 solana_signer::Signer,
550 solana_system_interface::instruction as system_instruction,
551 solana_transaction::Transaction,
552 std::sync::{Arc, RwLock},
553 tarpc::transport,
554 tokio::{
555 runtime::Runtime,
556 time::{sleep, Duration},
557 },
558 };
559
560 #[test]
561 fn test_banks_client_new() {
562 let (client_transport, _server_transport) = transport::channel::unbounded();
563 BanksClient::new(client::Config::default(), client_transport);
564 }
565
566 #[test]
567 #[allow(clippy::result_large_err)]
568 fn test_banks_server_transfer_via_server() -> Result<(), BanksClientError> {
569 let genesis = create_genesis_config(10);
574 let bank = Bank::new_for_tests(&genesis.genesis_config);
575 let slot = bank.slot();
576 let block_commitment_cache = Arc::new(RwLock::new(
577 BlockCommitmentCache::new_for_tests_with_slots(slot, slot),
578 ));
579 let bank_forks = BankForks::new_rw_arc(bank);
580
581 let bob_pubkey = solana_pubkey::new_rand();
582 let mint_pubkey = genesis.mint_keypair.pubkey();
583 let instruction = system_instruction::transfer(&mint_pubkey, &bob_pubkey, 1);
584 let message = Message::new(&[instruction], Some(&mint_pubkey));
585
586 Runtime::new()?.block_on(async {
587 let client_transport =
588 start_local_server(bank_forks, block_commitment_cache, Duration::from_millis(1))
589 .await;
590 let banks_client = start_client(client_transport).await?;
591
592 let recent_blockhash = banks_client.get_latest_blockhash().await?;
593 let transaction = Transaction::new(&[&genesis.mint_keypair], message, recent_blockhash);
594 let simulation_result = banks_client
595 .simulate_transaction(transaction.clone())
596 .await
597 .unwrap();
598 assert!(simulation_result.result.unwrap().is_ok());
599 banks_client.process_transaction(transaction).await.unwrap();
600 assert_eq!(banks_client.get_balance(bob_pubkey).await?, 1);
601 Ok(())
602 })
603 }
604
605 #[test]
606 #[allow(clippy::result_large_err)]
607 fn test_banks_server_transfer_via_client() -> Result<(), BanksClientError> {
608 let genesis = create_genesis_config(10);
613 let bank = Bank::new_for_tests(&genesis.genesis_config);
614 let slot = bank.slot();
615 let block_commitment_cache = Arc::new(RwLock::new(
616 BlockCommitmentCache::new_for_tests_with_slots(slot, slot),
617 ));
618 let bank_forks = BankForks::new_rw_arc(bank);
619
620 let mint_pubkey = &genesis.mint_keypair.pubkey();
621 let bob_pubkey = solana_pubkey::new_rand();
622 let instruction = system_instruction::transfer(mint_pubkey, &bob_pubkey, 1);
623 let message = Message::new(&[instruction], Some(mint_pubkey));
624
625 Runtime::new()?.block_on(async {
626 let client_transport =
627 start_local_server(bank_forks, block_commitment_cache, Duration::from_millis(1))
628 .await;
629 let banks_client = start_client(client_transport).await?;
630 let (recent_blockhash, last_valid_block_height) = banks_client
631 .get_latest_blockhash_with_commitment(CommitmentLevel::default())
632 .await?
633 .unwrap();
634 let transaction = Transaction::new(&[&genesis.mint_keypair], message, recent_blockhash);
635 let signature = transaction.signatures[0];
636 banks_client.send_transaction(transaction).await?;
637
638 let mut status = banks_client.get_transaction_status(signature).await?;
639
640 while status.is_none() {
641 let root_block_height = banks_client.get_root_block_height().await?;
642 if root_block_height > last_valid_block_height {
643 break;
644 }
645 sleep(Duration::from_millis(100)).await;
646 status = banks_client.get_transaction_status(signature).await?;
647 }
648 assert!(status.unwrap().err.is_none());
649 assert_eq!(banks_client.get_balance(bob_pubkey).await?, 1);
650 Ok(())
651 })
652 }
653}