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 zebra_chain::{
12 amount::{self, Amount, NegativeOrZero, NonNegative},
13 block::{self, merkle::AUTH_DIGEST_PLACEHOLDER, Height},
14 parameters::Network,
15 primitives::ed25519,
16 sapling::NotSmallOrderValueCommitment,
17 transaction::{self, SerializedTransaction, Transaction, UnminedTx, VerifiedUnminedTx},
18 transparent::Script,
19};
20use zebra_consensus::groth16::Description;
21use zebra_state::IntoDisk;
22
23use super::super::opthex;
24use super::zec::Zec;
25
26#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
28#[serde(bound = "FeeConstraint: amount::Constraint + Clone")]
29pub struct TransactionTemplate<FeeConstraint>
30where
31 FeeConstraint: amount::Constraint + Clone + Copy,
32{
33 #[serde(with = "hex")]
35 pub(crate) data: SerializedTransaction,
36
37 #[serde(with = "hex")]
39 #[getter(copy)]
40 pub(crate) hash: transaction::Hash,
41
42 #[serde(rename = "authdigest")]
44 #[serde(with = "hex")]
45 #[getter(copy)]
46 pub(crate) auth_digest: transaction::AuthDigest,
47
48 pub(crate) depends: Vec<u16>,
55
56 #[getter(copy)]
62 pub(crate) fee: Amount<FeeConstraint>,
63
64 pub(crate) sigops: u64,
66
67 pub(crate) required: bool,
71}
72
73impl From<&VerifiedUnminedTx> for TransactionTemplate<NonNegative> {
75 fn from(tx: &VerifiedUnminedTx) -> Self {
76 assert!(
77 !tx.transaction.transaction.is_coinbase(),
78 "unexpected coinbase transaction in mempool"
79 );
80
81 Self {
82 data: tx.transaction.transaction.as_ref().into(),
83 hash: tx.transaction.id.mined_id(),
84 auth_digest: tx
85 .transaction
86 .id
87 .auth_digest()
88 .unwrap_or(AUTH_DIGEST_PLACEHOLDER),
89
90 depends: Vec::new(),
92
93 fee: tx.miner_fee,
94
95 sigops: tx.legacy_sigop_count,
96
97 required: false,
99 }
100 }
101}
102
103impl From<VerifiedUnminedTx> for TransactionTemplate<NonNegative> {
104 fn from(tx: VerifiedUnminedTx) -> Self {
105 Self::from(&tx)
106 }
107}
108
109impl TransactionTemplate<NegativeOrZero> {
110 pub fn from_coinbase(tx: &UnminedTx, miner_fee: Amount<NonNegative>) -> Self {
116 assert!(
117 tx.transaction.is_coinbase(),
118 "invalid generated coinbase transaction: \
119 must have exactly one input, which must be a coinbase input",
120 );
121
122 let miner_fee = (-miner_fee)
123 .constrain()
124 .expect("negating a NonNegative amount always results in a valid NegativeOrZero");
125
126 let legacy_sigop_count = zebra_script::legacy_sigop_count(&tx.transaction).expect(
127 "invalid generated coinbase transaction: \
128 failure in zcash_script sigop count",
129 );
130
131 Self {
132 data: tx.transaction.as_ref().into(),
133 hash: tx.id.mined_id(),
134 auth_digest: tx.id.auth_digest().unwrap_or(AUTH_DIGEST_PLACEHOLDER),
135
136 depends: Vec::new(),
138
139 fee: miner_fee,
140
141 sigops: legacy_sigop_count,
142
143 required: true,
145 }
146 }
147}
148
149#[allow(clippy::too_many_arguments)]
152#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
153pub struct TransactionObject {
154 #[serde(skip_serializing_if = "Option::is_none")]
157 #[getter(copy)]
158 pub(crate) in_active_chain: Option<bool>,
159 #[serde(with = "hex")]
161 pub(crate) hex: SerializedTransaction,
162 #[serde(skip_serializing_if = "Option::is_none")]
165 #[getter(copy)]
166 pub(crate) height: Option<u32>,
167 #[serde(skip_serializing_if = "Option::is_none")]
170 #[getter(copy)]
171 pub(crate) confirmations: Option<u32>,
172
173 #[serde(rename = "vin")]
175 pub(crate) inputs: Vec<Input>,
176
177 #[serde(rename = "vout")]
179 pub(crate) outputs: Vec<Output>,
180
181 #[serde(rename = "vShieldedSpend")]
183 pub(crate) shielded_spends: Vec<ShieldedSpend>,
184
185 #[serde(rename = "vShieldedOutput")]
187 pub(crate) shielded_outputs: Vec<ShieldedOutput>,
188
189 #[serde(
191 skip_serializing_if = "Option::is_none",
192 with = "opthex",
193 default,
194 rename = "bindingSig"
195 )]
196 #[getter(copy)]
197 pub(crate) binding_sig: Option<[u8; 64]>,
198
199 #[serde(
201 skip_serializing_if = "Option::is_none",
202 with = "opthex",
203 default,
204 rename = "joinSplitPubKey"
205 )]
206 #[getter(copy)]
207 pub(crate) joinsplit_pub_key: Option<[u8; 32]>,
208
209 #[serde(
211 skip_serializing_if = "Option::is_none",
212 with = "opthex",
213 default,
214 rename = "joinSplitSig"
215 )]
216 #[getter(copy)]
217 pub(crate) joinsplit_sig: Option<[u8; ed25519::Signature::BYTE_SIZE]>,
218
219 #[serde(rename = "orchard", skip_serializing_if = "Option::is_none")]
221 pub(crate) orchard: Option<Orchard>,
222
223 #[serde(rename = "valueBalance", skip_serializing_if = "Option::is_none")]
225 #[getter(copy)]
226 pub(crate) value_balance: Option<f64>,
227
228 #[serde(rename = "valueBalanceZat", skip_serializing_if = "Option::is_none")]
230 #[getter(copy)]
231 pub(crate) value_balance_zat: Option<i64>,
232
233 #[serde(skip_serializing_if = "Option::is_none")]
235 #[getter(copy)]
236 pub(crate) size: Option<i64>,
237
238 #[serde(skip_serializing_if = "Option::is_none")]
240 #[getter(copy)]
241 pub(crate) time: Option<i64>,
242
243 #[serde(with = "hex")]
245 #[getter(copy)]
246 pub txid: transaction::Hash,
247
248 #[serde(
253 rename = "authdigest",
254 with = "opthex",
255 skip_serializing_if = "Option::is_none",
256 default
257 )]
258 #[getter(copy)]
259 pub(crate) auth_digest: Option<transaction::AuthDigest>,
260
261 pub(crate) overwintered: bool,
263
264 pub(crate) version: u32,
266
267 #[serde(
269 rename = "versiongroupid",
270 with = "opthex",
271 skip_serializing_if = "Option::is_none",
272 default
273 )]
274 pub(crate) version_group_id: Option<Vec<u8>>,
275
276 #[serde(rename = "locktime")]
278 pub(crate) lock_time: u32,
279
280 #[serde(rename = "expiryheight", skip_serializing_if = "Option::is_none")]
282 #[getter(copy)]
283 pub(crate) expiry_height: Option<Height>,
284
285 #[serde(
287 rename = "blockhash",
288 with = "opthex",
289 skip_serializing_if = "Option::is_none",
290 default
291 )]
292 #[getter(copy)]
293 pub(crate) block_hash: Option<block::Hash>,
294
295 #[serde(rename = "blocktime", skip_serializing_if = "Option::is_none")]
297 #[getter(copy)]
298 pub(crate) block_time: Option<i64>,
299}
300
301#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
303#[serde(untagged)]
304pub enum Input {
305 Coinbase {
307 #[serde(with = "hex")]
309 coinbase: Vec<u8>,
310 sequence: u32,
312 },
313 NonCoinbase {
315 txid: String,
317 vout: u32,
319 #[serde(rename = "scriptSig")]
321 script_sig: ScriptSig,
322 sequence: u32,
324 #[serde(skip_serializing_if = "Option::is_none")]
326 value: Option<f64>,
327 #[serde(rename = "valueSat", skip_serializing_if = "Option::is_none")]
329 value_zat: Option<i64>,
330 #[serde(skip_serializing_if = "Option::is_none")]
332 address: Option<String>,
333 },
334}
335
336#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
338pub struct Output {
339 value: f64,
341 #[serde(rename = "valueZat")]
343 value_zat: i64,
344 n: u32,
346 #[serde(rename = "scriptPubKey")]
348 script_pub_key: ScriptPubKey,
349}
350
351#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
353pub struct ScriptPubKey {
354 asm: String,
357 #[serde(with = "hex")]
359 hex: Script,
360 #[serde(rename = "reqSigs")]
362 #[serde(default)]
363 #[serde(skip_serializing_if = "Option::is_none")]
364 #[getter(copy)]
365 req_sigs: Option<u32>,
366 r#type: String,
369 #[serde(default)]
371 #[serde(skip_serializing_if = "Option::is_none")]
372 addresses: Option<Vec<String>>,
373}
374
375#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
377pub struct ScriptSig {
378 asm: String,
381 hex: Script,
383}
384
385#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
387pub struct ShieldedSpend {
388 #[serde(with = "hex")]
390 #[getter(copy)]
391 cv: NotSmallOrderValueCommitment,
392 #[serde(with = "hex")]
394 #[getter(copy)]
395 anchor: [u8; 32],
396 #[serde(with = "hex")]
398 #[getter(copy)]
399 nullifier: [u8; 32],
400 #[serde(with = "hex")]
402 #[getter(copy)]
403 rk: [u8; 32],
404 #[serde(with = "hex")]
406 #[getter(copy)]
407 proof: [u8; 192],
408 #[serde(rename = "spendAuthSig", with = "hex")]
410 #[getter(copy)]
411 spend_auth_sig: [u8; 64],
412}
413
414#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
416pub struct ShieldedOutput {
417 #[serde(with = "hex")]
419 #[getter(copy)]
420 cv: NotSmallOrderValueCommitment,
421 #[serde(rename = "cmu", with = "hex")]
423 cm_u: [u8; 32],
424 #[serde(rename = "ephemeralKey", with = "hex")]
426 ephemeral_key: [u8; 32],
427 #[serde(rename = "encCiphertext", with = "arrayhex")]
429 enc_ciphertext: [u8; 580],
430 #[serde(rename = "outCiphertext", with = "hex")]
432 out_ciphertext: [u8; 80],
433 #[serde(with = "hex")]
435 proof: [u8; 192],
436}
437
438#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
440pub struct Orchard {
441 actions: Vec<OrchardAction>,
443 #[serde(rename = "valueBalance")]
445 value_balance: f64,
446 #[serde(rename = "valueBalanceZat")]
448 value_balance_zat: i64,
449}
450
451#[allow(clippy::too_many_arguments)]
453#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
454pub struct OrchardAction {
455 #[serde(with = "hex")]
457 cv: [u8; 32],
458 #[serde(with = "hex")]
460 nullifier: [u8; 32],
461 #[serde(with = "hex")]
463 rk: [u8; 32],
464 #[serde(rename = "cmx", with = "hex")]
466 cm_x: [u8; 32],
467 #[serde(rename = "ephemeralKey", with = "hex")]
469 ephemeral_key: [u8; 32],
470 #[serde(rename = "encCiphertext", with = "arrayhex")]
472 enc_ciphertext: [u8; 580],
473 #[serde(rename = "spendAuthSig", with = "hex")]
475 spend_auth_sig: [u8; 64],
476 #[serde(rename = "outCiphertext", with = "hex")]
478 out_ciphertext: [u8; 80],
479}
480
481impl Default for TransactionObject {
482 fn default() -> Self {
483 Self {
484 hex: SerializedTransaction::from(
485 [0u8; zebra_chain::transaction::MIN_TRANSPARENT_TX_SIZE as usize].to_vec(),
486 ),
487 height: Option::default(),
488 confirmations: Option::default(),
489 inputs: Vec::new(),
490 outputs: Vec::new(),
491 shielded_spends: Vec::new(),
492 shielded_outputs: Vec::new(),
493 orchard: None,
494 binding_sig: None,
495 joinsplit_pub_key: None,
496 joinsplit_sig: None,
497 value_balance: None,
498 value_balance_zat: None,
499 size: None,
500 time: None,
501 txid: transaction::Hash::from([0u8; 32]),
502 in_active_chain: None,
503 auth_digest: None,
504 overwintered: false,
505 version: 4,
506 version_group_id: None,
507 lock_time: 0,
508 expiry_height: None,
509 block_hash: None,
510 block_time: None,
511 }
512 }
513}
514
515impl TransactionObject {
516 #[allow(clippy::unwrap_in_result)]
518 #[allow(clippy::too_many_arguments)]
519 pub fn from_transaction(
520 tx: Arc<Transaction>,
521 height: Option<block::Height>,
522 confirmations: Option<u32>,
523 network: &Network,
524 block_time: Option<DateTime<Utc>>,
525 block_hash: Option<block::Hash>,
526 in_active_chain: Option<bool>,
527 txid: transaction::Hash,
528 ) -> Self {
529 let block_time = block_time.map(|bt| bt.timestamp());
530 Self {
531 hex: tx.clone().into(),
532 height: height.map(|height| height.0),
533 confirmations,
534 inputs: tx
535 .inputs()
536 .iter()
537 .map(|input| match input {
538 zebra_chain::transparent::Input::Coinbase { sequence, .. } => Input::Coinbase {
539 coinbase: input
540 .coinbase_script()
541 .expect("we know it is a valid coinbase script"),
542 sequence: *sequence,
543 },
544 zebra_chain::transparent::Input::PrevOut {
545 sequence,
546 unlock_script,
547 outpoint,
548 } => Input::NonCoinbase {
549 txid: outpoint.hash.encode_hex(),
550 vout: outpoint.index,
551 script_sig: ScriptSig {
552 asm: "".to_string(),
553 hex: unlock_script.clone(),
554 },
555 sequence: *sequence,
556 value: None,
557 value_zat: None,
558 address: None,
559 },
560 })
561 .collect(),
562 outputs: tx
563 .outputs()
564 .iter()
565 .enumerate()
566 .map(|output| {
567 let (addresses, req_sigs) = output
569 .1
570 .address(network)
571 .map(|address| (vec![address.to_string()], 1))
572 .unzip();
573
574 Output {
575 value: Zec::from(output.1.value).lossy_zec(),
576 value_zat: output.1.value.zatoshis(),
577 n: output.0 as u32,
578 script_pub_key: ScriptPubKey {
579 asm: "".to_string(),
581 hex: output.1.lock_script.clone(),
582 req_sigs,
583 r#type: "".to_string(),
585 addresses,
586 },
587 }
588 })
589 .collect(),
590 shielded_spends: tx
591 .sapling_spends_per_anchor()
592 .map(|spend| {
593 let mut anchor = spend.per_spend_anchor.as_bytes();
594 anchor.reverse();
595
596 let mut nullifier = spend.nullifier.as_bytes();
597 nullifier.reverse();
598
599 let mut rk: [u8; 32] = spend.clone().rk.into();
600 rk.reverse();
601
602 let spend_auth_sig: [u8; 64] = spend.spend_auth_sig.into();
603
604 ShieldedSpend {
605 cv: spend.cv,
606 anchor,
607 nullifier,
608 rk,
609 proof: spend.proof().0,
610 spend_auth_sig,
611 }
612 })
613 .collect(),
614 shielded_outputs: tx
615 .sapling_outputs()
616 .map(|output| {
617 let mut cm_u: [u8; 32] = output.cm_u.to_bytes();
618 cm_u.reverse();
619 let mut ephemeral_key: [u8; 32] = output.ephemeral_key.into();
620 ephemeral_key.reverse();
621 let enc_ciphertext: [u8; 580] = output.enc_ciphertext.into();
622 let out_ciphertext: [u8; 80] = output.out_ciphertext.into();
623
624 ShieldedOutput {
625 cv: output.cv,
626 cm_u,
627 ephemeral_key,
628 enc_ciphertext,
629 out_ciphertext,
630 proof: output.proof().0,
631 }
632 })
633 .collect(),
634 value_balance: Some(Zec::from(tx.sapling_value_balance().sapling_amount()).lossy_zec()),
635 value_balance_zat: Some(tx.sapling_value_balance().sapling_amount().zatoshis()),
636 orchard: if !tx.has_orchard_shielded_data() {
637 None
638 } else {
639 Some(Orchard {
640 actions: tx
641 .orchard_actions()
642 .collect::<Vec<_>>()
643 .iter()
644 .map(|action| {
645 let spend_auth_sig: [u8; 64] = tx
646 .orchard_shielded_data()
647 .and_then(|shielded_data| {
648 shielded_data
649 .actions
650 .iter()
651 .find(|authorized_action| {
652 authorized_action.action == **action
653 })
654 .map(|authorized_action| {
655 authorized_action.spend_auth_sig.into()
656 })
657 })
658 .unwrap_or([0; 64]);
659
660 let cv: [u8; 32] = action.cv.into();
661 let nullifier: [u8; 32] = action.nullifier.into();
662 let rk: [u8; 32] = action.rk.into();
663 let cm_x: [u8; 32] = action.cm_x.into();
664 let ephemeral_key: [u8; 32] = action.ephemeral_key.into();
665 let enc_ciphertext: [u8; 580] = action.enc_ciphertext.into();
666 let out_ciphertext: [u8; 80] = action.out_ciphertext.into();
667
668 OrchardAction {
669 cv,
670 nullifier,
671 rk,
672 cm_x,
673 ephemeral_key,
674 enc_ciphertext,
675 spend_auth_sig,
676 out_ciphertext,
677 }
678 })
679 .collect(),
680 value_balance: Zec::from(tx.orchard_value_balance().orchard_amount())
681 .lossy_zec(),
682 value_balance_zat: tx.orchard_value_balance().orchard_amount().zatoshis(),
683 })
684 },
685 binding_sig: tx.sapling_binding_sig().map(|raw_sig| raw_sig.into()),
686 joinsplit_pub_key: tx.joinsplit_pub_key().map(|raw_key| {
687 let mut key: [u8; 32] = raw_key.into();
689 key.reverse();
690 key
691 }),
692 joinsplit_sig: tx.joinsplit_sig().map(|raw_sig| raw_sig.into()),
693 size: tx.as_bytes().len().try_into().ok(),
694 time: block_time,
695 txid,
696 in_active_chain,
697 auth_digest: tx.auth_digest(),
698 overwintered: tx.is_overwintered(),
699 version: tx.version(),
700 version_group_id: tx.version_group_id().map(|id| id.to_be_bytes().to_vec()),
701 lock_time: tx.raw_lock_time(),
702 expiry_height: tx.expiry_height(),
703 block_hash,
704 block_time,
705 }
706 }
707}