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