1use std::sync::Arc;
4
5use crate::methods::arrayhex;
6use chrono::{DateTime, Utc};
7use derive_getters::Getters;
8use derive_new::new;
9use hex::ToHex;
10
11use zcash_script::script::Asm;
12use zebra_chain::{
13 amount::{self, Amount, NegativeOrZero, NonNegative},
14 block::{self, merkle::AUTH_DIGEST_PLACEHOLDER, Height},
15 orchard,
16 parameters::Network,
17 primitives::ed25519,
18 sapling::ValueCommitment,
19 serialization::ZcashSerialize,
20 transaction::{self, SerializedTransaction, Transaction, UnminedTx, VerifiedUnminedTx},
21 transparent::Script,
22};
23use zebra_script::Sigops;
24use zebra_state::IntoDisk;
25
26use super::super::opthex;
27use super::zec::Zec;
28
29#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
31#[serde(bound = "FeeConstraint: amount::Constraint + Clone")]
32pub struct TransactionTemplate<FeeConstraint>
33where
34 FeeConstraint: amount::Constraint + Clone + Copy,
35{
36 #[serde(with = "hex")]
38 pub(crate) data: SerializedTransaction,
39
40 #[serde(with = "hex")]
42 #[getter(copy)]
43 pub(crate) hash: transaction::Hash,
44
45 #[serde(rename = "authdigest")]
47 #[serde(with = "hex")]
48 #[getter(copy)]
49 pub(crate) auth_digest: transaction::AuthDigest,
50
51 pub(crate) depends: Vec<u16>,
58
59 #[getter(copy)]
65 pub(crate) fee: Amount<FeeConstraint>,
66
67 pub(crate) sigops: u32,
69
70 pub(crate) required: bool,
74}
75
76impl From<&VerifiedUnminedTx> for TransactionTemplate<NonNegative> {
78 fn from(tx: &VerifiedUnminedTx) -> Self {
79 assert!(
80 !tx.transaction.transaction.is_coinbase(),
81 "unexpected coinbase transaction in mempool"
82 );
83
84 Self {
85 data: tx.transaction.transaction.as_ref().into(),
86 hash: tx.transaction.id.mined_id(),
87 auth_digest: tx
88 .transaction
89 .id
90 .auth_digest()
91 .unwrap_or(AUTH_DIGEST_PLACEHOLDER),
92
93 depends: Vec::new(),
95
96 fee: tx.miner_fee,
97
98 sigops: tx.legacy_sigop_count,
99
100 required: false,
102 }
103 }
104}
105
106impl From<VerifiedUnminedTx> for TransactionTemplate<NonNegative> {
107 fn from(tx: VerifiedUnminedTx) -> Self {
108 Self::from(&tx)
109 }
110}
111
112impl TransactionTemplate<NegativeOrZero> {
113 pub fn from_coinbase(tx: &UnminedTx, miner_fee: Amount<NonNegative>) -> Self {
119 assert!(
120 tx.transaction.is_coinbase(),
121 "invalid generated coinbase transaction: \
122 must have exactly one input, which must be a coinbase input",
123 );
124
125 let miner_fee = (-miner_fee)
126 .constrain()
127 .expect("negating a NonNegative amount always results in a valid NegativeOrZero");
128
129 Self {
130 data: tx.transaction.as_ref().into(),
131 hash: tx.id.mined_id(),
132 auth_digest: tx.id.auth_digest().unwrap_or(AUTH_DIGEST_PLACEHOLDER),
133
134 depends: Vec::new(),
136
137 fee: miner_fee,
138
139 sigops: tx.sigops().expect("sigops count should be valid"),
140
141 required: true,
143 }
144 }
145}
146
147#[allow(clippy::too_many_arguments)]
150#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
151pub struct TransactionObject {
152 #[serde(skip_serializing_if = "Option::is_none")]
155 #[getter(copy)]
156 pub(crate) in_active_chain: Option<bool>,
157 #[serde(with = "hex")]
159 pub(crate) hex: SerializedTransaction,
160 #[serde(skip_serializing_if = "Option::is_none")]
163 #[getter(copy)]
164 pub(crate) height: Option<i32>,
165 #[serde(skip_serializing_if = "Option::is_none")]
169 #[getter(copy)]
170 pub(crate) confirmations: Option<u32>,
171
172 #[serde(rename = "vin")]
174 pub(crate) inputs: Vec<Input>,
175
176 #[serde(rename = "vout")]
178 pub(crate) outputs: Vec<Output>,
179
180 #[serde(rename = "vShieldedSpend")]
182 pub(crate) shielded_spends: Vec<ShieldedSpend>,
183
184 #[serde(rename = "vShieldedOutput")]
186 pub(crate) shielded_outputs: Vec<ShieldedOutput>,
187
188 #[serde(rename = "vjoinsplit")]
190 pub(crate) joinsplits: Vec<JoinSplit>,
191
192 #[serde(
194 skip_serializing_if = "Option::is_none",
195 with = "opthex",
196 default,
197 rename = "bindingSig"
198 )]
199 #[getter(copy)]
200 pub(crate) binding_sig: Option<[u8; 64]>,
201
202 #[serde(
204 skip_serializing_if = "Option::is_none",
205 with = "opthex",
206 default,
207 rename = "joinSplitPubKey"
208 )]
209 #[getter(copy)]
210 pub(crate) joinsplit_pub_key: Option<[u8; 32]>,
211
212 #[serde(
214 skip_serializing_if = "Option::is_none",
215 with = "opthex",
216 default,
217 rename = "joinSplitSig"
218 )]
219 #[getter(copy)]
220 pub(crate) joinsplit_sig: Option<[u8; ed25519::Signature::BYTE_SIZE]>,
221
222 #[serde(rename = "orchard", skip_serializing_if = "Option::is_none")]
224 pub(crate) orchard: Option<Orchard>,
225
226 #[serde(rename = "valueBalance", skip_serializing_if = "Option::is_none")]
228 #[getter(copy)]
229 pub(crate) value_balance: Option<f64>,
230
231 #[serde(rename = "valueBalanceZat", skip_serializing_if = "Option::is_none")]
233 #[getter(copy)]
234 pub(crate) value_balance_zat: Option<i64>,
235
236 #[serde(skip_serializing_if = "Option::is_none")]
238 #[getter(copy)]
239 pub(crate) size: Option<i64>,
240
241 #[serde(skip_serializing_if = "Option::is_none")]
243 #[getter(copy)]
244 pub(crate) time: Option<i64>,
245
246 #[serde(with = "hex")]
248 #[getter(copy)]
249 pub txid: transaction::Hash,
250
251 #[serde(
254 rename = "authdigest",
255 with = "opthex",
256 skip_serializing_if = "Option::is_none",
257 default
258 )]
259 #[getter(copy)]
260 pub(crate) auth_digest: Option<transaction::AuthDigest>,
261
262 pub(crate) overwintered: bool,
264
265 pub(crate) version: u32,
267
268 #[serde(
270 rename = "versiongroupid",
271 with = "opthex",
272 skip_serializing_if = "Option::is_none",
273 default
274 )]
275 pub(crate) version_group_id: Option<Vec<u8>>,
276
277 #[serde(rename = "locktime")]
279 pub(crate) lock_time: u32,
280
281 #[serde(rename = "expiryheight", skip_serializing_if = "Option::is_none")]
285 #[getter(copy)]
286 pub(crate) expiry_height: Option<Height>,
287
288 #[serde(
290 rename = "blockhash",
291 with = "opthex",
292 skip_serializing_if = "Option::is_none",
293 default
294 )]
295 #[getter(copy)]
296 pub(crate) block_hash: Option<block::Hash>,
297
298 #[serde(rename = "blocktime", skip_serializing_if = "Option::is_none")]
300 #[getter(copy)]
301 pub(crate) block_time: Option<i64>,
302}
303
304#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
306#[serde(untagged)]
307pub enum Input {
308 Coinbase {
310 #[serde(with = "hex")]
312 coinbase: Vec<u8>,
313 sequence: u32,
315 },
316 NonCoinbase {
318 txid: String,
320 vout: u32,
322 #[serde(rename = "scriptSig")]
324 script_sig: ScriptSig,
325 sequence: u32,
327 #[serde(skip_serializing_if = "Option::is_none")]
329 value: Option<f64>,
330 #[serde(rename = "valueSat", skip_serializing_if = "Option::is_none")]
332 value_zat: Option<i64>,
333 #[serde(skip_serializing_if = "Option::is_none")]
335 address: Option<String>,
336 },
337}
338
339#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
341pub struct Output {
342 value: f64,
344 #[serde(rename = "valueZat")]
346 value_zat: i64,
347 n: u32,
349 #[serde(rename = "scriptPubKey")]
351 script_pub_key: ScriptPubKey,
352}
353
354#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
356pub struct OutputObject {
357 #[serde(rename = "bestblock")]
358 best_block: String,
359 confirmations: u32,
360 value: f64,
361 #[serde(rename = "scriptPubKey")]
362 script_pub_key: ScriptPubKey,
363 version: u32,
364 coinbase: bool,
365}
366impl OutputObject {
367 pub fn from_output(
368 output: &zebra_chain::transparent::Output,
369 best_block: String,
370 confirmations: u32,
371 version: u32,
372 coinbase: bool,
373 network: &Network,
374 ) -> Self {
375 let lock_script = &output.lock_script;
376 let addresses = output.address(network).map(|addr| vec![addr.to_string()]);
377 let req_sigs = addresses.as_ref().map(|a| a.len() as u32);
378
379 let script_pub_key = ScriptPubKey::new(
380 zcash_script::script::Code(lock_script.as_raw_bytes().to_vec()).to_asm(false),
381 lock_script.clone(),
382 req_sigs,
383 zcash_script::script::Code(lock_script.as_raw_bytes().to_vec())
384 .to_component()
385 .ok()
386 .and_then(|c| c.refine().ok())
387 .and_then(|component| zcash_script::solver::standard(&component))
388 .map(|kind| match kind {
389 zcash_script::solver::ScriptKind::PubKeyHash { .. } => "pubkeyhash",
390 zcash_script::solver::ScriptKind::ScriptHash { .. } => "scripthash",
391 zcash_script::solver::ScriptKind::MultiSig { .. } => "multisig",
392 zcash_script::solver::ScriptKind::NullData { .. } => "nulldata",
393 zcash_script::solver::ScriptKind::PubKey { .. } => "pubkey",
394 })
395 .unwrap_or("nonstandard")
396 .to_string(),
397 addresses,
398 );
399
400 Self {
401 best_block,
402 confirmations,
403 value: crate::methods::types::zec::Zec::from(output.value()).lossy_zec(),
404 script_pub_key,
405 version,
406 coinbase,
407 }
408 }
409}
410
411#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
413pub struct ScriptPubKey {
414 asm: String,
416 #[serde(with = "hex")]
418 hex: Script,
419 #[serde(rename = "reqSigs")]
421 #[serde(default)]
422 #[serde(skip_serializing_if = "Option::is_none")]
423 #[getter(copy)]
424 req_sigs: Option<u32>,
425 r#type: String,
427 #[serde(default)]
429 #[serde(skip_serializing_if = "Option::is_none")]
430 addresses: Option<Vec<String>>,
431}
432
433#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
435pub struct ScriptSig {
436 asm: String,
438 hex: Script,
440}
441
442#[allow(clippy::too_many_arguments)]
444#[serde_with::serde_as]
445#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
446pub struct JoinSplit {
447 #[serde(rename = "vpub_old")]
449 old_public_value: f64,
450 #[serde(rename = "vpub_oldZat")]
452 old_public_value_zat: i64,
453 #[serde(rename = "vpub_new")]
455 new_public_value: f64,
456 #[serde(rename = "vpub_newZat")]
458 new_public_value_zat: i64,
459 #[serde(with = "hex")]
461 #[getter(copy)]
462 anchor: [u8; 32],
463 #[serde_as(as = "Vec<serde_with::hex::Hex>")]
465 nullifiers: Vec<[u8; 32]>,
466 #[serde_as(as = "Vec<serde_with::hex::Hex>")]
468 commitments: Vec<[u8; 32]>,
469 #[serde(rename = "onetimePubKey")]
471 #[serde(with = "hex")]
472 #[getter(copy)]
473 one_time_pubkey: [u8; 32],
474 #[serde(rename = "randomSeed")]
476 #[serde(with = "hex")]
477 #[getter(copy)]
478 random_seed: [u8; 32],
479 #[serde_as(as = "Vec<serde_with::hex::Hex>")]
481 macs: Vec<[u8; 32]>,
482 #[serde(with = "hex")]
484 proof: Vec<u8>,
485 #[serde_as(as = "Vec<serde_with::hex::Hex>")]
487 ciphertexts: Vec<Vec<u8>>,
488}
489
490#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
492pub struct ShieldedSpend {
493 #[serde(with = "hex")]
495 #[getter(skip)]
496 cv: ValueCommitment,
497 #[serde(with = "hex")]
499 #[getter(copy)]
500 anchor: [u8; 32],
501 #[serde(with = "hex")]
503 #[getter(copy)]
504 nullifier: [u8; 32],
505 #[serde(with = "hex")]
507 #[getter(copy)]
508 rk: [u8; 32],
509 #[serde(with = "hex")]
511 #[getter(copy)]
512 proof: [u8; 192],
513 #[serde(rename = "spendAuthSig", with = "hex")]
515 #[getter(copy)]
516 spend_auth_sig: [u8; 64],
517}
518
519impl ShieldedSpend {
521 pub fn cv(&self) -> ValueCommitment {
523 self.cv.clone()
524 }
525}
526
527#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
529pub struct ShieldedOutput {
530 #[serde(with = "hex")]
532 #[getter(skip)]
533 cv: ValueCommitment,
534 #[serde(rename = "cmu", with = "hex")]
536 cm_u: [u8; 32],
537 #[serde(rename = "ephemeralKey", with = "hex")]
539 ephemeral_key: [u8; 32],
540 #[serde(rename = "encCiphertext", with = "arrayhex")]
542 enc_ciphertext: [u8; 580],
543 #[serde(rename = "outCiphertext", with = "hex")]
545 out_ciphertext: [u8; 80],
546 #[serde(with = "hex")]
548 proof: [u8; 192],
549}
550
551impl ShieldedOutput {
553 pub fn cv(&self) -> ValueCommitment {
555 self.cv.clone()
556 }
557}
558
559#[serde_with::serde_as]
561#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
562pub struct Orchard {
563 actions: Vec<OrchardAction>,
565 #[serde(rename = "valueBalance")]
567 value_balance: f64,
568 #[serde(rename = "valueBalanceZat")]
570 value_balance_zat: i64,
571 #[serde(skip_serializing_if = "Option::is_none")]
573 flags: Option<OrchardFlags>,
574 #[serde_as(as = "Option<serde_with::hex::Hex>")]
576 #[serde(skip_serializing_if = "Option::is_none")]
577 #[getter(copy)]
578 anchor: Option<[u8; 32]>,
579 #[serde_as(as = "Option<serde_with::hex::Hex>")]
581 #[serde(skip_serializing_if = "Option::is_none")]
582 proof: Option<Vec<u8>>,
583 #[serde(rename = "bindingSig")]
585 #[serde(skip_serializing_if = "Option::is_none")]
586 #[serde_as(as = "Option<serde_with::hex::Hex>")]
587 #[getter(copy)]
588 binding_sig: Option<[u8; 64]>,
589}
590
591#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
593pub struct OrchardFlags {
594 #[serde(rename = "enableOutputs")]
596 enable_outputs: bool,
597 #[serde(rename = "enableSpends")]
599 enable_spends: bool,
600}
601
602#[allow(clippy::too_many_arguments)]
604#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
605pub struct OrchardAction {
606 #[serde(with = "hex")]
608 cv: [u8; 32],
609 #[serde(with = "hex")]
611 nullifier: [u8; 32],
612 #[serde(with = "hex")]
614 rk: [u8; 32],
615 #[serde(rename = "cmx", with = "hex")]
617 cm_x: [u8; 32],
618 #[serde(rename = "ephemeralKey", with = "hex")]
620 ephemeral_key: [u8; 32],
621 #[serde(rename = "encCiphertext", with = "arrayhex")]
623 enc_ciphertext: [u8; 580],
624 #[serde(rename = "spendAuthSig", with = "hex")]
626 spend_auth_sig: [u8; 64],
627 #[serde(rename = "outCiphertext", with = "hex")]
629 out_ciphertext: [u8; 80],
630}
631
632impl Default for TransactionObject {
633 fn default() -> Self {
634 Self {
635 hex: SerializedTransaction::from(
636 [0u8; zebra_chain::transaction::MIN_TRANSPARENT_TX_SIZE as usize].to_vec(),
637 ),
638 height: Option::default(),
639 confirmations: Option::default(),
640 inputs: Vec::new(),
641 outputs: Vec::new(),
642 shielded_spends: Vec::new(),
643 shielded_outputs: Vec::new(),
644 joinsplits: Vec::new(),
645 orchard: None,
646 binding_sig: None,
647 joinsplit_pub_key: None,
648 joinsplit_sig: None,
649 value_balance: None,
650 value_balance_zat: None,
651 size: None,
652 time: None,
653 txid: transaction::Hash::from([0u8; 32]),
654 in_active_chain: None,
655 auth_digest: None,
656 overwintered: false,
657 version: 4,
658 version_group_id: None,
659 lock_time: 0,
660 expiry_height: None,
661 block_hash: None,
662 block_time: None,
663 }
664 }
665}
666
667impl TransactionObject {
668 #[allow(clippy::unwrap_in_result)]
670 #[allow(clippy::too_many_arguments)]
671 pub fn from_transaction(
672 tx: Arc<Transaction>,
673 height: Option<block::Height>,
674 confirmations: Option<u32>,
675 network: &Network,
676 block_time: Option<DateTime<Utc>>,
677 block_hash: Option<block::Hash>,
678 in_active_chain: Option<bool>,
679 txid: transaction::Hash,
680 ) -> Self {
681 let block_time = block_time.map(|bt| bt.timestamp());
682 Self {
683 hex: tx.clone().into(),
684 height: if in_active_chain.unwrap_or_default() {
685 height.map(|height| height.0 as i32)
686 } else if block_hash.is_some() {
687 Some(-1)
689 } else {
690 None
692 },
693 confirmations: if in_active_chain.unwrap_or_default() {
694 confirmations
695 } else if block_hash.is_some() {
696 Some(0)
698 } else {
699 None
701 },
702 inputs: tx
703 .inputs()
704 .iter()
705 .map(|input| match input {
706 zebra_chain::transparent::Input::Coinbase { sequence, .. } => Input::Coinbase {
707 coinbase: input
708 .coinbase_script()
709 .expect("we know it is a valid coinbase script"),
710 sequence: *sequence,
711 },
712 zebra_chain::transparent::Input::PrevOut {
713 sequence,
714 unlock_script,
715 outpoint,
716 } => Input::NonCoinbase {
717 txid: outpoint.hash.encode_hex(),
718 vout: outpoint.index,
719 script_sig: ScriptSig {
720 asm: zcash_script::script::Code(unlock_script.as_raw_bytes().to_vec())
722 .to_asm(true),
723 hex: unlock_script.clone(),
724 },
725 sequence: *sequence,
726 value: None,
727 value_zat: None,
728 address: None,
729 },
730 })
731 .collect(),
732 outputs: tx
733 .outputs()
734 .iter()
735 .enumerate()
736 .map(|output| {
737 let (addresses, req_sigs) = output
739 .1
740 .address(network)
741 .map(|address| (vec![address.to_string()], 1))
742 .unzip();
743
744 Output {
745 value: Zec::from(output.1.value).lossy_zec(),
746 value_zat: output.1.value.zatoshis(),
747 n: output.0 as u32,
748 script_pub_key: ScriptPubKey {
749 asm: zcash_script::script::Code(
752 output.1.lock_script.as_raw_bytes().to_vec(),
753 )
754 .to_asm(false),
755 hex: output.1.lock_script.clone(),
756 req_sigs,
757 r#type: zcash_script::script::Code(
758 output.1.lock_script.as_raw_bytes().to_vec(),
759 )
760 .to_component()
761 .ok()
762 .and_then(|c| c.refine().ok())
763 .and_then(|component| zcash_script::solver::standard(&component))
764 .map(|kind| match kind {
765 zcash_script::solver::ScriptKind::PubKeyHash { .. } => "pubkeyhash",
766 zcash_script::solver::ScriptKind::ScriptHash { .. } => "scripthash",
767 zcash_script::solver::ScriptKind::MultiSig { .. } => "multisig",
768 zcash_script::solver::ScriptKind::NullData { .. } => "nulldata",
769 zcash_script::solver::ScriptKind::PubKey { .. } => "pubkey",
770 })
771 .unwrap_or("nonstandard")
772 .to_string(),
773 addresses,
774 },
775 }
776 })
777 .collect(),
778 shielded_spends: tx
779 .sapling_spends_per_anchor()
780 .map(|spend| {
781 let mut anchor = spend.per_spend_anchor.as_bytes();
782 anchor.reverse();
783
784 let mut nullifier = spend.nullifier.as_bytes();
785 nullifier.reverse();
786
787 let mut rk: [u8; 32] = spend.clone().rk.into();
788 rk.reverse();
789
790 let spend_auth_sig: [u8; 64] = spend.spend_auth_sig.into();
791
792 ShieldedSpend {
793 cv: spend.cv.clone(),
794 anchor,
795 nullifier,
796 rk,
797 proof: spend.zkproof.0,
798 spend_auth_sig,
799 }
800 })
801 .collect(),
802 shielded_outputs: tx
803 .sapling_outputs()
804 .map(|output| {
805 let mut cm_u: [u8; 32] = output.cm_u.to_bytes();
806 cm_u.reverse();
807 let mut ephemeral_key: [u8; 32] = output.ephemeral_key.into();
808 ephemeral_key.reverse();
809 let enc_ciphertext: [u8; 580] = output.enc_ciphertext.into();
810 let out_ciphertext: [u8; 80] = output.out_ciphertext.into();
811
812 ShieldedOutput {
813 cv: output.cv.clone(),
814 cm_u,
815 ephemeral_key,
816 enc_ciphertext,
817 out_ciphertext,
818 proof: output.zkproof.0,
819 }
820 })
821 .collect(),
822 joinsplits: tx
823 .sprout_joinsplits()
824 .map(|joinsplit| {
825 let mut ephemeral_key_bytes: [u8; 32] = joinsplit.ephemeral_key.to_bytes();
826 ephemeral_key_bytes.reverse();
827
828 JoinSplit {
829 old_public_value: Zec::from(joinsplit.vpub_old).lossy_zec(),
830 old_public_value_zat: joinsplit.vpub_old.zatoshis(),
831 new_public_value: Zec::from(joinsplit.vpub_new).lossy_zec(),
832 new_public_value_zat: joinsplit.vpub_new.zatoshis(),
833 anchor: joinsplit.anchor.bytes_in_display_order(),
834 nullifiers: joinsplit
835 .nullifiers
836 .iter()
837 .map(|n| n.bytes_in_display_order())
838 .collect(),
839 commitments: joinsplit
840 .commitments
841 .iter()
842 .map(|c| c.bytes_in_display_order())
843 .collect(),
844 one_time_pubkey: ephemeral_key_bytes,
845 random_seed: joinsplit.random_seed.bytes_in_display_order(),
846 macs: joinsplit
847 .vmacs
848 .iter()
849 .map(|m| m.bytes_in_display_order())
850 .collect(),
851 proof: joinsplit.zkproof.unwrap_or_default(),
852 ciphertexts: joinsplit
853 .enc_ciphertexts
854 .iter()
855 .map(|c| c.zcash_serialize_to_vec().unwrap_or_default())
856 .collect(),
857 }
858 })
859 .collect(),
860 value_balance: Some(Zec::from(tx.sapling_value_balance().sapling_amount()).lossy_zec()),
861 value_balance_zat: Some(tx.sapling_value_balance().sapling_amount().zatoshis()),
862 orchard: Some(Orchard {
863 actions: tx
864 .orchard_actions()
865 .collect::<Vec<_>>()
866 .iter()
867 .map(|action| {
868 let spend_auth_sig: [u8; 64] = tx
869 .orchard_shielded_data()
870 .and_then(|shielded_data| {
871 shielded_data
872 .actions
873 .iter()
874 .find(|authorized_action| authorized_action.action == **action)
875 .map(|authorized_action| {
876 authorized_action.spend_auth_sig.into()
877 })
878 })
879 .unwrap_or([0; 64]);
880
881 let cv: [u8; 32] = action.cv.into();
882 let nullifier: [u8; 32] = action.nullifier.into();
883 let rk: [u8; 32] = action.rk.into();
884 let cm_x: [u8; 32] = action.cm_x.into();
885 let ephemeral_key: [u8; 32] = action.ephemeral_key.into();
886 let enc_ciphertext: [u8; 580] = action.enc_ciphertext.into();
887 let out_ciphertext: [u8; 80] = action.out_ciphertext.into();
888
889 OrchardAction {
890 cv,
891 nullifier,
892 rk,
893 cm_x,
894 ephemeral_key,
895 enc_ciphertext,
896 spend_auth_sig,
897 out_ciphertext,
898 }
899 })
900 .collect(),
901 value_balance: Zec::from(tx.orchard_value_balance().orchard_amount()).lossy_zec(),
902 value_balance_zat: tx.orchard_value_balance().orchard_amount().zatoshis(),
903 flags: tx.orchard_shielded_data().map(|data| {
904 OrchardFlags::new(
905 data.flags.contains(orchard::Flags::ENABLE_OUTPUTS),
906 data.flags.contains(orchard::Flags::ENABLE_SPENDS),
907 )
908 }),
909 anchor: tx
910 .orchard_shielded_data()
911 .map(|data| data.shared_anchor.bytes_in_display_order()),
912 proof: tx
913 .orchard_shielded_data()
914 .map(|data| data.proof.bytes_in_display_order()),
915 binding_sig: tx
916 .orchard_shielded_data()
917 .map(|data| data.binding_sig.into()),
918 }),
919 binding_sig: tx.sapling_binding_sig().map(|raw_sig| raw_sig.into()),
920 joinsplit_pub_key: tx.joinsplit_pub_key().map(|raw_key| {
921 let mut key: [u8; 32] = raw_key.into();
923 key.reverse();
924 key
925 }),
926 joinsplit_sig: tx.joinsplit_sig().map(|raw_sig| raw_sig.into()),
927 size: tx.as_bytes().len().try_into().ok(),
928 time: block_time,
929 txid,
930 in_active_chain,
931 auth_digest: tx.auth_digest(),
932 overwintered: tx.is_overwintered(),
933 version: tx.version(),
934 version_group_id: tx.version_group_id().map(|id| id.to_be_bytes().to_vec()),
935 lock_time: tx.raw_lock_time(),
936 expiry_height: if tx.is_overwintered() {
939 Some(tx.expiry_height().unwrap_or(Height(0)))
940 } else {
941 None
942 },
943 block_hash,
944 block_time,
945 }
946 }
947}