client: Add option to pass in mock rpc client (#3053)

This commit is contained in:
cryptopapi997 2024-07-19 22:36:29 +02:00 committed by GitHub
parent bdee6fb544
commit f8d0f5208a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 100 additions and 48 deletions

View File

@ -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)). - 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: 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)). - 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 ### Fixes
@ -33,14 +34,15 @@ The minor version will be incremented upon a breaking change and the patch versi
### Breaking ### Breaking
- syn: Remove `bpf` target support in `hash` feature ([#3078](https://github.com/coral-xyz/anchor/pull/3078)). - 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])). - 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])). - 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 ## [0.30.1] - 2024-06-20
### Features ### 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: 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)) - 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)). - idl, ts: Add accounts resolution for associated token accounts ([#2927](https://github.com/coral-xyz/anchor/pull/2927)).

View File

@ -13,6 +13,7 @@ rustdoc-args = ["--cfg", "docsrs"]
[features] [features]
async = [] async = []
debug = [] debug = []
mock = []
[dependencies] [dependencies]
anchor-lang = { path = "../lang", version = "0.30.1" } anchor-lang = { path = "../lang", version = "0.30.1" }

View File

@ -116,7 +116,7 @@ pub async fn composite<C: Deref<Target = impl Signer> + Clone>(
&program.payer(), &program.payer(),
&dummy_a.pubkey(), &dummy_a.pubkey(),
program program
.async_rpc() .rpc()
.get_minimum_balance_for_rent_exemption(500) .get_minimum_balance_for_rent_exemption(500)
.await?, .await?,
500, 500,
@ -126,7 +126,7 @@ pub async fn composite<C: Deref<Target = impl Signer> + Clone>(
&program.payer(), &program.payer(),
&dummy_b.pubkey(), &dummy_b.pubkey(),
program program
.async_rpc() .rpc()
.get_minimum_balance_for_rent_exemption(500) .get_minimum_balance_for_rent_exemption(500)
.await?, .await?,
500, 500,
@ -307,7 +307,7 @@ pub async fn optional<C: Deref<Target = impl Signer> + Clone>(
&program.payer(), &program.payer(),
&required_keypair.pubkey(), &required_keypair.pubkey(),
program program
.async_rpc() .rpc()
.get_minimum_balance_for_rent_exemption(DataAccount::LEN) .get_minimum_balance_for_rent_exemption(DataAccount::LEN)
.await?, .await?,
DataAccount::LEN as u64, DataAccount::LEN as u64,

View File

@ -3,7 +3,12 @@ use crate::{
RequestBuilder, RequestBuilder,
}; };
use anchor_lang::{prelude::Pubkey, AccountDeserialize, Discriminator}; 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::{ use solana_sdk::{
commitment_config::CommitmentConfig, signature::Signature, signer::Signer, commitment_config::CommitmentConfig, signature::Signature, signer::Signer,
transaction::Transaction, transaction::Transaction,
@ -22,17 +27,42 @@ impl<'a> EventUnsubscriber<'a> {
} }
impl<C: Deref<Target = impl Signer> + Clone> Program<C> { impl<C: Deref<Target = impl Signer> + Clone> Program<C> {
pub fn new(program_id: Pubkey, cfg: Config<C>) -> Result<Self, ClientError> { pub fn new(
program_id: Pubkey,
cfg: Config<C>,
#[cfg(feature = "mock")] rpc_client: AsyncRpcClient,
) -> Result<Self, ClientError> {
let rt: tokio::runtime::Runtime = Builder::new_multi_thread().enable_all().build()?; 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 { Ok(Self {
program_id, program_id,
cfg, cfg,
sub_client: Arc::new(RwLock::new(None)), sub_client: Arc::new(RwLock::new(None)),
internal_rpc_client: rpc_client,
rt, 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. /// Returns a request builder.
pub fn request(&self) -> RequestBuilder<'_, C, Box<dyn Signer + '_>> { pub fn request(&self) -> RequestBuilder<'_, C, Box<dyn Signer + '_>> {
RequestBuilder::from( RequestBuilder::from(
@ -42,6 +72,7 @@ impl<C: Deref<Target = impl Signer> + Clone> Program<C> {
self.cfg.options, self.cfg.options,
#[cfg(not(feature = "async"))] #[cfg(not(feature = "async"))]
self.rt.handle(), self.rt.handle(),
&self.internal_rpc_client,
) )
} }
@ -89,6 +120,7 @@ impl<'a, C: Deref<Target = impl Signer> + Clone> RequestBuilder<'a, C, Box<dyn S
payer: C, payer: C,
options: Option<CommitmentConfig>, options: Option<CommitmentConfig>,
handle: &'a Handle, handle: &'a Handle,
rpc_client: &'a AsyncRpcClient,
) -> Self { ) -> Self {
Self { Self {
program_id, program_id,
@ -100,6 +132,7 @@ impl<'a, C: Deref<Target = impl Signer> + Clone> RequestBuilder<'a, C, Box<dyn S
instruction_data: None, instruction_data: None,
signers: Vec::new(), signers: Vec::new(),
handle, handle,
internal_rpc_client: rpc_client,
_phantom: PhantomData, _phantom: PhantomData,
} }
} }

View File

@ -66,6 +66,7 @@ use anchor_lang::{AccountDeserialize, Discriminator, InstructionData, ToAccountM
use futures::{Future, StreamExt}; use futures::{Future, StreamExt};
use regex::Regex; use regex::Regex;
use solana_account_decoder::UiAccountEncoding; use solana_account_decoder::UiAccountEncoding;
use solana_client::nonblocking::rpc_client::RpcClient as AsyncRpcClient;
use solana_client::rpc_config::{ use solana_client::rpc_config::{
RpcAccountInfoConfig, RpcProgramAccountsConfig, RpcSendTransactionConfig, RpcAccountInfoConfig, RpcProgramAccountsConfig, RpcSendTransactionConfig,
RpcTransactionLogsConfig, RpcTransactionLogsFilter, RpcTransactionLogsConfig, RpcTransactionLogsFilter,
@ -73,11 +74,7 @@ use solana_client::rpc_config::{
use solana_client::rpc_filter::{Memcmp, RpcFilterType}; use solana_client::rpc_filter::{Memcmp, RpcFilterType};
use solana_client::{ use solana_client::{
client_error::ClientError as SolanaClientError, client_error::ClientError as SolanaClientError,
nonblocking::{ nonblocking::pubsub_client::{PubsubClient, PubsubClientError},
pubsub_client::{PubsubClient, PubsubClientError},
rpc_client::RpcClient as AsyncRpcClient,
},
rpc_client::RpcClient,
rpc_response::{Response as RpcResponse, RpcLogsResponse}, rpc_response::{Response as RpcResponse, RpcLogsResponse},
}; };
use solana_sdk::account::Account; use solana_sdk::account::Account;
@ -146,14 +143,23 @@ impl<C: Clone + Deref<Target = impl Signer>> Client<C> {
} }
} }
pub fn program(&self, program_id: Pubkey) -> Result<Program<C>, ClientError> { pub fn program(
&self,
program_id: Pubkey,
#[cfg(feature = "mock")] rpc_client: AsyncRpcClient,
) -> Result<Program<C>, ClientError> {
let cfg = Config { let cfg = Config {
cluster: self.cfg.cluster.clone(), cluster: self.cfg.cluster.clone(),
options: self.cfg.options, options: self.cfg.options,
payer: self.cfg.payer.clone(), 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<C> {
sub_client: Arc<RwLock<Option<PubsubClient>>>, sub_client: Arc<RwLock<Option<PubsubClient>>>,
#[cfg(not(feature = "async"))] #[cfg(not(feature = "async"))]
rt: tokio::runtime::Runtime, rt: tokio::runtime::Runtime,
internal_rpc_client: AsyncRpcClient,
} }
impl<C: Deref<Target = impl Signer> + Clone> Program<C> { impl<C: Deref<Target = impl Signer> + Clone> Program<C> {
@ -231,29 +238,12 @@ impl<C: Deref<Target = impl Signer> + Clone> Program<C> {
self.program_id 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<T: AccountDeserialize>( async fn account_internal<T: AccountDeserialize>(
&self, &self,
address: Pubkey, address: Pubkey,
) -> Result<T, ClientError> { ) -> Result<T, ClientError> {
let rpc_client = AsyncRpcClient::new_with_commitment( let account = self
self.cfg.cluster.url().to_string(), .internal_rpc_client
self.cfg.options.unwrap_or_default(),
);
let account = rpc_client
.get_account_with_commitment(&address, CommitmentConfig::processed()) .get_account_with_commitment(&address, CommitmentConfig::processed())
.await? .await?
.value .value
@ -276,9 +266,10 @@ impl<C: Deref<Target = impl Signer> + Clone> Program<C> {
}, },
..RpcProgramAccountsConfig::default() ..RpcProgramAccountsConfig::default()
}; };
Ok(ProgramAccountsIterator { Ok(ProgramAccountsIterator {
inner: self inner: self
.async_rpc() .internal_rpc_client
.get_program_accounts_with_config(&self.id(), config) .get_program_accounts_with_config(&self.id(), config)
.await? .await?
.into_iter() .into_iter()
@ -514,6 +505,7 @@ pub struct RequestBuilder<'a, C, S: 'a> {
signers: Vec<S>, signers: Vec<S>,
#[cfg(not(feature = "async"))] #[cfg(not(feature = "async"))]
handle: &'a Handle, handle: &'a Handle,
internal_rpc_client: &'a AsyncRpcClient,
_phantom: PhantomData<&'a ()>, _phantom: PhantomData<&'a ()>,
} }
@ -631,21 +623,17 @@ impl<'a, C: Deref<Target = impl Signer> + Clone, S: AsSigner> RequestBuilder<'a,
} }
async fn signed_transaction_internal(&self) -> Result<Transaction, ClientError> { async fn signed_transaction_internal(&self) -> Result<Transaction, ClientError> {
let latest_hash = let latest_hash = self.internal_rpc_client.get_latest_blockhash().await?;
AsyncRpcClient::new_with_commitment(self.cluster.to_owned(), self.options)
.get_latest_blockhash()
.await?;
let tx = self.signed_transaction_with_blockhash(latest_hash)?;
let tx = self.signed_transaction_with_blockhash(latest_hash)?;
Ok(tx) Ok(tx)
} }
async fn send_internal(&self) -> Result<Signature, ClientError> { async fn send_internal(&self) -> Result<Signature, ClientError> {
let rpc_client = AsyncRpcClient::new_with_commitment(self.cluster.to_owned(), self.options); let latest_hash = self.internal_rpc_client.get_latest_blockhash().await?;
let latest_hash = rpc_client.get_latest_blockhash().await?;
let tx = self.signed_transaction_with_blockhash(latest_hash)?; let tx = self.signed_transaction_with_blockhash(latest_hash)?;
rpc_client self.internal_rpc_client
.send_and_confirm_transaction(&tx) .send_and_confirm_transaction(&tx)
.await .await
.map_err(Into::into) .map_err(Into::into)
@ -655,14 +643,13 @@ impl<'a, C: Deref<Target = impl Signer> + Clone, S: AsSigner> RequestBuilder<'a,
&self, &self,
config: RpcSendTransactionConfig, config: RpcSendTransactionConfig,
) -> Result<Signature, ClientError> { ) -> Result<Signature, ClientError> {
let rpc_client = AsyncRpcClient::new_with_commitment(self.cluster.to_owned(), self.options); let latest_hash = self.internal_rpc_client.get_latest_blockhash().await?;
let latest_hash = rpc_client.get_latest_blockhash().await?;
let tx = self.signed_transaction_with_blockhash(latest_hash)?; let tx = self.signed_transaction_with_blockhash(latest_hash)?;
rpc_client self.internal_rpc_client
.send_and_confirm_transaction_with_spinner_and_config( .send_and_confirm_transaction_with_spinner_and_config(
&tx, &tx,
rpc_client.commitment(), self.internal_rpc_client.commitment(),
config, config,
) )
.await .await

View File

@ -3,6 +3,7 @@ use crate::{
ProgramAccountsIterator, RequestBuilder, ProgramAccountsIterator, RequestBuilder,
}; };
use anchor_lang::{prelude::Pubkey, AccountDeserialize, Discriminator}; 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_client::{rpc_config::RpcSendTransactionConfig, rpc_filter::RpcFilterType};
use solana_sdk::{ use solana_sdk::{
commitment_config::CommitmentConfig, signature::Signature, signer::Signer, commitment_config::CommitmentConfig, signature::Signature, signer::Signer,
@ -35,14 +36,39 @@ impl AsSigner for Arc<dyn ThreadSafeSigner> {
} }
impl<C: Deref<Target = impl Signer> + Clone> Program<C> { impl<C: Deref<Target = impl Signer> + Clone> Program<C> {
pub fn new(program_id: Pubkey, cfg: Config<C>) -> Result<Self, ClientError> { pub fn new(
program_id: Pubkey,
cfg: Config<C>,
#[cfg(feature = "mock")] rpc_client: AsyncRpcClient,
) -> Result<Self, ClientError> {
#[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 { Ok(Self {
program_id, program_id,
cfg, cfg,
sub_client: Arc::new(RwLock::new(None)), 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 /// Returns a threadsafe request builder
pub fn request(&self) -> RequestBuilder<'_, C, Arc<dyn ThreadSafeSigner>> { pub fn request(&self) -> RequestBuilder<'_, C, Arc<dyn ThreadSafeSigner>> {
RequestBuilder::from( RequestBuilder::from(
@ -50,6 +76,7 @@ impl<C: Deref<Target = impl Signer> + Clone> Program<C> {
self.cfg.cluster.url(), self.cfg.cluster.url(),
self.cfg.payer.clone(), self.cfg.payer.clone(),
self.cfg.options, self.cfg.options,
&self.internal_rpc_client,
) )
} }
@ -98,6 +125,7 @@ impl<'a, C: Deref<Target = impl Signer> + Clone> RequestBuilder<'a, C, Arc<dyn T
cluster: &str, cluster: &str,
payer: C, payer: C,
options: Option<CommitmentConfig>, options: Option<CommitmentConfig>,
rpc_client: &'a AsyncRpcClient,
) -> Self { ) -> Self {
Self { Self {
program_id, program_id,
@ -108,6 +136,7 @@ impl<'a, C: Deref<Target = impl Signer> + Clone> RequestBuilder<'a, C, Arc<dyn T
instructions: Vec::new(), instructions: Vec::new(),
instruction_data: None, instruction_data: None,
signers: Vec::new(), signers: Vec::new(),
internal_rpc_client: rpc_client,
_phantom: PhantomData, _phantom: PhantomData,
} }
} }