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