Document APIs related to durable transaction nonces
This commit is contained in:
committed by
Trent Nelson
parent
b741b86403
commit
210d98bc06
@ -53,6 +53,7 @@ tungstenite = { version = "0.17.2", features = ["rustls-tls-webpki-roots"] }
|
||||
url = "2.2.2"
|
||||
|
||||
[dev-dependencies]
|
||||
anyhow = "1.0.45"
|
||||
assert_matches = "1.5.0"
|
||||
jsonrpc-http-server = "18.0.0"
|
||||
solana-logger = { path = "../logger", version = "=1.11.0" }
|
||||
|
@ -1,3 +1,5 @@
|
||||
//! Durable transaction nonce helpers.
|
||||
|
||||
use {
|
||||
crate::rpc_client::RpcClient,
|
||||
solana_sdk::{
|
||||
@ -32,10 +34,23 @@ pub enum Error {
|
||||
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 fn get_account(rpc_client: &RpcClient, nonce_pubkey: &Pubkey) -> Result<Account, Error> {
|
||||
get_account_with_commitment(rpc_client, nonce_pubkey, CommitmentConfig::default())
|
||||
}
|
||||
|
||||
/// 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 fn get_account_with_commitment(
|
||||
rpc_client: &RpcClient,
|
||||
nonce_pubkey: &Pubkey,
|
||||
@ -52,6 +67,13 @@ 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)
|
||||
@ -62,6 +84,47 @@ pub fn account_identity_ok<T: ReadableAccount>(account: &T) -> Result<(), Error>
|
||||
}
|
||||
}
|
||||
|
||||
/// 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> {
|
||||
@ -71,6 +134,93 @@ pub fn state_from_account<T: ReadableAccount + StateMut<Versions>>(
|
||||
.map(|v| v.convert_to_current())
|
||||
}
|
||||
|
||||
/// 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> {
|
||||
@ -78,6 +228,12 @@ pub fn data_from_account<T: ReadableAccount + StateMut<Versions>>(
|
||||
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),
|
||||
|
Reference in New Issue
Block a user