diff --git a/CHANGELOG.md b/CHANGELOG.md index a6a0d5787e..cd6b02bd3b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ The minor version will be incremented upon a breaking change and the patch versi - cli: Add checks for incorrect usage of `idl-build` feature ([#3061](https://github.com/coral-xyz/anchor/pull/3061)). - lang: Export `Discriminator` trait from `prelude` ([#3075](https://github.com/coral-xyz/anchor/pull/3075)). - lang: Add `Account` utility type to get accounts from bytes ([#3091](https://github.com/coral-xyz/anchor/pull/3091)). +- client: Add option to pass in mock rpc client when using anchor_client ([#3053](https://github.com/coral-xyz/anchor/pull/3053)). ### Fixes @@ -33,14 +34,15 @@ The minor version will be incremented upon a breaking change and the patch versi ### Breaking - syn: Remove `bpf` target support in `hash` feature ([#3078](https://github.com/coral-xyz/anchor/pull/3078)). -- client: Add `tokio` support to `RequestBuilder` with `async` feature ([#3057](https://github.com/coral-xyz/anchor/pull/3057])). -- lang: Remove `EventData` trait ([#3083](https://github.com/coral-xyz/anchor/pull/3083])). +- client: Add `tokio` support to `RequestBuilder` with `async` feature ([#3057](https://github.com/coral-xyz/anchor/pull/3057)). +- lang: Remove `EventData` trait ([#3083](https://github.com/coral-xyz/anchor/pull/3083)). +- client: Remove `async_rpc` method ([#3053](https://github.com/coral-xyz/anchor/pull/3053)). ## [0.30.1] - 2024-06-20 ### Features -- idl: Allow overriding the idl build toolchain with the `RUSTUP_TOOLCHAIN` environment variable ([#2941](https://github.com/coral-xyz/anchor/pull/2941])). +- idl: Allow overriding the idl build toolchain with the `RUSTUP_TOOLCHAIN` environment variable ([#2941](https://github.com/coral-xyz/anchor/pull/2941)). - avm: Support customizing the installation location using `AVM_HOME` environment variable ([#2917](https://github.com/coral-xyz/anchor/pull/2917)). - avm: Optimize `avm list` when GitHub API rate limits are reached ([#2962](https://github.com/coral-xyz/anchor/pull/2962)) - idl, ts: Add accounts resolution for associated token accounts ([#2927](https://github.com/coral-xyz/anchor/pull/2927)). diff --git a/client/Cargo.toml b/client/Cargo.toml index 7b4dcd620d..603c7ce1ee 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -13,6 +13,7 @@ rustdoc-args = ["--cfg", "docsrs"] [features] async = [] debug = [] +mock = [] [dependencies] anchor-lang = { path = "../lang", version = "0.30.1" } diff --git a/client/example/src/nonblocking.rs b/client/example/src/nonblocking.rs index 0f558699e0..4c7b0fd66b 100644 --- a/client/example/src/nonblocking.rs +++ b/client/example/src/nonblocking.rs @@ -116,7 +116,7 @@ pub async fn composite + Clone>( &program.payer(), &dummy_a.pubkey(), program - .async_rpc() + .rpc() .get_minimum_balance_for_rent_exemption(500) .await?, 500, @@ -126,7 +126,7 @@ pub async fn composite + Clone>( &program.payer(), &dummy_b.pubkey(), program - .async_rpc() + .rpc() .get_minimum_balance_for_rent_exemption(500) .await?, 500, @@ -307,7 +307,7 @@ pub async fn optional + Clone>( &program.payer(), &required_keypair.pubkey(), program - .async_rpc() + .rpc() .get_minimum_balance_for_rent_exemption(DataAccount::LEN) .await?, DataAccount::LEN as u64, diff --git a/client/src/blocking.rs b/client/src/blocking.rs index 541af4cff2..a0522d7ed0 100644 --- a/client/src/blocking.rs +++ b/client/src/blocking.rs @@ -3,7 +3,12 @@ use crate::{ RequestBuilder, }; use anchor_lang::{prelude::Pubkey, AccountDeserialize, Discriminator}; -use solana_client::{rpc_config::RpcSendTransactionConfig, rpc_filter::RpcFilterType}; +#[cfg(not(feature = "mock"))] +use solana_client::rpc_client::RpcClient; +use solana_client::{ + nonblocking::rpc_client::RpcClient as AsyncRpcClient, rpc_config::RpcSendTransactionConfig, + rpc_filter::RpcFilterType, +}; use solana_sdk::{ commitment_config::CommitmentConfig, signature::Signature, signer::Signer, transaction::Transaction, @@ -22,17 +27,42 @@ impl<'a> EventUnsubscriber<'a> { } impl + Clone> Program { - pub fn new(program_id: Pubkey, cfg: Config) -> Result { + pub fn new( + program_id: Pubkey, + cfg: Config, + #[cfg(feature = "mock")] rpc_client: AsyncRpcClient, + ) -> Result { let rt: tokio::runtime::Runtime = Builder::new_multi_thread().enable_all().build()?; + #[cfg(not(feature = "mock"))] + let rpc_client = { + let comm_config = cfg.options.unwrap_or_default(); + let cluster_url = cfg.cluster.url().to_string(); + AsyncRpcClient::new_with_commitment(cluster_url.clone(), comm_config) + }; + Ok(Self { program_id, cfg, sub_client: Arc::new(RwLock::new(None)), + internal_rpc_client: rpc_client, rt, }) } + // We disable the `rpc` method for `mock` feature because otherwise we'd either have to + // return a new `RpcClient` instance (which is different to the one used internally) + // or require the user to pass another one in for blocking (since we use the non-blocking one under the hood). + // The former of these would be confusing and the latter would be very annoying, especially since a user + // using the mock feature likely already has a `RpcClient` instance at hand anyway. + #[cfg(not(feature = "mock"))] + pub fn rpc(&self) -> RpcClient { + RpcClient::new_with_commitment( + self.cfg.cluster.url().to_string(), + self.cfg.options.unwrap_or_default(), + ) + } + /// Returns a request builder. pub fn request(&self) -> RequestBuilder<'_, C, Box> { RequestBuilder::from( @@ -42,6 +72,7 @@ impl + Clone> Program { self.cfg.options, #[cfg(not(feature = "async"))] self.rt.handle(), + &self.internal_rpc_client, ) } @@ -89,6 +120,7 @@ impl<'a, C: Deref + Clone> RequestBuilder<'a, C, Box, handle: &'a Handle, + rpc_client: &'a AsyncRpcClient, ) -> Self { Self { program_id, @@ -100,6 +132,7 @@ impl<'a, C: Deref + Clone> RequestBuilder<'a, C, Box> Client { } } - pub fn program(&self, program_id: Pubkey) -> Result, ClientError> { + pub fn program( + &self, + program_id: Pubkey, + #[cfg(feature = "mock")] rpc_client: AsyncRpcClient, + ) -> Result, ClientError> { let cfg = Config { cluster: self.cfg.cluster.clone(), options: self.cfg.options, payer: self.cfg.payer.clone(), }; - Program::new(program_id, cfg) + Program::new( + program_id, + cfg, + #[cfg(feature = "mock")] + rpc_client, + ) } } @@ -220,6 +226,7 @@ pub struct Program { sub_client: Arc>>, #[cfg(not(feature = "async"))] rt: tokio::runtime::Runtime, + internal_rpc_client: AsyncRpcClient, } impl + Clone> Program { @@ -231,29 +238,12 @@ impl + Clone> Program { self.program_id } - pub fn rpc(&self) -> RpcClient { - RpcClient::new_with_commitment( - self.cfg.cluster.url().to_string(), - self.cfg.options.unwrap_or_default(), - ) - } - - pub fn async_rpc(&self) -> AsyncRpcClient { - AsyncRpcClient::new_with_commitment( - self.cfg.cluster.url().to_string(), - self.cfg.options.unwrap_or_default(), - ) - } - async fn account_internal( &self, address: Pubkey, ) -> Result { - let rpc_client = AsyncRpcClient::new_with_commitment( - self.cfg.cluster.url().to_string(), - self.cfg.options.unwrap_or_default(), - ); - let account = rpc_client + let account = self + .internal_rpc_client .get_account_with_commitment(&address, CommitmentConfig::processed()) .await? .value @@ -276,9 +266,10 @@ impl + Clone> Program { }, ..RpcProgramAccountsConfig::default() }; + Ok(ProgramAccountsIterator { inner: self - .async_rpc() + .internal_rpc_client .get_program_accounts_with_config(&self.id(), config) .await? .into_iter() @@ -514,6 +505,7 @@ pub struct RequestBuilder<'a, C, S: 'a> { signers: Vec, #[cfg(not(feature = "async"))] handle: &'a Handle, + internal_rpc_client: &'a AsyncRpcClient, _phantom: PhantomData<&'a ()>, } @@ -631,21 +623,17 @@ impl<'a, C: Deref + Clone, S: AsSigner> RequestBuilder<'a, } async fn signed_transaction_internal(&self) -> Result { - let latest_hash = - AsyncRpcClient::new_with_commitment(self.cluster.to_owned(), self.options) - .get_latest_blockhash() - .await?; - let tx = self.signed_transaction_with_blockhash(latest_hash)?; + let latest_hash = self.internal_rpc_client.get_latest_blockhash().await?; + let tx = self.signed_transaction_with_blockhash(latest_hash)?; Ok(tx) } async fn send_internal(&self) -> Result { - let rpc_client = AsyncRpcClient::new_with_commitment(self.cluster.to_owned(), self.options); - let latest_hash = rpc_client.get_latest_blockhash().await?; + let latest_hash = self.internal_rpc_client.get_latest_blockhash().await?; let tx = self.signed_transaction_with_blockhash(latest_hash)?; - rpc_client + self.internal_rpc_client .send_and_confirm_transaction(&tx) .await .map_err(Into::into) @@ -655,14 +643,13 @@ impl<'a, C: Deref + Clone, S: AsSigner> RequestBuilder<'a, &self, config: RpcSendTransactionConfig, ) -> Result { - let rpc_client = AsyncRpcClient::new_with_commitment(self.cluster.to_owned(), self.options); - let latest_hash = rpc_client.get_latest_blockhash().await?; + let latest_hash = self.internal_rpc_client.get_latest_blockhash().await?; let tx = self.signed_transaction_with_blockhash(latest_hash)?; - rpc_client + self.internal_rpc_client .send_and_confirm_transaction_with_spinner_and_config( &tx, - rpc_client.commitment(), + self.internal_rpc_client.commitment(), config, ) .await diff --git a/client/src/nonblocking.rs b/client/src/nonblocking.rs index a45153465f..317bc0775a 100644 --- a/client/src/nonblocking.rs +++ b/client/src/nonblocking.rs @@ -3,6 +3,7 @@ use crate::{ ProgramAccountsIterator, RequestBuilder, }; use anchor_lang::{prelude::Pubkey, AccountDeserialize, Discriminator}; +use solana_client::nonblocking::rpc_client::RpcClient as AsyncRpcClient; use solana_client::{rpc_config::RpcSendTransactionConfig, rpc_filter::RpcFilterType}; use solana_sdk::{ commitment_config::CommitmentConfig, signature::Signature, signer::Signer, @@ -35,14 +36,39 @@ impl AsSigner for Arc { } impl + Clone> Program { - pub fn new(program_id: Pubkey, cfg: Config) -> Result { + pub fn new( + program_id: Pubkey, + cfg: Config, + #[cfg(feature = "mock")] rpc_client: AsyncRpcClient, + ) -> Result { + #[cfg(not(feature = "mock"))] + let rpc_client = { + let comm_config = cfg.options.unwrap_or_default(); + let cluster_url = cfg.cluster.url().to_string(); + AsyncRpcClient::new_with_commitment(cluster_url.clone(), comm_config) + }; + Ok(Self { program_id, cfg, sub_client: Arc::new(RwLock::new(None)), + internal_rpc_client: rpc_client, }) } + // We disable the `rpc` method for `mock` feature because otherwise we'd either have to + // return a new `RpcClient` instance (which is different to the one used internally) + // or require the user to pass another one in for blocking (since we use the non-blocking one under the hood). + // The former of these would be confusing and the latter would be very annoying, especially since a user + // using the mock feature likely already has a `RpcClient` instance at hand anyway. + #[cfg(not(feature = "mock"))] + pub fn rpc(&self) -> AsyncRpcClient { + AsyncRpcClient::new_with_commitment( + self.cfg.cluster.url().to_string(), + self.cfg.options.unwrap_or_default(), + ) + } + /// Returns a threadsafe request builder pub fn request(&self) -> RequestBuilder<'_, C, Arc> { RequestBuilder::from( @@ -50,6 +76,7 @@ impl + Clone> Program { self.cfg.cluster.url(), self.cfg.payer.clone(), self.cfg.options, + &self.internal_rpc_client, ) } @@ -98,6 +125,7 @@ impl<'a, C: Deref + Clone> RequestBuilder<'a, C, Arc, + rpc_client: &'a AsyncRpcClient, ) -> Self { Self { program_id, @@ -108,6 +136,7 @@ impl<'a, C: Deref + Clone> RequestBuilder<'a, C, Arc