Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add option to pass in rpc client when using anchor_client #3053

Merged
merged 14 commits into from
Jul 19, 2024
Merged
8 changes: 5 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -32,14 +33,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])).
acheroncrypto marked this conversation as resolved.
Show resolved Hide resolved
- 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)).
Expand Down
1 change: 1 addition & 0 deletions client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ rustdoc-args = ["--cfg", "docsrs"]
[features]
async = []
debug = []
mock = []

[dependencies]
anchor-lang = { path = "../lang", version = "0.30.1" }
Expand Down
6 changes: 3 additions & 3 deletions client/example/src/nonblocking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ pub async fn composite<C: Deref<Target = impl Signer> + Clone>(
&program.payer(),
&dummy_a.pubkey(),
program
.async_rpc()
.rpc()
.get_minimum_balance_for_rent_exemption(500)
.await?,
500,
Expand All @@ -126,7 +126,7 @@ pub async fn composite<C: Deref<Target = impl Signer> + Clone>(
&program.payer(),
&dummy_b.pubkey(),
program
.async_rpc()
.rpc()
.get_minimum_balance_for_rent_exemption(500)
.await?,
500,
Expand Down Expand Up @@ -307,7 +307,7 @@ pub async fn optional<C: Deref<Target = impl Signer> + Clone>(
&program.payer(),
&required_keypair.pubkey(),
program
.async_rpc()
.rpc()
.get_minimum_balance_for_rent_exemption(DataAccount::LEN)
.await?,
DataAccount::LEN as u64,
Expand Down
37 changes: 35 additions & 2 deletions client/src/blocking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -22,17 +27,42 @@ impl<'a> EventUnsubscriber<'a> {
}

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()?;

#[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<dyn Signer + '_>> {
RequestBuilder::from(
Expand All @@ -42,6 +72,7 @@ impl<C: Deref<Target = impl Signer> + Clone> Program<C> {
self.cfg.options,
#[cfg(not(feature = "async"))]
self.rt.handle(),
&self.internal_rpc_client,
)
}

Expand Down Expand Up @@ -89,6 +120,7 @@ impl<'a, C: Deref<Target = impl Signer> + Clone> RequestBuilder<'a, C, Box<dyn S
payer: C,
options: Option<CommitmentConfig>,
handle: &'a Handle,
rpc_client: &'a AsyncRpcClient,
) -> Self {
Self {
program_id,
Expand All @@ -100,6 +132,7 @@ impl<'a, C: Deref<Target = impl Signer> + Clone> RequestBuilder<'a, C, Box<dyn S
instruction_data: None,
signers: Vec::new(),
handle,
internal_rpc_client: rpc_client,
_phantom: PhantomData,
}
}
Expand Down
65 changes: 26 additions & 39 deletions client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,18 +66,15 @@ use anchor_lang::{AccountDeserialize, Discriminator, InstructionData, ToAccountM
use futures::{Future, StreamExt};
use regex::Regex;
use solana_account_decoder::UiAccountEncoding;
use solana_client::nonblocking::rpc_client::RpcClient as AsyncRpcClient;
use solana_client::rpc_config::{
RpcAccountInfoConfig, RpcProgramAccountsConfig, RpcSendTransactionConfig,
RpcTransactionLogsConfig, RpcTransactionLogsFilter,
};
use solana_client::rpc_filter::{Memcmp, RpcFilterType};
use solana_client::{
client_error::ClientError as SolanaClientError,
nonblocking::{
pubsub_client::{PubsubClient, PubsubClientError},
rpc_client::RpcClient as AsyncRpcClient,
},
rpc_client::RpcClient,
nonblocking::pubsub_client::{PubsubClient, PubsubClientError},
rpc_response::{Response as RpcResponse, RpcLogsResponse},
};
use solana_sdk::account::Account;
Expand Down Expand Up @@ -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 {
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,
)
}
}

Expand Down Expand Up @@ -220,6 +226,7 @@ pub struct Program<C> {
sub_client: Arc<RwLock<Option<PubsubClient>>>,
#[cfg(not(feature = "async"))]
rt: tokio::runtime::Runtime,
internal_rpc_client: AsyncRpcClient,
}

impl<C: Deref<Target = impl Signer> + Clone> Program<C> {
Expand All @@ -231,29 +238,12 @@ impl<C: Deref<Target = impl Signer> + Clone> Program<C> {
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(),
)
}
cryptopapi997 marked this conversation as resolved.
Show resolved Hide resolved

async fn account_internal<T: AccountDeserialize>(
&self,
address: Pubkey,
) -> Result<T, ClientError> {
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
Expand All @@ -276,9 +266,10 @@ impl<C: Deref<Target = impl Signer> + Clone> Program<C> {
},
..RpcProgramAccountsConfig::default()
};

Ok(ProgramAccountsIterator {
inner: self
.async_rpc()
.internal_rpc_client
.get_program_accounts_with_config(&self.id(), config)
.await?
.into_iter()
Expand Down Expand Up @@ -514,6 +505,7 @@ pub struct RequestBuilder<'a, C, S: 'a> {
signers: Vec<S>,
#[cfg(not(feature = "async"))]
handle: &'a Handle,
internal_rpc_client: &'a AsyncRpcClient,
_phantom: PhantomData<&'a ()>,
}

Expand Down Expand Up @@ -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> {
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<Signature, ClientError> {
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)
Expand All @@ -655,14 +643,13 @@ impl<'a, C: Deref<Target = impl Signer> + Clone, S: AsSigner> RequestBuilder<'a,
&self,
config: RpcSendTransactionConfig,
) -> Result<Signature, ClientError> {
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
Expand Down
31 changes: 30 additions & 1 deletion client/src/nonblocking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -35,21 +36,47 @@ impl AsSigner for Arc<dyn ThreadSafeSigner> {
}

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 {
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<dyn ThreadSafeSigner>> {
RequestBuilder::from(
self.program_id,
self.cfg.cluster.url(),
self.cfg.payer.clone(),
self.cfg.options,
&self.internal_rpc_client,
)
}

Expand Down Expand Up @@ -98,6 +125,7 @@ impl<'a, C: Deref<Target = impl Signer> + Clone> RequestBuilder<'a, C, Arc<dyn T
cluster: &str,
payer: C,
options: Option<CommitmentConfig>,
rpc_client: &'a AsyncRpcClient,
) -> Self {
Self {
program_id,
Expand All @@ -108,6 +136,7 @@ impl<'a, C: Deref<Target = impl Signer> + Clone> RequestBuilder<'a, C, Arc<dyn T
instructions: Vec::new(),
instruction_data: None,
signers: Vec::new(),
internal_rpc_client: rpc_client,
_phantom: PhantomData,
}
}
Expand Down
Loading