solana_rpc_client_nonce_utils/nonblocking/
mod.rs

1//! Durable transaction nonce helpers.
2
3pub mod blockhash_query;
4
5use {
6    solana_account::{state_traits::StateMut, Account, ReadableAccount},
7    solana_commitment_config::CommitmentConfig,
8    solana_hash::Hash,
9    solana_nonce::{
10        state::{Data, State},
11        versions::Versions,
12    },
13    solana_pubkey::Pubkey,
14    solana_rpc_client::nonblocking::rpc_client::RpcClient,
15};
16
17#[derive(Debug, thiserror::Error, PartialEq, Eq)]
18pub enum Error {
19    #[error("invalid account owner")]
20    InvalidAccountOwner,
21    #[error("invalid account data")]
22    InvalidAccountData,
23    #[error("unexpected account data size")]
24    UnexpectedDataSize,
25    #[error("provided hash ({provided}) does not match nonce hash ({expected})")]
26    InvalidHash { provided: Hash, expected: Hash },
27    #[error("provided authority ({provided}) does not match nonce authority ({expected})")]
28    InvalidAuthority { provided: Pubkey, expected: Pubkey },
29    #[error("invalid state for requested operation")]
30    InvalidStateForOperation,
31    #[error("client error: {0}")]
32    Client(String),
33}
34
35/// Get a nonce account from the network.
36///
37/// This is like [`RpcClient::get_account`] except:
38///
39/// - it returns this module's [`Error`] type,
40/// - it returns an error if any of the checks from [`account_identity_ok`] fail.
41pub async fn get_account(rpc_client: &RpcClient, nonce_pubkey: &Pubkey) -> Result<Account, Error> {
42    get_account_with_commitment(rpc_client, nonce_pubkey, CommitmentConfig::default()).await
43}
44
45/// Get a nonce account from the network.
46///
47/// This is like [`RpcClient::get_account_with_commitment`] except:
48///
49/// - it returns this module's [`Error`] type,
50/// - it returns an error if the account does not exist,
51/// - it returns an error if any of the checks from [`account_identity_ok`] fail.
52pub async fn get_account_with_commitment(
53    rpc_client: &RpcClient,
54    nonce_pubkey: &Pubkey,
55    commitment: CommitmentConfig,
56) -> Result<Account, Error> {
57    rpc_client
58        .get_account_with_commitment(nonce_pubkey, commitment)
59        .await
60        .map_err(|e| Error::Client(format!("{e}")))
61        .and_then(|result| {
62            result
63                .value
64                .ok_or_else(|| Error::Client(format!("AccountNotFound: pubkey={nonce_pubkey}")))
65        })
66        .and_then(|a| account_identity_ok(&a).map(|()| a))
67}
68
69/// Perform basic checks that an account has nonce-like properties.
70///
71/// # Errors
72///
73/// Returns [`Error::InvalidAccountOwner`] if the account is not owned by the
74/// system program. Returns [`Error::UnexpectedDataSize`] if the account
75/// contains no data.
76pub fn account_identity_ok<T: ReadableAccount>(account: &T) -> Result<(), Error> {
77    if account.owner() != &solana_sdk_ids::system_program::ID {
78        Err(Error::InvalidAccountOwner)
79    } else if account.data().is_empty() {
80        Err(Error::UnexpectedDataSize)
81    } else {
82        Ok(())
83    }
84}
85
86/// Deserialize the state of a durable transaction nonce account.
87///
88/// # Errors
89///
90/// Returns an error if the account is not owned by the system program or
91/// contains no data.
92///
93/// # Examples
94///
95/// Determine if a nonce account is initialized:
96///
97/// ```no_run
98/// use solana_rpc_client_nonce_utils::nonblocking;
99/// use solana_rpc_client::nonblocking::rpc_client::RpcClient;
100/// use solana_nonce::state::State;
101/// use solana_pubkey::Pubkey;
102/// use anyhow::Result;
103///
104/// futures::executor::block_on(async {
105/// async fn is_nonce_initialized(
106///     client: &RpcClient,
107///     nonce_account_pubkey: &Pubkey,
108/// ) -> Result<bool> {
109///
110///     // Sign the tx with nonce_account's `blockhash` instead of the
111///     // network's latest blockhash.
112///     let nonce_account = client.get_account(nonce_account_pubkey).await?;
113///     let nonce_state = nonblocking::state_from_account(&nonce_account)?;
114///
115///     Ok(!matches!(nonce_state, State::Uninitialized))
116/// }
117/// #
118/// # let client = RpcClient::new(String::new());
119/// # let nonce_account_pubkey = Pubkey::new_unique();
120/// # is_nonce_initialized(&client, &nonce_account_pubkey).await?;
121/// # Ok::<(), anyhow::Error>(())
122/// # })?;
123/// # Ok::<(), anyhow::Error>(())
124/// ```
125pub fn state_from_account<T: ReadableAccount + StateMut<Versions>>(
126    account: &T,
127) -> Result<State, Error> {
128    account_identity_ok(account)?;
129    let versions = StateMut::<Versions>::state(account).map_err(|_| Error::InvalidAccountData)?;
130    Ok(State::from(versions))
131}
132
133/// Deserialize the state data of a durable transaction nonce account.
134///
135/// # Errors
136///
137/// Returns an error if the account is not owned by the system program or
138/// contains no data. Returns an error if the account state is uninitialized or
139/// fails to deserialize.
140///
141/// # Examples
142///
143/// Create and sign a transaction with a durable nonce:
144///
145/// ```no_run
146/// use solana_rpc_client_nonce_utils::nonblocking;
147/// use solana_rpc_client::nonblocking::rpc_client::RpcClient;
148/// use solana_keypair::Keypair;
149/// use solana_message::Message;
150/// use solana_system_interface::instruction as system_instruction;
151/// use solana_pubkey::Pubkey;
152/// use solana_signer::Signer;
153/// use solana_transaction::Transaction;
154/// use std::path::Path;
155/// use anyhow::Result;
156/// # use anyhow::anyhow;
157///
158/// futures::executor::block_on(async {
159/// async fn create_transfer_tx_with_nonce(
160///     client: &RpcClient,
161///     nonce_account_pubkey: &Pubkey,
162///     payer: &Keypair,
163///     receiver: &Pubkey,
164///     amount: u64,
165///     tx_path: &Path,
166/// ) -> Result<()> {
167///
168///     let instr_transfer = system_instruction::transfer(
169///         &payer.pubkey(),
170///         receiver,
171///         amount,
172///     );
173///
174///     // In this example, `payer` is `nonce_account_pubkey`'s authority
175///     let instr_advance_nonce_account = system_instruction::advance_nonce_account(
176///         nonce_account_pubkey,
177///         &payer.pubkey(),
178///     );
179///
180///     // The `advance_nonce_account` instruction must be the first issued in
181///     // the transaction.
182///     let message = Message::new(
183///         &[
184///             instr_advance_nonce_account,
185///             instr_transfer
186///         ],
187///         Some(&payer.pubkey()),
188///     );
189///
190///     let mut tx = Transaction::new_unsigned(message);
191///
192///     // Sign the tx with nonce_account's `blockhash` instead of the
193///     // network's latest blockhash.
194///     let nonce_account = client.get_account(nonce_account_pubkey).await?;
195///     let nonce_data = nonblocking::data_from_account(&nonce_account)?;
196///     let blockhash = nonce_data.blockhash();
197///
198///     tx.try_sign(&[payer], blockhash)?;
199///
200///     // Save the signed transaction locally for later submission.
201///     save_tx_to_file(&tx_path, &tx)?;
202///
203///     Ok(())
204/// }
205/// #
206/// # fn save_tx_to_file(path: &Path, tx: &Transaction) -> Result<()> {
207/// #     Ok(())
208/// # }
209/// #
210/// # let client = RpcClient::new(String::new());
211/// # let nonce_account_pubkey = Pubkey::new_unique();
212/// # let payer = Keypair::new();
213/// # let receiver = Pubkey::new_unique();
214/// # create_transfer_tx_with_nonce(&client, &nonce_account_pubkey, &payer, &receiver, 1024, Path::new("new_tx")).await?;
215/// #
216/// # Ok::<(), anyhow::Error>(())
217/// # })?;
218/// # Ok::<(), anyhow::Error>(())
219/// ```
220pub fn data_from_account<T: ReadableAccount + StateMut<Versions>>(
221    account: &T,
222) -> Result<Data, Error> {
223    account_identity_ok(account)?;
224    state_from_account(account).and_then(|ref s| data_from_state(s).cloned())
225}
226
227/// Get the nonce data from its [`State`] value.
228///
229/// # Errors
230///
231/// Returns [`Error::InvalidStateForOperation`] if `state` is
232/// [`State::Uninitialized`].
233pub fn data_from_state(state: &State) -> Result<&Data, Error> {
234    match state {
235        State::Uninitialized => Err(Error::InvalidStateForOperation),
236        State::Initialized(data) => Ok(data),
237    }
238}