Przeglądaj źródła

client: add typed getProgramAccounts support (#1297)

Drew Nutter 3 lat temu
rodzic
commit
a8f820c391
3 zmienionych plików z 69 dodań i 2 usunięć
  1. 1 0
      Cargo.lock
  2. 1 0
      client/Cargo.toml
  3. 67 2
      client/src/lib.rs

+ 1 - 0
Cargo.lock

@@ -183,6 +183,7 @@ dependencies = [
  "anyhow",
  "regex",
  "serde",
+ "solana-account-decoder",
  "solana-client",
  "solana-sdk",
  "thiserror",

+ 1 - 0
client/Cargo.toml

@@ -16,5 +16,6 @@ regex = "1.4.5"
 serde = { version = "1.0.122", features = ["derive"] }
 solana-client = "1.7.2"
 solana-sdk = "1.7.2"
+solana-account-decoder = "1.7.2"
 thiserror = "1.0.20"
 url = "2.2.2"

+ 67 - 2
client/src/lib.rs

@@ -5,18 +5,27 @@ 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::solana_program::system_program;
-use anchor_lang::{AccountDeserialize, InstructionData, ToAccountMetas};
+use anchor_lang::{AccountDeserialize, Discriminator, InstructionData, ToAccountMetas};
 use regex::Regex;
+use solana_account_decoder::UiAccountEncoding;
 use solana_client::client_error::ClientError as SolanaClientError;
 use solana_client::pubsub_client::{PubsubClient, PubsubClientError, PubsubClientSubscription};
 use solana_client::rpc_client::RpcClient;
-use solana_client::rpc_config::{RpcTransactionLogsConfig, RpcTransactionLogsFilter};
+use solana_client::rpc_config::{
+    RpcAccountInfoConfig, RpcProgramAccountsConfig, RpcTransactionLogsConfig,
+    RpcTransactionLogsFilter,
+};
+use solana_client::rpc_filter::{Memcmp, MemcmpEncodedBytes, RpcFilterType};
 use solana_client::rpc_response::{Response as RpcResponse, RpcLogsResponse};
+use solana_sdk::account::Account;
+use solana_sdk::bs58;
 use solana_sdk::commitment_config::CommitmentConfig;
 use solana_sdk::signature::{Signature, Signer};
 use solana_sdk::transaction::Transaction;
 use std::convert::Into;
+use std::iter::Map;
 use std::rc::Rc;
+use std::vec::IntoIter;
 use thiserror::Error;
 
 pub use anchor_lang;
@@ -129,6 +138,45 @@ impl Program {
         T::try_deserialize(&mut data).map_err(Into::into)
     }
 
+    /// Returns all program accounts of the given type matching the given filters
+    pub fn accounts<T: AccountDeserialize + Discriminator>(
+        &self,
+        filters: Vec<RpcFilterType>,
+    ) -> Result<Vec<(Pubkey, T)>, ClientError> {
+        self.accounts_lazy(filters)?.collect()
+    }
+
+    /// Returns all program accounts of the given type matching the given filters as an iterator
+    /// Deserialization is executed lazily
+    pub fn accounts_lazy<T: AccountDeserialize + Discriminator>(
+        &self,
+        filters: Vec<RpcFilterType>,
+    ) -> Result<ProgramAccountsIterator<T>, ClientError> {
+        let account_type_filter = RpcFilterType::Memcmp(Memcmp {
+            offset: 0,
+            bytes: MemcmpEncodedBytes::Base58(bs58::encode(T::discriminator()).into_string()),
+            encoding: None,
+        });
+        let config = RpcProgramAccountsConfig {
+            filters: Some([vec![account_type_filter], filters].concat()),
+            account_config: RpcAccountInfoConfig {
+                encoding: Some(UiAccountEncoding::Base64),
+                data_slice: None,
+                commitment: None,
+            },
+            with_context: None,
+        };
+        Ok(ProgramAccountsIterator {
+            inner: self
+                .rpc()
+                .get_program_accounts_with_config(&self.id(), config)?
+                .into_iter()
+                .map(|(key, account)| {
+                    Ok((key, T::try_deserialize(&mut (&account.data as &[u8]))?))
+                }),
+        })
+    }
+
     pub fn state<T: AccountDeserialize>(&self) -> Result<T, ClientError> {
         self.account(anchor_lang::__private::state::address(&self.program_id))
     }
@@ -209,6 +257,23 @@ impl Program {
     }
 }
 
+/// Iterator with items of type (Pubkey, T). Used to lazily deserialize account structs.
+/// Wrapper type hides the inner type from usages so the implementation can be changed.
+pub struct ProgramAccountsIterator<T> {
+    inner: Map<IntoIter<(Pubkey, Account)>, AccountConverterFunction<T>>,
+}
+
+/// Function type that accepts solana accounts and returns deserialized anchor accounts
+type AccountConverterFunction<T> = fn((Pubkey, Account)) -> Result<(Pubkey, T), ClientError>;
+
+impl<T> Iterator for ProgramAccountsIterator<T> {
+    type Item = Result<(Pubkey, T), ClientError>;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        self.inner.next()
+    }
+}
+
 fn handle_program_log<T: anchor_lang::Event + anchor_lang::AnchorDeserialize>(
     self_program_str: &str,
     l: &str,