Skip to content

Commit

Permalink
client: Add tokio support to RequestBuilder with async feature (#…
Browse files Browse the repository at this point in the history
…3057)

Co-authored-by: acheron <[email protected]>
  • Loading branch information
cryptopapi997 and acheroncrypto authored Jul 12, 2024
1 parent fd6174a commit f677742
Show file tree
Hide file tree
Showing 6 changed files with 128 additions and 43 deletions.
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ 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])).

## [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

0 comments on commit f677742

Please sign in to comment.