zebra_rpc/methods/types/
transaction.rs

1//! Transaction-related types.
2
3use 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/// Transaction data and fields needed to generate blocks using the `getblocktemplate` RPC.
31#[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    /// The hex-encoded serialized data for this transaction.
38    #[serde(with = "hex")]
39    pub(crate) data: SerializedTransaction,
40
41    /// The transaction ID of this transaction.
42    #[serde(with = "hex")]
43    #[getter(copy)]
44    pub(crate) hash: transaction::Hash,
45
46    /// The authorizing data digest of a v5 transaction, or a placeholder for older versions.
47    #[serde(rename = "authdigest")]
48    #[serde(with = "hex")]
49    #[getter(copy)]
50    pub(crate) auth_digest: transaction::AuthDigest,
51
52    /// The transactions in this block template that this transaction depends upon.
53    /// These are 1-based indexes in the `transactions` list.
54    ///
55    /// Zebra's mempool does not support transaction dependencies, so this list is always empty.
56    ///
57    /// We use `u16` because 2 MB blocks are limited to around 39,000 transactions.
58    pub(crate) depends: Vec<u16>,
59
60    /// The fee for this transaction.
61    ///
62    /// Non-coinbase transactions must be `NonNegative`.
63    /// The Coinbase transaction `fee` is the negative sum of the fees of the transactions in
64    /// the block, so their fee must be `NegativeOrZero`.
65    #[getter(copy)]
66    pub(crate) fee: Amount<FeeConstraint>,
67
68    /// The number of transparent signature operations in this transaction.
69    pub(crate) sigops: u32,
70
71    /// Is this transaction required in the block?
72    ///
73    /// Coinbase transactions are required, all other transactions are not.
74    pub(crate) required: bool,
75}
76
77// Convert from a mempool transaction to a non-coinbase transaction template.
78impl 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            // Always empty, not supported by Zebra's mempool.
95            depends: Vec::new(),
96
97            fee: tx.miner_fee,
98
99            sigops: tx.sigops,
100
101            // Zebra does not require any transactions except the coinbase transaction.
102            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    /// Convert from a generated coinbase transaction into a coinbase transaction template.
115    ///
116    /// `miner_fee` is the total miner fees for the block, excluding newly created block rewards.
117    //
118    // TODO: use a different type for generated coinbase transactions?
119    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            // Always empty, coinbase transactions never have inputs.
136            depends: Vec::new(),
137
138            fee: miner_fee,
139
140            sigops: tx.sigops().expect("sigops count should be valid"),
141
142            // Zcash requires a coinbase transaction.
143            required: true,
144        }
145    }
146}
147
148/// A Transaction object as returned by `getrawtransaction` and `getblock` RPC
149/// requests.
150#[allow(clippy::too_many_arguments)]
151#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
152pub struct TransactionObject {
153    /// Whether specified block is in the active chain or not (only present with
154    /// explicit "blockhash" argument)
155    #[serde(skip_serializing_if = "Option::is_none")]
156    #[getter(copy)]
157    pub(crate) in_active_chain: Option<bool>,
158    /// The raw transaction, encoded as hex bytes.
159    #[serde(with = "hex")]
160    pub(crate) hex: SerializedTransaction,
161    /// The height of the block in the best chain that contains the tx or `None` if the tx is in
162    /// the mempool.
163    #[serde(skip_serializing_if = "Option::is_none")]
164    #[getter(copy)]
165    pub(crate) height: Option<u32>,
166    /// The height diff between the block containing the tx and the best chain tip + 1 or `None`
167    /// if the tx is in the mempool.
168    #[serde(skip_serializing_if = "Option::is_none")]
169    #[getter(copy)]
170    pub(crate) confirmations: Option<u32>,
171
172    /// Transparent inputs of the transaction.
173    #[serde(rename = "vin")]
174    pub(crate) inputs: Vec<Input>,
175
176    /// Transparent outputs of the transaction.
177    #[serde(rename = "vout")]
178    pub(crate) outputs: Vec<Output>,
179
180    /// Sapling spends of the transaction.
181    #[serde(rename = "vShieldedSpend")]
182    pub(crate) shielded_spends: Vec<ShieldedSpend>,
183
184    /// Sapling outputs of the transaction.
185    #[serde(rename = "vShieldedOutput")]
186    pub(crate) shielded_outputs: Vec<ShieldedOutput>,
187
188    /// Transparent outputs of the transaction.
189    #[serde(rename = "vjoinsplit")]
190    pub(crate) joinsplits: Vec<JoinSplit>,
191
192    /// Sapling binding signature of the transaction.
193    #[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    /// JoinSplit public key of the transaction.
203    #[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    /// JoinSplit signature of the transaction.
213    #[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    /// Orchard actions of the transaction.
223    #[serde(rename = "orchard", skip_serializing_if = "Option::is_none")]
224    pub(crate) orchard: Option<Orchard>,
225
226    /// The net value of Sapling Spends minus Outputs in ZEC
227    #[serde(rename = "valueBalance", skip_serializing_if = "Option::is_none")]
228    #[getter(copy)]
229    pub(crate) value_balance: Option<f64>,
230
231    /// The net value of Sapling Spends minus Outputs in zatoshis
232    #[serde(rename = "valueBalanceZat", skip_serializing_if = "Option::is_none")]
233    #[getter(copy)]
234    pub(crate) value_balance_zat: Option<i64>,
235
236    /// The size of the transaction in bytes.
237    #[serde(skip_serializing_if = "Option::is_none")]
238    #[getter(copy)]
239    pub(crate) size: Option<i64>,
240
241    /// The time the transaction was included in a block.
242    #[serde(skip_serializing_if = "Option::is_none")]
243    #[getter(copy)]
244    pub(crate) time: Option<i64>,
245
246    /// The transaction identifier, encoded as hex bytes.
247    #[serde(with = "hex")]
248    #[getter(copy)]
249    pub txid: transaction::Hash,
250
251    /// The transaction's auth digest. For pre-v5 transactions this will be
252    /// ffff..ffff
253    #[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    /// Whether the overwintered flag is set
263    pub(crate) overwintered: bool,
264
265    /// The version of the transaction.
266    pub(crate) version: u32,
267
268    /// The version group ID.
269    #[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    /// The lock time
278    #[serde(rename = "locktime")]
279    pub(crate) lock_time: u32,
280
281    /// The block height after which the transaction expires
282    #[serde(rename = "expiryheight", skip_serializing_if = "Option::is_none")]
283    #[getter(copy)]
284    pub(crate) expiry_height: Option<Height>,
285
286    /// The block hash
287    #[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    /// The block height after which the transaction expires
297    #[serde(rename = "blocktime", skip_serializing_if = "Option::is_none")]
298    #[getter(copy)]
299    pub(crate) block_time: Option<i64>,
300}
301
302/// The transparent input of a transaction.
303#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
304#[serde(untagged)]
305pub enum Input {
306    /// A coinbase input.
307    Coinbase {
308        /// The coinbase scriptSig as hex.
309        #[serde(with = "hex")]
310        coinbase: Vec<u8>,
311        /// The script sequence number.
312        sequence: u32,
313    },
314    /// A non-coinbase input.
315    NonCoinbase {
316        /// The transaction id.
317        txid: String,
318        /// The vout index.
319        vout: u32,
320        /// The script.
321        #[serde(rename = "scriptSig")]
322        script_sig: ScriptSig,
323        /// The script sequence number.
324        sequence: u32,
325        /// The value of the output being spent in ZEC.
326        #[serde(skip_serializing_if = "Option::is_none")]
327        value: Option<f64>,
328        /// The value of the output being spent, in zats, named to match zcashd.
329        #[serde(rename = "valueSat", skip_serializing_if = "Option::is_none")]
330        value_zat: Option<i64>,
331        /// The address of the output being spent.
332        #[serde(skip_serializing_if = "Option::is_none")]
333        address: Option<String>,
334    },
335}
336
337/// The transparent output of a transaction.
338#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
339pub struct Output {
340    /// The value in ZEC.
341    value: f64,
342    /// The value in zats.
343    #[serde(rename = "valueZat")]
344    value_zat: i64,
345    /// index.
346    n: u32,
347    /// The scriptPubKey.
348    #[serde(rename = "scriptPubKey")]
349    script_pub_key: ScriptPubKey,
350}
351
352/// The scriptPubKey of a transaction output.
353#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
354pub struct ScriptPubKey {
355    /// the asm.
356    // #9330: The `asm` field is not currently populated.
357    asm: String,
358    /// the hex.
359    #[serde(with = "hex")]
360    hex: Script,
361    /// The required sigs.
362    #[serde(rename = "reqSigs")]
363    #[serde(default)]
364    #[serde(skip_serializing_if = "Option::is_none")]
365    #[getter(copy)]
366    req_sigs: Option<u32>,
367    /// The type, eg 'pubkeyhash'.
368    // #9330: The `type` field is not currently populated.
369    r#type: String,
370    /// The addresses.
371    #[serde(default)]
372    #[serde(skip_serializing_if = "Option::is_none")]
373    addresses: Option<Vec<String>>,
374}
375
376/// The scriptSig of a transaction input.
377#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
378pub struct ScriptSig {
379    /// The asm.
380    // #9330: The `asm` field is not currently populated.
381    asm: String,
382    /// The hex.
383    hex: Script,
384}
385
386/// A Sprout JoinSplit of a transaction.
387#[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    /// Public input value in ZEC.
392    #[serde(rename = "vpub_old")]
393    old_public_value: f64,
394    /// Public input value in zatoshis.
395    #[serde(rename = "vpub_oldZat")]
396    old_public_value_zat: i64,
397    /// Public input value in ZEC.
398    #[serde(rename = "vpub_new")]
399    new_public_value: f64,
400    /// Public input value in zatoshis.
401    #[serde(rename = "vpub_newZat")]
402    new_public_value_zat: i64,
403    /// Merkle root of the Sprout note commitment tree.
404    #[serde(with = "hex")]
405    #[getter(copy)]
406    anchor: [u8; 32],
407    /// The nullifier of the input notes.
408    #[serde_as(as = "Vec<serde_with::hex::Hex>")]
409    nullifiers: Vec<[u8; 32]>,
410    /// The commitments of the output notes.
411    #[serde_as(as = "Vec<serde_with::hex::Hex>")]
412    commitments: Vec<[u8; 32]>,
413    /// The onetime public key used to encrypt the ciphertexts
414    #[serde(rename = "onetimePubKey")]
415    #[serde(with = "hex")]
416    #[getter(copy)]
417    one_time_pubkey: [u8; 32],
418    /// The random seed
419    #[serde(rename = "randomSeed")]
420    #[serde(with = "hex")]
421    #[getter(copy)]
422    random_seed: [u8; 32],
423    /// The input notes MACs.
424    #[serde_as(as = "Vec<serde_with::hex::Hex>")]
425    macs: Vec<[u8; 32]>,
426    /// A zero-knowledge proof using the Sprout circuit.
427    #[serde(with = "hex")]
428    proof: Vec<u8>,
429    /// The output notes ciphertexts.
430    #[serde_as(as = "Vec<serde_with::hex::Hex>")]
431    ciphertexts: Vec<Vec<u8>>,
432}
433
434/// A Sapling spend of a transaction.
435#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
436pub struct ShieldedSpend {
437    /// Value commitment to the input note.
438    #[serde(with = "hex")]
439    #[getter(copy)]
440    cv: NotSmallOrderValueCommitment,
441    /// Merkle root of the Sapling note commitment tree.
442    #[serde(with = "hex")]
443    #[getter(copy)]
444    anchor: [u8; 32],
445    /// The nullifier of the input note.
446    #[serde(with = "hex")]
447    #[getter(copy)]
448    nullifier: [u8; 32],
449    /// The randomized public key for spendAuthSig.
450    #[serde(with = "hex")]
451    #[getter(copy)]
452    rk: [u8; 32],
453    /// A zero-knowledge proof using the Sapling Spend circuit.
454    #[serde(with = "hex")]
455    #[getter(copy)]
456    proof: [u8; 192],
457    /// A signature authorizing this Spend.
458    #[serde(rename = "spendAuthSig", with = "hex")]
459    #[getter(copy)]
460    spend_auth_sig: [u8; 64],
461}
462
463/// A Sapling output of a transaction.
464#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
465pub struct ShieldedOutput {
466    /// Value commitment to the input note.
467    #[serde(with = "hex")]
468    #[getter(copy)]
469    cv: NotSmallOrderValueCommitment,
470    /// The u-coordinate of the note commitment for the output note.
471    #[serde(rename = "cmu", with = "hex")]
472    cm_u: [u8; 32],
473    /// A Jubjub public key.
474    #[serde(rename = "ephemeralKey", with = "hex")]
475    ephemeral_key: [u8; 32],
476    /// The output note encrypted to the recipient.
477    #[serde(rename = "encCiphertext", with = "arrayhex")]
478    enc_ciphertext: [u8; 580],
479    /// A ciphertext enabling the sender to recover the output note.
480    #[serde(rename = "outCiphertext", with = "hex")]
481    out_ciphertext: [u8; 80],
482    /// A zero-knowledge proof using the Sapling Output circuit.
483    #[serde(with = "hex")]
484    proof: [u8; 192],
485}
486
487/// Object with Orchard-specific information.
488#[serde_with::serde_as]
489#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
490pub struct Orchard {
491    /// Array of Orchard actions.
492    actions: Vec<OrchardAction>,
493    /// The net value of Orchard Actions in ZEC.
494    #[serde(rename = "valueBalance")]
495    value_balance: f64,
496    /// The net value of Orchard Actions in zatoshis.
497    #[serde(rename = "valueBalanceZat")]
498    value_balance_zat: i64,
499    /// The flags.
500    #[serde(skip_serializing_if = "Option::is_none")]
501    flags: Option<OrchardFlags>,
502    /// A root of the Orchard note commitment tree at some block height in the past
503    #[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    /// Encoding of aggregated zk-SNARK proofs for Orchard Actions
508    #[serde_as(as = "Option<serde_with::hex::Hex>")]
509    #[serde(skip_serializing_if = "Option::is_none")]
510    proof: Option<Vec<u8>>,
511    /// An Orchard binding signature on the SIGHASH transaction hash
512    #[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/// Object with Orchard-specific information.
520#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
521pub struct OrchardFlags {
522    /// Whether Orchard outputs are enabled.
523    #[serde(rename = "enableOutputs")]
524    enable_outputs: bool,
525    /// Whether Orchard spends are enabled.
526    #[serde(rename = "enableSpends")]
527    enable_spends: bool,
528}
529
530/// The Orchard action of a transaction.
531#[allow(clippy::too_many_arguments)]
532#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
533pub struct OrchardAction {
534    /// A value commitment to the net value of the input note minus the output note.
535    #[serde(with = "hex")]
536    cv: [u8; 32],
537    /// The nullifier of the input note.
538    #[serde(with = "hex")]
539    nullifier: [u8; 32],
540    /// The randomized validating key for spendAuthSig.
541    #[serde(with = "hex")]
542    rk: [u8; 32],
543    /// The x-coordinate of the note commitment for the output note.
544    #[serde(rename = "cmx", with = "hex")]
545    cm_x: [u8; 32],
546    /// An encoding of an ephemeral Pallas public key.
547    #[serde(rename = "ephemeralKey", with = "hex")]
548    ephemeral_key: [u8; 32],
549    /// The output note encrypted to the recipient.
550    #[serde(rename = "encCiphertext", with = "arrayhex")]
551    enc_ciphertext: [u8; 580],
552    /// A ciphertext enabling the sender to recover the output note.
553    #[serde(rename = "spendAuthSig", with = "hex")]
554    spend_auth_sig: [u8; 64],
555    /// A signature authorizing the spend in this Action.
556    #[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    /// Converts `tx` and `height` into a new `GetRawTransaction` in the `verbose` format.
597    #[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                    // Parse the scriptPubKey to find destination addresses.
648                    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                            // TODO: Fill this out.
660                            asm: "".to_string(),
661                            hex: output.1.lock_script.clone(),
662                            req_sigs,
663                            // TODO: Fill this out.
664                            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                // Display order is reversed in the RPC output.
814                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}