1use std::{cmp::max, collections::HashMap, ops::Neg, sync::Arc};
4
5use chrono::{TimeZone, Utc};
6use proptest::{array, collection::vec, option, prelude::*, test_runner::TestRunner};
7use reddsa::{orchard::Binding, Signature};
8
9use crate::{
10 amount::{self, Amount, NegativeAllowed, NonNegative},
11 at_least_one,
12 block::{self, arbitrary::MAX_PARTIAL_CHAIN_BLOCKS},
13 orchard,
14 parameters::{Network, NetworkUpgrade},
15 primitives::{Bctv14Proof, Groth16Proof, Halo2Proof, ZkSnarkProof},
16 sapling::{self, AnchorVariant, PerSpendAnchor, SharedAnchor},
17 serialization::{self, ZcashDeserializeInto},
18 sprout, transparent,
19 value_balance::{ValueBalance, ValueBalanceError},
20 LedgerState,
21};
22
23use itertools::Itertools;
24
25use super::{
26 FieldNotPresent, JoinSplitData, LockTime, Memo, Transaction, UnminedTx, VerifiedUnminedTx,
27};
28
29pub const MAX_ARBITRARY_ITEMS: usize = 4;
34
35impl Transaction {
38 pub fn v1_strategy(ledger_state: LedgerState) -> BoxedStrategy<Self> {
40 (
41 transparent::Input::vec_strategy(&ledger_state, MAX_ARBITRARY_ITEMS),
42 vec(any::<transparent::Output>(), 0..MAX_ARBITRARY_ITEMS),
43 any::<LockTime>(),
44 )
45 .prop_map(|(inputs, outputs, lock_time)| Transaction::V1 {
46 inputs,
47 outputs,
48 lock_time,
49 })
50 .boxed()
51 }
52
53 pub fn v2_strategy(ledger_state: LedgerState) -> BoxedStrategy<Self> {
55 (
56 transparent::Input::vec_strategy(&ledger_state, MAX_ARBITRARY_ITEMS),
57 vec(any::<transparent::Output>(), 0..MAX_ARBITRARY_ITEMS),
58 any::<LockTime>(),
59 option::of(any::<JoinSplitData<Bctv14Proof>>()),
60 )
61 .prop_map(
62 |(inputs, outputs, lock_time, joinsplit_data)| Transaction::V2 {
63 inputs,
64 outputs,
65 lock_time,
66 joinsplit_data,
67 },
68 )
69 .boxed()
70 }
71
72 pub fn v3_strategy(ledger_state: LedgerState) -> BoxedStrategy<Self> {
74 (
75 transparent::Input::vec_strategy(&ledger_state, MAX_ARBITRARY_ITEMS),
76 vec(any::<transparent::Output>(), 0..MAX_ARBITRARY_ITEMS),
77 any::<LockTime>(),
78 any::<block::Height>(),
79 option::of(any::<JoinSplitData<Bctv14Proof>>()),
80 )
81 .prop_map(
82 |(inputs, outputs, lock_time, expiry_height, joinsplit_data)| Transaction::V3 {
83 inputs,
84 outputs,
85 lock_time,
86 expiry_height,
87 joinsplit_data,
88 },
89 )
90 .boxed()
91 }
92
93 pub fn v4_strategy(ledger_state: LedgerState) -> BoxedStrategy<Self> {
95 (
96 transparent::Input::vec_strategy(&ledger_state, MAX_ARBITRARY_ITEMS),
97 vec(any::<transparent::Output>(), 0..MAX_ARBITRARY_ITEMS),
98 any::<LockTime>(),
99 any::<block::Height>(),
100 option::of(any::<JoinSplitData<Groth16Proof>>()),
101 option::of(any::<sapling::ShieldedData<sapling::PerSpendAnchor>>()),
102 )
103 .prop_map(
104 move |(
105 inputs,
106 outputs,
107 lock_time,
108 expiry_height,
109 joinsplit_data,
110 sapling_shielded_data,
111 )| {
112 Transaction::V4 {
113 inputs,
114 outputs,
115 lock_time,
116 expiry_height,
117 joinsplit_data: if ledger_state.height.is_min() {
118 None
120 } else {
121 joinsplit_data
122 },
123 sapling_shielded_data: if ledger_state.height.is_min() {
124 None
126 } else {
127 sapling_shielded_data
128 },
129 }
130 },
131 )
132 .boxed()
133 }
134
135 pub fn v5_strategy(ledger_state: LedgerState) -> BoxedStrategy<Self> {
137 (
138 NetworkUpgrade::nu5_branch_id_strategy(),
139 any::<LockTime>(),
140 any::<block::Height>(),
141 transparent::Input::vec_strategy(&ledger_state, MAX_ARBITRARY_ITEMS),
142 vec(any::<transparent::Output>(), 0..MAX_ARBITRARY_ITEMS),
143 option::of(any::<sapling::ShieldedData<sapling::SharedAnchor>>()),
144 option::of(any::<orchard::ShieldedData>()),
145 )
146 .prop_map(
147 move |(
148 network_upgrade,
149 lock_time,
150 expiry_height,
151 inputs,
152 outputs,
153 sapling_shielded_data,
154 orchard_shielded_data,
155 )| {
156 Transaction::V5 {
157 network_upgrade: if ledger_state.transaction_has_valid_network_upgrade() {
158 ledger_state.network_upgrade()
159 } else {
160 network_upgrade
161 },
162 lock_time,
163 expiry_height,
164 inputs,
165 outputs,
166 sapling_shielded_data: if ledger_state.height.is_min() {
167 None
169 } else {
170 sapling_shielded_data
171 },
172 orchard_shielded_data: if ledger_state.height.is_min() {
173 None
175 } else {
176 orchard_shielded_data
177 },
178 }
179 },
180 )
181 .boxed()
182 }
183
184 pub fn vec_strategy(
187 mut ledger_state: LedgerState,
188 len: usize,
189 ) -> BoxedStrategy<Vec<Arc<Self>>> {
190 let coinbase = Transaction::arbitrary_with(ledger_state.clone()).prop_map(Arc::new);
192 ledger_state.has_coinbase = false;
193 let remainder = vec(
194 Transaction::arbitrary_with(ledger_state).prop_map(Arc::new),
195 0..=len,
196 );
197
198 (coinbase, remainder)
199 .prop_map(|(first, mut remainder)| {
200 remainder.insert(0, first);
201 remainder
202 })
203 .boxed()
204 }
205
206 pub fn for_each_value_mut<F>(&mut self, mut f: F)
209 where
210 F: FnMut(&mut Amount<NonNegative>),
211 {
212 for output_value in self.output_values_mut() {
213 f(output_value);
214 }
215
216 for sprout_added_value in self.output_values_to_sprout_mut() {
217 f(sprout_added_value);
218 }
219 for sprout_removed_value in self.input_values_from_sprout_mut() {
220 f(sprout_removed_value);
221 }
222 }
223
224 pub fn for_each_value_balance_mut<F>(&mut self, mut f: F)
227 where
228 F: FnMut(&mut Amount<NegativeAllowed>),
229 {
230 if let Some(sapling_value_balance) = self.sapling_value_balance_mut() {
231 f(sapling_value_balance);
232 }
233
234 if let Some(orchard_value_balance) = self.orchard_value_balance_mut() {
235 f(orchard_value_balance);
236 }
237 }
238
239 pub fn fix_overflow(&mut self) {
246 fn scale_to_avoid_overflow<C: amount::Constraint>(amount: &mut Amount<C>)
247 where
248 Amount<C>: Copy,
249 {
250 const POOL_COUNT: u64 = 4;
251
252 let max_arbitrary_items: u64 = MAX_ARBITRARY_ITEMS.try_into().unwrap();
253 let max_partial_chain_blocks: u64 = MAX_PARTIAL_CHAIN_BLOCKS.try_into().unwrap();
254
255 let transaction_pool_scaling_divisor =
257 max_arbitrary_items * POOL_COUNT * max_arbitrary_items;
258 let chain_pool_scaling_divisor =
260 max_arbitrary_items * max_arbitrary_items * max_partial_chain_blocks;
261 let scaling_divisor = max(transaction_pool_scaling_divisor, chain_pool_scaling_divisor);
262
263 *amount = (*amount / scaling_divisor).expect("divisor is not zero");
264 }
265
266 self.for_each_value_mut(scale_to_avoid_overflow);
267 self.for_each_value_balance_mut(scale_to_avoid_overflow);
268 }
269
270 pub fn fix_chain_value_pools(
301 &mut self,
302 chain_value_pools: ValueBalance<NonNegative>,
303 outputs: &HashMap<transparent::OutPoint, transparent::Output>,
304 ) -> Result<(Amount<NonNegative>, ValueBalance<NonNegative>), ValueBalanceError> {
305 self.fix_overflow();
306
307 let mut input_chain_value_pools = chain_value_pools;
310
311 for input in self.inputs() {
312 input_chain_value_pools = input_chain_value_pools
313 .add_transparent_input(input, outputs)
314 .expect("find_valid_utxo_for_spend only spends unspent transparent outputs");
315 }
316
317 for input in self.input_values_from_sprout_mut() {
323 match input_chain_value_pools
324 .add_chain_value_pool_change(ValueBalance::from_sprout_amount(input.neg()))
325 {
326 Ok(new_chain_pools) => input_chain_value_pools = new_chain_pools,
327 Err(_) => *input = Amount::zero(),
329 }
330 }
331
332 let sapling_input = self.sapling_value_balance().constrain::<NonNegative>();
335 if let Ok(sapling_input) = sapling_input {
336 match input_chain_value_pools.add_chain_value_pool_change(-sapling_input) {
337 Ok(new_chain_pools) => input_chain_value_pools = new_chain_pools,
338 Err(_) => *self.sapling_value_balance_mut().unwrap() = Amount::zero(),
339 }
340 }
341
342 let orchard_input = self.orchard_value_balance().constrain::<NonNegative>();
343 if let Ok(orchard_input) = orchard_input {
344 match input_chain_value_pools.add_chain_value_pool_change(-orchard_input) {
345 Ok(new_chain_pools) => input_chain_value_pools = new_chain_pools,
346 Err(_) => *self.orchard_value_balance_mut().unwrap() = Amount::zero(),
347 }
348 }
349
350 let remaining_transaction_value = self.fix_remaining_value(outputs)?;
351
352 let transaction_chain_value_pool_change =
354 self
355 .value_balance_from_outputs(outputs)
356 .expect("chain value pool and remaining transaction value fixes produce valid transaction value balances")
357 .neg();
358
359 let chain_value_pools = chain_value_pools
360 .add_transaction(self, outputs)
361 .unwrap_or_else(|err| {
362 panic!(
363 "unexpected chain value pool error: {err:?}, \n\
364 original chain value pools: {chain_value_pools:?}, \n\
365 transaction chain value change: {transaction_chain_value_pool_change:?}, \n\
366 input-only transaction chain value pools: {input_chain_value_pools:?}, \n\
367 calculated remaining transaction value: {remaining_transaction_value:?}",
368 )
369 });
370
371 Ok((remaining_transaction_value, chain_value_pools))
372 }
373
374 fn input_value_pool(
381 &self,
382 outputs: &HashMap<transparent::OutPoint, transparent::Output>,
383 ) -> Result<Amount<NonNegative>, ValueBalanceError> {
384 let transparent_inputs = self
385 .inputs()
386 .iter()
387 .map(|input| input.value_from_outputs(outputs))
388 .sum::<Result<Amount<NonNegative>, amount::Error>>()
389 .map_err(ValueBalanceError::Transparent)?;
390 let sprout_inputs = self
396 .input_values_from_sprout()
397 .sum::<Result<Amount<NonNegative>, amount::Error>>()
398 .expect("chain is limited to MAX_MONEY");
399
400 let sapling_input = self
402 .sapling_value_balance()
403 .sapling_amount()
404 .constrain::<NonNegative>()
405 .unwrap_or_else(|_| Amount::zero());
406
407 let orchard_input = self
408 .orchard_value_balance()
409 .orchard_amount()
410 .constrain::<NonNegative>()
411 .unwrap_or_else(|_| Amount::zero());
412
413 let transaction_input_value_pool =
414 (transparent_inputs + sprout_inputs + sapling_input + orchard_input)
415 .expect("chain is limited to MAX_MONEY");
416
417 Ok(transaction_input_value_pool)
418 }
419
420 pub fn fix_remaining_value(
441 &mut self,
442 outputs: &HashMap<transparent::OutPoint, transparent::Output>,
443 ) -> Result<Amount<NonNegative>, ValueBalanceError> {
444 if self.is_coinbase() {
445 return Ok(Amount::zero());
453 }
454
455 let mut remaining_input_value = self.input_value_pool(outputs)?;
456
457 for output_value in self.output_values_mut() {
461 if remaining_input_value >= *output_value {
462 remaining_input_value = (remaining_input_value - *output_value)
463 .expect("input >= output so result is always non-negative");
464 } else {
465 *output_value = Amount::zero();
466 }
467 }
468
469 for output_value in self.output_values_to_sprout_mut() {
470 if remaining_input_value >= *output_value {
471 remaining_input_value = (remaining_input_value - *output_value)
472 .expect("input >= output so result is always non-negative");
473 } else {
474 *output_value = Amount::zero();
475 }
476 }
477
478 if let Some(value_balance) = self.sapling_value_balance_mut() {
479 if let Ok(output_value) = value_balance.neg().constrain::<NonNegative>() {
480 if remaining_input_value >= output_value {
481 remaining_input_value = (remaining_input_value - output_value)
482 .expect("input >= output so result is always non-negative");
483 } else {
484 *value_balance = Amount::zero();
485 }
486 }
487 }
488
489 if let Some(value_balance) = self.orchard_value_balance_mut() {
490 if let Ok(output_value) = value_balance.neg().constrain::<NonNegative>() {
491 if remaining_input_value >= output_value {
492 remaining_input_value = (remaining_input_value - output_value)
493 .expect("input >= output so result is always non-negative");
494 } else {
495 *value_balance = Amount::zero();
496 }
497 }
498 }
499
500 let remaining_transaction_value = self
502 .value_balance_from_outputs(outputs)
503 .expect("chain is limited to MAX_MONEY")
504 .remaining_transaction_value()
505 .unwrap_or_else(|err| {
506 panic!(
507 "unexpected remaining transaction value: {err:?}, \
508 calculated remaining input value: {remaining_input_value:?}"
509 )
510 });
511 assert_eq!(
512 remaining_input_value,
513 remaining_transaction_value,
514 "fix_remaining_value and remaining_transaction_value calculated different remaining values"
515 );
516
517 Ok(remaining_transaction_value)
518 }
519}
520
521impl Arbitrary for Memo {
522 type Parameters = ();
523
524 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
525 (vec(any::<u8>(), 512))
526 .prop_map(|v| {
527 let mut bytes = [0; 512];
528 bytes.copy_from_slice(v.as_slice());
529 Memo(Box::new(bytes))
530 })
531 .boxed()
532 }
533
534 type Strategy = BoxedStrategy<Self>;
535}
536
537impl Arbitrary for LockTime {
539 type Parameters = ();
540
541 fn arbitrary_with(_args: ()) -> Self::Strategy {
542 prop_oneof![
543 (block::Height::MIN.0..=LockTime::MAX_HEIGHT.0)
544 .prop_map(|n| LockTime::Height(block::Height(n))),
545 (LockTime::MIN_TIMESTAMP..=LockTime::MAX_TIMESTAMP).prop_map(|n| {
546 LockTime::Time(
547 Utc.timestamp_opt(n, 0)
548 .single()
549 .expect("in-range number of seconds and valid nanosecond"),
550 )
551 })
552 ]
553 .boxed()
554 }
555
556 type Strategy = BoxedStrategy<Self>;
557}
558
559impl<P: ZkSnarkProof + Arbitrary + 'static> Arbitrary for JoinSplitData<P> {
560 type Parameters = ();
561
562 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
563 (
564 any::<sprout::JoinSplit<P>>(),
565 vec(any::<sprout::JoinSplit<P>>(), 0..MAX_ARBITRARY_ITEMS),
566 array::uniform32(any::<u8>()),
567 vec(any::<u8>(), 64),
568 )
569 .prop_map(|(first, rest, pub_key_bytes, sig_bytes)| Self {
570 first,
571 rest,
572 pub_key: ed25519_zebra::VerificationKeyBytes::from(pub_key_bytes),
573 sig: ed25519_zebra::Signature::from({
574 let mut b = [0u8; 64];
575 b.copy_from_slice(sig_bytes.as_slice());
576 b
577 }),
578 })
579 .boxed()
580 }
581
582 type Strategy = BoxedStrategy<Self>;
583}
584
585impl<AnchorV> Arbitrary for sapling::ShieldedData<AnchorV>
586where
587 AnchorV: AnchorVariant + Clone + std::fmt::Debug + 'static,
588 sapling::TransferData<AnchorV>: Arbitrary,
589{
590 type Parameters = ();
591
592 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
593 (
594 any::<Amount>(),
595 any::<sapling::TransferData<AnchorV>>(),
596 vec(any::<u8>(), 64),
597 )
598 .prop_map(|(value_balance, transfers, sig_bytes)| Self {
599 value_balance,
600 transfers,
601 binding_sig: redjubjub::Signature::from({
602 let mut b = [0u8; 64];
603 b.copy_from_slice(sig_bytes.as_slice());
604 b
605 }),
606 })
607 .boxed()
608 }
609
610 type Strategy = BoxedStrategy<Self>;
611}
612
613impl Arbitrary for sapling::TransferData<PerSpendAnchor> {
614 type Parameters = ();
615
616 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
617 vec(any::<sapling::Output>(), 0..MAX_ARBITRARY_ITEMS)
618 .prop_flat_map(|outputs| {
619 (
620 if outputs.is_empty() {
621 vec(
623 any::<sapling::Spend<PerSpendAnchor>>(),
624 1..MAX_ARBITRARY_ITEMS,
625 )
626 } else {
627 vec(
628 any::<sapling::Spend<PerSpendAnchor>>(),
629 0..MAX_ARBITRARY_ITEMS,
630 )
631 },
632 Just(outputs),
633 )
634 })
635 .prop_map(|(spends, outputs)| {
636 if !spends.is_empty() {
637 sapling::TransferData::SpendsAndMaybeOutputs {
638 shared_anchor: FieldNotPresent,
639 spends: spends.try_into().unwrap(),
640 maybe_outputs: outputs,
641 }
642 } else if !outputs.is_empty() {
643 sapling::TransferData::JustOutputs {
644 outputs: outputs.try_into().unwrap(),
645 }
646 } else {
647 unreachable!("there must be at least one generated spend or output")
648 }
649 })
650 .boxed()
651 }
652
653 type Strategy = BoxedStrategy<Self>;
654}
655
656impl Arbitrary for sapling::TransferData<SharedAnchor> {
657 type Parameters = ();
658
659 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
660 vec(any::<sapling::Output>(), 0..MAX_ARBITRARY_ITEMS)
661 .prop_flat_map(|outputs| {
662 (
663 any::<sapling::tree::Root>(),
664 if outputs.is_empty() {
665 vec(
667 any::<sapling::Spend<SharedAnchor>>(),
668 1..MAX_ARBITRARY_ITEMS,
669 )
670 } else {
671 vec(
672 any::<sapling::Spend<SharedAnchor>>(),
673 0..MAX_ARBITRARY_ITEMS,
674 )
675 },
676 Just(outputs),
677 )
678 })
679 .prop_map(|(shared_anchor, spends, outputs)| {
680 if !spends.is_empty() {
681 sapling::TransferData::SpendsAndMaybeOutputs {
682 shared_anchor,
683 spends: spends.try_into().unwrap(),
684 maybe_outputs: outputs,
685 }
686 } else if !outputs.is_empty() {
687 sapling::TransferData::JustOutputs {
688 outputs: outputs.try_into().unwrap(),
689 }
690 } else {
691 unreachable!("there must be at least one generated spend or output")
692 }
693 })
694 .boxed()
695 }
696
697 type Strategy = BoxedStrategy<Self>;
698}
699
700impl Arbitrary for orchard::ShieldedData {
701 type Parameters = ();
702
703 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
704 (
705 any::<orchard::shielded_data::Flags>(),
706 any::<Amount>(),
707 any::<orchard::tree::Root>(),
708 vec(
709 any::<orchard::shielded_data::AuthorizedAction>(),
710 1..MAX_ARBITRARY_ITEMS,
711 ),
712 any::<BindingSignature>(),
713 )
714 .prop_flat_map(
715 |(flags, value_balance, shared_anchor, actions, binding_sig)| {
716 let proof_size = orchard::shielded_data::expected_proof_size(actions.len());
723 (
724 Just(flags),
725 Just(value_balance),
726 Just(shared_anchor),
727 vec(any::<u8>(), proof_size).prop_map(Halo2Proof),
728 Just(actions),
729 Just(binding_sig),
730 )
731 },
732 )
733 .prop_map(
734 |(flags, value_balance, shared_anchor, proof, actions, binding_sig)| Self {
735 flags,
736 value_balance,
737 shared_anchor,
738 proof,
739 actions: actions
740 .try_into()
741 .expect("arbitrary vector size range produces at least one action"),
742 binding_sig: binding_sig.0,
743 },
744 )
745 .boxed()
746 }
747
748 type Strategy = BoxedStrategy<Self>;
749}
750
751#[derive(Copy, Clone, Debug, Eq, PartialEq)]
752struct BindingSignature(pub(crate) Signature<Binding>);
753
754impl Arbitrary for BindingSignature {
755 type Parameters = ();
756
757 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
758 (vec(any::<u8>(), 64))
759 .prop_filter_map(
760 "zero Signature::<Binding> values are invalid",
761 |sig_bytes| {
762 let mut b = [0u8; 64];
763 b.copy_from_slice(sig_bytes.as_slice());
764 if b == [0u8; 64] {
765 return None;
766 }
767 Some(BindingSignature(Signature::<Binding>::from(b)))
768 },
769 )
770 .boxed()
771 }
772
773 type Strategy = BoxedStrategy<Self>;
774}
775
776impl Arbitrary for Transaction {
777 type Parameters = LedgerState;
778
779 fn arbitrary_with(ledger_state: Self::Parameters) -> Self::Strategy {
780 match ledger_state.transaction_version_override() {
781 Some(1) => return Self::v1_strategy(ledger_state),
782 Some(2) => return Self::v2_strategy(ledger_state),
783 Some(3) => return Self::v3_strategy(ledger_state),
784 Some(4) => return Self::v4_strategy(ledger_state),
785 Some(5) => return Self::v5_strategy(ledger_state),
786 Some(_) => unreachable!("invalid transaction version in override"),
787 None => {}
788 }
789
790 match ledger_state.network_upgrade() {
791 NetworkUpgrade::Genesis | NetworkUpgrade::BeforeOverwinter => {
792 Self::v1_strategy(ledger_state)
793 }
794 NetworkUpgrade::Overwinter => Self::v2_strategy(ledger_state),
795 NetworkUpgrade::Sapling => Self::v3_strategy(ledger_state),
796 NetworkUpgrade::Blossom | NetworkUpgrade::Heartwood | NetworkUpgrade::Canopy => {
797 Self::v4_strategy(ledger_state)
798 }
799 NetworkUpgrade::Nu5
800 | NetworkUpgrade::Nu6
801 | NetworkUpgrade::Nu6_1
802 | NetworkUpgrade::Nu6_2
803 | NetworkUpgrade::Nu7 => prop_oneof![
804 Self::v4_strategy(ledger_state.clone()),
805 Self::v5_strategy(ledger_state)
806 ]
807 .boxed(),
808
809 #[cfg(zcash_unstable = "zfuture")]
810 NetworkUpgrade::ZFuture => prop_oneof![
811 Self::v4_strategy(ledger_state.clone()),
812 Self::v5_strategy(ledger_state)
813 ]
814 .boxed(),
815 }
816 }
817
818 type Strategy = BoxedStrategy<Self>;
819}
820
821impl Arbitrary for UnminedTx {
822 type Parameters = ();
823
824 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
825 any::<Transaction>().prop_map_into().boxed()
826 }
827
828 type Strategy = BoxedStrategy<Self>;
829}
830
831impl Arbitrary for VerifiedUnminedTx {
832 type Parameters = ();
833
834 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
835 (
836 any::<UnminedTx>(),
837 any::<Amount<NonNegative>>(),
838 any::<u32>(),
839 any::<u32>(),
840 any::<(u16, u16)>().prop_map(|(unpaid_actions, conventional_actions)| {
841 (
842 unpaid_actions % conventional_actions.saturating_add(1),
843 conventional_actions,
844 )
845 }),
846 any::<f32>(),
847 serialization::arbitrary::datetime_u32(),
848 any::<block::Height>(),
849 )
850 .prop_map(
851 |(
852 transaction,
853 miner_fee,
854 sigops,
855 p2sh_sigops,
856 (conventional_actions, mut unpaid_actions),
857 fee_weight_ratio,
858 time,
859 height,
860 )| {
861 if unpaid_actions > conventional_actions {
862 unpaid_actions = conventional_actions;
863 }
864
865 let conventional_actions = conventional_actions as u32;
866 let unpaid_actions = unpaid_actions as u32;
867
868 Self {
869 transaction,
870 miner_fee,
871 legacy_sigop_count: sigops,
872 p2sh_sigop_count: p2sh_sigops,
873 conventional_actions,
874 unpaid_actions,
875 fee_weight_ratio,
876 time: Some(time),
877 height: Some(height),
878 spent_outputs: std::sync::Arc::new(vec![]),
879 }
880 },
881 )
882 .boxed()
883 }
884 type Strategy = BoxedStrategy<Self>;
885}
886
887pub fn transaction_to_fake_v5(
892 trans: &Transaction,
893 network: &Network,
894 height: block::Height,
895) -> Transaction {
896 use Transaction::*;
897
898 let block_nu = NetworkUpgrade::current(network, height);
899
900 match trans {
901 V1 {
902 inputs,
903 outputs,
904 lock_time,
905 } => V5 {
906 network_upgrade: block_nu,
907 inputs: inputs.to_vec(),
908 outputs: outputs.to_vec(),
909 lock_time: *lock_time,
910 expiry_height: height,
911 sapling_shielded_data: None,
912 orchard_shielded_data: None,
913 },
914 V2 {
915 inputs,
916 outputs,
917 lock_time,
918 ..
919 } => V5 {
920 network_upgrade: block_nu,
921 inputs: inputs.to_vec(),
922 outputs: outputs.to_vec(),
923 lock_time: *lock_time,
924 expiry_height: height,
925 sapling_shielded_data: None,
926 orchard_shielded_data: None,
927 },
928 V3 {
929 inputs,
930 outputs,
931 lock_time,
932 ..
933 } => V5 {
934 network_upgrade: block_nu,
935 inputs: inputs.to_vec(),
936 outputs: outputs.to_vec(),
937 lock_time: *lock_time,
938 expiry_height: height,
939 sapling_shielded_data: None,
940 orchard_shielded_data: None,
941 },
942 V4 {
943 inputs,
944 outputs,
945 lock_time,
946 sapling_shielded_data,
947 ..
948 } => V5 {
949 network_upgrade: block_nu,
950 inputs: inputs.to_vec(),
951 outputs: outputs.to_vec(),
952 lock_time: *lock_time,
953 expiry_height: height,
954 sapling_shielded_data: sapling_shielded_data
955 .clone()
956 .and_then(sapling_shielded_v4_to_fake_v5),
957 orchard_shielded_data: None,
958 },
959 v5 @ V5 { .. } => v5.clone(),
960 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
961 v6 @ V6 { .. } => v6.clone(),
962 }
963}
964
965fn sapling_shielded_v4_to_fake_v5(
968 v4_shielded: sapling::ShieldedData<PerSpendAnchor>,
969) -> Option<sapling::ShieldedData<SharedAnchor>> {
970 use sapling::ShieldedData;
971 use sapling::TransferData::*;
972
973 let unique_anchors: Vec<_> = v4_shielded
974 .spends()
975 .map(|spend| spend.per_spend_anchor)
976 .unique()
977 .collect();
978
979 let fake_spends: Vec<_> = v4_shielded
980 .spends()
981 .cloned()
982 .map(sapling_spend_v4_to_fake_v5)
983 .collect();
984
985 let transfers = match v4_shielded.transfers {
986 SpendsAndMaybeOutputs { maybe_outputs, .. } => {
987 let shared_anchor = match unique_anchors.as_slice() {
988 [unique_anchor] => *unique_anchor,
989 _ => return None,
991 };
992
993 SpendsAndMaybeOutputs {
994 shared_anchor,
995 spends: fake_spends.try_into().unwrap(),
996 maybe_outputs,
997 }
998 }
999 JustOutputs { outputs } => JustOutputs { outputs },
1000 };
1001
1002 let fake_shielded_v5 = ShieldedData::<SharedAnchor> {
1003 value_balance: v4_shielded.value_balance,
1004 transfers,
1005 binding_sig: v4_shielded.binding_sig,
1006 };
1007
1008 Some(fake_shielded_v5)
1009}
1010
1011fn sapling_spend_v4_to_fake_v5(
1013 v4_spend: sapling::Spend<PerSpendAnchor>,
1014) -> sapling::Spend<SharedAnchor> {
1015 use sapling::Spend;
1016
1017 Spend::<SharedAnchor> {
1018 cv: v4_spend.cv,
1019 per_spend_anchor: FieldNotPresent,
1020 nullifier: v4_spend.nullifier,
1021 rk: v4_spend.rk,
1022 zkproof: v4_spend.zkproof,
1023 spend_auth_sig: v4_spend.spend_auth_sig,
1024 }
1025}
1026
1027pub fn test_transactions(
1029 network: &Network,
1030) -> impl DoubleEndedIterator<Item = (block::Height, Arc<Transaction>)> {
1031 let blocks = network.block_iter();
1032
1033 transactions_from_blocks(blocks)
1034}
1035
1036pub fn v5_transactions<'b>(
1038 blocks: impl DoubleEndedIterator<Item = (&'b u32, &'b &'static [u8])> + 'b,
1039) -> impl DoubleEndedIterator<Item = Transaction> + 'b {
1040 transactions_from_blocks(blocks).filter_map(|(_, tx)| match *tx {
1041 Transaction::V1 { .. }
1042 | Transaction::V2 { .. }
1043 | Transaction::V3 { .. }
1044 | Transaction::V4 { .. } => None,
1045 ref tx @ Transaction::V5 { .. } => Some(tx.clone()),
1046 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
1047 ref tx @ Transaction::V6 { .. } => Some(tx.clone()),
1048 })
1049}
1050
1051pub fn transactions_from_blocks<'a>(
1053 blocks: impl DoubleEndedIterator<Item = (&'a u32, &'a &'static [u8])> + 'a,
1054) -> impl DoubleEndedIterator<Item = (block::Height, Arc<Transaction>)> + 'a {
1055 blocks.flat_map(|(&block_height, &block_bytes)| {
1056 let block = block_bytes
1057 .zcash_deserialize_into::<block::Block>()
1058 .expect("block is structurally valid");
1059
1060 block
1061 .transactions
1062 .into_iter()
1063 .map(move |transaction| (block::Height(block_height), transaction))
1064 })
1065}
1066
1067pub fn insert_fake_orchard_shielded_data(
1080 transaction: &mut Transaction,
1081) -> &mut orchard::ShieldedData {
1082 let mut runner = TestRunner::default();
1084 let dummy_action = orchard::Action::arbitrary()
1085 .new_tree(&mut runner)
1086 .unwrap()
1087 .current();
1088
1089 let dummy_authorized_action = orchard::AuthorizedAction {
1091 action: dummy_action,
1092 spend_auth_sig: Signature::from([0u8; 64]),
1093 };
1094
1095 let dummy_shielded_data = orchard::ShieldedData {
1097 flags: orchard::Flags::empty(),
1098 value_balance: Amount::try_from(0).expect("invalid transaction amount"),
1099 shared_anchor: orchard::tree::Root::default(),
1100 proof: Halo2Proof(vec![]),
1101 actions: at_least_one![dummy_authorized_action],
1102 binding_sig: Signature::from([0u8; 64]),
1103 };
1104
1105 match transaction {
1107 Transaction::V5 {
1108 orchard_shielded_data,
1109 ..
1110 } => {
1111 *orchard_shielded_data = Some(dummy_shielded_data);
1112
1113 orchard_shielded_data
1114 .as_mut()
1115 .expect("shielded data was just inserted")
1116 }
1117 _ => panic!("Fake V5 transaction is not V5"),
1118 }
1119}