Эх сурвалжийг харах

client: Add `tokio` support to `RequestBuilder` with `async` feature (#3057)

Co-authored-by: acheron <98934430+acheroncrypto@users.noreply.github.com>
cryptopapi997 1 жил өмнө
parent
commit
f677742a97

+ 1 - 0
.gitignore

@@ -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

+ 1 - 0
CHANGELOG.md

@@ -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
 

+ 49 - 12
client/example/src/nonblocking.rs

@@ -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;
@@ -43,7 +43,7 @@ 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());
 
@@ -51,6 +51,7 @@ pub async fn main() -> Result<()> {
     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;
@@ -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,
@@ -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
@@ -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(),
@@ -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,
@@ -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.
@@ -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(),

+ 20 - 1
client/src/blocking.rs

@@ -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))
@@ -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,
@@ -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())
     }

+ 21 - 27
client/src/lib.rs

@@ -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};
@@ -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;
@@ -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
     }
@@ -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;
@@ -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 {
@@ -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,
         );
 

+ 36 - 3
client/src/nonblocking.rs

@@ -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};
@@ -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 {
@@ -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
@@ -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,
@@ -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
     }