1use {
4 crate::Transaction,
5 solana_message::{inline_nonce::is_advance_nonce_instruction_data, VersionedMessage},
6 solana_sanitize::SanitizeError,
7 solana_sdk_ids::system_program,
8 solana_signature::Signature,
9 std::cmp::Ordering,
10};
11#[cfg(feature = "wincode")]
12use {
13 core::mem::MaybeUninit,
14 solana_message::{v1::SIGNATURE_SIZE, MESSAGE_VERSION_PREFIX},
15 solana_short_vec::ShortU16,
16 solana_signer::{signers::Signers, SignerError},
17 wincode::{
18 config::Config,
19 containers, context,
20 io::{Reader, Writer},
21 ReadError, ReadResult, SchemaRead, SchemaReadContext, SchemaWrite, UninitBuilder,
22 WriteResult,
23 },
24};
25#[cfg(feature = "serde")]
26use {
27 serde_derive::{Deserialize, Serialize},
28 solana_short_vec as short_vec,
29};
30
31pub mod sanitized;
32
33#[cfg_attr(
35 feature = "serde",
36 derive(Deserialize, Serialize),
37 serde(rename_all = "camelCase")
38)]
39#[derive(Clone, Debug, PartialEq, Eq)]
40pub enum Legacy {
41 Legacy,
42}
43
44#[cfg_attr(
45 feature = "serde",
46 derive(Deserialize, Serialize),
47 serde(rename_all = "camelCase", untagged)
48)]
49#[derive(Clone, Debug, PartialEq, Eq)]
50pub enum TransactionVersion {
51 Legacy(Legacy),
52 Number(u8),
53}
54
55impl TransactionVersion {
56 pub const LEGACY: Self = Self::Legacy(Legacy::Legacy);
57}
58
59#[cfg_attr(feature = "frozen-abi", derive(solana_frozen_abi_macro::AbiExample))]
62#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
63#[cfg_attr(feature = "wincode", derive(UninitBuilder))]
64#[derive(Debug, PartialEq, Default, Eq, Clone)]
65pub struct VersionedTransaction {
66 #[cfg_attr(feature = "serde", serde(with = "short_vec"))]
68 #[cfg_attr(
69 feature = "wincode",
70 wincode(with = "containers::Vec<Signature, ShortU16>")
71 )]
72 pub signatures: Vec<Signature>,
73 pub message: VersionedMessage,
75}
76
77impl From<Transaction> for VersionedTransaction {
78 fn from(transaction: Transaction) -> Self {
79 Self {
80 signatures: transaction.signatures,
81 message: VersionedMessage::Legacy(transaction.message),
82 }
83 }
84}
85
86impl VersionedTransaction {
87 #[cfg(feature = "wincode")]
90 pub fn try_new<T: Signers + ?Sized>(
91 message: VersionedMessage,
92 keypairs: &T,
93 ) -> std::result::Result<Self, SignerError> {
94 let static_account_keys = message.static_account_keys();
95 if static_account_keys.len() < message.header().num_required_signatures as usize {
96 return Err(SignerError::InvalidInput("invalid message".to_string()));
97 }
98
99 let signer_keys = keypairs.try_pubkeys()?;
100 let expected_signer_keys =
101 &static_account_keys[0..message.header().num_required_signatures as usize];
102
103 match signer_keys.len().cmp(&expected_signer_keys.len()) {
104 Ordering::Greater => Err(SignerError::TooManySigners),
105 Ordering::Less => Err(SignerError::NotEnoughSigners),
106 Ordering::Equal => Ok(()),
107 }?;
108
109 let message_data = message.serialize();
110 let signature_indexes: Vec<usize> = expected_signer_keys
111 .iter()
112 .map(|signer_key| {
113 signer_keys
114 .iter()
115 .position(|key| key == signer_key)
116 .ok_or(SignerError::KeypairPubkeyMismatch)
117 })
118 .collect::<std::result::Result<_, SignerError>>()?;
119
120 let unordered_signatures = keypairs.try_sign_message(&message_data)?;
121 let signatures: Vec<Signature> = signature_indexes
122 .into_iter()
123 .map(|index| {
124 unordered_signatures
125 .get(index)
126 .copied()
127 .ok_or_else(|| SignerError::InvalidInput("invalid keypairs".to_string()))
128 })
129 .collect::<std::result::Result<_, SignerError>>()?;
130
131 Ok(Self {
132 signatures,
133 message,
134 })
135 }
136
137 pub fn sanitize(&self) -> std::result::Result<(), SanitizeError> {
138 self.message.sanitize()?;
139 self.sanitize_signatures()?;
140 Ok(())
141 }
142
143 pub(crate) fn sanitize_signatures(&self) -> std::result::Result<(), SanitizeError> {
144 Self::sanitize_signatures_inner(
145 usize::from(self.message.header().num_required_signatures),
146 self.message.static_account_keys().len(),
147 self.signatures.len(),
148 )
149 }
150
151 pub(crate) fn sanitize_signatures_inner(
152 num_required_signatures: usize,
153 num_static_account_keys: usize,
154 num_signatures: usize,
155 ) -> std::result::Result<(), SanitizeError> {
156 match num_required_signatures.cmp(&num_signatures) {
157 Ordering::Greater => Err(SanitizeError::IndexOutOfBounds),
158 Ordering::Less => Err(SanitizeError::InvalidValue),
159 Ordering::Equal => Ok(()),
160 }?;
161
162 if num_signatures > num_static_account_keys {
165 return Err(SanitizeError::IndexOutOfBounds);
166 }
167
168 Ok(())
169 }
170
171 pub fn version(&self) -> TransactionVersion {
173 match self.message {
174 VersionedMessage::Legacy(_) => TransactionVersion::LEGACY,
175 VersionedMessage::V0(_) => TransactionVersion::Number(0),
176 VersionedMessage::V1(_) => TransactionVersion::Number(1),
177 }
178 }
179
180 pub fn into_legacy_transaction(self) -> Option<Transaction> {
182 match self.message {
183 VersionedMessage::Legacy(message) => Some(Transaction {
184 signatures: self.signatures,
185 message,
186 }),
187 _ => None,
188 }
189 }
190
191 #[cfg(feature = "verify")]
192 pub fn verify_and_hash_message(
194 &self,
195 ) -> solana_transaction_error::TransactionResult<solana_hash::Hash> {
196 let message_bytes = self.message.serialize();
197 if !self
198 ._verify_with_results(&message_bytes)
199 .iter()
200 .all(|verify_result| *verify_result)
201 {
202 Err(solana_transaction_error::TransactionError::SignatureFailure)
203 } else {
204 Ok(VersionedMessage::hash_raw_message(&message_bytes))
205 }
206 }
207
208 #[cfg(feature = "verify")]
209 pub fn verify_with_results(&self) -> Vec<bool> {
211 let message_bytes = self.message.serialize();
212 self._verify_with_results(&message_bytes)
213 }
214
215 #[cfg(feature = "verify")]
216 fn _verify_with_results(&self, message_bytes: &[u8]) -> Vec<bool> {
217 self.signatures
218 .iter()
219 .zip(self.message.static_account_keys().iter())
220 .map(|(signature, pubkey)| signature.verify(pubkey.as_ref(), message_bytes))
221 .collect()
222 }
223
224 pub fn uses_durable_nonce(&self) -> bool {
226 let message = &self.message;
227 message
228 .instructions()
229 .get(crate::NONCED_TX_MARKER_IX_INDEX as usize)
230 .filter(|instruction| {
231 matches!(
233 message.static_account_keys().get(instruction.program_id_index as usize),
234 Some(program_id) if system_program::check_id(program_id)
235 ) && is_advance_nonce_instruction_data(&instruction.data)
236 })
237 .is_some()
238 }
239}
240
241#[cfg(feature = "wincode")]
242unsafe impl<C: Config> SchemaWrite<C> for VersionedTransaction {
243 type Src = Self;
244
245 #[allow(clippy::arithmetic_side_effects)]
246 #[inline]
247 fn size_of(src: &Self::Src) -> WriteResult<usize> {
248 match src.message {
249 VersionedMessage::Legacy(_) | VersionedMessage::V0(_) => {
250 Ok(
251 <containers::Vec<Signature, ShortU16> as SchemaWrite<C>>::size_of(
252 &src.signatures,
253 )? + <VersionedMessage as SchemaWrite<C>>::size_of(&src.message)?,
254 )
255 }
256 VersionedMessage::V1(_) => Ok(
257 <VersionedMessage as SchemaWrite<C>>::size_of(&src.message)?
260 + src.signatures.len() * SIGNATURE_SIZE,
261 ),
262 }
263 }
264
265 #[inline]
266 fn write(mut writer: impl Writer, src: &Self::Src) -> WriteResult<()> {
267 match src.message {
268 VersionedMessage::Legacy(_) | VersionedMessage::V0(_) => {
269 <containers::Vec<Signature, ShortU16> as SchemaWrite<C>>::write(
271 &mut writer,
272 &src.signatures,
273 )?;
274 <VersionedMessage as SchemaWrite<C>>::write(writer, &src.message)
275 }
276 VersionedMessage::V1(_) => {
277 <VersionedMessage as SchemaWrite<C>>::write(&mut writer, &src.message)?;
278 unsafe {
279 writer
280 .write_slice_t(&src.signatures)
281 .map_err(wincode::WriteError::Io)
282 }
283 }
284 }
285 }
286}
287
288#[cfg(feature = "wincode")]
289unsafe impl<'de, C: Config> SchemaRead<'de, C> for VersionedTransaction {
290 type Dst = Self;
291
292 #[inline]
293 fn read(mut reader: impl Reader<'de>, dst: &mut MaybeUninit<Self::Dst>) -> ReadResult<()> {
294 use solana_message::v1::V1_PREFIX;
304 let discriminator = reader.take_byte()?;
305
306 if discriminator & MESSAGE_VERSION_PREFIX == 0 {
307 let signatures = <Vec<Signature> as SchemaReadContext<C, _>>::get_with_context(
310 context::Len(discriminator as usize),
312 reader.by_ref(),
313 )?;
314 let message = <VersionedMessage as SchemaRead<C>>::get(reader)?;
315
316 if !matches!(
318 message,
319 VersionedMessage::Legacy(_) | VersionedMessage::V0(_)
320 ) {
321 return Err(ReadError::Custom("invalid message version"));
322 }
323
324 dst.write(Self {
325 signatures,
326 message,
327 });
328 } else if discriminator == V1_PREFIX {
329 let message = <VersionedMessage as SchemaReadContext<C, _>>::get_with_context(
332 discriminator,
335 reader.by_ref(),
336 )?;
337
338 if !matches!(message, VersionedMessage::V1(_)) {
340 return Err(ReadError::Custom("invalid message version"));
341 }
342
343 let num_signatures = message.header().num_required_signatures as usize;
344 let signatures = <Vec<Signature> as SchemaReadContext<C, _>>::get_with_context(
345 context::Len(num_signatures),
346 reader,
347 )?;
348
349 dst.write(Self {
350 signatures,
351 message,
352 });
353 } else {
354 return Err(ReadError::Custom("invalid transaction discriminator"));
355 }
356
357 Ok(())
358 }
359}
360
361#[cfg(test)]
362mod tests {
363 use {
364 super::*,
365 solana_address::{Address, ADDRESS_BYTES},
366 solana_hash::Hash,
367 solana_instruction::{AccountMeta, Instruction},
368 solana_keypair::Keypair,
369 solana_message::{
370 compiled_instruction::CompiledInstruction,
371 v0::Message as MessageV0,
372 v1::{
373 InstructionHeader, Message, TransactionConfig, FIXED_HEADER_SIZE,
374 MAX_TRANSACTION_SIZE, SIGNATURE_SIZE,
375 },
376 Message as LegacyMessage, MessageHeader,
377 },
378 solana_pubkey::Pubkey,
379 solana_signer::Signer,
380 solana_system_interface::instruction as system_instruction,
381 test_case::test_case,
382 };
383
384 #[test]
385 fn test_try_new() {
386 let keypair0 = Keypair::new();
387 let keypair1 = Keypair::new();
388 let keypair2 = Keypair::new();
389
390 let message = VersionedMessage::Legacy(LegacyMessage::new(
391 &[Instruction::new_with_bytes(
392 Pubkey::new_unique(),
393 &[],
394 vec![
395 AccountMeta::new_readonly(keypair1.pubkey(), true),
396 AccountMeta::new_readonly(keypair2.pubkey(), false),
397 ],
398 )],
399 Some(&keypair0.pubkey()),
400 ));
401
402 assert_eq!(
403 VersionedTransaction::try_new(message.clone(), &[&keypair0]),
404 Err(SignerError::NotEnoughSigners)
405 );
406
407 assert_eq!(
408 VersionedTransaction::try_new(message.clone(), &[&keypair0, &keypair0]),
409 Err(SignerError::KeypairPubkeyMismatch)
410 );
411
412 assert_eq!(
413 VersionedTransaction::try_new(message.clone(), &[&keypair1, &keypair2]),
414 Err(SignerError::KeypairPubkeyMismatch)
415 );
416
417 match VersionedTransaction::try_new(message.clone(), &[&keypair0, &keypair1]) {
418 Ok(tx) => assert_eq!(tx.verify_with_results(), vec![true; 2]),
419 Err(err) => assert_eq!(Some(err), None),
420 }
421
422 match VersionedTransaction::try_new(message, &[&keypair1, &keypair0]) {
423 Ok(tx) => assert_eq!(tx.verify_with_results(), vec![true; 2]),
424 Err(err) => assert_eq!(Some(err), None),
425 }
426 }
427
428 fn nonced_transfer_tx() -> (Pubkey, Pubkey, VersionedTransaction) {
429 let from_keypair = Keypair::new();
430 let from_pubkey = from_keypair.pubkey();
431 let nonce_keypair = Keypair::new();
432 let nonce_pubkey = nonce_keypair.pubkey();
433 let instructions = [
434 system_instruction::advance_nonce_account(&nonce_pubkey, &nonce_pubkey),
435 system_instruction::transfer(&from_pubkey, &nonce_pubkey, 42),
436 ];
437 let message = LegacyMessage::new(&instructions, Some(&nonce_pubkey));
438 let tx = Transaction::new(&[&from_keypair, &nonce_keypair], message, Hash::default());
439 (from_pubkey, nonce_pubkey, tx.into())
440 }
441
442 #[test]
443 fn tx_uses_nonce_ok() {
444 let (_, _, tx) = nonced_transfer_tx();
445 assert!(tx.uses_durable_nonce());
446 }
447
448 #[test]
449 fn tx_uses_nonce_empty_ix_fail() {
450 let tx = VersionedTransaction {
451 message: VersionedMessage::V0(MessageV0::default()),
452 signatures: vec![],
453 };
454 assert!(!tx.uses_durable_nonce());
455 }
456
457 #[test]
458 fn tx_uses_nonce_bad_prog_id_idx_fail() {
459 let (_, _, mut tx) = nonced_transfer_tx();
460 match &mut tx.message {
461 VersionedMessage::Legacy(message) => {
462 message.instructions.get_mut(0).unwrap().program_id_index = 255u8;
463 }
464 _ => unreachable!(),
465 };
466 assert!(!tx.uses_durable_nonce());
467 }
468
469 #[test]
470 fn tx_uses_nonce_first_prog_id_not_nonce_fail() {
471 let from_keypair = Keypair::new();
472 let from_pubkey = from_keypair.pubkey();
473 let nonce_keypair = Keypair::new();
474 let nonce_pubkey = nonce_keypair.pubkey();
475 let instructions = [
476 system_instruction::transfer(&from_pubkey, &nonce_pubkey, 42),
477 system_instruction::advance_nonce_account(&nonce_pubkey, &nonce_pubkey),
478 ];
479 let message = LegacyMessage::new(&instructions, Some(&from_pubkey));
480 let tx = Transaction::new(&[&from_keypair, &nonce_keypair], message, Hash::default());
481 let tx = VersionedTransaction::from(tx);
482 assert!(!tx.uses_durable_nonce());
483 }
484
485 #[test]
486 fn tx_uses_nonce_wrong_first_nonce_ix_fail() {
487 let from_keypair = Keypair::new();
488 let from_pubkey = from_keypair.pubkey();
489 let nonce_keypair = Keypair::new();
490 let nonce_pubkey = nonce_keypair.pubkey();
491 let instructions = [
492 system_instruction::withdraw_nonce_account(
493 &nonce_pubkey,
494 &nonce_pubkey,
495 &from_pubkey,
496 42,
497 ),
498 system_instruction::transfer(&from_pubkey, &nonce_pubkey, 42),
499 ];
500 let message = LegacyMessage::new(&instructions, Some(&nonce_pubkey));
501 let tx = Transaction::new(&[&from_keypair, &nonce_keypair], message, Hash::default());
502 let tx = VersionedTransaction::from(tx);
503 assert!(!tx.uses_durable_nonce());
504 }
505
506 #[test]
507 fn test_sanitize_signatures_inner() {
508 assert_eq!(
509 VersionedTransaction::sanitize_signatures_inner(1, 1, 0),
510 Err(SanitizeError::IndexOutOfBounds)
511 );
512 assert_eq!(
513 VersionedTransaction::sanitize_signatures_inner(1, 1, 2),
514 Err(SanitizeError::InvalidValue)
515 );
516 assert_eq!(
517 VersionedTransaction::sanitize_signatures_inner(2, 1, 2),
518 Err(SanitizeError::IndexOutOfBounds)
519 );
520 assert_eq!(
521 VersionedTransaction::sanitize_signatures_inner(1, 1, 1),
522 Ok(())
523 );
524 }
525
526 #[test]
527 fn versioned_transaction_wincode_bincode_roundtrip() {
528 use {
529 super::*,
530 proptest::prelude::*,
531 solana_address::{Address, ADDRESS_BYTES},
532 solana_hash::{Hash, HASH_BYTES},
533 solana_message::{
534 compiled_instruction::CompiledInstruction,
535 v0::{self, MessageAddressTableLookup},
536 Message as LegacyMessage, MessageHeader,
537 },
538 solana_signature::SIGNATURE_BYTES,
539 };
540
541 #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
545 #[derive(Debug, PartialEq, Default, Eq, Clone)]
546 struct BincodeVersionedTransaction {
547 #[cfg_attr(feature = "serde", serde(with = "short_vec"))]
549 pub signatures: Vec<Signature>,
550 pub message: VersionedMessage,
552 }
553
554 fn strat_byte_vec(max_len: usize) -> impl Strategy<Value = Vec<u8>> {
555 proptest::collection::vec(any::<u8>(), 0..=max_len)
556 }
557
558 fn strat_signature() -> impl Strategy<Value = Signature> {
559 any::<[u8; SIGNATURE_BYTES]>().prop_map(Signature::from)
560 }
561
562 fn strat_address() -> impl Strategy<Value = Address> {
563 any::<[u8; ADDRESS_BYTES]>().prop_map(Address::new_from_array)
564 }
565
566 fn strat_hash() -> impl Strategy<Value = Hash> {
567 any::<[u8; HASH_BYTES]>().prop_map(Hash::new_from_array)
568 }
569
570 fn strat_message_header() -> impl Strategy<Value = MessageHeader> {
571 (0u8..128, any::<u8>(), any::<u8>()).prop_map(|(a, b, c)| MessageHeader {
572 num_required_signatures: a,
573 num_readonly_signed_accounts: b,
574 num_readonly_unsigned_accounts: c,
575 })
576 }
577
578 fn strat_compiled_instruction() -> impl Strategy<Value = CompiledInstruction> {
579 (any::<u8>(), strat_byte_vec(128), strat_byte_vec(128)).prop_map(
580 |(program_id_index, accounts, data)| {
581 CompiledInstruction::new_from_raw_parts(program_id_index, accounts, data)
582 },
583 )
584 }
585
586 fn strat_address_table_lookup() -> impl Strategy<Value = MessageAddressTableLookup> {
587 (strat_address(), strat_byte_vec(128), strat_byte_vec(128)).prop_map(
588 |(account_key, writable_indexes, readonly_indexes)| MessageAddressTableLookup {
589 account_key,
590 writable_indexes,
591 readonly_indexes,
592 },
593 )
594 }
595
596 fn strat_legacy_message() -> impl Strategy<Value = LegacyMessage> {
597 (
598 strat_message_header(),
599 proptest::collection::vec(strat_address(), 0..=8),
600 strat_hash(),
601 proptest::collection::vec(strat_compiled_instruction(), 0..=8),
602 )
603 .prop_map(|(header, account_keys, recent_blockhash, instructions)| {
604 LegacyMessage {
605 header,
606 account_keys,
607 recent_blockhash,
608 instructions,
609 }
610 })
611 }
612
613 fn strat_v0_message() -> impl Strategy<Value = v0::Message> {
614 (
615 strat_message_header(),
616 proptest::collection::vec(strat_address(), 0..=8),
617 strat_hash(),
618 proptest::collection::vec(strat_compiled_instruction(), 0..=4),
619 proptest::collection::vec(strat_address_table_lookup(), 0..=4),
620 )
621 .prop_map(
622 |(
623 header,
624 account_keys,
625 recent_blockhash,
626 instructions,
627 address_table_lookups,
628 )| {
629 v0::Message {
630 header,
631 account_keys,
632 recent_blockhash,
633 instructions,
634 address_table_lookups,
635 }
636 },
637 )
638 }
639
640 fn strat_versioned_message() -> impl Strategy<Value = VersionedMessage> {
641 prop_oneof![
642 strat_legacy_message().prop_map(VersionedMessage::Legacy),
643 strat_v0_message().prop_map(VersionedMessage::V0),
644 ]
645 }
646
647 fn strat_versioned_transaction(
648 ) -> impl Strategy<Value = (VersionedTransaction, BincodeVersionedTransaction)> {
649 (
650 proptest::collection::vec(strat_signature(), 0..=8),
651 strat_versioned_message(),
652 )
653 .prop_map(|(signatures, message)| {
654 (
655 VersionedTransaction {
656 message: message.clone(),
657 signatures: signatures.clone(),
658 },
659 BincodeVersionedTransaction {
660 message: message.clone(),
661 signatures: signatures.clone(),
662 },
663 )
664 })
665 }
666
667 proptest!(|(tx in strat_versioned_transaction())| {
668 let wincode_serialized = wincode::serialize(&tx.0).unwrap();
669 let bincode_serialized = bincode::serialize(&tx.1).unwrap();
670
671 assert_eq!(bincode_serialized, wincode_serialized);
672
673 let bincode_deserialized: BincodeVersionedTransaction = bincode::deserialize(&bincode_serialized).unwrap();
674 let wincode_deserialized: VersionedTransaction = wincode::deserialize(&wincode_serialized).unwrap();
675
676 assert_eq!(&bincode_deserialized.message, &wincode_deserialized.message);
677 assert_eq!(&bincode_deserialized.signatures, &wincode_deserialized.signatures);
678
679 assert_eq!(wincode_deserialized, tx.0);
680 });
681 }
682
683 #[test_case(0 ; "at max size")]
684 #[test_case(1 ; "over by one")]
685 #[allow(clippy::arithmetic_side_effects)]
686 fn v1_transaction_serialization(delta: usize) {
687 const NUM_SIGNATURES: usize = 1;
695 const NUM_ADDRESSES: usize = 2;
696 const NUM_INSTRUCTION_ACCOUNTS: usize = 1;
697
698 let overhead = 1 + (NUM_SIGNATURES * SIGNATURE_SIZE)
700 + FIXED_HEADER_SIZE
701 + (NUM_ADDRESSES * ADDRESS_BYTES)
702 + size_of::<InstructionHeader>()
703 + NUM_INSTRUCTION_ACCOUNTS;
704
705 let max_data_size = MAX_TRANSACTION_SIZE - overhead + delta;
708 let data = vec![0u8; max_data_size];
709
710 let message = Message {
711 header: MessageHeader {
712 num_required_signatures: NUM_SIGNATURES as u8,
713 num_readonly_signed_accounts: 0,
714 num_readonly_unsigned_accounts: 0,
715 },
716 config: TransactionConfig::default(),
717 account_keys: vec![Address::new_unique(), Address::new_unique()],
718 lifetime_specifier: Hash::new_unique(),
719 instructions: vec![CompiledInstruction {
720 program_id_index: 1,
721 accounts: vec![0],
722 data,
723 }],
724 };
725
726 let v1_tx = VersionedTransaction {
727 message: VersionedMessage::V1(message),
728 signatures: vec![Signature::default()],
729 };
730
731 let serialized = wincode::serialize(&v1_tx).unwrap();
732
733 match delta {
734 0 => assert_eq!(
735 serialized.len(),
736 MAX_TRANSACTION_SIZE,
737 "Transaction should be exactly at max size"
738 ),
739 d => assert_eq!(
740 serialized.len(),
741 MAX_TRANSACTION_SIZE + d,
742 "Transaction should be over by {d} byte(s)"
743 ),
744 }
745
746 let deserialized = wincode::deserialize(&serialized).unwrap();
747
748 assert_eq!(
749 v1_tx, deserialized,
750 "Deserialized payload should match original"
751 );
752 }
753
754 #[test]
755 fn test_v1_message_in_legacy_transaction() {
756 #[rustfmt::skip]
757 let malformed_input: &[u8] = &[
758 0x00, 0x81, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
766 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
768 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
769 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
770 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
771 0x00,
773 0x01,
775 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
777 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
778 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
779 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
780 ];
781
782 let result: Result<VersionedTransaction, _> = wincode::deserialize(malformed_input);
783
784 if let Err(wincode::ReadError::Custom(msg)) = result {
785 assert_eq!(msg, "invalid message version");
786 } else {
787 panic!("Deserialization should not succeed with a V1 message in Legacy/V0 format")
788 }
789 }
790}