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