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 serde_with::serde_as;
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::NotSmallOrderValueCommitment,
19 serialization::ZcashSerialize,
20 transaction::{self, SerializedTransaction, Transaction, UnminedTx, VerifiedUnminedTx},
21 transparent::Script,
22};
23use zebra_consensus::groth16::Description;
24use zebra_script::Sigops;
25use zebra_state::IntoDisk;
26
27use super::super::opthex;
28use super::zec::Zec;
29
30#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
32#[serde(bound = "FeeConstraint: amount::Constraint + Clone")]
33pub struct TransactionTemplate<FeeConstraint>
34where
35 FeeConstraint: amount::Constraint + Clone + Copy,
36{
37 #[serde(with = "hex")]
39 pub(crate) data: SerializedTransaction,
40
41 #[serde(with = "hex")]
43 #[getter(copy)]
44 pub(crate) hash: transaction::Hash,
45
46 #[serde(rename = "authdigest")]
48 #[serde(with = "hex")]
49 #[getter(copy)]
50 pub(crate) auth_digest: transaction::AuthDigest,
51
52 pub(crate) depends: Vec<u16>,
59
60 #[getter(copy)]
66 pub(crate) fee: Amount<FeeConstraint>,
67
68 pub(crate) sigops: u32,
70
71 pub(crate) required: bool,
75}
76
77impl From<&VerifiedUnminedTx> for TransactionTemplate<NonNegative> {
79 fn from(tx: &VerifiedUnminedTx) -> Self {
80 assert!(
81 !tx.transaction.transaction.is_coinbase(),
82 "unexpected coinbase transaction in mempool"
83 );
84
85 Self {
86 data: tx.transaction.transaction.as_ref().into(),
87 hash: tx.transaction.id.mined_id(),
88 auth_digest: tx
89 .transaction
90 .id
91 .auth_digest()
92 .unwrap_or(AUTH_DIGEST_PLACEHOLDER),
93
94 depends: Vec::new(),
96
97 fee: tx.miner_fee,
98
99 sigops: tx.sigops,
100
101 required: false,
103 }
104 }
105}
106
107impl From<VerifiedUnminedTx> for TransactionTemplate<NonNegative> {
108 fn from(tx: VerifiedUnminedTx) -> Self {
109 Self::from(&tx)
110 }
111}
112
113impl TransactionTemplate<NegativeOrZero> {
114 pub fn from_coinbase(tx: &UnminedTx, miner_fee: Amount<NonNegative>) -> Self {
120 assert!(
121 tx.transaction.is_coinbase(),
122 "invalid generated coinbase transaction: \
123 must have exactly one input, which must be a coinbase input",
124 );
125
126 let miner_fee = (-miner_fee)
127 .constrain()
128 .expect("negating a NonNegative amount always results in a valid NegativeOrZero");
129
130 Self {
131 data: tx.transaction.as_ref().into(),
132 hash: tx.id.mined_id(),
133 auth_digest: tx.id.auth_digest().unwrap_or(AUTH_DIGEST_PLACEHOLDER),
134
135 depends: Vec::new(),
137
138 fee: miner_fee,
139
140 sigops: tx.sigops().expect("sigops count should be valid"),
141
142 required: true,
144 }
145 }
146}
147
148#[allow(clippy::too_many_arguments)]
151#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
152pub struct TransactionObject {
153 #[serde(skip_serializing_if = "Option::is_none")]
156 #[getter(copy)]
157 pub(crate) in_active_chain: Option<bool>,
158 #[serde(with = "hex")]
160 pub(crate) hex: SerializedTransaction,
161 #[serde(skip_serializing_if = "Option::is_none")]
164 #[getter(copy)]
165 pub(crate) height: Option<u32>,
166 #[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")]
283 #[getter(copy)]
284 pub(crate) expiry_height: Option<Height>,
285
286 #[serde(
288 rename = "blockhash",
289 with = "opthex",
290 skip_serializing_if = "Option::is_none",
291 default
292 )]
293 #[getter(copy)]
294 pub(crate) block_hash: Option<block::Hash>,
295
296 #[serde(rename = "blocktime", skip_serializing_if = "Option::is_none")]
298 #[getter(copy)]
299 pub(crate) block_time: Option<i64>,
300}
301
302#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
304#[serde(untagged)]
305pub enum Input {
306 Coinbase {
308 #[serde(with = "hex")]
310 coinbase: Vec<u8>,
311 sequence: u32,
313 },
314 NonCoinbase {
316 txid: String,
318 vout: u32,
320 #[serde(rename = "scriptSig")]
322 script_sig: ScriptSig,
323 sequence: u32,
325 #[serde(skip_serializing_if = "Option::is_none")]
327 value: Option<f64>,
328 #[serde(rename = "valueSat", skip_serializing_if = "Option::is_none")]
330 value_zat: Option<i64>,
331 #[serde(skip_serializing_if = "Option::is_none")]
333 address: Option<String>,
334 },
335}
336
337#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
339pub struct Output {
340 value: f64,
342 #[serde(rename = "valueZat")]
344 value_zat: i64,
345 n: u32,
347 #[serde(rename = "scriptPubKey")]
349 script_pub_key: ScriptPubKey,
350}
351
352#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
354pub struct ScriptPubKey {
355 asm: String,
358 #[serde(with = "hex")]
360 hex: Script,
361 #[serde(rename = "reqSigs")]
363 #[serde(default)]
364 #[serde(skip_serializing_if = "Option::is_none")]
365 #[getter(copy)]
366 req_sigs: Option<u32>,
367 r#type: String,
370 #[serde(default)]
372 #[serde(skip_serializing_if = "Option::is_none")]
373 addresses: Option<Vec<String>>,
374}
375
376#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
378pub struct ScriptSig {
379 asm: String,
382 hex: Script,
384}
385
386#[allow(clippy::too_many_arguments)]
388#[serde_with::serde_as]
389#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
390pub struct JoinSplit {
391 #[serde(rename = "vpub_old")]
393 old_public_value: f64,
394 #[serde(rename = "vpub_oldZat")]
396 old_public_value_zat: i64,
397 #[serde(rename = "vpub_new")]
399 new_public_value: f64,
400 #[serde(rename = "vpub_newZat")]
402 new_public_value_zat: i64,
403 #[serde(with = "hex")]
405 #[getter(copy)]
406 anchor: [u8; 32],
407 #[serde_as(as = "Vec<serde_with::hex::Hex>")]
409 nullifiers: Vec<[u8; 32]>,
410 #[serde_as(as = "Vec<serde_with::hex::Hex>")]
412 commitments: Vec<[u8; 32]>,
413 #[serde(rename = "onetimePubKey")]
415 #[serde(with = "hex")]
416 #[getter(copy)]
417 one_time_pubkey: [u8; 32],
418 #[serde(rename = "randomSeed")]
420 #[serde(with = "hex")]
421 #[getter(copy)]
422 random_seed: [u8; 32],
423 #[serde_as(as = "Vec<serde_with::hex::Hex>")]
425 macs: Vec<[u8; 32]>,
426 #[serde(with = "hex")]
428 proof: Vec<u8>,
429 #[serde_as(as = "Vec<serde_with::hex::Hex>")]
431 ciphertexts: Vec<Vec<u8>>,
432}
433
434#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
436pub struct ShieldedSpend {
437 #[serde(with = "hex")]
439 #[getter(copy)]
440 cv: NotSmallOrderValueCommitment,
441 #[serde(with = "hex")]
443 #[getter(copy)]
444 anchor: [u8; 32],
445 #[serde(with = "hex")]
447 #[getter(copy)]
448 nullifier: [u8; 32],
449 #[serde(with = "hex")]
451 #[getter(copy)]
452 rk: [u8; 32],
453 #[serde(with = "hex")]
455 #[getter(copy)]
456 proof: [u8; 192],
457 #[serde(rename = "spendAuthSig", with = "hex")]
459 #[getter(copy)]
460 spend_auth_sig: [u8; 64],
461}
462
463#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
465pub struct ShieldedOutput {
466 #[serde(with = "hex")]
468 #[getter(copy)]
469 cv: NotSmallOrderValueCommitment,
470 #[serde(rename = "cmu", with = "hex")]
472 cm_u: [u8; 32],
473 #[serde(rename = "ephemeralKey", with = "hex")]
475 ephemeral_key: [u8; 32],
476 #[serde(rename = "encCiphertext", with = "arrayhex")]
478 enc_ciphertext: [u8; 580],
479 #[serde(rename = "outCiphertext", with = "hex")]
481 out_ciphertext: [u8; 80],
482 #[serde(with = "hex")]
484 proof: [u8; 192],
485}
486
487#[serde_with::serde_as]
489#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
490pub struct Orchard {
491 actions: Vec<OrchardAction>,
493 #[serde(rename = "valueBalance")]
495 value_balance: f64,
496 #[serde(rename = "valueBalanceZat")]
498 value_balance_zat: i64,
499 #[serde(skip_serializing_if = "Option::is_none")]
501 flags: Option<OrchardFlags>,
502 #[serde_as(as = "Option<serde_with::hex::Hex>")]
504 #[serde(skip_serializing_if = "Option::is_none")]
505 #[getter(copy)]
506 anchor: Option<[u8; 32]>,
507 #[serde_as(as = "Option<serde_with::hex::Hex>")]
509 #[serde(skip_serializing_if = "Option::is_none")]
510 proof: Option<Vec<u8>>,
511 #[serde(rename = "bindingSig")]
513 #[serde(skip_serializing_if = "Option::is_none")]
514 #[serde_as(as = "Option<serde_with::hex::Hex>")]
515 #[getter(copy)]
516 binding_sig: Option<[u8; 64]>,
517}
518
519#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
521pub struct OrchardFlags {
522 #[serde(rename = "enableOutputs")]
524 enable_outputs: bool,
525 #[serde(rename = "enableSpends")]
527 enable_spends: bool,
528}
529
530#[allow(clippy::too_many_arguments)]
532#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
533pub struct OrchardAction {
534 #[serde(with = "hex")]
536 cv: [u8; 32],
537 #[serde(with = "hex")]
539 nullifier: [u8; 32],
540 #[serde(with = "hex")]
542 rk: [u8; 32],
543 #[serde(rename = "cmx", with = "hex")]
545 cm_x: [u8; 32],
546 #[serde(rename = "ephemeralKey", with = "hex")]
548 ephemeral_key: [u8; 32],
549 #[serde(rename = "encCiphertext", with = "arrayhex")]
551 enc_ciphertext: [u8; 580],
552 #[serde(rename = "spendAuthSig", with = "hex")]
554 spend_auth_sig: [u8; 64],
555 #[serde(rename = "outCiphertext", with = "hex")]
557 out_ciphertext: [u8; 80],
558}
559
560impl Default for TransactionObject {
561 fn default() -> Self {
562 Self {
563 hex: SerializedTransaction::from(
564 [0u8; zebra_chain::transaction::MIN_TRANSPARENT_TX_SIZE as usize].to_vec(),
565 ),
566 height: Option::default(),
567 confirmations: Option::default(),
568 inputs: Vec::new(),
569 outputs: Vec::new(),
570 shielded_spends: Vec::new(),
571 shielded_outputs: Vec::new(),
572 joinsplits: Vec::new(),
573 orchard: None,
574 binding_sig: None,
575 joinsplit_pub_key: None,
576 joinsplit_sig: None,
577 value_balance: None,
578 value_balance_zat: None,
579 size: None,
580 time: None,
581 txid: transaction::Hash::from([0u8; 32]),
582 in_active_chain: None,
583 auth_digest: None,
584 overwintered: false,
585 version: 4,
586 version_group_id: None,
587 lock_time: 0,
588 expiry_height: None,
589 block_hash: None,
590 block_time: None,
591 }
592 }
593}
594
595impl TransactionObject {
596 #[allow(clippy::unwrap_in_result)]
598 #[allow(clippy::too_many_arguments)]
599 pub fn from_transaction(
600 tx: Arc<Transaction>,
601 height: Option<block::Height>,
602 confirmations: Option<u32>,
603 network: &Network,
604 block_time: Option<DateTime<Utc>>,
605 block_hash: Option<block::Hash>,
606 in_active_chain: Option<bool>,
607 txid: transaction::Hash,
608 ) -> Self {
609 let block_time = block_time.map(|bt| bt.timestamp());
610 Self {
611 hex: tx.clone().into(),
612 height: height.map(|height| height.0),
613 confirmations,
614 inputs: tx
615 .inputs()
616 .iter()
617 .map(|input| match input {
618 zebra_chain::transparent::Input::Coinbase { sequence, .. } => Input::Coinbase {
619 coinbase: input
620 .coinbase_script()
621 .expect("we know it is a valid coinbase script"),
622 sequence: *sequence,
623 },
624 zebra_chain::transparent::Input::PrevOut {
625 sequence,
626 unlock_script,
627 outpoint,
628 } => Input::NonCoinbase {
629 txid: outpoint.hash.encode_hex(),
630 vout: outpoint.index,
631 script_sig: ScriptSig {
632 asm: "".to_string(),
633 hex: unlock_script.clone(),
634 },
635 sequence: *sequence,
636 value: None,
637 value_zat: None,
638 address: None,
639 },
640 })
641 .collect(),
642 outputs: tx
643 .outputs()
644 .iter()
645 .enumerate()
646 .map(|output| {
647 let (addresses, req_sigs) = output
649 .1
650 .address(network)
651 .map(|address| (vec![address.to_string()], 1))
652 .unzip();
653
654 Output {
655 value: Zec::from(output.1.value).lossy_zec(),
656 value_zat: output.1.value.zatoshis(),
657 n: output.0 as u32,
658 script_pub_key: ScriptPubKey {
659 asm: "".to_string(),
661 hex: output.1.lock_script.clone(),
662 req_sigs,
663 r#type: "".to_string(),
665 addresses,
666 },
667 }
668 })
669 .collect(),
670 shielded_spends: tx
671 .sapling_spends_per_anchor()
672 .map(|spend| {
673 let mut anchor = spend.per_spend_anchor.as_bytes();
674 anchor.reverse();
675
676 let mut nullifier = spend.nullifier.as_bytes();
677 nullifier.reverse();
678
679 let mut rk: [u8; 32] = spend.clone().rk.into();
680 rk.reverse();
681
682 let spend_auth_sig: [u8; 64] = spend.spend_auth_sig.into();
683
684 ShieldedSpend {
685 cv: spend.cv,
686 anchor,
687 nullifier,
688 rk,
689 proof: spend.proof().0,
690 spend_auth_sig,
691 }
692 })
693 .collect(),
694 shielded_outputs: tx
695 .sapling_outputs()
696 .map(|output| {
697 let mut cm_u: [u8; 32] = output.cm_u.to_bytes();
698 cm_u.reverse();
699 let mut ephemeral_key: [u8; 32] = output.ephemeral_key.into();
700 ephemeral_key.reverse();
701 let enc_ciphertext: [u8; 580] = output.enc_ciphertext.into();
702 let out_ciphertext: [u8; 80] = output.out_ciphertext.into();
703
704 ShieldedOutput {
705 cv: output.cv,
706 cm_u,
707 ephemeral_key,
708 enc_ciphertext,
709 out_ciphertext,
710 proof: output.proof().0,
711 }
712 })
713 .collect(),
714 joinsplits: tx
715 .sprout_joinsplits()
716 .map(|joinsplit| {
717 let mut ephemeral_key_bytes: [u8; 32] = joinsplit.ephemeral_key.to_bytes();
718 ephemeral_key_bytes.reverse();
719
720 JoinSplit {
721 old_public_value: Zec::from(joinsplit.vpub_old).lossy_zec(),
722 old_public_value_zat: joinsplit.vpub_old.zatoshis(),
723 new_public_value: Zec::from(joinsplit.vpub_new).lossy_zec(),
724 new_public_value_zat: joinsplit.vpub_new.zatoshis(),
725 anchor: joinsplit.anchor.bytes_in_display_order(),
726 nullifiers: joinsplit
727 .nullifiers
728 .iter()
729 .map(|n| n.bytes_in_display_order())
730 .collect(),
731 commitments: joinsplit
732 .commitments
733 .iter()
734 .map(|c| c.bytes_in_display_order())
735 .collect(),
736 one_time_pubkey: ephemeral_key_bytes,
737 random_seed: joinsplit.random_seed.bytes_in_display_order(),
738 macs: joinsplit
739 .vmacs
740 .iter()
741 .map(|m| m.bytes_in_display_order())
742 .collect(),
743 proof: joinsplit.zkproof.unwrap_or_default(),
744 ciphertexts: joinsplit
745 .enc_ciphertexts
746 .iter()
747 .map(|c| c.zcash_serialize_to_vec().unwrap_or_default())
748 .collect(),
749 }
750 })
751 .collect(),
752 value_balance: Some(Zec::from(tx.sapling_value_balance().sapling_amount()).lossy_zec()),
753 value_balance_zat: Some(tx.sapling_value_balance().sapling_amount().zatoshis()),
754 orchard: Some(Orchard {
755 actions: tx
756 .orchard_actions()
757 .collect::<Vec<_>>()
758 .iter()
759 .map(|action| {
760 let spend_auth_sig: [u8; 64] = tx
761 .orchard_shielded_data()
762 .and_then(|shielded_data| {
763 shielded_data
764 .actions
765 .iter()
766 .find(|authorized_action| authorized_action.action == **action)
767 .map(|authorized_action| {
768 authorized_action.spend_auth_sig.into()
769 })
770 })
771 .unwrap_or([0; 64]);
772
773 let cv: [u8; 32] = action.cv.into();
774 let nullifier: [u8; 32] = action.nullifier.into();
775 let rk: [u8; 32] = action.rk.into();
776 let cm_x: [u8; 32] = action.cm_x.into();
777 let ephemeral_key: [u8; 32] = action.ephemeral_key.into();
778 let enc_ciphertext: [u8; 580] = action.enc_ciphertext.into();
779 let out_ciphertext: [u8; 80] = action.out_ciphertext.into();
780
781 OrchardAction {
782 cv,
783 nullifier,
784 rk,
785 cm_x,
786 ephemeral_key,
787 enc_ciphertext,
788 spend_auth_sig,
789 out_ciphertext,
790 }
791 })
792 .collect(),
793 value_balance: Zec::from(tx.orchard_value_balance().orchard_amount()).lossy_zec(),
794 value_balance_zat: tx.orchard_value_balance().orchard_amount().zatoshis(),
795 flags: tx.orchard_shielded_data().map(|data| {
796 OrchardFlags::new(
797 data.flags.contains(orchard::Flags::ENABLE_OUTPUTS),
798 data.flags.contains(orchard::Flags::ENABLE_SPENDS),
799 )
800 }),
801 anchor: tx
802 .orchard_shielded_data()
803 .map(|data| data.shared_anchor.bytes_in_display_order()),
804 proof: tx
805 .orchard_shielded_data()
806 .map(|data| data.proof.bytes_in_display_order()),
807 binding_sig: tx
808 .orchard_shielded_data()
809 .map(|data| data.binding_sig.into()),
810 }),
811 binding_sig: tx.sapling_binding_sig().map(|raw_sig| raw_sig.into()),
812 joinsplit_pub_key: tx.joinsplit_pub_key().map(|raw_key| {
813 let mut key: [u8; 32] = raw_key.into();
815 key.reverse();
816 key
817 }),
818 joinsplit_sig: tx.joinsplit_sig().map(|raw_sig| raw_sig.into()),
819 size: tx.as_bytes().len().try_into().ok(),
820 time: block_time,
821 txid,
822 in_active_chain,
823 auth_digest: tx.auth_digest(),
824 overwintered: tx.is_overwintered(),
825 version: tx.version(),
826 version_group_id: tx.version_group_id().map(|id| id.to_be_bytes().to_vec()),
827 lock_time: tx.raw_lock_time(),
828 expiry_height: tx.expiry_height(),
829 block_hash,
830 block_time,
831 }
832 }
833}