client: add typed getProgramAccounts support (#1297)

This commit is contained in:
Drew Nutter 2022-01-12 12:14:25 -03:00 committed by GitHub
parent 9f46b30bc7
commit a8f820c391
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 69 additions and 2 deletions

1
Cargo.lock generated
View File

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

View File

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

View File

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