use serde::Deserialize;
use serde::Serialize;
use solana_account::Account;
use solana_account::ReadableAccount;
use solana_account::state_traits::StateMut;
use solana_commitment_config::CommitmentConfig;
use solana_hash::Hash;
use solana_nonce::state::Data;
use solana_nonce::state::State;
use solana_nonce::versions::Versions;
use solana_pubkey::Pubkey;
use solana_sdk_ids::system_program;
use crate::ClientResult;
use crate::SolanaRpcClient;
use crate::rpc_config::RpcAccountInfoConfig;
#[derive(Clone, Debug, Serialize, Deserialize, thiserror::Error, PartialEq, Eq)]
pub enum NonceError {
#[error("invalid account owner")]
InvalidAccountOwner,
#[error("invalid account data")]
InvalidAccountData,
#[error("unexpected account data size")]
UnexpectedDataSize,
#[error("provided hash ({provided}) does not match nonce hash ({expected})")]
InvalidHash { provided: Hash, expected: Hash },
#[error("provided authority ({provided}) does not match nonce authority ({expected})")]
InvalidAuthority { provided: Pubkey, expected: Pubkey },
#[error("invalid state for requested operation")]
InvalidStateForOperation,
#[error("client error: {0}")]
Client(String),
}
pub async fn get_account(
rpc_client: &SolanaRpcClient,
nonce_pubkey: &Pubkey,
) -> Result<Account, NonceError> {
get_account_with_commitment(rpc_client, nonce_pubkey, CommitmentConfig::default()).await
}
pub async fn get_account_with_commitment(
rpc_client: &SolanaRpcClient,
nonce_pubkey: &Pubkey,
commitment_config: CommitmentConfig,
) -> Result<Account, NonceError> {
rpc_client
.get_account_with_config(
nonce_pubkey,
RpcAccountInfoConfig {
commitment: Some(commitment_config),
..Default::default()
},
)
.await
.map_err(|e| NonceError::Client(format!("{e}")))
.and_then(|opt| {
opt.ok_or_else(|| NonceError::Client(format!("AccountNotFound: pubkey={nonce_pubkey}")))
})
.and_then(|a| account_identity_ok(&a).map(|()| a))
}
pub fn account_identity_ok<T: ReadableAccount>(account: &T) -> Result<(), NonceError> {
if account.owner() != &system_program::ID {
Err(NonceError::InvalidAccountOwner)
} else if account.data().is_empty() {
Err(NonceError::UnexpectedDataSize)
} else {
Ok(())
}
}
pub fn state_from_account<T: ReadableAccount + StateMut<Versions>>(
account: &T,
) -> Result<State, NonceError> {
account_identity_ok(account)?;
StateMut::<Versions>::state(account)
.map_err(|_| NonceError::InvalidAccountData)
.map(|v| v.state().clone())
}
pub fn data_from_account<T: ReadableAccount + StateMut<Versions>>(
account: &T,
) -> Result<Data, NonceError> {
account_identity_ok(account)?;
state_from_account(account).and_then(|ref s| data_from_state(s).cloned())
}
pub fn data_from_state(state: &State) -> Result<&Data, NonceError> {
match state {
State::Uninitialized => Err(NonceError::InvalidStateForOperation),
State::Initialized(data) => Ok(data),
}
}
impl SolanaRpcClient {
pub async fn get_nonce_account_state(&self, nonce_pubkey: &Pubkey) -> ClientResult<State> {
let account = self.get_account(nonce_pubkey).await?;
account_identity_ok(&account)?;
let state = state_from_account(&account)?;
Ok(state)
}
pub async fn get_nonce_account_data(&self, nonce_pubkey: &Pubkey) -> ClientResult<Data> {
let state = self.get_nonce_account_state(nonce_pubkey).await?;
let data = data_from_state(&state).cloned()?;
Ok(data)
}
}