Skip to content

Commit f76efd9

Browse files
client: Add tokio support to RequestBuilder with async feature (otter-sec#3057)
Co-authored-by: acheron <98934430+acheroncrypto@users.noreply.github.com>
1 parent c66628a commit f76efd9

6 files changed

Lines changed: 128 additions & 43 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ target/
1010
test-ledger
1111
examples/*/Cargo.lock
1212
examples/**/Cargo.lock
13+
*/example/Cargo.lock
1314
tests/*/Cargo.lock
1415
tests/**/Cargo.lock
1516
tests/*/yarn.lock

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ The minor version will be incremented upon a breaking change and the patch versi
2828
### Breaking
2929

3030
- syn: Remove `bpf` target support in `hash` feature ([#3078](https://github.com/coral-xyz/anchor/pull/3078)).
31+
- client: Add `tokio` support to `RequestBuilder` with `async` feature ([#3057](https://github.com/coral-xyz/anchor/pull/3057])).
3132

3233
## [0.30.1] - 2024-06-20
3334

client/example/src/nonblocking.rs

Lines changed: 49 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ use composite::instruction as composite_instruction;
2626
use composite::{DummyA, DummyB};
2727
use optional::account::{DataAccount, DataPda};
2828
use std::ops::Deref;
29-
use std::rc::Rc;
29+
use std::sync::Arc;
3030
use std::time::Duration;
3131
use tokio::sync::mpsc;
3232
use tokio::time::sleep;
@@ -43,14 +43,15 @@ pub async fn main() -> Result<()> {
4343
);
4444

4545
// Client.
46-
let payer = Rc::new(payer);
46+
let payer = Arc::new(payer);
4747
let client =
4848
Client::new_with_options(url.clone(), payer.clone(), CommitmentConfig::processed());
4949

5050
println!("\nStarting async test...");
5151
composite(&client, opts.composite_pid).await?;
5252
basic_2(&client, opts.basic_2_pid).await?;
5353
basic_4(&client, opts.basic_4_pid).await?;
54+
test_tokio(client, opts.basic_2_pid).await?;
5455

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

65+
pub async fn test_tokio(client: Client<Arc<Keypair>>, pid: Pubkey) -> Result<()> {
66+
tokio::spawn(async move {
67+
let program = client.program(pid).unwrap();
68+
69+
// `Create` parameters.
70+
let counter = Arc::new(Keypair::new());
71+
let counter_pubkey = counter.pubkey();
72+
let authority = program.payer();
73+
74+
// Build and send a transaction.
75+
program
76+
.request()
77+
.signer(counter)
78+
.accounts(basic_2_accounts::Create {
79+
counter: counter_pubkey,
80+
user: authority,
81+
system_program: system_program::ID,
82+
})
83+
.args(basic_2_instruction::Create { authority })
84+
.send()
85+
.await
86+
.unwrap();
87+
88+
let counter_account: Counter = program.account(counter_pubkey).await.unwrap();
89+
90+
assert_eq!(counter_account.authority, authority);
91+
assert_eq!(counter_account.count, 0);
92+
})
93+
.await
94+
.unwrap();
95+
96+
println!("Tokio success!");
97+
98+
Ok(())
99+
}
100+
64101
pub async fn composite<C: Deref<Target = impl Signer> + Clone>(
65102
client: &Client<C>,
66103
pid: Pubkey,
@@ -69,8 +106,8 @@ pub async fn composite<C: Deref<Target = impl Signer> + Clone>(
69106
let program = client.program(pid)?;
70107

71108
// `Initialize` parameters.
72-
let dummy_a = Keypair::new();
73-
let dummy_b = Keypair::new();
109+
let dummy_a = Arc::new(Keypair::new());
110+
let dummy_b = Arc::new(Keypair::new());
74111

75112
// Build and send a transaction.
76113
program
@@ -95,8 +132,8 @@ pub async fn composite<C: Deref<Target = impl Signer> + Clone>(
95132
500,
96133
&program.id(),
97134
))
98-
.signer(&dummy_a)
99-
.signer(&dummy_b)
135+
.signer(dummy_a.clone())
136+
.signer(dummy_b.clone())
100137
.accounts(Initialize {
101138
dummy_a: dummy_a.pubkey(),
102139
dummy_b: dummy_b.pubkey(),
@@ -147,13 +184,13 @@ pub async fn basic_2<C: Deref<Target = impl Signer> + Clone>(
147184
let program = client.program(pid)?;
148185

149186
// `Create` parameters.
150-
let counter = Keypair::new();
187+
let counter = Arc::new(Keypair::new());
151188
let authority = program.payer();
152189

153190
// Build and send a transaction.
154191
program
155192
.request()
156-
.signer(&counter)
193+
.signer(counter.clone())
157194
.accounts(basic_2_accounts::Create {
158195
counter: counter.pubkey(),
159196
user: authority,
@@ -253,13 +290,13 @@ pub async fn optional<C: Deref<Target = impl Signer> + Clone>(
253290
let program = client.program(pid)?;
254291

255292
// `Initialize` parameters.
256-
let data_account_keypair = Keypair::new();
293+
let data_account_keypair = Arc::new(Keypair::new());
257294

258295
let data_account_key = data_account_keypair.pubkey();
259296

260297
let data_pda_seeds = &[DataPda::PREFIX.as_ref(), data_account_key.as_ref()];
261298
let data_pda_key = Pubkey::find_program_address(data_pda_seeds, &pid).0;
262-
let required_keypair = Keypair::new();
299+
let required_keypair = Arc::new(Keypair::new());
263300
let value: u64 = 10;
264301

265302
// Build and send a transaction.
@@ -276,8 +313,8 @@ pub async fn optional<C: Deref<Target = impl Signer> + Clone>(
276313
DataAccount::LEN as u64,
277314
&program.id(),
278315
))
279-
.signer(&data_account_keypair)
280-
.signer(&required_keypair)
316+
.signer(data_account_keypair.clone())
317+
.signer(required_keypair.clone())
281318
.accounts(OptionalInitialize {
282319
payer: Some(program.payer()),
283320
required: required_keypair.pubkey(),

client/src/blocking.rs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,18 @@ impl<C: Deref<Target = impl Signer> + Clone> Program<C> {
3333
})
3434
}
3535

36+
/// Returns a request builder.
37+
pub fn request(&self) -> RequestBuilder<'_, C, Box<dyn Signer + '_>> {
38+
RequestBuilder::from(
39+
self.program_id,
40+
self.cfg.cluster.url(),
41+
self.cfg.payer.clone(),
42+
self.cfg.options,
43+
#[cfg(not(feature = "async"))]
44+
self.rt.handle(),
45+
)
46+
}
47+
3648
/// Returns the account at the given address.
3749
pub fn account<T: AccountDeserialize>(&self, address: Pubkey) -> Result<T, ClientError> {
3850
self.rt.block_on(self.account_internal(address))
@@ -70,7 +82,7 @@ impl<C: Deref<Target = impl Signer> + Clone> Program<C> {
7082
}
7183
}
7284

73-
impl<'a, C: Deref<Target = impl Signer> + Clone> RequestBuilder<'a, C> {
85+
impl<'a, C: Deref<Target = impl Signer> + Clone> RequestBuilder<'a, C, Box<dyn Signer + 'a>> {
7486
pub fn from(
7587
program_id: Pubkey,
7688
cluster: &str,
@@ -88,9 +100,16 @@ impl<'a, C: Deref<Target = impl Signer> + Clone> RequestBuilder<'a, C> {
88100
instruction_data: None,
89101
signers: Vec::new(),
90102
handle,
103+
_phantom: PhantomData,
91104
}
92105
}
93106

107+
#[must_use]
108+
pub fn signer<T: Signer + 'a>(mut self, signer: T) -> Self {
109+
self.signers.push(Box::new(signer));
110+
self
111+
}
112+
94113
pub fn signed_transaction(&self) -> Result<Transaction, ClientError> {
95114
self.handle.block_on(self.signed_transaction_internal())
96115
}

client/src/lib.rs

Lines changed: 21 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,6 @@
6060
//! anchor-client = { version = "0.30.1 ", features = ["async"] }
6161
//! ````
6262
63-
use anchor_lang::solana_program::hash::Hash;
64-
use anchor_lang::solana_program::instruction::{AccountMeta, Instruction};
6563
use anchor_lang::solana_program::program_error::ProgramError;
6664
use anchor_lang::solana_program::pubkey::Pubkey;
6765
use anchor_lang::{AccountDeserialize, Discriminator, InstructionData, ToAccountMetas};
@@ -84,6 +82,8 @@ use solana_client::{
8482
};
8583
use solana_sdk::account::Account;
8684
use solana_sdk::commitment_config::CommitmentConfig;
85+
use solana_sdk::hash::Hash;
86+
use solana_sdk::instruction::{AccountMeta, Instruction};
8787
use solana_sdk::signature::{Signature, Signer};
8888
use solana_sdk::transaction::Transaction;
8989
use std::iter::Map;
@@ -227,18 +227,6 @@ impl<C: Deref<Target = impl Signer> + Clone> Program<C> {
227227
self.cfg.payer.pubkey()
228228
}
229229

230-
/// Returns a request builder.
231-
pub fn request(&self) -> RequestBuilder<C> {
232-
RequestBuilder::from(
233-
self.program_id,
234-
self.cfg.cluster.url(),
235-
self.cfg.payer.clone(),
236-
self.cfg.options,
237-
#[cfg(not(feature = "async"))]
238-
self.rt.handle(),
239-
)
240-
}
241-
242230
pub fn id(&self) -> Pubkey {
243231
self.program_id
244232
}
@@ -503,23 +491,34 @@ pub enum ClientError {
503491
IOError(#[from] std::io::Error),
504492
}
505493

494+
pub trait AsSigner {
495+
fn as_signer(&self) -> &dyn Signer;
496+
}
497+
498+
impl<'a> AsSigner for Box<dyn Signer + 'a> {
499+
fn as_signer(&self) -> &dyn Signer {
500+
self.as_ref()
501+
}
502+
}
503+
506504
/// `RequestBuilder` provides a builder interface to create and send
507505
/// transactions to a cluster.
508-
pub struct RequestBuilder<'a, C> {
506+
pub struct RequestBuilder<'a, C, S: 'a> {
509507
cluster: String,
510508
program_id: Pubkey,
511509
accounts: Vec<AccountMeta>,
512510
options: CommitmentConfig,
513511
instructions: Vec<Instruction>,
514512
payer: C,
515-
// Serialized instruction data for the target RPC.
516513
instruction_data: Option<Vec<u8>>,
517-
signers: Vec<&'a dyn Signer>,
514+
signers: Vec<S>,
518515
#[cfg(not(feature = "async"))]
519516
handle: &'a Handle,
517+
_phantom: PhantomData<&'a ()>,
520518
}
521519

522-
impl<'a, C: Deref<Target = impl Signer> + Clone> RequestBuilder<'a, C> {
520+
// Shared implementation for all RequestBuilders
521+
impl<'a, C: Deref<Target = impl Signer> + Clone, S: AsSigner> RequestBuilder<'a, C, S> {
523522
#[must_use]
524523
pub fn payer(mut self, payer: C) -> Self {
525524
self.payer = payer;
@@ -593,12 +592,6 @@ impl<'a, C: Deref<Target = impl Signer> + Clone> RequestBuilder<'a, C> {
593592
self
594593
}
595594

596-
#[must_use]
597-
pub fn signer(mut self, signer: &'a dyn Signer) -> Self {
598-
self.signers.push(signer);
599-
self
600-
}
601-
602595
pub fn instructions(&self) -> Result<Vec<Instruction>, ClientError> {
603596
let mut instructions = self.instructions.clone();
604597
if let Some(ix_data) = &self.instruction_data {
@@ -617,13 +610,14 @@ impl<'a, C: Deref<Target = impl Signer> + Clone> RequestBuilder<'a, C> {
617610
latest_hash: Hash,
618611
) -> Result<Transaction, ClientError> {
619612
let instructions = self.instructions()?;
620-
let mut signers = self.signers.clone();
621-
signers.push(&*self.payer);
613+
let signers: Vec<&dyn Signer> = self.signers.iter().map(|s| s.as_signer()).collect();
614+
let mut all_signers = signers;
615+
all_signers.push(&*self.payer);
622616

623617
let tx = Transaction::new_signed_with_payer(
624618
&instructions,
625619
Some(&self.payer.pubkey()),
626-
&signers,
620+
&all_signers,
627621
latest_hash,
628622
);
629623

client/src/nonblocking.rs

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use crate::{
2-
ClientError, Config, EventContext, EventUnsubscriber, Program, ProgramAccountsIterator,
3-
RequestBuilder,
2+
AsSigner, ClientError, Config, EventContext, EventUnsubscriber, Program,
3+
ProgramAccountsIterator, RequestBuilder,
44
};
55
use anchor_lang::{prelude::Pubkey, AccountDeserialize, Discriminator};
66
use solana_client::{rpc_config::RpcSendTransactionConfig, rpc_filter::RpcFilterType};
@@ -18,6 +18,22 @@ impl<'a> EventUnsubscriber<'a> {
1818
}
1919
}
2020

21+
pub trait ThreadSafeSigner: Signer + Send + Sync + 'static {
22+
fn to_signer(&self) -> &dyn Signer;
23+
}
24+
25+
impl<T: Signer + Send + Sync + 'static> ThreadSafeSigner for T {
26+
fn to_signer(&self) -> &dyn Signer {
27+
self
28+
}
29+
}
30+
31+
impl AsSigner for Arc<dyn ThreadSafeSigner> {
32+
fn as_signer(&self) -> &dyn Signer {
33+
self.to_signer()
34+
}
35+
}
36+
2137
impl<C: Deref<Target = impl Signer> + Clone> Program<C> {
2238
pub fn new(program_id: Pubkey, cfg: Config<C>) -> Result<Self, ClientError> {
2339
Ok(Self {
@@ -27,6 +43,16 @@ impl<C: Deref<Target = impl Signer> + Clone> Program<C> {
2743
})
2844
}
2945

46+
/// Returns a threadsafe request builder
47+
pub fn request(&self) -> RequestBuilder<'_, C, Arc<dyn ThreadSafeSigner>> {
48+
RequestBuilder::from(
49+
self.program_id,
50+
self.cfg.cluster.url(),
51+
self.cfg.payer.clone(),
52+
self.cfg.options,
53+
)
54+
}
55+
3056
/// Returns the account at the given address.
3157
pub async fn account<T: AccountDeserialize>(&self, address: Pubkey) -> Result<T, ClientError> {
3258
self.account_internal(address).await
@@ -66,7 +92,7 @@ impl<C: Deref<Target = impl Signer> + Clone> Program<C> {
6692
}
6793
}
6894

69-
impl<'a, C: Deref<Target = impl Signer> + Clone> RequestBuilder<'a, C> {
95+
impl<'a, C: Deref<Target = impl Signer> + Clone> RequestBuilder<'a, C, Arc<dyn ThreadSafeSigner>> {
7096
pub fn from(
7197
program_id: Pubkey,
7298
cluster: &str,
@@ -82,9 +108,16 @@ impl<'a, C: Deref<Target = impl Signer> + Clone> RequestBuilder<'a, C> {
82108
instructions: Vec::new(),
83109
instruction_data: None,
84110
signers: Vec::new(),
111+
_phantom: PhantomData,
85112
}
86113
}
87114

115+
#[must_use]
116+
pub fn signer<T: ThreadSafeSigner>(mut self, signer: T) -> Self {
117+
self.signers.push(Arc::new(signer));
118+
self
119+
}
120+
88121
pub async fn signed_transaction(&self) -> Result<Transaction, ClientError> {
89122
self.signed_transaction_internal().await
90123
}

0 commit comments

Comments
 (0)