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