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"
|
name = "solana-sdk"
|
||||||
version = "1.10.0"
|
version = "1.10.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
"assert_matches",
|
"assert_matches",
|
||||||
"base64 0.13.0",
|
"base64 0.13.0",
|
||||||
"bincode",
|
"bincode",
|
||||||
|
|
|
@ -84,6 +84,7 @@ wasm-bindgen = "0.2"
|
||||||
js-sys = "0.3.55"
|
js-sys = "0.3.55"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
anyhow = "1.0.45"
|
||||||
curve25519-dalek = "3.2.0"
|
curve25519-dalek = "3.2.0"
|
||||||
tiny-bip39 = "0.8.2"
|
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;
|
||||||
pub mod entrypoint_deprecated;
|
pub mod entrypoint_deprecated;
|
||||||
pub mod epoch_info;
|
pub mod epoch_info;
|
||||||
|
pub mod example_mocks;
|
||||||
pub mod exit;
|
pub mod exit;
|
||||||
pub mod feature;
|
pub mod feature;
|
||||||
pub mod feature_set;
|
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")]
|
#![cfg(feature = "full")]
|
||||||
|
|
||||||
|
@ -38,16 +147,38 @@ pub enum TransactionVerificationMode {
|
||||||
|
|
||||||
pub type Result<T> = result::Result<T, TransactionError>;
|
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]
|
#[wasm_bindgen]
|
||||||
#[frozen_abi(digest = "FZtncnS1Xk8ghHfKiXE5oGiUbw2wJhmfXQuNgQR3K6Mc")]
|
#[frozen_abi(digest = "FZtncnS1Xk8ghHfKiXE5oGiUbw2wJhmfXQuNgQR3K6Mc")]
|
||||||
#[derive(Debug, PartialEq, Default, Eq, Clone, Serialize, Deserialize, AbiExample)]
|
#[derive(Debug, PartialEq, Default, Eq, Clone, Serialize, Deserialize, AbiExample)]
|
||||||
pub struct Transaction {
|
pub struct Transaction {
|
||||||
/// A set of digital signatures of a serialized [`Message`], signed by the
|
/// A set of signatures of a serialized [`Message`], signed by the first
|
||||||
/// first `signatures.len()` keys of [`account_keys`].
|
/// 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
|
/// [`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.
|
// NOTE: Serialization-related changes must be paired with the direct read at sigverify.
|
||||||
#[wasm_bindgen(skip)]
|
#[wasm_bindgen(skip)]
|
||||||
#[serde(with = "short_vec")]
|
#[serde(with = "short_vec")]
|
||||||
|
@ -71,6 +202,72 @@ impl Sanitize for Transaction {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl 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 {
|
pub fn new_unsigned(message: Message) -> Self {
|
||||||
Self {
|
Self {
|
||||||
signatures: vec![Signature::default(); message.header.num_required_signatures as usize],
|
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 {
|
/// Create a fully-signed transaction from a [`Message`].
|
||||||
let message = Message::new(instructions, payer);
|
|
||||||
Self::new_unsigned(message)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a signed transaction with the given payer.
|
|
||||||
///
|
///
|
||||||
/// # Panics
|
/// # Panics
|
||||||
///
|
///
|
||||||
/// Panics when signing fails.
|
/// Panics when signing fails. See [`Transaction::try_sign`] and
|
||||||
pub fn new_signed_with_payer<T: Signers>(
|
/// [`Transaction::try_partial_sign`] for a full description of failure
|
||||||
instructions: &[Instruction],
|
/// scenarios.
|
||||||
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
|
/// # 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>(
|
pub fn new<T: Signers>(
|
||||||
from_keypairs: &T,
|
from_keypairs: &T,
|
||||||
message: Message,
|
message: Message,
|
||||||
|
@ -113,7 +356,165 @@ impl Transaction {
|
||||||
tx
|
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.
|
/// * `from_keypairs` - The keys used to sign the transaction.
|
||||||
/// * `keys` - The keys for the transaction. These are the program state
|
/// * `keys` - The keys for the transaction. These are the program state
|
||||||
/// instances or lamport recipient keys.
|
/// instances or lamport recipient keys.
|
||||||
|
@ -123,7 +524,8 @@ impl Transaction {
|
||||||
///
|
///
|
||||||
/// # Panics
|
/// # 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>(
|
pub fn new_with_compiled_instructions<T: Signers>(
|
||||||
from_keypairs: &T,
|
from_keypairs: &T,
|
||||||
keys: &[Pubkey],
|
keys: &[Pubkey],
|
||||||
|
@ -146,6 +548,17 @@ impl Transaction {
|
||||||
Transaction::new(from_keypairs, message, recent_blockhash)
|
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] {
|
pub fn data(&self, instruction_index: usize) -> &[u8] {
|
||||||
&self.message.instructions[instruction_index].data
|
&self.message.instructions[instruction_index].data
|
||||||
}
|
}
|
||||||
|
@ -158,11 +571,41 @@ impl Transaction {
|
||||||
.map(|&account_keys_index| account_keys_index as usize)
|
.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> {
|
pub fn key(&self, instruction_index: usize, accounts_index: usize) -> Option<&Pubkey> {
|
||||||
self.key_index(instruction_index, accounts_index)
|
self.key_index(instruction_index, accounts_index)
|
||||||
.and_then(|account_keys_index| self.message.account_keys.get(account_keys_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> {
|
pub fn signer_key(&self, instruction_index: usize, accounts_index: usize) -> Option<&Pubkey> {
|
||||||
match self.key_index(instruction_index, accounts_index) {
|
match self.key_index(instruction_index, accounts_index) {
|
||||||
None => None,
|
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 {
|
pub fn message(&self) -> &Message {
|
||||||
&self.message
|
&self.message
|
||||||
}
|
}
|
||||||
|
@ -185,36 +628,128 @@ impl Transaction {
|
||||||
self.message().serialize()
|
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
|
||||||
///
|
///
|
||||||
/// 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) {
|
pub fn sign<T: Signers>(&mut self, keypairs: &T, recent_blockhash: Hash) {
|
||||||
if let Err(e) = self.try_sign(keypairs, recent_blockhash) {
|
if let Err(e) = self.try_sign(keypairs, recent_blockhash) {
|
||||||
panic!("Transaction::sign failed with error {:?}", e);
|
panic!("Transaction::sign failed with error {:?}", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sign using some subset of required keys
|
/// Sign the transaction with a subset of required keys.
|
||||||
/// if recent_blockhash is not the same as currently in the transaction,
|
///
|
||||||
/// clear any prior signatures and update recent_blockhash
|
/// 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
|
||||||
///
|
///
|
||||||
/// 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) {
|
pub fn partial_sign<T: Signers>(&mut self, keypairs: &T, recent_blockhash: Hash) {
|
||||||
if let Err(e) = self.try_partial_sign(keypairs, recent_blockhash) {
|
if let Err(e) = self.try_partial_sign(keypairs, recent_blockhash) {
|
||||||
panic!("Transaction::partial_sign failed with error {:?}", e);
|
panic!("Transaction::partial_sign failed with error {:?}", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sign the transaction and place the signatures in their associated positions in `signatures`
|
/// Sign the transaction with a subset of required keys.
|
||||||
/// without checking that the positions are correct.
|
///
|
||||||
|
/// 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
|
||||||
///
|
///
|
||||||
/// 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>(
|
pub fn partial_sign_unchecked<T: Signers>(
|
||||||
&mut self,
|
&mut self,
|
||||||
keypairs: &T,
|
keypairs: &T,
|
||||||
|
@ -229,8 +764,88 @@ impl Transaction {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check keys and keypair lengths, then sign this transaction, returning any signing errors
|
/// Sign the transaction, returning any errors.
|
||||||
/// encountered
|
///
|
||||||
|
/// 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>(
|
pub fn try_sign<T: Signers>(
|
||||||
&mut self,
|
&mut self,
|
||||||
keypairs: &T,
|
keypairs: &T,
|
||||||
|
@ -245,9 +860,55 @@ impl Transaction {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sign using some subset of required keys, returning any signing errors encountered. If
|
/// Sign the transaction with a subset of required keys, returning any errors.
|
||||||
/// recent_blockhash is not the same as currently in the transaction, clear any prior
|
///
|
||||||
/// signatures and update recent_blockhash
|
/// 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>(
|
pub fn try_partial_sign<T: Signers>(
|
||||||
&mut self,
|
&mut self,
|
||||||
keypairs: &T,
|
keypairs: &T,
|
||||||
|
@ -261,9 +922,19 @@ impl Transaction {
|
||||||
self.try_partial_sign_unchecked(keypairs, positions, recent_blockhash)
|
self.try_partial_sign_unchecked(keypairs, positions, recent_blockhash)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sign the transaction, returning any signing errors encountered, and place the
|
/// Sign the transaction with a subset of required keys, returning any
|
||||||
/// signatures in their associated positions in `signatures` without checking that the
|
/// 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.
|
/// positions are correct.
|
||||||
|
///
|
||||||
|
/// [`signatures`]: Transaction::signatures
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Returns an error if signing fails.
|
||||||
pub fn try_partial_sign_unchecked<T: Signers>(
|
pub fn try_partial_sign_unchecked<T: Signers>(
|
||||||
&mut self,
|
&mut self,
|
||||||
keypairs: &T,
|
keypairs: &T,
|
||||||
|
@ -285,7 +956,16 @@ impl Transaction {
|
||||||
Ok(())
|
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<()> {
|
pub fn verify(&self) -> Result<()> {
|
||||||
let message_bytes = self.message_data();
|
let message_bytes = self.message_data();
|
||||||
if !self
|
if !self
|
||||||
|
@ -299,11 +979,11 @@ impl Transaction {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_invalid_signature() -> Signature {
|
/// Verify the transaction and hash its message.
|
||||||
Signature::default()
|
///
|
||||||
}
|
/// # Errors
|
||||||
|
///
|
||||||
/// Verify the transaction and hash its message
|
/// Returns [`TransactionError::SignatureFailure`] on error.
|
||||||
pub fn verify_and_hash_message(&self) -> Result<Hash> {
|
pub fn verify_and_hash_message(&self) -> Result<Hash> {
|
||||||
let message_bytes = self.message_data();
|
let message_bytes = self.message_data();
|
||||||
if !self
|
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> {
|
pub fn verify_with_results(&self) -> Vec<bool> {
|
||||||
self._verify_with_results(&self.message_data())
|
self._verify_with_results(&self.message_data())
|
||||||
}
|
}
|
||||||
|
@ -329,7 +1013,7 @@ impl Transaction {
|
||||||
.collect()
|
.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<()> {
|
pub fn verify_precompiles(&self, feature_set: &Arc<feature_set::FeatureSet>) -> Result<()> {
|
||||||
for instruction in &self.message().instructions {
|
for instruction in &self.message().instructions {
|
||||||
// The Transaction may not be sanitized at this point
|
// The Transaction may not be sanitized at this point
|
||||||
|
@ -349,7 +1033,9 @@ impl Transaction {
|
||||||
Ok(())
|
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>>> {
|
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 {
|
if self.message.account_keys.len() < self.message.header.num_required_signatures as usize {
|
||||||
return Err(TransactionError::InvalidAccountIndex);
|
return Err(TransactionError::InvalidAccountIndex);
|
||||||
|
@ -363,7 +1049,7 @@ impl Transaction {
|
||||||
.collect())
|
.collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Replace all the signatures and pubkeys
|
/// Replace all the signatures and pubkeys.
|
||||||
pub fn replace_signatures(&mut self, signers: &[(Pubkey, Signature)]) -> Result<()> {
|
pub fn replace_signatures(&mut self, signers: &[(Pubkey, Signature)]) -> Result<()> {
|
||||||
let num_required_signatures = self.message.header.num_required_signatures as usize;
|
let num_required_signatures = self.message.header.num_required_signatures as usize;
|
||||||
if signers.len() != num_required_signatures
|
if signers.len() != num_required_signatures
|
||||||
|
|
Loading…
Reference in New Issue