#![cfg_attr(feature = "frozen-abi", feature(min_specialization))]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
//! 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
//! 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`]: https://docs.rs/solana-signer/latest/rialo_s_signer/trait.Signer.html
//! [`Keypair`]: https://docs.rs/solana-keypair/latest/rialo_s_keypair/struct.Keypair.html
//! [`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, the the valid from field must be provided. This allows
//! validators to drop old but unexecuted transactions; and to distinguish
//! between accidentally duplicated transactions and intentionally duplicated
//! transactions &mdash; any identical transactions will not be executed more
//! than once, so updating the valid from field between submitting otherwise identical
//! transactions makes them unique.
//!
//! ```
//! # use rialo_s_sdk::example_mocks::solana_rpc_client;
//! use anyhow::Result;
//! use std::time::{SystemTime, UNIX_EPOCH};
//! use borsh::{BorshSerialize, BorshDeserialize};
//! use rialo_s_instruction::Instruction;
//! use rialo_s_keypair::Keypair;
//! use rialo_s_message::{ConfigHashPrefix, Message};
//! use rialo_s_pubkey::Pubkey;
//! use solana_rpc_client::rpc_client::RpcClient;
//! use rialo_s_signer::Signer;
//! use rialo_s_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 { kelvins: u64 },
//!     Withdraw { kelvins: 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 valid_from = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs();
//!     let config_hash = ConfigHashPrefix::new(0); // Use recent config hash prefix in production
//!     let mut tx = Transaction::new_signed_with_payer(
//!         &[instruction],
//!         Some(&payer.pubkey()),
//!         &[payer],
//!         valid_from as i64,
//!         config_hash,
//!         false, // use OCC?
//!     );
//!     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>(())
//! ```

use std::result;

use rialo_s_instruction::Instruction;
use rialo_s_message::{ConfigHashPrefix, Message};
use rialo_s_pubkey::Pubkey;
use rialo_s_signature::Signature;
use rialo_s_transaction_error::{TransactionError, TransactionResult as Result};
use rialo_sanitize::{Sanitize, SanitizeError};
#[cfg(target_arch = "wasm32")]
use wasm_bindgen::prelude::wasm_bindgen;
#[cfg(feature = "bincode")]
use {
    rialo_s_message::compiled_instruction::CompiledInstruction,
    rialo_s_signer::{signers::Signers, SignerError},
    std::time::{SystemTime, UNIX_EPOCH},
};
#[cfg(feature = "serde")]
use {
    rialo_s_short_vec as short_vec,
    serde_derive::{Deserialize, Serialize},
};

pub mod sanitized;
pub mod versioned;
mod wasm;

#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub enum TransactionVerificationMode {
    HashOnly,
    HashAndVerifyPrecompiles,
    FullVerification,
}

/// An atomically-committed 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`]: https://docs.rs/solana-signer/latest/rialo_s_signer/trait.Signer.html
///
/// 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.
#[cfg(not(target_arch = "wasm32"))]
#[cfg_attr(
    feature = "frozen-abi",
    derive(rialo_frozen_abi_macro::AbiExample),
    rialo_frozen_abi_macro::frozen_abi(digest = "76BDTr3Xm3VP7h4eSiw6pZHKc5yYewDufyia3Yedh6GG")
)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
#[derive(Debug, PartialEq, Default, Eq, Clone)]
pub struct Transaction {
    /// A set of signatures of a serialized [`Message`], signed by the first
    /// keys of the `Message`'s [`account_keys`], where the number of signatures
    /// is equal to [`num_required_signatures`] of the `Message`'s
    /// [`MessageHeader`].
    ///
    /// [`account_keys`]: https://docs.rs/solana-message/latest/rialo_s_message/legacy/struct.Message.html#structfield.account_keys
    /// [`MessageHeader`]: https://docs.rs/solana-message/latest/rialo_s_message/struct.MessageHeader.html
    /// [`num_required_signatures`]: https://docs.rs/solana-message/latest/rialo_s_message/struct.MessageHeader.html#structfield.num_required_signatures
    // NOTE: Serialization-related changes must be paired with the direct read at sigverify.
    #[cfg_attr(feature = "serde", serde(with = "short_vec"))]
    pub signatures: Vec<Signature>,

    /// The message to sign.
    pub message: Message,
}

/// wasm-bindgen version of the Transaction struct.
/// This duplication is required until https://github.com/rustwasm/wasm-bindgen/issues/3671
/// is fixed. This must not diverge from the regular non-wasm Transaction struct.
#[cfg(target_arch = "wasm32")]
#[wasm_bindgen]
#[cfg_attr(
    feature = "frozen-abi",
    derive(AbiExample),
    frozen_abi(digest = "H7xQFcd1MtMv9QKZWGatBAXwhg28tpeX59P3s8ZZLAY4")
)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
#[derive(Debug, PartialEq, Default, Eq, Clone)]
pub struct Transaction {
    #[wasm_bindgen(skip)]
    #[cfg_attr(feature = "serde", serde(with = "short_vec"))]
    pub signatures: Vec<Signature>,

    #[wasm_bindgen(skip)]
    pub message: Message,
}

impl Sanitize for Transaction {
    fn sanitize(&self) -> result::Result<(), SanitizeError> {
        if self.message.header.num_required_signatures as usize > self.signatures.len() {
            return Err(SanitizeError::IndexOutOfBounds);
        }
        if self.signatures.len() > self.message.account_keys.len() {
            return Err(SanitizeError::IndexOutOfBounds);
        }
        self.message.sanitize()
    }
}

impl Transaction {
    /// Create an unsigned transaction from a [`Message`].
    ///
    /// # Examples
    ///
    /// This example uses the [`solana_rpc_client`] and [`anyhow`] crates.
    ///
    /// [`solana_rpc_client`]: https://docs.rs/solana-rpc-client
    /// [`anyhow`]: https://docs.rs/anyhow
    ///
    /// ```
    /// # use rialo_s_sdk::example_mocks::solana_rpc_client;
    /// use std::time::{SystemTime, UNIX_EPOCH};
    /// use anyhow::Result;
    /// use borsh::{BorshSerialize, BorshDeserialize};
    /// use rialo_s_instruction::Instruction;
    /// use rialo_s_keypair::Keypair;
    /// use rialo_s_message::{ConfigHashPrefix, Message};
    /// use rialo_s_pubkey::Pubkey;
    /// use solana_rpc_client::rpc_client::RpcClient;
    /// use rialo_s_signer::Signer;
    /// use rialo_s_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 { kelvins: u64 },
    ///     Withdraw { kelvins: 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 config_hash = ConfigHashPrefix::new(0); // Use recent config hash prefix in production
    ///     let message = Message::new(
    ///         &[instruction],
    ///         Some(&payer.pubkey()),
    ///         config_hash,
    ///         false, // use OCC?
    ///     );
    ///
    ///     let mut tx = Transaction::new_unsigned(message);
    ///     let valid_from = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs();
    ///     tx.sign(&[payer], valid_from as i64);
    ///     client.send_and_confirm_transaction(&tx)?;
    ///
    ///     Ok(())
    /// }
    /// #
    /// # let client = RpcClient::new(String::new());
    /// # let program_id = Pubkey::new_unique();
    /// # let payer = Keypair::new();
    /// # send_initialize_tx(&client, program_id, &payer)?;
    /// #
    /// # Ok::<(), anyhow::Error>(())
    /// ```
    pub fn new_unsigned(message: Message) -> Self {
        Self {
            signatures: vec![Signature::default(); message.header.num_required_signatures as usize],
            message,
        }
    }

    /// Create a fully-signed transaction from a [`Message`].
    ///
    /// # 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_rpc_client`] and [`anyhow`] crates.
    ///
    /// [`solana_rpc_client`]: https://docs.rs/solana-rpc-client
    /// [`anyhow`]: https://docs.rs/anyhow
    ///
    /// ```
    /// # use rialo_s_sdk::example_mocks::solana_rpc_client;
    /// use std::time::{SystemTime, UNIX_EPOCH};
    /// use anyhow::Result;
    /// use borsh::{BorshSerialize, BorshDeserialize};
    /// use rialo_s_instruction::Instruction;
    /// use rialo_s_keypair::Keypair;
    /// use rialo_s_message::{ConfigHashPrefix, Message};
    /// use rialo_s_pubkey::Pubkey;
    /// use solana_rpc_client::rpc_client::RpcClient;
    /// use rialo_s_signer::Signer;
    /// use rialo_s_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 { kelvins: u64 },
    ///     Withdraw { kelvins: 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 config_hash = ConfigHashPrefix::new(0); // Use recent config hash prefix in production
    ///     let message = Message::new(
    ///         &[instruction],
    ///         Some(&payer.pubkey()),
    ///         config_hash,
    ///         false, // use OCC?
    ///     );
    ///
    ///     let valid_from = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs();
    ///     let mut tx = Transaction::new(&[payer], message, valid_from as i64);
    ///     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 = "bincode")]
    pub fn new<T: Signers + ?Sized>(
        from_keypairs: &T,
        message: Message,
        valid_from: i64,
    ) -> Transaction {
        let mut tx = Self::new_unsigned(message);
        tx.sign(from_keypairs, valid_from);
        tx
    }

    /// 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_rpc_client`] and [`anyhow`] crates.
    ///
    /// [`solana_rpc_client`]: https://docs.rs/solana-rpc-client
    /// [`anyhow`]: https://docs.rs/anyhow
    ///
    /// ```
    /// # use rialo_s_sdk::example_mocks::solana_rpc_client;
    /// use std::time::{SystemTime, UNIX_EPOCH};
    /// use anyhow::Result;
    /// use borsh::{BorshSerialize, BorshDeserialize};
    /// use rialo_s_instruction::Instruction;
    /// use rialo_s_keypair::Keypair;
    /// use rialo_s_message::{ConfigHashPrefix, Message};
    /// use rialo_s_pubkey::Pubkey;
    /// use solana_rpc_client::rpc_client::RpcClient;
    /// use rialo_s_signer::Signer;
    /// use rialo_s_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 { kelvins: u64 },
    ///     Withdraw { kelvins: 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 config_hash = ConfigHashPrefix::new(0); // Use recent config hash prefix in production
    ///     let mut tx = Transaction::new_with_payer(&[instruction], Some(&payer.pubkey()), config_hash, false);
    ///     let valid_from = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs();
    ///     tx.sign(&[payer], valid_from as i64);
    ///     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>,
        config_hash_prefix: ConfigHashPrefix,
        occ: bool,
    ) -> Self {
        let message = Message::new(instructions, payer, config_hash_prefix, occ);
        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_rpc_client`] and [`anyhow`] crates.
    ///
    /// [`solana_rpc_client`]: https://docs.rs/solana-rpc-client
    /// [`anyhow`]: https://docs.rs/anyhow
    ///
    /// ```
    /// # use rialo_s_sdk::example_mocks::solana_rpc_client;
    /// use std::time::{SystemTime, UNIX_EPOCH};
    /// use anyhow::Result;
    /// use borsh::{BorshSerialize, BorshDeserialize};
    /// use rialo_s_instruction::Instruction;
    /// use rialo_s_keypair::Keypair;
    /// use rialo_s_message::{ConfigHashPrefix, Message};
    /// use rialo_s_pubkey::Pubkey;
    /// use solana_rpc_client::rpc_client::RpcClient;
    /// use rialo_s_signer::Signer;
    /// use rialo_s_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 { kelvins: u64 },
    ///     Withdraw { kelvins: 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 valid_from = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs();
    ///     let config_hash = ConfigHashPrefix::new(0); // Use recent config hash prefix in production
    ///     let mut tx = Transaction::new_signed_with_payer(
    ///         &[instruction],
    ///         Some(&payer.pubkey()),
    ///         &[payer],
    ///         valid_from as i64,
    ///         config_hash,
    ///         false,
    ///     );
    ///     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 = "bincode")]
    pub fn new_signed_with_payer<T: Signers + ?Sized>(
        instructions: &[Instruction],
        payer: Option<&Pubkey>,
        signing_keypairs: &T,
        valid_from: i64,
        config_hash_prefix: ConfigHashPrefix,
        occ: bool,
    ) -> Self {
        let message = Message::new(instructions, payer, config_hash_prefix, occ);
        Self::new(signing_keypairs, message, valid_from)
    }

    /// Create a fully-signed transaction from pre-compiled instructions.
    ///
    /// # Arguments
    ///
    /// * `from_keypairs` - The keys used to sign the transaction.
    /// * `keys` - The keys for the transaction.  These are the program state
    ///   instances or kelvin recipient keys.
    /// * `program_ids` - The keys that identify programs used in the `instruction` vector.
    /// * `instructions` - Instructions that will be executed atomically.
    ///
    /// # Panics
    ///
    /// Panics when signing fails. See [`Transaction::try_sign`] and for a full
    /// description of failure conditions.
    #[cfg(feature = "bincode")]
    pub fn new_with_compiled_instructions<T: Signers + ?Sized>(
        from_keypairs: &T,
        keys: &[Pubkey],
        program_ids: Vec<Pubkey>,
        instructions: Vec<CompiledInstruction>,
        config_hash_prefix: ConfigHashPrefix,
        occ: bool,
    ) -> Self {
        let mut account_keys = from_keypairs.pubkeys();
        let from_keypairs_len = account_keys.len();
        account_keys.extend_from_slice(keys);
        account_keys.extend(&program_ids);
        let now_millis = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .expect("Failed to get current time")
            .as_millis();
        let now: i64 = now_millis.try_into().unwrap_or(i64::MAX);

        let message = Message::new_with_compiled_instructions(
            from_keypairs_len as u8,
            0,
            program_ids.len() as u8,
            account_keys,
            now,
            config_hash_prefix,
            occ,
            instructions,
        );
        Transaction::new(from_keypairs, message, now)
    }

    /// Get the data for an instruction at the given index.
    ///
    /// The `instruction_index` corresponds to the [`instructions`] vector of
    /// the `Transaction`'s [`Message`] value.
    ///
    /// [`instructions`]: Message::instructions
    ///
    /// # Panics
    ///
    /// Panics if `instruction_index` is greater than or equal to the number of
    /// instructions in the transaction.
    pub fn data(&self, instruction_index: usize) -> &[u8] {
        &self.message.instructions[instruction_index].data
    }

    fn key_index(&self, instruction_index: usize, accounts_index: usize) -> Option<usize> {
        self.message
            .instructions
            .get(instruction_index)
            .and_then(|instruction| instruction.accounts.get(accounts_index))
            .map(|&account_keys_index| account_keys_index as usize)
    }

    /// Get the `Pubkey` of an account required by one of the instructions in
    /// the transaction.
    ///
    /// The `instruction_index` corresponds to the [`instructions`] vector of
    /// the `Transaction`'s [`Message`] value; and the `account_index` to the
    /// [`accounts`] vector of the message's [`CompiledInstruction`]s.
    ///
    /// [`instructions`]: Message::instructions
    /// [`accounts`]: CompiledInstruction::accounts
    /// [`CompiledInstruction`]: CompiledInstruction
    ///
    /// Returns `None` if `instruction_index` is greater than or equal to the
    /// number of instructions in the transaction; or if `accounts_index` is
    /// greater than or equal to the number of accounts in the instruction.
    pub fn key(&self, instruction_index: usize, accounts_index: usize) -> Option<&Pubkey> {
        self.key_index(instruction_index, accounts_index)
            .and_then(|account_keys_index| self.message.account_keys.get(account_keys_index))
    }

    /// Get the `Pubkey` of a signing account required by one of the
    /// instructions in the transaction.
    ///
    /// The transaction does not need to be signed for this function to return a
    /// signing account's pubkey.
    ///
    /// Returns `None` if the indexed account is not required to sign the
    /// transaction. Returns `None` if the [`signatures`] field does not contain
    /// enough elements to hold a signature for the indexed account (this should
    /// only be possible if `Transaction` has been manually constructed).
    ///
    /// [`signatures`]: Transaction::signatures
    ///
    /// Returns `None` if `instruction_index` is greater than or equal to the
    /// number of instructions in the transaction; or if `accounts_index` is
    /// greater than or equal to the number of accounts in the instruction.
    pub fn signer_key(&self, instruction_index: usize, accounts_index: usize) -> Option<&Pubkey> {
        match self.key_index(instruction_index, accounts_index) {
            None => None,
            Some(signature_index) => {
                if signature_index >= self.signatures.len() {
                    return None;
                }
                self.message.account_keys.get(signature_index)
            }
        }
    }

    /// Return the message containing all data that should be signed.
    pub fn message(&self) -> &Message {
        &self.message
    }

    #[cfg(feature = "bincode")]
    /// Return the serialized message data to sign.
    pub fn message_data(&self) -> Vec<u8> {
        self.message().serialize()
    }

    /// 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`].
    ///
    /// # Panics
    ///
    /// 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_rpc_client`] and [`anyhow`] crates.
    ///
    /// [`solana_rpc_client`]: https://docs.rs/solana-rpc-client
    /// [`anyhow`]: https://docs.rs/anyhow
    ///
    /// ```
    /// # use rialo_s_sdk::example_mocks::solana_rpc_client;
    /// use std::time::{SystemTime, UNIX_EPOCH};
    /// use anyhow::Result;
    /// use borsh::{BorshSerialize, BorshDeserialize};
    /// use rialo_s_instruction::Instruction;
    /// use rialo_s_keypair::Keypair;
    /// use rialo_s_message::{ConfigHashPrefix, Message};
    /// use rialo_s_pubkey::Pubkey;
    /// use solana_rpc_client::rpc_client::RpcClient;
    /// use rialo_s_signer::Signer;
    /// use rialo_s_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 { kelvins: u64 },
    ///     Withdraw { kelvins: 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 config_hash = ConfigHashPrefix::new(0); // Use recent config hash prefix in production
    ///     let mut tx = Transaction::new_with_payer(&[instruction], Some(&payer.pubkey()), config_hash, false);
    ///     let valid_from = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs();
    ///     tx.sign(&[payer], valid_from as i64);
    ///     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 = "bincode")]
    pub fn sign<T: Signers + ?Sized>(&mut self, keypairs: &T, valid_from: i64) {
        if let Err(e) = self.try_sign(keypairs, valid_from) {
            panic!("Transaction::sign failed with error {e:?}");
        }
    }

    /// Sign the transaction with a subset of required keys.
    ///
    /// Unlike [`Transaction::sign`], this method does not require all keypairs
    /// to be provided, allowing a transaction to be signed in multiple steps.
    ///
    /// It is permitted to sign a transaction with the same keypair multiple
    /// times.
    ///
    /// If `valid_from` is different than recorded in the transaction message's
    /// [`valid_from`] field, then the message's `valid_from` will be updated
    /// to the provided `valid_from`, and any prior signatures will be cleared.
    ///
    /// [`valid_from`]: Message::valid_from
    ///
    /// # Panics
    ///
    /// 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.
    #[cfg(feature = "bincode")]
    pub fn partial_sign<T: Signers + ?Sized>(&mut self, keypairs: &T, valid_from: i64) {
        if let Err(e) = self.try_partial_sign(keypairs, valid_from) {
            panic!("Transaction::partial_sign failed with error {e:?}");
        }
    }

    /// Sign the transaction with a subset of required keys.
    ///
    /// This places each of the signatures created from `keypairs` in the
    /// corresponding position, as specified in the `positions` vector, in the
    /// transactions [`signatures`] field. It does not verify that the signature
    /// positions are correct.
    ///
    /// [`signatures`]: Transaction::signatures
    ///
    /// # Panics
    ///
    /// Panics if signing fails. Use [`Transaction::try_partial_sign_unchecked`]
    /// to handle the error.
    #[cfg(feature = "bincode")]
    pub fn partial_sign_unchecked<T: Signers + ?Sized>(
        &mut self,
        keypairs: &T,
        positions: Vec<usize>,
        valid_from: i64,
    ) {
        if let Err(e) = self.try_partial_sign_unchecked(keypairs, positions, valid_from) {
            panic!("Transaction::partial_sign_unchecked failed with error {e:?}");
        }
    }

    /// Sign the transaction, returning any errors.
    ///
    /// This method fully signs a transaction with all required signers, which
    /// must be present in the `keypairs` slice. To sign with only some of the
    /// required signers, use [`Transaction::try_partial_sign`].
    ///
    /// If `valid_from` is different than recorded in the transaction message's
    /// [`valid_from`] field, then the message's `valid_from` will be updated
    /// to the provided `valid_from`, and any prior signatures will be cleared.
    ///
    /// [`valid_from`]: Message::valid_from
    ///
    /// # 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_rpc_client`] and [`anyhow`] crates.
    ///
    /// [`solana_rpc_client`]: https://docs.rs/solana-rpc-client
    /// [`anyhow`]: https://docs.rs/anyhow
    ///
    /// ```
    /// # use rialo_s_sdk::example_mocks::solana_rpc_client;
    /// use std::time::{SystemTime, UNIX_EPOCH};
    /// use anyhow::Result;
    /// use borsh::{BorshSerialize, BorshDeserialize};
    /// use rialo_s_instruction::Instruction;
    /// use rialo_s_keypair::Keypair;
    /// use rialo_s_message::{ConfigHashPrefix, Message};
    /// use rialo_s_pubkey::Pubkey;
    /// use solana_rpc_client::rpc_client::RpcClient;
    /// use rialo_s_signer::Signer;
    /// use rialo_s_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 { kelvins: u64 },
    ///     Withdraw { kelvins: 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 config_hash = ConfigHashPrefix::new(0); // Use recent config hash prefix in production
    ///     let mut tx = Transaction::new_with_payer(&[instruction], Some(&payer.pubkey()), config_hash, false);
    ///     let valid_from = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs();
    ///     tx.try_sign(&[payer], valid_from as i64)?;
    ///     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 = "bincode")]
    pub fn try_sign<T: Signers + ?Sized>(
        &mut self,
        keypairs: &T,
        valid_from: i64,
    ) -> result::Result<(), SignerError> {
        self.try_partial_sign(keypairs, valid_from)?;

        if !self.is_signed() {
            Err(SignerError::NotEnoughSigners)
        } else {
            Ok(())
        }
    }

    /// Sign the transaction with a subset of required keys, returning any errors.
    ///
    /// Unlike [`Transaction::try_sign`], this method does not require all
    /// keypairs to be provided, allowing a transaction to be signed in multiple
    /// steps.
    ///
    /// It is permitted to sign a transaction with the same keypair multiple
    /// times.
    ///
    /// If `valid_from` is different than recorded in the transaction message's
    /// [`valid_from`] field, then the message's `valid_from` will be updated
    /// to the provided `valid_from`, and any prior signatures will be cleared.
    ///
    /// [`valid_from`]: Message::valid_from
    ///
    /// # 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`]: https://docs.rs/solana-message/latest/rialo_s_message/struct.MessageHeader.html#structfield.num_required_signatures
    /// [`account_keys`]: https://docs.rs/solana-message/latest/rialo_s_message/legacy/struct.Message.html#structfield.account_keys
    /// [`Presigner`]: https://docs.rs/solana-presigner/latest/rialo_s_presigner/struct.Presigner.html
    /// [`PresignerError`]: https://docs.rs/solana-signer/latest/rialo_s_signer/enum.PresignerError.html
    /// [`PresignerError::VerificationFailure`]: https://docs.rs/solana-signer/latest/rialo_s_signer/enum.PresignerError.html#variant.WrongSize
    /// [`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
    #[cfg(feature = "bincode")]
    pub fn try_partial_sign<T: Signers + ?Sized>(
        &mut self,
        keypairs: &T,
        valid_from: i64,
    ) -> result::Result<(), SignerError> {
        let positions: Vec<usize> = self
            .get_signing_keypair_positions(&keypairs.pubkeys())?
            .into_iter()
            .collect::<Option<_>>()
            .ok_or(SignerError::KeypairPubkeyMismatch)?;
        self.try_partial_sign_unchecked(keypairs, positions, valid_from)
    }

    /// Sign the transaction with a subset of required keys, returning any
    /// errors.
    ///
    /// This places each of the signatures created from `keypairs` in the
    /// corresponding position, as specified in the `positions` vector, in the
    /// transactions [`signatures`] field. It does not verify that the signature
    /// positions are correct.
    ///
    /// [`signatures`]: Transaction::signatures
    ///
    /// # Errors
    ///
    /// Returns an error if signing fails.
    #[cfg(feature = "bincode")]
    pub fn try_partial_sign_unchecked<T: Signers + ?Sized>(
        &mut self,
        keypairs: &T,
        positions: Vec<usize>,
        valid_from: i64,
    ) -> result::Result<(), SignerError> {
        // if you change the valid_from, you're re-signing...
        if valid_from != self.message.valid_from {
            self.message.valid_from = valid_from;
            self.signatures
                .iter_mut()
                .for_each(|signature| *signature = Signature::default());
        }

        let signatures = keypairs.try_sign_message(&self.message_data())?;
        for i in 0..positions.len() {
            self.signatures[positions[i]] = signatures[i];
        }
        Ok(())
    }

    /// Returns a signature that is not valid for signing this transaction.
    pub fn get_invalid_signature() -> Signature {
        Signature::default()
    }

    #[cfg(feature = "verify")]
    /// Verifies that all signers have signed the message.
    ///
    /// # Errors
    ///
    /// Returns [`TransactionError::SignatureFailure`] on error.
    pub fn verify(&self) -> Result<()> {
        let message_bytes = self.message_data();
        if !self
            ._verify_with_results(&message_bytes)
            .iter()
            .all(|verify_result| *verify_result)
        {
            Err(TransactionError::SignatureFailure)
        } else {
            Ok(())
        }
    }

    #[cfg(feature = "verify")]
    /// Verify the transaction and hash its message.
    ///
    /// # Errors
    ///
    /// Returns [`TransactionError::SignatureFailure`] on error.
    pub fn verify_and_hash_message(&self) -> Result<rialo_hash::Hash> {
        let message_bytes = self.message_data();
        if !self
            ._verify_with_results(&message_bytes)
            .iter()
            .all(|verify_result| *verify_result)
        {
            Err(TransactionError::SignatureFailure)
        } else {
            Ok(Message::hash_raw_message(&message_bytes))
        }
    }

    #[cfg(feature = "verify")]
    /// Verifies that all signers have signed the message.
    ///
    /// Returns a vector with the length of required signatures, where each
    /// element is either `true` if that signer has signed, or `false` if not.
    pub fn verify_with_results(&self) -> Vec<bool> {
        self._verify_with_results(&self.message_data())
    }

    #[cfg(feature = "verify")]
    pub(crate) fn _verify_with_results(&self, message_bytes: &[u8]) -> Vec<bool> {
        self.signatures
            .iter()
            .zip(&self.message.account_keys)
            .map(|(signature, pubkey)| signature.verify(pubkey.as_ref(), message_bytes))
            .collect()
    }

    #[cfg(feature = "precompiles")]
    /// Verify the precompiled programs in this transaction.
    pub fn verify_precompiles(&self, feature_set: &rialo_s_feature_set::FeatureSet) -> Result<()> {
        for instruction in &self.message().instructions {
            // The Transaction may not be sanitized at this point
            if instruction.program_id_index as usize >= self.message().account_keys.len() {
                return Err(TransactionError::AccountNotFound);
            }
            let program_id = &self.message().account_keys[instruction.program_id_index as usize];

            rialo_s_precompiles::verify_if_precompile(
                program_id,
                instruction,
                &self.message().instructions,
                feature_set,
            )
            .map_err(|_| TransactionError::InvalidAccountIndex)?;
        }
        Ok(())
    }

    /// Get the positions of the pubkeys in `account_keys` associated with signing keypairs.
    ///
    /// [`account_keys`]: Message::account_keys
    pub fn get_signing_keypair_positions(&self, pubkeys: &[Pubkey]) -> Result<Vec<Option<usize>>> {
        if self.message.account_keys.len() < self.message.header.num_required_signatures as usize {
            return Err(TransactionError::InvalidAccountIndex);
        }
        let signed_keys =
            &self.message.account_keys[0..self.message.header.num_required_signatures as usize];

        Ok(pubkeys
            .iter()
            .map(|pubkey| signed_keys.iter().position(|x| x == pubkey))
            .collect())
    }

    #[cfg(feature = "verify")]
    /// Replace all the signatures and pubkeys.
    pub fn replace_signatures(&mut self, signers: &[(Pubkey, Signature)]) -> Result<()> {
        let num_required_signatures = self.message.header.num_required_signatures as usize;
        if signers.len() != num_required_signatures
            || self.signatures.len() != num_required_signatures
            || self.message.account_keys.len() < num_required_signatures
        {
            return Err(TransactionError::InvalidAccountIndex);
        }

        for (index, account_key) in self
            .message
            .account_keys
            .iter()
            .enumerate()
            .take(num_required_signatures)
        {
            if let Some((_pubkey, signature)) =
                signers.iter().find(|(key, _signature)| account_key == key)
            {
                self.signatures[index] = *signature
            } else {
                return Err(TransactionError::InvalidAccountIndex);
            }
        }

        self.verify()
    }

    pub fn is_signed(&self) -> bool {
        self.signatures
            .iter()
            .all(|signature| *signature != Signature::default())
    }
}

#[cfg(test)]
mod tests {
    #![allow(deprecated)]

    use std::{
        mem::size_of,
        time::{SystemTime, UNIX_EPOCH},
    };

    use bincode::{deserialize, serialize, serialized_size};
    use rialo_s_instruction::AccountMeta;
    use rialo_s_keypair::Keypair;
    use rialo_s_message::ConfigHashPrefix;
    use rialo_s_presigner::Presigner;
    use rialo_s_signer::Signer;
    use rialo_s_system_interface::instruction as system_instruction;

    use super::*;

    fn get_program_id(tx: &Transaction, instruction_index: usize) -> &Pubkey {
        let message = tx.message();
        let instruction = &message.instructions[instruction_index];
        instruction.program_id(&message.account_keys)
    }

    #[test]
    fn test_refs() {
        let key = Keypair::new();
        let key1 = rialo_s_pubkey::new_rand();
        let key2 = rialo_s_pubkey::new_rand();
        let prog1 = rialo_s_pubkey::new_rand();
        let prog2 = rialo_s_pubkey::new_rand();
        let instructions = vec![
            CompiledInstruction::new(3, &(), vec![0, 1]),
            CompiledInstruction::new(4, &(), vec![0, 2]),
        ];
        let tx = Transaction::new_with_compiled_instructions(
            &[&key],
            &[key1, key2],
            vec![prog1, prog2],
            instructions,
            ConfigHashPrefix::new(0),
            false,
        );
        assert!(tx.sanitize().is_ok());

        assert_eq!(tx.key(0, 0), Some(&key.pubkey()));
        assert_eq!(tx.signer_key(0, 0), Some(&key.pubkey()));

        assert_eq!(tx.key(1, 0), Some(&key.pubkey()));
        assert_eq!(tx.signer_key(1, 0), Some(&key.pubkey()));

        assert_eq!(tx.key(0, 1), Some(&key1));
        assert_eq!(tx.signer_key(0, 1), None);

        assert_eq!(tx.key(1, 1), Some(&key2));
        assert_eq!(tx.signer_key(1, 1), None);

        assert_eq!(tx.key(2, 0), None);
        assert_eq!(tx.signer_key(2, 0), None);

        assert_eq!(tx.key(0, 2), None);
        assert_eq!(tx.signer_key(0, 2), None);

        assert_eq!(*get_program_id(&tx, 0), prog1);
        assert_eq!(*get_program_id(&tx, 1), prog2);
    }

    #[test]
    fn test_refs_invalid_program_id() {
        let key = Keypair::new();
        let instructions = vec![CompiledInstruction::new(1, &(), vec![])];
        let tx = Transaction::new_with_compiled_instructions(
            &[&key],
            &[],
            vec![],
            instructions,
            ConfigHashPrefix::new(0),
            false,
        );
        assert_eq!(tx.sanitize(), Err(SanitizeError::IndexOutOfBounds));
    }
    #[test]
    fn test_refs_invalid_account() {
        let key = Keypair::new();
        let instructions = vec![CompiledInstruction::new(1, &(), vec![2])];
        let tx = Transaction::new_with_compiled_instructions(
            &[&key],
            &[],
            vec![Pubkey::default()],
            instructions,
            ConfigHashPrefix::new(0),
            false,
        );
        assert_eq!(*get_program_id(&tx, 0), Pubkey::default());
        assert_eq!(tx.sanitize(), Err(SanitizeError::IndexOutOfBounds));
    }

    #[test]
    fn test_sanitize_txs() {
        let key = Keypair::new();
        let id0 = Pubkey::default();
        let program_id = rialo_s_pubkey::new_rand();
        let ix = Instruction::new_with_bincode(
            program_id,
            &0,
            vec![
                AccountMeta::new(key.pubkey(), true),
                AccountMeta::new(id0, true),
            ],
        );
        let mut tx = Transaction::new_with_payer(
            &[ix],
            Some(&key.pubkey()),
            ConfigHashPrefix::new(0),
            false,
        );
        let o = tx.clone();
        assert_eq!(tx.sanitize(), Ok(()));
        assert_eq!(tx.message.account_keys.len(), 3);

        tx = o.clone();
        tx.message.header.num_required_signatures = 3;
        assert_eq!(tx.sanitize(), Err(SanitizeError::IndexOutOfBounds));

        tx = o.clone();
        tx.message.header.num_readonly_signed_accounts = 4;
        tx.message.header.num_readonly_unsigned_accounts = 0;
        assert_eq!(tx.sanitize(), Err(SanitizeError::IndexOutOfBounds));

        tx = o.clone();
        tx.message.header.num_readonly_signed_accounts = 2;
        tx.message.header.num_readonly_unsigned_accounts = 2;
        assert_eq!(tx.sanitize(), Err(SanitizeError::IndexOutOfBounds));

        tx = o.clone();
        tx.message.header.num_readonly_signed_accounts = 0;
        tx.message.header.num_readonly_unsigned_accounts = 4;
        assert_eq!(tx.sanitize(), Err(SanitizeError::IndexOutOfBounds));

        tx = o.clone();
        tx.message.instructions[0].program_id_index = 3;
        assert_eq!(tx.sanitize(), Err(SanitizeError::IndexOutOfBounds));

        tx = o.clone();
        tx.message.instructions[0].accounts[0] = 3;
        assert_eq!(tx.sanitize(), Err(SanitizeError::IndexOutOfBounds));

        tx = o.clone();
        tx.message.instructions[0].program_id_index = 0;
        assert_eq!(tx.sanitize(), Err(SanitizeError::IndexOutOfBounds));

        tx = o.clone();
        tx.message.header.num_readonly_signed_accounts = 2;
        tx.message.header.num_readonly_unsigned_accounts = 3;
        tx.message.account_keys.resize(4, Pubkey::default());
        assert_eq!(tx.sanitize(), Err(SanitizeError::IndexOutOfBounds));

        tx = o;
        tx.message.header.num_readonly_signed_accounts = 2;
        tx.message.header.num_required_signatures = 1;
        assert_eq!(tx.sanitize(), Err(SanitizeError::IndexOutOfBounds));
    }

    fn create_sample_transaction() -> Transaction {
        let keypair = Keypair::from_bytes(&[
            255, 101, 36, 24, 124, 23, 167, 21, 132, 204, 155, 5, 185, 58, 121, 75, 156, 227, 116,
            193, 215, 38, 142, 22, 8, 14, 229, 239, 119, 93, 5, 218, 36, 100, 158, 252, 33, 161,
            97, 185, 62, 89, 99, 195, 250, 249, 187, 189, 171, 118, 241, 90, 248, 14, 68, 219, 231,
            62, 157, 5, 142, 27, 210, 117,
        ])
        .unwrap();
        let to = Pubkey::from([
            1, 1, 1, 4, 5, 6, 7, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 8, 7, 6, 5, 4,
            1, 1, 1,
        ]);

        let program_id = Pubkey::from([
            2, 2, 2, 4, 5, 6, 7, 8, 9, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 8, 7, 6, 5, 4,
            2, 2, 2,
        ]);
        let account_metas = vec![
            AccountMeta::new(keypair.pubkey(), true),
            AccountMeta::new(to, false),
        ];
        let instruction =
            Instruction::new_with_bincode(program_id, &(1u8, 2u8, 3u8), account_metas);
        let message = Message::new(
            &[instruction],
            Some(&keypair.pubkey()),
            ConfigHashPrefix::new(0),
            false,
        );
        let now_millis = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .expect("Failed to get current time")
            .as_millis();
        let now: i64 = now_millis.try_into().unwrap_or(i64::MAX);
        let tx = Transaction::new(&[&keypair], message, now);
        tx.verify().expect("valid sample transaction signatures");
        tx
    }

    #[test]
    fn test_transaction_serialize() {
        let tx = create_sample_transaction();
        let ser = serialize(&tx).unwrap();
        let deser = deserialize(&ser).unwrap();
        assert_eq!(tx, deser);
    }

    /// Detect changes to the serialized size of payment transactions, which affects TPS.
    #[test]
    #[ignore] // TODO(SUB-1547): Investigate how to re-enable tests after importing Agave crates
    fn test_transaction_minimum_serialized_size() {
        let alice_keypair = Keypair::new();
        let alice_pubkey = alice_keypair.pubkey();
        let bob_pubkey = rialo_s_pubkey::new_rand();
        let ix = system_instruction::transfer(&alice_pubkey, &bob_pubkey, 42);

        let expected_data_size = size_of::<u32>() + size_of::<u64>();
        assert_eq!(expected_data_size, 12);
        assert_eq!(
            ix.data.len(),
            expected_data_size,
            "unexpected system instruction size"
        );

        let expected_instruction_size = 1 + 1 + ix.accounts.len() + 1 + expected_data_size;
        assert_eq!(expected_instruction_size, 17);

        let message = Message::new(&[ix], Some(&alice_pubkey), ConfigHashPrefix::new(0), false);
        assert_eq!(
            serialized_size(&message.instructions[0]).unwrap() as usize,
            expected_instruction_size,
            "unexpected Instruction::serialized_size"
        );

        let now_millis = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .expect("Failed to get current time")
            .as_millis();
        let now: i64 = now_millis.try_into().unwrap_or(i64::MAX);
        let tx = Transaction::new(&[&alice_keypair], message, now);

        let len_size = 1;
        let num_required_sigs_size = 1;
        let num_readonly_accounts_size = 2;
        let valid_from_size = size_of::<i64>();
        let expected_transaction_size = len_size
            + (tx.signatures.len() * size_of::<Signature>())
            + num_required_sigs_size
            + num_readonly_accounts_size
            + valid_from_size
            + (tx.message.account_keys.len() * size_of::<Pubkey>())
            + len_size
            + expected_instruction_size;
        assert_eq!(expected_transaction_size, 215);

        assert_eq!(
            serialized_size(&tx).unwrap() as usize,
            expected_transaction_size,
            "unexpected serialized transaction size"
        );
    }

    /// Detect binary changes in the serialized transaction data, which could have a downstream
    /// affect on SDKs and applications
    #[test]
    #[ignore] // TODO(SUB-1547): Investigate how to re-enable tests after importing Agave crates
    fn test_sdk_serialize() {
        assert_eq!(
            serialize(&create_sample_transaction()).unwrap(),
            vec![
                1, 120, 138, 162, 185, 59, 209, 241, 157, 71, 157, 74, 131, 4, 87, 54, 28, 38, 180,
                222, 82, 64, 62, 61, 62, 22, 46, 17, 203, 187, 136, 62, 43, 11, 38, 235, 17, 239,
                82, 240, 139, 130, 217, 227, 214, 9, 242, 141, 223, 94, 29, 184, 110, 62, 32, 87,
                137, 63, 139, 100, 221, 20, 137, 4, 5, 1, 0, 1, 3, 36, 100, 158, 252, 33, 161, 97,
                185, 62, 89, 99, 195, 250, 249, 187, 189, 171, 118, 241, 90, 248, 14, 68, 219, 231,
                62, 157, 5, 142, 27, 210, 117, 1, 1, 1, 4, 5, 6, 7, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9,
                9, 9, 9, 9, 9, 9, 9, 8, 7, 6, 5, 4, 1, 1, 1, 2, 2, 2, 4, 5, 6, 7, 8, 9, 1, 1, 1, 1,
                1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 8, 7, 6, 5, 4, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 2, 0, 1,
                3, 1, 2, 3
            ]
        );
    }

    #[test]
    #[should_panic]
    fn test_transaction_missing_key() {
        let keypair = Keypair::new();
        let message = Message::new(&[], None, ConfigHashPrefix::new(0), false);
        let now_millis = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .expect("Failed to get current time")
            .as_millis();
        let now: i64 = now_millis.try_into().unwrap_or(i64::MAX);
        Transaction::new_unsigned(message).sign(&[&keypair], now);
    }

    #[test]
    #[should_panic]
    fn test_partial_sign_mismatched_key() {
        let keypair = Keypair::new();
        let fee_payer = rialo_s_pubkey::new_rand();
        let ix = Instruction::new_with_bincode(
            Pubkey::default(),
            &0,
            vec![AccountMeta::new(fee_payer, true)],
        );
        let message = Message::new(&[ix], Some(&fee_payer), ConfigHashPrefix::new(0), false);
        let now_millis = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .expect("Failed to get current time")
            .as_millis();
        let now: i64 = now_millis.try_into().unwrap_or(i64::MAX);
        Transaction::new_unsigned(message).partial_sign(&[&keypair], now);
    }

    #[test]
    fn test_partial_sign() {
        let keypair0 = Keypair::new();
        let keypair1 = Keypair::new();
        let keypair2 = Keypair::new();
        let ix = Instruction::new_with_bincode(
            Pubkey::default(),
            &0,
            vec![
                AccountMeta::new(keypair0.pubkey(), true),
                AccountMeta::new(keypair1.pubkey(), true),
                AccountMeta::new(keypair2.pubkey(), true),
            ],
        );
        let message = Message::new(
            &[ix],
            Some(&keypair0.pubkey()),
            ConfigHashPrefix::new(0),
            false,
        );
        let mut tx = Transaction::new_unsigned(message);

        let now_millis = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .expect("Failed to get current time")
            .as_millis();
        let now: i64 = now_millis.try_into().unwrap_or(i64::MAX);
        tx.partial_sign(&[&keypair0, &keypair2], now);
        assert!(!tx.is_signed());
        tx.partial_sign(&[&keypair1], now);
        assert!(tx.is_signed());

        let now2 = now + 1000;
        tx.partial_sign(&[&keypair1], now2);
        assert!(!tx.is_signed());
        tx.partial_sign(&[&keypair0, &keypair2], now2);
        assert!(tx.is_signed());
    }

    #[test]
    #[should_panic]
    fn test_transaction_missing_keypair() {
        let program_id = Pubkey::default();
        let keypair0 = Keypair::new();
        let id0 = keypair0.pubkey();
        let ix = Instruction::new_with_bincode(program_id, &0, vec![AccountMeta::new(id0, true)]);
        let message = Message::new(&[ix], Some(&id0), ConfigHashPrefix::new(0), false);
        let now_millis = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .expect("Failed to get current time")
            .as_millis();
        let now: i64 = now_millis.try_into().unwrap_or(i64::MAX);
        Transaction::new_unsigned(message).sign(&Vec::<&Keypair>::new(), now);
    }

    #[test]
    #[should_panic]
    fn test_transaction_wrong_key() {
        let program_id = Pubkey::default();
        let keypair0 = Keypair::new();
        let wrong_id = Pubkey::default();
        let ix =
            Instruction::new_with_bincode(program_id, &0, vec![AccountMeta::new(wrong_id, true)]);
        let message = Message::new(&[ix], Some(&wrong_id), ConfigHashPrefix::new(0), false);
        let now_millis = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .expect("Failed to get current time")
            .as_millis();
        let now: i64 = now_millis.try_into().unwrap_or(i64::MAX);
        Transaction::new_unsigned(message).sign(&[&keypair0], now);
    }

    #[test]
    fn test_transaction_correct_key() {
        let program_id = Pubkey::default();
        let keypair0 = Keypair::new();
        let id0 = keypair0.pubkey();
        let ix = Instruction::new_with_bincode(program_id, &0, vec![AccountMeta::new(id0, true)]);
        let message = Message::new(&[ix], Some(&id0), ConfigHashPrefix::new(0), false);
        let mut tx = Transaction::new_unsigned(message);
        let now_millis = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .expect("Failed to get current time")
            .as_millis();
        let now: i64 = now_millis.try_into().unwrap_or(i64::MAX);
        tx.sign(&[&keypair0], now);
        assert_eq!(
            tx.message.instructions[0],
            CompiledInstruction::new(1, &0, vec![0])
        );
        assert!(tx.is_signed());
    }

    #[test]
    fn test_transaction_instruction_with_duplicate_keys() {
        let program_id = Pubkey::default();
        let keypair0 = Keypair::new();
        let id0 = keypair0.pubkey();
        let id1 = rialo_s_pubkey::new_rand();
        let ix = Instruction::new_with_bincode(
            program_id,
            &0,
            vec![
                AccountMeta::new(id0, true),
                AccountMeta::new(id1, false),
                AccountMeta::new(id0, false),
                AccountMeta::new(id1, false),
            ],
        );
        let message = Message::new(&[ix], Some(&id0), ConfigHashPrefix::new(1), false);
        let mut tx = Transaction::new_unsigned(message);
        let now_millis = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .expect("Failed to get current time")
            .as_millis();
        let now: i64 = now_millis.try_into().unwrap_or(i64::MAX);
        tx.sign(&[&keypair0], now);
        assert_eq!(
            tx.message.instructions[0],
            CompiledInstruction::new(2, &0, vec![0, 1, 0, 1])
        );
        assert!(tx.is_signed());
    }

    #[test]
    fn test_try_sign_dyn_keypairs() {
        let program_id = Pubkey::default();
        let keypair = Keypair::new();
        let pubkey = keypair.pubkey();
        let presigner_keypair = Keypair::new();
        let presigner_pubkey = presigner_keypair.pubkey();

        let now_millis = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .expect("Failed to get current time")
            .as_millis();
        let now: i64 = now_millis.try_into().unwrap_or(i64::MAX);

        let ix = Instruction::new_with_bincode(
            program_id,
            &0,
            vec![
                AccountMeta::new(pubkey, true),
                AccountMeta::new(presigner_pubkey, true),
            ],
        );
        let message = Message::new_with_valid_from(
            &[ix],
            Some(&pubkey),
            now,
            ConfigHashPrefix::new(1),
            false,
        );
        let mut tx = Transaction::new_unsigned(message);

        let presigner_sig = presigner_keypair.sign_message(&tx.message_data());
        let presigner = Presigner::new(&presigner_pubkey, &presigner_sig);

        let signers: Vec<&dyn Signer> = vec![&keypair, &presigner];

        let res = tx.try_sign(&signers, now);
        assert_eq!(res, Ok(()));
        assert_eq!(tx.signatures[0], keypair.sign_message(&tx.message_data()));
        assert_eq!(tx.signatures[1], presigner_sig);

        // Wrong key should error, not panic
        let another_pubkey = rialo_s_pubkey::new_rand();
        let ix = Instruction::new_with_bincode(
            program_id,
            &0,
            vec![
                AccountMeta::new(another_pubkey, true),
                AccountMeta::new(presigner_pubkey, true),
            ],
        );
        let message = Message::new(
            &[ix],
            Some(&another_pubkey),
            ConfigHashPrefix::new(1),
            false,
        );
        let mut tx = Transaction::new_unsigned(message);

        let res = tx.try_sign(&signers, now);
        assert!(res.is_err());
        assert_eq!(
            tx.signatures,
            vec![Signature::default(), Signature::default()]
        );
    }

    #[test]
    fn tx_keypair_pubkey_mismatch() {
        let from_keypair = Keypair::new();
        let from_pubkey = from_keypair.pubkey();
        let to_pubkey = Pubkey::new_unique();
        let instructions = [system_instruction::transfer(&from_pubkey, &to_pubkey, 42)];
        let mut tx = Transaction::new_with_payer(
            &instructions,
            Some(&from_pubkey),
            ConfigHashPrefix::new(1),
            false,
        );
        let unused_keypair = Keypair::new();
        let now_millis = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .expect("Failed to get current time")
            .as_millis();
        let now: i64 = now_millis.try_into().unwrap_or(i64::MAX);
        let err = tx
            .try_partial_sign(&[&from_keypair, &unused_keypair], now)
            .unwrap_err();
        assert_eq!(err, SignerError::KeypairPubkeyMismatch);
    }

    #[test]
    fn test_unsized_signers() {
        fn instructions_to_tx(
            instructions: &[Instruction],
            signers: Box<dyn Signers>,
        ) -> Transaction {
            let pubkeys = signers.pubkeys();
            let first_signer = pubkeys.first().expect("should exist");
            let message = Message::new(
                instructions,
                Some(first_signer),
                ConfigHashPrefix::new(1),
                false,
            );
            let now_millis = SystemTime::now()
                .duration_since(UNIX_EPOCH)
                .expect("Failed to get current time")
                .as_millis();
            let now: i64 = now_millis.try_into().unwrap_or(i64::MAX);
            Transaction::new(signers.as_ref(), message, now)
        }

        let signer: Box<dyn Signer> = Box::new(Keypair::new());
        let tx = instructions_to_tx(&[], Box::new(vec![signer]));

        assert!(tx.is_signed());
    }

    #[test]
    fn test_replace_signatures() {
        let program_id = Pubkey::default();
        let keypair0 = Keypair::new();
        let keypair1 = Keypair::new();
        let pubkey0 = keypair0.pubkey();
        let pubkey1 = keypair1.pubkey();
        let ix = Instruction::new_with_bincode(
            program_id,
            &0,
            vec![
                AccountMeta::new(pubkey0, true),
                AccountMeta::new(pubkey1, true),
            ],
        );
        let message = Message::new(&[ix], Some(&pubkey0), ConfigHashPrefix::new(1), false);
        let expected_account_keys = message.account_keys.clone();
        let mut tx = Transaction::new_unsigned(message);
        let now_millis = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .expect("Failed to get current time")
            .as_millis();
        let now: i64 = now_millis.try_into().unwrap_or(i64::MAX);
        tx.sign(&[&keypair0, &keypair1], now);

        let signature0 = keypair0.sign_message(&tx.message_data());
        let signature1 = keypair1.sign_message(&tx.message_data());

        // Replace signatures with order swapped
        tx.replace_signatures(&[(pubkey1, signature1), (pubkey0, signature0)])
            .unwrap();
        // Order of account_keys should not change
        assert_eq!(tx.message.account_keys, expected_account_keys);
        // Order of signatures should match original account_keys list
        assert_eq!(tx.signatures, &[signature0, signature1]);
    }
}
