1use std::sync::Arc;
4
5use crate::methods::arrayhex;
6use chrono::{DateTime, Utc};
7use derive_getters::Getters;
8use derive_new::new;
9use hex::ToHex;
10use rand::rngs::OsRng;
11use zcash_script::script::Asm;
12
13use zcash_keys::address::Address;
14use zcash_primitives::transaction::{
15 builder::{BuildConfig, Builder},
16 fees::fixed::FeeRule,
17};
18use zcash_proofs::prover::LocalTxProver;
19use zcash_protocol::{consensus::BlockHeight, memo::MemoBytes, value::Zatoshis};
20use zebra_chain::{
21 amount::{self, Amount, NegativeOrZero, NonNegative},
22 block::{self, merkle::AUTH_DIGEST_PLACEHOLDER, Height},
23 orchard,
24 parameters::{
25 subsidy::{block_subsidy, funding_stream_values, miner_subsidy},
26 Network,
27 },
28 primitives::ed25519,
29 sapling::ValueCommitment,
30 serialization::ZcashSerialize,
31 transaction::{self, SerializedTransaction, Transaction, VerifiedUnminedTx},
32 transparent::Script,
33};
34use zebra_consensus::{error::TransactionError, funding_stream_address};
35use zebra_script::Sigops;
36use zebra_state::IntoDisk;
37
38use super::zec::Zec;
39use super::{super::opthex, get_block_template::MinerParams};
40
41#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
43#[serde(bound = "FeeConstraint: amount::Constraint + Clone")]
44pub struct TransactionTemplate<FeeConstraint>
45where
46 FeeConstraint: amount::Constraint + Clone + Copy,
47{
48 #[serde(with = "hex")]
50 pub(crate) data: SerializedTransaction,
51
52 #[serde(with = "hex")]
54 #[getter(copy)]
55 pub(crate) hash: transaction::Hash,
56
57 #[serde(rename = "authdigest")]
59 #[serde(with = "hex")]
60 #[getter(copy)]
61 pub(crate) auth_digest: transaction::AuthDigest,
62
63 pub(crate) depends: Vec<u16>,
70
71 #[getter(copy)]
77 pub(crate) fee: Amount<FeeConstraint>,
78
79 pub(crate) sigops: u32,
81
82 pub(crate) required: bool,
86}
87
88impl From<&VerifiedUnminedTx> for TransactionTemplate<NonNegative> {
90 fn from(tx: &VerifiedUnminedTx) -> Self {
91 assert!(
92 !tx.transaction.transaction.is_coinbase(),
93 "unexpected coinbase transaction in mempool"
94 );
95
96 Self {
97 data: tx.transaction.transaction.as_ref().into(),
98 hash: tx.transaction.id.mined_id(),
99 auth_digest: tx
100 .transaction
101 .id
102 .auth_digest()
103 .unwrap_or(AUTH_DIGEST_PLACEHOLDER),
104
105 depends: Vec::new(),
107
108 fee: tx.miner_fee,
109
110 sigops: tx.block_sigop_count(),
113
114 required: false,
116 }
117 }
118}
119
120impl From<VerifiedUnminedTx> for TransactionTemplate<NonNegative> {
121 fn from(tx: VerifiedUnminedTx) -> Self {
122 Self::from(&tx)
123 }
124}
125
126impl TransactionTemplate<NegativeOrZero> {
127 pub fn new_coinbase(
129 net: &Network,
130 height: Height,
131 miner_params: &MinerParams,
132 txs_fee: Amount<NonNegative>,
133 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))] zip233_amount: Option<
134 Amount<NonNegative>,
135 >,
136 ) -> Result<Self, TransactionError> {
137 let block_subsidy = block_subsidy(height, net)?;
138 let miner_reward = miner_subsidy(height, net, block_subsidy)? + txs_fee;
139 let miner_reward = Zatoshis::try_from(miner_reward?)?;
140
141 let mut builder = Builder::new(
142 net,
143 BlockHeight::from(height),
144 BuildConfig::Coinbase {
145 miner_data: miner_params.data().clone(),
146 },
147 );
148
149 let default_memo = MemoBytes::empty();
150 let memo = miner_params.memo().unwrap_or(&default_memo);
151
152 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
153 {
154 let zip233_amount = if cfg!(zcash_unstable = "zip235") {
155 zip233_amount.unwrap_or_else(|| ((miner_fee * 6).unwrap() / 10).unwrap())
156 } else {
157 zip233_amount.unwrap_or(Amount::zero())
158 };
159
160 builder.set_zip233_amount(Zatoshis::try_from(zip233_amount)?);
161 }
162
163 macro_rules! trace_err {
164 ($res:expr, $type:expr) => {
165 $res.map_err(|err| tracing::error!("Failed to add {} output: {err}", $type))
166 .ok()
167 };
168 }
169
170 let add_orchard_reward = |builder: &mut Builder<'_, _, _>, addr: &_| {
171 trace_err!(
172 builder.add_orchard_output::<String>(
173 Some(::orchard::keys::OutgoingViewingKey::from([0u8; 32])),
174 *addr,
175 miner_reward,
176 memo.clone(),
177 ),
178 "Orchard"
179 )
180 };
181
182 let add_sapling_reward = |builder: &mut Builder<'_, _, _>, addr: &_| {
183 trace_err!(
184 builder.add_sapling_output::<String>(
185 Some(sapling_crypto::keys::OutgoingViewingKey([0u8; 32])),
186 *addr,
187 miner_reward,
188 memo.clone(),
189 ),
190 "Sapling"
191 )
192 };
193
194 let add_transparent_reward = |builder: &mut Builder<'_, _, _>, addr| {
195 trace_err!(
196 builder.add_transparent_output(addr, miner_reward),
197 "transparent"
198 )
199 };
200
201 match miner_params.addr() {
202 Address::Unified(addr) => addr
203 .orchard()
204 .and_then(|addr| add_orchard_reward(&mut builder, addr))
205 .or_else(|| {
206 addr.sapling()
207 .and_then(|addr| add_sapling_reward(&mut builder, addr))
208 })
209 .or_else(|| {
210 addr.transparent()
211 .and_then(|addr| add_transparent_reward(&mut builder, addr))
212 }),
213
214 Address::Sapling(addr) => add_sapling_reward(&mut builder, addr),
215
216 Address::Transparent(addr) => add_transparent_reward(&mut builder, addr),
217
218 _ => Err(TransactionError::CoinbaseConstruction(
219 "Address not supported for miner rewards".to_string(),
220 ))?,
221 }
222 .ok_or(TransactionError::CoinbaseConstruction(
223 "Could not construct output with miner reward".to_string(),
224 ))?;
225
226 let mut funding_streams = funding_stream_values(height, net, block_subsidy)?
227 .into_iter()
228 .filter_map(|(receiver, amount)| {
229 Some((*funding_stream_address(height, net, receiver)?, amount))
230 })
231 .chain(net.lockbox_disbursements(height))
232 .filter_map(|(addr, amount)| {
233 Some((Zatoshis::try_from(amount).ok()?, addr.try_into().ok()?))
234 })
235 .collect::<Vec<_>>();
236
237 funding_streams.sort();
238
239 for (fs_amount, fs_addr) in funding_streams {
240 builder.add_transparent_output(&fs_addr, fs_amount)?;
241 }
242
243 let sapling_prover = LocalTxProver::bundled();
244 let build_result = builder.build(
245 &Default::default(),
246 Default::default(),
247 Default::default(),
248 OsRng,
249 &sapling_prover,
250 &sapling_prover,
251 &FeeRule::non_standard(Zatoshis::ZERO),
252 )?;
253
254 let tx = build_result.transaction();
255 let mut data = vec![];
256 tx.write(&mut data)?;
257
258 Ok(Self {
259 data: data.into(),
260 hash: tx.txid().as_ref().into(),
261 auth_digest: tx.auth_commitment().as_ref().try_into()?,
262 depends: Vec::new(),
263 fee: (-txs_fee).constrain()?,
264 sigops: tx.sigops()?,
265 required: true,
266 })
267 }
268}
269
270#[allow(clippy::too_many_arguments)]
273#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
274pub struct TransactionObject {
275 #[serde(skip_serializing_if = "Option::is_none")]
278 #[getter(copy)]
279 pub(crate) in_active_chain: Option<bool>,
280 #[serde(with = "hex")]
282 pub(crate) hex: SerializedTransaction,
283 #[serde(skip_serializing_if = "Option::is_none")]
286 #[getter(copy)]
287 pub(crate) height: Option<i32>,
288 #[serde(skip_serializing_if = "Option::is_none")]
292 #[getter(copy)]
293 pub(crate) confirmations: Option<u32>,
294
295 #[serde(rename = "vin")]
297 pub(crate) inputs: Vec<Input>,
298
299 #[serde(rename = "vout")]
301 pub(crate) outputs: Vec<Output>,
302
303 #[serde(rename = "vShieldedSpend")]
305 pub(crate) shielded_spends: Vec<ShieldedSpend>,
306
307 #[serde(rename = "vShieldedOutput")]
309 pub(crate) shielded_outputs: Vec<ShieldedOutput>,
310
311 #[serde(rename = "vjoinsplit")]
313 pub(crate) joinsplits: Vec<JoinSplit>,
314
315 #[serde(
317 skip_serializing_if = "Option::is_none",
318 with = "opthex",
319 default,
320 rename = "bindingSig"
321 )]
322 #[getter(copy)]
323 pub(crate) binding_sig: Option<[u8; 64]>,
324
325 #[serde(
327 skip_serializing_if = "Option::is_none",
328 with = "opthex",
329 default,
330 rename = "joinSplitPubKey"
331 )]
332 #[getter(copy)]
333 pub(crate) joinsplit_pub_key: Option<[u8; 32]>,
334
335 #[serde(
337 skip_serializing_if = "Option::is_none",
338 with = "opthex",
339 default,
340 rename = "joinSplitSig"
341 )]
342 #[getter(copy)]
343 pub(crate) joinsplit_sig: Option<[u8; ed25519::Signature::BYTE_SIZE]>,
344
345 #[serde(rename = "orchard", skip_serializing_if = "Option::is_none")]
347 pub(crate) orchard: Option<Orchard>,
348
349 #[serde(rename = "valueBalance", skip_serializing_if = "Option::is_none")]
351 #[getter(copy)]
352 pub(crate) value_balance: Option<f64>,
353
354 #[serde(rename = "valueBalanceZat", skip_serializing_if = "Option::is_none")]
356 #[getter(copy)]
357 pub(crate) value_balance_zat: Option<i64>,
358
359 #[serde(skip_serializing_if = "Option::is_none")]
361 #[getter(copy)]
362 pub(crate) size: Option<i64>,
363
364 #[serde(skip_serializing_if = "Option::is_none")]
366 #[getter(copy)]
367 pub(crate) time: Option<i64>,
368
369 #[serde(with = "hex")]
371 #[getter(copy)]
372 pub txid: transaction::Hash,
373
374 #[serde(
377 rename = "authdigest",
378 with = "opthex",
379 skip_serializing_if = "Option::is_none",
380 default
381 )]
382 #[getter(copy)]
383 pub(crate) auth_digest: Option<transaction::AuthDigest>,
384
385 pub(crate) overwintered: bool,
387
388 pub(crate) version: u32,
390
391 #[serde(
393 rename = "versiongroupid",
394 with = "opthex",
395 skip_serializing_if = "Option::is_none",
396 default
397 )]
398 pub(crate) version_group_id: Option<Vec<u8>>,
399
400 #[serde(rename = "locktime")]
402 pub(crate) lock_time: u32,
403
404 #[serde(rename = "expiryheight", skip_serializing_if = "Option::is_none")]
408 #[getter(copy)]
409 pub(crate) expiry_height: Option<Height>,
410
411 #[serde(
413 rename = "blockhash",
414 with = "opthex",
415 skip_serializing_if = "Option::is_none",
416 default
417 )]
418 #[getter(copy)]
419 pub(crate) block_hash: Option<block::Hash>,
420
421 #[serde(rename = "blocktime", skip_serializing_if = "Option::is_none")]
423 #[getter(copy)]
424 pub(crate) block_time: Option<i64>,
425}
426
427#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
429#[serde(untagged)]
430pub enum Input {
431 Coinbase {
433 #[serde(with = "hex")]
435 coinbase: Vec<u8>,
436 sequence: u32,
438 },
439 NonCoinbase {
441 txid: String,
443 vout: u32,
445 #[serde(rename = "scriptSig")]
447 script_sig: ScriptSig,
448 sequence: u32,
450 #[serde(skip_serializing_if = "Option::is_none")]
452 value: Option<f64>,
453 #[serde(rename = "valueSat", skip_serializing_if = "Option::is_none")]
455 value_zat: Option<i64>,
456 #[serde(skip_serializing_if = "Option::is_none")]
458 address: Option<String>,
459 },
460}
461
462#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
464pub struct Output {
465 value: f64,
467 #[serde(rename = "valueZat")]
469 value_zat: i64,
470 n: u32,
472 #[serde(rename = "scriptPubKey")]
474 script_pub_key: ScriptPubKey,
475}
476
477#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
479pub struct OutputObject {
480 #[serde(rename = "bestblock")]
481 best_block: String,
482 confirmations: u32,
483 value: f64,
484 #[serde(rename = "scriptPubKey")]
485 script_pub_key: ScriptPubKey,
486 version: u32,
487 coinbase: bool,
488}
489impl OutputObject {
490 pub fn from_output(
491 output: &zebra_chain::transparent::Output,
492 best_block: String,
493 confirmations: u32,
494 version: u32,
495 coinbase: bool,
496 network: &Network,
497 ) -> Self {
498 let lock_script = &output.lock_script;
499 let addresses = output.address(network).map(|addr| vec![addr.to_string()]);
500 let req_sigs = addresses.as_ref().map(|a| a.len() as u32);
501
502 let script_pub_key = ScriptPubKey::new(
503 zcash_script::script::Code(lock_script.as_raw_bytes().to_vec()).to_asm(false),
504 lock_script.clone(),
505 req_sigs,
506 zcash_script::script::Code(lock_script.as_raw_bytes().to_vec())
507 .to_component()
508 .ok()
509 .and_then(|c| c.refine().ok())
510 .and_then(|component| zcash_script::solver::standard(&component))
511 .map(|kind| match kind {
512 zcash_script::solver::ScriptKind::PubKeyHash { .. } => "pubkeyhash",
513 zcash_script::solver::ScriptKind::ScriptHash { .. } => "scripthash",
514 zcash_script::solver::ScriptKind::MultiSig { .. } => "multisig",
515 zcash_script::solver::ScriptKind::NullData { .. } => "nulldata",
516 zcash_script::solver::ScriptKind::PubKey { .. } => "pubkey",
517 })
518 .unwrap_or("nonstandard")
519 .to_string(),
520 addresses,
521 );
522
523 Self {
524 best_block,
525 confirmations,
526 value: crate::methods::types::zec::Zec::from(output.value()).lossy_zec(),
527 script_pub_key,
528 version,
529 coinbase,
530 }
531 }
532}
533
534#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
536pub struct ScriptPubKey {
537 asm: String,
539 #[serde(with = "hex")]
541 hex: Script,
542 #[serde(rename = "reqSigs")]
544 #[serde(default)]
545 #[serde(skip_serializing_if = "Option::is_none")]
546 #[getter(copy)]
547 req_sigs: Option<u32>,
548 r#type: String,
550 #[serde(default)]
552 #[serde(skip_serializing_if = "Option::is_none")]
553 addresses: Option<Vec<String>>,
554}
555
556#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
558pub struct ScriptSig {
559 asm: String,
561 hex: Script,
563}
564
565#[allow(clippy::too_many_arguments)]
567#[serde_with::serde_as]
568#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
569pub struct JoinSplit {
570 #[serde(rename = "vpub_old")]
572 old_public_value: f64,
573 #[serde(rename = "vpub_oldZat")]
575 old_public_value_zat: i64,
576 #[serde(rename = "vpub_new")]
578 new_public_value: f64,
579 #[serde(rename = "vpub_newZat")]
581 new_public_value_zat: i64,
582 #[serde(with = "hex")]
584 #[getter(copy)]
585 anchor: [u8; 32],
586 #[serde_as(as = "Vec<serde_with::hex::Hex>")]
588 nullifiers: Vec<[u8; 32]>,
589 #[serde_as(as = "Vec<serde_with::hex::Hex>")]
591 commitments: Vec<[u8; 32]>,
592 #[serde(rename = "onetimePubKey")]
594 #[serde(with = "hex")]
595 #[getter(copy)]
596 one_time_pubkey: [u8; 32],
597 #[serde(rename = "randomSeed")]
599 #[serde(with = "hex")]
600 #[getter(copy)]
601 random_seed: [u8; 32],
602 #[serde_as(as = "Vec<serde_with::hex::Hex>")]
604 macs: Vec<[u8; 32]>,
605 #[serde(with = "hex")]
607 proof: Vec<u8>,
608 #[serde_as(as = "Vec<serde_with::hex::Hex>")]
610 ciphertexts: Vec<Vec<u8>>,
611}
612
613#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
615pub struct ShieldedSpend {
616 #[serde(with = "hex")]
618 #[getter(skip)]
619 cv: ValueCommitment,
620 #[serde(with = "hex")]
622 #[getter(copy)]
623 anchor: [u8; 32],
624 #[serde(with = "hex")]
626 #[getter(copy)]
627 nullifier: [u8; 32],
628 #[serde(with = "hex")]
630 #[getter(copy)]
631 rk: [u8; 32],
632 #[serde(with = "hex")]
634 #[getter(copy)]
635 proof: [u8; 192],
636 #[serde(rename = "spendAuthSig", with = "hex")]
638 #[getter(copy)]
639 spend_auth_sig: [u8; 64],
640}
641
642impl ShieldedSpend {
644 pub fn cv(&self) -> ValueCommitment {
646 self.cv.clone()
647 }
648}
649
650#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
652pub struct ShieldedOutput {
653 #[serde(with = "hex")]
655 #[getter(skip)]
656 cv: ValueCommitment,
657 #[serde(rename = "cmu", with = "hex")]
659 cm_u: [u8; 32],
660 #[serde(rename = "ephemeralKey", with = "hex")]
662 ephemeral_key: [u8; 32],
663 #[serde(rename = "encCiphertext", with = "arrayhex")]
665 enc_ciphertext: [u8; 580],
666 #[serde(rename = "outCiphertext", with = "hex")]
668 out_ciphertext: [u8; 80],
669 #[serde(with = "hex")]
671 proof: [u8; 192],
672}
673
674impl ShieldedOutput {
676 pub fn cv(&self) -> ValueCommitment {
678 self.cv.clone()
679 }
680}
681
682#[serde_with::serde_as]
684#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
685pub struct Orchard {
686 actions: Vec<OrchardAction>,
688 #[serde(rename = "valueBalance")]
690 value_balance: f64,
691 #[serde(rename = "valueBalanceZat")]
693 value_balance_zat: i64,
694 #[serde(skip_serializing_if = "Option::is_none")]
696 flags: Option<OrchardFlags>,
697 #[serde_as(as = "Option<serde_with::hex::Hex>")]
699 #[serde(skip_serializing_if = "Option::is_none")]
700 #[getter(copy)]
701 anchor: Option<[u8; 32]>,
702 #[serde_as(as = "Option<serde_with::hex::Hex>")]
704 #[serde(skip_serializing_if = "Option::is_none")]
705 proof: Option<Vec<u8>>,
706 #[serde(rename = "bindingSig")]
708 #[serde(skip_serializing_if = "Option::is_none")]
709 #[serde_as(as = "Option<serde_with::hex::Hex>")]
710 #[getter(copy)]
711 binding_sig: Option<[u8; 64]>,
712}
713
714#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
716pub struct OrchardFlags {
717 #[serde(rename = "enableOutputs")]
719 enable_outputs: bool,
720 #[serde(rename = "enableSpends")]
722 enable_spends: bool,
723}
724
725#[allow(clippy::too_many_arguments)]
727#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
728pub struct OrchardAction {
729 #[serde(with = "hex")]
731 cv: [u8; 32],
732 #[serde(with = "hex")]
734 nullifier: [u8; 32],
735 #[serde(with = "hex")]
737 rk: [u8; 32],
738 #[serde(rename = "cmx", with = "hex")]
740 cm_x: [u8; 32],
741 #[serde(rename = "ephemeralKey", with = "hex")]
743 ephemeral_key: [u8; 32],
744 #[serde(rename = "encCiphertext", with = "arrayhex")]
746 enc_ciphertext: [u8; 580],
747 #[serde(rename = "spendAuthSig", with = "hex")]
749 spend_auth_sig: [u8; 64],
750 #[serde(rename = "outCiphertext", with = "hex")]
752 out_ciphertext: [u8; 80],
753}
754
755impl Default for TransactionObject {
756 fn default() -> Self {
757 Self {
758 hex: SerializedTransaction::from(
759 [0u8; zebra_chain::transaction::MIN_TRANSPARENT_TX_SIZE as usize].to_vec(),
760 ),
761 height: Option::default(),
762 confirmations: Option::default(),
763 inputs: Vec::new(),
764 outputs: Vec::new(),
765 shielded_spends: Vec::new(),
766 shielded_outputs: Vec::new(),
767 joinsplits: Vec::new(),
768 orchard: None,
769 binding_sig: None,
770 joinsplit_pub_key: None,
771 joinsplit_sig: None,
772 value_balance: None,
773 value_balance_zat: None,
774 size: None,
775 time: None,
776 txid: transaction::Hash::from([0u8; 32]),
777 in_active_chain: None,
778 auth_digest: None,
779 overwintered: false,
780 version: 4,
781 version_group_id: None,
782 lock_time: 0,
783 expiry_height: None,
784 block_hash: None,
785 block_time: None,
786 }
787 }
788}
789
790impl TransactionObject {
791 #[allow(clippy::unwrap_in_result)]
793 #[allow(clippy::too_many_arguments)]
794 pub fn from_transaction(
795 tx: Arc<Transaction>,
796 height: Option<block::Height>,
797 confirmations: Option<u32>,
798 network: &Network,
799 block_time: Option<DateTime<Utc>>,
800 block_hash: Option<block::Hash>,
801 in_active_chain: Option<bool>,
802 txid: transaction::Hash,
803 ) -> Self {
804 let block_time = block_time.map(|bt| bt.timestamp());
805 Self {
806 hex: tx.clone().into(),
807 height: if in_active_chain.unwrap_or_default() {
808 height.map(|height| height.0 as i32)
809 } else if block_hash.is_some() {
810 Some(-1)
812 } else {
813 None
815 },
816 confirmations: if in_active_chain.unwrap_or_default() {
817 confirmations
818 } else if block_hash.is_some() {
819 Some(0)
821 } else {
822 None
824 },
825 inputs: tx
826 .inputs()
827 .iter()
828 .map(|input| match input {
829 zebra_chain::transparent::Input::Coinbase { sequence, .. } => Input::Coinbase {
830 coinbase: input
831 .coinbase_script()
832 .expect("we know it is a valid coinbase script"),
833 sequence: *sequence,
834 },
835 zebra_chain::transparent::Input::PrevOut {
836 sequence,
837 unlock_script,
838 outpoint,
839 } => Input::NonCoinbase {
840 txid: outpoint.hash.encode_hex(),
841 vout: outpoint.index,
842 script_sig: ScriptSig {
843 asm: zcash_script::script::Code(unlock_script.as_raw_bytes().to_vec())
845 .to_asm(true),
846 hex: unlock_script.clone(),
847 },
848 sequence: *sequence,
849 value: None,
850 value_zat: None,
851 address: None,
852 },
853 })
854 .collect(),
855 outputs: tx
856 .outputs()
857 .iter()
858 .enumerate()
859 .map(|output| {
860 let (addresses, req_sigs) = output
862 .1
863 .address(network)
864 .map(|address| (vec![address.to_string()], 1))
865 .unzip();
866
867 Output {
868 value: Zec::from(output.1.value).lossy_zec(),
869 value_zat: output.1.value.zatoshis(),
870 n: output.0 as u32,
871 script_pub_key: ScriptPubKey {
872 asm: zcash_script::script::Code(
875 output.1.lock_script.as_raw_bytes().to_vec(),
876 )
877 .to_asm(false),
878 hex: output.1.lock_script.clone(),
879 req_sigs,
880 r#type: zcash_script::script::Code(
881 output.1.lock_script.as_raw_bytes().to_vec(),
882 )
883 .to_component()
884 .ok()
885 .and_then(|c| c.refine().ok())
886 .and_then(|component| zcash_script::solver::standard(&component))
887 .map(|kind| match kind {
888 zcash_script::solver::ScriptKind::PubKeyHash { .. } => "pubkeyhash",
889 zcash_script::solver::ScriptKind::ScriptHash { .. } => "scripthash",
890 zcash_script::solver::ScriptKind::MultiSig { .. } => "multisig",
891 zcash_script::solver::ScriptKind::NullData { .. } => "nulldata",
892 zcash_script::solver::ScriptKind::PubKey { .. } => "pubkey",
893 })
894 .unwrap_or("nonstandard")
895 .to_string(),
896 addresses,
897 },
898 }
899 })
900 .collect(),
901 shielded_spends: tx
902 .sapling_spends_per_anchor()
903 .map(|spend| {
904 let mut anchor = spend.per_spend_anchor.as_bytes();
905 anchor.reverse();
906
907 let mut nullifier = spend.nullifier.as_bytes();
908 nullifier.reverse();
909
910 let mut rk: [u8; 32] = spend.clone().rk.into();
911 rk.reverse();
912
913 let spend_auth_sig: [u8; 64] = spend.spend_auth_sig.into();
914
915 ShieldedSpend {
916 cv: spend.cv.clone(),
917 anchor,
918 nullifier,
919 rk,
920 proof: spend.zkproof.0,
921 spend_auth_sig,
922 }
923 })
924 .collect(),
925 shielded_outputs: tx
926 .sapling_outputs()
927 .map(|output| {
928 let mut cm_u: [u8; 32] = output.cm_u.to_bytes();
929 cm_u.reverse();
930 let mut ephemeral_key: [u8; 32] = output.ephemeral_key.into();
931 ephemeral_key.reverse();
932 let enc_ciphertext: [u8; 580] = output.enc_ciphertext.into();
933 let out_ciphertext: [u8; 80] = output.out_ciphertext.into();
934
935 ShieldedOutput {
936 cv: output.cv.clone(),
937 cm_u,
938 ephemeral_key,
939 enc_ciphertext,
940 out_ciphertext,
941 proof: output.zkproof.0,
942 }
943 })
944 .collect(),
945 joinsplits: tx
946 .sprout_joinsplits()
947 .map(|joinsplit| {
948 let mut ephemeral_key_bytes: [u8; 32] = joinsplit.ephemeral_key.to_bytes();
949 ephemeral_key_bytes.reverse();
950
951 JoinSplit {
952 old_public_value: Zec::from(joinsplit.vpub_old).lossy_zec(),
953 old_public_value_zat: joinsplit.vpub_old.zatoshis(),
954 new_public_value: Zec::from(joinsplit.vpub_new).lossy_zec(),
955 new_public_value_zat: joinsplit.vpub_new.zatoshis(),
956 anchor: joinsplit.anchor.bytes_in_display_order(),
957 nullifiers: joinsplit
958 .nullifiers
959 .iter()
960 .map(|n| n.bytes_in_display_order())
961 .collect(),
962 commitments: joinsplit
963 .commitments
964 .iter()
965 .map(|c| c.bytes_in_display_order())
966 .collect(),
967 one_time_pubkey: ephemeral_key_bytes,
968 random_seed: joinsplit.random_seed.bytes_in_display_order(),
969 macs: joinsplit
970 .vmacs
971 .iter()
972 .map(|m| m.bytes_in_display_order())
973 .collect(),
974 proof: joinsplit.zkproof.unwrap_or_default(),
975 ciphertexts: joinsplit
976 .enc_ciphertexts
977 .iter()
978 .map(|c| c.zcash_serialize_to_vec().unwrap_or_default())
979 .collect(),
980 }
981 })
982 .collect(),
983 value_balance: Some(Zec::from(tx.sapling_value_balance().sapling_amount()).lossy_zec()),
984 value_balance_zat: Some(tx.sapling_value_balance().sapling_amount().zatoshis()),
985 orchard: Some(Orchard {
986 actions: tx
987 .orchard_actions()
988 .collect::<Vec<_>>()
989 .iter()
990 .map(|action| {
991 let spend_auth_sig: [u8; 64] = tx
992 .orchard_shielded_data()
993 .and_then(|shielded_data| {
994 shielded_data
995 .actions
996 .iter()
997 .find(|authorized_action| authorized_action.action == **action)
998 .map(|authorized_action| {
999 authorized_action.spend_auth_sig.into()
1000 })
1001 })
1002 .unwrap_or([0; 64]);
1003
1004 let cv: [u8; 32] = action.cv.into();
1005 let nullifier: [u8; 32] = action.nullifier.into();
1006 let rk: [u8; 32] = action.rk.into();
1007 let cm_x: [u8; 32] = action.cm_x.into();
1008 let ephemeral_key: [u8; 32] = action.ephemeral_key.into();
1009 let enc_ciphertext: [u8; 580] = action.enc_ciphertext.into();
1010 let out_ciphertext: [u8; 80] = action.out_ciphertext.into();
1011
1012 OrchardAction {
1013 cv,
1014 nullifier,
1015 rk,
1016 cm_x,
1017 ephemeral_key,
1018 enc_ciphertext,
1019 spend_auth_sig,
1020 out_ciphertext,
1021 }
1022 })
1023 .collect(),
1024 value_balance: Zec::from(tx.orchard_value_balance().orchard_amount()).lossy_zec(),
1025 value_balance_zat: tx.orchard_value_balance().orchard_amount().zatoshis(),
1026 flags: tx.orchard_shielded_data().map(|data| {
1027 OrchardFlags::new(
1028 data.flags.contains(orchard::Flags::ENABLE_OUTPUTS),
1029 data.flags.contains(orchard::Flags::ENABLE_SPENDS),
1030 )
1031 }),
1032 anchor: tx
1033 .orchard_shielded_data()
1034 .map(|data| data.shared_anchor.bytes_in_display_order()),
1035 proof: tx
1036 .orchard_shielded_data()
1037 .map(|data| data.proof.bytes_in_display_order()),
1038 binding_sig: tx
1039 .orchard_shielded_data()
1040 .map(|data| data.binding_sig.into()),
1041 }),
1042 binding_sig: tx.sapling_binding_sig().map(|raw_sig| raw_sig.into()),
1043 joinsplit_pub_key: tx.joinsplit_pub_key().map(|raw_key| {
1044 let mut key: [u8; 32] = raw_key.into();
1046 key.reverse();
1047 key
1048 }),
1049 joinsplit_sig: tx.joinsplit_sig().map(|raw_sig| raw_sig.into()),
1050 size: tx.as_bytes().len().try_into().ok(),
1051 time: block_time,
1052 txid,
1053 in_active_chain,
1054 auth_digest: tx.auth_digest(),
1055 overwintered: tx.is_overwintered(),
1056 version: tx.version(),
1057 version_group_id: tx.version_group_id().map(|id| id.to_be_bytes().to_vec()),
1058 lock_time: tx.raw_lock_time(),
1059 expiry_height: if tx.is_overwintered() {
1062 Some(tx.expiry_height().unwrap_or(Height(0)))
1063 } else {
1064 None
1065 },
1066 block_hash,
1067 block_time,
1068 }
1069 }
1070}