//! Defines a transaction which supports multiple versions of messages. #![cfg(feature = "full")] use { crate::{ hash::Hash, message::VersionedMessage, sanitize::SanitizeError, short_vec, signature::Signature, signer::SignerError, signers::Signers, transaction::{Result, Transaction, TransactionError}, }, serde::Serialize, std::cmp::Ordering, }; mod sanitized; pub use sanitized::*; use { crate::program_utils::limited_deserialize, solana_program::{ nonce::NONCED_TX_MARKER_IX_INDEX, system_instruction::SystemInstruction, system_program, }, }; /// Type that serializes to the string "legacy" #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub enum Legacy { Legacy, } #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase", untagged)] pub enum TransactionVersion { Legacy(Legacy), Number(u8), } impl TransactionVersion { pub const LEGACY: Self = Self::Legacy(Legacy::Legacy); } // NOTE: Serialization-related changes must be paired with the direct read at sigverify. /// An atomic transaction #[derive(Debug, PartialEq, Default, Eq, Clone, Serialize, Deserialize, AbiExample)] pub struct VersionedTransaction { /// List of signatures #[serde(with = "short_vec")] pub signatures: Vec, /// Message to sign. pub message: VersionedMessage, } impl From for VersionedTransaction { fn from(transaction: Transaction) -> Self { Self { signatures: transaction.signatures, message: VersionedMessage::Legacy(transaction.message), } } } impl VersionedTransaction { /// Signs a versioned message and if successful, returns a signed /// transaction. pub fn try_new( message: VersionedMessage, keypairs: &T, ) -> std::result::Result { let static_account_keys = message.static_account_keys(); if static_account_keys.len() < message.header().num_required_signatures as usize { return Err(SignerError::InvalidInput("invalid message".to_string())); } let signer_keys = keypairs.try_pubkeys()?; let expected_signer_keys = &static_account_keys[0..message.header().num_required_signatures as usize]; match signer_keys.len().cmp(&expected_signer_keys.len()) { Ordering::Greater => Err(SignerError::TooManySigners), Ordering::Less => Err(SignerError::NotEnoughSigners), Ordering::Equal => Ok(()), }?; let message_data = message.serialize(); let signature_indexes: Vec = expected_signer_keys .iter() .map(|signer_key| { signer_keys .iter() .position(|key| key == signer_key) .ok_or(SignerError::KeypairPubkeyMismatch) }) .collect::>()?; let unordered_signatures = keypairs.try_sign_message(&message_data)?; let signatures: Vec = signature_indexes .into_iter() .map(|index| { unordered_signatures .get(index) .copied() .ok_or_else(|| SignerError::InvalidInput("invalid keypairs".to_string())) }) .collect::>()?; Ok(Self { signatures, message, }) } pub fn sanitize(&self) -> std::result::Result<(), SanitizeError> { self.message.sanitize()?; self.sanitize_signatures()?; Ok(()) } pub(crate) fn sanitize_signatures(&self) -> std::result::Result<(), SanitizeError> { let num_required_signatures = usize::from(self.message.header().num_required_signatures); match num_required_signatures.cmp(&self.signatures.len()) { Ordering::Greater => Err(SanitizeError::IndexOutOfBounds), Ordering::Less => Err(SanitizeError::InvalidValue), Ordering::Equal => Ok(()), }?; // Signatures are verified before message keys are loaded so all signers // must correspond to static account keys. if self.signatures.len() > self.message.static_account_keys().len() { return Err(SanitizeError::IndexOutOfBounds); } Ok(()) } /// Returns the version of the transaction pub fn version(&self) -> TransactionVersion { match self.message { VersionedMessage::Legacy(_) => TransactionVersion::LEGACY, VersionedMessage::V0(_) => TransactionVersion::Number(0), } } /// Returns a legacy transaction if the transaction message is legacy. pub fn into_legacy_transaction(self) -> Option { match self.message { VersionedMessage::Legacy(message) => Some(Transaction { signatures: self.signatures, message, }), _ => None, } } /// Verify the transaction and hash its message pub fn verify_and_hash_message(&self) -> Result { let message_bytes = self.message.serialize(); if !self ._verify_with_results(&message_bytes) .iter() .all(|verify_result| *verify_result) { Err(TransactionError::SignatureFailure) } else { Ok(VersionedMessage::hash_raw_message(&message_bytes)) } } /// Verify the transaction and return a list of verification results pub fn verify_with_results(&self) -> Vec { let message_bytes = self.message.serialize(); self._verify_with_results(&message_bytes) } fn _verify_with_results(&self, message_bytes: &[u8]) -> Vec { self.signatures .iter() .zip(self.message.static_account_keys().iter()) .map(|(signature, pubkey)| signature.verify(pubkey.as_ref(), message_bytes)) .collect() } /// Returns true if transaction begins with a valid advance nonce /// instruction. Since dynamically loaded addresses can't have write locks /// demoted without loading addresses, this shouldn't be used in the /// runtime. pub fn uses_durable_nonce(&self) -> bool { let message = &self.message; message .instructions() .get(NONCED_TX_MARKER_IX_INDEX as usize) .filter(|instruction| { // Is system program matches!( message.static_account_keys().get(instruction.program_id_index as usize), Some(program_id) if system_program::check_id(program_id) ) // Is a nonce advance instruction && matches!( limited_deserialize(&instruction.data), Ok(SystemInstruction::AdvanceNonceAccount) ) // Nonce account is writable && matches!( instruction.accounts.first(), Some(index) if message.is_maybe_writable(*index as usize) ) }) .is_some() } } #[cfg(test)] mod tests { use { super::*, crate::{ message::Message as LegacyMessage, signer::{keypair::Keypair, Signer}, system_instruction, sysvar, }, solana_program::{ instruction::{AccountMeta, Instruction}, pubkey::Pubkey, }, }; #[test] fn test_try_new() { let keypair0 = Keypair::new(); let keypair1 = Keypair::new(); let keypair2 = Keypair::new(); let message = VersionedMessage::Legacy(LegacyMessage::new( &[Instruction::new_with_bytes( Pubkey::new_unique(), &[], vec![ AccountMeta::new_readonly(keypair1.pubkey(), true), AccountMeta::new_readonly(keypair2.pubkey(), false), ], )], Some(&keypair0.pubkey()), )); assert_eq!( VersionedTransaction::try_new(message.clone(), &[&keypair0]), Err(SignerError::NotEnoughSigners) ); assert_eq!( VersionedTransaction::try_new(message.clone(), &[&keypair0, &keypair0]), Err(SignerError::KeypairPubkeyMismatch) ); assert_eq!( VersionedTransaction::try_new(message.clone(), &[&keypair1, &keypair2]), Err(SignerError::KeypairPubkeyMismatch) ); match VersionedTransaction::try_new(message.clone(), &[&keypair0, &keypair1]) { Ok(tx) => assert_eq!(tx.verify_with_results(), vec![true; 2]), Err(err) => assert_eq!(Some(err), None), } match VersionedTransaction::try_new(message, &[&keypair1, &keypair0]) { Ok(tx) => assert_eq!(tx.verify_with_results(), vec![true; 2]), Err(err) => assert_eq!(Some(err), None), } } fn nonced_transfer_tx() -> (Pubkey, Pubkey, VersionedTransaction) { let from_keypair = Keypair::new(); let from_pubkey = from_keypair.pubkey(); let nonce_keypair = Keypair::new(); let nonce_pubkey = nonce_keypair.pubkey(); let instructions = [ system_instruction::advance_nonce_account(&nonce_pubkey, &nonce_pubkey), system_instruction::transfer(&from_pubkey, &nonce_pubkey, 42), ]; let message = LegacyMessage::new(&instructions, Some(&nonce_pubkey)); let tx = Transaction::new(&[&from_keypair, &nonce_keypair], message, Hash::default()); (from_pubkey, nonce_pubkey, tx.into()) } #[test] fn tx_uses_nonce_ok() { let (_, _, tx) = nonced_transfer_tx(); assert!(tx.uses_durable_nonce()); } #[test] fn tx_uses_nonce_empty_ix_fail() { assert!(!VersionedTransaction::default().uses_durable_nonce()); } #[test] fn tx_uses_nonce_bad_prog_id_idx_fail() { let (_, _, mut tx) = nonced_transfer_tx(); match &mut tx.message { VersionedMessage::Legacy(message) => { message.instructions.get_mut(0).unwrap().program_id_index = 255u8; } VersionedMessage::V0(_) => unreachable!(), }; assert!(!tx.uses_durable_nonce()); } #[test] fn tx_uses_nonce_first_prog_id_not_nonce_fail() { let from_keypair = Keypair::new(); let from_pubkey = from_keypair.pubkey(); let nonce_keypair = Keypair::new(); let nonce_pubkey = nonce_keypair.pubkey(); let instructions = [ system_instruction::transfer(&from_pubkey, &nonce_pubkey, 42), system_instruction::advance_nonce_account(&nonce_pubkey, &nonce_pubkey), ]; let message = LegacyMessage::new(&instructions, Some(&from_pubkey)); let tx = Transaction::new(&[&from_keypair, &nonce_keypair], message, Hash::default()); let tx = VersionedTransaction::from(tx); assert!(!tx.uses_durable_nonce()); } #[test] fn tx_uses_ro_nonce_account() { let from_keypair = Keypair::new(); let from_pubkey = from_keypair.pubkey(); let nonce_keypair = Keypair::new(); let nonce_pubkey = nonce_keypair.pubkey(); let account_metas = vec![ AccountMeta::new_readonly(nonce_pubkey, false), #[allow(deprecated)] AccountMeta::new_readonly(sysvar::recent_blockhashes::id(), false), AccountMeta::new_readonly(nonce_pubkey, true), ]; let nonce_instruction = Instruction::new_with_bincode( system_program::id(), &system_instruction::SystemInstruction::AdvanceNonceAccount, account_metas, ); let tx = Transaction::new_signed_with_payer( &[nonce_instruction], Some(&from_pubkey), &[&from_keypair, &nonce_keypair], Hash::default(), ); let tx = VersionedTransaction::from(tx); assert!(!tx.uses_durable_nonce()); } #[test] fn tx_uses_nonce_wrong_first_nonce_ix_fail() { let from_keypair = Keypair::new(); let from_pubkey = from_keypair.pubkey(); let nonce_keypair = Keypair::new(); let nonce_pubkey = nonce_keypair.pubkey(); let instructions = [ system_instruction::withdraw_nonce_account( &nonce_pubkey, &nonce_pubkey, &from_pubkey, 42, ), system_instruction::transfer(&from_pubkey, &nonce_pubkey, 42), ]; let message = LegacyMessage::new(&instructions, Some(&nonce_pubkey)); let tx = Transaction::new(&[&from_keypair, &nonce_keypair], message, Hash::default()); let tx = VersionedTransaction::from(tx); assert!(!tx.uses_durable_nonce()); } }