Document message APIs (#22873)
* Document message APIs * Ignore clippy * Update sdk/program/src/message/mod.rs Co-authored-by: Tyera Eulberg <teulberg@gmail.com> * Fix new_with_blockhash example * Rename nonce_account_address in example Co-authored-by: Trent Nelson <trent.a.b.nelson@gmail.com> Co-authored-by: Tyera Eulberg <teulberg@gmail.com> Co-authored-by: Trent Nelson <trent.a.b.nelson@gmail.com>
This commit is contained in:
parent
a75c9378f2
commit
f7753ce85f
|
@ -0,0 +1,142 @@
|
||||||
|
//! 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)]
|
||||||
|
#![allow(clippy::new_without_default)]
|
||||||
|
|
||||||
|
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::super::solana_sdk::{
|
||||||
|
hash::Hash, signature::Signature, transaction::Transaction,
|
||||||
|
};
|
||||||
|
use super::client_error::Result as ClientResult;
|
||||||
|
|
||||||
|
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())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_minimum_balance_for_rent_exemption(
|
||||||
|
&self,
|
||||||
|
_data_len: usize,
|
||||||
|
) -> ClientResult<u64> {
|
||||||
|
Ok(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Re-exports and mocks of solana-program modules that mirror those from
|
||||||
|
/// solana-program.
|
||||||
|
///
|
||||||
|
/// This lets examples in solana-program appear to be written as client
|
||||||
|
/// programs.
|
||||||
|
pub mod solana_sdk {
|
||||||
|
pub use crate::hash;
|
||||||
|
pub use crate::instruction;
|
||||||
|
pub use crate::message;
|
||||||
|
pub use crate::nonce;
|
||||||
|
pub use crate::pubkey;
|
||||||
|
pub use crate::system_instruction;
|
||||||
|
|
||||||
|
pub mod signature {
|
||||||
|
use crate::pubkey::Pubkey;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct Signature;
|
||||||
|
|
||||||
|
pub struct Keypair;
|
||||||
|
|
||||||
|
impl Keypair {
|
||||||
|
pub fn new() -> Keypair {
|
||||||
|
Keypair
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pubkey(&self) -> Pubkey {
|
||||||
|
Pubkey::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Signer for Keypair {}
|
||||||
|
|
||||||
|
pub trait Signer {}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod signers {
|
||||||
|
use super::signature::Signer;
|
||||||
|
|
||||||
|
pub trait Signers {}
|
||||||
|
|
||||||
|
impl<T: Signer> Signers for [&T; 1] {}
|
||||||
|
impl<T: Signer> Signers for [&T; 2] {}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod transaction {
|
||||||
|
use super::signers::Signers;
|
||||||
|
use crate::hash::Hash;
|
||||||
|
use crate::instruction::Instruction;
|
||||||
|
use crate::message::Message;
|
||||||
|
use crate::pubkey::Pubkey;
|
||||||
|
|
||||||
|
pub struct Transaction {
|
||||||
|
pub message: Message,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Transaction {
|
||||||
|
pub fn new<T: Signers>(
|
||||||
|
_from_keypairs: &T,
|
||||||
|
_message: Message,
|
||||||
|
_recent_blockhash: Hash,
|
||||||
|
) -> Transaction {
|
||||||
|
Transaction {
|
||||||
|
message: Message::new(&[], None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_unsigned(_message: Message) -> Self {
|
||||||
|
Transaction {
|
||||||
|
message: Message::new(&[], None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_with_payer(_instructions: &[Instruction], _payer: Option<&Pubkey>) -> Self {
|
||||||
|
Transaction {
|
||||||
|
message: Message::new(&[], None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sign<T: Signers>(&mut self, _keypairs: &T, _recent_blockhash: Hash) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,6 +19,7 @@ pub mod ed25519_program;
|
||||||
pub mod entrypoint;
|
pub mod entrypoint;
|
||||||
pub mod entrypoint_deprecated;
|
pub mod entrypoint_deprecated;
|
||||||
pub mod epoch_schedule;
|
pub mod epoch_schedule;
|
||||||
|
pub mod example_mocks;
|
||||||
pub mod feature;
|
pub mod feature;
|
||||||
pub mod fee_calculator;
|
pub mod fee_calculator;
|
||||||
pub mod hash;
|
pub mod hash;
|
||||||
|
|
|
@ -1,5 +1,15 @@
|
||||||
|
//! The original and current Solana message format.
|
||||||
|
//!
|
||||||
|
//! This crate defines two versions of `Message` in their own modules:
|
||||||
|
//! [`legacy`] and [`v0`]. `legacy` is the current version as of Solana 1.10.0.
|
||||||
|
//! `v0` is a [future message format] that encodes more account keys into a
|
||||||
|
//! transaction than the legacy format.
|
||||||
|
//!
|
||||||
|
//! [`legacy`]: crate::message::legacy
|
||||||
|
//! [`v0`]: crate::message::v0
|
||||||
|
//! [future message format]: https://docs.solana.com/proposals/transactions-v2
|
||||||
|
|
||||||
#![allow(clippy::integer_arithmetic)]
|
#![allow(clippy::integer_arithmetic)]
|
||||||
//! A library for generating a message from a sequence of instructions
|
|
||||||
|
|
||||||
use {
|
use {
|
||||||
crate::{
|
crate::{
|
||||||
|
@ -163,6 +173,20 @@ fn get_program_ids(instructions: &[Instruction]) -> Vec<Pubkey> {
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A Solana transaction message (legacy).
|
||||||
|
///
|
||||||
|
/// See the [`message`] module documentation for further description.
|
||||||
|
///
|
||||||
|
/// [`message`]: crate::message
|
||||||
|
///
|
||||||
|
/// 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.
|
||||||
// NOTE: Serialization-related changes must be paired with the custom serialization
|
// NOTE: Serialization-related changes must be paired with the custom serialization
|
||||||
// for versioned messages in the `RemainingLegacyMessage` struct.
|
// for versioned messages in the `RemainingLegacyMessage` struct.
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
|
@ -170,12 +194,12 @@ fn get_program_ids(instructions: &[Instruction]) -> Vec<Pubkey> {
|
||||||
#[derive(Serialize, Deserialize, Default, Debug, PartialEq, Eq, Clone, AbiExample)]
|
#[derive(Serialize, Deserialize, Default, Debug, PartialEq, Eq, Clone, AbiExample)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Message {
|
pub struct Message {
|
||||||
/// The message header, identifying signed and read-only `account_keys`
|
/// The message header, identifying signed and read-only `account_keys`.
|
||||||
/// 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)]
|
||||||
pub header: MessageHeader,
|
pub header: MessageHeader,
|
||||||
|
|
||||||
/// All the account keys used by this transaction
|
/// All the account keys used by this transaction.
|
||||||
#[wasm_bindgen(skip)]
|
#[wasm_bindgen(skip)]
|
||||||
#[serde(with = "short_vec")]
|
#[serde(with = "short_vec")]
|
||||||
pub account_keys: Vec<Pubkey>,
|
pub account_keys: Vec<Pubkey>,
|
||||||
|
@ -227,30 +251,147 @@ impl Sanitize for Message {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Message {
|
impl Message {
|
||||||
pub fn new_with_compiled_instructions(
|
/// Create a new `Message`.
|
||||||
num_required_signatures: u8,
|
///
|
||||||
num_readonly_signed_accounts: u8,
|
/// # Examples
|
||||||
num_readonly_unsigned_accounts: u8,
|
///
|
||||||
account_keys: Vec<Pubkey>,
|
/// This example uses the [`solana_sdk`], [`solana_client`] and [`anyhow`] crates.
|
||||||
recent_blockhash: Hash,
|
///
|
||||||
instructions: Vec<CompiledInstruction>,
|
/// [`solana_sdk`]: https://docs.rs/solana-sdk
|
||||||
) -> Self {
|
/// [`solana_client`]: https://docs.rs/solana-client
|
||||||
Self {
|
/// [`anyhow`]: https://docs.rs/anyhow
|
||||||
header: MessageHeader {
|
///
|
||||||
num_required_signatures,
|
/// ```
|
||||||
num_readonly_signed_accounts,
|
/// # use solana_program::example_mocks::solana_sdk;
|
||||||
num_readonly_unsigned_accounts,
|
/// # use solana_program::example_mocks::solana_client;
|
||||||
},
|
/// use anyhow::Result;
|
||||||
account_keys,
|
/// use borsh::{BorshSerialize, BorshDeserialize};
|
||||||
recent_blockhash,
|
/// use solana_client::rpc_client::RpcClient;
|
||||||
instructions,
|
/// use solana_sdk::{
|
||||||
}
|
/// instruction::Instruction,
|
||||||
}
|
/// message::Message,
|
||||||
|
/// pubkey::Pubkey,
|
||||||
|
/// signature::Keypair,
|
||||||
|
/// 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(instructions: &[Instruction], payer: Option<&Pubkey>) -> Self {
|
pub fn new(instructions: &[Instruction], payer: Option<&Pubkey>) -> Self {
|
||||||
Self::new_with_blockhash(instructions, payer, &Hash::default())
|
Self::new_with_blockhash(instructions, payer, &Hash::default())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a new message while setting the blockhash.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// This example uses the [`solana_sdk`], [`solana_client`] and [`anyhow`] crates.
|
||||||
|
///
|
||||||
|
/// [`solana_sdk`]: https://docs.rs/solana-sdk
|
||||||
|
/// [`solana_client`]: https://docs.rs/solana-client
|
||||||
|
/// [`anyhow`]: https://docs.rs/anyhow
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use solana_program::example_mocks::solana_sdk;
|
||||||
|
/// # use solana_program::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,
|
||||||
|
/// 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 message = Message::new_with_blockhash(
|
||||||
|
/// &[instruction],
|
||||||
|
/// Some(&payer.pubkey()),
|
||||||
|
/// &blockhash,
|
||||||
|
/// );
|
||||||
|
///
|
||||||
|
/// let mut tx = Transaction::new_unsigned(message);
|
||||||
|
/// tx.sign(&[payer], tx.message.recent_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_blockhash(
|
pub fn new_with_blockhash(
|
||||||
instructions: &[Instruction],
|
instructions: &[Instruction],
|
||||||
payer: Option<&Pubkey>,
|
payer: Option<&Pubkey>,
|
||||||
|
@ -275,6 +416,112 @@ impl Message {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a new message for a [nonced transaction].
|
||||||
|
///
|
||||||
|
/// [nonced transaction]: https://docs.solana.com/implemented-proposals/durable-tx-nonces
|
||||||
|
///
|
||||||
|
/// In this type of transaction, the blockhash is replaced with a _durable
|
||||||
|
/// transaction nonce_, allowing for extended time to pass between the
|
||||||
|
/// transaction's signing and submission to the blockchain.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// This example uses the [`solana_sdk`], [`solana_client`] and [`anyhow`] crates.
|
||||||
|
///
|
||||||
|
/// [`solana_sdk`]: https://docs.rs/solana-sdk
|
||||||
|
/// [`solana_client`]: https://docs.rs/solana-client
|
||||||
|
/// [`anyhow`]: https://docs.rs/anyhow
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use solana_program::example_mocks::solana_sdk;
|
||||||
|
/// # use solana_program::example_mocks::solana_client;
|
||||||
|
/// use anyhow::Result;
|
||||||
|
/// use borsh::{BorshSerialize, BorshDeserialize};
|
||||||
|
/// use solana_client::rpc_client::RpcClient;
|
||||||
|
/// use solana_sdk::{
|
||||||
|
/// hash::Hash,
|
||||||
|
/// instruction::Instruction,
|
||||||
|
/// message::Message,
|
||||||
|
/// nonce,
|
||||||
|
/// pubkey::Pubkey,
|
||||||
|
/// signature::Keypair,
|
||||||
|
/// system_instruction,
|
||||||
|
/// 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 },
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// // Create a nonced transaction for later signing and submission,
|
||||||
|
/// // returning it and the nonce account's pubkey.
|
||||||
|
/// fn create_offline_initialize_tx(
|
||||||
|
/// client: &RpcClient,
|
||||||
|
/// program_id: Pubkey,
|
||||||
|
/// payer: &Keypair
|
||||||
|
/// ) -> Result<(Transaction, Pubkey)> {
|
||||||
|
///
|
||||||
|
/// let bank_instruction = BankInstruction::Initialize;
|
||||||
|
/// let bank_instruction = Instruction::new_with_borsh(
|
||||||
|
/// program_id,
|
||||||
|
/// &bank_instruction,
|
||||||
|
/// vec![],
|
||||||
|
/// );
|
||||||
|
///
|
||||||
|
/// // This will create a nonce account and assign authority to the
|
||||||
|
/// // payer so they can sign to advance the nonce and withdraw its rent.
|
||||||
|
/// let nonce_account = make_nonce_account(client, payer)?;
|
||||||
|
///
|
||||||
|
/// let mut message = Message::new_with_nonce(
|
||||||
|
/// vec![bank_instruction],
|
||||||
|
/// Some(&payer.pubkey()),
|
||||||
|
/// &nonce_account,
|
||||||
|
/// &payer.pubkey()
|
||||||
|
/// );
|
||||||
|
///
|
||||||
|
/// // This transaction will need to be signed later, using the blockhash
|
||||||
|
/// // stored in the nonce account.
|
||||||
|
/// let tx = Transaction::new_unsigned(message);
|
||||||
|
///
|
||||||
|
/// Ok((tx, nonce_account))
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// fn make_nonce_account(client: &RpcClient, payer: &Keypair)
|
||||||
|
/// -> Result<Pubkey>
|
||||||
|
/// {
|
||||||
|
/// let nonce_account_address = Keypair::new();
|
||||||
|
/// let nonce_account_size = nonce::State::size();
|
||||||
|
/// let nonce_rent = client.get_minimum_balance_for_rent_exemption(nonce_account_size)?;
|
||||||
|
///
|
||||||
|
/// // Assigning the nonce authority to the payer so they can sign for the withdrawal,
|
||||||
|
/// // and we can throw away the nonce address secret key.
|
||||||
|
/// let create_nonce_instr = system_instruction::create_nonce_account(
|
||||||
|
/// &payer.pubkey(),
|
||||||
|
/// &nonce_account_address.pubkey(),
|
||||||
|
/// &payer.pubkey(),
|
||||||
|
/// nonce_rent,
|
||||||
|
/// );
|
||||||
|
///
|
||||||
|
/// let mut nonce_tx = Transaction::new_with_payer(&create_nonce_instr, Some(&payer.pubkey()));
|
||||||
|
/// let blockhash = client.get_latest_blockhash()?;
|
||||||
|
/// nonce_tx.sign(&[&payer, &nonce_account_address], blockhash);
|
||||||
|
/// client.send_and_confirm_transaction(&nonce_tx)?;
|
||||||
|
///
|
||||||
|
/// Ok(nonce_account_address.pubkey())
|
||||||
|
/// }
|
||||||
|
/// #
|
||||||
|
/// # let client = RpcClient::new(String::new());
|
||||||
|
/// # let program_id = Pubkey::new_unique();
|
||||||
|
/// # let payer = Keypair::new();
|
||||||
|
/// # create_offline_initialize_tx(&client, program_id, &payer)?;
|
||||||
|
/// # Ok::<(), anyhow::Error>(())
|
||||||
|
/// ```
|
||||||
pub fn new_with_nonce(
|
pub fn new_with_nonce(
|
||||||
mut instructions: Vec<Instruction>,
|
mut instructions: Vec<Instruction>,
|
||||||
payer: Option<&Pubkey>,
|
payer: Option<&Pubkey>,
|
||||||
|
@ -287,14 +534,34 @@ impl Message {
|
||||||
Self::new(&instructions, payer)
|
Self::new(&instructions, payer)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compute the blake3 hash of this transaction's message
|
pub fn new_with_compiled_instructions(
|
||||||
|
num_required_signatures: u8,
|
||||||
|
num_readonly_signed_accounts: u8,
|
||||||
|
num_readonly_unsigned_accounts: u8,
|
||||||
|
account_keys: Vec<Pubkey>,
|
||||||
|
recent_blockhash: Hash,
|
||||||
|
instructions: Vec<CompiledInstruction>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
header: MessageHeader {
|
||||||
|
num_required_signatures,
|
||||||
|
num_readonly_signed_accounts,
|
||||||
|
num_readonly_unsigned_accounts,
|
||||||
|
},
|
||||||
|
account_keys,
|
||||||
|
recent_blockhash,
|
||||||
|
instructions,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compute the blake3 hash of this transaction's message.
|
||||||
#[cfg(not(target_arch = "bpf"))]
|
#[cfg(not(target_arch = "bpf"))]
|
||||||
pub fn hash(&self) -> Hash {
|
pub fn hash(&self) -> Hash {
|
||||||
let message_bytes = self.serialize();
|
let message_bytes = self.serialize();
|
||||||
Self::hash_raw_message(&message_bytes)
|
Self::hash_raw_message(&message_bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compute the blake3 hash of a raw transaction message
|
/// Compute the blake3 hash of a raw transaction message.
|
||||||
#[cfg(not(target_arch = "bpf"))]
|
#[cfg(not(target_arch = "bpf"))]
|
||||||
pub fn hash_raw_message(message_bytes: &[u8]) -> Hash {
|
pub fn hash_raw_message(message_bytes: &[u8]) -> Hash {
|
||||||
use blake3::traits::digest::Digest;
|
use blake3::traits::digest::Digest;
|
||||||
|
@ -415,7 +682,7 @@ impl Message {
|
||||||
self.account_keys[..last_key].iter().collect()
|
self.account_keys[..last_key].iter().collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return true if account_keys has any duplicate keys
|
/// Returns `true` if `account_keys` has any duplicate keys.
|
||||||
pub fn has_duplicates(&self) -> bool {
|
pub fn has_duplicates(&self) -> bool {
|
||||||
// Note: This is an O(n^2) algorithm, but requires no heap allocations. The benchmark
|
// Note: This is an O(n^2) algorithm, but requires no heap allocations. The benchmark
|
||||||
// `bench_has_duplicates` in benches/message_processor.rs shows that this implementation is
|
// `bench_has_duplicates` in benches/message_processor.rs shows that this implementation is
|
||||||
|
@ -429,7 +696,7 @@ impl Message {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if any account is the bpf upgradeable loader
|
/// Returns `true` if any account is the BPF upgradeable loader.
|
||||||
pub fn is_upgradeable_loader_present(&self) -> bool {
|
pub fn is_upgradeable_loader_present(&self) -> bool {
|
||||||
self.account_keys
|
self.account_keys
|
||||||
.iter()
|
.iter()
|
||||||
|
|
|
@ -1,4 +1,41 @@
|
||||||
//! A library for generating a message from a sequence of instructions
|
//! Sequences of [`Instruction`]s executed within a single transaction.
|
||||||
|
//!
|
||||||
|
//! [`Instruction`]: crate::instruction::Instruction
|
||||||
|
//!
|
||||||
|
//! In Solana, programs execute instructions, and clients submit sequences
|
||||||
|
//! of instructions to the network to be atomically executed as [`Transaction`]s.
|
||||||
|
//!
|
||||||
|
//! [`Transaction`]: https://docs.rs/solana-sdk/latest/solana-sdk/transaction/struct.Transaction.html
|
||||||
|
//!
|
||||||
|
//! A [`Message`] is the compact internal encoding of a transaction, as
|
||||||
|
//! transmitted across the network and stored in, and operated on, by the
|
||||||
|
//! runtime. It contains a flat array of all accounts accessed by all
|
||||||
|
//! instructions in the message, a [`MessageHeader`] that describes the layout
|
||||||
|
//! of that account array, a [recent blockhash], and a compact encoding of the
|
||||||
|
//! message's instructions.
|
||||||
|
//!
|
||||||
|
//! [recent blockhash]: https://docs.solana.com/developing/programming-model/transactions#recent-blockhash
|
||||||
|
//!
|
||||||
|
//! Clients most often deal with `Instruction`s and `Transaction`s, with
|
||||||
|
//! `Message`s being created by `Transaction` constructors.
|
||||||
|
//!
|
||||||
|
//! To ensure reliable network delivery, serialized messages must fit into the
|
||||||
|
//! IPv6 MTU size, conservatively assumed to be 1280 bytes. Thus constrained,
|
||||||
|
//! care must be taken in the amount of data consumed by instructions, and the
|
||||||
|
//! number of accounts they require to function.
|
||||||
|
//!
|
||||||
|
//! This module defines two versions of `Message` in their own modules:
|
||||||
|
//! [`legacy`] and [`v0`]. `legacy` is reexported here and is the current
|
||||||
|
//! version as of Solana 1.10.0. `v0` is a [future message format] that encodes
|
||||||
|
//! more account keys into a transaction than the legacy format. The
|
||||||
|
//! [`VersionedMessage`] type is a thin wrapper around either message version.
|
||||||
|
//!
|
||||||
|
//! [future message format]: https://docs.solana.com/proposals/transactions-v2
|
||||||
|
//!
|
||||||
|
//! Despite living in the `solana-program` crate, there is no way to access the
|
||||||
|
//! runtime's messages from within a Solana program, and only the legacy message
|
||||||
|
//! types continue to be exposed to Solana programs, for backwards compatibility
|
||||||
|
//! reasons.
|
||||||
|
|
||||||
pub mod legacy;
|
pub mod legacy;
|
||||||
|
|
||||||
|
@ -16,23 +53,56 @@ pub use legacy::Message;
|
||||||
#[cfg(not(target_arch = "bpf"))]
|
#[cfg(not(target_arch = "bpf"))]
|
||||||
pub use non_bpf_modules::*;
|
pub use non_bpf_modules::*;
|
||||||
|
|
||||||
/// The length of a message header in bytes
|
/// The length of a message header in bytes.
|
||||||
pub const MESSAGE_HEADER_LENGTH: usize = 3;
|
pub const MESSAGE_HEADER_LENGTH: usize = 3;
|
||||||
|
|
||||||
|
/// Describes the organization of a `Message`'s account keys.
|
||||||
|
///
|
||||||
|
/// Every [`Instruction`] specifies which accounts it may reference, or
|
||||||
|
/// otherwise requires specific permissions of. Those specifications are:
|
||||||
|
/// whether the account is read-only, or read-write; and whether the account
|
||||||
|
/// must have signed the transaction containing the instruction.
|
||||||
|
///
|
||||||
|
/// Whereas individual `Instruction`s contain a list of all accounts they may
|
||||||
|
/// access, along with their required permissions, a `Message` contains a
|
||||||
|
/// single shared flat list of _all_ accounts required by _all_ instructions in
|
||||||
|
/// a transaction. When building a `Message`, this flat list is created and
|
||||||
|
/// `Instruction`s are converted to [`CompiledInstruction`]s. Those
|
||||||
|
/// `CompiledInstruction`s then reference by index the accounts they require in
|
||||||
|
/// the single shared account list.
|
||||||
|
///
|
||||||
|
/// [`Instruction`]: crate::instruction::Instruction
|
||||||
|
/// [`CompiledInstruction`]: crate::instruction::CompiledInstruction
|
||||||
|
///
|
||||||
|
/// The shared account list is ordered by the permissions required of the accounts:
|
||||||
|
///
|
||||||
|
/// - accounts that are writable and signers
|
||||||
|
/// - accounts that are read-only and signers
|
||||||
|
/// - accounts that are writable and not signers
|
||||||
|
/// - accounts that are read-only and not signers
|
||||||
|
///
|
||||||
|
/// Given this ordering, the fields of `MessageHeader` describe which accounts
|
||||||
|
/// in a transaction require which permissions.
|
||||||
|
///
|
||||||
|
/// When multiple transactions access the same read-only accounts, the runtime
|
||||||
|
/// may process them in parallel, in a single [PoH] entry. Transactions that
|
||||||
|
/// access the same read-write accounts are processed sequentially.
|
||||||
|
///
|
||||||
|
/// [PoH]: https://docs.solana.com/cluster/synchronization
|
||||||
#[derive(Serialize, Deserialize, Default, Debug, PartialEq, Eq, Clone, Copy, AbiExample)]
|
#[derive(Serialize, Deserialize, Default, Debug, PartialEq, Eq, Clone, Copy, AbiExample)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct MessageHeader {
|
pub struct MessageHeader {
|
||||||
/// The number of signatures required for this message to be considered valid. The
|
/// The number of signatures required for this message to be considered
|
||||||
/// signatures must match the first `num_required_signatures` of `account_keys`.
|
/// valid. The signers of those signatures must match the first
|
||||||
/// NOTE: Serialization-related changes must be paired with the direct read at sigverify.
|
/// `num_required_signatures` of [`Message::account_keys`].
|
||||||
|
// NOTE: Serialization-related changes must be paired with the direct read at sigverify.
|
||||||
pub num_required_signatures: u8,
|
pub num_required_signatures: u8,
|
||||||
|
|
||||||
/// The last num_readonly_signed_accounts of the signed keys are read-only accounts. Programs
|
/// The last `num_readonly_signed_accounts` of the signed keys are read-only
|
||||||
/// may process multiple transactions that load read-only accounts within a single PoH entry,
|
/// accounts.
|
||||||
/// but are not permitted to credit or debit lamports or modify account data. Transactions
|
|
||||||
/// targeting the same read-write account are evaluated sequentially.
|
|
||||||
pub num_readonly_signed_accounts: u8,
|
pub num_readonly_signed_accounts: u8,
|
||||||
|
|
||||||
/// The last num_readonly_unsigned_accounts of the unsigned keys are read-only accounts.
|
/// The last `num_readonly_unsigned_accounts` of the unsigned keys are
|
||||||
|
/// read-only accounts.
|
||||||
pub num_readonly_unsigned_accounts: u8,
|
pub num_readonly_unsigned_accounts: u8,
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,8 +18,7 @@ use {
|
||||||
thiserror::Error,
|
thiserror::Error,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Sanitized message of a transaction which includes a set of atomic
|
/// Sanitized message of a transaction.
|
||||||
/// instructions to be executed on-chain
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum SanitizedMessage {
|
pub enum SanitizedMessage {
|
||||||
/// Sanitized legacy message
|
/// Sanitized legacy message
|
||||||
|
|
|
@ -20,7 +20,7 @@ pub mod v0;
|
||||||
/// Bit mask that indicates whether a serialized message is versioned.
|
/// Bit mask that indicates whether a serialized message is versioned.
|
||||||
pub const MESSAGE_VERSION_PREFIX: u8 = 0x80;
|
pub const MESSAGE_VERSION_PREFIX: u8 = 0x80;
|
||||||
|
|
||||||
/// Message versions supported by the Solana runtime.
|
/// Either a legacy message or a v0 message.
|
||||||
///
|
///
|
||||||
/// # Serialization
|
/// # Serialization
|
||||||
///
|
///
|
||||||
|
|
|
@ -1,3 +1,14 @@
|
||||||
|
//! A future Solana message format.
|
||||||
|
//!
|
||||||
|
//! This crate defines two versions of `Message` in their own modules:
|
||||||
|
//! [`legacy`] and [`v0`]. `legacy` is the current version as of Solana 1.10.0.
|
||||||
|
//! `v0` is a [future message format] that encodes more account keys into a
|
||||||
|
//! transaction than the legacy format.
|
||||||
|
//!
|
||||||
|
//! [`legacy`]: crate::message::legacy
|
||||||
|
//! [`v0`]: crate::message::v0
|
||||||
|
//! [future message format]: https://docs.solana.com/proposals/transactions-v2
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
hash::Hash,
|
hash::Hash,
|
||||||
instruction::CompiledInstruction,
|
instruction::CompiledInstruction,
|
||||||
|
@ -26,8 +37,14 @@ pub struct MessageAddressTableLookup {
|
||||||
pub readonly_indexes: Vec<u8>,
|
pub readonly_indexes: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Transaction message format which supports succinct account loading with
|
/// A Solana transaction message (v0).
|
||||||
|
///
|
||||||
|
/// This message format supports succinct account loading with
|
||||||
/// on-chain address lookup tables.
|
/// on-chain address lookup tables.
|
||||||
|
///
|
||||||
|
/// See the [`message`] module documentation for further description.
|
||||||
|
///
|
||||||
|
/// [`message`]: crate::message
|
||||||
#[derive(Serialize, Deserialize, Default, Debug, PartialEq, Eq, Clone, AbiExample)]
|
#[derive(Serialize, Deserialize, Default, Debug, PartialEq, Eq, Clone, AbiExample)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Message {
|
pub struct Message {
|
||||||
|
|
Loading…
Reference in New Issue