rialo-s-message 0.4.1

Solana transaction message types.
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![cfg_attr(feature = "frozen-abi", feature(min_specialization))]
//! Sequences of [`Instruction`]s executed within a single transaction.
//!
//! This module also defines the [`ConfigHashPrefix`] type used for replay protection.
//!
//! [`Instruction`]: https://docs.rs/solana-instruction/latest/rialo_s_instruction/struct.Instruction.html
//!
//! 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://solana.com/docs/core/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.solanalabs.com/proposals/versioned-transactions
//!
//! 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 compiled_instruction;
mod compiled_keys;
pub mod inner_instruction;
pub mod legacy;
#[cfg(feature = "frozen-abi")]
use rialo_frozen_abi_macro::AbiExample;
#[cfg(feature = "serde")]
use serde_derive::{Deserialize, Serialize};

#[cfg(not(target_os = "solana"))]
#[path = ""]
mod non_bpf_modules {
    mod account_keys;
    mod sanitized;
    mod versions;

    pub use account_keys::*;
    pub use sanitized::*;
    pub use versions::*;
}

pub use compiled_keys::CompileError;
pub use legacy::Message;
#[cfg(not(target_os = "solana"))]
pub use non_bpf_modules::*;

/// The length of a message header in bytes.
pub const MESSAGE_HEADER_LENGTH: usize = 3;

/// Config hash prefix for transaction replay protection across chains.
///
/// This type wraps a `u64` value representing the first 64 bits of the validator
/// configuration hash. Every transaction must include the correct config hash
/// prefix matching the target chain's validator configuration.
///
/// # Purpose
///
/// The config hash prefix provides replay protection by ensuring transactions
/// are only valid on the intended chain. Transactions with mismatched config
/// hashes are rejected with `InvalidConfigHashPrefix` error.
///
/// # Obtaining the Value
///
/// In production code, fetch the config hash prefix from the network using
/// the `getRecentValidatorConfigHash` RPC method:
///
/// ```ignore
/// let config_hash = client.get_config_hash_prefix().await?;
/// let message = Message::new(&instructions, Some(&payer), config_hash);
/// ```
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize), serde(transparent))]
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ConfigHashPrefix(u64);

impl ConfigHashPrefix {
    /// Creates a new `ConfigHashPrefix` from a raw `u64` value.
    pub const fn new(value: u64) -> Self {
        Self(value)
    }

    /// Returns the underlying `u64` value.
    pub const fn value(&self) -> u64 {
        self.0
    }

    /// Returns the underlying `u64` value as little-endian bytes.
    pub const fn to_le_bytes(&self) -> [u8; 8] {
        self.0.to_le_bytes()
    }

    /// Creates a `ConfigHashPrefix` from little-endian bytes.
    pub const fn from_le_bytes(bytes: [u8; 8]) -> Self {
        Self(u64::from_le_bytes(bytes))
    }

    /// Wrapping (modular) addition. Computes `self + rhs`, wrapping around at
    /// the boundary of the type.
    pub const fn wrapping_add(self, rhs: u64) -> Self {
        Self(self.0.wrapping_add(rhs))
    }
}

impl From<u64> for ConfigHashPrefix {
    fn from(value: u64) -> Self {
        Self(value)
    }
}

impl From<ConfigHashPrefix> for u64 {
    fn from(config_hash: ConfigHashPrefix) -> Self {
        config_hash.0
    }
}

impl core::fmt::Display for ConfigHashPrefix {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        write!(f, "0x{:016X}", self.0)
    }
}

impl core::str::FromStr for ConfigHashPrefix {
    type Err = core::num::ParseIntError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        // Support both "0x" prefixed hex and plain decimal
        let value = if let Some(hex) = s.strip_prefix("0x").or_else(|| s.strip_prefix("0X")) {
            u64::from_str_radix(hex, 16)?
        } else {
            s.parse::<u64>()?
        };
        Ok(Self(value))
    }
}

/// Example config hash prefix value for use in documentation and examples.
///
/// **Important:** This is a placeholder value for documentation purposes only.
/// In production code, you must fetch the actual config hash prefix from the
/// network using the `getRecentValidatorConfigHash` RPC method.
///
/// The value `0xDEAD_BEEF_CAFE_BABE` is deliberately chosen to be obviously
/// fake and easily recognizable in logs/debugging if accidentally used.
///
/// # Example
///
/// ```ignore
/// // In real code, fetch from network:
/// let config_hash_prefix = client.get_config_hash_prefix().await?;
///
/// // For documentation/examples only:
/// use rialo_s_message::EXAMPLE_CONFIG_HASH;
/// let message = Message::new(&instructions, Some(&payer), EXAMPLE_CONFIG_HASH);
/// ```
pub const EXAMPLE_CONFIG_HASH: ConfigHashPrefix = ConfigHashPrefix::new(0xDEAD_BEEF_CAFE_BABE);

/// 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`]: https://docs.rs/solana-instruction/latest/rialo_s_instruction/struct.Instruction.html
/// [`CompiledInstruction`]: crate::compiled_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.solanalabs.com/consensus/synchronization
#[cfg_attr(feature = "frozen-abi", derive(AbiExample))]
#[cfg_attr(
    feature = "serde",
    derive(Deserialize, Serialize),
    serde(rename_all = "camelCase")
)]
#[derive(Default, Debug, PartialEq, Eq, Clone, Copy)]
pub struct MessageHeader {
    /// The number of signatures required for this message to be considered
    /// valid. The signers of those signatures must match the first
    /// `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,

    /// The last `num_readonly_signed_accounts` of the signed keys are read-only
    /// accounts.
    pub num_readonly_signed_accounts: u8,

    /// The last `num_readonly_unsigned_accounts` of the unsigned keys are
    /// read-only accounts.
    pub num_readonly_unsigned_accounts: u8,
}