Document transaction module (#22440)

* Document transaction module

* example_mocks is only for feature = full
This commit is contained in:
Brian Anderson 2022-01-21 19:30:12 -06:00 committed by GitHub
parent a7f2fff219
commit 8dd62854fa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 789 additions and 53 deletions

1
Cargo.lock generated
View File

@ -5659,6 +5659,7 @@ dependencies = [
name = "solana-sdk"
version = "1.10.0"
dependencies = [
"anyhow",
"assert_matches",
"base64 0.13.0",
"bincode",

View File

@ -84,6 +84,7 @@ wasm-bindgen = "0.2"
js-sys = "0.3.55"
[dev-dependencies]
anyhow = "1.0.45"
curve25519-dalek = "3.2.0"
tiny-bip39 = "0.8.2"

47
sdk/src/example_mocks.rs Normal file
View File

@ -0,0 +1,47 @@
//! Mock types for use in examples.
//!
//! These represent APIs from crates that themselves depend on this crate, and
//! which are useful for illustrating the examples for APIs in this crate.
//!
//! Directly depending on these crates though would cause problematic circular
//! dependencies, so instead they are mocked out here in a way that allows
//! examples to appear to use crates that this crate must not depend on.
//!
//! Each mod here has the name of a crate, so that examples can be structured to
//! appear to import from that crate.
#![doc(hidden)]
#![cfg(feature = "full")]
pub mod solana_client {
pub mod client_error {
use thiserror::Error;
#[derive(Error, Debug)]
#[error("mock-error")]
pub struct ClientError;
pub type Result<T> = std::result::Result<T, ClientError>;
}
pub mod rpc_client {
use super::client_error::Result as ClientResult;
use crate::{hash::Hash, signature::Signature, transaction::Transaction};
pub struct RpcClient;
impl RpcClient {
pub fn new(_url: String) -> Self {
RpcClient
}
pub fn get_latest_blockhash(&self) -> ClientResult<Hash> {
Ok(Hash::default())
}
pub fn send_and_confirm_transaction(
&self,
_transaction: &Transaction,
) -> ClientResult<Signature> {
Ok(Signature::default())
}
}
}
}

View File

@ -21,6 +21,7 @@ pub mod ed25519_instruction;
pub mod entrypoint;
pub mod entrypoint_deprecated;
pub mod epoch_info;
pub mod example_mocks;
pub mod exit;
pub mod feature;
pub mod feature_set;

View File

@ -1,4 +1,113 @@
//! Defines a Transaction type to package an atomic sequence of instructions.
//! Atomically-committed sequences of instructions.
//!
//! While [`Instruction`]s are the basic unit of computation in Solana, they are
//! submitted by clients in [`Transaction`]s containing one or more
//! instructions, and signed by one or more [`Signer`]s. Solana executes the
//! instructions in a transaction in order, and only commits any changes if all
//! instructions terminate without producing an error or exception.
//!
//! Transactions do not directly contain their instructions but instead include
//! a [`Message`], a precompiled representation of a sequence of instructions.
//! `Message`'s constructors handle the complex task of reordering the
//! individual lists of accounts required by each instruction into a single flat
//! list of deduplicated accounts required by the Solana runtime. The
//! `Transaction` type has constructors that build the `Message` so that clients
//! don't need to interact with them directly.
//!
//! Prior to submission to the network, transactions must be signed by one or or
//! more keypairs, and this signing is typically performed by an abstract
//! [`Signer`], which may be a [`Keypair`] but may also be other types of
//! signers including remote wallets, such as Ledger devices, as represented by
//! the [`RemoteKeypair`] type in the [`solana-remote-wallet`] crate.
//!
//! [`Signer`]: crate::signer::Signer
//! [`Keypair`]: crate::signer::keypair::Keypair
//! [`solana-remote-wallet`]: https://docs.rs/solana-remote-wallet/latest/
//! [`RemoteKeypair`]: https://docs.rs/solana-remote-wallet/latest/solana_remote_wallet/remote_keypair/struct.RemoteKeypair.html
//!
//! Every transaction must be signed by a fee-paying account, the account from
//! which the cost of executing the transaction is withdrawn. Other required
//! signatures are determined by the requirements of the programs being executed
//! by each instruction, and are conventionally specified by that program's
//! documentation.
//!
//! When signing a transaction, a recent blockhash must be provided (which can
//! be retrieved with [`RpcClient::get_latest_blockhash`]). This allows
//! validators to drop old but unexecuted transactions; and to distinguish
//! between accidentally duplicated transactions and intentionally duplicated
//! transactions &mdash; any identical transactions will not be executed more
//! than once, so updating the blockhash between submitting otherwise identical
//! transactions makes them unique. If a client must sign a transaction long
//! before submitting it to the network, then it can use the _[durable
//! transaction nonce]_ mechanism instead of a recent blockhash to ensure unique
//! transactions.
//!
//! [`RpcClient::get_latest_blockhash`]: https://docs.rs/solana-client/latest/solana_client/rpc_client/struct.RpcClient.html#method.get_latest_blockhash
//! [durable transaction nonce]: https://docs.solana.com/implemented-proposals/durable-tx-nonces
//!
//! # Examples
//!
//! This example uses the [`solana_client`] and [`anyhow`] crates.
//!
//! [`solana_client`]: https://docs.rs/solana-client
//! [`anyhow`]: https://docs.rs/anyhow
//!
//! ```
//! # use solana_sdk::example_mocks::solana_client;
//! use anyhow::Result;
//! use borsh::{BorshSerialize, BorshDeserialize};
//! use solana_client::rpc_client::RpcClient;
//! use solana_sdk::{
//! instruction::Instruction,
//! message::Message,
//! pubkey::Pubkey,
//! signature::{Keypair, Signer},
//! transaction::Transaction,
//! };
//!
//! // A custom program instruction. This would typically be defined in
//! // another crate so it can be shared between the on-chain program and
//! // the client.
//! #[derive(BorshSerialize, BorshDeserialize)]
//! enum BankInstruction {
//! Initialize,
//! Deposit { lamports: u64 },
//! Withdraw { lamports: u64 },
//! }
//!
//! fn send_initialize_tx(
//! client: &RpcClient,
//! program_id: Pubkey,
//! payer: &Keypair
//! ) -> Result<()> {
//!
//! let bank_instruction = BankInstruction::Initialize;
//!
//! let instruction = Instruction::new_with_borsh(
//! program_id,
//! &bank_instruction,
//! vec![],
//! );
//!
//! let blockhash = client.get_latest_blockhash()?;
//! let mut tx = Transaction::new_signed_with_payer(
//! &[instruction],
//! Some(&payer.pubkey()),
//! &[payer],
//! blockhash,
//! );
//! client.send_and_confirm_transaction(&tx)?;
//!
//! Ok(())
//! }
//! #
//! # let client = RpcClient::new(String::new());
//! # let program_id = Pubkey::new_unique();
//! # let payer = Keypair::new();
//! # send_initialize_tx(&client, program_id, &payer)?;
//! #
//! # Ok::<(), anyhow::Error>(())
//! ```
#![cfg(feature = "full")]
@ -38,16 +147,38 @@ pub enum TransactionVerificationMode {
pub type Result<T> = result::Result<T, TransactionError>;
/// An atomic transaction
/// An atomically-commited sequence of instructions.
///
/// While [`Instruction`]s are the basic unit of computation in Solana,
/// they are submitted by clients in [`Transaction`]s containing one or
/// more instructions, and signed by one or more [`Signer`]s.
///
/// [`Signer`]: crate::signer::Signer
///
/// See the [module documentation] for more details about transactions.
///
/// [module documentation]: self
///
/// Some constructors accept an optional `payer`, the account responsible for
/// paying the cost of executing a transaction. In most cases, callers should
/// specify the payer explicitly in these constructors. In some cases though,
/// the caller is not _required_ to specify the payer, but is still allowed to:
/// in the [`Message`] structure, the first account is always the fee-payer, so
/// if the caller has knowledge that the first account of the constructed
/// transaction's `Message` is both a signer and the expected fee-payer, then
/// redundantly specifying the fee-payer is not strictly required.
#[wasm_bindgen]
#[frozen_abi(digest = "FZtncnS1Xk8ghHfKiXE5oGiUbw2wJhmfXQuNgQR3K6Mc")]
#[derive(Debug, PartialEq, Default, Eq, Clone, Serialize, Deserialize, AbiExample)]
pub struct Transaction {
/// A set of digital signatures of a serialized [`Message`], signed by the
/// first `signatures.len()` keys of [`account_keys`].
/// A set of signatures of a serialized [`Message`], signed by the first
/// keys of the `Message`'s [`account_keys`], where the number of signatures
/// is equal to [`num_required_signatures`] of the `Message`'s
/// [`MessageHeader`].
///
/// [`account_keys`]: Message::account_keys
///
/// [`MessageHeader`]: crate::message::MessageHeader
/// [`num_required_signatures`]: crate::message::MessageHeader::num_required_signatures
// NOTE: Serialization-related changes must be paired with the direct read at sigverify.
#[wasm_bindgen(skip)]
#[serde(with = "short_vec")]
@ -71,6 +202,72 @@ impl Sanitize for Transaction {
}
impl Transaction {
/// Create an unsigned transaction from a [`Message`].
///
/// # Examples
///
/// This example uses the [`solana_client`] and [`anyhow`] crates.
///
/// [`solana_client`]: https://docs.rs/solana-client
/// [`anyhow`]: https://docs.rs/anyhow
///
/// ```
/// # use solana_sdk::example_mocks::solana_client;
/// use anyhow::Result;
/// use borsh::{BorshSerialize, BorshDeserialize};
/// use solana_client::rpc_client::RpcClient;
/// use solana_sdk::{
/// instruction::Instruction,
/// message::Message,
/// pubkey::Pubkey,
/// signature::{Keypair, Signer},
/// transaction::Transaction,
/// };
///
/// // A custom program instruction. This would typically be defined in
/// // another crate so it can be shared between the on-chain program and
/// // the client.
/// #[derive(BorshSerialize, BorshDeserialize)]
/// enum BankInstruction {
/// Initialize,
/// Deposit { lamports: u64 },
/// Withdraw { lamports: u64 },
/// }
///
/// fn send_initialize_tx(
/// client: &RpcClient,
/// program_id: Pubkey,
/// payer: &Keypair
/// ) -> Result<()> {
///
/// let bank_instruction = BankInstruction::Initialize;
///
/// let instruction = Instruction::new_with_borsh(
/// program_id,
/// &bank_instruction,
/// vec![],
/// );
///
/// let message = Message::new(
/// &[instruction],
/// Some(&payer.pubkey()),
/// );
///
/// let mut tx = Transaction::new_unsigned(message);
/// let blockhash = client.get_latest_blockhash()?;
/// tx.sign(&[payer], blockhash);
/// client.send_and_confirm_transaction(&tx)?;
///
/// Ok(())
/// }
/// #
/// # let client = RpcClient::new(String::new());
/// # let program_id = Pubkey::new_unique();
/// # let payer = Keypair::new();
/// # send_initialize_tx(&client, program_id, &payer)?;
/// #
/// # Ok::<(), anyhow::Error>(())
/// ```
pub fn new_unsigned(message: Message) -> Self {
Self {
signatures: vec![Signature::default(); message.header.num_required_signatures as usize],
@ -78,31 +275,77 @@ impl Transaction {
}
}
pub fn new_with_payer(instructions: &[Instruction], payer: Option<&Pubkey>) -> Self {
let message = Message::new(instructions, payer);
Self::new_unsigned(message)
}
/// Create a signed transaction with the given payer.
/// Create a fully-signed transaction from a [`Message`].
///
/// # Panics
///
/// Panics when signing fails.
pub fn new_signed_with_payer<T: Signers>(
instructions: &[Instruction],
payer: Option<&Pubkey>,
signing_keypairs: &T,
recent_blockhash: Hash,
) -> Self {
let message = Message::new(instructions, payer);
Self::new(signing_keypairs, message, recent_blockhash)
}
/// Create a signed transaction.
/// Panics when signing fails. See [`Transaction::try_sign`] and
/// [`Transaction::try_partial_sign`] for a full description of failure
/// scenarios.
///
/// # Panics
/// # Examples
///
/// Panics when signing fails.
/// This example uses the [`solana_client`] and [`anyhow`] crates.
///
/// [`solana_client`]: https://docs.rs/solana-client
/// [`anyhow`]: https://docs.rs/anyhow
///
/// ```
/// # use solana_sdk::example_mocks::solana_client;
/// use anyhow::Result;
/// use borsh::{BorshSerialize, BorshDeserialize};
/// use solana_client::rpc_client::RpcClient;
/// use solana_sdk::{
/// instruction::Instruction,
/// message::Message,
/// pubkey::Pubkey,
/// signature::{Keypair, Signer},
/// transaction::Transaction,
/// };
///
/// // A custom program instruction. This would typically be defined in
/// // another crate so it can be shared between the on-chain program and
/// // the client.
/// #[derive(BorshSerialize, BorshDeserialize)]
/// enum BankInstruction {
/// Initialize,
/// Deposit { lamports: u64 },
/// Withdraw { lamports: u64 },
/// }
///
/// fn send_initialize_tx(
/// client: &RpcClient,
/// program_id: Pubkey,
/// payer: &Keypair
/// ) -> Result<()> {
///
/// let bank_instruction = BankInstruction::Initialize;
///
/// let instruction = Instruction::new_with_borsh(
/// program_id,
/// &bank_instruction,
/// vec![],
/// );
///
/// let message = Message::new(
/// &[instruction],
/// Some(&payer.pubkey()),
/// );
///
/// let blockhash = client.get_latest_blockhash()?;
/// let mut tx = Transaction::new(&[payer], message, blockhash);
/// client.send_and_confirm_transaction(&tx)?;
///
/// Ok(())
/// }
/// #
/// # let client = RpcClient::new(String::new());
/// # let program_id = Pubkey::new_unique();
/// # let payer = Keypair::new();
/// # send_initialize_tx(&client, program_id, &payer)?;
/// #
/// # Ok::<(), anyhow::Error>(())
/// ```
pub fn new<T: Signers>(
from_keypairs: &T,
message: Message,
@ -113,7 +356,165 @@ impl Transaction {
tx
}
/// Create a signed transaction
/// Create an unsigned transaction from a list of [`Instruction`]s.
///
/// `payer` is the account responsible for paying the cost of executing the
/// transaction. It is typically provided, but is optional in some cases.
/// See the [`Transaction`] docs for more.
///
/// # Examples
///
/// This example uses the [`solana_client`] and [`anyhow`] crates.
///
/// [`solana_client`]: https://docs.rs/solana-client
/// [`anyhow`]: https://docs.rs/anyhow
///
/// ```
/// # use solana_sdk::example_mocks::solana_client;
/// use anyhow::Result;
/// use borsh::{BorshSerialize, BorshDeserialize};
/// use solana_client::rpc_client::RpcClient;
/// use solana_sdk::{
/// instruction::Instruction,
/// message::Message,
/// pubkey::Pubkey,
/// signature::{Keypair, Signer},
/// transaction::Transaction,
/// };
///
/// // A custom program instruction. This would typically be defined in
/// // another crate so it can be shared between the on-chain program and
/// // the client.
/// #[derive(BorshSerialize, BorshDeserialize)]
/// enum BankInstruction {
/// Initialize,
/// Deposit { lamports: u64 },
/// Withdraw { lamports: u64 },
/// }
///
/// fn send_initialize_tx(
/// client: &RpcClient,
/// program_id: Pubkey,
/// payer: &Keypair
/// ) -> Result<()> {
///
/// let bank_instruction = BankInstruction::Initialize;
///
/// let instruction = Instruction::new_with_borsh(
/// program_id,
/// &bank_instruction,
/// vec![],
/// );
///
/// let mut tx = Transaction::new_with_payer(&[instruction], Some(&payer.pubkey()));
/// let blockhash = client.get_latest_blockhash()?;
/// tx.sign(&[payer], blockhash);
/// client.send_and_confirm_transaction(&tx)?;
///
/// Ok(())
/// }
/// #
/// # let client = RpcClient::new(String::new());
/// # let program_id = Pubkey::new_unique();
/// # let payer = Keypair::new();
/// # send_initialize_tx(&client, program_id, &payer)?;
/// #
/// # Ok::<(), anyhow::Error>(())
/// ```
pub fn new_with_payer(instructions: &[Instruction], payer: Option<&Pubkey>) -> Self {
let message = Message::new(instructions, payer);
Self::new_unsigned(message)
}
/// Create a fully-signed transaction from a list of [`Instruction`]s.
///
/// `payer` is the account responsible for paying the cost of executing the
/// transaction. It is typically provided, but is optional in some cases.
/// See the [`Transaction`] docs for more.
///
/// # Panics
///
/// Panics when signing fails. See [`Transaction::try_sign`] and
/// [`Transaction::try_partial_sign`] for a full description of failure
/// scenarios.
///
/// # Examples
///
/// This example uses the [`solana_client`] and [`anyhow`] crates.
///
/// [`solana_client`]: https://docs.rs/solana-client
/// [`anyhow`]: https://docs.rs/anyhow
///
/// ```
/// # use solana_sdk::example_mocks::solana_client;
/// use anyhow::Result;
/// use borsh::{BorshSerialize, BorshDeserialize};
/// use solana_client::rpc_client::RpcClient;
/// use solana_sdk::{
/// instruction::Instruction,
/// message::Message,
/// pubkey::Pubkey,
/// signature::{Keypair, Signer},
/// transaction::Transaction,
/// };
///
/// // A custom program instruction. This would typically be defined in
/// // another crate so it can be shared between the on-chain program and
/// // the client.
/// #[derive(BorshSerialize, BorshDeserialize)]
/// enum BankInstruction {
/// Initialize,
/// Deposit { lamports: u64 },
/// Withdraw { lamports: u64 },
/// }
///
/// fn send_initialize_tx(
/// client: &RpcClient,
/// program_id: Pubkey,
/// payer: &Keypair
/// ) -> Result<()> {
///
/// let bank_instruction = BankInstruction::Initialize;
///
/// let instruction = Instruction::new_with_borsh(
/// program_id,
/// &bank_instruction,
/// vec![],
/// );
///
/// let blockhash = client.get_latest_blockhash()?;
/// let mut tx = Transaction::new_signed_with_payer(
/// &[instruction],
/// Some(&payer.pubkey()),
/// &[payer],
/// blockhash,
/// );
/// client.send_and_confirm_transaction(&tx)?;
///
/// Ok(())
/// }
/// #
/// # let client = RpcClient::new(String::new());
/// # let program_id = Pubkey::new_unique();
/// # let payer = Keypair::new();
/// # send_initialize_tx(&client, program_id, &payer)?;
/// #
/// # Ok::<(), anyhow::Error>(())
/// ```
pub fn new_signed_with_payer<T: Signers>(
instructions: &[Instruction],
payer: Option<&Pubkey>,
signing_keypairs: &T,
recent_blockhash: Hash,
) -> Self {
let message = Message::new(instructions, payer);
Self::new(signing_keypairs, message, recent_blockhash)
}
/// Create a fully-signed transaction from pre-compiled instructions.
///
/// # Arguments
///
/// * `from_keypairs` - The keys used to sign the transaction.
/// * `keys` - The keys for the transaction. These are the program state
/// instances or lamport recipient keys.
@ -123,7 +524,8 @@ impl Transaction {
///
/// # Panics
///
/// Panics when signing fails.
/// Panics when signing fails. See [`Transaction::try_sign`] and for a full
/// description of failure conditions.
pub fn new_with_compiled_instructions<T: Signers>(
from_keypairs: &T,
keys: &[Pubkey],
@ -146,6 +548,17 @@ impl Transaction {
Transaction::new(from_keypairs, message, recent_blockhash)
}
/// Get the data for an instruction at the given index.
///
/// The `instruction_index` corresponds to the [`instructions`] vector of
/// the `Transaction`'s [`Message`] value.
///
/// [`instructions`]: Message::instructions
///
/// # Panics
///
/// Panics if `instruction_index` is greater than or equal to the number of
/// instructions in the transaction.
pub fn data(&self, instruction_index: usize) -> &[u8] {
&self.message.instructions[instruction_index].data
}
@ -158,11 +571,41 @@ impl Transaction {
.map(|&account_keys_index| account_keys_index as usize)
}
/// Get the `Pubkey` of an account required by one of the instructions in
/// the transaction.
///
/// The `instruction_index` corresponds to the [`instructions`] vector of
/// the `Transaction`'s [`Message`] value; and the `account_index` to the
/// [`accounts`] vector of the message's [`CompiledInstruction`]s.
///
/// [`instructions`]: Message::instructions
/// [`accounts`]: CompiledInstruction::accounts
/// [`CompiledInstruction`]: CompiledInstruction
///
/// Returns `None` if `instruction_index` is greater than or equal to the
/// number of instructions in the transaction; or if `accounts_index` is
/// greater than or equal to the number of accounts in the instruction.
pub fn key(&self, instruction_index: usize, accounts_index: usize) -> Option<&Pubkey> {
self.key_index(instruction_index, accounts_index)
.and_then(|account_keys_index| self.message.account_keys.get(account_keys_index))
}
/// Get the `Pubkey` of a signing account required by one of the
/// instructions in the transaction.
///
/// The transaction does not need to be signed for this function to return a
/// signing account's pubkey.
///
/// Returns `None` if the indexed account is not required to sign the
/// transaction. Returns `None` if the [`signatures`] field does not contain
/// enough elements to hold a signature for the indexed account (this should
/// only be possible if `Transaction` has been manually constructed).
///
/// [`signatures`]: Transaction::signatures
///
/// Returns `None` if `instruction_index` is greater than or equal to the
/// number of instructions in the transaction; or if `accounts_index` is
/// greater than or equal to the number of accounts in the instruction.
pub fn signer_key(&self, instruction_index: usize, accounts_index: usize) -> Option<&Pubkey> {
match self.key_index(instruction_index, accounts_index) {
None => None,
@ -175,7 +618,7 @@ impl Transaction {
}
}
/// Return a message containing all data that should be signed.
/// Return the message containing all data that should be signed.
pub fn message(&self) -> &Message {
&self.message
}
@ -185,36 +628,128 @@ impl Transaction {
self.message().serialize()
}
/// Check keys and keypair lengths, then sign this transaction.
/// Sign the transaction.
///
/// This method fully signs a transaction with all required signers, which
/// must be present in the `keypairs` slice. To sign with only some of the
/// required signers, use [`Transaction::partial_sign`].
///
/// If `recent_blockhash` is different than recorded in the transaction message's
/// [`recent_blockhash`] field, then the message's `recent_blockhash` will be updated
/// to the provided `recent_blockhash`, and any prior signatures will be cleared.
///
/// [`recent_blockhash`]: Message::recent_blockhash
///
/// # Panics
///
/// Panics when signing fails, use [`Transaction::try_sign`] to handle the error.
/// Panics when signing fails. Use [`Transaction::try_sign`] to handle the
/// error. See the documentation for [`Transaction::try_sign`] for a full description of
/// failure conditions.
///
/// # Examples
///
/// This example uses the [`solana_client`] and [`anyhow`] crates.
///
/// [`solana_client`]: https://docs.rs/solana-client
/// [`anyhow`]: https://docs.rs/anyhow
///
/// ```
/// # use solana_sdk::example_mocks::solana_client;
/// use anyhow::Result;
/// use borsh::{BorshSerialize, BorshDeserialize};
/// use solana_client::rpc_client::RpcClient;
/// use solana_sdk::{
/// instruction::Instruction,
/// message::Message,
/// pubkey::Pubkey,
/// signature::{Keypair, Signer},
/// transaction::Transaction,
/// };
///
/// // A custom program instruction. This would typically be defined in
/// // another crate so it can be shared between the on-chain program and
/// // the client.
/// #[derive(BorshSerialize, BorshDeserialize)]
/// enum BankInstruction {
/// Initialize,
/// Deposit { lamports: u64 },
/// Withdraw { lamports: u64 },
/// }
///
/// fn send_initialize_tx(
/// client: &RpcClient,
/// program_id: Pubkey,
/// payer: &Keypair
/// ) -> Result<()> {
///
/// let bank_instruction = BankInstruction::Initialize;
///
/// let instruction = Instruction::new_with_borsh(
/// program_id,
/// &bank_instruction,
/// vec![],
/// );
///
/// let mut tx = Transaction::new_with_payer(&[instruction], Some(&payer.pubkey()));
/// let blockhash = client.get_latest_blockhash()?;
/// tx.sign(&[payer], blockhash);
/// client.send_and_confirm_transaction(&tx)?;
///
/// Ok(())
/// }
/// #
/// # let client = RpcClient::new(String::new());
/// # let program_id = Pubkey::new_unique();
/// # let payer = Keypair::new();
/// # send_initialize_tx(&client, program_id, &payer)?;
/// #
/// # Ok::<(), anyhow::Error>(())
/// ```
pub fn sign<T: Signers>(&mut self, keypairs: &T, recent_blockhash: Hash) {
if let Err(e) = self.try_sign(keypairs, recent_blockhash) {
panic!("Transaction::sign failed with error {:?}", e);
}
}
/// Sign using some subset of required keys
/// if recent_blockhash is not the same as currently in the transaction,
/// clear any prior signatures and update recent_blockhash
/// Sign the transaction with a subset of required keys.
///
/// Unlike [`Transaction::sign`], this method does not require all keypairs
/// to be provided, allowing a transaction to be signed in multiple steps.
///
/// It is permitted to sign a transaction with the same keypair multiple
/// times.
///
/// If `recent_blockhash` is different than recorded in the transaction message's
/// [`recent_blockhash`] field, then the message's `recent_blockhash` will be updated
/// to the provided `recent_blockhash`, and any prior signatures will be cleared.
///
/// [`recent_blockhash`]: Message::recent_blockhash
///
/// # Panics
///
/// Panics when signing fails, use [`Transaction::try_partial_sign`] to handle the error.
/// Panics when signing fails. Use [`Transaction::try_partial_sign`] to
/// handle the error. See the documentation for
/// [`Transaction::try_partial_sign`] for a full description of failure
/// conditions.
pub fn partial_sign<T: Signers>(&mut self, keypairs: &T, recent_blockhash: Hash) {
if let Err(e) = self.try_partial_sign(keypairs, recent_blockhash) {
panic!("Transaction::partial_sign failed with error {:?}", e);
}
}
/// Sign the transaction and place the signatures in their associated positions in `signatures`
/// without checking that the positions are correct.
/// Sign the transaction with a subset of required keys.
///
/// This places each of the signatures created from `keypairs` in the
/// corresponding position, as specified in the `positions` vector, in the
/// transactions [`signatures`] field. It does not verify that the signature
/// positions are correct.
///
/// [`signatures`]: Transaction::signatures
///
/// # Panics
///
/// Panics when signing fails, use [`Transaction::try_partial_sign_unchecked`] to handle the error.
/// Panics if signing fails. Use [`Transaction::try_partial_sign_unchecked`]
/// to handle the error.
pub fn partial_sign_unchecked<T: Signers>(
&mut self,
keypairs: &T,
@ -229,8 +764,88 @@ impl Transaction {
}
}
/// Check keys and keypair lengths, then sign this transaction, returning any signing errors
/// encountered
/// Sign the transaction, returning any errors.
///
/// This method fully signs a transaction with all required signers, which
/// must be present in the `keypairs` slice. To sign with only some of the
/// required signers, use [`Transaction::try_partial_sign`].
///
/// If `recent_blockhash` is different than recorded in the transaction message's
/// [`recent_blockhash`] field, then the message's `recent_blockhash` will be updated
/// to the provided `recent_blockhash`, and any prior signatures will be cleared.
///
/// [`recent_blockhash`]: Message::recent_blockhash
///
/// # Errors
///
/// Signing will fail if some required signers are not provided in
/// `keypairs`; or, if the transaction has previously been partially signed,
/// some of the remaining required signers are not provided in `keypairs`.
/// In other words, the transaction must be fully signed as a result of
/// calling this function. The error is [`SignerError::NotEnoughSigners`].
///
/// Signing will fail for any of the reasons described in the documentation
/// for [`Transaction::try_partial_sign`].
///
/// # Examples
///
/// This example uses the [`solana_client`] and [`anyhow`] crates.
///
/// [`solana_client`]: https://docs.rs/solana-client
/// [`anyhow`]: https://docs.rs/anyhow
///
/// ```
/// # use solana_sdk::example_mocks::solana_client;
/// use anyhow::Result;
/// use borsh::{BorshSerialize, BorshDeserialize};
/// use solana_client::rpc_client::RpcClient;
/// use solana_sdk::{
/// instruction::Instruction,
/// message::Message,
/// pubkey::Pubkey,
/// signature::{Keypair, Signer},
/// transaction::Transaction,
/// };
///
/// // A custom program instruction. This would typically be defined in
/// // another crate so it can be shared between the on-chain program and
/// // the client.
/// #[derive(BorshSerialize, BorshDeserialize)]
/// enum BankInstruction {
/// Initialize,
/// Deposit { lamports: u64 },
/// Withdraw { lamports: u64 },
/// }
///
/// fn send_initialize_tx(
/// client: &RpcClient,
/// program_id: Pubkey,
/// payer: &Keypair
/// ) -> Result<()> {
///
/// let bank_instruction = BankInstruction::Initialize;
///
/// let instruction = Instruction::new_with_borsh(
/// program_id,
/// &bank_instruction,
/// vec![],
/// );
///
/// let mut tx = Transaction::new_with_payer(&[instruction], Some(&payer.pubkey()));
/// let blockhash = client.get_latest_blockhash()?;
/// tx.try_sign(&[payer], blockhash)?;
/// client.send_and_confirm_transaction(&tx)?;
///
/// Ok(())
/// }
/// #
/// # let client = RpcClient::new(String::new());
/// # let program_id = Pubkey::new_unique();
/// # let payer = Keypair::new();
/// # send_initialize_tx(&client, program_id, &payer)?;
/// #
/// # Ok::<(), anyhow::Error>(())
/// ```
pub fn try_sign<T: Signers>(
&mut self,
keypairs: &T,
@ -245,9 +860,55 @@ impl Transaction {
}
}
/// Sign using some subset of required keys, returning any signing errors encountered. If
/// recent_blockhash is not the same as currently in the transaction, clear any prior
/// signatures and update recent_blockhash
/// Sign the transaction with a subset of required keys, returning any errors.
///
/// Unlike [`Transaction::try_sign`], this method does not require all
/// keypairs to be provided, allowing a transaction to be signed in multiple
/// steps.
///
/// It is permitted to sign a transaction with the same keypair multiple
/// times.
///
/// If `recent_blockhash` is different than recorded in the transaction message's
/// [`recent_blockhash`] field, then the message's `recent_blockhash` will be updated
/// to the provided `recent_blockhash`, and any prior signatures will be cleared.
///
/// [`recent_blockhash`]: Message::recent_blockhash
///
/// # Errors
///
/// Signing will fail if
///
/// - The transaction's [`Message`] is malformed such that the number of
/// required signatures recorded in its header
/// ([`num_required_signatures`]) is greater than the length of its
/// account keys ([`account_keys`]). The error is
/// [`SignerError::TransactionError`] where the interior
/// [`TransactionError`] is [`TransactionError::InvalidAccountIndex`].
/// - Any of the provided signers in `keypairs` is not a required signer of
/// the message. The error is [`SignerError::KeypairPubkeyMismatch`].
/// - Any of the signers is a [`Presigner`], and its provided signature is
/// incorrect. The error is [`SignerError::PresignerError`] where the
/// interior [`PresignerError`] is
/// [`PresignerError::VerificationFailure`].
/// - The signer is a [`RemoteKeypair`] and
/// - It does not understand the input provided ([`SignerError::InvalidInput`]).
/// - The device cannot be found ([`SignerError::NoDeviceFound`]).
/// - The user cancels the signing ([`SignerError::UserCancel`]).
/// - An error was encountered connecting ([`SignerError::Connection`]).
/// - Some device-specific protocol error occurs ([`SignerError::Protocol`]).
/// - Some other error occurs ([`SignerError::Custom`]).
///
/// See the documentation for the [`solana-remote-wallet`] crate for details
/// on the operation of [`RemoteKeypair`] signers.
///
/// [`num_required_signatures`]: crate::message::MessageHeader::num_required_signatures
/// [`account_keys`]: Message::account_keys
/// [`Presigner`]: crate::signer::presigner::Presigner
/// [`PresignerError`]: crate::signer::presigner::PresignerError
/// [`PresignerError::VerificationFailure`]: crate::signer::presigner::PresignerError::VerificationFailure
/// [`solana-remote-wallet`]: https://docs.rs/solana-remote-wallet/latest/
/// [`RemoteKeypair`]: https://docs.rs/solana-remote-wallet/latest/solana_remote_wallet/remote_keypair/struct.RemoteKeypair.html
pub fn try_partial_sign<T: Signers>(
&mut self,
keypairs: &T,
@ -261,9 +922,19 @@ impl Transaction {
self.try_partial_sign_unchecked(keypairs, positions, recent_blockhash)
}
/// Sign the transaction, returning any signing errors encountered, and place the
/// signatures in their associated positions in `signatures` without checking that the
/// Sign the transaction with a subset of required keys, returning any
/// errors.
///
/// This places each of the signatures created from `keypairs` in the
/// corresponding position, as specified in the `positions` vector, in the
/// transactions [`signatures`] field. It does not verify that the signature
/// positions are correct.
///
/// [`signatures`]: Transaction::signatures
///
/// # Errors
///
/// Returns an error if signing fails.
pub fn try_partial_sign_unchecked<T: Signers>(
&mut self,
keypairs: &T,
@ -285,7 +956,16 @@ impl Transaction {
Ok(())
}
/// Verify the transaction
/// Returns a signature that is not valid for signing this transaction.
pub fn get_invalid_signature() -> Signature {
Signature::default()
}
/// Verifies that all signers have signed the message.
///
/// # Errors
///
/// Returns [`TransactionError::SignatureFailure`] on error.
pub fn verify(&self) -> Result<()> {
let message_bytes = self.message_data();
if !self
@ -299,11 +979,11 @@ impl Transaction {
}
}
pub fn get_invalid_signature() -> Signature {
Signature::default()
}
/// Verify the transaction and hash its message
/// Verify the transaction and hash its message.
///
/// # Errors
///
/// Returns [`TransactionError::SignatureFailure`] on error.
pub fn verify_and_hash_message(&self) -> Result<Hash> {
let message_bytes = self.message_data();
if !self
@ -317,6 +997,10 @@ impl Transaction {
}
}
/// Verifies that all signers have signed the message.
///
/// Returns a vector with the length of required signatures, where each
/// element is either `true` if that signer has signed, or `false` if not.
pub fn verify_with_results(&self) -> Vec<bool> {
self._verify_with_results(&self.message_data())
}
@ -329,7 +1013,7 @@ impl Transaction {
.collect()
}
/// Verify the precompiled programs in this transaction
/// Verify the precompiled programs in this transaction.
pub fn verify_precompiles(&self, feature_set: &Arc<feature_set::FeatureSet>) -> Result<()> {
for instruction in &self.message().instructions {
// The Transaction may not be sanitized at this point
@ -349,7 +1033,9 @@ impl Transaction {
Ok(())
}
/// Get the positions of the pubkeys in `account_keys` associated with signing keypairs
/// Get the positions of the pubkeys in `account_keys` associated with signing keypairs.
///
/// [`account_keys`]: Message::account_keys
pub fn get_signing_keypair_positions(&self, pubkeys: &[Pubkey]) -> Result<Vec<Option<usize>>> {
if self.message.account_keys.len() < self.message.header.num_required_signatures as usize {
return Err(TransactionError::InvalidAccountIndex);
@ -363,7 +1049,7 @@ impl Transaction {
.collect())
}
/// Replace all the signatures and pubkeys
/// Replace all the signatures and pubkeys.
pub fn replace_signatures(&mut self, signers: &[(Pubkey, Signature)]) -> Result<()> {
let num_required_signatures = self.message.header.num_required_signatures as usize;
if signers.len() != num_required_signatures