light-client 0.25.0

Client library for Light Protocol
Documentation
//! Transaction v1 (SIMD-0385) helpers.
//!
//! A v1 transaction may be up to [`MAX_TX_V1_SIZE`] bytes instead of the
//! [`MAX_LEGACY_TX_SIZE`] bytes allowed for legacy and v0 transactions.
//! Two differences matter when building one:
//!
//! - There are no address lookup tables. Every account is a static key.
//! - The compute budget is not expressed as ComputeBudget instructions but
//!   carried in a [`TransactionConfig`] inside the message header. Do not
//!   prepend `ComputeBudgetInstruction`s to a v1 instruction list.
//!
//! `TransactionConfig::priority_fee` is a total in lamports, not a price per
//! compute unit.
//!
//! A cluster rejects v1 transactions until the `enable_tx_v1` feature gate is
//! active.

use solana_hash::Hash;
use solana_instruction::Instruction;
use solana_keypair::Keypair;
pub use solana_message::v1::TransactionConfig;
use solana_message::{v1, VersionedMessage};
use solana_pubkey::Pubkey;
use solana_transaction::versioned::VersionedTransaction;

use crate::rpc::errors::RpcError;

/// Maximum serialized size of a transaction v1 in bytes (SIMD-0296).
pub const MAX_TX_V1_SIZE: usize = 4096;

/// Maximum serialized size of a legacy or v0 transaction in bytes.
pub const MAX_LEGACY_TX_SIZE: usize = 1232;

/// Compute unit limit the client applies when the caller does not set one.
/// Matches the limit the legacy send paths request via ComputeBudget instructions.
pub const DEFAULT_COMPUTE_UNIT_LIMIT: u32 = 1_000_000;

/// Returns a [`TransactionConfig`] with the client's default compute unit and
/// loaded-accounts limits and no priority fee.
pub fn default_tx_config() -> TransactionConfig {
    with_defaults(TransactionConfig::default())
}

/// Maximum bytes of account data a transaction may load. Applied when the
/// caller does not set `loaded_accounts_data_size_limit`.
pub const DEFAULT_LOADED_ACCOUNTS_DATA_SIZE_LIMIT: u32 = 64 * 1024 * 1024;

/// Fills in defaults for the limits a v1 message treats as zero when unset.
///
/// In a v1 message `compute_unit_limit: None` means zero compute units and
/// `loaded_accounts_data_size_limit: None` means zero bytes of account data,
/// either of which fails every instruction. Legacy transactions get runtime
/// defaults for both; this restores that behavior.
pub fn with_defaults(config: TransactionConfig) -> TransactionConfig {
    TransactionConfig {
        compute_unit_limit: config
            .compute_unit_limit
            .or(Some(DEFAULT_COMPUTE_UNIT_LIMIT)),
        loaded_accounts_data_size_limit: config
            .loaded_accounts_data_size_limit
            .or(Some(DEFAULT_LOADED_ACCOUNTS_DATA_SIZE_LIMIT)),
        ..config
    }
}

/// Compiles `instructions` into a signed transaction v1.
///
/// Returns `RpcError::TransactionBuildError` when the message cannot be
/// compiled and `RpcError::SigningError` when signing fails.
pub fn build_v1_transaction(
    instructions: &[Instruction],
    payer: &Pubkey,
    signers: &[&Keypair],
    recent_blockhash: Hash,
    config: TransactionConfig,
) -> Result<VersionedTransaction, RpcError> {
    let message = v1::Message::try_compile_with_config(
        payer,
        instructions,
        recent_blockhash,
        with_defaults(config),
    )
    .map_err(|e| RpcError::TransactionBuildError(format!("Failed to compile v1 message: {e}")))?;

    VersionedTransaction::try_new(VersionedMessage::V1(message), signers)
        .map_err(|e| RpcError::SigningError(e.to_string()))
}

/// Serialized size in bytes of `transaction`, as it would travel over the wire.
pub fn serialized_size(transaction: &VersionedTransaction) -> Result<usize, RpcError> {
    bincode::serialized_size(transaction)
        .map(|size| size as usize)
        .map_err(|e| RpcError::CustomError(format!("Failed to serialize transaction: {e}")))
}