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