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, ptr::copy_nonoverlapping},
14 solana_message::v1::SIGNATURE_SIZE,
15 solana_message::MESSAGE_VERSION_PREFIX,
16 solana_signer::{signers::Signers, SignerError},
17 wincode::{
18 config::Config,
19 containers,
20 io::{Reader, Writer},
21 len::ShortU16,
22 ReadError, ReadResult, SchemaRead, SchemaWrite, UninitBuilder, 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.peek()?;
305 let mut builder = VersionedTransactionUninitBuilder::<C>::from_maybe_uninit_mut(dst);
306
307 if discriminator & MESSAGE_VERSION_PREFIX == 0 {
308 builder.read_signatures(&mut reader)?;
311 builder.read_message(reader)?;
312
313 let message = unsafe { builder.uninit_message_mut().assume_init_ref() };
316
317 if !matches!(
319 message,
320 VersionedMessage::Legacy(_) | VersionedMessage::V0(_)
321 ) {
322 return Err(ReadError::Custom("invalid message version"));
323 }
324
325 builder.finish();
326 } else if *discriminator == V1_PREFIX {
327 builder.read_message(&mut reader)?;
330
331 let message = unsafe { builder.uninit_message_mut().assume_init_ref() };
333
334 if !matches!(message, VersionedMessage::V1(_)) {
336 return Err(ReadError::Custom("invalid message version"));
337 }
338
339 let expected_signatures_len = message.header().num_required_signatures as usize;
340
341 let bytes_to_read = expected_signatures_len.saturating_mul(SIGNATURE_SIZE);
342 let bytes = reader.fill_exact(bytes_to_read)?;
343 let mut signatures = Vec::with_capacity(expected_signatures_len);
344
345 unsafe {
349 let signatures_ptr = signatures.as_mut_ptr();
350 copy_nonoverlapping(
351 bytes.as_ptr() as *const Signature,
352 signatures_ptr,
353 expected_signatures_len,
354 );
355 signatures.set_len(expected_signatures_len);
356 reader.consume_unchecked(bytes_to_read);
359 }
360
361 builder.write_signatures(signatures);
362 builder.finish();
363 } else {
364 return Err(ReadError::Custom("invalid transaction discriminator"));
365 }
366
367 Ok(())
368 }
369}
370
371#[cfg(test)]
372mod tests {
373 use {
374 super::*,
375 solana_address::{Address, ADDRESS_BYTES},
376 solana_hash::Hash,
377 solana_instruction::{AccountMeta, Instruction},
378 solana_keypair::Keypair,
379 solana_message::{
380 compiled_instruction::CompiledInstruction,
381 v0::Message as MessageV0,
382 v1::{
383 InstructionHeader, Message, TransactionConfig, FIXED_HEADER_SIZE,
384 MAX_TRANSACTION_SIZE, SIGNATURE_SIZE,
385 },
386 Message as LegacyMessage, MessageHeader,
387 },
388 solana_pubkey::Pubkey,
389 solana_signer::Signer,
390 solana_system_interface::instruction as system_instruction,
391 test_case::test_case,
392 };
393
394 #[test]
395 fn test_try_new() {
396 let keypair0 = Keypair::new();
397 let keypair1 = Keypair::new();
398 let keypair2 = Keypair::new();
399
400 let message = VersionedMessage::Legacy(LegacyMessage::new(
401 &[Instruction::new_with_bytes(
402 Pubkey::new_unique(),
403 &[],
404 vec![
405 AccountMeta::new_readonly(keypair1.pubkey(), true),
406 AccountMeta::new_readonly(keypair2.pubkey(), false),
407 ],
408 )],
409 Some(&keypair0.pubkey()),
410 ));
411
412 assert_eq!(
413 VersionedTransaction::try_new(message.clone(), &[&keypair0]),
414 Err(SignerError::NotEnoughSigners)
415 );
416
417 assert_eq!(
418 VersionedTransaction::try_new(message.clone(), &[&keypair0, &keypair0]),
419 Err(SignerError::KeypairPubkeyMismatch)
420 );
421
422 assert_eq!(
423 VersionedTransaction::try_new(message.clone(), &[&keypair1, &keypair2]),
424 Err(SignerError::KeypairPubkeyMismatch)
425 );
426
427 match VersionedTransaction::try_new(message.clone(), &[&keypair0, &keypair1]) {
428 Ok(tx) => assert_eq!(tx.verify_with_results(), vec![true; 2]),
429 Err(err) => assert_eq!(Some(err), None),
430 }
431
432 match VersionedTransaction::try_new(message, &[&keypair1, &keypair0]) {
433 Ok(tx) => assert_eq!(tx.verify_with_results(), vec![true; 2]),
434 Err(err) => assert_eq!(Some(err), None),
435 }
436 }
437
438 fn nonced_transfer_tx() -> (Pubkey, Pubkey, VersionedTransaction) {
439 let from_keypair = Keypair::new();
440 let from_pubkey = from_keypair.pubkey();
441 let nonce_keypair = Keypair::new();
442 let nonce_pubkey = nonce_keypair.pubkey();
443 let instructions = [
444 system_instruction::advance_nonce_account(&nonce_pubkey, &nonce_pubkey),
445 system_instruction::transfer(&from_pubkey, &nonce_pubkey, 42),
446 ];
447 let message = LegacyMessage::new(&instructions, Some(&nonce_pubkey));
448 let tx = Transaction::new(&[&from_keypair, &nonce_keypair], message, Hash::default());
449 (from_pubkey, nonce_pubkey, tx.into())
450 }
451
452 #[test]
453 fn tx_uses_nonce_ok() {
454 let (_, _, tx) = nonced_transfer_tx();
455 assert!(tx.uses_durable_nonce());
456 }
457
458 #[test]
459 fn tx_uses_nonce_empty_ix_fail() {
460 let tx = VersionedTransaction {
461 message: VersionedMessage::V0(MessageV0::default()),
462 signatures: vec![],
463 };
464 assert!(!tx.uses_durable_nonce());
465 }
466
467 #[test]
468 fn tx_uses_nonce_bad_prog_id_idx_fail() {
469 let (_, _, mut tx) = nonced_transfer_tx();
470 match &mut tx.message {
471 VersionedMessage::Legacy(message) => {
472 message.instructions.get_mut(0).unwrap().program_id_index = 255u8;
473 }
474 _ => unreachable!(),
475 };
476 assert!(!tx.uses_durable_nonce());
477 }
478
479 #[test]
480 fn tx_uses_nonce_first_prog_id_not_nonce_fail() {
481 let from_keypair = Keypair::new();
482 let from_pubkey = from_keypair.pubkey();
483 let nonce_keypair = Keypair::new();
484 let nonce_pubkey = nonce_keypair.pubkey();
485 let instructions = [
486 system_instruction::transfer(&from_pubkey, &nonce_pubkey, 42),
487 system_instruction::advance_nonce_account(&nonce_pubkey, &nonce_pubkey),
488 ];
489 let message = LegacyMessage::new(&instructions, Some(&from_pubkey));
490 let tx = Transaction::new(&[&from_keypair, &nonce_keypair], message, Hash::default());
491 let tx = VersionedTransaction::from(tx);
492 assert!(!tx.uses_durable_nonce());
493 }
494
495 #[test]
496 fn tx_uses_nonce_wrong_first_nonce_ix_fail() {
497 let from_keypair = Keypair::new();
498 let from_pubkey = from_keypair.pubkey();
499 let nonce_keypair = Keypair::new();
500 let nonce_pubkey = nonce_keypair.pubkey();
501 let instructions = [
502 system_instruction::withdraw_nonce_account(
503 &nonce_pubkey,
504 &nonce_pubkey,
505 &from_pubkey,
506 42,
507 ),
508 system_instruction::transfer(&from_pubkey, &nonce_pubkey, 42),
509 ];
510 let message = LegacyMessage::new(&instructions, Some(&nonce_pubkey));
511 let tx = Transaction::new(&[&from_keypair, &nonce_keypair], message, Hash::default());
512 let tx = VersionedTransaction::from(tx);
513 assert!(!tx.uses_durable_nonce());
514 }
515
516 #[test]
517 fn test_sanitize_signatures_inner() {
518 assert_eq!(
519 VersionedTransaction::sanitize_signatures_inner(1, 1, 0),
520 Err(SanitizeError::IndexOutOfBounds)
521 );
522 assert_eq!(
523 VersionedTransaction::sanitize_signatures_inner(1, 1, 2),
524 Err(SanitizeError::InvalidValue)
525 );
526 assert_eq!(
527 VersionedTransaction::sanitize_signatures_inner(2, 1, 2),
528 Err(SanitizeError::IndexOutOfBounds)
529 );
530 assert_eq!(
531 VersionedTransaction::sanitize_signatures_inner(1, 1, 1),
532 Ok(())
533 );
534 }
535
536 #[test]
537 fn versioned_transaction_wincode_bincode_roundtrip() {
538 use {
539 super::*,
540 proptest::prelude::*,
541 solana_address::{Address, ADDRESS_BYTES},
542 solana_hash::{Hash, HASH_BYTES},
543 solana_message::{
544 compiled_instruction::CompiledInstruction,
545 v0::{self, MessageAddressTableLookup},
546 Message as LegacyMessage, MessageHeader,
547 },
548 solana_signature::SIGNATURE_BYTES,
549 };
550
551 #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
555 #[derive(Debug, PartialEq, Default, Eq, Clone)]
556 struct BincodeVersionedTransaction {
557 #[cfg_attr(feature = "serde", serde(with = "short_vec"))]
559 pub signatures: Vec<Signature>,
560 pub message: VersionedMessage,
562 }
563
564 fn strat_byte_vec(max_len: usize) -> impl Strategy<Value = Vec<u8>> {
565 proptest::collection::vec(any::<u8>(), 0..=max_len)
566 }
567
568 fn strat_signature() -> impl Strategy<Value = Signature> {
569 any::<[u8; SIGNATURE_BYTES]>().prop_map(Signature::from)
570 }
571
572 fn strat_address() -> impl Strategy<Value = Address> {
573 any::<[u8; ADDRESS_BYTES]>().prop_map(Address::new_from_array)
574 }
575
576 fn strat_hash() -> impl Strategy<Value = Hash> {
577 any::<[u8; HASH_BYTES]>().prop_map(Hash::new_from_array)
578 }
579
580 fn strat_message_header() -> impl Strategy<Value = MessageHeader> {
581 (0u8..128, any::<u8>(), any::<u8>()).prop_map(|(a, b, c)| MessageHeader {
582 num_required_signatures: a,
583 num_readonly_signed_accounts: b,
584 num_readonly_unsigned_accounts: c,
585 })
586 }
587
588 fn strat_compiled_instruction() -> impl Strategy<Value = CompiledInstruction> {
589 (any::<u8>(), strat_byte_vec(128), strat_byte_vec(128)).prop_map(
590 |(program_id_index, accounts, data)| {
591 CompiledInstruction::new_from_raw_parts(program_id_index, accounts, data)
592 },
593 )
594 }
595
596 fn strat_address_table_lookup() -> impl Strategy<Value = MessageAddressTableLookup> {
597 (strat_address(), strat_byte_vec(128), strat_byte_vec(128)).prop_map(
598 |(account_key, writable_indexes, readonly_indexes)| MessageAddressTableLookup {
599 account_key,
600 writable_indexes,
601 readonly_indexes,
602 },
603 )
604 }
605
606 fn strat_legacy_message() -> impl Strategy<Value = LegacyMessage> {
607 (
608 strat_message_header(),
609 proptest::collection::vec(strat_address(), 0..=8),
610 strat_hash(),
611 proptest::collection::vec(strat_compiled_instruction(), 0..=8),
612 )
613 .prop_map(|(header, account_keys, recent_blockhash, instructions)| {
614 LegacyMessage {
615 header,
616 account_keys,
617 recent_blockhash,
618 instructions,
619 }
620 })
621 }
622
623 fn strat_v0_message() -> impl Strategy<Value = v0::Message> {
624 (
625 strat_message_header(),
626 proptest::collection::vec(strat_address(), 0..=8),
627 strat_hash(),
628 proptest::collection::vec(strat_compiled_instruction(), 0..=4),
629 proptest::collection::vec(strat_address_table_lookup(), 0..=4),
630 )
631 .prop_map(
632 |(
633 header,
634 account_keys,
635 recent_blockhash,
636 instructions,
637 address_table_lookups,
638 )| {
639 v0::Message {
640 header,
641 account_keys,
642 recent_blockhash,
643 instructions,
644 address_table_lookups,
645 }
646 },
647 )
648 }
649
650 fn strat_versioned_message() -> impl Strategy<Value = VersionedMessage> {
651 prop_oneof![
652 strat_legacy_message().prop_map(VersionedMessage::Legacy),
653 strat_v0_message().prop_map(VersionedMessage::V0),
654 ]
655 }
656
657 fn strat_versioned_transaction(
658 ) -> impl Strategy<Value = (VersionedTransaction, BincodeVersionedTransaction)> {
659 (
660 proptest::collection::vec(strat_signature(), 0..=8),
661 strat_versioned_message(),
662 )
663 .prop_map(|(signatures, message)| {
664 (
665 VersionedTransaction {
666 message: message.clone(),
667 signatures: signatures.clone(),
668 },
669 BincodeVersionedTransaction {
670 message: message.clone(),
671 signatures: signatures.clone(),
672 },
673 )
674 })
675 }
676
677 proptest!(|(tx in strat_versioned_transaction())| {
678 let wincode_serialized = wincode::serialize(&tx.0).unwrap();
679 let bincode_serialized = bincode::serialize(&tx.1).unwrap();
680
681 assert_eq!(bincode_serialized, wincode_serialized);
682
683 let bincode_deserialized: BincodeVersionedTransaction = bincode::deserialize(&bincode_serialized).unwrap();
684 let wincode_deserialized: VersionedTransaction = wincode::deserialize(&wincode_serialized).unwrap();
685
686 assert_eq!(&bincode_deserialized.message, &wincode_deserialized.message);
687 assert_eq!(&bincode_deserialized.signatures, &wincode_deserialized.signatures);
688
689 assert_eq!(wincode_deserialized, tx.0);
690 });
691 }
692
693 #[test_case(0 ; "at max size")]
694 #[test_case(1 ; "over by one")]
695 #[allow(clippy::arithmetic_side_effects)]
696 fn v1_transaction_serialization(delta: usize) {
697 const NUM_SIGNATURES: usize = 1;
705 const NUM_ADDRESSES: usize = 2;
706 const NUM_INSTRUCTION_ACCOUNTS: usize = 1;
707
708 let overhead = 1 + (NUM_SIGNATURES * SIGNATURE_SIZE)
710 + FIXED_HEADER_SIZE
711 + (NUM_ADDRESSES * ADDRESS_BYTES)
712 + size_of::<InstructionHeader>()
713 + NUM_INSTRUCTION_ACCOUNTS;
714
715 let max_data_size = MAX_TRANSACTION_SIZE - overhead + delta;
718 let data = vec![0u8; max_data_size];
719
720 let message = Message {
721 header: MessageHeader {
722 num_required_signatures: NUM_SIGNATURES as u8,
723 num_readonly_signed_accounts: 0,
724 num_readonly_unsigned_accounts: 0,
725 },
726 config: TransactionConfig::default(),
727 account_keys: vec![Address::new_unique(), Address::new_unique()],
728 lifetime_specifier: Hash::new_unique(),
729 instructions: vec![CompiledInstruction {
730 program_id_index: 1,
731 accounts: vec![0],
732 data,
733 }],
734 };
735
736 let v1_tx = VersionedTransaction {
737 message: VersionedMessage::V1(message),
738 signatures: vec![Signature::default()],
739 };
740
741 let serialized = wincode::serialize(&v1_tx).unwrap();
742
743 match delta {
744 0 => assert_eq!(
745 serialized.len(),
746 MAX_TRANSACTION_SIZE,
747 "Transaction should be exactly at max size"
748 ),
749 d => assert_eq!(
750 serialized.len(),
751 MAX_TRANSACTION_SIZE + d,
752 "Transaction should be over by {d} byte(s)"
753 ),
754 }
755
756 let deserialized = wincode::deserialize(&serialized).unwrap();
757
758 assert_eq!(
759 v1_tx, deserialized,
760 "Deserialized payload should match original"
761 );
762 }
763
764 #[test]
765 fn test_v1_message_in_legacy_transaction() {
766 #[rustfmt::skip]
767 let malformed_input: &[u8] = &[
768 0x00, 0x81, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
776 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 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
781 0x00,
783 0x01,
785 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
787 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
788 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
789 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
790 ];
791
792 let result: Result<VersionedTransaction, _> = wincode::deserialize(malformed_input);
793
794 if let Err(wincode::ReadError::Custom(msg)) = result {
795 assert_eq!(msg, "invalid message version");
796 } else {
797 panic!("Deserialization should not succeed with a V1 message in Legacy/V0 format")
798 }
799 }
800}