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 tokio support to anchor_client RequestBuilder #3057

Merged
merged 12 commits into from
Jul 12, 2024
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ target/
test-ledger
examples/*/Cargo.lock
examples/**/Cargo.lock
*/example/Cargo.lock
tests/*/Cargo.lock
tests/**/Cargo.lock
tests/*/yarn.lock
Expand Down
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@ The minor version will be incremented upon a breaking change and the patch versi
## [Unreleased]

### Features

cryptopapi997 marked this conversation as resolved.
Show resolved Hide resolved
- ts: Add optional `commitment` parameter to `Program.addEventListener` ([#3052](https://github.com/coral-xyz/anchor/pull/3052)).

### Fixes

- idl: Make safety comment checks fail silently when program path env is not set ([#3045](https://github.com/coral-xyz/anchor/pull/3045])).

### Breaking
- lang: Add tokio support to `RequestBuilder` to `async` feature in `anchor_client` ([#3057](https://github.com/coral-xyz/anchor/pull/3057])).
cryptopapi997 marked this conversation as resolved.
Show resolved Hide resolved

## [0.30.1] - 2024-06-20

Expand Down
61 changes: 49 additions & 12 deletions client/example/src/nonblocking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ use composite::instruction as composite_instruction;
use composite::{DummyA, DummyB};
use optional::account::{DataAccount, DataPda};
use std::ops::Deref;
use std::rc::Rc;
use std::sync::Arc;
use std::time::Duration;
use tokio::sync::mpsc;
use tokio::time::sleep;
Expand All @@ -43,14 +43,15 @@ pub async fn main() -> Result<()> {
);

// Client.
let payer = Rc::new(payer);
let payer = Arc::new(payer);
let client =
Client::new_with_options(url.clone(), payer.clone(), CommitmentConfig::processed());

println!("\nStarting async test...");
composite(&client, opts.composite_pid).await?;
basic_2(&client, opts.basic_2_pid).await?;
basic_4(&client, opts.basic_4_pid).await?;
test_tokio(client, opts.basic_2_pid).await?;

// Can also use references, since they deref to a signer
let payer: &Keypair = &payer;
Expand All @@ -61,6 +62,42 @@ pub async fn main() -> Result<()> {
Ok(())
}

pub async fn test_tokio(client: Client<Arc<Keypair>>, pid: Pubkey) -> Result<()> {
tokio::spawn(async move {
let program = client.program(pid).unwrap();

// `Create` parameters.
let counter = Arc::new(Keypair::new());
let counter_pubkey = counter.pubkey();
let authority = program.payer();

// Build and send a transaction.
program
.request()
.signer(counter)
.accounts(basic_2_accounts::Create {
counter: counter_pubkey,
user: authority,
system_program: system_program::ID,
})
.args(basic_2_instruction::Create { authority })
.send()
.await
.unwrap();

let counter_account: Counter = program.account(counter_pubkey).await.unwrap();

assert_eq!(counter_account.authority, authority);
assert_eq!(counter_account.count, 0);
})
.await
.unwrap();

println!("Tokio success!");

Ok(())
}

pub async fn composite<C: Deref<Target = impl Signer> + Clone>(
client: &Client<C>,
pid: Pubkey,
Expand All @@ -69,8 +106,8 @@ pub async fn composite<C: Deref<Target = impl Signer> + Clone>(
let program = client.program(pid)?;

// `Initialize` parameters.
let dummy_a = Keypair::new();
let dummy_b = Keypair::new();
let dummy_a = Arc::new(Keypair::new());
let dummy_b = Arc::new(Keypair::new());

// Build and send a transaction.
program
Expand All @@ -95,8 +132,8 @@ pub async fn composite<C: Deref<Target = impl Signer> + Clone>(
500,
&program.id(),
))
.signer(&dummy_a)
.signer(&dummy_b)
.signer(dummy_a.clone())
.signer(dummy_b.clone())
.accounts(Initialize {
dummy_a: dummy_a.pubkey(),
dummy_b: dummy_b.pubkey(),
Expand Down Expand Up @@ -147,13 +184,13 @@ pub async fn basic_2<C: Deref<Target = impl Signer> + Clone>(
let program = client.program(pid)?;

// `Create` parameters.
let counter = Keypair::new();
let counter = Arc::new(Keypair::new());
let authority = program.payer();

// Build and send a transaction.
program
.request()
.signer(&counter)
.signer(counter.clone())
.accounts(basic_2_accounts::Create {
counter: counter.pubkey(),
user: authority,
Expand Down Expand Up @@ -253,13 +290,13 @@ pub async fn optional<C: Deref<Target = impl Signer> + Clone>(
let program = client.program(pid)?;

// `Initialize` parameters.
let data_account_keypair = Keypair::new();
let data_account_keypair = Arc::new(Keypair::new());

let data_account_key = data_account_keypair.pubkey();

let data_pda_seeds = &[DataPda::PREFIX.as_ref(), data_account_key.as_ref()];
let data_pda_key = Pubkey::find_program_address(data_pda_seeds, &pid).0;
let required_keypair = Keypair::new();
let required_keypair = Arc::new(Keypair::new());
let value: u64 = 10;

// Build and send a transaction.
Expand All @@ -276,8 +313,8 @@ pub async fn optional<C: Deref<Target = impl Signer> + Clone>(
DataAccount::LEN as u64,
&program.id(),
))
.signer(&data_account_keypair)
.signer(&required_keypair)
.signer(data_account_keypair.clone())
.signer(required_keypair.clone())
.accounts(OptionalInitialize {
payer: Some(program.payer()),
required: required_keypair.pubkey(),
Expand Down
21 changes: 20 additions & 1 deletion client/src/blocking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,18 @@ impl<C: Deref<Target = impl Signer> + Clone> Program<C> {
})
}

/// Returns a request builder.
pub fn request(&self) -> RequestBuilder<'_, C, Box<dyn Signer + '_>> {
RequestBuilder::from(
self.program_id,
self.cfg.cluster.url(),
self.cfg.payer.clone(),
self.cfg.options,
#[cfg(not(feature = "async"))]
self.rt.handle(),
)
}

/// Returns the account at the given address.
pub fn account<T: AccountDeserialize>(&self, address: Pubkey) -> Result<T, ClientError> {
self.rt.block_on(self.account_internal(address))
Expand Down Expand Up @@ -70,7 +82,7 @@ impl<C: Deref<Target = impl Signer> + Clone> Program<C> {
}
}

impl<'a, C: Deref<Target = impl Signer> + Clone> RequestBuilder<'a, C> {
impl<'a, C: Deref<Target = impl Signer> + Clone> RequestBuilder<'a, C, Box<dyn Signer + 'a>> {
pub fn from(
program_id: Pubkey,
cluster: &str,
Expand All @@ -88,9 +100,16 @@ impl<'a, C: Deref<Target = impl Signer> + Clone> RequestBuilder<'a, C> {
instruction_data: None,
signers: Vec::new(),
handle,
_phantom: PhantomData,
}
}

#[must_use]
pub fn signer<T: Signer + 'a>(mut self, signer: T) -> Self {
self.signers.push(Box::new(signer));
self
}

pub fn signed_transaction(&self) -> Result<Transaction, ClientError> {
self.handle.block_on(self.signed_transaction_internal())
}
Expand Down
48 changes: 21 additions & 27 deletions client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,6 @@
//! anchor-client = { version = "0.30.1 ", features = ["async"] }
//! ````

use anchor_lang::solana_program::hash::Hash;
use anchor_lang::solana_program::instruction::{AccountMeta, Instruction};
use anchor_lang::solana_program::program_error::ProgramError;
use anchor_lang::solana_program::pubkey::Pubkey;
use anchor_lang::{AccountDeserialize, Discriminator, InstructionData, ToAccountMetas};
Expand All @@ -84,6 +82,8 @@ use solana_client::{
};
use solana_sdk::account::Account;
use solana_sdk::commitment_config::CommitmentConfig;
use solana_sdk::hash::Hash;
use solana_sdk::instruction::{AccountMeta, Instruction};
use solana_sdk::signature::{Signature, Signer};
use solana_sdk::transaction::Transaction;
use std::iter::Map;
Expand Down Expand Up @@ -227,18 +227,6 @@ impl<C: Deref<Target = impl Signer> + Clone> Program<C> {
self.cfg.payer.pubkey()
}

/// Returns a request builder.
pub fn request(&self) -> RequestBuilder<C> {
RequestBuilder::from(
self.program_id,
self.cfg.cluster.url(),
self.cfg.payer.clone(),
self.cfg.options,
#[cfg(not(feature = "async"))]
self.rt.handle(),
)
}

pub fn id(&self) -> Pubkey {
self.program_id
}
Expand Down Expand Up @@ -503,23 +491,34 @@ pub enum ClientError {
IOError(#[from] std::io::Error),
}

pub trait AsSigner {
fn as_signer(&self) -> &dyn Signer;
}

impl<'a> AsSigner for Box<dyn Signer + 'a> {
fn as_signer(&self) -> &dyn Signer {
self.as_ref()
}
}

/// `RequestBuilder` provides a builder interface to create and send
/// transactions to a cluster.
pub struct RequestBuilder<'a, C> {
pub struct RequestBuilder<'a, C, S: 'a> {
cluster: String,
program_id: Pubkey,
accounts: Vec<AccountMeta>,
options: CommitmentConfig,
instructions: Vec<Instruction>,
payer: C,
// Serialized instruction data for the target RPC.
instruction_data: Option<Vec<u8>>,
signers: Vec<&'a dyn Signer>,
signers: Vec<S>,
#[cfg(not(feature = "async"))]
handle: &'a Handle,
_phantom: PhantomData<&'a ()>,
}

impl<'a, C: Deref<Target = impl Signer> + Clone> RequestBuilder<'a, C> {
// Shared implementation for all RequestBuilders
impl<'a, C: Deref<Target = impl Signer> + Clone, S: AsSigner> RequestBuilder<'a, C, S> {
#[must_use]
pub fn payer(mut self, payer: C) -> Self {
self.payer = payer;
Expand Down Expand Up @@ -593,12 +592,6 @@ impl<'a, C: Deref<Target = impl Signer> + Clone> RequestBuilder<'a, C> {
self
}

#[must_use]
pub fn signer(mut self, signer: &'a dyn Signer) -> Self {
self.signers.push(signer);
self
}

pub fn instructions(&self) -> Result<Vec<Instruction>, ClientError> {
let mut instructions = self.instructions.clone();
if let Some(ix_data) = &self.instruction_data {
Expand All @@ -617,13 +610,14 @@ impl<'a, C: Deref<Target = impl Signer> + Clone> RequestBuilder<'a, C> {
latest_hash: Hash,
) -> Result<Transaction, ClientError> {
let instructions = self.instructions()?;
let mut signers = self.signers.clone();
signers.push(&*self.payer);
let signers: Vec<&dyn Signer> = self.signers.iter().map(|s| s.as_signer()).collect();
let mut all_signers = signers;
all_signers.push(&*self.payer);

let tx = Transaction::new_signed_with_payer(
&instructions,
Some(&self.payer.pubkey()),
&signers,
&all_signers,
latest_hash,
);

Expand Down
39 changes: 36 additions & 3 deletions client/src/nonblocking.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::{
ClientError, Config, EventContext, EventUnsubscriber, Program, ProgramAccountsIterator,
RequestBuilder,
AsSigner, ClientError, Config, EventContext, EventUnsubscriber, Program,
ProgramAccountsIterator, RequestBuilder,
};
use anchor_lang::{prelude::Pubkey, AccountDeserialize, Discriminator};
use solana_client::{rpc_config::RpcSendTransactionConfig, rpc_filter::RpcFilterType};
Expand All @@ -18,6 +18,22 @@ impl<'a> EventUnsubscriber<'a> {
}
}

pub trait ThreadSafeSigner: Signer + Send + Sync + 'static {
fn to_signer(&self) -> &dyn Signer;
}

impl<T: Signer + Send + Sync + 'static> ThreadSafeSigner for T {
fn to_signer(&self) -> &dyn Signer {
self
}
}

impl AsSigner for Arc<dyn ThreadSafeSigner> {
fn as_signer(&self) -> &dyn Signer {
self.to_signer()
}
}

impl<C: Deref<Target = impl Signer> + Clone> Program<C> {
pub fn new(program_id: Pubkey, cfg: Config<C>) -> Result<Self, ClientError> {
Ok(Self {
Expand All @@ -27,6 +43,16 @@ impl<C: Deref<Target = impl Signer> + Clone> Program<C> {
})
}

/// 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,
)
}

/// Returns the account at the given address.
pub async fn account<T: AccountDeserialize>(&self, address: Pubkey) -> Result<T, ClientError> {
self.account_internal(address).await
Expand Down Expand Up @@ -66,7 +92,7 @@ impl<C: Deref<Target = impl Signer> + Clone> Program<C> {
}
}

impl<'a, C: Deref<Target = impl Signer> + Clone> RequestBuilder<'a, C> {
impl<'a, C: Deref<Target = impl Signer> + Clone> RequestBuilder<'a, C, Arc<dyn ThreadSafeSigner>> {
pub fn from(
program_id: Pubkey,
cluster: &str,
Expand All @@ -82,9 +108,16 @@ impl<'a, C: Deref<Target = impl Signer> + Clone> RequestBuilder<'a, C> {
instructions: Vec::new(),
instruction_data: None,
signers: Vec::new(),
_phantom: PhantomData,
}
}

#[must_use]
pub fn signer<T: ThreadSafeSigner>(mut self, signer: T) -> Self {
self.signers.push(Arc::new(signer));
self
}

pub async fn signed_transaction(&self) -> Result<Transaction, ClientError> {
self.signed_transaction_internal().await
}
Expand Down
Loading