1use std::{collections::HashMap, fmt, iter};
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(feature = "getblocktemplate-rpcs")]
18pub mod builder;
19
20#[cfg(any(test, feature = "proptest-impl"))]
21#[allow(clippy::unwrap_in_result)]
22pub mod arbitrary;
23#[cfg(test)]
24mod tests;
25
26pub use auth_digest::AuthDigest;
27pub use hash::{Hash, WtxId};
28pub use joinsplit::JoinSplitData;
29pub use lock_time::LockTime;
30pub use memo::Memo;
31pub use sapling::FieldNotPresent;
32pub use serialize::{
33 SerializedTransaction, MIN_TRANSPARENT_TX_SIZE, MIN_TRANSPARENT_TX_V4_SIZE,
34 MIN_TRANSPARENT_TX_V5_SIZE,
35};
36pub use sighash::{HashType, SigHash, SigHasher};
37pub use unmined::{
38 zip317, UnminedTx, UnminedTxId, VerifiedUnminedTx, MEMPOOL_TRANSACTION_COST_THRESHOLD,
39};
40use zcash_protocol::consensus;
41
42use crate::{
43 amount::{Amount, Error as AmountError, NegativeAllowed, NonNegative},
44 block, orchard,
45 parameters::{Network, NetworkUpgrade},
46 primitives::{ed25519, Bctv14Proof, Groth16Proof},
47 sapling,
48 serialization::ZcashSerialize,
49 sprout,
50 transparent::{
51 self, outputs_from_utxos,
52 CoinbaseSpendRestriction::{self, *},
53 },
54 value_balance::{ValueBalance, ValueBalanceError},
55};
56
57#[derive(Clone, Debug, PartialEq, Eq)]
69#[cfg_attr(
70 any(test, feature = "proptest-impl", feature = "elasticsearch"),
71 derive(Serialize)
72)]
73pub enum Transaction {
74 V1 {
76 inputs: Vec<transparent::Input>,
78 outputs: Vec<transparent::Output>,
80 lock_time: LockTime,
83 },
84 V2 {
86 inputs: Vec<transparent::Input>,
88 outputs: Vec<transparent::Output>,
90 lock_time: LockTime,
93 joinsplit_data: Option<JoinSplitData<Bctv14Proof>>,
95 },
96 V3 {
98 inputs: Vec<transparent::Input>,
100 outputs: Vec<transparent::Output>,
102 lock_time: LockTime,
105 expiry_height: block::Height,
107 joinsplit_data: Option<JoinSplitData<Bctv14Proof>>,
109 },
110 V4 {
112 inputs: Vec<transparent::Input>,
114 outputs: Vec<transparent::Output>,
116 lock_time: LockTime,
119 expiry_height: block::Height,
121 joinsplit_data: Option<JoinSplitData<Groth16Proof>>,
123 sapling_shielded_data: Option<sapling::ShieldedData<sapling::PerSpendAnchor>>,
125 },
126 V5 {
128 network_upgrade: NetworkUpgrade,
132 lock_time: LockTime,
135 expiry_height: block::Height,
137 inputs: Vec<transparent::Input>,
139 outputs: Vec<transparent::Output>,
141 sapling_shielded_data: Option<sapling::ShieldedData<sapling::SharedAnchor>>,
143 orchard_shielded_data: Option<orchard::ShieldedData>,
145 },
146}
147
148impl fmt::Display for Transaction {
149 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
150 let mut fmter = f.debug_struct("Transaction");
151
152 fmter.field("version", &self.version());
153
154 if let Some(network_upgrade) = self.network_upgrade() {
155 fmter.field("network_upgrade", &network_upgrade);
156 }
157
158 if let Some(lock_time) = self.lock_time() {
159 fmter.field("lock_time", &lock_time);
160 }
161
162 if let Some(expiry_height) = self.expiry_height() {
163 fmter.field("expiry_height", &expiry_height);
164 }
165
166 fmter.field("transparent_inputs", &self.inputs().len());
167 fmter.field("transparent_outputs", &self.outputs().len());
168 fmter.field("sprout_joinsplits", &self.joinsplit_count());
169 fmter.field("sapling_spends", &self.sapling_spends_per_anchor().count());
170 fmter.field("sapling_outputs", &self.sapling_outputs().count());
171 fmter.field("orchard_actions", &self.orchard_actions().count());
172
173 fmter.field("unmined_id", &self.unmined_id());
174
175 fmter.finish()
176 }
177}
178
179impl Transaction {
180 pub fn hash(&self) -> Hash {
187 Hash::from(self)
188 }
189
190 pub fn unmined_id(&self) -> UnminedTxId {
195 UnminedTxId::from(self)
196 }
197
198 pub fn sighash(
224 &self,
225 nu: NetworkUpgrade,
226 hash_type: sighash::HashType,
227 all_previous_outputs: &[transparent::Output],
228 input_index_script_code: Option<(usize, Vec<u8>)>,
229 ) -> SigHash {
230 sighash::SigHasher::new(self, nu, all_previous_outputs)
231 .sighash(hash_type, input_index_script_code)
232 }
233
234 pub fn sighasher<'a>(
236 &'a self,
237 nu: NetworkUpgrade,
238 all_previous_outputs: &'a [transparent::Output],
239 ) -> sighash::SigHasher<'a> {
240 sighash::SigHasher::new(self, nu, all_previous_outputs)
241 }
242
243 pub fn auth_digest(&self) -> Option<AuthDigest> {
250 match self {
251 Transaction::V1 { .. }
252 | Transaction::V2 { .. }
253 | Transaction::V3 { .. }
254 | Transaction::V4 { .. } => None,
255 Transaction::V5 { .. } => Some(AuthDigest::from(self)),
256 }
257 }
258
259 pub fn has_transparent_inputs(&self) -> bool {
263 !self.inputs().is_empty()
264 }
265
266 pub fn has_transparent_outputs(&self) -> bool {
268 !self.outputs().is_empty()
269 }
270
271 pub fn has_transparent_inputs_or_outputs(&self) -> bool {
273 self.has_transparent_inputs() || self.has_transparent_outputs()
274 }
275
276 pub fn has_transparent_or_shielded_inputs(&self) -> bool {
278 self.has_transparent_inputs() || self.has_shielded_inputs()
279 }
280
281 pub fn has_shielded_inputs(&self) -> bool {
285 self.joinsplit_count() > 0
286 || self.sapling_spends_per_anchor().count() > 0
287 || (self.orchard_actions().count() > 0
288 && self
289 .orchard_flags()
290 .unwrap_or_else(orchard::Flags::empty)
291 .contains(orchard::Flags::ENABLE_SPENDS))
292 }
293
294 pub fn has_shielded_outputs(&self) -> bool {
298 self.joinsplit_count() > 0
299 || self.sapling_outputs().count() > 0
300 || (self.orchard_actions().count() > 0
301 && self
302 .orchard_flags()
303 .unwrap_or_else(orchard::Flags::empty)
304 .contains(orchard::Flags::ENABLE_OUTPUTS))
305 }
306
307 pub fn has_transparent_or_shielded_outputs(&self) -> bool {
309 self.has_transparent_outputs() || self.has_shielded_outputs()
310 }
311
312 pub fn has_enough_orchard_flags(&self) -> bool {
314 if self.version() < 5 || self.orchard_actions().count() == 0 {
315 return true;
316 }
317 self.orchard_flags()
318 .unwrap_or_else(orchard::Flags::empty)
319 .intersects(orchard::Flags::ENABLE_SPENDS | orchard::Flags::ENABLE_OUTPUTS)
320 }
321
322 pub fn coinbase_spend_restriction(
325 &self,
326 network: &Network,
327 spend_height: block::Height,
328 ) -> CoinbaseSpendRestriction {
329 if self.outputs().is_empty() || network.should_allow_unshielded_coinbase_spends() {
330 CheckCoinbaseMaturity { spend_height }
333 } else {
334 DisallowCoinbaseSpend
335 }
336 }
337
338 pub fn is_overwintered(&self) -> bool {
342 match self {
343 Transaction::V1 { .. } | Transaction::V2 { .. } => false,
344 Transaction::V3 { .. } | Transaction::V4 { .. } | Transaction::V5 { .. } => true,
345 }
346 }
347
348 pub fn version(&self) -> u32 {
360 match self {
361 Transaction::V1 { .. } => 1,
362 Transaction::V2 { .. } => 2,
363 Transaction::V3 { .. } => 3,
364 Transaction::V4 { .. } => 4,
365 Transaction::V5 { .. } => 5,
366 }
367 }
368
369 pub fn lock_time(&self) -> Option<LockTime> {
371 let lock_time = match self {
372 Transaction::V1 { lock_time, .. }
373 | Transaction::V2 { lock_time, .. }
374 | Transaction::V3 { lock_time, .. }
375 | Transaction::V4 { lock_time, .. }
376 | Transaction::V5 { lock_time, .. } => *lock_time,
377 };
378
379 if lock_time == LockTime::unlocked() {
386 return None;
387 }
388
389 let has_sequence_number_enabling_lock_time = self
404 .inputs()
405 .iter()
406 .map(transparent::Input::sequence)
407 .any(|sequence_number| sequence_number != u32::MAX);
408
409 if has_sequence_number_enabling_lock_time {
410 Some(lock_time)
411 } else {
412 None
413 }
414 }
415
416 pub fn raw_lock_time(&self) -> u32 {
418 let lock_time = match self {
419 Transaction::V1 { lock_time, .. }
420 | Transaction::V2 { lock_time, .. }
421 | Transaction::V3 { lock_time, .. }
422 | Transaction::V4 { lock_time, .. }
423 | Transaction::V5 { lock_time, .. } => *lock_time,
424 };
425 let mut lock_time_bytes = Vec::new();
426 lock_time
427 .zcash_serialize(&mut lock_time_bytes)
428 .expect("lock_time should serialize");
429 u32::from_le_bytes(
430 lock_time_bytes
431 .try_into()
432 .expect("should serialize as 4 bytes"),
433 )
434 }
435
436 pub fn lock_time_is_time(&self) -> bool {
440 if let Some(lock_time) = self.lock_time() {
441 return lock_time.is_time();
442 }
443
444 false
445 }
446
447 pub fn expiry_height(&self) -> Option<block::Height> {
449 match self {
450 Transaction::V1 { .. } | Transaction::V2 { .. } => None,
451 Transaction::V3 { expiry_height, .. }
452 | Transaction::V4 { expiry_height, .. }
453 | Transaction::V5 { expiry_height, .. } => match expiry_height {
454 block::Height(0) => None,
458 block::Height(expiry_height) => Some(block::Height(*expiry_height)),
459 },
460 }
461 }
462
463 pub fn network_upgrade(&self) -> Option<NetworkUpgrade> {
468 match self {
469 Transaction::V1 { .. }
470 | Transaction::V2 { .. }
471 | Transaction::V3 { .. }
472 | Transaction::V4 { .. } => None,
473 Transaction::V5 {
474 network_upgrade, ..
475 } => Some(*network_upgrade),
476 }
477 }
478
479 pub fn inputs(&self) -> &[transparent::Input] {
483 match self {
484 Transaction::V1 { ref inputs, .. } => inputs,
485 Transaction::V2 { ref inputs, .. } => inputs,
486 Transaction::V3 { ref inputs, .. } => inputs,
487 Transaction::V4 { ref inputs, .. } => inputs,
488 Transaction::V5 { ref inputs, .. } => inputs,
489 }
490 }
491
492 pub fn spent_outpoints(&self) -> impl Iterator<Item = transparent::OutPoint> + '_ {
494 self.inputs()
495 .iter()
496 .filter_map(transparent::Input::outpoint)
497 }
498
499 pub fn outputs(&self) -> &[transparent::Output] {
501 match self {
502 Transaction::V1 { ref outputs, .. } => outputs,
503 Transaction::V2 { ref outputs, .. } => outputs,
504 Transaction::V3 { ref outputs, .. } => outputs,
505 Transaction::V4 { ref outputs, .. } => outputs,
506 Transaction::V5 { ref outputs, .. } => outputs,
507 }
508 }
509
510 pub fn is_coinbase(&self) -> bool {
514 self.inputs().len() == 1
515 && matches!(
516 self.inputs().first(),
517 Some(transparent::Input::Coinbase { .. })
518 )
519 }
520
521 pub fn is_valid_non_coinbase(&self) -> bool {
528 self.inputs()
529 .iter()
530 .all(|input| matches!(input, transparent::Input::PrevOut { .. }))
531 }
532
533 pub fn sprout_groth16_joinsplits(
537 &self,
538 ) -> Box<dyn Iterator<Item = &sprout::JoinSplit<Groth16Proof>> + '_> {
539 match self {
540 Transaction::V4 {
542 joinsplit_data: Some(joinsplit_data),
543 ..
544 } => Box::new(joinsplit_data.joinsplits()),
545
546 Transaction::V1 { .. }
548 | Transaction::V2 { .. }
549 | Transaction::V3 { .. }
550 | Transaction::V4 {
551 joinsplit_data: None,
552 ..
553 }
554 | Transaction::V5 { .. } => Box::new(std::iter::empty()),
555 }
556 }
557
558 pub fn joinsplit_count(&self) -> usize {
560 match self {
561 Transaction::V2 {
563 joinsplit_data: Some(joinsplit_data),
564 ..
565 }
566 | Transaction::V3 {
567 joinsplit_data: Some(joinsplit_data),
568 ..
569 } => joinsplit_data.joinsplits().count(),
570 Transaction::V4 {
572 joinsplit_data: Some(joinsplit_data),
573 ..
574 } => joinsplit_data.joinsplits().count(),
575 Transaction::V1 { .. }
577 | Transaction::V2 {
578 joinsplit_data: None,
579 ..
580 }
581 | Transaction::V3 {
582 joinsplit_data: None,
583 ..
584 }
585 | Transaction::V4 {
586 joinsplit_data: None,
587 ..
588 }
589 | Transaction::V5 { .. } => 0,
590 }
591 }
592
593 pub fn sprout_nullifiers(&self) -> Box<dyn Iterator<Item = &sprout::Nullifier> + '_> {
595 match self {
600 Transaction::V2 {
602 joinsplit_data: Some(joinsplit_data),
603 ..
604 }
605 | Transaction::V3 {
606 joinsplit_data: Some(joinsplit_data),
607 ..
608 } => Box::new(joinsplit_data.nullifiers()),
609 Transaction::V4 {
611 joinsplit_data: Some(joinsplit_data),
612 ..
613 } => Box::new(joinsplit_data.nullifiers()),
614 Transaction::V1 { .. }
616 | Transaction::V2 {
617 joinsplit_data: None,
618 ..
619 }
620 | Transaction::V3 {
621 joinsplit_data: None,
622 ..
623 }
624 | Transaction::V4 {
625 joinsplit_data: None,
626 ..
627 }
628 | Transaction::V5 { .. } => Box::new(std::iter::empty()),
629 }
630 }
631
632 pub fn sprout_joinsplit_pub_key(&self) -> Option<ed25519::VerificationKeyBytes> {
635 match self {
636 Transaction::V2 {
638 joinsplit_data: Some(joinsplit_data),
639 ..
640 }
641 | Transaction::V3 {
642 joinsplit_data: Some(joinsplit_data),
643 ..
644 } => Some(joinsplit_data.pub_key),
645 Transaction::V4 {
647 joinsplit_data: Some(joinsplit_data),
648 ..
649 } => Some(joinsplit_data.pub_key),
650 Transaction::V1 { .. }
652 | Transaction::V2 {
653 joinsplit_data: None,
654 ..
655 }
656 | Transaction::V3 {
657 joinsplit_data: None,
658 ..
659 }
660 | Transaction::V4 {
661 joinsplit_data: None,
662 ..
663 }
664 | Transaction::V5 { .. } => None,
665 }
666 }
667
668 pub fn has_sprout_joinsplit_data(&self) -> bool {
670 match self {
671 Transaction::V1 { .. } | Transaction::V5 { .. } => false,
673
674 Transaction::V2 { joinsplit_data, .. } | Transaction::V3 { joinsplit_data, .. } => {
676 joinsplit_data.is_some()
677 }
678
679 Transaction::V4 { joinsplit_data, .. } => joinsplit_data.is_some(),
681 }
682 }
683
684 pub fn sprout_note_commitments(
686 &self,
687 ) -> Box<dyn Iterator<Item = &sprout::commitment::NoteCommitment> + '_> {
688 match self {
689 Transaction::V2 {
691 joinsplit_data: Some(joinsplit_data),
692 ..
693 }
694 | Transaction::V3 {
695 joinsplit_data: Some(joinsplit_data),
696 ..
697 } => Box::new(joinsplit_data.note_commitments()),
698
699 Transaction::V4 {
701 joinsplit_data: Some(joinsplit_data),
702 ..
703 } => Box::new(joinsplit_data.note_commitments()),
704
705 Transaction::V2 {
707 joinsplit_data: None,
708 ..
709 }
710 | Transaction::V3 {
711 joinsplit_data: None,
712 ..
713 }
714 | Transaction::V4 {
715 joinsplit_data: None,
716 ..
717 }
718 | Transaction::V1 { .. }
719 | Transaction::V5 { .. } => Box::new(std::iter::empty()),
720 }
721 }
722
723 pub fn sapling_anchors(&self) -> Box<dyn Iterator<Item = sapling::tree::Root> + '_> {
728 match self {
731 Transaction::V4 {
732 sapling_shielded_data: Some(sapling_shielded_data),
733 ..
734 } => Box::new(sapling_shielded_data.anchors()),
735
736 Transaction::V5 {
737 sapling_shielded_data: Some(sapling_shielded_data),
738 ..
739 } => Box::new(sapling_shielded_data.anchors()),
740
741 Transaction::V1 { .. }
743 | Transaction::V2 { .. }
744 | Transaction::V3 { .. }
745 | Transaction::V4 {
746 sapling_shielded_data: None,
747 ..
748 }
749 | Transaction::V5 {
750 sapling_shielded_data: None,
751 ..
752 } => Box::new(std::iter::empty()),
753 }
754 }
755
756 pub fn sapling_spends_per_anchor(
767 &self,
768 ) -> Box<dyn Iterator<Item = sapling::Spend<sapling::PerSpendAnchor>> + '_> {
769 match self {
770 Transaction::V4 {
771 sapling_shielded_data: Some(sapling_shielded_data),
772 ..
773 } => Box::new(sapling_shielded_data.spends_per_anchor()),
774 Transaction::V5 {
775 sapling_shielded_data: Some(sapling_shielded_data),
776 ..
777 } => Box::new(sapling_shielded_data.spends_per_anchor()),
778
779 Transaction::V1 { .. }
781 | Transaction::V2 { .. }
782 | Transaction::V3 { .. }
783 | Transaction::V4 {
784 sapling_shielded_data: None,
785 ..
786 }
787 | Transaction::V5 {
788 sapling_shielded_data: None,
789 ..
790 } => Box::new(std::iter::empty()),
791 }
792 }
793
794 pub fn sapling_outputs(&self) -> Box<dyn Iterator<Item = &sapling::Output> + '_> {
797 match self {
798 Transaction::V4 {
799 sapling_shielded_data: Some(sapling_shielded_data),
800 ..
801 } => Box::new(sapling_shielded_data.outputs()),
802 Transaction::V5 {
803 sapling_shielded_data: Some(sapling_shielded_data),
804 ..
805 } => Box::new(sapling_shielded_data.outputs()),
806
807 Transaction::V1 { .. }
809 | Transaction::V2 { .. }
810 | Transaction::V3 { .. }
811 | Transaction::V4 {
812 sapling_shielded_data: None,
813 ..
814 }
815 | Transaction::V5 {
816 sapling_shielded_data: None,
817 ..
818 } => Box::new(std::iter::empty()),
819 }
820 }
821
822 pub fn sapling_nullifiers(&self) -> Box<dyn Iterator<Item = &sapling::Nullifier> + '_> {
824 match self {
827 Transaction::V4 {
829 sapling_shielded_data: Some(sapling_shielded_data),
830 ..
831 } => Box::new(sapling_shielded_data.nullifiers()),
832 Transaction::V5 {
833 sapling_shielded_data: Some(sapling_shielded_data),
834 ..
835 } => Box::new(sapling_shielded_data.nullifiers()),
836
837 Transaction::V1 { .. }
839 | Transaction::V2 { .. }
840 | Transaction::V3 { .. }
841 | Transaction::V4 {
842 sapling_shielded_data: None,
843 ..
844 }
845 | Transaction::V5 {
846 sapling_shielded_data: None,
847 ..
848 } => Box::new(std::iter::empty()),
849 }
850 }
851
852 pub fn sapling_note_commitments(&self) -> Box<dyn Iterator<Item = &jubjub::Fq> + '_> {
854 match self {
857 Transaction::V4 {
859 sapling_shielded_data: Some(sapling_shielded_data),
860 ..
861 } => Box::new(sapling_shielded_data.note_commitments()),
862 Transaction::V5 {
863 sapling_shielded_data: Some(sapling_shielded_data),
864 ..
865 } => Box::new(sapling_shielded_data.note_commitments()),
866
867 Transaction::V1 { .. }
869 | Transaction::V2 { .. }
870 | Transaction::V3 { .. }
871 | Transaction::V4 {
872 sapling_shielded_data: None,
873 ..
874 }
875 | Transaction::V5 {
876 sapling_shielded_data: None,
877 ..
878 } => Box::new(std::iter::empty()),
879 }
880 }
881
882 pub fn has_sapling_shielded_data(&self) -> bool {
884 match self {
885 Transaction::V1 { .. } | Transaction::V2 { .. } | Transaction::V3 { .. } => false,
886 Transaction::V4 {
887 sapling_shielded_data,
888 ..
889 } => sapling_shielded_data.is_some(),
890 Transaction::V5 {
891 sapling_shielded_data,
892 ..
893 } => sapling_shielded_data.is_some(),
894 }
895 }
896
897 pub fn orchard_shielded_data(&self) -> Option<&orchard::ShieldedData> {
902 match self {
903 Transaction::V5 {
905 orchard_shielded_data,
906 ..
907 } => orchard_shielded_data.as_ref(),
908
909 Transaction::V1 { .. }
911 | Transaction::V2 { .. }
912 | Transaction::V3 { .. }
913 | Transaction::V4 { .. } => None,
914 }
915 }
916
917 pub fn orchard_actions(&self) -> impl Iterator<Item = &orchard::Action> {
920 self.orchard_shielded_data()
921 .into_iter()
922 .flat_map(orchard::ShieldedData::actions)
923 }
924
925 pub fn orchard_nullifiers(&self) -> impl Iterator<Item = &orchard::Nullifier> {
928 self.orchard_shielded_data()
929 .into_iter()
930 .flat_map(orchard::ShieldedData::nullifiers)
931 }
932
933 pub fn orchard_note_commitments(&self) -> impl Iterator<Item = &pallas::Base> {
936 self.orchard_shielded_data()
937 .into_iter()
938 .flat_map(orchard::ShieldedData::note_commitments)
939 }
940
941 pub fn orchard_flags(&self) -> Option<orchard::shielded_data::Flags> {
944 self.orchard_shielded_data()
945 .map(|orchard_shielded_data| orchard_shielded_data.flags)
946 }
947
948 pub fn has_orchard_shielded_data(&self) -> bool {
951 self.orchard_shielded_data().is_some()
952 }
953
954 #[allow(clippy::unwrap_in_result)]
961 fn transparent_value_balance_from_outputs(
962 &self,
963 outputs: &HashMap<transparent::OutPoint, transparent::Output>,
964 ) -> Result<ValueBalance<NegativeAllowed>, ValueBalanceError> {
965 let input_value = self
966 .inputs()
967 .iter()
968 .map(|i| i.value_from_outputs(outputs))
969 .sum::<Result<Amount<NonNegative>, AmountError>>()
970 .map_err(ValueBalanceError::Transparent)?
971 .constrain()
972 .expect("conversion from NonNegative to NegativeAllowed is always valid");
973
974 let output_value = self
975 .outputs()
976 .iter()
977 .map(|o| o.value())
978 .sum::<Result<Amount<NonNegative>, AmountError>>()
979 .map_err(ValueBalanceError::Transparent)?
980 .constrain()
981 .expect("conversion from NonNegative to NegativeAllowed is always valid");
982
983 (input_value - output_value)
984 .map(ValueBalance::from_transparent_amount)
985 .map_err(ValueBalanceError::Transparent)
986 }
987
988 pub fn output_values_to_sprout(&self) -> Box<dyn Iterator<Item = &Amount<NonNegative>> + '_> {
994 match self {
995 Transaction::V2 {
997 joinsplit_data: Some(joinsplit_data),
998 ..
999 }
1000 | Transaction::V3 {
1001 joinsplit_data: Some(joinsplit_data),
1002 ..
1003 } => Box::new(
1004 joinsplit_data
1005 .joinsplits()
1006 .map(|joinsplit| &joinsplit.vpub_old),
1007 ),
1008 Transaction::V4 {
1010 joinsplit_data: Some(joinsplit_data),
1011 ..
1012 } => Box::new(
1013 joinsplit_data
1014 .joinsplits()
1015 .map(|joinsplit| &joinsplit.vpub_old),
1016 ),
1017 Transaction::V1 { .. }
1019 | Transaction::V2 {
1020 joinsplit_data: None,
1021 ..
1022 }
1023 | Transaction::V3 {
1024 joinsplit_data: None,
1025 ..
1026 }
1027 | Transaction::V4 {
1028 joinsplit_data: None,
1029 ..
1030 }
1031 | Transaction::V5 { .. } => Box::new(std::iter::empty()),
1032 }
1033 }
1034
1035 pub fn input_values_from_sprout(&self) -> Box<dyn Iterator<Item = &Amount<NonNegative>> + '_> {
1041 match self {
1042 Transaction::V2 {
1044 joinsplit_data: Some(joinsplit_data),
1045 ..
1046 }
1047 | Transaction::V3 {
1048 joinsplit_data: Some(joinsplit_data),
1049 ..
1050 } => Box::new(
1051 joinsplit_data
1052 .joinsplits()
1053 .map(|joinsplit| &joinsplit.vpub_new),
1054 ),
1055 Transaction::V4 {
1057 joinsplit_data: Some(joinsplit_data),
1058 ..
1059 } => Box::new(
1060 joinsplit_data
1061 .joinsplits()
1062 .map(|joinsplit| &joinsplit.vpub_new),
1063 ),
1064 Transaction::V1 { .. }
1066 | Transaction::V2 {
1067 joinsplit_data: None,
1068 ..
1069 }
1070 | Transaction::V3 {
1071 joinsplit_data: None,
1072 ..
1073 }
1074 | Transaction::V4 {
1075 joinsplit_data: None,
1076 ..
1077 }
1078 | Transaction::V5 { .. } => Box::new(std::iter::empty()),
1079 }
1080 }
1081
1082 fn sprout_joinsplit_value_balances(
1091 &self,
1092 ) -> impl Iterator<Item = ValueBalance<NegativeAllowed>> + '_ {
1093 let joinsplit_value_balances = match self {
1094 Transaction::V2 {
1095 joinsplit_data: Some(joinsplit_data),
1096 ..
1097 }
1098 | Transaction::V3 {
1099 joinsplit_data: Some(joinsplit_data),
1100 ..
1101 } => joinsplit_data.joinsplit_value_balances(),
1102 Transaction::V4 {
1103 joinsplit_data: Some(joinsplit_data),
1104 ..
1105 } => joinsplit_data.joinsplit_value_balances(),
1106 Transaction::V1 { .. }
1107 | Transaction::V2 {
1108 joinsplit_data: None,
1109 ..
1110 }
1111 | Transaction::V3 {
1112 joinsplit_data: None,
1113 ..
1114 }
1115 | Transaction::V4 {
1116 joinsplit_data: None,
1117 ..
1118 }
1119 | Transaction::V5 { .. } => Box::new(iter::empty()),
1120 };
1121
1122 joinsplit_value_balances.map(ValueBalance::from_sprout_amount)
1123 }
1124
1125 fn sprout_value_balance(&self) -> Result<ValueBalance<NegativeAllowed>, ValueBalanceError> {
1137 self.sprout_joinsplit_value_balances().sum()
1138 }
1139
1140 pub fn sapling_value_balance(&self) -> ValueBalance<NegativeAllowed> {
1152 let sapling_value_balance = match self {
1153 Transaction::V4 {
1154 sapling_shielded_data: Some(sapling_shielded_data),
1155 ..
1156 } => sapling_shielded_data.value_balance,
1157 Transaction::V5 {
1158 sapling_shielded_data: Some(sapling_shielded_data),
1159 ..
1160 } => sapling_shielded_data.value_balance,
1161
1162 Transaction::V1 { .. }
1163 | Transaction::V2 { .. }
1164 | Transaction::V3 { .. }
1165 | Transaction::V4 {
1166 sapling_shielded_data: None,
1167 ..
1168 }
1169 | Transaction::V5 {
1170 sapling_shielded_data: None,
1171 ..
1172 } => Amount::zero(),
1173 };
1174
1175 ValueBalance::from_sapling_amount(sapling_value_balance)
1176 }
1177
1178 pub fn orchard_value_balance(&self) -> ValueBalance<NegativeAllowed> {
1190 let orchard_value_balance = self
1191 .orchard_shielded_data()
1192 .map(|shielded_data| shielded_data.value_balance)
1193 .unwrap_or_else(Amount::zero);
1194
1195 ValueBalance::from_orchard_amount(orchard_value_balance)
1196 }
1197
1198 pub(crate) fn value_balance_from_outputs(
1200 &self,
1201 outputs: &HashMap<transparent::OutPoint, transparent::Output>,
1202 ) -> Result<ValueBalance<NegativeAllowed>, ValueBalanceError> {
1203 self.transparent_value_balance_from_outputs(outputs)?
1204 + self.sprout_value_balance()?
1205 + self.sapling_value_balance()
1206 + self.orchard_value_balance()
1207 }
1208
1209 pub fn value_balance(
1230 &self,
1231 utxos: &HashMap<transparent::OutPoint, transparent::Utxo>,
1232 ) -> Result<ValueBalance<NegativeAllowed>, ValueBalanceError> {
1233 self.value_balance_from_outputs(&outputs_from_utxos(utxos.clone()))
1234 }
1235
1236 pub(crate) fn to_librustzcash(
1242 &self,
1243 nu: NetworkUpgrade,
1244 ) -> Result<zcash_primitives::transaction::Transaction, crate::Error> {
1245 if self.network_upgrade().is_some_and(|tx_nu| tx_nu != nu) {
1246 return Err(crate::Error::InvalidConsensusBranchId);
1247 }
1248
1249 let Some(branch_id) = nu.branch_id() else {
1250 return Err(crate::Error::InvalidConsensusBranchId);
1251 };
1252
1253 let Ok(branch_id) = consensus::BranchId::try_from(branch_id) else {
1254 return Err(crate::Error::InvalidConsensusBranchId);
1255 };
1256
1257 Ok(zcash_primitives::transaction::Transaction::read(
1258 &self.zcash_serialize_to_vec()?[..],
1259 branch_id,
1260 )?)
1261 }
1262
1263 pub fn has_shielded_data(&self) -> bool {
1267 self.has_shielded_inputs() || self.has_shielded_outputs()
1268 }
1269}
1270
1271#[cfg(any(test, feature = "proptest-impl"))]
1272impl Transaction {
1273 pub fn update_network_upgrade(&mut self, nu: NetworkUpgrade) -> Result<(), &str> {
1279 match self {
1280 Transaction::V1 { .. }
1281 | Transaction::V2 { .. }
1282 | Transaction::V3 { .. }
1283 | Transaction::V4 { .. } => Err(
1284 "Updating the network upgrade for V1, V2, V3 and V4 transactions is not possible.",
1285 ),
1286 Transaction::V5 {
1287 ref mut network_upgrade,
1288 ..
1289 } => {
1290 *network_upgrade = nu;
1291 Ok(())
1292 }
1293 }
1294 }
1295
1296 pub fn expiry_height_mut(&mut self) -> &mut block::Height {
1302 match self {
1303 Transaction::V1 { .. } | Transaction::V2 { .. } => {
1304 panic!("v1 and v2 transactions are not supported")
1305 }
1306 Transaction::V3 {
1307 ref mut expiry_height,
1308 ..
1309 }
1310 | Transaction::V4 {
1311 ref mut expiry_height,
1312 ..
1313 }
1314 | Transaction::V5 {
1315 ref mut expiry_height,
1316 ..
1317 } => expiry_height,
1318 }
1319 }
1320
1321 pub fn inputs_mut(&mut self) -> &mut Vec<transparent::Input> {
1323 match self {
1324 Transaction::V1 { ref mut inputs, .. } => inputs,
1325 Transaction::V2 { ref mut inputs, .. } => inputs,
1326 Transaction::V3 { ref mut inputs, .. } => inputs,
1327 Transaction::V4 { ref mut inputs, .. } => inputs,
1328 Transaction::V5 { ref mut inputs, .. } => inputs,
1329 }
1330 }
1331
1332 pub fn orchard_value_balance_mut(&mut self) -> Option<&mut Amount<NegativeAllowed>> {
1337 self.orchard_shielded_data_mut()
1338 .map(|shielded_data| &mut shielded_data.value_balance)
1339 }
1340
1341 pub fn sapling_value_balance_mut(&mut self) -> Option<&mut Amount<NegativeAllowed>> {
1346 match self {
1347 Transaction::V4 {
1348 sapling_shielded_data: Some(sapling_shielded_data),
1349 ..
1350 } => Some(&mut sapling_shielded_data.value_balance),
1351 Transaction::V5 {
1352 sapling_shielded_data: Some(sapling_shielded_data),
1353 ..
1354 } => Some(&mut sapling_shielded_data.value_balance),
1355 Transaction::V1 { .. }
1356 | Transaction::V2 { .. }
1357 | Transaction::V3 { .. }
1358 | Transaction::V4 {
1359 sapling_shielded_data: None,
1360 ..
1361 }
1362 | Transaction::V5 {
1363 sapling_shielded_data: None,
1364 ..
1365 } => None,
1366 }
1367 }
1368
1369 pub fn input_values_from_sprout_mut(
1374 &mut self,
1375 ) -> Box<dyn Iterator<Item = &mut Amount<NonNegative>> + '_> {
1376 match self {
1377 Transaction::V2 {
1379 joinsplit_data: Some(joinsplit_data),
1380 ..
1381 }
1382 | Transaction::V3 {
1383 joinsplit_data: Some(joinsplit_data),
1384 ..
1385 } => Box::new(
1386 joinsplit_data
1387 .joinsplits_mut()
1388 .map(|joinsplit| &mut joinsplit.vpub_new),
1389 ),
1390 Transaction::V4 {
1392 joinsplit_data: Some(joinsplit_data),
1393 ..
1394 } => Box::new(
1395 joinsplit_data
1396 .joinsplits_mut()
1397 .map(|joinsplit| &mut joinsplit.vpub_new),
1398 ),
1399 Transaction::V1 { .. }
1401 | Transaction::V2 {
1402 joinsplit_data: None,
1403 ..
1404 }
1405 | Transaction::V3 {
1406 joinsplit_data: None,
1407 ..
1408 }
1409 | Transaction::V4 {
1410 joinsplit_data: None,
1411 ..
1412 }
1413 | Transaction::V5 { .. } => Box::new(std::iter::empty()),
1414 }
1415 }
1416
1417 pub fn output_values_to_sprout_mut(
1422 &mut self,
1423 ) -> Box<dyn Iterator<Item = &mut Amount<NonNegative>> + '_> {
1424 match self {
1425 Transaction::V2 {
1427 joinsplit_data: Some(joinsplit_data),
1428 ..
1429 }
1430 | Transaction::V3 {
1431 joinsplit_data: Some(joinsplit_data),
1432 ..
1433 } => Box::new(
1434 joinsplit_data
1435 .joinsplits_mut()
1436 .map(|joinsplit| &mut joinsplit.vpub_old),
1437 ),
1438 Transaction::V4 {
1440 joinsplit_data: Some(joinsplit_data),
1441 ..
1442 } => Box::new(
1443 joinsplit_data
1444 .joinsplits_mut()
1445 .map(|joinsplit| &mut joinsplit.vpub_old),
1446 ),
1447 Transaction::V1 { .. }
1449 | Transaction::V2 {
1450 joinsplit_data: None,
1451 ..
1452 }
1453 | Transaction::V3 {
1454 joinsplit_data: None,
1455 ..
1456 }
1457 | Transaction::V4 {
1458 joinsplit_data: None,
1459 ..
1460 }
1461 | Transaction::V5 { .. } => Box::new(std::iter::empty()),
1462 }
1463 }
1464
1465 pub fn output_values_mut(&mut self) -> impl Iterator<Item = &mut Amount<NonNegative>> {
1467 self.outputs_mut()
1468 .iter_mut()
1469 .map(|output| &mut output.value)
1470 }
1471
1472 pub fn orchard_shielded_data_mut(&mut self) -> Option<&mut orchard::ShieldedData> {
1475 match self {
1476 Transaction::V5 {
1477 orchard_shielded_data: Some(orchard_shielded_data),
1478 ..
1479 } => Some(orchard_shielded_data),
1480
1481 Transaction::V1 { .. }
1482 | Transaction::V2 { .. }
1483 | Transaction::V3 { .. }
1484 | Transaction::V4 { .. }
1485 | Transaction::V5 {
1486 orchard_shielded_data: None,
1487 ..
1488 } => None,
1489 }
1490 }
1491
1492 pub fn outputs_mut(&mut self) -> &mut Vec<transparent::Output> {
1494 match self {
1495 Transaction::V1 {
1496 ref mut outputs, ..
1497 } => outputs,
1498 Transaction::V2 {
1499 ref mut outputs, ..
1500 } => outputs,
1501 Transaction::V3 {
1502 ref mut outputs, ..
1503 } => outputs,
1504 Transaction::V4 {
1505 ref mut outputs, ..
1506 } => outputs,
1507 Transaction::V5 {
1508 ref mut outputs, ..
1509 } => outputs,
1510 }
1511 }
1512}