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
28pub const MESSAGE_VERSION_PREFIX: u8 = 0x80;
30
31#[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 pub fn is_signer(&self, index: usize) -> bool {
82 index < usize::from(self.header().num_required_signatures)
83 }
84
85 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 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 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 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 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 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 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 #[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 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 de::Error::invalid_length(1, &self)
305 },
306 )?))
307 }
308 127 => {
309 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 {
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 {
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}