use { crate::{ hash::Hash, instruction::CompiledInstruction, message::{legacy::Message as LegacyMessage, v0::MessageAddressTableLookup, MessageHeader}, pubkey::Pubkey, sanitize::{Sanitize, SanitizeError}, short_vec, }, serde::{ de::{self, Deserializer, SeqAccess, Visitor}, ser::{SerializeTuple, Serializer}, Deserialize, Serialize, }, std::fmt, }; mod sanitized; pub mod v0; pub use sanitized::*; /// Bit mask that indicates whether a serialized message is versioned. pub const MESSAGE_VERSION_PREFIX: u8 = 0x80; /// Either a legacy message or a v0 message. /// /// # Serialization /// /// If the first bit is set, the remaining 7 bits will be used to determine /// which message version is serialized starting from version `0`. If the first /// is bit is not set, all bytes are used to encode the legacy `Message` /// format. #[frozen_abi(digest = "G4EAiqmGgBprgf5ePYemLJcoFfx4R7rhC1Weo2FVJ7fn")] #[derive(Debug, PartialEq, Eq, Clone, AbiEnumVisitor, AbiExample)] pub enum VersionedMessage { Legacy(LegacyMessage), V0(v0::Message), } impl VersionedMessage { pub fn sanitize(&self) -> Result<(), SanitizeError> { match self { Self::Legacy(message) => message.sanitize(), Self::V0(message) => message.sanitize(), } } pub fn header(&self) -> &MessageHeader { match self { Self::Legacy(message) => &message.header, Self::V0(message) => &message.header, } } pub fn static_account_keys(&self) -> &[Pubkey] { match self { Self::Legacy(message) => &message.account_keys, Self::V0(message) => &message.account_keys, } } pub fn address_table_lookups(&self) -> Option<&[MessageAddressTableLookup]> { match self { Self::Legacy(_) => None, Self::V0(message) => Some(&message.address_table_lookups), } } /// Returns true if the account at the specified index signed this /// message. pub fn is_signer(&self, index: usize) -> bool { index < usize::from(self.header().num_required_signatures) } /// Returns true if the account at the specified index is writable by the /// instructions in this message. Since dynamically loaded addresses can't /// have write locks demoted without loading addresses, this shouldn't be /// used in the runtime. pub fn is_maybe_writable(&self, index: usize) -> bool { match self { Self::Legacy(message) => message.is_writable(index), Self::V0(message) => message.is_maybe_writable(index), } } /// Returns true if the account at the specified index is an input to some /// program instruction in this message. fn is_key_passed_to_program(&self, key_index: usize) -> bool { if let Ok(key_index) = u8::try_from(key_index) { self.instructions() .iter() .any(|ix| ix.accounts.contains(&key_index)) } else { false } } pub fn is_invoked(&self, key_index: usize) -> bool { match self { Self::Legacy(message) => message.is_key_called_as_program(key_index), Self::V0(message) => message.is_key_called_as_program(key_index), } } /// Returns true if the account at the specified index is not invoked as a /// program or, if invoked, is passed to a program. pub fn is_non_loader_key(&self, key_index: usize) -> bool { !self.is_invoked(key_index) || self.is_key_passed_to_program(key_index) } pub fn recent_blockhash(&self) -> &Hash { match self { Self::Legacy(message) => &message.recent_blockhash, Self::V0(message) => &message.recent_blockhash, } } pub fn set_recent_blockhash(&mut self, recent_blockhash: Hash) { match self { Self::Legacy(message) => message.recent_blockhash = recent_blockhash, Self::V0(message) => message.recent_blockhash = recent_blockhash, } } /// Program instructions that will be executed in sequence and committed in /// one atomic transaction if all succeed. pub fn instructions(&self) -> &[CompiledInstruction] { match self { Self::Legacy(message) => &message.instructions, Self::V0(message) => &message.instructions, } } pub fn serialize(&self) -> Vec { bincode::serialize(self).unwrap() } /// Compute the blake3 hash of this transaction's message pub fn hash(&self) -> Hash { let message_bytes = self.serialize(); Self::hash_raw_message(&message_bytes) } /// Compute the blake3 hash of a raw transaction message pub fn hash_raw_message(message_bytes: &[u8]) -> Hash { use blake3::traits::digest::Digest; let mut hasher = blake3::Hasher::new(); hasher.update(b"solana-tx-message-v1"); hasher.update(message_bytes); Hash(<[u8; crate::hash::HASH_BYTES]>::try_from(hasher.finalize().as_slice()).unwrap()) } } impl Default for VersionedMessage { fn default() -> Self { Self::Legacy(LegacyMessage::default()) } } impl Serialize for VersionedMessage { fn serialize(&self, serializer: S) -> Result where S: Serializer, { match self { Self::Legacy(message) => { let mut seq = serializer.serialize_tuple(1)?; seq.serialize_element(message)?; seq.end() } Self::V0(message) => { let mut seq = serializer.serialize_tuple(2)?; seq.serialize_element(&MESSAGE_VERSION_PREFIX)?; seq.serialize_element(message)?; seq.end() } } } } enum MessagePrefix { Legacy(u8), Versioned(u8), } impl<'de> Deserialize<'de> for MessagePrefix { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { struct PrefixVisitor; impl<'de> Visitor<'de> for PrefixVisitor { type Value = MessagePrefix; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("message prefix byte") } fn visit_u8(self, byte: u8) -> Result { if byte & MESSAGE_VERSION_PREFIX != 0 { Ok(MessagePrefix::Versioned(byte & !MESSAGE_VERSION_PREFIX)) } else { Ok(MessagePrefix::Legacy(byte)) } } } deserializer.deserialize_u8(PrefixVisitor) } } impl<'de> Deserialize<'de> for VersionedMessage { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { struct MessageVisitor; impl<'de> Visitor<'de> for MessageVisitor { type Value = VersionedMessage; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("message bytes") } fn visit_seq(self, mut seq: A) -> Result where A: SeqAccess<'de>, { let prefix: MessagePrefix = seq .next_element()? .ok_or_else(|| de::Error::invalid_length(0, &self))?; match prefix { MessagePrefix::Legacy(num_required_signatures) => { // The remaining fields of the legacy Message struct after the first byte. #[derive(Serialize, Deserialize)] struct RemainingLegacyMessage { pub num_readonly_signed_accounts: u8, pub num_readonly_unsigned_accounts: u8, #[serde(with = "short_vec")] pub account_keys: Vec, pub recent_blockhash: Hash, #[serde(with = "short_vec")] pub instructions: Vec, } let message: RemainingLegacyMessage = seq.next_element()?.ok_or_else(|| { // will never happen since tuple length is always 2 de::Error::invalid_length(1, &self) })?; Ok(VersionedMessage::Legacy(LegacyMessage { header: MessageHeader { num_required_signatures, num_readonly_signed_accounts: message.num_readonly_signed_accounts, num_readonly_unsigned_accounts: message .num_readonly_unsigned_accounts, }, account_keys: message.account_keys, recent_blockhash: message.recent_blockhash, instructions: message.instructions, })) } MessagePrefix::Versioned(version) => { match version { 0 => { Ok(VersionedMessage::V0(seq.next_element()?.ok_or_else( || { // will never happen since tuple length is always 2 de::Error::invalid_length(1, &self) }, )?)) } 127 => { // 0xff is used as the first byte of the off-chain messages // which corresponds to version 127 of the versioned messages. // This explicit check is added to prevent the usage of version 127 // in the runtime as a valid transaction. Err(de::Error::custom("off-chain messages are not accepted")) } _ => Err(de::Error::invalid_value( de::Unexpected::Unsigned(version as u64), &"a valid transaction message version", )), } } } } } deserializer.deserialize_tuple(2, MessageVisitor) } } #[cfg(test)] mod tests { use { super::*, crate::{ instruction::{AccountMeta, Instruction}, message::v0::MessageAddressTableLookup, }, }; #[test] fn test_legacy_message_serialization() { let program_id0 = Pubkey::new_unique(); let program_id1 = Pubkey::new_unique(); let id0 = Pubkey::new_unique(); let id1 = Pubkey::new_unique(); let id2 = Pubkey::new_unique(); let id3 = Pubkey::new_unique(); let instructions = vec![ Instruction::new_with_bincode(program_id0, &0, vec![AccountMeta::new(id0, false)]), Instruction::new_with_bincode(program_id0, &0, vec![AccountMeta::new(id1, true)]), Instruction::new_with_bincode( program_id1, &0, vec![AccountMeta::new_readonly(id2, false)], ), Instruction::new_with_bincode( program_id1, &0, vec![AccountMeta::new_readonly(id3, true)], ), ]; let mut message = LegacyMessage::new(&instructions, Some(&id1)); message.recent_blockhash = Hash::new_unique(); let bytes1 = bincode::serialize(&message).unwrap(); let bytes2 = bincode::serialize(&VersionedMessage::Legacy(message.clone())).unwrap(); assert_eq!(bytes1, bytes2); let message1: LegacyMessage = bincode::deserialize(&bytes1).unwrap(); let message2: VersionedMessage = bincode::deserialize(&bytes2).unwrap(); if let VersionedMessage::Legacy(message2) = message2 { assert_eq!(message, message1); assert_eq!(message1, message2); } else { panic!("should deserialize to legacy message"); } } #[test] fn test_versioned_message_serialization() { let message = v0::Message { header: MessageHeader { num_required_signatures: 1, num_readonly_signed_accounts: 0, num_readonly_unsigned_accounts: 0, }, recent_blockhash: Hash::new_unique(), account_keys: vec![Pubkey::new_unique()], address_table_lookups: vec![ MessageAddressTableLookup { account_key: Pubkey::new_unique(), writable_indexes: vec![1], readonly_indexes: vec![0], }, MessageAddressTableLookup { account_key: Pubkey::new_unique(), writable_indexes: vec![0], readonly_indexes: vec![1], }, ], instructions: vec![CompiledInstruction { program_id_index: 1, accounts: vec![0, 2, 3, 4], data: vec![], }], }; let bytes = bincode::serialize(&VersionedMessage::V0(message.clone())).unwrap(); let message_from_bytes: VersionedMessage = bincode::deserialize(&bytes).unwrap(); if let VersionedMessage::V0(message_from_bytes) = message_from_bytes { assert_eq!(message, message_from_bytes); } else { panic!("should deserialize to versioned message"); } } }