1use std::sync::Arc;
4
5use crate::methods::arrayhex;
6use chrono::{DateTime, Utc};
7use hex::ToHex;
8
9use zebra_chain::{
10 amount::{self, Amount, NegativeOrZero, NonNegative},
11 block::{self, merkle::AUTH_DIGEST_PLACEHOLDER},
12 parameters::Network,
13 sapling::NotSmallOrderValueCommitment,
14 transaction::{self, SerializedTransaction, Transaction, UnminedTx, VerifiedUnminedTx},
15 transparent::Script,
16};
17use zebra_consensus::groth16::Description;
18use zebra_script::CachedFfiTransaction;
19use zebra_state::IntoDisk;
20
21use super::zec::Zec;
22
23#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
25#[serde(bound = "FeeConstraint: amount::Constraint + Clone")]
26pub struct TransactionTemplate<FeeConstraint>
27where
28 FeeConstraint: amount::Constraint + Clone,
29{
30 #[serde(with = "hex")]
32 pub data: SerializedTransaction,
33
34 #[serde(with = "hex")]
36 pub(crate) hash: transaction::Hash,
37
38 #[serde(rename = "authdigest")]
40 #[serde(with = "hex")]
41 pub(crate) auth_digest: transaction::AuthDigest,
42
43 pub(crate) depends: Vec<u16>,
50
51 pub(crate) fee: Amount<FeeConstraint>,
57
58 pub(crate) sigops: u64,
60
61 pub(crate) required: bool,
65}
66
67impl From<&VerifiedUnminedTx> for TransactionTemplate<NonNegative> {
69 fn from(tx: &VerifiedUnminedTx) -> Self {
70 assert!(
71 !tx.transaction.transaction.is_coinbase(),
72 "unexpected coinbase transaction in mempool"
73 );
74
75 Self {
76 data: tx.transaction.transaction.as_ref().into(),
77 hash: tx.transaction.id.mined_id(),
78 auth_digest: tx
79 .transaction
80 .id
81 .auth_digest()
82 .unwrap_or(AUTH_DIGEST_PLACEHOLDER),
83
84 depends: Vec::new(),
86
87 fee: tx.miner_fee,
88
89 sigops: tx.legacy_sigop_count,
90
91 required: false,
93 }
94 }
95}
96
97impl From<VerifiedUnminedTx> for TransactionTemplate<NonNegative> {
98 fn from(tx: VerifiedUnminedTx) -> Self {
99 Self::from(&tx)
100 }
101}
102
103impl TransactionTemplate<NegativeOrZero> {
104 pub fn from_coinbase(tx: &UnminedTx, miner_fee: Amount<NonNegative>) -> Self {
110 assert!(
111 tx.transaction.is_coinbase(),
112 "invalid generated coinbase transaction: \
113 must have exactly one input, which must be a coinbase input",
114 );
115
116 let miner_fee = (-miner_fee)
117 .constrain()
118 .expect("negating a NonNegative amount always results in a valid NegativeOrZero");
119
120 let legacy_sigop_count = CachedFfiTransaction::new(tx.transaction.clone(), Vec::new())
121 .legacy_sigop_count()
122 .expect(
123 "invalid generated coinbase transaction: \
124 failure in zcash_script sigop count",
125 );
126
127 Self {
128 data: tx.transaction.as_ref().into(),
129 hash: tx.id.mined_id(),
130 auth_digest: tx.id.auth_digest().unwrap_or(AUTH_DIGEST_PLACEHOLDER),
131
132 depends: Vec::new(),
134
135 fee: miner_fee,
136
137 sigops: legacy_sigop_count,
138
139 required: true,
141 }
142 }
143}
144
145#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
148pub struct TransactionObject {
149 #[serde(with = "hex")]
151 pub hex: SerializedTransaction,
152 #[serde(skip_serializing_if = "Option::is_none")]
155 pub height: Option<u32>,
156 #[serde(skip_serializing_if = "Option::is_none")]
159 pub confirmations: Option<u32>,
160
161 #[serde(rename = "vin", skip_serializing_if = "Option::is_none")]
163 pub inputs: Option<Vec<Input>>,
164
165 #[serde(rename = "vout", skip_serializing_if = "Option::is_none")]
167 pub outputs: Option<Vec<Output>>,
168
169 #[serde(rename = "vShieldedSpend", skip_serializing_if = "Option::is_none")]
171 pub shielded_spends: Option<Vec<ShieldedSpend>>,
172
173 #[serde(rename = "vShieldedOutput", skip_serializing_if = "Option::is_none")]
175 pub shielded_outputs: Option<Vec<ShieldedOutput>>,
176
177 #[serde(rename = "orchard", skip_serializing_if = "Option::is_none")]
179 pub orchard: Option<Orchard>,
180
181 #[serde(rename = "valueBalance", skip_serializing_if = "Option::is_none")]
183 pub value_balance: Option<f64>,
184
185 #[serde(rename = "valueBalanceZat", skip_serializing_if = "Option::is_none")]
187 pub value_balance_zat: Option<i64>,
188
189 #[serde(skip_serializing_if = "Option::is_none")]
191 pub size: Option<i64>,
192
193 #[serde(skip_serializing_if = "Option::is_none")]
195 pub time: Option<i64>,
196 }
198
199#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
201#[serde(untagged)]
202pub enum Input {
203 Coinbase {
205 #[serde(with = "hex")]
207 coinbase: Vec<u8>,
208 sequence: u32,
210 },
211 NonCoinbase {
213 txid: String,
215 vout: u32,
217 #[serde(rename = "scriptSig")]
219 script_sig: ScriptSig,
220 sequence: u32,
222 #[serde(skip_serializing_if = "Option::is_none")]
224 value: Option<f64>,
225 #[serde(rename = "valueSat", skip_serializing_if = "Option::is_none")]
227 value_zat: Option<i64>,
228 #[serde(skip_serializing_if = "Option::is_none")]
230 address: Option<String>,
231 },
232}
233
234#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
236pub struct Output {
237 value: f64,
239 #[serde(rename = "valueZat")]
241 value_zat: i64,
242 n: u32,
244 #[serde(rename = "scriptPubKey")]
246 script_pub_key: ScriptPubKey,
247}
248
249#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
251pub struct ScriptPubKey {
252 asm: String,
255 #[serde(with = "hex")]
257 hex: Script,
258 #[serde(rename = "reqSigs")]
260 #[serde(default)]
261 #[serde(skip_serializing_if = "Option::is_none")]
262 req_sigs: Option<u32>,
263 r#type: String,
266 #[serde(default)]
268 #[serde(skip_serializing_if = "Option::is_none")]
269 addresses: Option<Vec<String>>,
270}
271
272#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
274pub struct ScriptSig {
275 asm: String,
278 hex: Script,
280}
281
282#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
284pub struct ShieldedSpend {
285 #[serde(with = "hex")]
287 cv: NotSmallOrderValueCommitment,
288 #[serde(with = "hex")]
290 anchor: [u8; 32],
291 #[serde(with = "hex")]
293 nullifier: [u8; 32],
294 #[serde(with = "hex")]
296 rk: [u8; 32],
297 #[serde(with = "hex")]
299 proof: [u8; 192],
300 #[serde(rename = "spendAuthSig", with = "hex")]
302 spend_auth_sig: [u8; 64],
303}
304
305#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
307pub struct ShieldedOutput {
308 #[serde(with = "hex")]
310 cv: NotSmallOrderValueCommitment,
311 #[serde(rename = "cmu", with = "hex")]
313 cm_u: [u8; 32],
314 #[serde(rename = "ephemeralKey", with = "hex")]
316 ephemeral_key: [u8; 32],
317 #[serde(rename = "encCiphertext", with = "arrayhex")]
319 enc_ciphertext: [u8; 580],
320 #[serde(rename = "outCiphertext", with = "hex")]
322 out_ciphertext: [u8; 80],
323 #[serde(with = "hex")]
325 proof: [u8; 192],
326}
327
328#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
330pub struct Orchard {
331 actions: Vec<OrchardAction>,
333 #[serde(rename = "valueBalance")]
335 value_balance: f64,
336 #[serde(rename = "valueBalanceZat")]
338 value_balance_zat: i64,
339}
340
341#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
343pub struct OrchardAction {
344 #[serde(with = "hex")]
346 cv: [u8; 32],
347 #[serde(with = "hex")]
349 nullifier: [u8; 32],
350 #[serde(with = "hex")]
352 rk: [u8; 32],
353 #[serde(rename = "cmx", with = "hex")]
355 cm_x: [u8; 32],
356 #[serde(rename = "ephemeralKey", with = "hex")]
358 ephemeral_key: [u8; 32],
359 #[serde(rename = "encCiphertext", with = "arrayhex")]
361 enc_ciphertext: [u8; 580],
362 #[serde(rename = "spendAuthSig", with = "hex")]
364 spend_auth_sig: [u8; 64],
365 #[serde(rename = "outCiphertext", with = "hex")]
367 out_ciphertext: [u8; 80],
368}
369
370impl Default for TransactionObject {
371 fn default() -> Self {
372 Self {
373 hex: SerializedTransaction::from(
374 [0u8; zebra_chain::transaction::MIN_TRANSPARENT_TX_SIZE as usize].to_vec(),
375 ),
376 height: Option::default(),
377 confirmations: Option::default(),
378 inputs: None,
379 outputs: None,
380 shielded_spends: None,
381 shielded_outputs: None,
382 orchard: None,
383 value_balance: None,
384 value_balance_zat: None,
385 size: None,
386 time: None,
387 }
388 }
389}
390
391impl TransactionObject {
392 #[allow(clippy::unwrap_in_result)]
394 pub fn from_transaction(
395 tx: Arc<Transaction>,
396 height: Option<block::Height>,
397 confirmations: Option<u32>,
398 network: &Network,
399 block_time: Option<DateTime<Utc>>,
400 ) -> Self {
401 Self {
402 hex: tx.clone().into(),
403 height: height.map(|height| height.0),
404 confirmations,
405 inputs: Some(
406 tx.inputs()
407 .iter()
408 .map(|input| match input {
409 zebra_chain::transparent::Input::Coinbase { sequence, .. } => {
410 Input::Coinbase {
411 coinbase: input
412 .coinbase_script()
413 .expect("we know it is a valid coinbase script"),
414 sequence: *sequence,
415 }
416 }
417 zebra_chain::transparent::Input::PrevOut {
418 sequence,
419 unlock_script,
420 outpoint,
421 } => Input::NonCoinbase {
422 txid: outpoint.hash.encode_hex(),
423 vout: outpoint.index,
424 script_sig: ScriptSig {
425 asm: "".to_string(),
426 hex: unlock_script.clone(),
427 },
428 sequence: *sequence,
429 value: None,
430 value_zat: None,
431 address: None,
432 },
433 })
434 .collect(),
435 ),
436 outputs: Some(
437 tx.outputs()
438 .iter()
439 .enumerate()
440 .map(|output| {
441 let (addresses, req_sigs) = match output.1.address(network) {
443 Some(address) => Some((vec![address.to_string()], 1)),
450 None => None,
452 }
453 .unzip();
454
455 Output {
456 value: Zec::from(output.1.value).lossy_zec(),
457 value_zat: output.1.value.zatoshis(),
458 n: output.0 as u32,
459 script_pub_key: ScriptPubKey {
460 asm: "".to_string(),
462 hex: output.1.lock_script.clone(),
463 req_sigs,
464 r#type: "".to_string(),
466 addresses,
467 },
468 }
469 })
470 .collect(),
471 ),
472 shielded_spends: Some(
473 tx.sapling_spends_per_anchor()
474 .map(|spend| {
475 let mut anchor = spend.per_spend_anchor.as_bytes();
476 anchor.reverse();
477
478 let mut nullifier = spend.nullifier.as_bytes();
479 nullifier.reverse();
480
481 let mut rk: [u8; 32] = spend.clone().rk.into();
482 rk.reverse();
483
484 let spend_auth_sig: [u8; 64] = spend.spend_auth_sig.into();
485
486 ShieldedSpend {
487 cv: spend.cv,
488 anchor,
489 nullifier,
490 rk,
491 proof: spend.proof().0,
492 spend_auth_sig,
493 }
494 })
495 .collect(),
496 ),
497 shielded_outputs: Some(
498 tx.sapling_outputs()
499 .map(|output| {
500 let mut ephemeral_key: [u8; 32] = output.ephemeral_key.into();
501 ephemeral_key.reverse();
502 let enc_ciphertext: [u8; 580] = output.enc_ciphertext.into();
503 let out_ciphertext: [u8; 80] = output.out_ciphertext.into();
504
505 ShieldedOutput {
506 cv: output.cv,
507 cm_u: output.cm_u.to_bytes(),
508 ephemeral_key,
509 enc_ciphertext,
510 out_ciphertext,
511 proof: output.proof().0,
512 }
513 })
514 .collect(),
515 ),
516 value_balance: Some(Zec::from(tx.sapling_value_balance().sapling_amount()).lossy_zec()),
517 value_balance_zat: Some(tx.sapling_value_balance().sapling_amount().zatoshis()),
518
519 orchard: if !tx.has_orchard_shielded_data() {
520 None
521 } else {
522 Some(Orchard {
523 actions: tx
524 .orchard_actions()
525 .collect::<Vec<_>>()
526 .iter()
527 .map(|action| {
528 let spend_auth_sig: [u8; 64] = tx
529 .orchard_shielded_data()
530 .and_then(|shielded_data| {
531 shielded_data
532 .actions
533 .iter()
534 .find(|authorized_action| {
535 authorized_action.action == **action
536 })
537 .map(|authorized_action| {
538 authorized_action.spend_auth_sig.into()
539 })
540 })
541 .unwrap_or([0; 64]);
542
543 let cv: [u8; 32] = action.cv.into();
544 let nullifier: [u8; 32] = action.nullifier.into();
545 let rk: [u8; 32] = action.rk.into();
546 let cm_x: [u8; 32] = action.cm_x.into();
547 let ephemeral_key: [u8; 32] = action.ephemeral_key.into();
548 let enc_ciphertext: [u8; 580] = action.enc_ciphertext.into();
549 let out_ciphertext: [u8; 80] = action.out_ciphertext.into();
550
551 OrchardAction {
552 cv,
553 nullifier,
554 rk,
555 cm_x,
556 ephemeral_key,
557 enc_ciphertext,
558 spend_auth_sig,
559 out_ciphertext,
560 }
561 })
562 .collect(),
563 value_balance: Zec::from(tx.orchard_value_balance().orchard_amount())
564 .lossy_zec(),
565 value_balance_zat: tx.orchard_value_balance().orchard_amount().zatoshis(),
566 })
567 },
568 size: tx.as_bytes().len().try_into().ok(),
569 time: block_time.map(|bt| bt.timestamp()),
570 }
571 }
572}