1use std::{collections::HashMap, fmt, iter, sync::Arc};
4
5use halo2::pasta::pallas;
6
7mod auth_digest;
8mod hash;
9mod joinsplit;
10mod lock_time;
11mod memo;
12mod serialize;
13mod sighash;
14mod txid;
15mod unmined;
16
17#[cfg(any(test, feature = "proptest-impl"))]
18#[allow(clippy::unwrap_in_result)]
19pub mod arbitrary;
20#[cfg(test)]
21mod tests;
22
23pub use auth_digest::AuthDigest;
24pub use hash::{Hash, WtxId};
25pub use joinsplit::JoinSplitData;
26pub use lock_time::LockTime;
27pub use memo::Memo;
28use redjubjub::{Binding, Signature};
29pub use sapling::FieldNotPresent;
30pub use serialize::{
31 SerializedTransaction, MIN_TRANSPARENT_TX_SIZE, MIN_TRANSPARENT_TX_V4_SIZE,
32 MIN_TRANSPARENT_TX_V5_SIZE,
33};
34pub use sighash::{HashType, SigHash, SigHasher};
35pub use unmined::{
36 zip317, UnminedTx, UnminedTxId, VerifiedUnminedTx, MEMPOOL_TRANSACTION_COST_THRESHOLD,
37};
38use zcash_protocol::consensus;
39
40#[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
41use crate::parameters::TX_V6_VERSION_GROUP_ID;
42use crate::{
43 amount::{Amount, Error as AmountError, NegativeAllowed, NonNegative},
44 block, orchard,
45 parameters::{
46 Network, NetworkUpgrade, OVERWINTER_VERSION_GROUP_ID, SAPLING_VERSION_GROUP_ID,
47 TX_V5_VERSION_GROUP_ID,
48 },
49 primitives::{ed25519, Bctv14Proof, Groth16Proof},
50 sapling,
51 serialization::ZcashSerialize,
52 sprout,
53 transparent::{
54 self, outputs_from_utxos,
55 CoinbaseSpendRestriction::{self, *},
56 },
57 value_balance::{ValueBalance, ValueBalanceError},
58 Error,
59};
60
61#[derive(Clone, Debug, PartialEq, Eq)]
73#[cfg_attr(
74 any(test, feature = "proptest-impl", feature = "elasticsearch"),
75 derive(Serialize)
76)]
77pub enum Transaction {
78 V1 {
80 inputs: Vec<transparent::Input>,
82 outputs: Vec<transparent::Output>,
84 lock_time: LockTime,
87 },
88 V2 {
90 inputs: Vec<transparent::Input>,
92 outputs: Vec<transparent::Output>,
94 lock_time: LockTime,
97 joinsplit_data: Option<JoinSplitData<Bctv14Proof>>,
99 },
100 V3 {
102 inputs: Vec<transparent::Input>,
104 outputs: Vec<transparent::Output>,
106 lock_time: LockTime,
109 expiry_height: block::Height,
111 joinsplit_data: Option<JoinSplitData<Bctv14Proof>>,
113 },
114 V4 {
116 inputs: Vec<transparent::Input>,
118 outputs: Vec<transparent::Output>,
120 lock_time: LockTime,
123 expiry_height: block::Height,
125 joinsplit_data: Option<JoinSplitData<Groth16Proof>>,
127 sapling_shielded_data: Option<sapling::ShieldedData<sapling::PerSpendAnchor>>,
129 },
130 V5 {
132 network_upgrade: NetworkUpgrade,
136 lock_time: LockTime,
139 expiry_height: block::Height,
141 inputs: Vec<transparent::Input>,
143 outputs: Vec<transparent::Output>,
145 sapling_shielded_data: Option<sapling::ShieldedData<sapling::SharedAnchor>>,
147 orchard_shielded_data: Option<orchard::ShieldedData>,
149 },
150 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
152 V6 {
153 network_upgrade: NetworkUpgrade,
157 lock_time: LockTime,
160 expiry_height: block::Height,
162 zip233_amount: Amount<NonNegative>,
164 inputs: Vec<transparent::Input>,
166 outputs: Vec<transparent::Output>,
168 sapling_shielded_data: Option<sapling::ShieldedData<sapling::SharedAnchor>>,
170 orchard_shielded_data: Option<orchard::ShieldedData>,
172 },
173}
174
175impl fmt::Display for Transaction {
176 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
177 let mut fmter = f.debug_struct("Transaction");
178
179 fmter.field("version", &self.version());
180
181 if let Some(network_upgrade) = self.network_upgrade() {
182 fmter.field("network_upgrade", &network_upgrade);
183 }
184
185 if let Some(lock_time) = self.lock_time() {
186 fmter.field("lock_time", &lock_time);
187 }
188
189 if let Some(expiry_height) = self.expiry_height() {
190 fmter.field("expiry_height", &expiry_height);
191 }
192 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
193 fmter.field("zip233_amount", &self.zip233_amount());
194
195 fmter.field("transparent_inputs", &self.inputs().len());
196 fmter.field("transparent_outputs", &self.outputs().len());
197 fmter.field("sprout_joinsplits", &self.joinsplit_count());
198 fmter.field("sapling_spends", &self.sapling_spends_per_anchor().count());
199 fmter.field("sapling_outputs", &self.sapling_outputs().count());
200 fmter.field("orchard_actions", &self.orchard_actions().count());
201
202 fmter.field("unmined_id", &self.unmined_id());
203
204 fmter.finish()
205 }
206}
207
208impl Transaction {
209 pub fn hash(&self) -> Hash {
216 Hash::from(self)
217 }
218
219 pub fn unmined_id(&self) -> UnminedTxId {
224 UnminedTxId::from(self)
225 }
226
227 pub fn sighash(
253 &self,
254 nu: NetworkUpgrade,
255 hash_type: sighash::HashType,
256 all_previous_outputs: Arc<Vec<transparent::Output>>,
257 input_index_script_code: Option<(usize, Vec<u8>)>,
258 ) -> Result<SigHash, Error> {
259 Ok(sighash::SigHasher::new(self, nu, all_previous_outputs)?
260 .sighash(hash_type, input_index_script_code))
261 }
262
263 pub fn sighasher(
265 &self,
266 nu: NetworkUpgrade,
267 all_previous_outputs: Arc<Vec<transparent::Output>>,
268 ) -> Result<sighash::SigHasher, Error> {
269 sighash::SigHasher::new(self, nu, all_previous_outputs)
270 }
271
272 pub fn auth_digest(&self) -> Option<AuthDigest> {
279 match self {
280 Transaction::V1 { .. }
281 | Transaction::V2 { .. }
282 | Transaction::V3 { .. }
283 | Transaction::V4 { .. } => None,
284 Transaction::V5 { .. } => Some(AuthDigest::from(self)),
285 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
286 Transaction::V6 { .. } => Some(AuthDigest::from(self)),
287 }
288 }
289
290 pub fn has_transparent_inputs(&self) -> bool {
294 !self.inputs().is_empty()
295 }
296
297 pub fn has_transparent_outputs(&self) -> bool {
299 !self.outputs().is_empty()
300 }
301
302 pub fn has_transparent_inputs_or_outputs(&self) -> bool {
304 self.has_transparent_inputs() || self.has_transparent_outputs()
305 }
306
307 pub fn has_transparent_or_shielded_inputs(&self) -> bool {
309 self.has_transparent_inputs() || self.has_shielded_inputs()
310 }
311
312 pub fn has_shielded_inputs(&self) -> bool {
316 self.joinsplit_count() > 0
317 || self.sapling_spends_per_anchor().count() > 0
318 || (self.orchard_actions().count() > 0
319 && self
320 .orchard_flags()
321 .unwrap_or_else(orchard::Flags::empty)
322 .contains(orchard::Flags::ENABLE_SPENDS))
323 }
324
325 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
327 pub fn has_zip233_amount(&self) -> bool {
328 self.zip233_amount() > Amount::<NonNegative>::zero()
329 }
330 pub fn has_shielded_outputs(&self) -> bool {
334 self.joinsplit_count() > 0
335 || self.sapling_outputs().count() > 0
336 || (self.orchard_actions().count() > 0
337 && self
338 .orchard_flags()
339 .unwrap_or_else(orchard::Flags::empty)
340 .contains(orchard::Flags::ENABLE_OUTPUTS))
341 }
342
343 pub fn has_transparent_or_shielded_outputs(&self) -> bool {
345 self.has_transparent_outputs() || self.has_shielded_outputs()
346 }
347
348 pub fn has_enough_orchard_flags(&self) -> bool {
350 if self.version() < 5 || self.orchard_actions().count() == 0 {
351 return true;
352 }
353 self.orchard_flags()
354 .unwrap_or_else(orchard::Flags::empty)
355 .intersects(orchard::Flags::ENABLE_SPENDS | orchard::Flags::ENABLE_OUTPUTS)
356 }
357
358 pub fn coinbase_spend_restriction(
361 &self,
362 network: &Network,
363 spend_height: block::Height,
364 ) -> CoinbaseSpendRestriction {
365 if self.outputs().is_empty() || network.should_allow_unshielded_coinbase_spends() {
366 CheckCoinbaseMaturity { spend_height }
369 } else {
370 DisallowCoinbaseSpend
371 }
372 }
373
374 pub fn is_overwintered(&self) -> bool {
378 match self {
379 Transaction::V1 { .. } | Transaction::V2 { .. } => false,
380 Transaction::V3 { .. } | Transaction::V4 { .. } | Transaction::V5 { .. } => true,
381 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
382 Transaction::V6 { .. } => true,
383 }
384 }
385
386 pub fn version(&self) -> u32 {
398 match self {
399 Transaction::V1 { .. } => 1,
400 Transaction::V2 { .. } => 2,
401 Transaction::V3 { .. } => 3,
402 Transaction::V4 { .. } => 4,
403 Transaction::V5 { .. } => 5,
404 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
405 Transaction::V6 { .. } => 6,
406 }
407 }
408
409 pub fn lock_time(&self) -> Option<LockTime> {
411 let lock_time = match self {
412 Transaction::V1 { lock_time, .. }
413 | Transaction::V2 { lock_time, .. }
414 | Transaction::V3 { lock_time, .. }
415 | Transaction::V4 { lock_time, .. }
416 | Transaction::V5 { lock_time, .. } => *lock_time,
417 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
418 Transaction::V6 { lock_time, .. } => *lock_time,
419 };
420
421 if lock_time == LockTime::unlocked() {
428 return None;
429 }
430
431 let has_sequence_number_enabling_lock_time = self
446 .inputs()
447 .iter()
448 .map(transparent::Input::sequence)
449 .any(|sequence_number| sequence_number != u32::MAX);
450
451 if has_sequence_number_enabling_lock_time {
452 Some(lock_time)
453 } else {
454 None
455 }
456 }
457
458 pub fn raw_lock_time(&self) -> u32 {
460 let lock_time = match self {
461 Transaction::V1 { lock_time, .. }
462 | Transaction::V2 { lock_time, .. }
463 | Transaction::V3 { lock_time, .. }
464 | Transaction::V4 { lock_time, .. }
465 | Transaction::V5 { lock_time, .. } => *lock_time,
466 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
467 Transaction::V6 { lock_time, .. } => *lock_time,
468 };
469 let mut lock_time_bytes = Vec::new();
470 lock_time
471 .zcash_serialize(&mut lock_time_bytes)
472 .expect("lock_time should serialize");
473 u32::from_le_bytes(
474 lock_time_bytes
475 .try_into()
476 .expect("should serialize as 4 bytes"),
477 )
478 }
479
480 pub fn lock_time_is_time(&self) -> bool {
484 if let Some(lock_time) = self.lock_time() {
485 return lock_time.is_time();
486 }
487
488 false
489 }
490
491 pub fn expiry_height(&self) -> Option<block::Height> {
493 match self {
494 Transaction::V1 { .. } | Transaction::V2 { .. } => None,
495 Transaction::V3 { expiry_height, .. }
496 | Transaction::V4 { expiry_height, .. }
497 | Transaction::V5 { expiry_height, .. } => match expiry_height {
498 block::Height(0) => None,
502 block::Height(expiry_height) => Some(block::Height(*expiry_height)),
503 },
504 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
505 Transaction::V6 { expiry_height, .. } => match expiry_height {
506 block::Height(0) => None,
511 block::Height(expiry_height) => Some(block::Height(*expiry_height)),
512 },
513 }
514 }
515
516 pub fn network_upgrade(&self) -> Option<NetworkUpgrade> {
521 match self {
522 Transaction::V1 { .. }
523 | Transaction::V2 { .. }
524 | Transaction::V3 { .. }
525 | Transaction::V4 { .. } => None,
526 Transaction::V5 {
527 network_upgrade, ..
528 } => Some(*network_upgrade),
529 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
530 Transaction::V6 {
531 network_upgrade, ..
532 } => Some(*network_upgrade),
533 }
534 }
535
536 pub fn inputs(&self) -> &[transparent::Input] {
540 match self {
541 Transaction::V1 { ref inputs, .. } => inputs,
542 Transaction::V2 { ref inputs, .. } => inputs,
543 Transaction::V3 { ref inputs, .. } => inputs,
544 Transaction::V4 { ref inputs, .. } => inputs,
545 Transaction::V5 { ref inputs, .. } => inputs,
546 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
547 Transaction::V6 { ref inputs, .. } => inputs,
548 }
549 }
550
551 pub fn spent_outpoints(&self) -> impl Iterator<Item = transparent::OutPoint> + '_ {
553 self.inputs()
554 .iter()
555 .filter_map(transparent::Input::outpoint)
556 }
557
558 pub fn outputs(&self) -> &[transparent::Output] {
560 match self {
561 Transaction::V1 { ref outputs, .. } => outputs,
562 Transaction::V2 { ref outputs, .. } => outputs,
563 Transaction::V3 { ref outputs, .. } => outputs,
564 Transaction::V4 { ref outputs, .. } => outputs,
565 Transaction::V5 { ref outputs, .. } => outputs,
566 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
567 Transaction::V6 { ref outputs, .. } => outputs,
568 }
569 }
570
571 pub fn is_coinbase(&self) -> bool {
575 self.inputs().len() == 1
576 && matches!(
577 self.inputs().first(),
578 Some(transparent::Input::Coinbase { .. })
579 )
580 }
581
582 pub fn is_valid_non_coinbase(&self) -> bool {
589 self.inputs()
590 .iter()
591 .all(|input| matches!(input, transparent::Input::PrevOut { .. }))
592 }
593
594 pub fn sprout_groth16_joinsplits(
598 &self,
599 ) -> Box<dyn Iterator<Item = &sprout::JoinSplit<Groth16Proof>> + '_> {
600 match self {
601 Transaction::V4 {
603 joinsplit_data: Some(joinsplit_data),
604 ..
605 } => Box::new(joinsplit_data.joinsplits()),
606
607 Transaction::V1 { .. }
609 | Transaction::V2 { .. }
610 | Transaction::V3 { .. }
611 | Transaction::V4 {
612 joinsplit_data: None,
613 ..
614 }
615 | Transaction::V5 { .. } => Box::new(std::iter::empty()),
616 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
617 Transaction::V6 { .. } => Box::new(std::iter::empty()),
618 }
619 }
620
621 pub fn sprout_joinsplits(&self) -> Box<dyn Iterator<Item = sprout::GenericJoinSplit> + '_> {
623 match self {
624 Transaction::V2 {
626 joinsplit_data: Some(joinsplit_data),
627 ..
628 }
629 | Transaction::V3 {
630 joinsplit_data: Some(joinsplit_data),
631 ..
632 } => Box::new(joinsplit_data.joinsplits().map(|js| js.clone().into())),
633 Transaction::V4 {
635 joinsplit_data: Some(joinsplit_data),
636 ..
637 } => Box::new(joinsplit_data.joinsplits().map(|js| js.clone().into())),
638 Transaction::V1 { .. }
640 | Transaction::V2 {
641 joinsplit_data: None,
642 ..
643 }
644 | Transaction::V3 {
645 joinsplit_data: None,
646 ..
647 }
648 | Transaction::V4 {
649 joinsplit_data: None,
650 ..
651 }
652 | Transaction::V5 { .. } => Box::new(std::iter::empty()),
653 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
654 Transaction::V6 { .. } => Box::new(std::iter::empty()),
655 }
656 }
657
658 pub fn joinsplit_count(&self) -> usize {
660 match self {
661 Transaction::V2 {
663 joinsplit_data: Some(joinsplit_data),
664 ..
665 }
666 | Transaction::V3 {
667 joinsplit_data: Some(joinsplit_data),
668 ..
669 } => joinsplit_data.joinsplits().count(),
670 Transaction::V4 {
672 joinsplit_data: Some(joinsplit_data),
673 ..
674 } => joinsplit_data.joinsplits().count(),
675 Transaction::V1 { .. }
677 | Transaction::V2 {
678 joinsplit_data: None,
679 ..
680 }
681 | Transaction::V3 {
682 joinsplit_data: None,
683 ..
684 }
685 | Transaction::V4 {
686 joinsplit_data: None,
687 ..
688 }
689 | Transaction::V5 { .. } => 0,
690 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
691 Transaction::V6 { .. } => 0,
692 }
693 }
694
695 pub fn sprout_nullifiers(&self) -> Box<dyn Iterator<Item = &sprout::Nullifier> + '_> {
697 match self {
702 Transaction::V2 {
704 joinsplit_data: Some(joinsplit_data),
705 ..
706 }
707 | Transaction::V3 {
708 joinsplit_data: Some(joinsplit_data),
709 ..
710 } => Box::new(joinsplit_data.nullifiers()),
711 Transaction::V4 {
713 joinsplit_data: Some(joinsplit_data),
714 ..
715 } => Box::new(joinsplit_data.nullifiers()),
716 Transaction::V1 { .. }
718 | Transaction::V2 {
719 joinsplit_data: None,
720 ..
721 }
722 | Transaction::V3 {
723 joinsplit_data: None,
724 ..
725 }
726 | Transaction::V4 {
727 joinsplit_data: None,
728 ..
729 }
730 | Transaction::V5 { .. } => Box::new(std::iter::empty()),
731 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
732 Transaction::V6 { .. } => Box::new(std::iter::empty()),
733 }
734 }
735
736 pub fn sprout_joinsplit_pub_key(&self) -> Option<ed25519::VerificationKeyBytes> {
739 match self {
740 Transaction::V2 {
742 joinsplit_data: Some(joinsplit_data),
743 ..
744 }
745 | Transaction::V3 {
746 joinsplit_data: Some(joinsplit_data),
747 ..
748 } => Some(joinsplit_data.pub_key),
749 Transaction::V4 {
751 joinsplit_data: Some(joinsplit_data),
752 ..
753 } => Some(joinsplit_data.pub_key),
754 Transaction::V1 { .. }
756 | Transaction::V2 {
757 joinsplit_data: None,
758 ..
759 }
760 | Transaction::V3 {
761 joinsplit_data: None,
762 ..
763 }
764 | Transaction::V4 {
765 joinsplit_data: None,
766 ..
767 }
768 | Transaction::V5 { .. } => None,
769 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
770 Transaction::V6 { .. } => None,
771 }
772 }
773
774 pub fn has_sprout_joinsplit_data(&self) -> bool {
776 match self {
777 Transaction::V1 { .. } | Transaction::V5 { .. } => false,
779 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
780 Transaction::V6 { .. } => false,
781
782 Transaction::V2 { joinsplit_data, .. } | Transaction::V3 { joinsplit_data, .. } => {
784 joinsplit_data.is_some()
785 }
786
787 Transaction::V4 { joinsplit_data, .. } => joinsplit_data.is_some(),
789 }
790 }
791
792 pub fn sprout_note_commitments(
794 &self,
795 ) -> Box<dyn Iterator<Item = &sprout::commitment::NoteCommitment> + '_> {
796 match self {
797 Transaction::V2 {
799 joinsplit_data: Some(joinsplit_data),
800 ..
801 }
802 | Transaction::V3 {
803 joinsplit_data: Some(joinsplit_data),
804 ..
805 } => Box::new(joinsplit_data.note_commitments()),
806
807 Transaction::V4 {
809 joinsplit_data: Some(joinsplit_data),
810 ..
811 } => Box::new(joinsplit_data.note_commitments()),
812
813 Transaction::V2 {
815 joinsplit_data: None,
816 ..
817 }
818 | Transaction::V3 {
819 joinsplit_data: None,
820 ..
821 }
822 | Transaction::V4 {
823 joinsplit_data: None,
824 ..
825 }
826 | Transaction::V1 { .. }
827 | Transaction::V5 { .. } => Box::new(std::iter::empty()),
828 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
829 Transaction::V6 { .. } => Box::new(std::iter::empty()),
830 }
831 }
832
833 pub fn sapling_anchors(&self) -> Box<dyn Iterator<Item = sapling::tree::Root> + '_> {
838 match self {
841 Transaction::V4 {
842 sapling_shielded_data: Some(sapling_shielded_data),
843 ..
844 } => Box::new(sapling_shielded_data.anchors()),
845
846 Transaction::V5 {
847 sapling_shielded_data: Some(sapling_shielded_data),
848 ..
849 } => Box::new(sapling_shielded_data.anchors()),
850
851 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
852 Transaction::V6 {
853 sapling_shielded_data: Some(sapling_shielded_data),
854 ..
855 } => Box::new(sapling_shielded_data.anchors()),
856
857 Transaction::V1 { .. }
859 | Transaction::V2 { .. }
860 | Transaction::V3 { .. }
861 | Transaction::V4 {
862 sapling_shielded_data: None,
863 ..
864 }
865 | Transaction::V5 {
866 sapling_shielded_data: None,
867 ..
868 } => Box::new(std::iter::empty()),
869 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
870 Transaction::V6 {
871 sapling_shielded_data: None,
872 ..
873 } => Box::new(std::iter::empty()),
874 }
875 }
876
877 pub fn sapling_spends_per_anchor(
888 &self,
889 ) -> Box<dyn Iterator<Item = sapling::Spend<sapling::PerSpendAnchor>> + '_> {
890 match self {
891 Transaction::V4 {
892 sapling_shielded_data: Some(sapling_shielded_data),
893 ..
894 } => Box::new(sapling_shielded_data.spends_per_anchor()),
895 Transaction::V5 {
896 sapling_shielded_data: Some(sapling_shielded_data),
897 ..
898 } => Box::new(sapling_shielded_data.spends_per_anchor()),
899 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
900 Transaction::V6 {
901 sapling_shielded_data: Some(sapling_shielded_data),
902 ..
903 } => Box::new(sapling_shielded_data.spends_per_anchor()),
904
905 Transaction::V1 { .. }
907 | Transaction::V2 { .. }
908 | Transaction::V3 { .. }
909 | Transaction::V4 {
910 sapling_shielded_data: None,
911 ..
912 }
913 | Transaction::V5 {
914 sapling_shielded_data: None,
915 ..
916 } => Box::new(std::iter::empty()),
917 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
918 Transaction::V6 {
919 sapling_shielded_data: None,
920 ..
921 } => Box::new(std::iter::empty()),
922 }
923 }
924
925 pub fn sapling_outputs(&self) -> Box<dyn Iterator<Item = &sapling::Output> + '_> {
928 match self {
929 Transaction::V4 {
930 sapling_shielded_data: Some(sapling_shielded_data),
931 ..
932 } => Box::new(sapling_shielded_data.outputs()),
933 Transaction::V5 {
934 sapling_shielded_data: Some(sapling_shielded_data),
935 ..
936 } => Box::new(sapling_shielded_data.outputs()),
937 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
938 Transaction::V6 {
939 sapling_shielded_data: Some(sapling_shielded_data),
940 ..
941 } => Box::new(sapling_shielded_data.outputs()),
942
943 Transaction::V1 { .. }
945 | Transaction::V2 { .. }
946 | Transaction::V3 { .. }
947 | Transaction::V4 {
948 sapling_shielded_data: None,
949 ..
950 }
951 | Transaction::V5 {
952 sapling_shielded_data: None,
953 ..
954 } => Box::new(std::iter::empty()),
955 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
956 Transaction::V6 {
957 sapling_shielded_data: None,
958 ..
959 } => Box::new(std::iter::empty()),
960 }
961 }
962
963 pub fn sapling_nullifiers(&self) -> Box<dyn Iterator<Item = &sapling::Nullifier> + '_> {
965 match self {
968 Transaction::V4 {
970 sapling_shielded_data: Some(sapling_shielded_data),
971 ..
972 } => Box::new(sapling_shielded_data.nullifiers()),
973 Transaction::V5 {
974 sapling_shielded_data: Some(sapling_shielded_data),
975 ..
976 } => Box::new(sapling_shielded_data.nullifiers()),
977 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
978 Transaction::V6 {
979 sapling_shielded_data: Some(sapling_shielded_data),
980 ..
981 } => Box::new(sapling_shielded_data.nullifiers()),
982
983 Transaction::V1 { .. }
985 | Transaction::V2 { .. }
986 | Transaction::V3 { .. }
987 | Transaction::V4 {
988 sapling_shielded_data: None,
989 ..
990 }
991 | Transaction::V5 {
992 sapling_shielded_data: None,
993 ..
994 } => Box::new(std::iter::empty()),
995 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
996 Transaction::V6 {
997 sapling_shielded_data: None,
998 ..
999 } => Box::new(std::iter::empty()),
1000 }
1001 }
1002
1003 pub fn sapling_note_commitments(
1005 &self,
1006 ) -> Box<dyn Iterator<Item = &sapling_crypto::note::ExtractedNoteCommitment> + '_> {
1007 match self {
1010 Transaction::V4 {
1012 sapling_shielded_data: Some(sapling_shielded_data),
1013 ..
1014 } => Box::new(sapling_shielded_data.note_commitments()),
1015 Transaction::V5 {
1016 sapling_shielded_data: Some(sapling_shielded_data),
1017 ..
1018 } => Box::new(sapling_shielded_data.note_commitments()),
1019 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
1020 Transaction::V6 {
1021 sapling_shielded_data: Some(sapling_shielded_data),
1022 ..
1023 } => Box::new(sapling_shielded_data.note_commitments()),
1024
1025 Transaction::V1 { .. }
1027 | Transaction::V2 { .. }
1028 | Transaction::V3 { .. }
1029 | Transaction::V4 {
1030 sapling_shielded_data: None,
1031 ..
1032 }
1033 | Transaction::V5 {
1034 sapling_shielded_data: None,
1035 ..
1036 } => Box::new(std::iter::empty()),
1037 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
1038 Transaction::V6 {
1039 sapling_shielded_data: None,
1040 ..
1041 } => Box::new(std::iter::empty()),
1042 }
1043 }
1044
1045 pub fn has_sapling_shielded_data(&self) -> bool {
1047 match self {
1048 Transaction::V1 { .. } | Transaction::V2 { .. } | Transaction::V3 { .. } => false,
1049 Transaction::V4 {
1050 sapling_shielded_data,
1051 ..
1052 } => sapling_shielded_data.is_some(),
1053 Transaction::V5 {
1054 sapling_shielded_data,
1055 ..
1056 } => sapling_shielded_data.is_some(),
1057 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
1058 Transaction::V6 {
1059 sapling_shielded_data,
1060 ..
1061 } => sapling_shielded_data.is_some(),
1062 }
1063 }
1064
1065 pub fn orchard_shielded_data(&self) -> Option<&orchard::ShieldedData> {
1070 match self {
1071 Transaction::V5 {
1073 orchard_shielded_data,
1074 ..
1075 } => orchard_shielded_data.as_ref(),
1076 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
1077 Transaction::V6 {
1078 orchard_shielded_data,
1079 ..
1080 } => orchard_shielded_data.as_ref(),
1081
1082 Transaction::V1 { .. }
1084 | Transaction::V2 { .. }
1085 | Transaction::V3 { .. }
1086 | Transaction::V4 { .. } => None,
1087 }
1088 }
1089
1090 pub fn orchard_actions(&self) -> impl Iterator<Item = &orchard::Action> {
1093 self.orchard_shielded_data()
1094 .into_iter()
1095 .flat_map(orchard::ShieldedData::actions)
1096 }
1097
1098 pub fn orchard_nullifiers(&self) -> impl Iterator<Item = &orchard::Nullifier> {
1101 self.orchard_shielded_data()
1102 .into_iter()
1103 .flat_map(orchard::ShieldedData::nullifiers)
1104 }
1105
1106 pub fn orchard_note_commitments(&self) -> impl Iterator<Item = &pallas::Base> {
1109 self.orchard_shielded_data()
1110 .into_iter()
1111 .flat_map(orchard::ShieldedData::note_commitments)
1112 }
1113
1114 pub fn orchard_flags(&self) -> Option<orchard::shielded_data::Flags> {
1117 self.orchard_shielded_data()
1118 .map(|orchard_shielded_data| orchard_shielded_data.flags)
1119 }
1120
1121 pub fn has_orchard_shielded_data(&self) -> bool {
1124 self.orchard_shielded_data().is_some()
1125 }
1126
1127 #[allow(clippy::unwrap_in_result)]
1134 fn transparent_value_balance_from_outputs(
1135 &self,
1136 outputs: &HashMap<transparent::OutPoint, transparent::Output>,
1137 ) -> Result<ValueBalance<NegativeAllowed>, ValueBalanceError> {
1138 let input_value = self
1139 .inputs()
1140 .iter()
1141 .map(|i| i.value_from_outputs(outputs))
1142 .sum::<Result<Amount<NonNegative>, AmountError>>()
1143 .map_err(ValueBalanceError::Transparent)?
1144 .constrain()
1145 .expect("conversion from NonNegative to NegativeAllowed is always valid");
1146
1147 let output_value = self
1148 .outputs()
1149 .iter()
1150 .map(|o| o.value())
1151 .sum::<Result<Amount<NonNegative>, AmountError>>()
1152 .map_err(ValueBalanceError::Transparent)?
1153 .constrain()
1154 .expect("conversion from NonNegative to NegativeAllowed is always valid");
1155
1156 (input_value - output_value)
1157 .map(ValueBalance::from_transparent_amount)
1158 .map_err(ValueBalanceError::Transparent)
1159 }
1160
1161 pub fn output_values_to_sprout(&self) -> Box<dyn Iterator<Item = &Amount<NonNegative>> + '_> {
1167 match self {
1168 Transaction::V2 {
1170 joinsplit_data: Some(joinsplit_data),
1171 ..
1172 }
1173 | Transaction::V3 {
1174 joinsplit_data: Some(joinsplit_data),
1175 ..
1176 } => Box::new(
1177 joinsplit_data
1178 .joinsplits()
1179 .map(|joinsplit| &joinsplit.vpub_old),
1180 ),
1181 Transaction::V4 {
1183 joinsplit_data: Some(joinsplit_data),
1184 ..
1185 } => Box::new(
1186 joinsplit_data
1187 .joinsplits()
1188 .map(|joinsplit| &joinsplit.vpub_old),
1189 ),
1190 Transaction::V1 { .. }
1192 | Transaction::V2 {
1193 joinsplit_data: None,
1194 ..
1195 }
1196 | Transaction::V3 {
1197 joinsplit_data: None,
1198 ..
1199 }
1200 | Transaction::V4 {
1201 joinsplit_data: None,
1202 ..
1203 }
1204 | Transaction::V5 { .. } => Box::new(std::iter::empty()),
1205 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
1206 Transaction::V6 { .. } => Box::new(std::iter::empty()),
1207 }
1208 }
1209
1210 pub fn input_values_from_sprout(&self) -> Box<dyn Iterator<Item = &Amount<NonNegative>> + '_> {
1216 match self {
1217 Transaction::V2 {
1219 joinsplit_data: Some(joinsplit_data),
1220 ..
1221 }
1222 | Transaction::V3 {
1223 joinsplit_data: Some(joinsplit_data),
1224 ..
1225 } => Box::new(
1226 joinsplit_data
1227 .joinsplits()
1228 .map(|joinsplit| &joinsplit.vpub_new),
1229 ),
1230 Transaction::V4 {
1232 joinsplit_data: Some(joinsplit_data),
1233 ..
1234 } => Box::new(
1235 joinsplit_data
1236 .joinsplits()
1237 .map(|joinsplit| &joinsplit.vpub_new),
1238 ),
1239 Transaction::V1 { .. }
1241 | Transaction::V2 {
1242 joinsplit_data: None,
1243 ..
1244 }
1245 | Transaction::V3 {
1246 joinsplit_data: None,
1247 ..
1248 }
1249 | Transaction::V4 {
1250 joinsplit_data: None,
1251 ..
1252 }
1253 | Transaction::V5 { .. } => Box::new(std::iter::empty()),
1254 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
1255 Transaction::V6 { .. } => Box::new(std::iter::empty()),
1256 }
1257 }
1258
1259 fn sprout_joinsplit_value_balances(
1268 &self,
1269 ) -> impl Iterator<Item = ValueBalance<NegativeAllowed>> + '_ {
1270 let joinsplit_value_balances = match self {
1271 Transaction::V2 {
1272 joinsplit_data: Some(joinsplit_data),
1273 ..
1274 }
1275 | Transaction::V3 {
1276 joinsplit_data: Some(joinsplit_data),
1277 ..
1278 } => joinsplit_data.joinsplit_value_balances(),
1279 Transaction::V4 {
1280 joinsplit_data: Some(joinsplit_data),
1281 ..
1282 } => joinsplit_data.joinsplit_value_balances(),
1283 Transaction::V1 { .. }
1284 | Transaction::V2 {
1285 joinsplit_data: None,
1286 ..
1287 }
1288 | Transaction::V3 {
1289 joinsplit_data: None,
1290 ..
1291 }
1292 | Transaction::V4 {
1293 joinsplit_data: None,
1294 ..
1295 }
1296 | Transaction::V5 { .. } => Box::new(iter::empty()),
1297 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
1298 Transaction::V6 { .. } => Box::new(iter::empty()),
1299 };
1300
1301 joinsplit_value_balances.map(ValueBalance::from_sprout_amount)
1302 }
1303
1304 fn sprout_value_balance(&self) -> Result<ValueBalance<NegativeAllowed>, ValueBalanceError> {
1316 self.sprout_joinsplit_value_balances().sum()
1317 }
1318
1319 pub fn sapling_value_balance(&self) -> ValueBalance<NegativeAllowed> {
1331 let sapling_value_balance = match self {
1332 Transaction::V4 {
1333 sapling_shielded_data: Some(sapling_shielded_data),
1334 ..
1335 } => sapling_shielded_data.value_balance,
1336 Transaction::V5 {
1337 sapling_shielded_data: Some(sapling_shielded_data),
1338 ..
1339 } => sapling_shielded_data.value_balance,
1340 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
1341 Transaction::V6 {
1342 sapling_shielded_data: Some(sapling_shielded_data),
1343 ..
1344 } => sapling_shielded_data.value_balance,
1345
1346 Transaction::V1 { .. }
1347 | Transaction::V2 { .. }
1348 | Transaction::V3 { .. }
1349 | Transaction::V4 {
1350 sapling_shielded_data: None,
1351 ..
1352 }
1353 | Transaction::V5 {
1354 sapling_shielded_data: None,
1355 ..
1356 } => Amount::zero(),
1357 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
1358 Transaction::V6 {
1359 sapling_shielded_data: None,
1360 ..
1361 } => Amount::zero(),
1362 };
1363
1364 ValueBalance::from_sapling_amount(sapling_value_balance)
1365 }
1366
1367 pub fn sapling_binding_sig(&self) -> Option<Signature<Binding>> {
1372 match self {
1373 Transaction::V4 {
1374 sapling_shielded_data: Some(sapling_shielded_data),
1375 ..
1376 } => Some(sapling_shielded_data.binding_sig),
1377 Transaction::V5 {
1378 sapling_shielded_data: Some(sapling_shielded_data),
1379 ..
1380 } => Some(sapling_shielded_data.binding_sig),
1381 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
1382 Transaction::V6 {
1383 sapling_shielded_data: Some(sapling_shielded_data),
1384 ..
1385 } => Some(sapling_shielded_data.binding_sig),
1386 _ => None,
1387 }
1388 }
1389
1390 pub fn joinsplit_pub_key(&self) -> Option<ed25519::VerificationKeyBytes> {
1398 match self {
1399 Transaction::V2 {
1400 joinsplit_data: Some(joinsplit_data),
1401 ..
1402 } => Some(joinsplit_data.pub_key),
1403 Transaction::V3 {
1404 joinsplit_data: Some(joinsplit_data),
1405 ..
1406 } => Some(joinsplit_data.pub_key),
1407 Transaction::V4 {
1408 joinsplit_data: Some(joinsplit_data),
1409 ..
1410 } => Some(joinsplit_data.pub_key),
1411 _ => None,
1412 }
1413 }
1414
1415 pub fn joinsplit_sig(&self) -> Option<ed25519::Signature> {
1423 match self {
1424 Transaction::V2 {
1425 joinsplit_data: Some(joinsplit_data),
1426 ..
1427 } => Some(joinsplit_data.sig),
1428 Transaction::V3 {
1429 joinsplit_data: Some(joinsplit_data),
1430 ..
1431 } => Some(joinsplit_data.sig),
1432 Transaction::V4 {
1433 joinsplit_data: Some(joinsplit_data),
1434 ..
1435 } => Some(joinsplit_data.sig),
1436 _ => None,
1437 }
1438 }
1439
1440 pub fn orchard_value_balance(&self) -> ValueBalance<NegativeAllowed> {
1452 let orchard_value_balance = self
1453 .orchard_shielded_data()
1454 .map(|shielded_data| shielded_data.value_balance)
1455 .unwrap_or_else(Amount::zero);
1456
1457 ValueBalance::from_orchard_amount(orchard_value_balance)
1458 }
1459
1460 pub(crate) fn value_balance_from_outputs(
1462 &self,
1463 outputs: &HashMap<transparent::OutPoint, transparent::Output>,
1464 ) -> Result<ValueBalance<NegativeAllowed>, ValueBalanceError> {
1465 self.transparent_value_balance_from_outputs(outputs)?
1466 + self.sprout_value_balance()?
1467 + self.sapling_value_balance()
1468 + self.orchard_value_balance()
1469 }
1470
1471 pub fn value_balance(
1492 &self,
1493 utxos: &HashMap<transparent::OutPoint, transparent::Utxo>,
1494 ) -> Result<ValueBalance<NegativeAllowed>, ValueBalanceError> {
1495 self.value_balance_from_outputs(&outputs_from_utxos(utxos.clone()))
1496 }
1497
1498 pub(crate) fn to_librustzcash(
1504 &self,
1505 nu: NetworkUpgrade,
1506 ) -> Result<zcash_primitives::transaction::Transaction, crate::Error> {
1507 if self.network_upgrade().is_some_and(|tx_nu| tx_nu != nu) {
1508 return Err(crate::Error::InvalidConsensusBranchId);
1509 }
1510
1511 let Some(branch_id) = nu.branch_id() else {
1512 return Err(crate::Error::InvalidConsensusBranchId);
1513 };
1514
1515 let Ok(branch_id) = consensus::BranchId::try_from(branch_id) else {
1516 return Err(crate::Error::InvalidConsensusBranchId);
1517 };
1518
1519 Ok(zcash_primitives::transaction::Transaction::read(
1520 &self.zcash_serialize_to_vec()?[..],
1521 branch_id,
1522 )?)
1523 }
1524
1525 pub fn has_shielded_data(&self) -> bool {
1529 self.has_shielded_inputs() || self.has_shielded_outputs()
1530 }
1531
1532 pub fn version_group_id(&self) -> Option<u32> {
1534 match self {
1538 Transaction::V1 { .. } | Transaction::V2 { .. } => None,
1539 Transaction::V3 { .. } => Some(OVERWINTER_VERSION_GROUP_ID),
1540 Transaction::V4 { .. } => Some(SAPLING_VERSION_GROUP_ID),
1541 Transaction::V5 { .. } => Some(TX_V5_VERSION_GROUP_ID),
1542 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
1543 Transaction::V6 { .. } => Some(TX_V6_VERSION_GROUP_ID),
1544 }
1545 }
1546
1547 pub fn zip233_amount(&self) -> Amount<NonNegative> {
1549 match self {
1550 Transaction::V1 { .. }
1551 | Transaction::V2 { .. }
1552 | Transaction::V3 { .. }
1553 | Transaction::V4 { .. }
1554 | Transaction::V5 { .. } => Amount::zero(),
1555 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
1556 Transaction::V6 { zip233_amount, .. } => *zip233_amount,
1557 }
1558 }
1559}
1560
1561#[cfg(any(test, feature = "proptest-impl"))]
1562impl Transaction {
1563 pub fn update_network_upgrade(&mut self, nu: NetworkUpgrade) -> Result<(), &str> {
1569 match self {
1570 Transaction::V1 { .. }
1571 | Transaction::V2 { .. }
1572 | Transaction::V3 { .. }
1573 | Transaction::V4 { .. } => Err(
1574 "Updating the network upgrade for V1, V2, V3 and V4 transactions is not possible.",
1575 ),
1576 Transaction::V5 {
1577 ref mut network_upgrade,
1578 ..
1579 } => {
1580 *network_upgrade = nu;
1581 Ok(())
1582 }
1583 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
1584 Transaction::V6 {
1585 ref mut network_upgrade,
1586 ..
1587 } => {
1588 *network_upgrade = nu;
1589 Ok(())
1590 }
1591 }
1592 }
1593
1594 pub fn expiry_height_mut(&mut self) -> &mut block::Height {
1600 match self {
1601 Transaction::V1 { .. } | Transaction::V2 { .. } => {
1602 panic!("v1 and v2 transactions are not supported")
1603 }
1604 Transaction::V3 {
1605 ref mut expiry_height,
1606 ..
1607 }
1608 | Transaction::V4 {
1609 ref mut expiry_height,
1610 ..
1611 }
1612 | Transaction::V5 {
1613 ref mut expiry_height,
1614 ..
1615 } => expiry_height,
1616 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
1617 Transaction::V6 {
1618 ref mut expiry_height,
1619 ..
1620 } => expiry_height,
1621 }
1622 }
1623
1624 pub fn inputs_mut(&mut self) -> &mut Vec<transparent::Input> {
1626 match self {
1627 Transaction::V1 { ref mut inputs, .. } => inputs,
1628 Transaction::V2 { ref mut inputs, .. } => inputs,
1629 Transaction::V3 { ref mut inputs, .. } => inputs,
1630 Transaction::V4 { ref mut inputs, .. } => inputs,
1631 Transaction::V5 { ref mut inputs, .. } => inputs,
1632 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
1633 Transaction::V6 { ref mut inputs, .. } => inputs,
1634 }
1635 }
1636
1637 pub fn orchard_value_balance_mut(&mut self) -> Option<&mut Amount<NegativeAllowed>> {
1642 self.orchard_shielded_data_mut()
1643 .map(|shielded_data| &mut shielded_data.value_balance)
1644 }
1645
1646 pub fn sapling_value_balance_mut(&mut self) -> Option<&mut Amount<NegativeAllowed>> {
1651 match self {
1652 Transaction::V4 {
1653 sapling_shielded_data: Some(sapling_shielded_data),
1654 ..
1655 } => Some(&mut sapling_shielded_data.value_balance),
1656 Transaction::V5 {
1657 sapling_shielded_data: Some(sapling_shielded_data),
1658 ..
1659 } => Some(&mut sapling_shielded_data.value_balance),
1660 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
1661 Transaction::V6 {
1662 sapling_shielded_data: Some(sapling_shielded_data),
1663 ..
1664 } => Some(&mut sapling_shielded_data.value_balance),
1665 Transaction::V1 { .. }
1666 | Transaction::V2 { .. }
1667 | Transaction::V3 { .. }
1668 | Transaction::V4 {
1669 sapling_shielded_data: None,
1670 ..
1671 }
1672 | Transaction::V5 {
1673 sapling_shielded_data: None,
1674 ..
1675 } => None,
1676 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
1677 Transaction::V6 {
1678 sapling_shielded_data: None,
1679 ..
1680 } => None,
1681 }
1682 }
1683
1684 pub fn input_values_from_sprout_mut(
1689 &mut self,
1690 ) -> Box<dyn Iterator<Item = &mut Amount<NonNegative>> + '_> {
1691 match self {
1692 Transaction::V2 {
1694 joinsplit_data: Some(joinsplit_data),
1695 ..
1696 }
1697 | Transaction::V3 {
1698 joinsplit_data: Some(joinsplit_data),
1699 ..
1700 } => Box::new(
1701 joinsplit_data
1702 .joinsplits_mut()
1703 .map(|joinsplit| &mut joinsplit.vpub_new),
1704 ),
1705 Transaction::V4 {
1707 joinsplit_data: Some(joinsplit_data),
1708 ..
1709 } => Box::new(
1710 joinsplit_data
1711 .joinsplits_mut()
1712 .map(|joinsplit| &mut joinsplit.vpub_new),
1713 ),
1714 Transaction::V1 { .. }
1716 | Transaction::V2 {
1717 joinsplit_data: None,
1718 ..
1719 }
1720 | Transaction::V3 {
1721 joinsplit_data: None,
1722 ..
1723 }
1724 | Transaction::V4 {
1725 joinsplit_data: None,
1726 ..
1727 }
1728 | Transaction::V5 { .. } => Box::new(std::iter::empty()),
1729 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
1730 Transaction::V6 { .. } => Box::new(std::iter::empty()),
1731 }
1732 }
1733
1734 pub fn output_values_to_sprout_mut(
1739 &mut self,
1740 ) -> Box<dyn Iterator<Item = &mut Amount<NonNegative>> + '_> {
1741 match self {
1742 Transaction::V2 {
1744 joinsplit_data: Some(joinsplit_data),
1745 ..
1746 }
1747 | Transaction::V3 {
1748 joinsplit_data: Some(joinsplit_data),
1749 ..
1750 } => Box::new(
1751 joinsplit_data
1752 .joinsplits_mut()
1753 .map(|joinsplit| &mut joinsplit.vpub_old),
1754 ),
1755 Transaction::V4 {
1757 joinsplit_data: Some(joinsplit_data),
1758 ..
1759 } => Box::new(
1760 joinsplit_data
1761 .joinsplits_mut()
1762 .map(|joinsplit| &mut joinsplit.vpub_old),
1763 ),
1764 Transaction::V1 { .. }
1766 | Transaction::V2 {
1767 joinsplit_data: None,
1768 ..
1769 }
1770 | Transaction::V3 {
1771 joinsplit_data: None,
1772 ..
1773 }
1774 | Transaction::V4 {
1775 joinsplit_data: None,
1776 ..
1777 }
1778 | Transaction::V5 { .. } => Box::new(std::iter::empty()),
1779 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
1780 Transaction::V6 { .. } => Box::new(std::iter::empty()),
1781 }
1782 }
1783
1784 pub fn output_values_mut(&mut self) -> impl Iterator<Item = &mut Amount<NonNegative>> {
1786 self.outputs_mut()
1787 .iter_mut()
1788 .map(|output| &mut output.value)
1789 }
1790
1791 pub fn orchard_shielded_data_mut(&mut self) -> Option<&mut orchard::ShieldedData> {
1794 match self {
1795 Transaction::V5 {
1796 orchard_shielded_data: Some(orchard_shielded_data),
1797 ..
1798 } => Some(orchard_shielded_data),
1799 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
1800 Transaction::V6 {
1801 orchard_shielded_data: Some(orchard_shielded_data),
1802 ..
1803 } => Some(orchard_shielded_data),
1804
1805 Transaction::V1 { .. }
1806 | Transaction::V2 { .. }
1807 | Transaction::V3 { .. }
1808 | Transaction::V4 { .. }
1809 | Transaction::V5 {
1810 orchard_shielded_data: None,
1811 ..
1812 } => None,
1813 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
1814 Transaction::V6 {
1815 orchard_shielded_data: None,
1816 ..
1817 } => None,
1818 }
1819 }
1820
1821 pub fn outputs_mut(&mut self) -> &mut Vec<transparent::Output> {
1823 match self {
1824 Transaction::V1 {
1825 ref mut outputs, ..
1826 } => outputs,
1827 Transaction::V2 {
1828 ref mut outputs, ..
1829 } => outputs,
1830 Transaction::V3 {
1831 ref mut outputs, ..
1832 } => outputs,
1833 Transaction::V4 {
1834 ref mut outputs, ..
1835 } => outputs,
1836 Transaction::V5 {
1837 ref mut outputs, ..
1838 } => outputs,
1839 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
1840 Transaction::V6 {
1841 ref mut outputs, ..
1842 } => outputs,
1843 }
1844 }
1845}