1pub mod shielded;
4
5pub use shielded::{
6 fake_bundle_for_branch, fake_orchard_bundle, fake_orchard_bundle_duplicate_nullifiers,
7 fake_orchard_bundle_with_note, fake_v6_transaction, insert_fake_orchard_shielded_data,
8 outputs_enabled_flags, v6_ironwood_flags_offset, v6_orchard_flags_offset,
9 with_garbage_orchard_authorization, with_orchard_flags, with_orchard_value_balance,
10};
11
12use std::{cmp::max, collections::HashMap, ops::Neg, sync::Arc};
13
14use chrono::{TimeZone, Utc};
15use proptest::{array, collection::vec, option, prelude::*};
16use reddsa::{orchard::Binding, Signature};
17
18use crate::{
19 amount::{self, Amount, NegativeAllowed, NonNegative},
20 block::{self, arbitrary::MAX_PARTIAL_CHAIN_BLOCKS},
21 orchard,
22 parameters::{Network, NetworkUpgrade},
23 primitives::{Halo2Proof, ZkSnarkProof},
24 sapling::{self, AnchorVariant, PerSpendAnchor, SharedAnchor},
25 serialization::{self, ZcashDeserializeInto},
26 sprout, transparent,
27 value_balance::{ValueBalance, ValueBalanceError},
28 LedgerState,
29};
30
31use zcash_primitives::transaction::TxVersion;
32use zcash_transparent;
33
34use super::{
35 FieldNotPresent, JoinSplitData, LockTime, Memo, Transaction, UnminedTx, VerifiedUnminedTx,
36};
37
38fn branch_id_of(
48 network_upgrade: NetworkUpgrade,
49 fallback: zcash_protocol::consensus::BranchId,
50) -> zcash_protocol::consensus::BranchId {
51 network_upgrade
52 .branch_id()
53 .and_then(|cbid| zcash_protocol::consensus::BranchId::try_from(cbid).ok())
54 .unwrap_or(fallback)
55}
56
57pub const MAX_ARBITRARY_ITEMS: usize = 4;
62
63impl Transaction {
66 pub fn v1_strategy(ledger_state: LedgerState) -> BoxedStrategy<Self> {
68 (
69 transparent::Input::vec_strategy(&ledger_state, MAX_ARBITRARY_ITEMS),
70 vec(any::<transparent::Output>(), 0..MAX_ARBITRARY_ITEMS),
71 any::<LockTime>(),
72 )
73 .prop_map(|(inputs, outputs, lock_time)| {
74 Transaction::test_v1(inputs, outputs, lock_time)
75 })
76 .boxed()
77 }
78
79 pub fn v2_strategy(ledger_state: LedgerState) -> BoxedStrategy<Self> {
84 (
85 transparent::Input::vec_strategy(&ledger_state, MAX_ARBITRARY_ITEMS),
86 vec(any::<transparent::Output>(), 0..MAX_ARBITRARY_ITEMS),
87 any::<LockTime>(),
88 )
89 .prop_map(|(inputs, outputs, lock_time)| {
90 Transaction::test_v2(inputs, outputs, lock_time)
91 })
92 .boxed()
93 }
94
95 pub fn v3_strategy(ledger_state: LedgerState) -> BoxedStrategy<Self> {
100 (
101 transparent::Input::vec_strategy(&ledger_state, MAX_ARBITRARY_ITEMS),
102 vec(any::<transparent::Output>(), 0..MAX_ARBITRARY_ITEMS),
103 any::<LockTime>(),
104 any::<block::Height>(),
105 )
106 .prop_map(|(inputs, outputs, lock_time, expiry_height)| {
107 Transaction::test_v3(inputs, outputs, lock_time, expiry_height)
108 })
109 .boxed()
110 }
111
112 pub fn v4_strategy(ledger_state: LedgerState) -> BoxedStrategy<Self> {
117 (
118 transparent::Input::vec_strategy(&ledger_state, MAX_ARBITRARY_ITEMS),
119 vec(any::<transparent::Output>(), 0..MAX_ARBITRARY_ITEMS),
120 any::<LockTime>(),
121 any::<block::Height>(),
122 )
123 .prop_map(|(inputs, outputs, lock_time, expiry_height)| {
124 Transaction::test_v4(inputs, outputs, lock_time, expiry_height)
125 })
126 .boxed()
127 }
128
129 pub fn v5_strategy(ledger_state: LedgerState) -> BoxedStrategy<Self> {
134 (
135 NetworkUpgrade::nu5_branch_id_strategy(),
136 any::<LockTime>(),
137 any::<block::Height>(),
138 transparent::Input::vec_strategy(&ledger_state, MAX_ARBITRARY_ITEMS),
139 vec(any::<transparent::Output>(), 0..MAX_ARBITRARY_ITEMS),
140 option::of((1usize..=2usize, any::<u64>())),
141 )
142 .prop_map(
143 move |(network_upgrade, lock_time, expiry_height, inputs, outputs, orchard)| {
144 let nu = if ledger_state.transaction_has_valid_network_upgrade() {
145 let ledger_nu = ledger_state.network_upgrade();
148 if ledger_nu.branch_id().is_some() {
149 ledger_nu
150 } else {
151 network_upgrade
152 }
153 } else {
154 network_upgrade
155 };
156
157 let orchard_bundle = (!ledger_state.height.is_min())
159 .then_some(orchard)
160 .flatten()
161 .and_then(|(n_actions, seed)| {
162 shielded::fake_bundle_for_branch(
163 branch_id_of(nu, zcash_protocol::consensus::BranchId::Nu5),
164 ::orchard::ValuePool::Orchard,
165 n_actions,
166 seed,
167 )
168 });
169
170 Transaction::test_v5_with_orchard(
171 nu,
172 inputs,
173 outputs,
174 lock_time,
175 expiry_height,
176 orchard_bundle,
177 )
178 },
179 )
180 .boxed()
181 }
182
183 pub fn v6_strategy(ledger_state: LedgerState) -> BoxedStrategy<Self> {
188 (
189 NetworkUpgrade::nu6_3_branch_id_strategy(),
190 any::<LockTime>(),
191 any::<block::Height>(),
192 transparent::Input::vec_strategy(&ledger_state, MAX_ARBITRARY_ITEMS),
193 vec(any::<transparent::Output>(), 0..MAX_ARBITRARY_ITEMS),
194 option::of((1usize..=2usize, any::<u64>())),
195 option::of((1usize..=2usize, any::<u64>())),
196 )
197 .prop_map(
198 move |(
199 network_upgrade,
200 lock_time,
201 expiry_height,
202 inputs,
203 outputs,
204 orchard,
205 ironwood,
206 )| {
207 let nu = if ledger_state.transaction_has_valid_network_upgrade() {
208 let ledger_nu = ledger_state.network_upgrade();
209 if ledger_nu.branch_id().is_some() {
210 ledger_nu
211 } else {
212 network_upgrade
213 }
214 } else {
215 network_upgrade
216 };
217
218 let is_genesis = ledger_state.height.is_min();
220
221 let orchard_bundle =
222 (!is_genesis)
223 .then_some(orchard)
224 .flatten()
225 .and_then(|(n_actions, seed)| {
226 shielded::fake_bundle_for_branch(
227 branch_id_of(nu, zcash_protocol::consensus::BranchId::Nu6_3),
228 ::orchard::ValuePool::Orchard,
229 n_actions,
230 seed,
231 )
232 });
233
234 let ironwood_bundle = (!is_genesis).then_some(ironwood).flatten().and_then(
238 |(n_actions, seed)| {
239 shielded::fake_bundle_for_branch(
240 branch_id_of(nu, zcash_protocol::consensus::BranchId::Nu6_3),
241 ::orchard::ValuePool::Ironwood,
242 n_actions,
243 seed ^ 0xEEEE_0000_u64,
244 )
245 },
246 );
247
248 Transaction::test_v6_with_bundles(
249 nu,
250 inputs,
251 outputs,
252 lock_time,
253 expiry_height,
254 orchard_bundle,
255 ironwood_bundle,
256 )
257 },
258 )
259 .boxed()
260 }
261
262 pub fn vec_strategy(
265 mut ledger_state: LedgerState,
266 len: usize,
267 ) -> BoxedStrategy<Vec<Arc<Self>>> {
268 let coinbase = Transaction::arbitrary_with(ledger_state.clone()).prop_map(Arc::new);
270 ledger_state.has_coinbase = false;
271 let remainder = vec(
272 Transaction::arbitrary_with(ledger_state).prop_map(Arc::new),
273 0..=len,
274 );
275
276 (coinbase, remainder)
277 .prop_map(|(first, mut remainder)| {
278 remainder.insert(0, first);
279 remainder
280 })
281 .boxed()
282 }
283
284 pub fn for_each_value_mut<F>(&mut self, mut f: F)
289 where
290 F: FnMut(&mut Amount<NonNegative>),
291 {
292 let mut outputs = self.outputs();
293 let mut changed = false;
294 for output in &mut outputs {
295 let old = output.value;
296 f(&mut output.value);
297 if output.value != old {
298 changed = true;
299 }
300 }
301 if changed {
302 *self = self.clone().with_transparent_outputs(outputs);
303 }
304 }
305
306 pub fn for_each_value_balance_mut<F>(&mut self, _f: F)
311 where
312 F: FnMut(&mut Amount<NegativeAllowed>),
313 {
314 }
320
321 pub fn fix_overflow(&mut self) {
328 fn scale_to_avoid_overflow<C: amount::Constraint>(amount: &mut Amount<C>)
329 where
330 Amount<C>: Copy,
331 {
332 const POOL_COUNT: u64 = 5;
334
335 let max_arbitrary_items: u64 = MAX_ARBITRARY_ITEMS.try_into().unwrap();
336 let max_partial_chain_blocks: u64 = MAX_PARTIAL_CHAIN_BLOCKS.try_into().unwrap();
337
338 let transaction_pool_scaling_divisor =
340 max_arbitrary_items * POOL_COUNT * max_arbitrary_items;
341 let chain_pool_scaling_divisor =
343 max_arbitrary_items * max_arbitrary_items * max_partial_chain_blocks;
344 let scaling_divisor = max(transaction_pool_scaling_divisor, chain_pool_scaling_divisor);
345
346 *amount = (*amount / scaling_divisor).expect("divisor is not zero");
347 }
348
349 self.for_each_value_mut(scale_to_avoid_overflow);
350 }
352
353 pub fn fix_chain_value_pools(
384 &mut self,
385 chain_value_pools: ValueBalance<NonNegative>,
386 outputs: &HashMap<transparent::OutPoint, transparent::Output>,
387 ) -> Result<(Amount<NonNegative>, ValueBalance<NonNegative>), ValueBalanceError> {
388 self.fix_overflow();
389
390 let mut input_chain_value_pools = chain_value_pools;
393
394 for input in self.inputs() {
395 input_chain_value_pools = input_chain_value_pools
396 .add_transparent_input(input, outputs)
397 .expect("find_valid_utxo_for_spend only spends unspent transparent outputs");
398 }
399
400 let remaining_transaction_value = self.fix_remaining_value(outputs)?;
408
409 let transaction_chain_value_pool_change =
411 self
412 .transparent_value_balance_from_outputs(outputs)
413 .expect("chain value pool and remaining transaction value fixes produce valid transaction value balances")
414 .neg();
415
416 let chain_value_pools = chain_value_pools
417 .add_transaction(self, outputs)
418 .unwrap_or_else(|err| {
419 panic!(
420 "unexpected chain value pool error: {err:?}, \n\
421 original chain value pools: {chain_value_pools:?}, \n\
422 transaction chain value change: {transaction_chain_value_pool_change:?}, \n\
423 input-only transaction chain value pools: {input_chain_value_pools:?}, \n\
424 calculated remaining transaction value: {remaining_transaction_value:?}",
425 )
426 });
427
428 Ok((remaining_transaction_value, chain_value_pools))
429 }
430
431 fn input_value_pool(
438 &self,
439 outputs: &HashMap<transparent::OutPoint, transparent::Output>,
440 ) -> Result<Amount<NonNegative>, ValueBalanceError> {
441 let transparent_inputs = self
442 .inputs()
443 .iter()
444 .map(|input| input.value_from_outputs(outputs))
445 .sum::<Result<Amount<NonNegative>, amount::Error>>()
446 .map_err(ValueBalanceError::Transparent)?;
447 let sprout_inputs = Amount::<NonNegative>::zero();
454
455 let sapling_input = self
457 .sapling_value_balance()
458 .sapling_amount()
459 .constrain::<NonNegative>()
460 .unwrap_or_else(|_| Amount::zero());
461
462 let orchard_input = self
463 .orchard_value_balance()
464 .orchard_amount()
465 .constrain::<NonNegative>()
466 .unwrap_or_else(|_| Amount::zero());
467
468 let ironwood_input = self
469 .ironwood_value_balance()
470 .ironwood_amount()
471 .constrain::<NonNegative>()
472 .unwrap_or_else(|_| Amount::zero());
473
474 let transaction_input_value_pool =
475 (transparent_inputs + sprout_inputs + sapling_input + orchard_input + ironwood_input)
476 .expect("chain is limited to MAX_MONEY");
477
478 Ok(transaction_input_value_pool)
479 }
480
481 pub fn fix_remaining_value(
502 &mut self,
503 outputs: &HashMap<transparent::OutPoint, transparent::Output>,
504 ) -> Result<Amount<NonNegative>, ValueBalanceError> {
505 if self.is_coinbase() {
506 return Ok(Amount::zero());
514 }
515
516 let mut remaining_input_value = self.input_value_pool(outputs)?;
517
518 let mut tx_outputs = self.outputs();
521 let mut outputs_changed = false;
522 for output in &mut tx_outputs {
523 if remaining_input_value >= output.value {
524 remaining_input_value = (remaining_input_value - output.value)
525 .expect("input >= output so result is always non-negative");
526 } else {
527 output.value = Amount::zero();
528 outputs_changed = true;
529 }
530 }
531 if outputs_changed {
532 *self = self.clone().with_transparent_outputs(tx_outputs);
533 }
534
535 let remaining_transaction_value = self
540 .transparent_value_balance_from_outputs(outputs)
541 .expect("chain is limited to MAX_MONEY")
542 .remaining_transaction_value()
543 .unwrap_or_else(|err| {
544 panic!(
545 "unexpected remaining transaction value: {err:?}, \
546 calculated remaining input value: {remaining_input_value:?}"
547 )
548 });
549 assert_eq!(
550 remaining_input_value,
551 remaining_transaction_value,
552 "fix_remaining_value and remaining_transaction_value calculated different remaining values"
553 );
554
555 Ok(remaining_transaction_value)
556 }
557}
558
559impl Arbitrary for Memo {
560 type Parameters = ();
561
562 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
563 (vec(any::<u8>(), 512))
564 .prop_map(|v| {
565 let mut bytes = [0; 512];
566 bytes.copy_from_slice(v.as_slice());
567 Memo(Box::new(bytes))
568 })
569 .boxed()
570 }
571
572 type Strategy = BoxedStrategy<Self>;
573}
574
575impl Arbitrary for LockTime {
577 type Parameters = ();
578
579 fn arbitrary_with(_args: ()) -> Self::Strategy {
580 prop_oneof![
581 (block::Height::MIN.0..=LockTime::MAX_HEIGHT.0)
582 .prop_map(|n| LockTime::Height(block::Height(n))),
583 (LockTime::MIN_TIMESTAMP..=LockTime::MAX_TIMESTAMP).prop_map(|n| {
584 LockTime::Time(
585 Utc.timestamp_opt(n, 0)
586 .single()
587 .expect("in-range number of seconds and valid nanosecond"),
588 )
589 })
590 ]
591 .boxed()
592 }
593
594 type Strategy = BoxedStrategy<Self>;
595}
596
597impl<P: ZkSnarkProof + Arbitrary + 'static> Arbitrary for JoinSplitData<P> {
598 type Parameters = ();
599
600 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
601 (
602 any::<sprout::JoinSplit<P>>(),
603 vec(any::<sprout::JoinSplit<P>>(), 0..MAX_ARBITRARY_ITEMS),
604 array::uniform32(any::<u8>()),
605 vec(any::<u8>(), 64),
606 )
607 .prop_map(|(first, rest, pub_key_bytes, sig_bytes)| Self {
608 first,
609 rest,
610 pub_key: ed25519_zebra::VerificationKeyBytes::from(pub_key_bytes),
611 sig: ed25519_zebra::Signature::from({
612 let mut b = [0u8; 64];
613 b.copy_from_slice(sig_bytes.as_slice());
614 b
615 }),
616 })
617 .boxed()
618 }
619
620 type Strategy = BoxedStrategy<Self>;
621}
622
623impl<AnchorV> Arbitrary for sapling::ShieldedData<AnchorV>
624where
625 AnchorV: AnchorVariant + Clone + std::fmt::Debug + 'static,
626 sapling::TransferData<AnchorV>: Arbitrary,
627{
628 type Parameters = ();
629
630 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
631 (
632 any::<Amount>(),
633 any::<sapling::TransferData<AnchorV>>(),
634 vec(any::<u8>(), 64),
635 )
636 .prop_map(|(value_balance, transfers, sig_bytes)| Self {
637 value_balance,
638 transfers,
639 binding_sig: redjubjub::Signature::from({
640 let mut b = [0u8; 64];
641 b.copy_from_slice(sig_bytes.as_slice());
642 b
643 }),
644 })
645 .boxed()
646 }
647
648 type Strategy = BoxedStrategy<Self>;
649}
650
651impl Arbitrary for sapling::TransferData<PerSpendAnchor> {
652 type Parameters = ();
653
654 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
655 vec(any::<sapling::Output>(), 0..MAX_ARBITRARY_ITEMS)
656 .prop_flat_map(|outputs| {
657 (
658 if outputs.is_empty() {
659 vec(
661 any::<sapling::Spend<PerSpendAnchor>>(),
662 1..MAX_ARBITRARY_ITEMS,
663 )
664 } else {
665 vec(
666 any::<sapling::Spend<PerSpendAnchor>>(),
667 0..MAX_ARBITRARY_ITEMS,
668 )
669 },
670 Just(outputs),
671 )
672 })
673 .prop_map(|(spends, outputs)| {
674 if !spends.is_empty() {
675 sapling::TransferData::SpendsAndMaybeOutputs {
676 shared_anchor: FieldNotPresent,
677 spends: spends.try_into().unwrap(),
678 maybe_outputs: outputs,
679 }
680 } else if !outputs.is_empty() {
681 sapling::TransferData::JustOutputs {
682 outputs: outputs.try_into().unwrap(),
683 }
684 } else {
685 unreachable!("there must be at least one generated spend or output")
686 }
687 })
688 .boxed()
689 }
690
691 type Strategy = BoxedStrategy<Self>;
692}
693
694impl Arbitrary for sapling::TransferData<SharedAnchor> {
695 type Parameters = ();
696
697 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
698 vec(any::<sapling::Output>(), 0..MAX_ARBITRARY_ITEMS)
699 .prop_flat_map(|outputs| {
700 (
701 any::<sapling::tree::Root>(),
702 if outputs.is_empty() {
703 vec(
705 any::<sapling::Spend<SharedAnchor>>(),
706 1..MAX_ARBITRARY_ITEMS,
707 )
708 } else {
709 vec(
710 any::<sapling::Spend<SharedAnchor>>(),
711 0..MAX_ARBITRARY_ITEMS,
712 )
713 },
714 Just(outputs),
715 )
716 })
717 .prop_map(|(shared_anchor, spends, outputs)| {
718 if !spends.is_empty() {
719 sapling::TransferData::SpendsAndMaybeOutputs {
720 shared_anchor,
721 spends: spends.try_into().unwrap(),
722 maybe_outputs: outputs,
723 }
724 } else if !outputs.is_empty() {
725 sapling::TransferData::JustOutputs {
726 outputs: outputs.try_into().unwrap(),
727 }
728 } else {
729 unreachable!("there must be at least one generated spend or output")
730 }
731 })
732 .boxed()
733 }
734
735 type Strategy = BoxedStrategy<Self>;
736}
737
738impl Arbitrary for orchard::ShieldedData {
739 type Parameters = ();
740
741 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
742 (
743 any::<orchard::shielded_data::Flags>(),
744 any::<Amount>(),
745 any::<orchard::tree::Root>(),
746 vec(
747 any::<orchard::shielded_data::AuthorizedAction>(),
748 1..MAX_ARBITRARY_ITEMS,
749 ),
750 any::<BindingSignature>(),
751 )
752 .prop_flat_map(
753 |(flags, value_balance, shared_anchor, actions, binding_sig)| {
754 let proof_size = orchard::shielded_data::expected_proof_size(actions.len());
761 (
762 Just(flags),
763 Just(value_balance),
764 Just(shared_anchor),
765 vec(any::<u8>(), proof_size).prop_map(Halo2Proof),
766 Just(actions),
767 Just(binding_sig),
768 )
769 },
770 )
771 .prop_map(
772 |(flags, value_balance, shared_anchor, proof, actions, binding_sig)| Self {
773 flags,
774 value_balance,
775 shared_anchor,
776 proof,
777 actions: actions
778 .try_into()
779 .expect("arbitrary vector size range produces at least one action"),
780 binding_sig: binding_sig.0,
781 },
782 )
783 .boxed()
784 }
785
786 type Strategy = BoxedStrategy<Self>;
787}
788
789impl Arbitrary for orchard::ShieldedDataV6 {
790 type Parameters = ();
791
792 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
793 any::<orchard::ShieldedData>()
797 .prop_map(orchard::ShieldedDataV6::new)
798 .boxed()
799 }
800
801 type Strategy = BoxedStrategy<Self>;
802}
803
804#[derive(Copy, Clone, Debug, Eq, PartialEq)]
805struct BindingSignature(pub(crate) Signature<Binding>);
806
807impl Arbitrary for BindingSignature {
808 type Parameters = ();
809
810 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
811 (vec(any::<u8>(), 64))
812 .prop_filter_map(
813 "zero Signature::<Binding> values are invalid",
814 |sig_bytes| {
815 let mut b = [0u8; 64];
816 b.copy_from_slice(sig_bytes.as_slice());
817 if b == [0u8; 64] {
818 return None;
819 }
820 Some(BindingSignature(Signature::<Binding>::from(b)))
821 },
822 )
823 .boxed()
824 }
825
826 type Strategy = BoxedStrategy<Self>;
827}
828
829impl Arbitrary for Transaction {
830 type Parameters = LedgerState;
831
832 fn arbitrary_with(ledger_state: Self::Parameters) -> Self::Strategy {
833 match ledger_state.transaction_version_override() {
834 Some(1) => return Self::v1_strategy(ledger_state),
835 Some(2) => return Self::v2_strategy(ledger_state),
836 Some(3) => return Self::v3_strategy(ledger_state),
837 Some(4) => return Self::v4_strategy(ledger_state),
838 Some(5) => return Self::v5_strategy(ledger_state),
839 Some(6) => return Self::v6_strategy(ledger_state),
840 Some(_) => unreachable!("invalid transaction version in override"),
841 None => {}
842 }
843
844 match ledger_state.network_upgrade() {
845 NetworkUpgrade::Genesis | NetworkUpgrade::BeforeOverwinter => {
846 Self::v1_strategy(ledger_state)
847 }
848 NetworkUpgrade::Overwinter => Self::v2_strategy(ledger_state),
849 NetworkUpgrade::Sapling => Self::v3_strategy(ledger_state),
850 NetworkUpgrade::Blossom | NetworkUpgrade::Heartwood | NetworkUpgrade::Canopy => {
851 Self::v4_strategy(ledger_state)
852 }
853 NetworkUpgrade::Nu5
854 | NetworkUpgrade::Nu6
855 | NetworkUpgrade::Nu6_1
856 | NetworkUpgrade::Nu6_2 => prop_oneof![
857 Self::v4_strategy(ledger_state.clone()),
858 Self::v5_strategy(ledger_state)
859 ]
860 .boxed(),
861
862 NetworkUpgrade::Nu6_3 | NetworkUpgrade::Nu7 => prop_oneof![
864 Self::v4_strategy(ledger_state.clone()),
865 Self::v5_strategy(ledger_state.clone()),
866 Self::v6_strategy(ledger_state)
867 ]
868 .boxed(),
869
870 #[cfg(zcash_unstable = "zfuture")]
871 NetworkUpgrade::ZFuture => prop_oneof![
872 Self::v4_strategy(ledger_state.clone()),
873 Self::v5_strategy(ledger_state)
874 ]
875 .boxed(),
876 }
877 }
878
879 type Strategy = BoxedStrategy<Self>;
880}
881
882impl Arbitrary for UnminedTx {
883 type Parameters = ();
884
885 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
886 any::<Transaction>()
887 .prop_map(|tx| UnminedTx::from(Arc::new(tx)))
888 .boxed()
889 }
890
891 type Strategy = BoxedStrategy<Self>;
892}
893
894impl Arbitrary for VerifiedUnminedTx {
895 type Parameters = ();
896
897 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
898 (
899 any::<UnminedTx>(),
900 any::<Amount<NonNegative>>(),
901 any::<u32>(),
902 any::<u32>(),
903 any::<(u16, u16)>().prop_map(|(unpaid_actions, conventional_actions)| {
904 (
905 unpaid_actions % conventional_actions.saturating_add(1),
906 conventional_actions,
907 )
908 }),
909 any::<f32>(),
910 serialization::arbitrary::datetime_u32(),
911 any::<block::Height>(),
912 )
913 .prop_map(
914 |(
915 transaction,
916 miner_fee,
917 sigops,
918 p2sh_sigops,
919 (conventional_actions, mut unpaid_actions),
920 fee_weight_ratio,
921 time,
922 height,
923 )| {
924 if unpaid_actions > conventional_actions {
925 unpaid_actions = conventional_actions;
926 }
927
928 let conventional_actions = conventional_actions as u32;
929 let unpaid_actions = unpaid_actions as u32;
930
931 Self {
932 transaction,
933 miner_fee,
934 legacy_sigop_count: sigops,
935 p2sh_sigop_count: p2sh_sigops,
936 conventional_actions,
937 unpaid_actions,
938 fee_weight_ratio,
939 time: Some(time),
940 height: Some(height),
941 spent_outputs: std::sync::Arc::new(vec![]),
942 }
943 },
944 )
945 .boxed()
946 }
947 type Strategy = BoxedStrategy<Self>;
948}
949
950pub fn transaction_to_fake_v5(
958 trans: &Transaction,
959 network: &Network,
960 height: block::Height,
961) -> Transaction {
962 let block_nu = NetworkUpgrade::current(network, height);
963
964 match trans.tx_version() {
965 TxVersion::V5 | TxVersion::V6 => trans.clone(),
967 _ => {
969 use crate::transaction::compat;
970 use zcash_primitives::transaction::{self as zp_tx, TxVersion};
971 use zcash_protocol::consensus::BranchId;
972
973 let branch_id = block_nu
974 .branch_id()
975 .and_then(|cbid| BranchId::try_from(cbid).ok())
976 .unwrap_or(BranchId::Nu5);
977
978 let inputs = trans.inputs();
979 let outputs = trans.outputs();
980 let vin = inputs.iter().map(compat::input_to_txin).collect();
981 let vout = outputs.iter().map(compat::output_to_txout).collect();
982
983 let lock_time_u32 =
984 compat::lock_time_to_u32(&trans.lock_time().unwrap_or(LockTime::unlocked()));
985
986 let transparent_bundle = Some(zcash_transparent::bundle::Bundle {
987 vin,
988 vout,
989 authorization: zcash_transparent::bundle::Authorized,
990 });
991
992 let sapling_bundle = trans.0.sapling_bundle().cloned();
994
995 let tx_data = zp_tx::TransactionData::from_parts(
996 TxVersion::V5,
997 branch_id,
998 lock_time_u32,
999 zcash_protocol::consensus::BlockHeight::from_u32(height.0),
1000 transparent_bundle,
1001 None,
1002 sapling_bundle,
1003 None,
1004 );
1005
1006 Transaction(tx_data.freeze().expect("rebuilt from valid transaction"))
1007 }
1008 #[allow(unreachable_patterns)]
1010 _ => trans.clone(),
1011 }
1012}
1013
1014pub fn test_transactions(
1016 network: &Network,
1017) -> impl DoubleEndedIterator<Item = (block::Height, Arc<Transaction>)> {
1018 let blocks = network.block_iter();
1019
1020 transactions_from_blocks(blocks)
1021}
1022
1023pub fn v5_transactions<'b>(
1025 blocks: impl DoubleEndedIterator<Item = (&'b u32, &'b &'static [u8])> + 'b,
1026) -> impl DoubleEndedIterator<Item = Transaction> + 'b {
1027 transactions_from_blocks(blocks).filter_map(|(_, tx)| match tx.tx_version() {
1028 TxVersion::V5 | TxVersion::V6 => Some((*tx).clone()),
1029 _ => None,
1030 })
1031}
1032
1033pub fn transactions_from_blocks<'a>(
1035 blocks: impl DoubleEndedIterator<Item = (&'a u32, &'a &'static [u8])> + 'a,
1036) -> impl DoubleEndedIterator<Item = (block::Height, Arc<Transaction>)> + 'a {
1037 blocks.flat_map(|(&block_height, &block_bytes)| {
1038 let block = block_bytes
1039 .zcash_deserialize_into::<block::Block>()
1040 .expect("block is structurally valid");
1041
1042 block
1043 .transactions
1044 .into_iter()
1045 .map(move |transaction| (block::Height(block_height), transaction))
1046 })
1047}