Implement nonblocking version of BlockhashQuery (#27040)

This commit is contained in:
hana 2022-08-09 20:18:36 -07:00 committed by GitHub
parent ddd660e2d3
commit b9a5af0a41
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 686 additions and 203 deletions

View File

@ -0,0 +1,433 @@
use {
crate::nonblocking::{nonce_utils, rpc_client::RpcClient},
clap::ArgMatches,
solana_clap_utils::{
input_parsers::{pubkey_of, value_of},
nonce::*,
offline::*,
},
solana_sdk::{commitment_config::CommitmentConfig, hash::Hash, pubkey::Pubkey},
};
#[derive(Debug, PartialEq, Eq)]
pub enum Source {
Cluster,
NonceAccount(Pubkey),
}
impl Source {
pub async fn get_blockhash(
&self,
rpc_client: &RpcClient,
commitment: CommitmentConfig,
) -> Result<Hash, Box<dyn std::error::Error>> {
match self {
Self::Cluster => {
let (blockhash, _) = rpc_client
.get_latest_blockhash_with_commitment(commitment)
.await?;
Ok(blockhash)
}
Self::NonceAccount(ref pubkey) => {
#[allow(clippy::redundant_closure)]
let data = nonce_utils::get_account_with_commitment(rpc_client, pubkey, commitment)
.await
.and_then(|ref a| nonce_utils::data_from_account(a))?;
Ok(data.blockhash())
}
}
}
pub async fn is_blockhash_valid(
&self,
rpc_client: &RpcClient,
blockhash: &Hash,
commitment: CommitmentConfig,
) -> Result<bool, Box<dyn std::error::Error>> {
Ok(match self {
Self::Cluster => rpc_client.is_blockhash_valid(blockhash, commitment).await?,
Self::NonceAccount(ref pubkey) => {
#[allow(clippy::redundant_closure)]
let _ = nonce_utils::get_account_with_commitment(rpc_client, pubkey, commitment)
.await
.and_then(|ref a| nonce_utils::data_from_account(a))?;
true
}
})
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum BlockhashQuery {
Static(Hash),
Validated(Source, Hash),
Rpc(Source),
}
impl BlockhashQuery {
pub fn new(blockhash: Option<Hash>, sign_only: bool, nonce_account: Option<Pubkey>) -> Self {
let source = nonce_account
.map(Source::NonceAccount)
.unwrap_or(Source::Cluster);
match blockhash {
Some(hash) if sign_only => Self::Static(hash),
Some(hash) if !sign_only => Self::Validated(source, hash),
None if !sign_only => Self::Rpc(source),
_ => panic!("Cannot resolve blockhash"),
}
}
pub fn new_from_matches(matches: &ArgMatches<'_>) -> Self {
let blockhash = value_of(matches, BLOCKHASH_ARG.name);
let sign_only = matches.is_present(SIGN_ONLY_ARG.name);
let nonce_account = pubkey_of(matches, NONCE_ARG.name);
BlockhashQuery::new(blockhash, sign_only, nonce_account)
}
pub async fn get_blockhash(
&self,
rpc_client: &RpcClient,
commitment: CommitmentConfig,
) -> Result<Hash, Box<dyn std::error::Error>> {
match self {
BlockhashQuery::Static(hash) => Ok(*hash),
BlockhashQuery::Validated(source, hash) => {
if !source
.is_blockhash_valid(rpc_client, hash, commitment)
.await?
{
return Err(format!("Hash has expired {:?}", hash).into());
}
Ok(*hash)
}
BlockhashQuery::Rpc(source) => source.get_blockhash(rpc_client, commitment).await,
}
}
}
impl Default for BlockhashQuery {
fn default() -> Self {
BlockhashQuery::Rpc(Source::Cluster)
}
}
#[cfg(test)]
mod tests {
use {
super::*,
crate::{
nonblocking::blockhash_query,
rpc_request::RpcRequest,
rpc_response::{Response, RpcBlockhash, RpcResponseContext},
},
clap::App,
serde_json::{self, json},
solana_account_decoder::{UiAccount, UiAccountEncoding},
solana_sdk::{
account::Account,
fee_calculator::FeeCalculator,
hash::hash,
nonce::{self, state::DurableNonce},
system_program,
},
std::collections::HashMap,
};
#[test]
fn test_blockhash_query_new_ok() {
let blockhash = hash(&[1u8]);
let nonce_pubkey = Pubkey::new(&[1u8; 32]);
assert_eq!(
BlockhashQuery::new(Some(blockhash), true, None),
BlockhashQuery::Static(blockhash),
);
assert_eq!(
BlockhashQuery::new(Some(blockhash), false, None),
BlockhashQuery::Validated(blockhash_query::Source::Cluster, blockhash),
);
assert_eq!(
BlockhashQuery::new(None, false, None),
BlockhashQuery::Rpc(blockhash_query::Source::Cluster)
);
assert_eq!(
BlockhashQuery::new(Some(blockhash), true, Some(nonce_pubkey)),
BlockhashQuery::Static(blockhash),
);
assert_eq!(
BlockhashQuery::new(Some(blockhash), false, Some(nonce_pubkey)),
BlockhashQuery::Validated(
blockhash_query::Source::NonceAccount(nonce_pubkey),
blockhash
),
);
assert_eq!(
BlockhashQuery::new(None, false, Some(nonce_pubkey)),
BlockhashQuery::Rpc(blockhash_query::Source::NonceAccount(nonce_pubkey)),
);
}
#[test]
#[should_panic]
fn test_blockhash_query_new_no_nonce_fail() {
BlockhashQuery::new(None, true, None);
}
#[test]
#[should_panic]
fn test_blockhash_query_new_nonce_fail() {
let nonce_pubkey = Pubkey::new(&[1u8; 32]);
BlockhashQuery::new(None, true, Some(nonce_pubkey));
}
#[test]
fn test_blockhash_query_new_from_matches_ok() {
let test_commands = App::new("blockhash_query_test")
.nonce_args(false)
.offline_args();
let blockhash = hash(&[1u8]);
let blockhash_string = blockhash.to_string();
let matches = test_commands.clone().get_matches_from(vec![
"blockhash_query_test",
"--blockhash",
&blockhash_string,
"--sign-only",
]);
assert_eq!(
BlockhashQuery::new_from_matches(&matches),
BlockhashQuery::Static(blockhash),
);
let matches = test_commands.clone().get_matches_from(vec![
"blockhash_query_test",
"--blockhash",
&blockhash_string,
]);
assert_eq!(
BlockhashQuery::new_from_matches(&matches),
BlockhashQuery::Validated(blockhash_query::Source::Cluster, blockhash),
);
let matches = test_commands
.clone()
.get_matches_from(vec!["blockhash_query_test"]);
assert_eq!(
BlockhashQuery::new_from_matches(&matches),
BlockhashQuery::Rpc(blockhash_query::Source::Cluster),
);
let nonce_pubkey = Pubkey::new(&[1u8; 32]);
let nonce_string = nonce_pubkey.to_string();
let matches = test_commands.clone().get_matches_from(vec![
"blockhash_query_test",
"--blockhash",
&blockhash_string,
"--sign-only",
"--nonce",
&nonce_string,
]);
assert_eq!(
BlockhashQuery::new_from_matches(&matches),
BlockhashQuery::Static(blockhash),
);
let matches = test_commands.clone().get_matches_from(vec![
"blockhash_query_test",
"--blockhash",
&blockhash_string,
"--nonce",
&nonce_string,
]);
assert_eq!(
BlockhashQuery::new_from_matches(&matches),
BlockhashQuery::Validated(
blockhash_query::Source::NonceAccount(nonce_pubkey),
blockhash
),
);
}
#[test]
#[should_panic]
fn test_blockhash_query_new_from_matches_without_nonce_fail() {
let test_commands = App::new("blockhash_query_test")
.arg(blockhash_arg())
// We can really only hit this case if the arg requirements
// are broken, so unset the requires() to recreate that condition
.arg(sign_only_arg().requires(""));
let matches = test_commands
.clone()
.get_matches_from(vec!["blockhash_query_test", "--sign-only"]);
BlockhashQuery::new_from_matches(&matches);
}
#[test]
#[should_panic]
fn test_blockhash_query_new_from_matches_with_nonce_fail() {
let test_commands = App::new("blockhash_query_test")
.arg(blockhash_arg())
// We can really only hit this case if the arg requirements
// are broken, so unset the requires() to recreate that condition
.arg(sign_only_arg().requires(""));
let nonce_pubkey = Pubkey::new(&[1u8; 32]);
let nonce_string = nonce_pubkey.to_string();
let matches = test_commands.clone().get_matches_from(vec![
"blockhash_query_test",
"--sign-only",
"--nonce",
&nonce_string,
]);
BlockhashQuery::new_from_matches(&matches);
}
#[tokio::test]
async fn test_blockhash_query_get_blockhash() {
let test_blockhash = hash(&[0u8]);
let rpc_blockhash = hash(&[1u8]);
let get_latest_blockhash_response = json!(Response {
context: RpcResponseContext {
slot: 1,
api_version: None
},
value: json!(RpcBlockhash {
blockhash: rpc_blockhash.to_string(),
last_valid_block_height: 42,
}),
});
let is_blockhash_valid_response = json!(Response {
context: RpcResponseContext {
slot: 1,
api_version: None
},
value: true
});
let mut mocks = HashMap::new();
mocks.insert(
RpcRequest::GetLatestBlockhash,
get_latest_blockhash_response.clone(),
);
let rpc_client = RpcClient::new_mock_with_mocks("".to_string(), mocks);
assert_eq!(
BlockhashQuery::default()
.get_blockhash(&rpc_client, CommitmentConfig::default())
.await
.unwrap(),
rpc_blockhash,
);
let mut mocks = HashMap::new();
mocks.insert(
RpcRequest::GetLatestBlockhash,
get_latest_blockhash_response.clone(),
);
mocks.insert(
RpcRequest::IsBlockhashValid,
is_blockhash_valid_response.clone(),
);
let rpc_client = RpcClient::new_mock_with_mocks("".to_string(), mocks);
assert_eq!(
BlockhashQuery::Validated(Source::Cluster, test_blockhash)
.get_blockhash(&rpc_client, CommitmentConfig::default())
.await
.unwrap(),
test_blockhash,
);
let mut mocks = HashMap::new();
mocks.insert(
RpcRequest::GetLatestBlockhash,
get_latest_blockhash_response.clone(),
);
let rpc_client = RpcClient::new_mock_with_mocks("".to_string(), mocks);
assert_eq!(
BlockhashQuery::Static(test_blockhash)
.get_blockhash(&rpc_client, CommitmentConfig::default())
.await
.unwrap(),
test_blockhash,
);
let rpc_client = RpcClient::new_mock("fails".to_string());
assert!(BlockhashQuery::default()
.get_blockhash(&rpc_client, CommitmentConfig::default())
.await
.is_err());
let durable_nonce = DurableNonce::from_blockhash(&Hash::new(&[2u8; 32]));
let nonce_blockhash = *durable_nonce.as_hash();
let nonce_fee_calc = FeeCalculator::new(4242);
let data = nonce::state::Data {
authority: Pubkey::new(&[3u8; 32]),
durable_nonce,
fee_calculator: nonce_fee_calc,
};
let nonce_account = Account::new_data_with_space(
42,
&nonce::state::Versions::new(nonce::State::Initialized(data)),
nonce::State::size(),
&system_program::id(),
)
.unwrap();
let nonce_pubkey = Pubkey::new(&[4u8; 32]);
let rpc_nonce_account = UiAccount::encode(
&nonce_pubkey,
&nonce_account,
UiAccountEncoding::Base64,
None,
None,
);
let get_account_response = json!(Response {
context: RpcResponseContext {
slot: 1,
api_version: None
},
value: json!(Some(rpc_nonce_account)),
});
let mut mocks = HashMap::new();
mocks.insert(RpcRequest::GetAccountInfo, get_account_response.clone());
let rpc_client = RpcClient::new_mock_with_mocks("".to_string(), mocks);
assert_eq!(
BlockhashQuery::Rpc(Source::NonceAccount(nonce_pubkey))
.get_blockhash(&rpc_client, CommitmentConfig::default())
.await
.unwrap(),
nonce_blockhash,
);
let mut mocks = HashMap::new();
mocks.insert(RpcRequest::GetAccountInfo, get_account_response.clone());
let rpc_client = RpcClient::new_mock_with_mocks("".to_string(), mocks);
assert_eq!(
BlockhashQuery::Validated(Source::NonceAccount(nonce_pubkey), nonce_blockhash)
.get_blockhash(&rpc_client, CommitmentConfig::default())
.await
.unwrap(),
nonce_blockhash,
);
let mut mocks = HashMap::new();
mocks.insert(RpcRequest::GetAccountInfo, get_account_response);
let rpc_client = RpcClient::new_mock_with_mocks("".to_string(), mocks);
assert_eq!(
BlockhashQuery::Static(nonce_blockhash)
.get_blockhash(&rpc_client, CommitmentConfig::default())
.await
.unwrap(),
nonce_blockhash,
);
let rpc_client = RpcClient::new_mock("fails".to_string());
assert!(BlockhashQuery::Rpc(Source::NonceAccount(nonce_pubkey))
.get_blockhash(&rpc_client, CommitmentConfig::default())
.await
.is_err());
}
}

View File

@ -1,3 +1,5 @@
pub mod blockhash_query;
pub mod nonce_utils;
pub mod pubsub_client;
pub mod quic_client;
pub mod rpc_client;

View File

@ -0,0 +1,247 @@
//! Durable transaction nonce helpers.
use {
crate::nonblocking::rpc_client::RpcClient,
solana_sdk::{
account::{Account, ReadableAccount},
account_utils::StateMut,
commitment_config::CommitmentConfig,
hash::Hash,
nonce::{
state::{Data, Versions},
State,
},
pubkey::Pubkey,
system_program,
},
};
#[derive(Debug, thiserror::Error, PartialEq, Eq)]
pub enum Error {
#[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),
}
/// Get a nonce account from the network.
///
/// This is like [`RpcClient::get_account`] except:
///
/// - it returns this module's [`Error`] type,
/// - it returns an error if any of the checks from [`account_identity_ok`] fail.
pub async fn get_account(rpc_client: &RpcClient, nonce_pubkey: &Pubkey) -> Result<Account, Error> {
get_account_with_commitment(rpc_client, nonce_pubkey, CommitmentConfig::default()).await
}
/// Get a nonce account from the network.
///
/// This is like [`RpcClient::get_account_with_commitment`] except:
///
/// - it returns this module's [`Error`] type,
/// - it returns an error if the account does not exist,
/// - it returns an error if any of the checks from [`account_identity_ok`] fail.
pub async fn get_account_with_commitment(
rpc_client: &RpcClient,
nonce_pubkey: &Pubkey,
commitment: CommitmentConfig,
) -> Result<Account, Error> {
rpc_client
.get_account_with_commitment(nonce_pubkey, commitment)
.await
.map_err(|e| Error::Client(format!("{}", e)))
.and_then(|result| {
result
.value
.ok_or_else(|| Error::Client(format!("AccountNotFound: pubkey={}", nonce_pubkey)))
})
.and_then(|a| account_identity_ok(&a).map(|()| a))
}
/// Perform basic checks that an account has nonce-like properties.
///
/// # Errors
///
/// Returns [`Error::InvalidAccountOwner`] if the account is not owned by the
/// system program. Returns [`Error::UnexpectedDataSize`] if the account
/// contains no data.
pub fn account_identity_ok<T: ReadableAccount>(account: &T) -> Result<(), Error> {
if account.owner() != &system_program::id() {
Err(Error::InvalidAccountOwner)
} else if account.data().is_empty() {
Err(Error::UnexpectedDataSize)
} else {
Ok(())
}
}
/// Deserialize the state of a durable transaction nonce account.
///
/// # Errors
///
/// Returns an error if the account is not owned by the system program or
/// contains no data.
///
/// # Examples
///
/// Determine if a nonce account is initialized:
///
/// ```no_run
/// use solana_client::nonblocking::{
/// rpc_client::RpcClient,
/// nonce_utils,
/// };
/// use solana_sdk::{
/// nonce::State,
/// pubkey::Pubkey,
/// };
/// use anyhow::Result;
///
/// futures::executor::block_on(async {
/// async fn is_nonce_initialized(
/// client: &RpcClient,
/// nonce_account_pubkey: &Pubkey,
/// ) -> Result<bool> {
///
/// // Sign the tx with nonce_account's `blockhash` instead of the
/// // network's latest blockhash.
/// let nonce_account = client.get_account(nonce_account_pubkey).await?;
/// let nonce_state = nonce_utils::state_from_account(&nonce_account)?;
///
/// Ok(!matches!(nonce_state, State::Uninitialized))
/// }
/// #
/// # let client = RpcClient::new(String::new());
/// # let nonce_account_pubkey = Pubkey::new_unique();
/// # is_nonce_initialized(&client, &nonce_account_pubkey).await?;
/// # Ok::<(), anyhow::Error>(())
/// # })?;
/// # Ok::<(), anyhow::Error>(())
/// ```
pub fn state_from_account<T: ReadableAccount + StateMut<Versions>>(
account: &T,
) -> Result<State, Error> {
account_identity_ok(account)?;
let versions = StateMut::<Versions>::state(account).map_err(|_| Error::InvalidAccountData)?;
Ok(State::from(versions))
}
/// Deserialize the state data of a durable transaction nonce account.
///
/// # Errors
///
/// Returns an error if the account is not owned by the system program or
/// contains no data. Returns an error if the account state is uninitialized or
/// fails to deserialize.
///
/// # Examples
///
/// Create and sign a transaction with a durable nonce:
///
/// ```no_run
/// use solana_client::nonblocking::{
/// rpc_client::RpcClient,
/// nonce_utils,
/// };
/// use solana_sdk::{
/// message::Message,
/// pubkey::Pubkey,
/// signature::{Keypair, Signer},
/// system_instruction,
/// transaction::Transaction,
/// };
/// use std::path::Path;
/// use anyhow::Result;
/// # use anyhow::anyhow;
///
/// futures::executor::block_on(async {
/// async fn create_transfer_tx_with_nonce(
/// client: &RpcClient,
/// nonce_account_pubkey: &Pubkey,
/// payer: &Keypair,
/// receiver: &Pubkey,
/// amount: u64,
/// tx_path: &Path,
/// ) -> Result<()> {
///
/// let instr_transfer = system_instruction::transfer(
/// &payer.pubkey(),
/// receiver,
/// amount,
/// );
///
/// // In this example, `payer` is `nonce_account_pubkey`'s authority
/// let instr_advance_nonce_account = system_instruction::advance_nonce_account(
/// nonce_account_pubkey,
/// &payer.pubkey(),
/// );
///
/// // The `advance_nonce_account` instruction must be the first issued in
/// // the transaction.
/// let message = Message::new(
/// &[
/// instr_advance_nonce_account,
/// instr_transfer
/// ],
/// Some(&payer.pubkey()),
/// );
///
/// let mut tx = Transaction::new_unsigned(message);
///
/// // Sign the tx with nonce_account's `blockhash` instead of the
/// // network's latest blockhash.
/// let nonce_account = client.get_account(nonce_account_pubkey).await?;
/// let nonce_data = nonce_utils::data_from_account(&nonce_account)?;
/// let blockhash = nonce_data.blockhash();
///
/// tx.try_sign(&[payer], blockhash)?;
///
/// // Save the signed transaction locally for later submission.
/// save_tx_to_file(&tx_path, &tx)?;
///
/// Ok(())
/// }
/// #
/// # fn save_tx_to_file(path: &Path, tx: &Transaction) -> Result<()> {
/// # Ok(())
/// # }
/// #
/// # let client = RpcClient::new(String::new());
/// # let nonce_account_pubkey = Pubkey::new_unique();
/// # let payer = Keypair::new();
/// # let receiver = Pubkey::new_unique();
/// # create_transfer_tx_with_nonce(&client, &nonce_account_pubkey, &payer, &receiver, 1024, Path::new("new_tx")).await?;
/// #
/// # Ok::<(), anyhow::Error>(())
/// # })?;
/// # Ok::<(), anyhow::Error>(())
/// ```
pub fn data_from_account<T: ReadableAccount + StateMut<Versions>>(
account: &T,
) -> Result<Data, Error> {
account_identity_ok(account)?;
state_from_account(account).and_then(|ref s| data_from_state(s).map(|d| d.clone()))
}
/// Get the nonce data from its [`State`] value.
///
/// # Errors
///
/// Returns [`Error::InvalidStateForOperation`] if `state` is
/// [`State::Uninitialized`].
pub fn data_from_state(state: &State) -> Result<&Data, Error> {
match state {
State::Uninitialized => Err(Error::InvalidStateForOperation),
State::Initialized(data) => Ok(data),
}
}

View File

@ -1,39 +1,13 @@
//! Durable transaction nonce helpers.
pub use crate::nonblocking::nonce_utils::{
account_identity_ok, data_from_account, data_from_state, state_from_account, Error,
};
use {
crate::rpc_client::RpcClient,
solana_sdk::{
account::{Account, ReadableAccount},
account_utils::StateMut,
commitment_config::CommitmentConfig,
hash::Hash,
nonce::{
state::{Data, Versions},
State,
},
pubkey::Pubkey,
system_program,
},
solana_sdk::{account::Account, commitment_config::CommitmentConfig, pubkey::Pubkey},
};
#[derive(Debug, thiserror::Error, PartialEq, Eq)]
pub enum Error {
#[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),
}
/// Get a nonce account from the network.
///
/// This is like [`RpcClient::get_account`] except:
@ -66,176 +40,3 @@ pub fn get_account_with_commitment(
})
.and_then(|a| account_identity_ok(&a).map(|()| a))
}
/// Perform basic checks that an account has nonce-like properties.
///
/// # Errors
///
/// Returns [`Error::InvalidAccountOwner`] if the account is not owned by the
/// system program. Returns [`Error::UnexpectedDataSize`] if the account
/// contains no data.
pub fn account_identity_ok<T: ReadableAccount>(account: &T) -> Result<(), Error> {
if account.owner() != &system_program::id() {
Err(Error::InvalidAccountOwner)
} else if account.data().is_empty() {
Err(Error::UnexpectedDataSize)
} else {
Ok(())
}
}
/// Deserialize the state of a durable transaction nonce account.
///
/// # Errors
///
/// Returns an error if the account is not owned by the system program or
/// contains no data.
///
/// # Examples
///
/// Determine if a nonce account is initialized:
///
/// ```no_run
/// use solana_client::{
/// rpc_client::RpcClient,
/// nonce_utils,
/// };
/// use solana_sdk::{
/// nonce::State,
/// pubkey::Pubkey,
/// };
/// use anyhow::Result;
///
/// fn is_nonce_initialized(
/// client: &RpcClient,
/// nonce_account_pubkey: &Pubkey,
/// ) -> Result<bool> {
///
/// // Sign the tx with nonce_account's `blockhash` instead of the
/// // network's latest blockhash.
/// let nonce_account = client.get_account(nonce_account_pubkey)?;
/// let nonce_state = nonce_utils::state_from_account(&nonce_account)?;
///
/// Ok(!matches!(nonce_state, State::Uninitialized))
/// }
/// #
/// # let client = RpcClient::new(String::new());
/// # let nonce_account_pubkey = Pubkey::new_unique();
/// # is_nonce_initialized(&client, &nonce_account_pubkey)?;
/// #
/// # Ok::<(), anyhow::Error>(())
/// ```
pub fn state_from_account<T: ReadableAccount + StateMut<Versions>>(
account: &T,
) -> Result<State, Error> {
account_identity_ok(account)?;
let versions = StateMut::<Versions>::state(account).map_err(|_| Error::InvalidAccountData)?;
Ok(State::from(versions))
}
/// Deserialize the state data of a durable transaction nonce account.
///
/// # Errors
///
/// Returns an error if the account is not owned by the system program or
/// contains no data. Returns an error if the account state is uninitialized or
/// fails to deserialize.
///
/// # Examples
///
/// Create and sign a transaction with a durable nonce:
///
/// ```no_run
/// use solana_client::{
/// rpc_client::RpcClient,
/// nonce_utils,
/// };
/// use solana_sdk::{
/// message::Message,
/// pubkey::Pubkey,
/// signature::{Keypair, Signer},
/// system_instruction,
/// transaction::Transaction,
/// };
/// use std::path::Path;
/// use anyhow::Result;
/// # use anyhow::anyhow;
///
/// fn create_transfer_tx_with_nonce(
/// client: &RpcClient,
/// nonce_account_pubkey: &Pubkey,
/// payer: &Keypair,
/// receiver: &Pubkey,
/// amount: u64,
/// tx_path: &Path,
/// ) -> Result<()> {
///
/// let instr_transfer = system_instruction::transfer(
/// &payer.pubkey(),
/// receiver,
/// amount,
/// );
///
/// // In this example, `payer` is `nonce_account_pubkey`'s authority
/// let instr_advance_nonce_account = system_instruction::advance_nonce_account(
/// nonce_account_pubkey,
/// &payer.pubkey(),
/// );
///
/// // The `advance_nonce_account` instruction must be the first issued in
/// // the transaction.
/// let message = Message::new(
/// &[
/// instr_advance_nonce_account,
/// instr_transfer
/// ],
/// Some(&payer.pubkey()),
/// );
///
/// let mut tx = Transaction::new_unsigned(message);
///
/// // Sign the tx with nonce_account's `blockhash` instead of the
/// // network's latest blockhash.
/// let nonce_account = client.get_account(nonce_account_pubkey)?;
/// let nonce_data = nonce_utils::data_from_account(&nonce_account)?;
/// let blockhash = nonce_data.blockhash();
///
/// tx.try_sign(&[payer], blockhash)?;
///
/// // Save the signed transaction locally for later submission.
/// save_tx_to_file(&tx_path, &tx)?;
///
/// Ok(())
/// }
/// #
/// # fn save_tx_to_file(path: &Path, tx: &Transaction) -> Result<()> {
/// # Ok(())
/// # }
/// #
/// # let client = RpcClient::new(String::new());
/// # let nonce_account_pubkey = Pubkey::new_unique();
/// # let payer = Keypair::new();
/// # let receiver = Pubkey::new_unique();
/// # create_transfer_tx_with_nonce(&client, &nonce_account_pubkey, &payer, &receiver, 1024, Path::new("new_tx"))?;
/// #
/// # Ok::<(), anyhow::Error>(())
/// ```
pub fn data_from_account<T: ReadableAccount + StateMut<Versions>>(
account: &T,
) -> Result<Data, Error> {
account_identity_ok(account)?;
state_from_account(account).and_then(|ref s| data_from_state(s).map(|d| d.clone()))
}
/// Get the nonce data from its [`State`] value.
///
/// # Errors
///
/// Returns [`Error::InvalidStateForOperation`] if `state` is
/// [`State::Uninitialized`].
pub fn data_from_state(state: &State) -> Result<&Data, Error> {
match state {
State::Uninitialized => Err(Error::InvalidStateForOperation),
State::Initialized(data) => Ok(data),
}
}