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: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ The minor version will be incremented upon a breaking change and the patch versi
## [Unreleased]

### Features
- lang: Add tokio support with `request_threadsafe` to `anchor_client` ([#3057](https://github.com/coral-xyz/anchor/pull/3057])).


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)).

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
86 changes: 83 additions & 3 deletions client/src/blocking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@ use crate::{
RequestBuilder,
};
use anchor_lang::{prelude::Pubkey, AccountDeserialize, Discriminator};
use solana_client::{rpc_config::RpcSendTransactionConfig, rpc_filter::RpcFilterType};
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,
commitment_config::CommitmentConfig, hash::Hash, signature::Signature, signer::Signer,
transaction::Transaction,
};
use std::{marker::PhantomData, ops::Deref, sync::Arc};
Expand Down Expand Up @@ -33,6 +36,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 +85,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 +103,74 @@ 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_with_blockhash(
&self,
latest_hash: Hash,
) -> Result<Transaction, ClientError> {
let instructions = self.instructions()?;
let signers: Vec<&dyn Signer> = self.signers.iter().map(|s| s.as_ref()).collect();
let mut all_signers = signers;
all_signers.push(&*self.payer);

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

Ok(tx)
}

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

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 tx = self.signed_transaction_with_blockhash(latest_hash)?;

rpc_client
.send_and_confirm_transaction(&tx)
.await
.map_err(Into::into)
}

async fn send_with_spinner_and_config_internal(
&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 tx = self.signed_transaction_with_blockhash(latest_hash)?;

rpc_client
.send_and_confirm_transaction_with_spinner_and_config(
&tx,
rpc_client.commitment(),
config,
)
.await
.map_err(Into::into)
}
acheroncrypto marked this conversation as resolved.
Show resolved Hide resolved

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