solana_message/versions/
mod.rs

1#[cfg(feature = "frozen-abi")]
2use solana_frozen_abi_macro::{frozen_abi, AbiEnumVisitor, AbiExample};
3use {
4    crate::{
5        compiled_instruction::CompiledInstruction, legacy::Message as LegacyMessage,
6        v0::MessageAddressTableLookup, MessageHeader,
7    },
8    solana_address::Address,
9    solana_hash::Hash,
10    solana_sanitize::{Sanitize, SanitizeError},
11    std::collections::HashSet,
12};
13#[cfg(feature = "serde")]
14use {
15    serde::{
16        de::{self, Deserializer, SeqAccess, Unexpected, Visitor},
17        ser::{SerializeTuple, Serializer},
18    },
19    serde_derive::{Deserialize, Serialize},
20    std::fmt,
21};
22
23mod sanitized;
24pub mod v0;
25
26pub use sanitized::*;
27
28/// Bit mask that indicates whether a serialized message is versioned.
29pub const MESSAGE_VERSION_PREFIX: u8 = 0x80;
30
31/// Either a legacy message or a v0 message.
32///
33/// # Serialization
34///
35/// If the first bit is set, the remaining 7 bits will be used to determine
36/// which message version is serialized starting from version `0`. If the first
37/// is bit is not set, all bytes are used to encode the legacy `Message`
38/// format.
39#[cfg_attr(
40    feature = "frozen-abi",
41    frozen_abi(digest = "Hndd1SDxQ5qNZvzHo77dpW6uD5c1DJNVjtg8tE6hc432"),
42    derive(AbiEnumVisitor, AbiExample)
43)]
44#[derive(Debug, PartialEq, Eq, Clone)]
45pub enum VersionedMessage {
46    Legacy(LegacyMessage),
47    V0(v0::Message),
48}
49
50impl VersionedMessage {
51    pub fn sanitize(&self) -> Result<(), SanitizeError> {
52        match self {
53            Self::Legacy(message) => message.sanitize(),
54            Self::V0(message) => message.sanitize(),
55        }
56    }
57
58    pub fn header(&self) -> &MessageHeader {
59        match self {
60            Self::Legacy(message) => &message.header,
61            Self::V0(message) => &message.header,
62        }
63    }
64
65    pub fn static_account_keys(&self) -> &[Address] {
66        match self {
67            Self::Legacy(message) => &message.account_keys,
68            Self::V0(message) => &message.account_keys,
69        }
70    }
71
72    pub fn address_table_lookups(&self) -> Option<&[MessageAddressTableLookup]> {
73        match self {
74            Self::Legacy(_) => None,
75            Self::V0(message) => Some(&message.address_table_lookups),
76        }
77    }
78
79    /// Returns true if the account at the specified index signed this
80    /// message.
81    pub fn is_signer(&self, index: usize) -> bool {
82        index < usize::from(self.header().num_required_signatures)
83    }
84
85    /// Returns true if the account at the specified index is writable by the
86    /// instructions in this message. Since dynamically loaded addresses can't
87    /// have write locks demoted without loading addresses, this shouldn't be
88    /// used in the runtime.
89    pub fn is_maybe_writable(
90        &self,
91        index: usize,
92        reserved_account_keys: Option<&HashSet<Address>>,
93    ) -> bool {
94        match self {
95            Self::Legacy(message) => message.is_maybe_writable(index, reserved_account_keys),
96            Self::V0(message) => message.is_maybe_writable(index, reserved_account_keys),
97        }
98    }
99
100    /// Returns true if the account at the specified index is an input to some
101    /// program instruction in this message.
102    fn is_instruction_account(&self, key_index: usize) -> bool {
103        if let Ok(key_index) = u8::try_from(key_index) {
104            self.instructions()
105                .iter()
106                .any(|ix| ix.accounts.contains(&key_index))
107        } else {
108            false
109        }
110    }
111
112    pub fn is_invoked(&self, key_index: usize) -> bool {
113        match self {
114            Self::Legacy(message) => message.is_key_called_as_program(key_index),
115            Self::V0(message) => message.is_key_called_as_program(key_index),
116        }
117    }
118
119    /// Returns true if the account at the specified index is not invoked as a
120    /// program or, if invoked, is passed to a program.
121    pub fn is_non_loader_key(&self, key_index: usize) -> bool {
122        !self.is_invoked(key_index) || self.is_instruction_account(key_index)
123    }
124
125    pub fn recent_blockhash(&self) -> &Hash {
126        match self {
127            Self::Legacy(message) => &message.recent_blockhash,
128            Self::V0(message) => &message.recent_blockhash,
129        }
130    }
131
132    pub fn set_recent_blockhash(&mut self, recent_blockhash: Hash) {
133        match self {
134            Self::Legacy(message) => message.recent_blockhash = recent_blockhash,
135            Self::V0(message) => message.recent_blockhash = recent_blockhash,
136        }
137    }
138
139    /// Program instructions that will be executed in sequence and committed in
140    /// one atomic transaction if all succeed.
141    pub fn instructions(&self) -> &[CompiledInstruction] {
142        match self {
143            Self::Legacy(message) => &message.instructions,
144            Self::V0(message) => &message.instructions,
145        }
146    }
147
148    #[cfg(feature = "bincode")]
149    pub fn serialize(&self) -> Vec<u8> {
150        bincode::serialize(self).unwrap()
151    }
152
153    #[cfg(all(feature = "bincode", feature = "blake3"))]
154    /// Compute the blake3 hash of this transaction's message
155    pub fn hash(&self) -> Hash {
156        let message_bytes = self.serialize();
157        Self::hash_raw_message(&message_bytes)
158    }
159
160    #[cfg(feature = "blake3")]
161    /// Compute the blake3 hash of a raw transaction message
162    pub fn hash_raw_message(message_bytes: &[u8]) -> Hash {
163        use blake3::traits::digest::Digest;
164        let mut hasher = blake3::Hasher::new();
165        hasher.update(b"solana-tx-message-v1");
166        hasher.update(message_bytes);
167        let hash_bytes: [u8; solana_hash::HASH_BYTES] = hasher.finalize().into();
168        hash_bytes.into()
169    }
170}
171
172impl Default for VersionedMessage {
173    fn default() -> Self {
174        Self::Legacy(LegacyMessage::default())
175    }
176}
177
178#[cfg(feature = "serde")]
179impl serde::Serialize for VersionedMessage {
180    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
181    where
182        S: Serializer,
183    {
184        match self {
185            Self::Legacy(message) => {
186                let mut seq = serializer.serialize_tuple(1)?;
187                seq.serialize_element(message)?;
188                seq.end()
189            }
190            Self::V0(message) => {
191                let mut seq = serializer.serialize_tuple(2)?;
192                seq.serialize_element(&MESSAGE_VERSION_PREFIX)?;
193                seq.serialize_element(message)?;
194                seq.end()
195            }
196        }
197    }
198}
199
200#[cfg(feature = "serde")]
201enum MessagePrefix {
202    Legacy(u8),
203    Versioned(u8),
204}
205
206#[cfg(feature = "serde")]
207impl<'de> serde::Deserialize<'de> for MessagePrefix {
208    fn deserialize<D>(deserializer: D) -> Result<MessagePrefix, D::Error>
209    where
210        D: Deserializer<'de>,
211    {
212        struct PrefixVisitor;
213
214        impl Visitor<'_> for PrefixVisitor {
215            type Value = MessagePrefix;
216
217            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
218                formatter.write_str("message prefix byte")
219            }
220
221            // Serde's integer visitors bubble up to u64 so check the prefix
222            // with this function instead of visit_u8. This approach is
223            // necessary because serde_json directly calls visit_u64 for
224            // unsigned integers.
225            fn visit_u64<E: de::Error>(self, value: u64) -> Result<MessagePrefix, E> {
226                if value > u8::MAX as u64 {
227                    Err(de::Error::invalid_type(Unexpected::Unsigned(value), &self))?;
228                }
229
230                let byte = value as u8;
231                if byte & MESSAGE_VERSION_PREFIX != 0 {
232                    Ok(MessagePrefix::Versioned(byte & !MESSAGE_VERSION_PREFIX))
233                } else {
234                    Ok(MessagePrefix::Legacy(byte))
235                }
236            }
237        }
238
239        deserializer.deserialize_u8(PrefixVisitor)
240    }
241}
242
243#[cfg(feature = "serde")]
244impl<'de> serde::Deserialize<'de> for VersionedMessage {
245    fn deserialize<D>(deserializer: D) -> Result<VersionedMessage, D::Error>
246    where
247        D: Deserializer<'de>,
248    {
249        struct MessageVisitor;
250
251        impl<'de> Visitor<'de> for MessageVisitor {
252            type Value = VersionedMessage;
253
254            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
255                formatter.write_str("message bytes")
256            }
257
258            fn visit_seq<A>(self, mut seq: A) -> Result<VersionedMessage, A::Error>
259            where
260                A: SeqAccess<'de>,
261            {
262                let prefix: MessagePrefix = seq
263                    .next_element()?
264                    .ok_or_else(|| de::Error::invalid_length(0, &self))?;
265
266                match prefix {
267                    MessagePrefix::Legacy(num_required_signatures) => {
268                        // The remaining fields of the legacy Message struct after the first byte.
269                        #[derive(Serialize, Deserialize)]
270                        struct RemainingLegacyMessage {
271                            pub num_readonly_signed_accounts: u8,
272                            pub num_readonly_unsigned_accounts: u8,
273                            #[cfg_attr(feature = "serde", serde(with = "solana_short_vec"))]
274                            pub account_keys: Vec<Address>,
275                            pub recent_blockhash: Hash,
276                            #[cfg_attr(feature = "serde", serde(with = "solana_short_vec"))]
277                            pub instructions: Vec<CompiledInstruction>,
278                        }
279
280                        let message: RemainingLegacyMessage =
281                            seq.next_element()?.ok_or_else(|| {
282                                // will never happen since tuple length is always 2
283                                de::Error::invalid_length(1, &self)
284                            })?;
285
286                        Ok(VersionedMessage::Legacy(LegacyMessage {
287                            header: MessageHeader {
288                                num_required_signatures,
289                                num_readonly_signed_accounts: message.num_readonly_signed_accounts,
290                                num_readonly_unsigned_accounts: message
291                                    .num_readonly_unsigned_accounts,
292                            },
293                            account_keys: message.account_keys,
294                            recent_blockhash: message.recent_blockhash,
295                            instructions: message.instructions,
296                        }))
297                    }
298                    MessagePrefix::Versioned(version) => {
299                        match version {
300                            0 => {
301                                Ok(VersionedMessage::V0(seq.next_element()?.ok_or_else(
302                                    || {
303                                        // will never happen since tuple length is always 2
304                                        de::Error::invalid_length(1, &self)
305                                    },
306                                )?))
307                            }
308                            127 => {
309                                // 0xff is used as the first byte of the off-chain messages
310                                // which corresponds to version 127 of the versioned messages.
311                                // This explicit check is added to prevent the usage of version 127
312                                // in the runtime as a valid transaction.
313                                Err(de::Error::custom("off-chain messages are not accepted"))
314                            }
315                            _ => Err(de::Error::invalid_value(
316                                de::Unexpected::Unsigned(version as u64),
317                                &"a valid transaction message version",
318                            )),
319                        }
320                    }
321                }
322            }
323        }
324
325        deserializer.deserialize_tuple(2, MessageVisitor)
326    }
327}
328
329#[cfg(test)]
330mod tests {
331    use {
332        super::*,
333        crate::v0::MessageAddressTableLookup,
334        solana_instruction::{AccountMeta, Instruction},
335    };
336
337    #[test]
338    fn test_legacy_message_serialization() {
339        let program_id0 = Address::new_unique();
340        let program_id1 = Address::new_unique();
341        let id0 = Address::new_unique();
342        let id1 = Address::new_unique();
343        let id2 = Address::new_unique();
344        let id3 = Address::new_unique();
345        let instructions = vec![
346            Instruction::new_with_bincode(program_id0, &0, vec![AccountMeta::new(id0, false)]),
347            Instruction::new_with_bincode(program_id0, &0, vec![AccountMeta::new(id1, true)]),
348            Instruction::new_with_bincode(
349                program_id1,
350                &0,
351                vec![AccountMeta::new_readonly(id2, false)],
352            ),
353            Instruction::new_with_bincode(
354                program_id1,
355                &0,
356                vec![AccountMeta::new_readonly(id3, true)],
357            ),
358        ];
359
360        let mut message = LegacyMessage::new(&instructions, Some(&id1));
361        message.recent_blockhash = Hash::new_unique();
362        let wrapped_message = VersionedMessage::Legacy(message.clone());
363
364        // bincode
365        {
366            let bytes = bincode::serialize(&message).unwrap();
367            assert_eq!(bytes, bincode::serialize(&wrapped_message).unwrap());
368
369            let message_from_bytes: LegacyMessage = bincode::deserialize(&bytes).unwrap();
370            let wrapped_message_from_bytes: VersionedMessage =
371                bincode::deserialize(&bytes).unwrap();
372
373            assert_eq!(message, message_from_bytes);
374            assert_eq!(wrapped_message, wrapped_message_from_bytes);
375        }
376
377        // serde_json
378        {
379            let string = serde_json::to_string(&message).unwrap();
380            let message_from_string: LegacyMessage = serde_json::from_str(&string).unwrap();
381            assert_eq!(message, message_from_string);
382        }
383    }
384
385    #[test]
386    fn test_versioned_message_serialization() {
387        let message = VersionedMessage::V0(v0::Message {
388            header: MessageHeader {
389                num_required_signatures: 1,
390                num_readonly_signed_accounts: 0,
391                num_readonly_unsigned_accounts: 0,
392            },
393            recent_blockhash: Hash::new_unique(),
394            account_keys: vec![Address::new_unique()],
395            address_table_lookups: vec![
396                MessageAddressTableLookup {
397                    account_key: Address::new_unique(),
398                    writable_indexes: vec![1],
399                    readonly_indexes: vec![0],
400                },
401                MessageAddressTableLookup {
402                    account_key: Address::new_unique(),
403                    writable_indexes: vec![0],
404                    readonly_indexes: vec![1],
405                },
406            ],
407            instructions: vec![CompiledInstruction {
408                program_id_index: 1,
409                accounts: vec![0, 2, 3, 4],
410                data: vec![],
411            }],
412        });
413
414        let bytes = bincode::serialize(&message).unwrap();
415        let message_from_bytes: VersionedMessage = bincode::deserialize(&bytes).unwrap();
416        assert_eq!(message, message_from_bytes);
417
418        let string = serde_json::to_string(&message).unwrap();
419        let message_from_string: VersionedMessage = serde_json::from_str(&string).unwrap();
420        assert_eq!(message, message_from_string);
421    }
422}