solana_program/message/versions/
mod.rs

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