Document transaction module (#22440)
* Document transaction module * example_mocks is only for feature = full
This commit is contained in:
parent
a7f2fff219
commit
8dd62854fa
|
@ -5659,6 +5659,7 @@ dependencies = [
|
|||
name = "solana-sdk"
|
||||
version = "1.10.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"assert_matches",
|
||||
"base64 0.13.0",
|
||||
"bincode",
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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 — 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
|
||||
|
|
Loading…
Reference in New Issue