Document APIs related to durable transaction nonces
This commit is contained in:
committed by
Trent Nelson
parent
b741b86403
commit
210d98bc06
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -4604,6 +4604,7 @@ dependencies = [
|
|||||||
name = "solana-client"
|
name = "solana-client"
|
||||||
version = "1.11.0"
|
version = "1.11.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
"assert_matches",
|
"assert_matches",
|
||||||
"async-mutex",
|
"async-mutex",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
|
@ -53,6 +53,7 @@ tungstenite = { version = "0.17.2", features = ["rustls-tls-webpki-roots"] }
|
|||||||
url = "2.2.2"
|
url = "2.2.2"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
anyhow = "1.0.45"
|
||||||
assert_matches = "1.5.0"
|
assert_matches = "1.5.0"
|
||||||
jsonrpc-http-server = "18.0.0"
|
jsonrpc-http-server = "18.0.0"
|
||||||
solana-logger = { path = "../logger", version = "=1.11.0" }
|
solana-logger = { path = "../logger", version = "=1.11.0" }
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
//! Durable transaction nonce helpers.
|
||||||
|
|
||||||
use {
|
use {
|
||||||
crate::rpc_client::RpcClient,
|
crate::rpc_client::RpcClient,
|
||||||
solana_sdk::{
|
solana_sdk::{
|
||||||
@ -32,10 +34,23 @@ pub enum Error {
|
|||||||
Client(String),
|
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> {
|
pub fn get_account(rpc_client: &RpcClient, nonce_pubkey: &Pubkey) -> Result<Account, Error> {
|
||||||
get_account_with_commitment(rpc_client, nonce_pubkey, CommitmentConfig::default())
|
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(
|
pub fn get_account_with_commitment(
|
||||||
rpc_client: &RpcClient,
|
rpc_client: &RpcClient,
|
||||||
nonce_pubkey: &Pubkey,
|
nonce_pubkey: &Pubkey,
|
||||||
@ -52,6 +67,13 @@ pub fn get_account_with_commitment(
|
|||||||
.and_then(|a| account_identity_ok(&a).map(|()| a))
|
.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> {
|
pub fn account_identity_ok<T: ReadableAccount>(account: &T) -> Result<(), Error> {
|
||||||
if account.owner() != &system_program::id() {
|
if account.owner() != &system_program::id() {
|
||||||
Err(Error::InvalidAccountOwner)
|
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>>(
|
pub fn state_from_account<T: ReadableAccount + StateMut<Versions>>(
|
||||||
account: &T,
|
account: &T,
|
||||||
) -> Result<State, Error> {
|
) -> Result<State, Error> {
|
||||||
@ -71,6 +134,93 @@ pub fn state_from_account<T: ReadableAccount + StateMut<Versions>>(
|
|||||||
.map(|v| v.convert_to_current())
|
.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>>(
|
pub fn data_from_account<T: ReadableAccount + StateMut<Versions>>(
|
||||||
account: &T,
|
account: &T,
|
||||||
) -> Result<Data, Error> {
|
) -> 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()))
|
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> {
|
pub fn data_from_state(state: &State) -> Result<&Data, Error> {
|
||||||
match state {
|
match state {
|
||||||
State::Uninitialized => Err(Error::InvalidStateForOperation),
|
State::Uninitialized => Err(Error::InvalidStateForOperation),
|
||||||
|
@ -15,18 +15,38 @@
|
|||||||
|
|
||||||
pub mod solana_client {
|
pub mod solana_client {
|
||||||
pub mod client_error {
|
pub mod client_error {
|
||||||
use thiserror::Error;
|
#[derive(thiserror::Error, Debug)]
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
|
||||||
#[error("mock-error")]
|
#[error("mock-error")]
|
||||||
pub struct ClientError;
|
pub struct ClientError;
|
||||||
pub type Result<T> = std::result::Result<T, ClientError>;
|
pub type Result<T> = std::result::Result<T, ClientError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub mod nonce_utils {
|
||||||
|
use {
|
||||||
|
super::super::solana_sdk::{
|
||||||
|
account::ReadableAccount, account_utils::StateMut, hash::Hash, pubkey::Pubkey,
|
||||||
|
},
|
||||||
|
crate::nonce::state::{Data, Versions},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(thiserror::Error, Debug)]
|
||||||
|
#[error("mock-error")]
|
||||||
|
pub struct Error;
|
||||||
|
|
||||||
|
pub fn data_from_account<T: ReadableAccount + StateMut<Versions>>(
|
||||||
|
_account: &T,
|
||||||
|
) -> Result<Data, Error> {
|
||||||
|
Ok(Data::new(Pubkey::new_unique(), Hash::default(), 5000))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub mod rpc_client {
|
pub mod rpc_client {
|
||||||
use super::{
|
use super::{
|
||||||
super::solana_sdk::{hash::Hash, signature::Signature, transaction::Transaction},
|
super::solana_sdk::{
|
||||||
client_error::Result as ClientResult,
|
account::Account, hash::Hash, pubkey::Pubkey, signature::Signature,
|
||||||
|
transaction::Transaction,
|
||||||
|
},
|
||||||
|
client_error::{ClientError, Result as ClientResult},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct RpcClient;
|
pub struct RpcClient;
|
||||||
@ -53,6 +73,14 @@ pub mod solana_client {
|
|||||||
) -> ClientResult<u64> {
|
) -> ClientResult<u64> {
|
||||||
Ok(0)
|
Ok(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_account(&self, _pubkey: &Pubkey) -> Result<Account, ClientError> {
|
||||||
|
Ok(Account {})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_balance(&self, _pubkey: &Pubkey) -> ClientResult<u64> {
|
||||||
|
Ok(0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -63,7 +91,33 @@ pub mod solana_client {
|
|||||||
/// This lets examples in solana-program appear to be written as client
|
/// This lets examples in solana-program appear to be written as client
|
||||||
/// programs.
|
/// programs.
|
||||||
pub mod solana_sdk {
|
pub mod solana_sdk {
|
||||||
pub use crate::{hash, instruction, message, nonce, pubkey, system_instruction};
|
pub use crate::{
|
||||||
|
hash, instruction, message, nonce,
|
||||||
|
pubkey::{self, Pubkey},
|
||||||
|
system_instruction,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub mod account {
|
||||||
|
pub struct Account;
|
||||||
|
|
||||||
|
pub trait ReadableAccount: Sized {
|
||||||
|
fn data(&self) -> &[u8];
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ReadableAccount for Account {
|
||||||
|
fn data(&self) -> &[u8] {
|
||||||
|
&[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod account_utils {
|
||||||
|
use super::account::Account;
|
||||||
|
|
||||||
|
pub trait StateMut<T> {}
|
||||||
|
|
||||||
|
impl<T> StateMut<T> for Account {}
|
||||||
|
}
|
||||||
|
|
||||||
pub mod signature {
|
pub mod signature {
|
||||||
use crate::pubkey::Pubkey;
|
use crate::pubkey::Pubkey;
|
||||||
@ -93,6 +147,9 @@ pub mod solana_sdk {
|
|||||||
pub mod signers {
|
pub mod signers {
|
||||||
use super::signature::Signer;
|
use super::signature::Signer;
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error, PartialEq)]
|
||||||
|
pub enum SignerError {}
|
||||||
|
|
||||||
pub trait Signers {}
|
pub trait Signers {}
|
||||||
|
|
||||||
impl<T: Signer> Signers for [&T; 1] {}
|
impl<T: Signer> Signers for [&T; 1] {}
|
||||||
@ -101,10 +158,12 @@ pub mod solana_sdk {
|
|||||||
|
|
||||||
pub mod transaction {
|
pub mod transaction {
|
||||||
use {
|
use {
|
||||||
super::signers::Signers,
|
super::signers::{SignerError, Signers},
|
||||||
crate::{hash::Hash, instruction::Instruction, message::Message, pubkey::Pubkey},
|
crate::{hash::Hash, instruction::Instruction, message::Message, pubkey::Pubkey},
|
||||||
|
serde::Serialize,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
pub struct Transaction {
|
pub struct Transaction {
|
||||||
pub message: Message,
|
pub message: Message,
|
||||||
}
|
}
|
||||||
@ -143,6 +202,14 @@ pub mod solana_sdk {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn sign<T: Signers>(&mut self, _keypairs: &T, _recent_blockhash: Hash) {}
|
pub fn sign<T: Signers>(&mut self, _keypairs: &T, _recent_blockhash: Hash) {}
|
||||||
|
|
||||||
|
pub fn try_sign<T: Signers>(
|
||||||
|
&mut self,
|
||||||
|
_keypairs: &T,
|
||||||
|
_recent_blockhash: Hash,
|
||||||
|
) -> Result<(), SignerError> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
//! Durable transaction nonces.
|
||||||
|
|
||||||
pub mod state;
|
pub mod state;
|
||||||
pub use state::State;
|
pub use state::State;
|
||||||
|
|
||||||
|
@ -4,14 +4,21 @@ use {
|
|||||||
serde_derive::{Deserialize, Serialize},
|
serde_derive::{Deserialize, Serialize},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Initialized data of a durable transaction nonce account.
|
||||||
|
///
|
||||||
|
/// This is stored within [`State`] for initialized nonce accounts.
|
||||||
#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Clone)]
|
#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Clone)]
|
||||||
pub struct Data {
|
pub struct Data {
|
||||||
|
/// Address of the account that signs transactions using the nonce account.
|
||||||
pub authority: Pubkey,
|
pub authority: Pubkey,
|
||||||
|
/// A valid previous blockhash.
|
||||||
pub blockhash: Hash,
|
pub blockhash: Hash,
|
||||||
|
/// The fee calculator associated with the blockhash.
|
||||||
pub fee_calculator: FeeCalculator,
|
pub fee_calculator: FeeCalculator,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Data {
|
impl Data {
|
||||||
|
/// Create new durable transaction nonce data.
|
||||||
pub fn new(authority: Pubkey, blockhash: Hash, lamports_per_signature: u64) -> Self {
|
pub fn new(authority: Pubkey, blockhash: Hash, lamports_per_signature: u64) -> Self {
|
||||||
Data {
|
Data {
|
||||||
authority,
|
authority,
|
||||||
@ -19,11 +26,17 @@ impl Data {
|
|||||||
fee_calculator: FeeCalculator::new(lamports_per_signature),
|
fee_calculator: FeeCalculator::new(lamports_per_signature),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the cost per signature for the next transaction to use this nonce.
|
||||||
pub fn get_lamports_per_signature(&self) -> u64 {
|
pub fn get_lamports_per_signature(&self) -> u64 {
|
||||||
self.fee_calculator.lamports_per_signature
|
self.fee_calculator.lamports_per_signature
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The state of a durable transaction nonce account.
|
||||||
|
///
|
||||||
|
/// When created in memory with [`State::default`] or when deserialized from an
|
||||||
|
/// uninitialized account, a nonce account will be [`State::Uninitialized`].
|
||||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
|
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
|
||||||
pub enum State {
|
pub enum State {
|
||||||
Uninitialized,
|
Uninitialized,
|
||||||
@ -37,6 +50,7 @@ impl Default for State {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl State {
|
impl State {
|
||||||
|
/// Create new durable transaction nonce state.
|
||||||
pub fn new_initialized(
|
pub fn new_initialized(
|
||||||
authority: &Pubkey,
|
authority: &Pubkey,
|
||||||
blockhash: &Hash,
|
blockhash: &Hash,
|
||||||
@ -44,6 +58,8 @@ impl State {
|
|||||||
) -> Self {
|
) -> Self {
|
||||||
Self::Initialized(Data::new(*authority, *blockhash, lamports_per_signature))
|
Self::Initialized(Data::new(*authority, *blockhash, lamports_per_signature))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the serialized size of the nonce state.
|
||||||
pub fn size() -> usize {
|
pub fn size() -> usize {
|
||||||
let data = Versions::new_current(State::Initialized(Data::default()));
|
let data = Versions::new_current(State::Initialized(Data::default()));
|
||||||
bincode::serialized_size(&data).unwrap() as usize
|
bincode::serialized_size(&data).unwrap() as usize
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
//! State for durable transaction nonces.
|
||||||
|
|
||||||
mod current;
|
mod current;
|
||||||
pub use current::{Data, State};
|
pub use current::{Data, State};
|
||||||
use serde_derive::{Deserialize, Serialize};
|
use serde_derive::{Deserialize, Serialize};
|
||||||
|
@ -494,6 +494,115 @@ pub fn create_nonce_account_with_seed(
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create an account containing a durable transaction nonce.
|
||||||
|
///
|
||||||
|
/// This function produces a vector of [`Instruction`]s which must be submitted
|
||||||
|
/// in a [`Transaction`] or [invoked] to take effect.
|
||||||
|
///
|
||||||
|
/// [`Transaction`]: https://docs.rs/solana-sdk/latest/solana_sdk/transaction/struct.Transaction.html
|
||||||
|
/// [invoked]: crate::program::invoke
|
||||||
|
///
|
||||||
|
/// A [durable transaction nonce][dtn] is a special account that enables
|
||||||
|
/// execution of transactions that have been signed in the past.
|
||||||
|
///
|
||||||
|
/// Standard Solana transactions include a [recent blockhash][rbh] (sometimes
|
||||||
|
/// referred to as a _[nonce]_). During execution the Solana runtime verifies
|
||||||
|
/// the recent blockhash is approximately less than two minutes old, and that in
|
||||||
|
/// those two minutes no other identical transaction with the same blockhash has
|
||||||
|
/// been executed. These checks prevent accidental replay of transactions.
|
||||||
|
/// Consequently, it is not possible to sign a transaction, wait more than two
|
||||||
|
/// minutes, then successfully execute that transaction.
|
||||||
|
///
|
||||||
|
/// [dtn]: https://docs.solana.com/implemented-proposals/durable-tx-nonces
|
||||||
|
/// [rbh]: crate::message::Message::recent_blockhash
|
||||||
|
/// [nonce]: https://en.wikipedia.org/wiki/Cryptographic_nonce
|
||||||
|
///
|
||||||
|
/// Durable transaction nonces are an alternative to the standard recent
|
||||||
|
/// blockhash nonce. They are stored in accounts on chain, and every time they
|
||||||
|
/// are used their value is changed to a new value for their next use. The
|
||||||
|
/// runtime verifies that each durable nonce value is only used once, and there
|
||||||
|
/// are no restrictions on how "old" the nonce is. Because they are stored on
|
||||||
|
/// chain and require additional instructions to use, transacting with durable
|
||||||
|
/// transaction nonces is more expensive than with standard transactions.
|
||||||
|
///
|
||||||
|
/// The value of the durable nonce is itself a blockhash and is accessible via
|
||||||
|
/// the [`blockhash`] field of [`nonce::state::Data`], which is deserialized
|
||||||
|
/// from the nonce account data.
|
||||||
|
///
|
||||||
|
/// [`blockhash`]: crate::nonce::state::Data::blockhash
|
||||||
|
/// [`nonce::state::Data`]: crate::nonce::state::Data
|
||||||
|
///
|
||||||
|
/// The basic durable transaction nonce lifecycle is
|
||||||
|
///
|
||||||
|
/// 1) Create the nonce account with the `create_nonce_account` instruction.
|
||||||
|
/// 2) Submit specially-formed transactions that include the
|
||||||
|
/// [`advance_nonce_account`] instruction.
|
||||||
|
/// 3) Destroy the nonce account by withdrawing its lamports with the
|
||||||
|
/// [`withdraw_nonce_account`] instruction.
|
||||||
|
///
|
||||||
|
/// Nonce accounts have an associated _authority_ account, which is stored in
|
||||||
|
/// their account data, and can be changed with the [`authorize_nonce_account`]
|
||||||
|
/// instruction. The authority must sign transactions that include the
|
||||||
|
/// `advance_nonce_account`, `authorize_nonce_account` and
|
||||||
|
/// `withdraw_nonce_account` instructions.
|
||||||
|
///
|
||||||
|
/// Nonce accounts are owned by the system program.
|
||||||
|
///
|
||||||
|
/// This constructor creates a [`SystemInstruction::CreateAccount`] instruction
|
||||||
|
/// and a [`SystemInstruction::InitializeNonceAccount`] instruction.
|
||||||
|
///
|
||||||
|
/// # Required signers
|
||||||
|
///
|
||||||
|
/// The `from_pubkey` and `nonce_pubkey` signers must sign the transaction.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// Create a nonce account from an off-chain client:
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use solana_program::example_mocks::solana_sdk;
|
||||||
|
/// # use solana_program::example_mocks::solana_client;
|
||||||
|
/// use solana_client::rpc_client::RpcClient;
|
||||||
|
/// use solana_sdk::{
|
||||||
|
/// # pubkey::Pubkey,
|
||||||
|
/// signature::{Keypair, Signer},
|
||||||
|
/// system_instruction,
|
||||||
|
/// transaction::Transaction,
|
||||||
|
/// nonce::State,
|
||||||
|
/// };
|
||||||
|
/// use anyhow::Result;
|
||||||
|
///
|
||||||
|
/// fn submit_create_nonce_account_tx(
|
||||||
|
/// client: &RpcClient,
|
||||||
|
/// payer: &Keypair,
|
||||||
|
/// ) -> Result<()> {
|
||||||
|
///
|
||||||
|
/// let nonce_account = Keypair::new();
|
||||||
|
///
|
||||||
|
/// let nonce_rent = client.get_minimum_balance_for_rent_exemption(State::size())?;
|
||||||
|
/// let instr = system_instruction::create_nonce_account(
|
||||||
|
/// &payer.pubkey(),
|
||||||
|
/// &nonce_account.pubkey(),
|
||||||
|
/// &payer.pubkey(), // Make the fee payer the nonce account authority
|
||||||
|
/// nonce_rent,
|
||||||
|
/// );
|
||||||
|
///
|
||||||
|
/// let mut tx = Transaction::new_with_payer(&instr, Some(&payer.pubkey()));
|
||||||
|
///
|
||||||
|
/// let blockhash = client.get_latest_blockhash()?;
|
||||||
|
/// tx.try_sign(&[&nonce_account, payer], blockhash)?;
|
||||||
|
///
|
||||||
|
/// client.send_and_confirm_transaction(&tx)?;
|
||||||
|
///
|
||||||
|
/// Ok(())
|
||||||
|
/// }
|
||||||
|
/// #
|
||||||
|
/// # let client = RpcClient::new(String::new());
|
||||||
|
/// # let payer = Keypair::new();
|
||||||
|
/// # submit_create_nonce_account_tx(&client, &payer)?;
|
||||||
|
/// #
|
||||||
|
/// # Ok::<(), anyhow::Error>(())
|
||||||
|
/// ```
|
||||||
pub fn create_nonce_account(
|
pub fn create_nonce_account(
|
||||||
from_pubkey: &Pubkey,
|
from_pubkey: &Pubkey,
|
||||||
nonce_pubkey: &Pubkey,
|
nonce_pubkey: &Pubkey,
|
||||||
@ -521,6 +630,122 @@ pub fn create_nonce_account(
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Advance the value of a durable transaction nonce.
|
||||||
|
///
|
||||||
|
/// This function produces an [`Instruction`] which must be submitted in a
|
||||||
|
/// [`Transaction`] or [invoked] to take effect.
|
||||||
|
///
|
||||||
|
/// [`Transaction`]: https://docs.rs/solana-sdk/latest/solana_sdk/transaction/struct.Transaction.html
|
||||||
|
/// [invoked]: crate::program::invoke
|
||||||
|
///
|
||||||
|
/// Every transaction that relies on a durable transaction nonce must contain a
|
||||||
|
/// [`SystemInstruction::AdvanceNonceAccount`] instruction as the first
|
||||||
|
/// instruction in the [`Message`], as created by this function. When included
|
||||||
|
/// in the first position, the Solana runtime recognizes the transaction as one
|
||||||
|
/// that relies on a durable transaction nonce and processes it accordingly. The
|
||||||
|
/// [`Message::new_with_nonce`] function can be used to construct a `Message` in
|
||||||
|
/// the correct format without calling `advance_nonce_account` directly.
|
||||||
|
///
|
||||||
|
/// When constructing a transaction that includes an `AdvanceNonceInstruction`
|
||||||
|
/// the [`recent_blockhash`] must be treated differently — instead of
|
||||||
|
/// setting it to a recent blockhash, the value of the nonce must be retreived
|
||||||
|
/// and deserialized from the nonce account, and that value specified as the
|
||||||
|
/// "recent blockhash". A nonce account can be deserialized with the
|
||||||
|
/// [`solana_client::nonce_utils::data_from_account`][dfa] function.
|
||||||
|
///
|
||||||
|
/// For further description of durable transaction nonces see
|
||||||
|
/// [`create_nonce_account`].
|
||||||
|
///
|
||||||
|
/// [`Message`]: crate::message::Message
|
||||||
|
/// [`Message::new_with_nonce`]: crate::message::Message::new_with_nonce
|
||||||
|
/// [`recent_blockhash`]: crate::message::Message::recent_blockhash
|
||||||
|
/// [dfa]: https://docs.rs/solana-client/latest/solana_client/nonce_utils/fn.data_from_account.html
|
||||||
|
///
|
||||||
|
/// # Required signers
|
||||||
|
///
|
||||||
|
/// The `authorized_pubkey` signer must sign the transaction.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// Create and sign a transaction with a durable nonce:
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use solana_program::example_mocks::solana_sdk;
|
||||||
|
/// # use solana_program::example_mocks::solana_client;
|
||||||
|
/// 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 advance_nonce_account(nonce_pubkey: &Pubkey, authorized_pubkey: &Pubkey) -> Instruction {
|
pub fn advance_nonce_account(nonce_pubkey: &Pubkey, authorized_pubkey: &Pubkey) -> Instruction {
|
||||||
let account_metas = vec![
|
let account_metas = vec![
|
||||||
AccountMeta::new(*nonce_pubkey, false),
|
AccountMeta::new(*nonce_pubkey, false),
|
||||||
@ -535,6 +760,77 @@ pub fn advance_nonce_account(nonce_pubkey: &Pubkey, authorized_pubkey: &Pubkey)
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Withdraw lamports from a durable transaction nonce account.
|
||||||
|
///
|
||||||
|
/// This function produces an [`Instruction`] which must be submitted in a
|
||||||
|
/// [`Transaction`] or [invoked] to take effect.
|
||||||
|
///
|
||||||
|
/// [`Transaction`]: https://docs.rs/solana-sdk/latest/solana_sdk/transaction/struct.Transaction.html
|
||||||
|
/// [invoked]: crate::program::invoke
|
||||||
|
///
|
||||||
|
/// Withdrawing the entire balance of a nonce account will cause the runtime to
|
||||||
|
/// destroy it upon successful completion of the transaction.
|
||||||
|
///
|
||||||
|
/// Otherwise, nonce accounts must maintain a balance greater than or equal to
|
||||||
|
/// the minimum required for [rent exemption]. If the result of this instruction
|
||||||
|
/// would leave the nonce account with a balance less than required for rent
|
||||||
|
/// exemption, but also greater than zero, then the transaction will fail.
|
||||||
|
///
|
||||||
|
/// [rent exemption]: https://docs.solana.com/developing/programming-model/accounts#rent-exemption
|
||||||
|
///
|
||||||
|
/// This constructor creates a [`SystemInstruction::WithdrawNonceAccount`]
|
||||||
|
/// instruction.
|
||||||
|
///
|
||||||
|
/// # Required signers
|
||||||
|
///
|
||||||
|
/// The `authorized_pubkey` signer must sign the transaction.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use solana_program::example_mocks::solana_sdk;
|
||||||
|
/// # use solana_program::example_mocks::solana_client;
|
||||||
|
/// use solana_client::rpc_client::RpcClient;
|
||||||
|
/// use solana_sdk::{
|
||||||
|
/// pubkey::Pubkey,
|
||||||
|
/// signature::{Keypair, Signer},
|
||||||
|
/// system_instruction,
|
||||||
|
/// transaction::Transaction,
|
||||||
|
/// };
|
||||||
|
/// use anyhow::Result;
|
||||||
|
///
|
||||||
|
/// fn submit_withdraw_nonce_account_tx(
|
||||||
|
/// client: &RpcClient,
|
||||||
|
/// nonce_account_pubkey: &Pubkey,
|
||||||
|
/// authorized_account: &Keypair,
|
||||||
|
/// ) -> Result<()> {
|
||||||
|
///
|
||||||
|
/// let nonce_balance = client.get_balance(nonce_account_pubkey)?;
|
||||||
|
///
|
||||||
|
/// let instr = system_instruction::withdraw_nonce_account(
|
||||||
|
/// &nonce_account_pubkey,
|
||||||
|
/// &authorized_account.pubkey(),
|
||||||
|
/// &authorized_account.pubkey(),
|
||||||
|
/// nonce_balance,
|
||||||
|
/// );
|
||||||
|
///
|
||||||
|
/// let mut tx = Transaction::new_with_payer(&[instr], Some(&authorized_account.pubkey()));
|
||||||
|
///
|
||||||
|
/// let blockhash = client.get_latest_blockhash()?;
|
||||||
|
/// tx.try_sign(&[authorized_account], blockhash)?;
|
||||||
|
///
|
||||||
|
/// client.send_and_confirm_transaction(&tx)?;
|
||||||
|
///
|
||||||
|
/// Ok(())
|
||||||
|
/// }
|
||||||
|
/// #
|
||||||
|
/// # let client = RpcClient::new(String::new());
|
||||||
|
/// # let nonce_account_pubkey = Pubkey::new_unique();
|
||||||
|
/// # let payer = Keypair::new();
|
||||||
|
/// # submit_withdraw_nonce_account_tx(&client, &nonce_account_pubkey, &payer)?;
|
||||||
|
/// #
|
||||||
|
/// # Ok::<(), anyhow::Error>(())
|
||||||
|
/// ```
|
||||||
pub fn withdraw_nonce_account(
|
pub fn withdraw_nonce_account(
|
||||||
nonce_pubkey: &Pubkey,
|
nonce_pubkey: &Pubkey,
|
||||||
authorized_pubkey: &Pubkey,
|
authorized_pubkey: &Pubkey,
|
||||||
@ -556,6 +852,66 @@ pub fn withdraw_nonce_account(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Change the authority of a durable transaction nonce account.
|
||||||
|
///
|
||||||
|
/// This function produces an [`Instruction`] which must be submitted in a
|
||||||
|
/// [`Transaction`] or [invoked] to take effect.
|
||||||
|
///
|
||||||
|
/// [`Transaction`]: https://docs.rs/solana-sdk/latest/solana_sdk/transaction/struct.Transaction.html
|
||||||
|
/// [invoked]: crate::program::invoke
|
||||||
|
///
|
||||||
|
/// This constructor creates a [`SystemInstruction::AuthorizeNonceAccount`]
|
||||||
|
/// instruction.
|
||||||
|
///
|
||||||
|
/// # Required signers
|
||||||
|
///
|
||||||
|
/// The `authorized_pubkey` signer must sign the transaction.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use solana_program::example_mocks::solana_sdk;
|
||||||
|
/// # use solana_program::example_mocks::solana_client;
|
||||||
|
/// use solana_client::rpc_client::RpcClient;
|
||||||
|
/// use solana_sdk::{
|
||||||
|
/// pubkey::Pubkey,
|
||||||
|
/// signature::{Keypair, Signer},
|
||||||
|
/// system_instruction,
|
||||||
|
/// transaction::Transaction,
|
||||||
|
/// };
|
||||||
|
/// use anyhow::Result;
|
||||||
|
///
|
||||||
|
/// fn authorize_nonce_account_tx(
|
||||||
|
/// client: &RpcClient,
|
||||||
|
/// nonce_account_pubkey: &Pubkey,
|
||||||
|
/// authorized_account: &Keypair,
|
||||||
|
/// new_authority_pubkey: &Pubkey,
|
||||||
|
/// ) -> Result<()> {
|
||||||
|
///
|
||||||
|
/// let instr = system_instruction::authorize_nonce_account(
|
||||||
|
/// &nonce_account_pubkey,
|
||||||
|
/// &authorized_account.pubkey(),
|
||||||
|
/// &new_authority_pubkey,
|
||||||
|
/// );
|
||||||
|
///
|
||||||
|
/// let mut tx = Transaction::new_with_payer(&[instr], Some(&authorized_account.pubkey()));
|
||||||
|
///
|
||||||
|
/// let blockhash = client.get_latest_blockhash()?;
|
||||||
|
/// tx.try_sign(&[authorized_account], blockhash)?;
|
||||||
|
///
|
||||||
|
/// client.send_and_confirm_transaction(&tx)?;
|
||||||
|
///
|
||||||
|
/// Ok(())
|
||||||
|
/// }
|
||||||
|
/// #
|
||||||
|
/// # let client = RpcClient::new(String::new());
|
||||||
|
/// # let nonce_account_pubkey = Pubkey::new_unique();
|
||||||
|
/// # let payer = Keypair::new();
|
||||||
|
/// # let new_authority_pubkey = Pubkey::new_unique();
|
||||||
|
/// # authorize_nonce_account_tx(&client, &nonce_account_pubkey, &payer, &new_authority_pubkey)?;
|
||||||
|
/// #
|
||||||
|
/// # Ok::<(), anyhow::Error>(())
|
||||||
|
/// ```
|
||||||
pub fn authorize_nonce_account(
|
pub fn authorize_nonce_account(
|
||||||
nonce_pubkey: &Pubkey,
|
nonce_pubkey: &Pubkey,
|
||||||
authorized_pubkey: &Pubkey,
|
authorized_pubkey: &Pubkey,
|
||||||
|
Reference in New Issue
Block a user