From 210d98bc069a98c8a80fa0d4d8f95deb82c6b8fd Mon Sep 17 00:00:00 2001 From: Brian Anderson Date: Fri, 25 Mar 2022 14:36:36 -0600 Subject: [PATCH] Document APIs related to durable transaction nonces --- Cargo.lock | 1 + client/Cargo.toml | 1 + client/src/nonce_utils.rs | 156 +++++++++++ sdk/program/src/example_mocks.rs | 81 +++++- sdk/program/src/nonce/mod.rs | 2 + sdk/program/src/nonce/state/current.rs | 16 ++ sdk/program/src/nonce/state/mod.rs | 2 + sdk/program/src/system_instruction.rs | 356 +++++++++++++++++++++++++ 8 files changed, 608 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b31bc24a9c..062c0e5142 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4604,6 +4604,7 @@ dependencies = [ name = "solana-client" version = "1.11.0" dependencies = [ + "anyhow", "assert_matches", "async-mutex", "async-trait", diff --git a/client/Cargo.toml b/client/Cargo.toml index f6aba43634..715a7c7143 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -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" } diff --git a/client/src/nonce_utils.rs b/client/src/nonce_utils.rs index 855e670cb5..5e80f1b410 100644 --- a/client/src/nonce_utils.rs +++ b/client/src/nonce_utils.rs @@ -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 { 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(account: &T) -> Result<(), Error> { if account.owner() != &system_program::id() { Err(Error::InvalidAccountOwner) @@ -62,6 +84,47 @@ pub fn account_identity_ok(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 { +/// +/// // 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>( account: &T, ) -> Result { @@ -71,6 +134,93 @@ pub fn state_from_account>( .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>( account: &T, ) -> Result { @@ -78,6 +228,12 @@ pub fn data_from_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), diff --git a/sdk/program/src/example_mocks.rs b/sdk/program/src/example_mocks.rs index 6f0770592d..0b9f1e819d 100644 --- a/sdk/program/src/example_mocks.rs +++ b/sdk/program/src/example_mocks.rs @@ -15,18 +15,38 @@ pub mod solana_client { pub mod client_error { - use thiserror::Error; - - #[derive(Error, Debug)] + #[derive(thiserror::Error, Debug)] #[error("mock-error")] pub struct ClientError; pub type Result = std::result::Result; } + 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>( + _account: &T, + ) -> Result { + Ok(Data::new(Pubkey::new_unique(), Hash::default(), 5000)) + } + } + pub mod rpc_client { use super::{ - super::solana_sdk::{hash::Hash, signature::Signature, transaction::Transaction}, - client_error::Result as ClientResult, + super::solana_sdk::{ + account::Account, hash::Hash, pubkey::Pubkey, signature::Signature, + transaction::Transaction, + }, + client_error::{ClientError, Result as ClientResult}, }; pub struct RpcClient; @@ -53,6 +73,14 @@ pub mod solana_client { ) -> ClientResult { Ok(0) } + + pub fn get_account(&self, _pubkey: &Pubkey) -> Result { + Ok(Account {}) + } + + pub fn get_balance(&self, _pubkey: &Pubkey) -> ClientResult { + Ok(0) + } } } } @@ -63,7 +91,33 @@ pub mod solana_client { /// This lets examples in solana-program appear to be written as client /// programs. 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 {} + + impl StateMut for Account {} + } pub mod signature { use crate::pubkey::Pubkey; @@ -93,6 +147,9 @@ pub mod solana_sdk { pub mod signers { use super::signature::Signer; + #[derive(Debug, thiserror::Error, PartialEq)] + pub enum SignerError {} + pub trait Signers {} impl Signers for [&T; 1] {} @@ -101,10 +158,12 @@ pub mod solana_sdk { pub mod transaction { use { - super::signers::Signers, + super::signers::{SignerError, Signers}, crate::{hash::Hash, instruction::Instruction, message::Message, pubkey::Pubkey}, + serde::Serialize, }; + #[derive(Serialize)] pub struct Transaction { pub message: Message, } @@ -143,6 +202,14 @@ pub mod solana_sdk { } pub fn sign(&mut self, _keypairs: &T, _recent_blockhash: Hash) {} + + pub fn try_sign( + &mut self, + _keypairs: &T, + _recent_blockhash: Hash, + ) -> Result<(), SignerError> { + Ok(()) + } } } } diff --git a/sdk/program/src/nonce/mod.rs b/sdk/program/src/nonce/mod.rs index 3247fbe4ec..61bbefa5a9 100644 --- a/sdk/program/src/nonce/mod.rs +++ b/sdk/program/src/nonce/mod.rs @@ -1,3 +1,5 @@ +//! Durable transaction nonces. + pub mod state; pub use state::State; diff --git a/sdk/program/src/nonce/state/current.rs b/sdk/program/src/nonce/state/current.rs index 50d1942db3..7c23246ed1 100644 --- a/sdk/program/src/nonce/state/current.rs +++ b/sdk/program/src/nonce/state/current.rs @@ -4,14 +4,21 @@ use { 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)] pub struct Data { + /// Address of the account that signs transactions using the nonce account. pub authority: Pubkey, + /// A valid previous blockhash. pub blockhash: Hash, + /// The fee calculator associated with the blockhash. pub fee_calculator: FeeCalculator, } impl Data { + /// Create new durable transaction nonce data. pub fn new(authority: Pubkey, blockhash: Hash, lamports_per_signature: u64) -> Self { Data { authority, @@ -19,11 +26,17 @@ impl Data { 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 { 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)] pub enum State { Uninitialized, @@ -37,6 +50,7 @@ impl Default for State { } impl State { + /// Create new durable transaction nonce state. pub fn new_initialized( authority: &Pubkey, blockhash: &Hash, @@ -44,6 +58,8 @@ impl State { ) -> Self { Self::Initialized(Data::new(*authority, *blockhash, lamports_per_signature)) } + + /// Get the serialized size of the nonce state. pub fn size() -> usize { let data = Versions::new_current(State::Initialized(Data::default())); bincode::serialized_size(&data).unwrap() as usize diff --git a/sdk/program/src/nonce/state/mod.rs b/sdk/program/src/nonce/state/mod.rs index 910843d2fd..92c0bb6e7e 100644 --- a/sdk/program/src/nonce/state/mod.rs +++ b/sdk/program/src/nonce/state/mod.rs @@ -1,3 +1,5 @@ +//! State for durable transaction nonces. + mod current; pub use current::{Data, State}; use serde_derive::{Deserialize, Serialize}; diff --git a/sdk/program/src/system_instruction.rs b/sdk/program/src/system_instruction.rs index 1ae02021d9..25d72c61ca 100644 --- a/sdk/program/src/system_instruction.rs +++ b/sdk/program/src/system_instruction.rs @@ -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( from_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 { let account_metas = vec![ 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( nonce_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( nonce_pubkey: &Pubkey, authorized_pubkey: &Pubkey,