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, NegativeAllowed, 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, NetworkUpgrade,
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 ) -> Result<Self, TransactionError> {
134 let block_subsidy = block_subsidy(height, net)?;
135 let miner_reward = miner_subsidy(height, net, block_subsidy)? + txs_fee;
136 let miner_reward = Zatoshis::try_from(miner_reward?)?;
137
138 let mut builder = Builder::new(
139 net,
140 BlockHeight::from(height),
141 BuildConfig::Coinbase {
142 miner_data: miner_params.data().clone(),
143 },
144 );
145
146 let default_memo = MemoBytes::empty();
147 let memo = miner_params.memo().unwrap_or(&default_memo);
148
149 macro_rules! trace_err {
154 ($res:expr, $type:expr) => {
155 $res.map_err(|err| tracing::error!("Failed to add {} output: {err}", $type))
156 .ok()
157 };
158 }
159
160 let use_ironwood = NetworkUpgrade::current(net, height) >= NetworkUpgrade::Nu6_3;
166
167 let add_shielded_reward = |builder: &mut Builder<_, _>, addr: &_| {
168 let ovk = Some(::orchard::keys::OutgoingViewingKey::from([0u8; 32]));
169 if use_ironwood {
170 trace_err!(
171 builder.add_ironwood_output::<String>(ovk, *addr, miner_reward, memo.clone()),
172 "Ironwood"
173 )
174 } else {
175 trace_err!(
176 builder.add_orchard_output::<String>(ovk, *addr, miner_reward, memo.clone()),
177 "Orchard"
178 )
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_shielded_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<i64>,
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 = "ironwood", skip_serializing_if = "Option::is_none")]
353 pub(crate) ironwood: Option<Orchard>,
354
355 #[serde(rename = "valueBalance", skip_serializing_if = "Option::is_none")]
357 #[getter(copy)]
358 pub(crate) value_balance: Option<f64>,
359
360 #[serde(rename = "valueBalanceZat", skip_serializing_if = "Option::is_none")]
362 #[getter(copy)]
363 pub(crate) value_balance_zat: Option<i64>,
364
365 #[serde(skip_serializing_if = "Option::is_none")]
367 #[getter(copy)]
368 pub(crate) size: Option<i64>,
369
370 #[serde(skip_serializing_if = "Option::is_none")]
372 #[getter(copy)]
373 pub(crate) time: Option<i64>,
374
375 #[serde(with = "hex")]
377 #[getter(copy)]
378 pub txid: transaction::Hash,
379
380 #[serde(
383 rename = "authdigest",
384 with = "opthex",
385 skip_serializing_if = "Option::is_none",
386 default
387 )]
388 #[getter(copy)]
389 pub(crate) auth_digest: Option<transaction::AuthDigest>,
390
391 pub(crate) overwintered: bool,
393
394 pub(crate) version: u32,
396
397 #[serde(
399 rename = "versiongroupid",
400 with = "opthex",
401 skip_serializing_if = "Option::is_none",
402 default
403 )]
404 pub(crate) version_group_id: Option<Vec<u8>>,
405
406 #[serde(rename = "locktime")]
408 pub(crate) lock_time: u32,
409
410 #[serde(rename = "expiryheight", skip_serializing_if = "Option::is_none")]
414 #[getter(copy)]
415 pub(crate) expiry_height: Option<Height>,
416
417 #[serde(
419 rename = "blockhash",
420 with = "opthex",
421 skip_serializing_if = "Option::is_none",
422 default
423 )]
424 #[getter(copy)]
425 pub(crate) block_hash: Option<block::Hash>,
426
427 #[serde(rename = "blocktime", skip_serializing_if = "Option::is_none")]
429 #[getter(copy)]
430 pub(crate) block_time: Option<i64>,
431}
432
433#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
435#[serde(untagged)]
436pub enum Input {
437 Coinbase {
439 #[serde(with = "hex")]
441 coinbase: Vec<u8>,
442 sequence: u32,
444 },
445 NonCoinbase {
447 txid: String,
449 vout: u32,
451 #[serde(rename = "scriptSig")]
453 script_sig: ScriptSig,
454 sequence: u32,
456 #[serde(skip_serializing_if = "Option::is_none")]
458 value: Option<f64>,
459 #[serde(rename = "valueSat", skip_serializing_if = "Option::is_none")]
461 value_zat: Option<i64>,
462 #[serde(skip_serializing_if = "Option::is_none")]
464 address: Option<String>,
465 },
466}
467
468#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
470pub struct Output {
471 value: f64,
473 #[serde(rename = "valueZat")]
475 value_zat: i64,
476 n: u32,
478 #[serde(rename = "scriptPubKey")]
480 script_pub_key: ScriptPubKey,
481}
482
483#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
485pub struct OutputObject {
486 #[serde(rename = "bestblock")]
487 best_block: String,
488 confirmations: u32,
489 value: f64,
490 #[serde(rename = "scriptPubKey")]
491 script_pub_key: ScriptPubKey,
492 version: u32,
493 coinbase: bool,
494}
495impl OutputObject {
496 pub fn from_output(
497 output: &zebra_chain::transparent::Output,
498 best_block: String,
499 confirmations: u32,
500 version: u32,
501 coinbase: bool,
502 network: &Network,
503 ) -> Self {
504 let lock_script = &output.lock_script;
505 let addresses = output.address(network).map(|addr| vec![addr.to_string()]);
506 let req_sigs = addresses.as_ref().map(|a| a.len() as u32);
507
508 let script_pub_key = ScriptPubKey::new(
509 zcash_script::script::Code(lock_script.as_raw_bytes().to_vec()).to_asm(false),
510 lock_script.clone(),
511 req_sigs,
512 zcash_script::script::Code(lock_script.as_raw_bytes().to_vec())
513 .to_component()
514 .ok()
515 .and_then(|c| c.refine().ok())
516 .and_then(|component| zcash_script::solver::standard(&component))
517 .map(|kind| match kind {
518 zcash_script::solver::ScriptKind::PubKeyHash { .. } => "pubkeyhash",
519 zcash_script::solver::ScriptKind::ScriptHash { .. } => "scripthash",
520 zcash_script::solver::ScriptKind::MultiSig { .. } => "multisig",
521 zcash_script::solver::ScriptKind::NullData { .. } => "nulldata",
522 zcash_script::solver::ScriptKind::PubKey { .. } => "pubkey",
523 })
524 .unwrap_or("nonstandard")
525 .to_string(),
526 addresses,
527 );
528
529 Self {
530 best_block,
531 confirmations,
532 value: crate::methods::types::zec::Zec::from(output.value()).lossy_zec(),
533 script_pub_key,
534 version,
535 coinbase,
536 }
537 }
538}
539
540#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
542pub struct ScriptPubKey {
543 asm: String,
545 #[serde(with = "hex")]
547 hex: Script,
548 #[serde(rename = "reqSigs")]
550 #[serde(default)]
551 #[serde(skip_serializing_if = "Option::is_none")]
552 #[getter(copy)]
553 req_sigs: Option<u32>,
554 r#type: String,
556 #[serde(default)]
558 #[serde(skip_serializing_if = "Option::is_none")]
559 addresses: Option<Vec<String>>,
560}
561
562#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
564pub struct ScriptSig {
565 asm: String,
567 hex: Script,
569}
570
571#[allow(clippy::too_many_arguments)]
573#[serde_with::serde_as]
574#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
575pub struct JoinSplit {
576 #[serde(rename = "vpub_old")]
578 old_public_value: f64,
579 #[serde(rename = "vpub_oldZat")]
581 old_public_value_zat: i64,
582 #[serde(rename = "vpub_new")]
584 new_public_value: f64,
585 #[serde(rename = "vpub_newZat")]
587 new_public_value_zat: i64,
588 #[serde(with = "hex")]
590 #[getter(copy)]
591 anchor: [u8; 32],
592 #[serde_as(as = "Vec<serde_with::hex::Hex>")]
594 nullifiers: Vec<[u8; 32]>,
595 #[serde_as(as = "Vec<serde_with::hex::Hex>")]
597 commitments: Vec<[u8; 32]>,
598 #[serde(rename = "onetimePubKey")]
600 #[serde(with = "hex")]
601 #[getter(copy)]
602 one_time_pubkey: [u8; 32],
603 #[serde(rename = "randomSeed")]
605 #[serde(with = "hex")]
606 #[getter(copy)]
607 random_seed: [u8; 32],
608 #[serde_as(as = "Vec<serde_with::hex::Hex>")]
610 macs: Vec<[u8; 32]>,
611 #[serde(with = "hex")]
613 proof: Vec<u8>,
614 #[serde_as(as = "Vec<serde_with::hex::Hex>")]
616 ciphertexts: Vec<Vec<u8>>,
617}
618
619#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
621pub struct ShieldedSpend {
622 #[serde(with = "hex")]
624 #[getter(skip)]
625 cv: ValueCommitment,
626 #[serde(with = "hex")]
628 #[getter(copy)]
629 anchor: [u8; 32],
630 #[serde(with = "hex")]
632 #[getter(copy)]
633 nullifier: [u8; 32],
634 #[serde(with = "hex")]
636 #[getter(copy)]
637 rk: [u8; 32],
638 #[serde(with = "hex")]
640 #[getter(copy)]
641 proof: [u8; 192],
642 #[serde(rename = "spendAuthSig", with = "hex")]
644 #[getter(copy)]
645 spend_auth_sig: [u8; 64],
646}
647
648impl ShieldedSpend {
650 pub fn cv(&self) -> ValueCommitment {
652 self.cv.clone()
653 }
654}
655
656#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
658pub struct ShieldedOutput {
659 #[serde(with = "hex")]
661 #[getter(skip)]
662 cv: ValueCommitment,
663 #[serde(rename = "cmu", with = "hex")]
665 cm_u: [u8; 32],
666 #[serde(rename = "ephemeralKey", with = "hex")]
668 ephemeral_key: [u8; 32],
669 #[serde(rename = "encCiphertext", with = "arrayhex")]
671 enc_ciphertext: [u8; 580],
672 #[serde(rename = "outCiphertext", with = "hex")]
674 out_ciphertext: [u8; 80],
675 #[serde(with = "hex")]
677 proof: [u8; 192],
678}
679
680impl ShieldedOutput {
682 pub fn cv(&self) -> ValueCommitment {
684 self.cv.clone()
685 }
686}
687
688#[serde_with::serde_as]
690#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
691pub struct Orchard {
692 actions: Vec<OrchardAction>,
694 #[serde(rename = "valueBalance")]
696 value_balance: f64,
697 #[serde(rename = "valueBalanceZat")]
699 value_balance_zat: i64,
700 #[serde(skip_serializing_if = "Option::is_none")]
702 flags: Option<OrchardFlags>,
703 #[serde_as(as = "Option<serde_with::hex::Hex>")]
705 #[serde(skip_serializing_if = "Option::is_none")]
706 #[getter(copy)]
707 anchor: Option<[u8; 32]>,
708 #[serde_as(as = "Option<serde_with::hex::Hex>")]
710 #[serde(skip_serializing_if = "Option::is_none")]
711 proof: Option<Vec<u8>>,
712 #[serde(rename = "bindingSig")]
714 #[serde(skip_serializing_if = "Option::is_none")]
715 #[serde_as(as = "Option<serde_with::hex::Hex>")]
716 #[getter(copy)]
717 binding_sig: Option<[u8; 64]>,
718}
719
720#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
722pub struct OrchardFlags {
723 #[serde(rename = "enableOutputs")]
725 enable_outputs: bool,
726 #[serde(rename = "enableSpends")]
728 enable_spends: bool,
729}
730
731#[allow(clippy::too_many_arguments)]
733#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
734pub struct OrchardAction {
735 #[serde(with = "hex")]
737 cv: [u8; 32],
738 #[serde(with = "hex")]
740 nullifier: [u8; 32],
741 #[serde(with = "hex")]
743 rk: [u8; 32],
744 #[serde(rename = "cmx", with = "hex")]
746 cm_x: [u8; 32],
747 #[serde(rename = "ephemeralKey", with = "hex")]
749 ephemeral_key: [u8; 32],
750 #[serde(rename = "encCiphertext", with = "arrayhex")]
752 enc_ciphertext: [u8; 580],
753 #[serde(rename = "spendAuthSig", with = "hex")]
755 spend_auth_sig: [u8; 64],
756 #[serde(rename = "outCiphertext", with = "hex")]
758 out_ciphertext: [u8; 80],
759}
760
761fn orchard_shaped_object(
767 shielded_data: Option<&orchard::ShieldedData>,
768 value_balance: Amount<NegativeAllowed>,
769) -> Orchard {
770 let actions = shielded_data
771 .into_iter()
772 .flat_map(|data| data.actions.iter())
773 .map(|authorized_action| {
774 let action = &authorized_action.action;
775 OrchardAction {
776 cv: action.cv.into(),
777 nullifier: action.nullifier.into(),
778 rk: action.rk.into(),
779 cm_x: action.cm_x.into(),
780 ephemeral_key: action.ephemeral_key.into(),
781 enc_ciphertext: action.enc_ciphertext.into(),
782 spend_auth_sig: authorized_action.spend_auth_sig.into(),
783 out_ciphertext: action.out_ciphertext.into(),
784 }
785 })
786 .collect();
787
788 Orchard {
789 actions,
790 value_balance: Zec::from(value_balance).lossy_zec(),
791 value_balance_zat: value_balance.zatoshis(),
792 flags: shielded_data.map(|data| {
793 OrchardFlags::new(
794 data.flags.contains(orchard::Flags::ENABLE_OUTPUTS),
795 data.flags.contains(orchard::Flags::ENABLE_SPENDS),
796 )
797 }),
798 anchor: shielded_data.map(|data| data.shared_anchor.bytes_in_display_order()),
799 proof: shielded_data.map(|data| data.proof.bytes_in_display_order()),
800 binding_sig: shielded_data.map(|data| data.binding_sig.into()),
801 }
802}
803
804impl Default for TransactionObject {
805 fn default() -> Self {
806 Self {
807 hex: SerializedTransaction::from(
808 [0u8; zebra_chain::transaction::MIN_TRANSPARENT_TX_SIZE as usize].to_vec(),
809 ),
810 height: Option::default(),
811 confirmations: Option::default(),
812 inputs: Vec::new(),
813 outputs: Vec::new(),
814 shielded_spends: Vec::new(),
815 shielded_outputs: Vec::new(),
816 joinsplits: Vec::new(),
817 orchard: None,
818 ironwood: None,
819 binding_sig: None,
820 joinsplit_pub_key: None,
821 joinsplit_sig: None,
822 value_balance: None,
823 value_balance_zat: None,
824 size: None,
825 time: None,
826 txid: transaction::Hash::from([0u8; 32]),
827 in_active_chain: None,
828 auth_digest: None,
829 overwintered: false,
830 version: 4,
831 version_group_id: None,
832 lock_time: 0,
833 expiry_height: None,
834 block_hash: None,
835 block_time: None,
836 }
837 }
838}
839
840impl TransactionObject {
841 #[allow(clippy::unwrap_in_result)]
843 #[allow(clippy::too_many_arguments)]
844 pub fn from_transaction(
845 tx: Arc<Transaction>,
846 height: Option<block::Height>,
847 confirmations: Option<i64>,
848 network: &Network,
849 block_time: Option<DateTime<Utc>>,
850 block_hash: Option<block::Hash>,
851 in_active_chain: Option<bool>,
852 txid: transaction::Hash,
853 ) -> Self {
854 let block_time = block_time.map(|bt| bt.timestamp());
855 Self {
856 hex: tx.clone().into(),
857 height: if in_active_chain.unwrap_or_default() {
858 height.map(|height| height.0 as i32)
859 } else if block_hash.is_some() {
860 Some(-1)
862 } else {
863 None
865 },
866 confirmations: if in_active_chain.unwrap_or_default() {
867 confirmations
868 } else if block_hash.is_some() {
869 Some(0)
871 } else {
872 None
874 },
875 inputs: tx
876 .inputs()
877 .iter()
878 .map(|input| match input {
879 zebra_chain::transparent::Input::Coinbase { sequence, .. } => Input::Coinbase {
880 coinbase: input
881 .coinbase_script()
882 .expect("we know it is a valid coinbase script"),
883 sequence: *sequence,
884 },
885 zebra_chain::transparent::Input::PrevOut {
886 sequence,
887 unlock_script,
888 outpoint,
889 } => Input::NonCoinbase {
890 txid: outpoint.hash.encode_hex(),
891 vout: outpoint.index,
892 script_sig: ScriptSig {
893 asm: zcash_script::script::Code(unlock_script.as_raw_bytes().to_vec())
895 .to_asm(true),
896 hex: unlock_script.clone(),
897 },
898 sequence: *sequence,
899 value: None,
900 value_zat: None,
901 address: None,
902 },
903 })
904 .collect(),
905 outputs: tx
906 .outputs()
907 .iter()
908 .enumerate()
909 .map(|output| {
910 let (addresses, req_sigs) = output
912 .1
913 .address(network)
914 .map(|address| (vec![address.to_string()], 1))
915 .unzip();
916
917 Output {
918 value: Zec::from(output.1.value).lossy_zec(),
919 value_zat: output.1.value.zatoshis(),
920 n: output.0 as u32,
921 script_pub_key: ScriptPubKey {
922 asm: zcash_script::script::Code(
925 output.1.lock_script.as_raw_bytes().to_vec(),
926 )
927 .to_asm(false),
928 hex: output.1.lock_script.clone(),
929 req_sigs,
930 r#type: zcash_script::script::Code(
931 output.1.lock_script.as_raw_bytes().to_vec(),
932 )
933 .to_component()
934 .ok()
935 .and_then(|c| c.refine().ok())
936 .and_then(|component| zcash_script::solver::standard(&component))
937 .map(|kind| match kind {
938 zcash_script::solver::ScriptKind::PubKeyHash { .. } => "pubkeyhash",
939 zcash_script::solver::ScriptKind::ScriptHash { .. } => "scripthash",
940 zcash_script::solver::ScriptKind::MultiSig { .. } => "multisig",
941 zcash_script::solver::ScriptKind::NullData { .. } => "nulldata",
942 zcash_script::solver::ScriptKind::PubKey { .. } => "pubkey",
943 })
944 .unwrap_or("nonstandard")
945 .to_string(),
946 addresses,
947 },
948 }
949 })
950 .collect(),
951 shielded_spends: tx
952 .sapling_spends_per_anchor()
953 .map(|spend| {
954 let mut anchor = spend.per_spend_anchor.as_bytes();
955 anchor.reverse();
956
957 let mut nullifier = spend.nullifier.as_bytes();
958 nullifier.reverse();
959
960 let mut rk: [u8; 32] = spend.clone().rk.into();
961 rk.reverse();
962
963 let spend_auth_sig: [u8; 64] = spend.spend_auth_sig.into();
964
965 ShieldedSpend {
966 cv: spend.cv.clone(),
967 anchor,
968 nullifier,
969 rk,
970 proof: spend.zkproof.0,
971 spend_auth_sig,
972 }
973 })
974 .collect(),
975 shielded_outputs: tx
976 .sapling_outputs()
977 .map(|output| {
978 let mut cm_u: [u8; 32] = output.cm_u.to_bytes();
979 cm_u.reverse();
980 let mut ephemeral_key: [u8; 32] = output.ephemeral_key.into();
981 ephemeral_key.reverse();
982 let enc_ciphertext: [u8; 580] = output.enc_ciphertext.into();
983 let out_ciphertext: [u8; 80] = output.out_ciphertext.into();
984
985 ShieldedOutput {
986 cv: output.cv.clone(),
987 cm_u,
988 ephemeral_key,
989 enc_ciphertext,
990 out_ciphertext,
991 proof: output.zkproof.0,
992 }
993 })
994 .collect(),
995 joinsplits: tx
996 .sprout_joinsplits()
997 .map(|joinsplit| {
998 let mut ephemeral_key_bytes: [u8; 32] = joinsplit.ephemeral_key.to_bytes();
999 ephemeral_key_bytes.reverse();
1000
1001 JoinSplit {
1002 old_public_value: Zec::from(joinsplit.vpub_old).lossy_zec(),
1003 old_public_value_zat: joinsplit.vpub_old.zatoshis(),
1004 new_public_value: Zec::from(joinsplit.vpub_new).lossy_zec(),
1005 new_public_value_zat: joinsplit.vpub_new.zatoshis(),
1006 anchor: joinsplit.anchor.bytes_in_display_order(),
1007 nullifiers: joinsplit
1008 .nullifiers
1009 .iter()
1010 .map(|n| n.bytes_in_display_order())
1011 .collect(),
1012 commitments: joinsplit
1013 .commitments
1014 .iter()
1015 .map(|c| c.bytes_in_display_order())
1016 .collect(),
1017 one_time_pubkey: ephemeral_key_bytes,
1018 random_seed: joinsplit.random_seed.bytes_in_display_order(),
1019 macs: joinsplit
1020 .vmacs
1021 .iter()
1022 .map(|m| m.bytes_in_display_order())
1023 .collect(),
1024 proof: joinsplit.zkproof.unwrap_or_default(),
1025 ciphertexts: joinsplit
1026 .enc_ciphertexts
1027 .iter()
1028 .map(|c| c.zcash_serialize_to_vec().unwrap_or_default())
1029 .collect(),
1030 }
1031 })
1032 .collect(),
1033 value_balance: Some(Zec::from(tx.sapling_value_balance().sapling_amount()).lossy_zec()),
1034 value_balance_zat: Some(tx.sapling_value_balance().sapling_amount().zatoshis()),
1035 orchard: Some(orchard_shaped_object(
1036 tx.orchard_shielded_data(),
1037 tx.orchard_value_balance().orchard_amount(),
1038 )),
1039 ironwood: tx.ironwood_shielded_data().map(|data| {
1040 orchard_shaped_object(Some(data), tx.ironwood_value_balance().ironwood_amount())
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}