1#[cfg(any(feature = "wincode", feature = "serde"))]
2use alloc::vec::Vec;
3#[cfg(feature = "frozen-abi")]
4use solana_frozen_abi_macro::{frozen_abi, AbiEnumVisitor, AbiExample};
5#[cfg(feature = "std")]
6use std::collections::HashSet;
7use {
8 crate::{
9 compiled_instruction::CompiledInstruction, legacy::Message as LegacyMessage,
10 v0::MessageAddressTableLookup, MessageHeader,
11 },
12 solana_address::Address,
13 solana_hash::Hash,
14 solana_sanitize::{Sanitize, SanitizeError},
15};
16#[cfg(feature = "serde")]
17use {
18 core::fmt,
19 serde::{
20 de::{self, Deserializer, SeqAccess, Unexpected, Visitor},
21 ser::{SerializeTuple, Serializer},
22 },
23 serde_derive::{Deserialize, Serialize},
24};
25#[cfg(feature = "wincode")]
26use {
27 core::mem::MaybeUninit,
28 wincode::{
29 config::Config,
30 io::{Reader, Writer},
31 ReadResult, SchemaRead, SchemaReadContext, SchemaWrite, WriteResult,
32 },
33};
34
35mod sanitized;
36pub mod v0;
37pub mod v1;
38
39pub use sanitized::*;
40
41pub const MESSAGE_VERSION_PREFIX: u8 = 0x80;
43
44#[cfg_attr(
53 feature = "frozen-abi",
54 frozen_abi(digest = "9xQQLkQntX2QKgwxbbpeuNrs5V2WopsBa11su46WWCro"),
55 derive(AbiEnumVisitor, AbiExample)
56)]
57#[derive(Debug, PartialEq, Eq, Clone)]
58pub enum VersionedMessage {
59 Legacy(LegacyMessage),
60 V0(v0::Message),
61 V1(v1::Message),
62}
63
64impl VersionedMessage {
65 pub fn sanitize(&self) -> Result<(), SanitizeError> {
66 match self {
67 Self::Legacy(message) => message.sanitize(),
68 Self::V0(message) => message.sanitize(),
69 Self::V1(message) => message.sanitize(),
70 }
71 }
72
73 pub fn header(&self) -> &MessageHeader {
74 match self {
75 Self::Legacy(message) => &message.header,
76 Self::V0(message) => &message.header,
77 Self::V1(message) => &message.header,
78 }
79 }
80
81 pub fn static_account_keys(&self) -> &[Address] {
82 match self {
83 Self::Legacy(message) => &message.account_keys,
84 Self::V0(message) => &message.account_keys,
85 Self::V1(message) => &message.account_keys,
86 }
87 }
88
89 pub fn address_table_lookups(&self) -> Option<&[MessageAddressTableLookup]> {
90 match self {
91 Self::Legacy(_) => None,
92 Self::V0(message) => Some(&message.address_table_lookups),
93 Self::V1(_) => None,
94 }
95 }
96
97 pub fn is_signer(&self, index: usize) -> bool {
100 index < usize::from(self.header().num_required_signatures)
101 }
102
103 #[cfg(feature = "std")]
108 pub fn is_maybe_writable(
109 &self,
110 index: usize,
111 reserved_account_keys: Option<&HashSet<Address>>,
112 ) -> bool {
113 match self {
114 Self::Legacy(message) => message.is_maybe_writable(index, reserved_account_keys),
115 Self::V0(message) => message.is_maybe_writable(index, reserved_account_keys),
116 Self::V1(message) => message.is_maybe_writable(index, reserved_account_keys),
117 }
118 }
119
120 fn is_instruction_account(&self, key_index: usize) -> bool {
123 if let Ok(key_index) = u8::try_from(key_index) {
124 self.instructions()
125 .iter()
126 .any(|ix| ix.accounts.contains(&key_index))
127 } else {
128 false
129 }
130 }
131
132 pub fn is_invoked(&self, key_index: usize) -> bool {
133 match self {
134 Self::Legacy(message) => message.is_key_called_as_program(key_index),
135 Self::V0(message) => message.is_key_called_as_program(key_index),
136 Self::V1(message) => message.is_key_called_as_program(key_index),
137 }
138 }
139
140 pub fn is_non_loader_key(&self, key_index: usize) -> bool {
143 !self.is_invoked(key_index) || self.is_instruction_account(key_index)
144 }
145
146 pub fn recent_blockhash(&self) -> &Hash {
147 match self {
148 Self::Legacy(message) => &message.recent_blockhash,
149 Self::V0(message) => &message.recent_blockhash,
150 Self::V1(message) => &message.lifetime_specifier,
151 }
152 }
153
154 pub fn set_recent_blockhash(&mut self, recent_blockhash: Hash) {
155 match self {
156 Self::Legacy(message) => message.recent_blockhash = recent_blockhash,
157 Self::V0(message) => message.recent_blockhash = recent_blockhash,
158 Self::V1(message) => message.lifetime_specifier = recent_blockhash,
159 }
160 }
161
162 #[inline(always)]
165 pub fn instructions(&self) -> &[CompiledInstruction] {
166 match self {
167 Self::Legacy(message) => &message.instructions,
168 Self::V0(message) => &message.instructions,
169 Self::V1(message) => &message.instructions,
170 }
171 }
172
173 #[cfg(feature = "wincode")]
174 pub fn serialize(&self) -> Vec<u8> {
175 wincode::serialize(self).unwrap()
176 }
177
178 #[cfg(all(feature = "wincode", feature = "blake3"))]
179 pub fn hash(&self) -> Hash {
181 let message_bytes = self.serialize();
182 Self::hash_raw_message(&message_bytes)
183 }
184
185 #[cfg(feature = "blake3")]
186 pub fn hash_raw_message(message_bytes: &[u8]) -> Hash {
188 use blake3::traits::digest::Digest;
189 let mut hasher = blake3::Hasher::new();
190 hasher.update(b"solana-tx-message-v1");
191 hasher.update(message_bytes);
192 let hash_bytes: [u8; solana_hash::HASH_BYTES] = hasher.finalize().into();
193 hash_bytes.into()
194 }
195}
196
197impl Default for VersionedMessage {
198 fn default() -> Self {
199 Self::Legacy(LegacyMessage::default())
200 }
201}
202
203#[cfg(feature = "serde")]
204impl serde::Serialize for VersionedMessage {
205 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
206 where
207 S: Serializer,
208 {
209 match self {
210 Self::Legacy(message) => {
211 let mut seq = serializer.serialize_tuple(1)?;
212 seq.serialize_element(message)?;
213 seq.end()
214 }
215 Self::V0(message) => {
216 let mut seq = serializer.serialize_tuple(2)?;
217 seq.serialize_element(&MESSAGE_VERSION_PREFIX)?;
218 seq.serialize_element(message)?;
219 seq.end()
220 }
221 Self::V1(message) => {
222 let mut seq = serializer.serialize_tuple(2)?;
225 seq.serialize_element(&crate::v1::V1_PREFIX)?;
226 seq.serialize_element(message)?;
227 seq.end()
228 }
229 }
230 }
231}
232
233#[cfg(feature = "serde")]
234enum MessagePrefix {
235 Legacy(u8),
236 Versioned(u8),
237}
238
239#[cfg(feature = "serde")]
240impl<'de> serde::Deserialize<'de> for MessagePrefix {
241 fn deserialize<D>(deserializer: D) -> Result<MessagePrefix, D::Error>
242 where
243 D: Deserializer<'de>,
244 {
245 struct PrefixVisitor;
246
247 impl Visitor<'_> for PrefixVisitor {
248 type Value = MessagePrefix;
249
250 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
251 formatter.write_str("message prefix byte")
252 }
253
254 fn visit_u64<E: de::Error>(self, value: u64) -> Result<MessagePrefix, E> {
259 if value > u8::MAX as u64 {
260 Err(de::Error::invalid_type(Unexpected::Unsigned(value), &self))?;
261 }
262
263 let byte = value as u8;
264 if byte & MESSAGE_VERSION_PREFIX != 0 {
265 Ok(MessagePrefix::Versioned(byte & !MESSAGE_VERSION_PREFIX))
266 } else {
267 Ok(MessagePrefix::Legacy(byte))
268 }
269 }
270 }
271
272 deserializer.deserialize_u8(PrefixVisitor)
273 }
274}
275
276#[cfg(feature = "serde")]
277impl<'de> serde::Deserialize<'de> for VersionedMessage {
278 fn deserialize<D>(deserializer: D) -> Result<VersionedMessage, D::Error>
279 where
280 D: Deserializer<'de>,
281 {
282 struct MessageVisitor;
283
284 impl<'de> Visitor<'de> for MessageVisitor {
285 type Value = VersionedMessage;
286
287 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
288 formatter.write_str("message bytes")
289 }
290
291 fn visit_seq<A>(self, mut seq: A) -> Result<VersionedMessage, A::Error>
292 where
293 A: SeqAccess<'de>,
294 {
295 let prefix: MessagePrefix = seq
296 .next_element()?
297 .ok_or_else(|| de::Error::invalid_length(0, &self))?;
298
299 match prefix {
300 MessagePrefix::Legacy(num_required_signatures) => {
301 #[derive(Serialize, Deserialize)]
303 struct RemainingLegacyMessage {
304 pub num_readonly_signed_accounts: u8,
305 pub num_readonly_unsigned_accounts: u8,
306 #[cfg_attr(feature = "serde", serde(with = "solana_short_vec"))]
307 pub account_keys: Vec<Address>,
308 pub recent_blockhash: Hash,
309 #[cfg_attr(feature = "serde", serde(with = "solana_short_vec"))]
310 pub instructions: Vec<CompiledInstruction>,
311 }
312
313 let message: RemainingLegacyMessage =
314 seq.next_element()?.ok_or_else(|| {
315 de::Error::invalid_length(1, &self)
317 })?;
318
319 Ok(VersionedMessage::Legacy(LegacyMessage {
320 header: MessageHeader {
321 num_required_signatures,
322 num_readonly_signed_accounts: message.num_readonly_signed_accounts,
323 num_readonly_unsigned_accounts: message
324 .num_readonly_unsigned_accounts,
325 },
326 account_keys: message.account_keys,
327 recent_blockhash: message.recent_blockhash,
328 instructions: message.instructions,
329 }))
330 }
331 MessagePrefix::Versioned(version) => {
332 match version {
333 0 => {
334 Ok(VersionedMessage::V0(seq.next_element()?.ok_or_else(
335 || {
336 de::Error::invalid_length(1, &self)
338 },
339 )?))
340 }
341 1 => {
342 Ok(VersionedMessage::V1(seq.next_element()?.ok_or_else(
343 || {
344 de::Error::invalid_length(1, &self)
346 },
347 )?))
348 }
349 127 => {
350 Err(de::Error::custom("off-chain messages are not accepted"))
355 }
356 _ => Err(de::Error::invalid_value(
357 de::Unexpected::Unsigned(version as u64),
358 &"a valid transaction message version",
359 )),
360 }
361 }
362 }
363 }
364 }
365
366 deserializer.deserialize_tuple(2, MessageVisitor)
367 }
368}
369
370#[cfg(feature = "wincode")]
371unsafe impl<C: Config> SchemaWrite<C> for VersionedMessage {
372 type Src = Self;
373
374 #[allow(clippy::arithmetic_side_effects)]
376 #[inline(always)]
377 fn size_of(src: &Self::Src) -> WriteResult<usize> {
378 match src {
379 VersionedMessage::Legacy(message) => {
380 <LegacyMessage as SchemaWrite<C>>::size_of(message)
381 }
382 VersionedMessage::V0(message) => {
383 Ok(1 + <v0::Message as SchemaWrite<C>>::size_of(message)?)
384 }
385 VersionedMessage::V1(message) => Ok(1 + message.size()),
386 }
387 }
388
389 #[allow(clippy::arithmetic_side_effects)]
391 #[inline(always)]
392 fn write(mut writer: impl Writer, src: &Self::Src) -> WriteResult<()> {
393 match src {
394 VersionedMessage::Legacy(message) => {
395 <LegacyMessage as SchemaWrite<C>>::write(writer, message)
396 }
397 VersionedMessage::V0(message) => {
398 <u8 as SchemaWrite<C>>::write(&mut writer, &MESSAGE_VERSION_PREFIX)?;
399 <v0::Message as SchemaWrite<C>>::write(writer, message)
400 }
401 VersionedMessage::V1(message) => {
402 <u8 as SchemaWrite<C>>::write(writer.by_ref(), &crate::v1::V1_PREFIX)?;
403 <v1::Message as SchemaWrite<C>>::write(writer, message)
404 }
405 }
406 }
407}
408
409#[cfg(feature = "wincode")]
410unsafe impl<'de, C: Config> SchemaReadContext<'de, C, u8> for VersionedMessage {
411 type Dst = Self;
412
413 fn read_with_context(
414 discriminant: u8,
415 reader: impl Reader<'de>,
416 dst: &mut MaybeUninit<Self::Dst>,
417 ) -> ReadResult<()> {
418 if discriminant & MESSAGE_VERSION_PREFIX != 0 {
423 use wincode::error::invalid_tag_encoding;
424
425 let version = discriminant & !MESSAGE_VERSION_PREFIX;
426 return match version {
427 0 => {
428 let msg = <v0::Message as SchemaRead<C>>::get(reader)?;
429 dst.write(VersionedMessage::V0(msg));
430 Ok(())
431 }
432 1 => {
433 let message = <v1::Message as SchemaRead<C>>::get(reader)?;
434 dst.write(VersionedMessage::V1(message));
435
436 Ok(())
437 }
438 _ => Err(invalid_tag_encoding(version as usize)),
439 };
440 };
441 let legacy =
442 <LegacyMessage as SchemaReadContext<C, _>>::get_with_context(discriminant, reader)?;
443 dst.write(VersionedMessage::Legacy(legacy));
444
445 Ok(())
446 }
447}
448#[cfg(feature = "wincode")]
449unsafe impl<'de, C: Config> SchemaRead<'de, C> for VersionedMessage {
450 type Dst = Self;
451
452 #[inline]
453 fn read(mut reader: impl Reader<'de>, dst: &mut MaybeUninit<Self::Dst>) -> ReadResult<()> {
454 let discriminant = reader.take_byte()?;
455 <VersionedMessage as SchemaReadContext<C, _>>::read_with_context(discriminant, reader, dst)
456 }
457}
458
459#[cfg(test)]
460mod tests {
461 use {
462 super::*,
463 crate::{
464 v0::MessageAddressTableLookup,
465 v1::{MAX_HEAP_SIZE, MIN_HEAP_SIZE, V1_PREFIX},
466 },
467 alloc::vec,
468 proptest::{
469 collection::vec,
470 option::of,
471 prelude::{any, Just},
472 prop_compose, proptest,
473 },
474 solana_instruction::{AccountMeta, Instruction},
475 };
476
477 #[derive(Clone, Debug)]
478 struct TestMessageData {
479 required_signatures: u8,
480 lifetime: [u8; 32],
481 accounts: Vec<[u8; 32]>,
482 priority_fee: Option<u64>,
483 compute_unit_limit: Option<u32>,
484 loaded_accounts_data_size_limit: Option<u32>,
485 heap_size: Option<u32>,
486 program_id_index: u8,
487 instr_accounts: Vec<u8>,
488 data: Vec<u8>,
489 }
490
491 #[test]
492 fn test_legacy_message_serialization() {
493 let program_id0 = Address::new_unique();
494 let program_id1 = Address::new_unique();
495 let id0 = Address::new_unique();
496 let id1 = Address::new_unique();
497 let id2 = Address::new_unique();
498 let id3 = Address::new_unique();
499 let instructions = vec![
500 Instruction::new_with_bincode(program_id0, &0, vec![AccountMeta::new(id0, false)]),
501 Instruction::new_with_bincode(program_id0, &0, vec![AccountMeta::new(id1, true)]),
502 Instruction::new_with_bincode(
503 program_id1,
504 &0,
505 vec![AccountMeta::new_readonly(id2, false)],
506 ),
507 Instruction::new_with_bincode(
508 program_id1,
509 &0,
510 vec![AccountMeta::new_readonly(id3, true)],
511 ),
512 ];
513
514 let mut message = LegacyMessage::new(&instructions, Some(&id1));
515 message.recent_blockhash = Hash::new_unique();
516 let wrapped_message = VersionedMessage::Legacy(message.clone());
517
518 {
520 let bytes = bincode::serialize(&message).unwrap();
521 assert_eq!(bytes, bincode::serialize(&wrapped_message).unwrap());
522
523 let message_from_bytes: LegacyMessage = bincode::deserialize(&bytes).unwrap();
524 let wrapped_message_from_bytes: VersionedMessage =
525 bincode::deserialize(&bytes).unwrap();
526
527 assert_eq!(message, message_from_bytes);
528 assert_eq!(wrapped_message, wrapped_message_from_bytes);
529 }
530
531 {
533 let string = serde_json::to_string(&message).unwrap();
534 let message_from_string: LegacyMessage = serde_json::from_str(&string).unwrap();
535 assert_eq!(message, message_from_string);
536 }
537 }
538
539 #[test]
540 fn test_versioned_message_serialization() {
541 let message = VersionedMessage::V0(v0::Message {
542 header: MessageHeader {
543 num_required_signatures: 1,
544 num_readonly_signed_accounts: 0,
545 num_readonly_unsigned_accounts: 0,
546 },
547 recent_blockhash: Hash::new_unique(),
548 account_keys: vec![Address::new_unique()],
549 address_table_lookups: vec![
550 MessageAddressTableLookup {
551 account_key: Address::new_unique(),
552 writable_indexes: vec![1],
553 readonly_indexes: vec![0],
554 },
555 MessageAddressTableLookup {
556 account_key: Address::new_unique(),
557 writable_indexes: vec![0],
558 readonly_indexes: vec![1],
559 },
560 ],
561 instructions: vec![CompiledInstruction {
562 program_id_index: 1,
563 accounts: vec![0, 2, 3, 4],
564 data: vec![],
565 }],
566 });
567
568 let bytes = bincode::serialize(&message).unwrap();
569 let message_from_bytes: VersionedMessage = bincode::deserialize(&bytes).unwrap();
570 assert_eq!(message, message_from_bytes);
571
572 let string = serde_json::to_string(&message).unwrap();
573 let message_from_string: VersionedMessage = serde_json::from_str(&string).unwrap();
574 assert_eq!(message, message_from_string);
575 }
576
577 prop_compose! {
578 fn generate_message_data()
579 (
580 accounts in vec(any::<[u8; 32]>(), 12..=64),
583 lifetime in any::<[u8; 32]>(),
584 priority_fee in of(any::<u64>()),
585 compute_unit_limit in of(0..=1_400_000u32),
586 loaded_accounts_data_size_limit in of(0..=20_480u32),
587 heap_size in of(MIN_HEAP_SIZE.saturating_div(1024)..=MAX_HEAP_SIZE.saturating_div(1024)),
590 required_signatures in 1..=12u8,
591 )
592 (
593 program_id_index in 1u8..accounts.len() as u8,
595 instr_accounts in vec(
597 0u8..accounts.len() as u8,
598 (required_signatures as usize)..=accounts.len(),
599 ),
600 data in vec(any::<u8>(), 0..=2048),
603 accounts in Just(accounts),
604 lifetime in Just(lifetime),
605 priority_fee in Just(priority_fee),
606 compute_unit_limit in Just(compute_unit_limit),
607 loaded_accounts_data_size_limit in Just(loaded_accounts_data_size_limit),
608 heap_size in Just(heap_size.map(|size| size.saturating_mul(1024))),
609 required_signatures in Just(required_signatures),
610 ) -> TestMessageData
611 {
612 TestMessageData {
613 required_signatures,
614 lifetime,
615 accounts,
616 priority_fee,
617 compute_unit_limit,
618 loaded_accounts_data_size_limit,
619 heap_size,
620 program_id_index,
621 instr_accounts,
622 data,
623 }
624 }
625 }
626
627 proptest! {
628 #[test]
629 fn test_v1_message_raw_bytes_roundtrip(test_data in generate_message_data()) {
630 let accounts: Vec<Address> = test_data.accounts.into_iter()
631 .map(Address::new_from_array).collect();
632 let lifetime = Hash::new_from_array(test_data.lifetime);
633
634 let mut builder = v1::MessageBuilder::new()
635 .required_signatures(test_data.required_signatures)
636 .lifetime_specifier(lifetime)
637 .accounts(accounts)
638 .instruction(CompiledInstruction {
639 program_id_index: test_data.program_id_index,
640 accounts: test_data.instr_accounts,
641 data: test_data.data,
642 });
643
644 if let Some(priority_fee) = test_data.priority_fee {
646 builder = builder.priority_fee(priority_fee);
647 }
648 if let Some(compute_unit_limit) = test_data.compute_unit_limit {
649 builder = builder.compute_unit_limit(compute_unit_limit);
650 }
651 if let Some(loaded_accounts_data_size_limit) = test_data.loaded_accounts_data_size_limit {
652 builder = builder.loaded_accounts_data_size_limit(loaded_accounts_data_size_limit);
653 }
654 if let Some(heap_size) = test_data.heap_size {
655 builder = builder.heap_size(heap_size);
656 }
657
658 let message = builder.build().unwrap();
659
660 let bytes = wincode::serialize(&message).unwrap();
662 let parsed = v1::deserialize(&bytes).unwrap();
664
665 assert_eq!(message, parsed);
667 assert_eq!(message, wincode::deserialize(&bytes).unwrap());
668
669 let versioned = VersionedMessage::V1(message);
671 let serialized = versioned.serialize();
672
673 assert!(!serialized.is_empty());
678 assert_eq!(serialized[0], V1_PREFIX);
679 assert_eq!(&serialized[1..], bytes.as_slice());
680 }
681 }
682
683 #[test]
684 fn test_v1_versioned_message_json_roundtrip() {
685 let msg = v1::MessageBuilder::new()
686 .required_signatures(1)
687 .lifetime_specifier(Hash::new_unique())
688 .accounts(vec![Address::new_unique(), Address::new_unique()])
689 .priority_fee(1000)
690 .compute_unit_limit(200_000)
691 .instruction(CompiledInstruction {
692 program_id_index: 1,
693 accounts: vec![0],
694 data: vec![1, 2, 3, 4],
695 })
696 .build()
697 .unwrap();
698
699 let vm = VersionedMessage::V1(msg);
700 let s = serde_json::to_string(&vm).unwrap();
701 let back: VersionedMessage = serde_json::from_str(&s).unwrap();
702 assert_eq!(vm, back);
703 }
704
705 #[cfg(feature = "wincode")]
706 #[test]
707 fn test_v1_wincode_roundtrip() {
708 let test_messages = [
709 v1::MessageBuilder::new()
711 .required_signatures(1)
712 .lifetime_specifier(Hash::new_unique())
713 .accounts(vec![Address::new_unique(), Address::new_unique()])
714 .instruction(CompiledInstruction {
715 program_id_index: 1,
716 accounts: vec![0],
717 data: vec![],
718 })
719 .build()
720 .unwrap(),
721 v1::MessageBuilder::new()
723 .required_signatures(1)
724 .lifetime_specifier(Hash::new_unique())
725 .accounts(vec![Address::new_unique(), Address::new_unique()])
726 .priority_fee(1000)
727 .compute_unit_limit(200_000)
728 .instruction(CompiledInstruction {
729 program_id_index: 1,
730 accounts: vec![0],
731 data: vec![1, 2, 3, 4],
732 })
733 .build()
734 .unwrap(),
735 v1::MessageBuilder::new()
737 .required_signatures(2)
738 .lifetime_specifier(Hash::new_unique())
739 .accounts(vec![
740 Address::new_unique(),
741 Address::new_unique(),
742 Address::new_unique(),
743 ])
744 .heap_size(65536)
745 .instructions(vec![
746 CompiledInstruction {
747 program_id_index: 2,
748 accounts: vec![0, 1],
749 data: vec![0xAA, 0xBB],
750 },
751 CompiledInstruction {
752 program_id_index: 2,
753 accounts: vec![1],
754 data: vec![0xCC],
755 },
756 ])
757 .build()
758 .unwrap(),
759 ];
760
761 for message in test_messages {
762 let versioned = VersionedMessage::V1(message.clone());
763
764 let bytes = wincode::serialize(&versioned).expect("Wincode serialize failed");
766 let deserialized: VersionedMessage =
767 wincode::deserialize(&bytes).expect("Wincode deserialize failed");
768
769 match deserialized {
770 VersionedMessage::V1(parsed) => assert_eq!(parsed, message),
771 _ => panic!("Expected V1 message"),
772 }
773 }
774 }
775}