From 3be11061ed01cb1ac2ee255b987d18859cdaf5f9 Mon Sep 17 00:00:00 2001 From: Jon Cinque Date: Wed, 8 Jun 2022 13:56:54 +0200 Subject: [PATCH] banks-client: Add `simulate_transaction` implementation (#25830) --- banks-client/src/lib.rs | 40 ++++++++++++++++ banks-interface/src/lib.rs | 4 ++ banks-server/src/banks_server.rs | 82 +++++++++++++++++++------------- 3 files changed, 93 insertions(+), 33 deletions(-) diff --git a/banks-client/src/lib.rs b/banks-client/src/lib.rs index 6af3383208..fd41dd4f8c 100644 --- a/banks-client/src/lib.rs +++ b/banks-client/src/lib.rs @@ -138,6 +138,18 @@ impl BanksClient { .map_err(Into::into) } + pub fn simulate_transaction_with_commitment_and_context( + &mut self, + ctx: Context, + transaction: Transaction, + commitment: CommitmentLevel, + ) -> impl Future> + '_ + { + self.inner + .simulate_transaction_with_commitment_and_context(ctx, transaction, commitment) + .map_err(Into::into) + } + pub fn get_account_with_commitment_and_context( &mut self, ctx: Context, @@ -300,6 +312,29 @@ impl BanksClient { self.process_transactions_with_commitment(transactions, CommitmentLevel::default()) } + /// Simulate a transaction at the given commitment level + pub fn simulate_transaction_with_commitment( + &mut self, + transaction: Transaction, + commitment: CommitmentLevel, + ) -> impl Future> + '_ + { + self.simulate_transaction_with_commitment_and_context( + context::current(), + transaction, + commitment, + ) + } + + /// Simulate a transaction at the default commitment level + pub fn simulate_transaction( + &mut self, + transaction: Transaction, + ) -> impl Future> + '_ + { + self.simulate_transaction_with_commitment(transaction, CommitmentLevel::default()) + } + /// Return the most recent rooted slot. All transactions at or below this slot /// are said to be finalized. The cluster will not fork to a higher slot. pub fn get_root_slot(&mut self) -> impl Future> + '_ { @@ -516,6 +551,11 @@ mod tests { let recent_blockhash = banks_client.get_latest_blockhash().await?; let transaction = Transaction::new(&[&genesis.mint_keypair], message, recent_blockhash); + let simulation_result = banks_client + .simulate_transaction(transaction.clone()) + .await + .unwrap(); + assert!(simulation_result.result.unwrap().is_ok()); banks_client.process_transaction(transaction).await.unwrap(); assert_eq!(banks_client.get_balance(bob_pubkey).await?, 1); Ok(()) diff --git a/banks-interface/src/lib.rs b/banks-interface/src/lib.rs index 344a101f2b..cf38502a20 100644 --- a/banks-interface/src/lib.rs +++ b/banks-interface/src/lib.rs @@ -67,6 +67,10 @@ pub trait Banks { transaction: Transaction, commitment: CommitmentLevel, ) -> Option>; + async fn simulate_transaction_with_commitment_and_context( + transaction: Transaction, + commitment: CommitmentLevel, + ) -> BanksTransactionResultWithSimulation; async fn get_account_with_commitment_and_context( address: Pubkey, commitment: CommitmentLevel, diff --git a/banks-server/src/banks_server.rs b/banks-server/src/banks_server.rs index 782adff6bc..d54783e367 100644 --- a/banks-server/src/banks_server.rs +++ b/banks-server/src/banks_server.rs @@ -161,6 +161,38 @@ fn verify_transaction( } } +fn simulate_transaction( + bank: &Bank, + transaction: Transaction, +) -> BanksTransactionResultWithSimulation { + let sanitized_transaction = match SanitizedTransaction::try_from_legacy_transaction(transaction) + { + Err(err) => { + return BanksTransactionResultWithSimulation { + result: Some(Err(err)), + simulation_details: None, + }; + } + Ok(tx) => tx, + }; + let TransactionSimulationResult { + result, + logs, + post_simulation_accounts: _, + units_consumed, + return_data, + } = bank.simulate_transaction_unchecked(sanitized_transaction); + let simulation_details = TransactionSimulationDetails { + logs, + units_consumed, + return_data, + }; + BanksTransactionResultWithSimulation { + result: Some(result), + simulation_details: Some(simulation_details), + } +} + #[tarpc::server] impl Banks for BanksServer { async fn send_transaction_with_context(self, _: Context, transaction: Transaction) { @@ -252,41 +284,25 @@ impl Banks for BanksServer { transaction: Transaction, commitment: CommitmentLevel, ) -> BanksTransactionResultWithSimulation { - let sanitized_transaction = - match SanitizedTransaction::try_from_legacy_transaction(transaction.clone()) { - Err(err) => { - return BanksTransactionResultWithSimulation { - result: Some(Err(err)), - simulation_details: None, - }; - } - Ok(tx) => tx, - }; - if let TransactionSimulationResult { - result: Err(err), - logs, - post_simulation_accounts: _, - units_consumed, - return_data, - } = self - .bank(commitment) - .simulate_transaction_unchecked(sanitized_transaction) - { - return BanksTransactionResultWithSimulation { - result: Some(Err(err)), - simulation_details: Some(TransactionSimulationDetails { - logs, - units_consumed, - return_data, - }), - }; - } - BanksTransactionResultWithSimulation { - result: self + let mut simulation_result = + simulate_transaction(&self.bank(commitment), transaction.clone()); + // Simulation was ok, so process the real transaction and replace the + // simulation's result with the real transaction result + if let Some(Ok(_)) = simulation_result.result { + simulation_result.result = self .process_transaction_with_commitment_and_context(ctx, transaction, commitment) - .await, - simulation_details: None, + .await; } + simulation_result + } + + async fn simulate_transaction_with_commitment_and_context( + self, + _: Context, + transaction: Transaction, + commitment: CommitmentLevel, + ) -> BanksTransactionResultWithSimulation { + simulate_transaction(&self.bank(commitment), transaction) } async fn process_transaction_with_commitment_and_context(