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 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/// Transaction data and fields needed to generate blocks using the `getblocktemplate` RPC.
27#[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    /// The hex-encoded serialized data for this transaction.
34    #[serde(with = "hex")]
35    pub(crate) data: SerializedTransaction,
36
37    /// The transaction ID of this transaction.
38    #[serde(with = "hex")]
39    #[getter(copy)]
40    pub(crate) hash: transaction::Hash,
41
42    /// The authorizing data digest of a v5 transaction, or a placeholder for older versions.
43    #[serde(rename = "authdigest")]
44    #[serde(with = "hex")]
45    #[getter(copy)]
46    pub(crate) auth_digest: transaction::AuthDigest,
47
48    /// The transactions in this block template that this transaction depends upon.
49    /// These are 1-based indexes in the `transactions` list.
50    ///
51    /// Zebra's mempool does not support transaction dependencies, so this list is always empty.
52    ///
53    /// We use `u16` because 2 MB blocks are limited to around 39,000 transactions.
54    pub(crate) depends: Vec<u16>,
55
56    /// The fee for this transaction.
57    ///
58    /// Non-coinbase transactions must be `NonNegative`.
59    /// The Coinbase transaction `fee` is the negative sum of the fees of the transactions in
60    /// the block, so their fee must be `NegativeOrZero`.
61    #[getter(copy)]
62    pub(crate) fee: Amount<FeeConstraint>,
63
64    /// The number of transparent signature operations in this transaction.
65    pub(crate) sigops: u64,
66
67    /// Is this transaction required in the block?
68    ///
69    /// Coinbase transactions are required, all other transactions are not.
70    pub(crate) required: bool,
71}
72
73// Convert from a mempool transaction to a non-coinbase transaction template.
74impl 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            // Always empty, not supported by Zebra's mempool.
91            depends: Vec::new(),
92
93            fee: tx.miner_fee,
94
95            sigops: tx.legacy_sigop_count,
96
97            // Zebra does not require any transactions except the coinbase transaction.
98            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    /// Convert from a generated coinbase transaction into a coinbase transaction template.
111    ///
112    /// `miner_fee` is the total miner fees for the block, excluding newly created block rewards.
113    //
114    // TODO: use a different type for generated coinbase transactions?
115    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            // Always empty, coinbase transactions never have inputs.
137            depends: Vec::new(),
138
139            fee: miner_fee,
140
141            sigops: legacy_sigop_count,
142
143            // Zcash requires a coinbase transaction.
144            required: true,
145        }
146    }
147}
148
149/// A Transaction object as returned by `getrawtransaction` and `getblock` RPC
150/// requests.
151#[allow(clippy::too_many_arguments)]
152#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
153pub struct TransactionObject {
154    /// Whether specified block is in the active chain or not (only present with
155    /// explicit "blockhash" argument)
156    #[serde(skip_serializing_if = "Option::is_none")]
157    #[getter(copy)]
158    pub(crate) in_active_chain: Option<bool>,
159    /// The raw transaction, encoded as hex bytes.
160    #[serde(with = "hex")]
161    pub(crate) hex: SerializedTransaction,
162    /// The height of the block in the best chain that contains the tx or `None` if the tx is in
163    /// the mempool.
164    #[serde(skip_serializing_if = "Option::is_none")]
165    #[getter(copy)]
166    pub(crate) height: Option<u32>,
167    /// The height diff between the block containing the tx and the best chain tip + 1 or `None`
168    /// if the tx is in the mempool.
169    #[serde(skip_serializing_if = "Option::is_none")]
170    #[getter(copy)]
171    pub(crate) confirmations: Option<u32>,
172
173    /// Transparent inputs of the transaction.
174    #[serde(rename = "vin")]
175    pub(crate) inputs: Vec<Input>,
176
177    /// Transparent outputs of the transaction.
178    #[serde(rename = "vout")]
179    pub(crate) outputs: Vec<Output>,
180
181    /// Sapling spends of the transaction.
182    #[serde(rename = "vShieldedSpend")]
183    pub(crate) shielded_spends: Vec<ShieldedSpend>,
184
185    /// Sapling outputs of the transaction.
186    #[serde(rename = "vShieldedOutput")]
187    pub(crate) shielded_outputs: Vec<ShieldedOutput>,
188
189    /// Sapling binding signature of the transaction.
190    #[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    /// JoinSplit public key of the transaction.
200    #[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    /// JoinSplit signature of the transaction.
210    #[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    /// Orchard actions of the transaction.
220    #[serde(rename = "orchard", skip_serializing_if = "Option::is_none")]
221    pub(crate) orchard: Option<Orchard>,
222
223    /// The net value of Sapling Spends minus Outputs in ZEC
224    #[serde(rename = "valueBalance", skip_serializing_if = "Option::is_none")]
225    #[getter(copy)]
226    pub(crate) value_balance: Option<f64>,
227
228    /// The net value of Sapling Spends minus Outputs in zatoshis
229    #[serde(rename = "valueBalanceZat", skip_serializing_if = "Option::is_none")]
230    #[getter(copy)]
231    pub(crate) value_balance_zat: Option<i64>,
232
233    /// The size of the transaction in bytes.
234    #[serde(skip_serializing_if = "Option::is_none")]
235    #[getter(copy)]
236    pub(crate) size: Option<i64>,
237
238    /// The time the transaction was included in a block.
239    #[serde(skip_serializing_if = "Option::is_none")]
240    #[getter(copy)]
241    pub(crate) time: Option<i64>,
242
243    /// The transaction identifier, encoded as hex bytes.
244    #[serde(with = "hex")]
245    #[getter(copy)]
246    pub txid: transaction::Hash,
247
248    // TODO: some fields not yet supported
249    //
250    /// The transaction's auth digest. For pre-v5 transactions this will be
251    /// ffff..ffff
252    #[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    /// Whether the overwintered flag is set
262    pub(crate) overwintered: bool,
263
264    /// The version of the transaction.
265    pub(crate) version: u32,
266
267    /// The version group ID.
268    #[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    /// The lock time
277    #[serde(rename = "locktime")]
278    pub(crate) lock_time: u32,
279
280    /// The block height after which the transaction expires
281    #[serde(rename = "expiryheight", skip_serializing_if = "Option::is_none")]
282    #[getter(copy)]
283    pub(crate) expiry_height: Option<Height>,
284
285    /// The block hash
286    #[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    /// The block height after which the transaction expires
296    #[serde(rename = "blocktime", skip_serializing_if = "Option::is_none")]
297    #[getter(copy)]
298    pub(crate) block_time: Option<i64>,
299}
300
301/// The transparent input of a transaction.
302#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
303#[serde(untagged)]
304pub enum Input {
305    /// A coinbase input.
306    Coinbase {
307        /// The coinbase scriptSig as hex.
308        #[serde(with = "hex")]
309        coinbase: Vec<u8>,
310        /// The script sequence number.
311        sequence: u32,
312    },
313    /// A non-coinbase input.
314    NonCoinbase {
315        /// The transaction id.
316        txid: String,
317        /// The vout index.
318        vout: u32,
319        /// The script.
320        #[serde(rename = "scriptSig")]
321        script_sig: ScriptSig,
322        /// The script sequence number.
323        sequence: u32,
324        /// The value of the output being spent in ZEC.
325        #[serde(skip_serializing_if = "Option::is_none")]
326        value: Option<f64>,
327        /// The value of the output being spent, in zats, named to match zcashd.
328        #[serde(rename = "valueSat", skip_serializing_if = "Option::is_none")]
329        value_zat: Option<i64>,
330        /// The address of the output being spent.
331        #[serde(skip_serializing_if = "Option::is_none")]
332        address: Option<String>,
333    },
334}
335
336/// The transparent output of a transaction.
337#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
338pub struct Output {
339    /// The value in ZEC.
340    value: f64,
341    /// The value in zats.
342    #[serde(rename = "valueZat")]
343    value_zat: i64,
344    /// index.
345    n: u32,
346    /// The scriptPubKey.
347    #[serde(rename = "scriptPubKey")]
348    script_pub_key: ScriptPubKey,
349}
350
351/// The scriptPubKey of a transaction output.
352#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
353pub struct ScriptPubKey {
354    /// the asm.
355    // #9330: The `asm` field is not currently populated.
356    asm: String,
357    /// the hex.
358    #[serde(with = "hex")]
359    hex: Script,
360    /// The required sigs.
361    #[serde(rename = "reqSigs")]
362    #[serde(default)]
363    #[serde(skip_serializing_if = "Option::is_none")]
364    #[getter(copy)]
365    req_sigs: Option<u32>,
366    /// The type, eg 'pubkeyhash'.
367    // #9330: The `type` field is not currently populated.
368    r#type: String,
369    /// The addresses.
370    #[serde(default)]
371    #[serde(skip_serializing_if = "Option::is_none")]
372    addresses: Option<Vec<String>>,
373}
374
375/// The scriptSig of a transaction input.
376#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
377pub struct ScriptSig {
378    /// The asm.
379    // #9330: The `asm` field is not currently populated.
380    asm: String,
381    /// The hex.
382    hex: Script,
383}
384
385/// A Sapling spend of a transaction.
386#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
387pub struct ShieldedSpend {
388    /// Value commitment to the input note.
389    #[serde(with = "hex")]
390    #[getter(copy)]
391    cv: NotSmallOrderValueCommitment,
392    /// Merkle root of the Sapling note commitment tree.
393    #[serde(with = "hex")]
394    #[getter(copy)]
395    anchor: [u8; 32],
396    /// The nullifier of the input note.
397    #[serde(with = "hex")]
398    #[getter(copy)]
399    nullifier: [u8; 32],
400    /// The randomized public key for spendAuthSig.
401    #[serde(with = "hex")]
402    #[getter(copy)]
403    rk: [u8; 32],
404    /// A zero-knowledge proof using the Sapling Spend circuit.
405    #[serde(with = "hex")]
406    #[getter(copy)]
407    proof: [u8; 192],
408    /// A signature authorizing this Spend.
409    #[serde(rename = "spendAuthSig", with = "hex")]
410    #[getter(copy)]
411    spend_auth_sig: [u8; 64],
412}
413
414/// A Sapling output of a transaction.
415#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
416pub struct ShieldedOutput {
417    /// Value commitment to the input note.
418    #[serde(with = "hex")]
419    #[getter(copy)]
420    cv: NotSmallOrderValueCommitment,
421    /// The u-coordinate of the note commitment for the output note.
422    #[serde(rename = "cmu", with = "hex")]
423    cm_u: [u8; 32],
424    /// A Jubjub public key.
425    #[serde(rename = "ephemeralKey", with = "hex")]
426    ephemeral_key: [u8; 32],
427    /// The output note encrypted to the recipient.
428    #[serde(rename = "encCiphertext", with = "arrayhex")]
429    enc_ciphertext: [u8; 580],
430    /// A ciphertext enabling the sender to recover the output note.
431    #[serde(rename = "outCiphertext", with = "hex")]
432    out_ciphertext: [u8; 80],
433    /// A zero-knowledge proof using the Sapling Output circuit.
434    #[serde(with = "hex")]
435    proof: [u8; 192],
436}
437
438/// Object with Orchard-specific information.
439#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
440pub struct Orchard {
441    /// Array of Orchard actions.
442    actions: Vec<OrchardAction>,
443    /// The net value of Orchard Actions in ZEC.
444    #[serde(rename = "valueBalance")]
445    value_balance: f64,
446    /// The net value of Orchard Actions in zatoshis.
447    #[serde(rename = "valueBalanceZat")]
448    value_balance_zat: i64,
449}
450
451/// The Orchard action of a transaction.
452#[allow(clippy::too_many_arguments)]
453#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
454pub struct OrchardAction {
455    /// A value commitment to the net value of the input note minus the output note.
456    #[serde(with = "hex")]
457    cv: [u8; 32],
458    /// The nullifier of the input note.
459    #[serde(with = "hex")]
460    nullifier: [u8; 32],
461    /// The randomized validating key for spendAuthSig.
462    #[serde(with = "hex")]
463    rk: [u8; 32],
464    /// The x-coordinate of the note commitment for the output note.
465    #[serde(rename = "cmx", with = "hex")]
466    cm_x: [u8; 32],
467    /// An encoding of an ephemeral Pallas public key.
468    #[serde(rename = "ephemeralKey", with = "hex")]
469    ephemeral_key: [u8; 32],
470    /// The output note encrypted to the recipient.
471    #[serde(rename = "encCiphertext", with = "arrayhex")]
472    enc_ciphertext: [u8; 580],
473    /// A ciphertext enabling the sender to recover the output note.
474    #[serde(rename = "spendAuthSig", with = "hex")]
475    spend_auth_sig: [u8; 64],
476    /// A signature authorizing the spend in this Action.
477    #[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    /// Converts `tx` and `height` into a new `GetRawTransaction` in the `verbose` format.
517    #[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                    // Parse the scriptPubKey to find destination addresses.
568                    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                            // TODO: Fill this out.
580                            asm: "".to_string(),
581                            hex: output.1.lock_script.clone(),
582                            req_sigs,
583                            // TODO: Fill this out.
584                            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                // Display order is reversed in the RPC output.
688                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}