solana_runtime/
bank_client.rs

1use {
2    crate::bank::Bank,
3    crossbeam_channel::{unbounded, Receiver, Sender},
4    solana_account::Account,
5    solana_client_traits::{AsyncClient, Client, SyncClient},
6    solana_commitment_config::CommitmentConfig,
7    solana_epoch_info::EpochInfo,
8    solana_hash::Hash,
9    solana_instruction::Instruction,
10    solana_keypair::Keypair,
11    solana_message::{Message, SanitizedMessage},
12    solana_pubkey::Pubkey,
13    solana_signature::Signature,
14    solana_signer::{signers::Signers, Signer},
15    solana_system_interface::instruction as system_instruction,
16    solana_sysvar::Sysvar,
17    solana_sysvar_id::SysvarId,
18    solana_transaction::{versioned::VersionedTransaction, Transaction},
19    solana_transaction_error::{TransportError, TransportResult as Result},
20    std::{
21        io,
22        sync::Arc,
23        thread::{sleep, Builder},
24        time::{Duration, Instant},
25    },
26};
27mod transaction {
28    pub use solana_transaction_error::TransactionResult as Result;
29}
30#[cfg(feature = "dev-context-only-utils")]
31use {crate::bank_forks::BankForks, solana_clock as clock, std::sync::RwLock};
32
33pub struct BankClient {
34    bank: Arc<Bank>,
35    transaction_sender: Sender<VersionedTransaction>,
36}
37
38impl Client for BankClient {
39    fn tpu_addr(&self) -> String {
40        "Local BankClient".to_string()
41    }
42}
43
44impl AsyncClient for BankClient {
45    fn async_send_versioned_transaction(
46        &self,
47        transaction: VersionedTransaction,
48    ) -> Result<Signature> {
49        let signature = transaction.signatures.first().cloned().unwrap_or_default();
50        let transaction_sender = self.transaction_sender.clone();
51        transaction_sender.send(transaction).unwrap();
52        Ok(signature)
53    }
54}
55
56impl SyncClient for BankClient {
57    fn send_and_confirm_message<T: Signers + ?Sized>(
58        &self,
59        keypairs: &T,
60        message: Message,
61    ) -> Result<Signature> {
62        let blockhash = self.bank.last_blockhash();
63        let transaction = Transaction::new(keypairs, message, blockhash);
64        self.bank.process_transaction(&transaction)?;
65        Ok(transaction.signatures.first().cloned().unwrap_or_default())
66    }
67
68    /// Create and process a transaction from a single instruction.
69    fn send_and_confirm_instruction(
70        &self,
71        keypair: &Keypair,
72        instruction: Instruction,
73    ) -> Result<Signature> {
74        let message = Message::new(&[instruction], Some(&keypair.pubkey()));
75        self.send_and_confirm_message(&[keypair], message)
76    }
77
78    /// Transfer `lamports` from `keypair` to `pubkey`
79    fn transfer_and_confirm(
80        &self,
81        lamports: u64,
82        keypair: &Keypair,
83        pubkey: &Pubkey,
84    ) -> Result<Signature> {
85        let transfer_instruction =
86            system_instruction::transfer(&keypair.pubkey(), pubkey, lamports);
87        self.send_and_confirm_instruction(keypair, transfer_instruction)
88    }
89
90    fn get_account_data(&self, pubkey: &Pubkey) -> Result<Option<Vec<u8>>> {
91        Ok(self
92            .bank
93            .get_account(pubkey)
94            .map(|account| Account::from(account).data))
95    }
96
97    fn get_account(&self, pubkey: &Pubkey) -> Result<Option<Account>> {
98        Ok(self.bank.get_account(pubkey).map(Account::from))
99    }
100
101    fn get_account_with_commitment(
102        &self,
103        pubkey: &Pubkey,
104        _commitment_config: CommitmentConfig,
105    ) -> Result<Option<Account>> {
106        Ok(self.bank.get_account(pubkey).map(Account::from))
107    }
108
109    fn get_balance(&self, pubkey: &Pubkey) -> Result<u64> {
110        Ok(self.bank.get_balance(pubkey))
111    }
112
113    fn get_balance_with_commitment(
114        &self,
115        pubkey: &Pubkey,
116        _commitment_config: CommitmentConfig,
117    ) -> Result<u64> {
118        Ok(self.bank.get_balance(pubkey))
119    }
120
121    fn get_minimum_balance_for_rent_exemption(&self, data_len: usize) -> Result<u64> {
122        Ok(self.bank.get_minimum_balance_for_rent_exemption(data_len))
123    }
124
125    fn get_signature_status(
126        &self,
127        signature: &Signature,
128    ) -> Result<Option<transaction::Result<()>>> {
129        Ok(self.bank.get_signature_status(signature))
130    }
131
132    fn get_signature_status_with_commitment(
133        &self,
134        signature: &Signature,
135        _commitment_config: CommitmentConfig,
136    ) -> Result<Option<transaction::Result<()>>> {
137        Ok(self.bank.get_signature_status(signature))
138    }
139
140    fn get_slot(&self) -> Result<u64> {
141        Ok(self.bank.slot())
142    }
143
144    fn get_slot_with_commitment(&self, _commitment_config: CommitmentConfig) -> Result<u64> {
145        Ok(self.bank.slot())
146    }
147
148    fn get_transaction_count(&self) -> Result<u64> {
149        Ok(self.bank.transaction_count())
150    }
151
152    fn get_transaction_count_with_commitment(
153        &self,
154        _commitment_config: CommitmentConfig,
155    ) -> Result<u64> {
156        Ok(self.bank.transaction_count())
157    }
158
159    fn poll_for_signature_confirmation(
160        &self,
161        signature: &Signature,
162        min_confirmed_blocks: usize,
163    ) -> Result<usize> {
164        // https://github.com/solana-labs/solana/issues/7199
165        assert_eq!(min_confirmed_blocks, 1, "BankClient cannot observe the passage of multiple blocks, so min_confirmed_blocks must be 1");
166        let now = Instant::now();
167        let confirmed_blocks;
168        loop {
169            if self.bank.get_signature_status(signature).is_some() {
170                confirmed_blocks = 1;
171                break;
172            }
173            if now.elapsed().as_secs() > 15 {
174                return Err(TransportError::IoError(io::Error::other(format!(
175                    "signature not found after {} seconds",
176                    now.elapsed().as_secs()
177                ))));
178            }
179            sleep(Duration::from_millis(250));
180        }
181        Ok(confirmed_blocks)
182    }
183
184    fn poll_for_signature(&self, signature: &Signature) -> Result<()> {
185        let now = Instant::now();
186        loop {
187            let response = self.bank.get_signature_status(signature);
188            if let Some(res) = response {
189                if res.is_ok() {
190                    break;
191                }
192            }
193            if now.elapsed().as_secs() > 15 {
194                return Err(TransportError::IoError(io::Error::other(format!(
195                    "signature not found after {} seconds",
196                    now.elapsed().as_secs()
197                ))));
198            }
199            sleep(Duration::from_millis(250));
200        }
201        Ok(())
202    }
203
204    fn get_epoch_info(&self) -> Result<EpochInfo> {
205        Ok(self.bank.get_epoch_info())
206    }
207
208    fn get_latest_blockhash(&self) -> Result<Hash> {
209        Ok(self.bank.last_blockhash())
210    }
211
212    fn get_latest_blockhash_with_commitment(
213        &self,
214        _commitment_config: CommitmentConfig,
215    ) -> Result<(Hash, u64)> {
216        let blockhash = self.bank.last_blockhash();
217        let last_valid_block_height = self
218            .bank
219            .get_blockhash_last_valid_block_height(&blockhash)
220            .expect("bank blockhash queue should contain blockhash");
221        Ok((blockhash, last_valid_block_height))
222    }
223
224    fn is_blockhash_valid(
225        &self,
226        blockhash: &Hash,
227        _commitment_config: CommitmentConfig,
228    ) -> Result<bool> {
229        Ok(self.bank.is_blockhash_valid(blockhash))
230    }
231
232    fn get_fee_for_message(&self, message: &Message) -> Result<u64> {
233        SanitizedMessage::try_from_legacy_message(
234            message.clone(),
235            self.bank.get_reserved_account_keys(),
236        )
237        .ok()
238        .and_then(|sanitized_message| self.bank.get_fee_for_message(&sanitized_message))
239        .ok_or_else(|| TransportError::IoError(io::Error::other("Unable calculate fee")))
240    }
241}
242
243impl BankClient {
244    fn run(bank: &Bank, transaction_receiver: Receiver<VersionedTransaction>) {
245        while let Ok(tx) = transaction_receiver.recv() {
246            let mut transactions = vec![tx];
247            while let Ok(tx) = transaction_receiver.try_recv() {
248                transactions.push(tx);
249            }
250            let _ = bank.try_process_entry_transactions(transactions);
251        }
252    }
253
254    pub fn new_shared(bank: Arc<Bank>) -> Self {
255        let (transaction_sender, transaction_receiver) = unbounded();
256        let thread_bank = bank.clone();
257        Builder::new()
258            .name("solBankClient".to_string())
259            .spawn(move || Self::run(&thread_bank, transaction_receiver))
260            .unwrap();
261        Self {
262            bank,
263            transaction_sender,
264        }
265    }
266
267    pub fn new(bank: Bank) -> Self {
268        Self::new_shared(Arc::new(bank))
269    }
270
271    pub fn set_sysvar_for_tests<T: Sysvar + SysvarId>(&self, sysvar: &T) {
272        self.bank.set_sysvar_for_tests(sysvar);
273    }
274
275    #[cfg(feature = "dev-context-only-utils")]
276    pub fn advance_slot(
277        &mut self,
278        by: u64,
279        bank_forks: &RwLock<BankForks>,
280        collector_id: &Pubkey,
281    ) -> Option<Arc<Bank>> {
282        let new_bank = Bank::new_from_parent(
283            self.bank.clone(),
284            collector_id,
285            self.bank.slot().checked_add(by)?,
286        );
287        self.bank = bank_forks
288            .write()
289            .unwrap()
290            .insert(new_bank)
291            .clone_without_scheduler();
292
293        self.set_sysvar_for_tests(&clock::Clock {
294            slot: self.bank.slot(),
295            ..clock::Clock::default()
296        });
297        Some(self.bank.clone())
298    }
299}
300
301#[cfg(test)]
302mod tests {
303    use {
304        super::*, solana_genesis_config::create_genesis_config, solana_instruction::AccountMeta,
305        solana_native_token::sol_to_lamports,
306    };
307
308    #[test]
309    fn test_bank_client_new_with_keypairs() {
310        let (genesis_config, john_doe_keypair) = create_genesis_config(sol_to_lamports(1.0));
311        let john_pubkey = john_doe_keypair.pubkey();
312        let jane_doe_keypair = Keypair::new();
313        let jane_pubkey = jane_doe_keypair.pubkey();
314        let doe_keypairs = vec![&john_doe_keypair, &jane_doe_keypair];
315        let (bank, _bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
316        let bank_client = BankClient::new_shared(bank);
317        let amount = genesis_config.rent.minimum_balance(0);
318
319        // Create 2-2 Multisig Transfer instruction.
320        let bob_pubkey = solana_pubkey::new_rand();
321        let mut transfer_instruction =
322            system_instruction::transfer(&john_pubkey, &bob_pubkey, amount);
323        transfer_instruction
324            .accounts
325            .push(AccountMeta::new(jane_pubkey, true));
326
327        let message = Message::new(&[transfer_instruction], Some(&john_pubkey));
328        bank_client
329            .send_and_confirm_message(&doe_keypairs, message)
330            .unwrap();
331        assert_eq!(bank_client.get_balance(&bob_pubkey).unwrap(), amount);
332    }
333}