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
40use crate::parameters::TX_V6_VERSION_GROUP_ID;
41use crate::{
42 amount::{Amount, Error as AmountError, NegativeAllowed, NonNegative},
43 block, ironwood, orchard,
44 parameters::{
45 Network, NetworkUpgrade, OVERWINTER_VERSION_GROUP_ID, SAPLING_VERSION_GROUP_ID,
46 TX_V5_VERSION_GROUP_ID,
47 },
48 primitives::{ed25519, Bctv14Proof, Groth16Proof},
49 sapling,
50 serialization::ZcashSerialize,
51 sprout,
52 transparent::{
53 self,
54 CoinbaseSpendRestriction::{self, *},
55 },
56 value_balance::{ValueBalance, ValueBalanceError},
57 Error,
58};
59
60#[derive(Clone, Debug, PartialEq, Eq)]
72#[cfg_attr(
73 any(test, feature = "proptest-impl", feature = "elasticsearch"),
74 derive(Serialize)
75)]
76pub enum Transaction {
77 V1 {
79 inputs: Vec<transparent::Input>,
81 outputs: Vec<transparent::Output>,
83 lock_time: LockTime,
86 },
87 V2 {
89 inputs: Vec<transparent::Input>,
91 outputs: Vec<transparent::Output>,
93 lock_time: LockTime,
96 joinsplit_data: Option<JoinSplitData<Bctv14Proof>>,
98 },
99 V3 {
101 inputs: Vec<transparent::Input>,
103 outputs: Vec<transparent::Output>,
105 lock_time: LockTime,
108 expiry_height: block::Height,
110 joinsplit_data: Option<JoinSplitData<Bctv14Proof>>,
112 },
113 V4 {
115 inputs: Vec<transparent::Input>,
117 outputs: Vec<transparent::Output>,
119 lock_time: LockTime,
122 expiry_height: block::Height,
124 joinsplit_data: Option<JoinSplitData<Groth16Proof>>,
126 sapling_shielded_data: Option<sapling::ShieldedData<sapling::PerSpendAnchor>>,
128 },
129 V5 {
131 network_upgrade: NetworkUpgrade,
135 lock_time: LockTime,
138 expiry_height: block::Height,
140 inputs: Vec<transparent::Input>,
142 outputs: Vec<transparent::Output>,
144 sapling_shielded_data: Option<sapling::ShieldedData<sapling::SharedAnchor>>,
146 orchard_shielded_data: Option<orchard::ShieldedData>,
148 },
149 V6 {
151 network_upgrade: NetworkUpgrade,
155 lock_time: LockTime,
158 expiry_height: block::Height,
160 inputs: Vec<transparent::Input>,
162 outputs: Vec<transparent::Output>,
164 sapling_shielded_data: Option<sapling::ShieldedData<sapling::SharedAnchor>>,
166 orchard_shielded_data: Option<orchard::ShieldedDataV6>,
172 ironwood_shielded_data: Option<ironwood::ShieldedData>,
178 },
179}
180
181impl fmt::Display for Transaction {
182 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
183 let mut fmter = f.debug_struct("Transaction");
184
185 fmter.field("version", &self.version());
186
187 if let Some(network_upgrade) = self.network_upgrade() {
188 fmter.field("network_upgrade", &network_upgrade);
189 }
190
191 if let Some(lock_time) = self.lock_time() {
192 fmter.field("lock_time", &lock_time);
193 }
194
195 if let Some(expiry_height) = self.expiry_height() {
196 fmter.field("expiry_height", &expiry_height);
197 }
198
199 fmter.field("transparent_inputs", &self.inputs().len());
200 fmter.field("transparent_outputs", &self.outputs().len());
201 fmter.field("sprout_joinsplits", &self.joinsplit_count());
202 fmter.field("sapling_spends", &self.sapling_spends_per_anchor().count());
203 fmter.field("sapling_outputs", &self.sapling_outputs().count());
204 fmter.field("orchard_actions", &self.orchard_actions().count());
205 fmter.field("ironwood_actions", &self.ironwood_actions().count());
206
207 fmter.field("unmined_id", &self.unmined_id());
208
209 fmter.finish()
210 }
211}
212
213impl Transaction {
214 pub fn hash(&self) -> Hash {
221 Hash::from(self)
222 }
223
224 pub fn unmined_id(&self) -> UnminedTxId {
229 UnminedTxId::from(self)
230 }
231
232 pub fn sighash(
258 &self,
259 nu: NetworkUpgrade,
260 hash_type: sighash::HashType,
261 all_previous_outputs: Arc<Vec<transparent::Output>>,
262 input_index_script_code: Option<(usize, Vec<u8>)>,
263 ) -> Result<SigHash, Error> {
264 Ok(sighash::SigHasher::new(self, nu, all_previous_outputs)?
265 .sighash(hash_type, input_index_script_code))
266 }
267
268 pub fn sighasher(
270 &self,
271 nu: NetworkUpgrade,
272 all_previous_outputs: Arc<Vec<transparent::Output>>,
273 ) -> Result<sighash::SigHasher, Error> {
274 sighash::SigHasher::new(self, nu, all_previous_outputs)
275 }
276
277 pub fn auth_digest(&self) -> Option<AuthDigest> {
284 match self {
285 Transaction::V1 { .. }
286 | Transaction::V2 { .. }
287 | Transaction::V3 { .. }
288 | Transaction::V4 { .. } => None,
289 Transaction::V5 { .. } => Some(AuthDigest::from(self)),
290 Transaction::V6 { .. } => Some(AuthDigest::from(self)),
291 }
292 }
293
294 pub fn has_transparent_inputs(&self) -> bool {
298 !self.inputs().is_empty()
299 }
300
301 pub fn has_transparent_outputs(&self) -> bool {
303 !self.outputs().is_empty()
304 }
305
306 pub fn has_transparent_inputs_or_outputs(&self) -> bool {
308 self.has_transparent_inputs() || self.has_transparent_outputs()
309 }
310
311 pub fn has_transparent_or_shielded_inputs(&self) -> bool {
313 self.has_transparent_inputs() || self.has_shielded_inputs()
314 }
315
316 pub fn has_shielded_inputs(&self) -> bool {
320 self.joinsplit_count() > 0
321 || self.sapling_spends_per_anchor().count() > 0
322 || (self.orchard_actions().count() > 0
323 && self
324 .orchard_flags()
325 .unwrap_or_else(orchard::Flags::empty)
326 .contains(orchard::Flags::ENABLE_SPENDS))
327 || (self.has_ironwood_shielded_data()
328 && self
329 .ironwood_flags()
330 .unwrap_or_else(orchard::Flags::empty)
331 .contains(orchard::Flags::ENABLE_SPENDS))
332 }
333
334 pub fn has_shielded_outputs(&self) -> bool {
338 self.joinsplit_count() > 0
339 || self.sapling_outputs().count() > 0
340 || (self.orchard_actions().count() > 0
341 && self
342 .orchard_flags()
343 .unwrap_or_else(orchard::Flags::empty)
344 .contains(orchard::Flags::ENABLE_OUTPUTS))
345 || (self.has_ironwood_shielded_data()
346 && self
347 .ironwood_flags()
348 .unwrap_or_else(orchard::Flags::empty)
349 .contains(orchard::Flags::ENABLE_OUTPUTS))
350 }
351
352 pub fn has_transparent_or_shielded_outputs(&self) -> bool {
354 self.has_transparent_outputs() || self.has_shielded_outputs()
355 }
356
357 pub fn has_enough_orchard_flags(&self) -> bool {
359 if self.version() < 5 || self.orchard_actions().count() == 0 {
360 return true;
361 }
362 self.orchard_flags()
363 .unwrap_or_else(orchard::Flags::empty)
364 .intersects(orchard::Flags::ENABLE_SPENDS | orchard::Flags::ENABLE_OUTPUTS)
365 }
366
367 pub fn has_enough_ironwood_flags(&self) -> bool {
372 if !self.has_ironwood_shielded_data() {
373 return true;
374 }
375 self.ironwood_flags()
376 .unwrap_or_else(orchard::Flags::empty)
377 .intersects(orchard::Flags::ENABLE_SPENDS | orchard::Flags::ENABLE_OUTPUTS)
378 }
379
380 pub fn coinbase_spend_restriction(
383 &self,
384 network: &Network,
385 spend_height: block::Height,
386 ) -> CoinbaseSpendRestriction {
387 if self.outputs().is_empty() || network.should_allow_unshielded_coinbase_spends() {
388 CheckCoinbaseMaturity { spend_height }
391 } else {
392 DisallowCoinbaseSpend
393 }
394 }
395
396 pub fn is_overwintered(&self) -> bool {
400 match self {
401 Transaction::V1 { .. } | Transaction::V2 { .. } => false,
402 Transaction::V3 { .. } | Transaction::V4 { .. } | Transaction::V5 { .. } => true,
403 Transaction::V6 { .. } => true,
404 }
405 }
406
407 pub fn version(&self) -> u32 {
419 match self {
420 Transaction::V1 { .. } => 1,
421 Transaction::V2 { .. } => 2,
422 Transaction::V3 { .. } => 3,
423 Transaction::V4 { .. } => 4,
424 Transaction::V5 { .. } => 5,
425 Transaction::V6 { .. } => 6,
426 }
427 }
428
429 pub fn lock_time(&self) -> Option<LockTime> {
431 let lock_time = match self {
432 Transaction::V1 { lock_time, .. }
433 | Transaction::V2 { lock_time, .. }
434 | Transaction::V3 { lock_time, .. }
435 | Transaction::V4 { lock_time, .. }
436 | Transaction::V5 { lock_time, .. } => *lock_time,
437 Transaction::V6 { lock_time, .. } => *lock_time,
438 };
439
440 if lock_time == LockTime::unlocked() {
447 return None;
448 }
449
450 let has_sequence_number_enabling_lock_time = self
465 .inputs()
466 .iter()
467 .map(transparent::Input::sequence)
468 .any(|sequence_number| sequence_number != u32::MAX);
469
470 if has_sequence_number_enabling_lock_time {
471 Some(lock_time)
472 } else {
473 None
474 }
475 }
476
477 pub fn raw_lock_time(&self) -> u32 {
479 let lock_time = match self {
480 Transaction::V1 { lock_time, .. }
481 | Transaction::V2 { lock_time, .. }
482 | Transaction::V3 { lock_time, .. }
483 | Transaction::V4 { lock_time, .. }
484 | Transaction::V5 { lock_time, .. } => *lock_time,
485 Transaction::V6 { lock_time, .. } => *lock_time,
486 };
487 let mut lock_time_bytes = Vec::new();
488 lock_time
489 .zcash_serialize(&mut lock_time_bytes)
490 .expect("lock_time should serialize");
491 u32::from_le_bytes(
492 lock_time_bytes
493 .try_into()
494 .expect("should serialize as 4 bytes"),
495 )
496 }
497
498 pub fn lock_time_is_time(&self) -> bool {
502 if let Some(lock_time) = self.lock_time() {
503 return lock_time.is_time();
504 }
505
506 false
507 }
508
509 pub fn expiry_height(&self) -> Option<block::Height> {
511 match self {
512 Transaction::V1 { .. } | Transaction::V2 { .. } => None,
513 Transaction::V3 { expiry_height, .. }
514 | Transaction::V4 { expiry_height, .. }
515 | Transaction::V5 { expiry_height, .. } => match expiry_height {
516 block::Height(0) => None,
520 block::Height(expiry_height) => Some(block::Height(*expiry_height)),
521 },
522 Transaction::V6 { expiry_height, .. } => match expiry_height {
523 block::Height(0) => None,
528 block::Height(expiry_height) => Some(block::Height(*expiry_height)),
529 },
530 }
531 }
532
533 pub fn network_upgrade(&self) -> Option<NetworkUpgrade> {
538 match self {
539 Transaction::V1 { .. }
540 | Transaction::V2 { .. }
541 | Transaction::V3 { .. }
542 | Transaction::V4 { .. } => None,
543 Transaction::V5 {
544 network_upgrade, ..
545 } => Some(*network_upgrade),
546 Transaction::V6 {
547 network_upgrade, ..
548 } => Some(*network_upgrade),
549 }
550 }
551
552 pub fn inputs(&self) -> &[transparent::Input] {
556 match self {
557 Transaction::V1 { ref inputs, .. } => inputs,
558 Transaction::V2 { ref inputs, .. } => inputs,
559 Transaction::V3 { ref inputs, .. } => inputs,
560 Transaction::V4 { ref inputs, .. } => inputs,
561 Transaction::V5 { ref inputs, .. } => inputs,
562 Transaction::V6 { ref inputs, .. } => inputs,
563 }
564 }
565
566 pub fn spent_outpoints(&self) -> impl Iterator<Item = transparent::OutPoint> + '_ {
568 self.inputs()
569 .iter()
570 .filter_map(transparent::Input::outpoint)
571 }
572
573 pub fn outputs(&self) -> &[transparent::Output] {
575 match self {
576 Transaction::V1 { ref outputs, .. } => outputs,
577 Transaction::V2 { ref outputs, .. } => outputs,
578 Transaction::V3 { ref outputs, .. } => outputs,
579 Transaction::V4 { ref outputs, .. } => outputs,
580 Transaction::V5 { ref outputs, .. } => outputs,
581 Transaction::V6 { ref outputs, .. } => outputs,
582 }
583 }
584
585 pub fn is_coinbase(&self) -> bool {
589 self.inputs().len() == 1
590 && matches!(
591 self.inputs().first(),
592 Some(transparent::Input::Coinbase { .. })
593 )
594 }
595
596 pub fn is_valid_non_coinbase(&self) -> bool {
603 self.inputs()
604 .iter()
605 .all(|input| matches!(input, transparent::Input::PrevOut { .. }))
606 }
607
608 pub fn sprout_groth16_joinsplits(
612 &self,
613 ) -> Box<dyn Iterator<Item = &sprout::JoinSplit<Groth16Proof>> + '_> {
614 match self {
615 Transaction::V4 {
617 joinsplit_data: Some(joinsplit_data),
618 ..
619 } => Box::new(joinsplit_data.joinsplits()),
620
621 Transaction::V1 { .. }
623 | Transaction::V2 { .. }
624 | Transaction::V3 { .. }
625 | Transaction::V4 {
626 joinsplit_data: None,
627 ..
628 }
629 | Transaction::V5 { .. } => Box::new(std::iter::empty()),
630 Transaction::V6 { .. } => Box::new(std::iter::empty()),
631 }
632 }
633
634 pub fn sprout_joinsplits(&self) -> Box<dyn Iterator<Item = sprout::GenericJoinSplit> + '_> {
636 match self {
637 Transaction::V2 {
639 joinsplit_data: Some(joinsplit_data),
640 ..
641 }
642 | Transaction::V3 {
643 joinsplit_data: Some(joinsplit_data),
644 ..
645 } => Box::new(joinsplit_data.joinsplits().map(|js| js.clone().into())),
646 Transaction::V4 {
648 joinsplit_data: Some(joinsplit_data),
649 ..
650 } => Box::new(joinsplit_data.joinsplits().map(|js| js.clone().into())),
651 Transaction::V1 { .. }
653 | Transaction::V2 {
654 joinsplit_data: None,
655 ..
656 }
657 | Transaction::V3 {
658 joinsplit_data: None,
659 ..
660 }
661 | Transaction::V4 {
662 joinsplit_data: None,
663 ..
664 }
665 | Transaction::V5 { .. } => Box::new(std::iter::empty()),
666 Transaction::V6 { .. } => Box::new(std::iter::empty()),
667 }
668 }
669
670 pub fn joinsplit_count(&self) -> usize {
672 match self {
673 Transaction::V2 {
675 joinsplit_data: Some(joinsplit_data),
676 ..
677 }
678 | Transaction::V3 {
679 joinsplit_data: Some(joinsplit_data),
680 ..
681 } => joinsplit_data.joinsplits().count(),
682 Transaction::V4 {
684 joinsplit_data: Some(joinsplit_data),
685 ..
686 } => joinsplit_data.joinsplits().count(),
687 Transaction::V1 { .. }
689 | Transaction::V2 {
690 joinsplit_data: None,
691 ..
692 }
693 | Transaction::V3 {
694 joinsplit_data: None,
695 ..
696 }
697 | Transaction::V4 {
698 joinsplit_data: None,
699 ..
700 }
701 | Transaction::V5 { .. } => 0,
702 Transaction::V6 { .. } => 0,
703 }
704 }
705
706 pub fn sprout_nullifiers(&self) -> Box<dyn Iterator<Item = &sprout::Nullifier> + '_> {
708 match self {
713 Transaction::V2 {
715 joinsplit_data: Some(joinsplit_data),
716 ..
717 }
718 | Transaction::V3 {
719 joinsplit_data: Some(joinsplit_data),
720 ..
721 } => Box::new(joinsplit_data.nullifiers()),
722 Transaction::V4 {
724 joinsplit_data: Some(joinsplit_data),
725 ..
726 } => Box::new(joinsplit_data.nullifiers()),
727 Transaction::V1 { .. }
729 | Transaction::V2 {
730 joinsplit_data: None,
731 ..
732 }
733 | Transaction::V3 {
734 joinsplit_data: None,
735 ..
736 }
737 | Transaction::V4 {
738 joinsplit_data: None,
739 ..
740 }
741 | Transaction::V5 { .. } => Box::new(std::iter::empty()),
742 Transaction::V6 { .. } => Box::new(std::iter::empty()),
743 }
744 }
745
746 pub fn sprout_joinsplit_pub_key(&self) -> Option<ed25519::VerificationKeyBytes> {
749 match self {
750 Transaction::V2 {
752 joinsplit_data: Some(joinsplit_data),
753 ..
754 }
755 | Transaction::V3 {
756 joinsplit_data: Some(joinsplit_data),
757 ..
758 } => Some(joinsplit_data.pub_key),
759 Transaction::V4 {
761 joinsplit_data: Some(joinsplit_data),
762 ..
763 } => Some(joinsplit_data.pub_key),
764 Transaction::V1 { .. }
766 | Transaction::V2 {
767 joinsplit_data: None,
768 ..
769 }
770 | Transaction::V3 {
771 joinsplit_data: None,
772 ..
773 }
774 | Transaction::V4 {
775 joinsplit_data: None,
776 ..
777 }
778 | Transaction::V5 { .. } => None,
779 Transaction::V6 { .. } => None,
780 }
781 }
782
783 pub fn has_sprout_joinsplit_data(&self) -> bool {
785 match self {
786 Transaction::V1 { .. } | Transaction::V5 { .. } => false,
788 Transaction::V6 { .. } => false,
789
790 Transaction::V2 { joinsplit_data, .. } | Transaction::V3 { joinsplit_data, .. } => {
792 joinsplit_data.is_some()
793 }
794
795 Transaction::V4 { joinsplit_data, .. } => joinsplit_data.is_some(),
797 }
798 }
799
800 pub fn sprout_note_commitments(
802 &self,
803 ) -> Box<dyn Iterator<Item = &sprout::commitment::NoteCommitment> + '_> {
804 match self {
805 Transaction::V2 {
807 joinsplit_data: Some(joinsplit_data),
808 ..
809 }
810 | Transaction::V3 {
811 joinsplit_data: Some(joinsplit_data),
812 ..
813 } => Box::new(joinsplit_data.note_commitments()),
814
815 Transaction::V4 {
817 joinsplit_data: Some(joinsplit_data),
818 ..
819 } => Box::new(joinsplit_data.note_commitments()),
820
821 Transaction::V2 {
823 joinsplit_data: None,
824 ..
825 }
826 | Transaction::V3 {
827 joinsplit_data: None,
828 ..
829 }
830 | Transaction::V4 {
831 joinsplit_data: None,
832 ..
833 }
834 | Transaction::V1 { .. }
835 | Transaction::V5 { .. } => Box::new(std::iter::empty()),
836 Transaction::V6 { .. } => Box::new(std::iter::empty()),
837 }
838 }
839
840 pub fn sapling_anchors(&self) -> Box<dyn Iterator<Item = sapling::tree::Root> + '_> {
845 match self {
848 Transaction::V4 {
849 sapling_shielded_data: Some(sapling_shielded_data),
850 ..
851 } => Box::new(sapling_shielded_data.anchors()),
852
853 Transaction::V5 {
854 sapling_shielded_data: Some(sapling_shielded_data),
855 ..
856 } => Box::new(sapling_shielded_data.anchors()),
857
858 Transaction::V6 {
859 sapling_shielded_data: Some(sapling_shielded_data),
860 ..
861 } => Box::new(sapling_shielded_data.anchors()),
862
863 Transaction::V1 { .. }
865 | Transaction::V2 { .. }
866 | Transaction::V3 { .. }
867 | Transaction::V4 {
868 sapling_shielded_data: None,
869 ..
870 }
871 | Transaction::V5 {
872 sapling_shielded_data: None,
873 ..
874 } => Box::new(std::iter::empty()),
875 Transaction::V6 {
876 sapling_shielded_data: None,
877 ..
878 } => Box::new(std::iter::empty()),
879 }
880 }
881
882 pub fn sapling_spends_per_anchor(
893 &self,
894 ) -> Box<dyn Iterator<Item = sapling::Spend<sapling::PerSpendAnchor>> + '_> {
895 match self {
896 Transaction::V4 {
897 sapling_shielded_data: Some(sapling_shielded_data),
898 ..
899 } => Box::new(sapling_shielded_data.spends_per_anchor()),
900 Transaction::V5 {
901 sapling_shielded_data: Some(sapling_shielded_data),
902 ..
903 } => Box::new(sapling_shielded_data.spends_per_anchor()),
904 Transaction::V6 {
905 sapling_shielded_data: Some(sapling_shielded_data),
906 ..
907 } => Box::new(sapling_shielded_data.spends_per_anchor()),
908
909 Transaction::V1 { .. }
911 | Transaction::V2 { .. }
912 | Transaction::V3 { .. }
913 | Transaction::V4 {
914 sapling_shielded_data: None,
915 ..
916 }
917 | Transaction::V5 {
918 sapling_shielded_data: None,
919 ..
920 } => Box::new(std::iter::empty()),
921 Transaction::V6 {
922 sapling_shielded_data: None,
923 ..
924 } => Box::new(std::iter::empty()),
925 }
926 }
927
928 pub fn sapling_outputs(&self) -> Box<dyn Iterator<Item = &sapling::Output> + '_> {
931 match self {
932 Transaction::V4 {
933 sapling_shielded_data: Some(sapling_shielded_data),
934 ..
935 } => Box::new(sapling_shielded_data.outputs()),
936 Transaction::V5 {
937 sapling_shielded_data: Some(sapling_shielded_data),
938 ..
939 } => Box::new(sapling_shielded_data.outputs()),
940 Transaction::V6 {
941 sapling_shielded_data: Some(sapling_shielded_data),
942 ..
943 } => Box::new(sapling_shielded_data.outputs()),
944
945 Transaction::V1 { .. }
947 | Transaction::V2 { .. }
948 | Transaction::V3 { .. }
949 | Transaction::V4 {
950 sapling_shielded_data: None,
951 ..
952 }
953 | Transaction::V5 {
954 sapling_shielded_data: None,
955 ..
956 } => Box::new(std::iter::empty()),
957 Transaction::V6 {
958 sapling_shielded_data: None,
959 ..
960 } => Box::new(std::iter::empty()),
961 }
962 }
963
964 pub fn sapling_nullifiers(&self) -> Box<dyn Iterator<Item = &sapling::Nullifier> + '_> {
966 match self {
969 Transaction::V4 {
971 sapling_shielded_data: Some(sapling_shielded_data),
972 ..
973 } => Box::new(sapling_shielded_data.nullifiers()),
974 Transaction::V5 {
975 sapling_shielded_data: Some(sapling_shielded_data),
976 ..
977 } => Box::new(sapling_shielded_data.nullifiers()),
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 Transaction::V6 {
996 sapling_shielded_data: None,
997 ..
998 } => Box::new(std::iter::empty()),
999 }
1000 }
1001
1002 pub fn sapling_note_commitments(
1004 &self,
1005 ) -> Box<dyn Iterator<Item = &sapling_crypto::note::ExtractedNoteCommitment> + '_> {
1006 match self {
1009 Transaction::V4 {
1011 sapling_shielded_data: Some(sapling_shielded_data),
1012 ..
1013 } => Box::new(sapling_shielded_data.note_commitments()),
1014 Transaction::V5 {
1015 sapling_shielded_data: Some(sapling_shielded_data),
1016 ..
1017 } => Box::new(sapling_shielded_data.note_commitments()),
1018 Transaction::V6 {
1019 sapling_shielded_data: Some(sapling_shielded_data),
1020 ..
1021 } => Box::new(sapling_shielded_data.note_commitments()),
1022
1023 Transaction::V1 { .. }
1025 | Transaction::V2 { .. }
1026 | Transaction::V3 { .. }
1027 | Transaction::V4 {
1028 sapling_shielded_data: None,
1029 ..
1030 }
1031 | Transaction::V5 {
1032 sapling_shielded_data: None,
1033 ..
1034 } => Box::new(std::iter::empty()),
1035 Transaction::V6 {
1036 sapling_shielded_data: None,
1037 ..
1038 } => Box::new(std::iter::empty()),
1039 }
1040 }
1041
1042 pub fn has_sapling_shielded_data(&self) -> bool {
1044 match self {
1045 Transaction::V1 { .. } | Transaction::V2 { .. } | Transaction::V3 { .. } => false,
1046 Transaction::V4 {
1047 sapling_shielded_data,
1048 ..
1049 } => sapling_shielded_data.is_some(),
1050 Transaction::V5 {
1051 sapling_shielded_data,
1052 ..
1053 } => sapling_shielded_data.is_some(),
1054 Transaction::V6 {
1055 sapling_shielded_data,
1056 ..
1057 } => sapling_shielded_data.is_some(),
1058 }
1059 }
1060
1061 pub fn orchard_shielded_data(&self) -> Option<&orchard::ShieldedData> {
1066 match self {
1067 Transaction::V5 {
1069 orchard_shielded_data,
1070 ..
1071 } => orchard_shielded_data.as_ref(),
1072 Transaction::V6 {
1073 orchard_shielded_data,
1074 ..
1075 } => orchard_shielded_data.as_ref().map(|data| data.data()),
1076
1077 Transaction::V1 { .. }
1079 | Transaction::V2 { .. }
1080 | Transaction::V3 { .. }
1081 | Transaction::V4 { .. } => None,
1082 }
1083 }
1084
1085 pub fn orchard_actions(&self) -> impl Iterator<Item = &orchard::Action> {
1088 self.orchard_shielded_data()
1089 .into_iter()
1090 .flat_map(orchard::ShieldedData::actions)
1091 }
1092
1093 pub fn orchard_nullifiers(&self) -> impl Iterator<Item = &orchard::Nullifier> {
1096 self.orchard_shielded_data()
1097 .into_iter()
1098 .flat_map(orchard::ShieldedData::nullifiers)
1099 }
1100
1101 pub fn orchard_note_commitments(&self) -> impl Iterator<Item = &pallas::Base> {
1104 self.orchard_shielded_data()
1105 .into_iter()
1106 .flat_map(orchard::ShieldedData::note_commitments)
1107 }
1108
1109 pub fn orchard_flags(&self) -> Option<orchard::shielded_data::Flags> {
1112 self.orchard_shielded_data()
1113 .map(|orchard_shielded_data| orchard_shielded_data.flags)
1114 }
1115
1116 pub fn has_orchard_shielded_data(&self) -> bool {
1119 self.orchard_shielded_data().is_some()
1120 }
1121
1122 pub fn ironwood_shielded_data(&self) -> Option<&orchard::ShieldedData> {
1130 match self {
1131 Transaction::V6 {
1132 ironwood_shielded_data,
1133 ..
1134 } => ironwood_shielded_data.as_ref().map(|data| data.data()),
1135
1136 Transaction::V1 { .. }
1137 | Transaction::V2 { .. }
1138 | Transaction::V3 { .. }
1139 | Transaction::V4 { .. }
1140 | Transaction::V5 { .. } => None,
1141 }
1142 }
1143
1144 pub fn ironwood_actions(&self) -> impl Iterator<Item = &orchard::Action> {
1147 self.ironwood_shielded_data()
1148 .into_iter()
1149 .flat_map(orchard::ShieldedData::actions)
1150 }
1151
1152 pub fn ironwood_nullifiers(&self) -> impl Iterator<Item = ironwood::Nullifier> + '_ {
1158 self.ironwood_shielded_data()
1159 .into_iter()
1160 .flat_map(orchard::ShieldedData::nullifiers)
1161 .map(|nullifier| ironwood::Nullifier::from(*nullifier))
1162 }
1163
1164 pub fn ironwood_note_commitments(&self) -> impl Iterator<Item = &pallas::Base> {
1167 self.ironwood_shielded_data()
1168 .into_iter()
1169 .flat_map(orchard::ShieldedData::note_commitments)
1170 }
1171
1172 pub fn ironwood_flags(&self) -> Option<orchard::shielded_data::Flags> {
1175 self.ironwood_shielded_data()
1176 .map(|ironwood_shielded_data| ironwood_shielded_data.flags)
1177 }
1178
1179 pub fn has_ironwood_shielded_data(&self) -> bool {
1182 self.ironwood_shielded_data().is_some()
1183 }
1184
1185 #[allow(clippy::unwrap_in_result)]
1192 fn transparent_value_balance_from_outputs(
1193 &self,
1194 outputs: &HashMap<transparent::OutPoint, transparent::Output>,
1195 ) -> Result<ValueBalance<NegativeAllowed>, ValueBalanceError> {
1196 let input_value = self
1197 .inputs()
1198 .iter()
1199 .map(|i| i.value_from_outputs(outputs))
1200 .sum::<Result<Amount<NonNegative>, AmountError>>()
1201 .map_err(ValueBalanceError::Transparent)?
1202 .constrain()
1203 .expect("conversion from NonNegative to NegativeAllowed is always valid");
1204
1205 let output_value = self
1206 .outputs()
1207 .iter()
1208 .map(|o| o.value())
1209 .sum::<Result<Amount<NonNegative>, AmountError>>()
1210 .map_err(ValueBalanceError::Transparent)?
1211 .constrain()
1212 .expect("conversion from NonNegative to NegativeAllowed is always valid");
1213
1214 (input_value - output_value)
1215 .map(ValueBalance::from_transparent_amount)
1216 .map_err(ValueBalanceError::Transparent)
1217 }
1218
1219 pub fn output_values_to_sprout(&self) -> Box<dyn Iterator<Item = &Amount<NonNegative>> + '_> {
1225 match self {
1226 Transaction::V2 {
1228 joinsplit_data: Some(joinsplit_data),
1229 ..
1230 }
1231 | Transaction::V3 {
1232 joinsplit_data: Some(joinsplit_data),
1233 ..
1234 } => Box::new(
1235 joinsplit_data
1236 .joinsplits()
1237 .map(|joinsplit| &joinsplit.vpub_old),
1238 ),
1239 Transaction::V4 {
1241 joinsplit_data: Some(joinsplit_data),
1242 ..
1243 } => Box::new(
1244 joinsplit_data
1245 .joinsplits()
1246 .map(|joinsplit| &joinsplit.vpub_old),
1247 ),
1248 Transaction::V1 { .. }
1250 | Transaction::V2 {
1251 joinsplit_data: None,
1252 ..
1253 }
1254 | Transaction::V3 {
1255 joinsplit_data: None,
1256 ..
1257 }
1258 | Transaction::V4 {
1259 joinsplit_data: None,
1260 ..
1261 }
1262 | Transaction::V5 { .. } => Box::new(std::iter::empty()),
1263 Transaction::V6 { .. } => Box::new(std::iter::empty()),
1264 }
1265 }
1266
1267 pub fn input_values_from_sprout(&self) -> Box<dyn Iterator<Item = &Amount<NonNegative>> + '_> {
1273 match self {
1274 Transaction::V2 {
1276 joinsplit_data: Some(joinsplit_data),
1277 ..
1278 }
1279 | Transaction::V3 {
1280 joinsplit_data: Some(joinsplit_data),
1281 ..
1282 } => Box::new(
1283 joinsplit_data
1284 .joinsplits()
1285 .map(|joinsplit| &joinsplit.vpub_new),
1286 ),
1287 Transaction::V4 {
1289 joinsplit_data: Some(joinsplit_data),
1290 ..
1291 } => Box::new(
1292 joinsplit_data
1293 .joinsplits()
1294 .map(|joinsplit| &joinsplit.vpub_new),
1295 ),
1296 Transaction::V1 { .. }
1298 | Transaction::V2 {
1299 joinsplit_data: None,
1300 ..
1301 }
1302 | Transaction::V3 {
1303 joinsplit_data: None,
1304 ..
1305 }
1306 | Transaction::V4 {
1307 joinsplit_data: None,
1308 ..
1309 }
1310 | Transaction::V5 { .. } => Box::new(std::iter::empty()),
1311 Transaction::V6 { .. } => Box::new(std::iter::empty()),
1312 }
1313 }
1314
1315 fn sprout_joinsplit_value_balances(
1324 &self,
1325 ) -> impl Iterator<Item = ValueBalance<NegativeAllowed>> + '_ {
1326 let joinsplit_value_balances = match self {
1327 Transaction::V2 {
1328 joinsplit_data: Some(joinsplit_data),
1329 ..
1330 }
1331 | Transaction::V3 {
1332 joinsplit_data: Some(joinsplit_data),
1333 ..
1334 } => joinsplit_data.joinsplit_value_balances(),
1335 Transaction::V4 {
1336 joinsplit_data: Some(joinsplit_data),
1337 ..
1338 } => joinsplit_data.joinsplit_value_balances(),
1339 Transaction::V1 { .. }
1340 | Transaction::V2 {
1341 joinsplit_data: None,
1342 ..
1343 }
1344 | Transaction::V3 {
1345 joinsplit_data: None,
1346 ..
1347 }
1348 | Transaction::V4 {
1349 joinsplit_data: None,
1350 ..
1351 }
1352 | Transaction::V5 { .. } => Box::new(iter::empty()),
1353 Transaction::V6 { .. } => Box::new(iter::empty()),
1354 };
1355
1356 joinsplit_value_balances.map(ValueBalance::from_sprout_amount)
1357 }
1358
1359 fn sprout_value_balance(&self) -> Result<ValueBalance<NegativeAllowed>, ValueBalanceError> {
1371 self.sprout_joinsplit_value_balances().sum()
1372 }
1373
1374 pub fn sapling_value_balance(&self) -> ValueBalance<NegativeAllowed> {
1386 let sapling_value_balance = match self {
1387 Transaction::V4 {
1388 sapling_shielded_data: Some(sapling_shielded_data),
1389 ..
1390 } => sapling_shielded_data.value_balance,
1391 Transaction::V5 {
1392 sapling_shielded_data: Some(sapling_shielded_data),
1393 ..
1394 } => sapling_shielded_data.value_balance,
1395 Transaction::V6 {
1396 sapling_shielded_data: Some(sapling_shielded_data),
1397 ..
1398 } => sapling_shielded_data.value_balance,
1399
1400 Transaction::V1 { .. }
1401 | Transaction::V2 { .. }
1402 | Transaction::V3 { .. }
1403 | Transaction::V4 {
1404 sapling_shielded_data: None,
1405 ..
1406 }
1407 | Transaction::V5 {
1408 sapling_shielded_data: None,
1409 ..
1410 } => Amount::zero(),
1411 Transaction::V6 {
1412 sapling_shielded_data: None,
1413 ..
1414 } => Amount::zero(),
1415 };
1416
1417 ValueBalance::from_sapling_amount(sapling_value_balance)
1418 }
1419
1420 pub fn sapling_binding_sig(&self) -> Option<Signature<Binding>> {
1425 match self {
1426 Transaction::V4 {
1427 sapling_shielded_data: Some(sapling_shielded_data),
1428 ..
1429 } => Some(sapling_shielded_data.binding_sig),
1430 Transaction::V5 {
1431 sapling_shielded_data: Some(sapling_shielded_data),
1432 ..
1433 } => Some(sapling_shielded_data.binding_sig),
1434 Transaction::V6 {
1435 sapling_shielded_data: Some(sapling_shielded_data),
1436 ..
1437 } => Some(sapling_shielded_data.binding_sig),
1438 _ => None,
1439 }
1440 }
1441
1442 pub fn joinsplit_pub_key(&self) -> Option<ed25519::VerificationKeyBytes> {
1450 match self {
1451 Transaction::V2 {
1452 joinsplit_data: Some(joinsplit_data),
1453 ..
1454 } => Some(joinsplit_data.pub_key),
1455 Transaction::V3 {
1456 joinsplit_data: Some(joinsplit_data),
1457 ..
1458 } => Some(joinsplit_data.pub_key),
1459 Transaction::V4 {
1460 joinsplit_data: Some(joinsplit_data),
1461 ..
1462 } => Some(joinsplit_data.pub_key),
1463 _ => None,
1464 }
1465 }
1466
1467 pub fn joinsplit_sig(&self) -> Option<ed25519::Signature> {
1475 match self {
1476 Transaction::V2 {
1477 joinsplit_data: Some(joinsplit_data),
1478 ..
1479 } => Some(joinsplit_data.sig),
1480 Transaction::V3 {
1481 joinsplit_data: Some(joinsplit_data),
1482 ..
1483 } => Some(joinsplit_data.sig),
1484 Transaction::V4 {
1485 joinsplit_data: Some(joinsplit_data),
1486 ..
1487 } => Some(joinsplit_data.sig),
1488 _ => None,
1489 }
1490 }
1491
1492 pub fn orchard_value_balance(&self) -> ValueBalance<NegativeAllowed> {
1504 let orchard_value_balance = self
1505 .orchard_shielded_data()
1506 .map(|shielded_data| shielded_data.value_balance)
1507 .unwrap_or_else(Amount::zero);
1508
1509 ValueBalance::from_orchard_amount(orchard_value_balance)
1510 }
1511
1512 pub fn ironwood_value_balance(&self) -> ValueBalance<NegativeAllowed> {
1521 let ironwood_value_balance = self
1522 .ironwood_shielded_data()
1523 .map(|shielded_data| shielded_data.value_balance)
1524 .unwrap_or_else(Amount::zero);
1525
1526 ValueBalance::from_ironwood_amount(ironwood_value_balance)
1527 }
1528
1529 pub(crate) fn value_balance_from_outputs(
1531 &self,
1532 outputs: &HashMap<transparent::OutPoint, transparent::Output>,
1533 ) -> Result<ValueBalance<NegativeAllowed>, ValueBalanceError> {
1534 self.transparent_value_balance_from_outputs(outputs)?
1535 + self.sprout_value_balance()?
1536 + self.sapling_value_balance()
1537 + self.orchard_value_balance()
1538 + self.ironwood_value_balance()
1539 }
1540
1541 pub fn value_balance(
1562 &self,
1563 utxos: &HashMap<transparent::OutPoint, transparent::Utxo>,
1564 ) -> Result<ValueBalance<NegativeAllowed>, ValueBalanceError> {
1565 let outputs = self
1566 .spent_outpoints()
1567 .filter_map(|outpoint| {
1568 utxos
1569 .get(&outpoint)
1570 .map(|utxo| (outpoint, utxo.output.clone()))
1571 })
1572 .collect();
1573
1574 self.value_balance_from_outputs(&outputs)
1575 }
1576
1577 pub(crate) fn to_librustzcash(
1583 &self,
1584 nu: NetworkUpgrade,
1585 ) -> Result<zcash_primitives::transaction::Transaction, crate::Error> {
1586 if self.network_upgrade().is_some_and(|tx_nu| tx_nu != nu) {
1587 return Err(crate::Error::InvalidConsensusBranchId);
1588 }
1589
1590 let Some(branch_id) = nu.branch_id() else {
1591 return Err(crate::Error::InvalidConsensusBranchId);
1592 };
1593
1594 let Ok(branch_id) = consensus::BranchId::try_from(branch_id) else {
1595 return Err(crate::Error::InvalidConsensusBranchId);
1596 };
1597
1598 Ok(zcash_primitives::transaction::Transaction::read(
1599 &self.zcash_serialize_to_vec()?[..],
1600 branch_id,
1601 )?)
1602 }
1603
1604 pub fn has_shielded_data(&self) -> bool {
1608 self.has_shielded_inputs() || self.has_shielded_outputs()
1609 }
1610
1611 pub fn version_group_id(&self) -> Option<u32> {
1613 match self {
1617 Transaction::V1 { .. } | Transaction::V2 { .. } => None,
1618 Transaction::V3 { .. } => Some(OVERWINTER_VERSION_GROUP_ID),
1619 Transaction::V4 { .. } => Some(SAPLING_VERSION_GROUP_ID),
1620 Transaction::V5 { .. } => Some(TX_V5_VERSION_GROUP_ID),
1621 Transaction::V6 { .. } => Some(TX_V6_VERSION_GROUP_ID),
1622 }
1623 }
1624}
1625
1626#[cfg(any(test, feature = "proptest-impl"))]
1627impl Transaction {
1628 pub fn update_network_upgrade(&mut self, nu: NetworkUpgrade) -> Result<(), &str> {
1634 match self {
1635 Transaction::V1 { .. }
1636 | Transaction::V2 { .. }
1637 | Transaction::V3 { .. }
1638 | Transaction::V4 { .. } => Err(
1639 "Updating the network upgrade for V1, V2, V3 and V4 transactions is not possible.",
1640 ),
1641 Transaction::V5 {
1642 ref mut network_upgrade,
1643 ..
1644 } => {
1645 *network_upgrade = nu;
1646 Ok(())
1647 }
1648 Transaction::V6 {
1649 ref mut network_upgrade,
1650 ..
1651 } => {
1652 *network_upgrade = nu;
1653 Ok(())
1654 }
1655 }
1656 }
1657
1658 pub fn expiry_height_mut(&mut self) -> &mut block::Height {
1664 match self {
1665 Transaction::V1 { .. } | Transaction::V2 { .. } => {
1666 panic!("v1 and v2 transactions are not supported")
1667 }
1668 Transaction::V3 {
1669 ref mut expiry_height,
1670 ..
1671 }
1672 | Transaction::V4 {
1673 ref mut expiry_height,
1674 ..
1675 }
1676 | Transaction::V5 {
1677 ref mut expiry_height,
1678 ..
1679 } => expiry_height,
1680 Transaction::V6 {
1681 ref mut expiry_height,
1682 ..
1683 } => expiry_height,
1684 }
1685 }
1686
1687 pub fn inputs_mut(&mut self) -> &mut Vec<transparent::Input> {
1689 match self {
1690 Transaction::V1 { ref mut inputs, .. } => inputs,
1691 Transaction::V2 { ref mut inputs, .. } => inputs,
1692 Transaction::V3 { ref mut inputs, .. } => inputs,
1693 Transaction::V4 { ref mut inputs, .. } => inputs,
1694 Transaction::V5 { ref mut inputs, .. } => inputs,
1695 Transaction::V6 { ref mut inputs, .. } => inputs,
1696 }
1697 }
1698
1699 pub fn orchard_value_balance_mut(&mut self) -> Option<&mut Amount<NegativeAllowed>> {
1704 self.orchard_shielded_data_mut()
1705 .map(|shielded_data| &mut shielded_data.value_balance)
1706 }
1707
1708 pub fn sapling_value_balance_mut(&mut self) -> Option<&mut Amount<NegativeAllowed>> {
1713 match self {
1714 Transaction::V4 {
1715 sapling_shielded_data: Some(sapling_shielded_data),
1716 ..
1717 } => Some(&mut sapling_shielded_data.value_balance),
1718 Transaction::V5 {
1719 sapling_shielded_data: Some(sapling_shielded_data),
1720 ..
1721 } => Some(&mut sapling_shielded_data.value_balance),
1722 Transaction::V6 {
1723 sapling_shielded_data: Some(sapling_shielded_data),
1724 ..
1725 } => Some(&mut sapling_shielded_data.value_balance),
1726 Transaction::V1 { .. }
1727 | Transaction::V2 { .. }
1728 | Transaction::V3 { .. }
1729 | Transaction::V4 {
1730 sapling_shielded_data: None,
1731 ..
1732 }
1733 | Transaction::V5 {
1734 sapling_shielded_data: None,
1735 ..
1736 } => None,
1737 Transaction::V6 {
1738 sapling_shielded_data: None,
1739 ..
1740 } => None,
1741 }
1742 }
1743
1744 pub fn input_values_from_sprout_mut(
1749 &mut self,
1750 ) -> Box<dyn Iterator<Item = &mut Amount<NonNegative>> + '_> {
1751 match self {
1752 Transaction::V2 {
1754 joinsplit_data: Some(joinsplit_data),
1755 ..
1756 }
1757 | Transaction::V3 {
1758 joinsplit_data: Some(joinsplit_data),
1759 ..
1760 } => Box::new(
1761 joinsplit_data
1762 .joinsplits_mut()
1763 .map(|joinsplit| &mut joinsplit.vpub_new),
1764 ),
1765 Transaction::V4 {
1767 joinsplit_data: Some(joinsplit_data),
1768 ..
1769 } => Box::new(
1770 joinsplit_data
1771 .joinsplits_mut()
1772 .map(|joinsplit| &mut joinsplit.vpub_new),
1773 ),
1774 Transaction::V1 { .. }
1776 | Transaction::V2 {
1777 joinsplit_data: None,
1778 ..
1779 }
1780 | Transaction::V3 {
1781 joinsplit_data: None,
1782 ..
1783 }
1784 | Transaction::V4 {
1785 joinsplit_data: None,
1786 ..
1787 }
1788 | Transaction::V5 { .. } => Box::new(std::iter::empty()),
1789 Transaction::V6 { .. } => Box::new(std::iter::empty()),
1790 }
1791 }
1792
1793 pub fn output_values_to_sprout_mut(
1798 &mut self,
1799 ) -> Box<dyn Iterator<Item = &mut Amount<NonNegative>> + '_> {
1800 match self {
1801 Transaction::V2 {
1803 joinsplit_data: Some(joinsplit_data),
1804 ..
1805 }
1806 | Transaction::V3 {
1807 joinsplit_data: Some(joinsplit_data),
1808 ..
1809 } => Box::new(
1810 joinsplit_data
1811 .joinsplits_mut()
1812 .map(|joinsplit| &mut joinsplit.vpub_old),
1813 ),
1814 Transaction::V4 {
1816 joinsplit_data: Some(joinsplit_data),
1817 ..
1818 } => Box::new(
1819 joinsplit_data
1820 .joinsplits_mut()
1821 .map(|joinsplit| &mut joinsplit.vpub_old),
1822 ),
1823 Transaction::V1 { .. }
1825 | Transaction::V2 {
1826 joinsplit_data: None,
1827 ..
1828 }
1829 | Transaction::V3 {
1830 joinsplit_data: None,
1831 ..
1832 }
1833 | Transaction::V4 {
1834 joinsplit_data: None,
1835 ..
1836 }
1837 | Transaction::V5 { .. } => Box::new(std::iter::empty()),
1838 Transaction::V6 { .. } => Box::new(std::iter::empty()),
1839 }
1840 }
1841
1842 pub fn output_values_mut(&mut self) -> impl Iterator<Item = &mut Amount<NonNegative>> {
1844 self.outputs_mut()
1845 .iter_mut()
1846 .map(|output| &mut output.value)
1847 }
1848
1849 pub fn orchard_shielded_data_mut(&mut self) -> Option<&mut orchard::ShieldedData> {
1852 match self {
1853 Transaction::V5 {
1854 orchard_shielded_data: Some(orchard_shielded_data),
1855 ..
1856 } => Some(orchard_shielded_data),
1857 Transaction::V6 {
1858 orchard_shielded_data: Some(orchard_shielded_data),
1859 ..
1860 } => Some(orchard_shielded_data.data_mut()),
1861
1862 Transaction::V1 { .. }
1863 | Transaction::V2 { .. }
1864 | Transaction::V3 { .. }
1865 | Transaction::V4 { .. }
1866 | Transaction::V5 {
1867 orchard_shielded_data: None,
1868 ..
1869 } => None,
1870 Transaction::V6 {
1871 orchard_shielded_data: None,
1872 ..
1873 } => None,
1874 }
1875 }
1876
1877 pub fn outputs_mut(&mut self) -> &mut Vec<transparent::Output> {
1879 match self {
1880 Transaction::V1 {
1881 ref mut outputs, ..
1882 } => outputs,
1883 Transaction::V2 {
1884 ref mut outputs, ..
1885 } => outputs,
1886 Transaction::V3 {
1887 ref mut outputs, ..
1888 } => outputs,
1889 Transaction::V4 {
1890 ref mut outputs, ..
1891 } => outputs,
1892 Transaction::V5 {
1893 ref mut outputs, ..
1894 } => outputs,
1895 Transaction::V6 {
1896 ref mut outputs, ..
1897 } => outputs,
1898 }
1899 }
1900}