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::{v0::MessageAddressTableLookup, v1::V1_PREFIX},
460 proptest::{
461 collection::vec,
462 option::of,
463 prelude::{any, Just},
464 prop_compose, proptest,
465 strategy::Strategy,
466 },
467 solana_instruction::{AccountMeta, Instruction},
468 };
469
470 #[derive(Clone, Debug)]
471 struct TestMessageData {
472 required_signatures: u8,
473 lifetime: [u8; 32],
474 accounts: Vec<[u8; 32]>,
475 priority_fee: Option<u64>,
476 compute_unit_limit: Option<u32>,
477 loaded_accounts_data_size_limit: Option<u32>,
478 heap_size: Option<u32>,
479 program_id_index: u8,
480 instr_accounts: Vec<u8>,
481 data: Vec<u8>,
482 }
483
484 #[test]
485 fn test_legacy_message_serialization() {
486 let program_id0 = Address::new_unique();
487 let program_id1 = Address::new_unique();
488 let id0 = Address::new_unique();
489 let id1 = Address::new_unique();
490 let id2 = Address::new_unique();
491 let id3 = Address::new_unique();
492 let instructions = vec![
493 Instruction::new_with_bincode(program_id0, &0, vec![AccountMeta::new(id0, false)]),
494 Instruction::new_with_bincode(program_id0, &0, vec![AccountMeta::new(id1, true)]),
495 Instruction::new_with_bincode(
496 program_id1,
497 &0,
498 vec![AccountMeta::new_readonly(id2, false)],
499 ),
500 Instruction::new_with_bincode(
501 program_id1,
502 &0,
503 vec![AccountMeta::new_readonly(id3, true)],
504 ),
505 ];
506
507 let mut message = LegacyMessage::new(&instructions, Some(&id1));
508 message.recent_blockhash = Hash::new_unique();
509 let wrapped_message = VersionedMessage::Legacy(message.clone());
510
511 {
513 let bytes = bincode::serialize(&message).unwrap();
514 assert_eq!(bytes, bincode::serialize(&wrapped_message).unwrap());
515
516 let message_from_bytes: LegacyMessage = bincode::deserialize(&bytes).unwrap();
517 let wrapped_message_from_bytes: VersionedMessage =
518 bincode::deserialize(&bytes).unwrap();
519
520 assert_eq!(message, message_from_bytes);
521 assert_eq!(wrapped_message, wrapped_message_from_bytes);
522 }
523
524 {
526 let string = serde_json::to_string(&message).unwrap();
527 let message_from_string: LegacyMessage = serde_json::from_str(&string).unwrap();
528 assert_eq!(message, message_from_string);
529 }
530 }
531
532 #[test]
533 fn test_versioned_message_serialization() {
534 let message = VersionedMessage::V0(v0::Message {
535 header: MessageHeader {
536 num_required_signatures: 1,
537 num_readonly_signed_accounts: 0,
538 num_readonly_unsigned_accounts: 0,
539 },
540 recent_blockhash: Hash::new_unique(),
541 account_keys: vec![Address::new_unique()],
542 address_table_lookups: vec![
543 MessageAddressTableLookup {
544 account_key: Address::new_unique(),
545 writable_indexes: vec![1],
546 readonly_indexes: vec![0],
547 },
548 MessageAddressTableLookup {
549 account_key: Address::new_unique(),
550 writable_indexes: vec![0],
551 readonly_indexes: vec![1],
552 },
553 ],
554 instructions: vec![CompiledInstruction {
555 program_id_index: 1,
556 accounts: vec![0, 2, 3, 4],
557 data: vec![],
558 }],
559 });
560
561 let bytes = bincode::serialize(&message).unwrap();
562 let message_from_bytes: VersionedMessage = bincode::deserialize(&bytes).unwrap();
563 assert_eq!(message, message_from_bytes);
564
565 let string = serde_json::to_string(&message).unwrap();
566 let message_from_string: VersionedMessage = serde_json::from_str(&string).unwrap();
567 assert_eq!(message, message_from_string);
568 }
569
570 prop_compose! {
571 fn generate_message_data()
572 (
573 accounts in vec(any::<[u8; 32]>(), 12..=64),
576 lifetime in any::<[u8; 32]>(),
577 priority_fee in of(any::<u64>()),
578 compute_unit_limit in of(0..=1_400_000u32),
579 loaded_accounts_data_size_limit in of(0..=20_480u32),
580 heap_size in of((0..=32u32).prop_map(|n| n.saturating_mul(1024))),
581 required_signatures in 1..=12u8,
582 )
583 (
584 program_id_index in 1u8..accounts.len() as u8,
586 instr_accounts in vec(
588 0u8..accounts.len() as u8,
589 (required_signatures as usize)..=accounts.len(),
590 ),
591 data in vec(any::<u8>(), 0..=2048),
594 accounts in Just(accounts),
595 lifetime in Just(lifetime),
596 priority_fee in Just(priority_fee),
597 compute_unit_limit in Just(compute_unit_limit),
598 loaded_accounts_data_size_limit in Just(loaded_accounts_data_size_limit),
599 heap_size in Just(heap_size),
600 required_signatures in Just(required_signatures),
601 ) -> TestMessageData
602 {
603 TestMessageData {
604 required_signatures,
605 lifetime,
606 accounts,
607 priority_fee,
608 compute_unit_limit,
609 loaded_accounts_data_size_limit,
610 heap_size,
611 program_id_index,
612 instr_accounts,
613 data,
614 }
615 }
616 }
617
618 proptest! {
619 #[test]
620 fn test_v1_message_raw_bytes_roundtrip(test_data in generate_message_data()) {
621 let accounts: Vec<Address> = test_data.accounts.into_iter()
622 .map(Address::new_from_array).collect();
623 let lifetime = Hash::new_from_array(test_data.lifetime);
624
625 let mut builder = v1::MessageBuilder::new()
626 .required_signatures(test_data.required_signatures)
627 .lifetime_specifier(lifetime)
628 .accounts(accounts)
629 .instruction(CompiledInstruction {
630 program_id_index: test_data.program_id_index,
631 accounts: test_data.instr_accounts,
632 data: test_data.data,
633 });
634
635 if let Some(priority_fee) = test_data.priority_fee {
637 builder = builder.priority_fee(priority_fee);
638 }
639 if let Some(compute_unit_limit) = test_data.compute_unit_limit {
640 builder = builder.compute_unit_limit(compute_unit_limit);
641 }
642 if let Some(loaded_accounts_data_size_limit) = test_data.loaded_accounts_data_size_limit {
643 builder = builder.loaded_accounts_data_size_limit(loaded_accounts_data_size_limit);
644 }
645 if let Some(heap_size) = test_data.heap_size {
646 builder = builder.heap_size(heap_size);
647 }
648
649 let message = builder.build().unwrap();
650
651 let bytes = v1::serialize(&message);
653 let parsed = v1::deserialize(&bytes).unwrap();
655
656 assert_eq!(message, parsed);
658 assert_eq!(message, wincode::deserialize(&bytes).unwrap());
659
660 let versioned = VersionedMessage::V1(message);
662 let serialized = versioned.serialize();
663
664 assert!(!serialized.is_empty());
669 assert_eq!(serialized[0], V1_PREFIX);
670 assert_eq!(&serialized[1..], bytes.as_slice());
671 }
672 }
673
674 #[test]
675 fn test_v1_versioned_message_json_roundtrip() {
676 let msg = v1::MessageBuilder::new()
677 .required_signatures(1)
678 .lifetime_specifier(Hash::new_unique())
679 .accounts(vec![Address::new_unique(), Address::new_unique()])
680 .priority_fee(1000)
681 .compute_unit_limit(200_000)
682 .instruction(CompiledInstruction {
683 program_id_index: 1,
684 accounts: vec![0],
685 data: vec![1, 2, 3, 4],
686 })
687 .build()
688 .unwrap();
689
690 let vm = VersionedMessage::V1(msg);
691 let s = serde_json::to_string(&vm).unwrap();
692 let back: VersionedMessage = serde_json::from_str(&s).unwrap();
693 assert_eq!(vm, back);
694 }
695
696 #[cfg(feature = "wincode")]
697 #[test]
698 fn test_v1_wincode_roundtrip() {
699 let test_messages = [
700 v1::MessageBuilder::new()
702 .required_signatures(1)
703 .lifetime_specifier(Hash::new_unique())
704 .accounts(vec![Address::new_unique(), Address::new_unique()])
705 .instruction(CompiledInstruction {
706 program_id_index: 1,
707 accounts: vec![0],
708 data: vec![],
709 })
710 .build()
711 .unwrap(),
712 v1::MessageBuilder::new()
714 .required_signatures(1)
715 .lifetime_specifier(Hash::new_unique())
716 .accounts(vec![Address::new_unique(), Address::new_unique()])
717 .priority_fee(1000)
718 .compute_unit_limit(200_000)
719 .instruction(CompiledInstruction {
720 program_id_index: 1,
721 accounts: vec![0],
722 data: vec![1, 2, 3, 4],
723 })
724 .build()
725 .unwrap(),
726 v1::MessageBuilder::new()
728 .required_signatures(2)
729 .lifetime_specifier(Hash::new_unique())
730 .accounts(vec![
731 Address::new_unique(),
732 Address::new_unique(),
733 Address::new_unique(),
734 ])
735 .heap_size(65536)
736 .instructions(vec![
737 CompiledInstruction {
738 program_id_index: 2,
739 accounts: vec![0, 1],
740 data: vec![0xAA, 0xBB],
741 },
742 CompiledInstruction {
743 program_id_index: 2,
744 accounts: vec![1],
745 data: vec![0xCC],
746 },
747 ])
748 .build()
749 .unwrap(),
750 ];
751
752 for message in test_messages {
753 let versioned = VersionedMessage::V1(message.clone());
754
755 let bytes = wincode::serialize(&versioned).expect("Wincode serialize failed");
757 let deserialized: VersionedMessage =
758 wincode::deserialize(&bytes).expect("Wincode deserialize failed");
759
760 match deserialized {
761 VersionedMessage::V1(parsed) => assert_eq!(parsed, message),
762 _ => panic!("Expected V1 message"),
763 }
764 }
765 }
766}