Skip to main content

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 zcash_script::script::Asm;
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::ValueCommitment,
19    serialization::ZcashSerialize,
20    transaction::{self, SerializedTransaction, Transaction, UnminedTx, VerifiedUnminedTx},
21    transparent::Script,
22};
23use zebra_script::Sigops;
24use zebra_state::IntoDisk;
25
26use super::super::opthex;
27use super::zec::Zec;
28
29/// Transaction data and fields needed to generate blocks using the `getblocktemplate` RPC.
30#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
31#[serde(bound = "FeeConstraint: amount::Constraint + Clone")]
32pub struct TransactionTemplate<FeeConstraint>
33where
34    FeeConstraint: amount::Constraint + Clone + Copy,
35{
36    /// The hex-encoded serialized data for this transaction.
37    #[serde(with = "hex")]
38    pub(crate) data: SerializedTransaction,
39
40    /// The transaction ID of this transaction.
41    #[serde(with = "hex")]
42    #[getter(copy)]
43    pub(crate) hash: transaction::Hash,
44
45    /// The authorizing data digest of a v5 transaction, or a placeholder for older versions.
46    #[serde(rename = "authdigest")]
47    #[serde(with = "hex")]
48    #[getter(copy)]
49    pub(crate) auth_digest: transaction::AuthDigest,
50
51    /// The transactions in this block template that this transaction depends upon.
52    /// These are 1-based indexes in the `transactions` list.
53    ///
54    /// Zebra's mempool does not support transaction dependencies, so this list is always empty.
55    ///
56    /// We use `u16` because 2 MB blocks are limited to around 39,000 transactions.
57    pub(crate) depends: Vec<u16>,
58
59    /// The fee for this transaction.
60    ///
61    /// Non-coinbase transactions must be `NonNegative`.
62    /// The Coinbase transaction `fee` is the negative sum of the fees of the transactions in
63    /// the block, so their fee must be `NegativeOrZero`.
64    #[getter(copy)]
65    pub(crate) fee: Amount<FeeConstraint>,
66
67    /// The number of transparent signature operations in this transaction.
68    pub(crate) sigops: u32,
69
70    /// Is this transaction required in the block?
71    ///
72    /// Coinbase transactions are required, all other transactions are not.
73    pub(crate) required: bool,
74}
75
76// Convert from a mempool transaction to a non-coinbase transaction template.
77impl From<&VerifiedUnminedTx> for TransactionTemplate<NonNegative> {
78    fn from(tx: &VerifiedUnminedTx) -> Self {
79        assert!(
80            !tx.transaction.transaction.is_coinbase(),
81            "unexpected coinbase transaction in mempool"
82        );
83
84        Self {
85            data: tx.transaction.transaction.as_ref().into(),
86            hash: tx.transaction.id.mined_id(),
87            auth_digest: tx
88                .transaction
89                .id
90                .auth_digest()
91                .unwrap_or(AUTH_DIGEST_PLACEHOLDER),
92
93            // Always empty, not supported by Zebra's mempool.
94            depends: Vec::new(),
95
96            fee: tx.miner_fee,
97
98            sigops: tx.legacy_sigop_count,
99
100            // Zebra does not require any transactions except the coinbase transaction.
101            required: false,
102        }
103    }
104}
105
106impl From<VerifiedUnminedTx> for TransactionTemplate<NonNegative> {
107    fn from(tx: VerifiedUnminedTx) -> Self {
108        Self::from(&tx)
109    }
110}
111
112impl TransactionTemplate<NegativeOrZero> {
113    /// Convert from a generated coinbase transaction into a coinbase transaction template.
114    ///
115    /// `miner_fee` is the total miner fees for the block, excluding newly created block rewards.
116    //
117    // TODO: use a different type for generated coinbase transactions?
118    pub fn from_coinbase(tx: &UnminedTx, miner_fee: Amount<NonNegative>) -> Self {
119        assert!(
120            tx.transaction.is_coinbase(),
121            "invalid generated coinbase transaction: \
122             must have exactly one input, which must be a coinbase input",
123        );
124
125        let miner_fee = (-miner_fee)
126            .constrain()
127            .expect("negating a NonNegative amount always results in a valid NegativeOrZero");
128
129        Self {
130            data: tx.transaction.as_ref().into(),
131            hash: tx.id.mined_id(),
132            auth_digest: tx.id.auth_digest().unwrap_or(AUTH_DIGEST_PLACEHOLDER),
133
134            // Always empty, coinbase transactions never have inputs.
135            depends: Vec::new(),
136
137            fee: miner_fee,
138
139            sigops: tx.sigops().expect("sigops count should be valid"),
140
141            // Zcash requires a coinbase transaction.
142            required: true,
143        }
144    }
145}
146
147/// A Transaction object as returned by `getrawtransaction` and `getblock` RPC
148/// requests.
149#[allow(clippy::too_many_arguments)]
150#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
151pub struct TransactionObject {
152    /// Whether specified block is in the active chain or not (only present with
153    /// explicit "blockhash" argument)
154    #[serde(skip_serializing_if = "Option::is_none")]
155    #[getter(copy)]
156    pub(crate) in_active_chain: Option<bool>,
157    /// The raw transaction, encoded as hex bytes.
158    #[serde(with = "hex")]
159    pub(crate) hex: SerializedTransaction,
160    /// The height of the block in the best chain that contains the tx, -1 if
161    /// it's in a side chain block, or `None` if the tx is in the mempool.
162    #[serde(skip_serializing_if = "Option::is_none")]
163    #[getter(copy)]
164    pub(crate) height: Option<i32>,
165    /// The height diff between the block containing the tx and the best chain
166    /// tip + 1, 0 if it's in a side chain, or `None` if the tx is in the
167    /// 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    /// Included for Overwinter+ transactions (matching zcashd), omitted for V1/V2.
283    /// See: <https://github.com/zcash/zcash/blob/v6.11.0/src/rpc/rawtransaction.cpp#L224-L226>
284    #[serde(rename = "expiryheight", skip_serializing_if = "Option::is_none")]
285    #[getter(copy)]
286    pub(crate) expiry_height: Option<Height>,
287
288    /// The block hash
289    #[serde(
290        rename = "blockhash",
291        with = "opthex",
292        skip_serializing_if = "Option::is_none",
293        default
294    )]
295    #[getter(copy)]
296    pub(crate) block_hash: Option<block::Hash>,
297
298    /// The block height after which the transaction expires
299    #[serde(rename = "blocktime", skip_serializing_if = "Option::is_none")]
300    #[getter(copy)]
301    pub(crate) block_time: Option<i64>,
302}
303
304/// The transparent input of a transaction.
305#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
306#[serde(untagged)]
307pub enum Input {
308    /// A coinbase input.
309    Coinbase {
310        /// The coinbase scriptSig as hex.
311        #[serde(with = "hex")]
312        coinbase: Vec<u8>,
313        /// The script sequence number.
314        sequence: u32,
315    },
316    /// A non-coinbase input.
317    NonCoinbase {
318        /// The transaction id.
319        txid: String,
320        /// The vout index.
321        vout: u32,
322        /// The script.
323        #[serde(rename = "scriptSig")]
324        script_sig: ScriptSig,
325        /// The script sequence number.
326        sequence: u32,
327        /// The value of the output being spent in ZEC.
328        #[serde(skip_serializing_if = "Option::is_none")]
329        value: Option<f64>,
330        /// The value of the output being spent, in zats, named to match zcashd.
331        #[serde(rename = "valueSat", skip_serializing_if = "Option::is_none")]
332        value_zat: Option<i64>,
333        /// The address of the output being spent.
334        #[serde(skip_serializing_if = "Option::is_none")]
335        address: Option<String>,
336    },
337}
338
339/// The transparent output of a transaction.
340#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
341pub struct Output {
342    /// The value in ZEC.
343    value: f64,
344    /// The value in zats.
345    #[serde(rename = "valueZat")]
346    value_zat: i64,
347    /// index.
348    n: u32,
349    /// The scriptPubKey.
350    #[serde(rename = "scriptPubKey")]
351    script_pub_key: ScriptPubKey,
352}
353
354/// The output object returned by `gettxout` RPC requests.
355#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
356pub struct OutputObject {
357    #[serde(rename = "bestblock")]
358    best_block: String,
359    confirmations: u32,
360    value: f64,
361    #[serde(rename = "scriptPubKey")]
362    script_pub_key: ScriptPubKey,
363    version: u32,
364    coinbase: bool,
365}
366impl OutputObject {
367    pub fn from_output(
368        output: &zebra_chain::transparent::Output,
369        best_block: String,
370        confirmations: u32,
371        version: u32,
372        coinbase: bool,
373        network: &Network,
374    ) -> Self {
375        let lock_script = &output.lock_script;
376        let addresses = output.address(network).map(|addr| vec![addr.to_string()]);
377        let req_sigs = addresses.as_ref().map(|a| a.len() as u32);
378
379        let script_pub_key = ScriptPubKey::new(
380            zcash_script::script::Code(lock_script.as_raw_bytes().to_vec()).to_asm(false),
381            lock_script.clone(),
382            req_sigs,
383            zcash_script::script::Code(lock_script.as_raw_bytes().to_vec())
384                .to_component()
385                .ok()
386                .and_then(|c| c.refine().ok())
387                .and_then(|component| zcash_script::solver::standard(&component))
388                .map(|kind| match kind {
389                    zcash_script::solver::ScriptKind::PubKeyHash { .. } => "pubkeyhash",
390                    zcash_script::solver::ScriptKind::ScriptHash { .. } => "scripthash",
391                    zcash_script::solver::ScriptKind::MultiSig { .. } => "multisig",
392                    zcash_script::solver::ScriptKind::NullData { .. } => "nulldata",
393                    zcash_script::solver::ScriptKind::PubKey { .. } => "pubkey",
394                })
395                .unwrap_or("nonstandard")
396                .to_string(),
397            addresses,
398        );
399
400        Self {
401            best_block,
402            confirmations,
403            value: crate::methods::types::zec::Zec::from(output.value()).lossy_zec(),
404            script_pub_key,
405            version,
406            coinbase,
407        }
408    }
409}
410
411/// The scriptPubKey of a transaction output.
412#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
413pub struct ScriptPubKey {
414    /// the asm.
415    asm: String,
416    /// the hex.
417    #[serde(with = "hex")]
418    hex: Script,
419    /// The required sigs.
420    #[serde(rename = "reqSigs")]
421    #[serde(default)]
422    #[serde(skip_serializing_if = "Option::is_none")]
423    #[getter(copy)]
424    req_sigs: Option<u32>,
425    /// The type, eg 'pubkeyhash'.
426    r#type: String,
427    /// The addresses.
428    #[serde(default)]
429    #[serde(skip_serializing_if = "Option::is_none")]
430    addresses: Option<Vec<String>>,
431}
432
433/// The scriptSig of a transaction input.
434#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
435pub struct ScriptSig {
436    /// The asm.
437    asm: String,
438    /// The hex.
439    hex: Script,
440}
441
442/// A Sprout JoinSplit of a transaction.
443#[allow(clippy::too_many_arguments)]
444#[serde_with::serde_as]
445#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
446pub struct JoinSplit {
447    /// Public input value in ZEC.
448    #[serde(rename = "vpub_old")]
449    old_public_value: f64,
450    /// Public input value in zatoshis.
451    #[serde(rename = "vpub_oldZat")]
452    old_public_value_zat: i64,
453    /// Public input value in ZEC.
454    #[serde(rename = "vpub_new")]
455    new_public_value: f64,
456    /// Public input value in zatoshis.
457    #[serde(rename = "vpub_newZat")]
458    new_public_value_zat: i64,
459    /// Merkle root of the Sprout note commitment tree.
460    #[serde(with = "hex")]
461    #[getter(copy)]
462    anchor: [u8; 32],
463    /// The nullifier of the input notes.
464    #[serde_as(as = "Vec<serde_with::hex::Hex>")]
465    nullifiers: Vec<[u8; 32]>,
466    /// The commitments of the output notes.
467    #[serde_as(as = "Vec<serde_with::hex::Hex>")]
468    commitments: Vec<[u8; 32]>,
469    /// The onetime public key used to encrypt the ciphertexts
470    #[serde(rename = "onetimePubKey")]
471    #[serde(with = "hex")]
472    #[getter(copy)]
473    one_time_pubkey: [u8; 32],
474    /// The random seed
475    #[serde(rename = "randomSeed")]
476    #[serde(with = "hex")]
477    #[getter(copy)]
478    random_seed: [u8; 32],
479    /// The input notes MACs.
480    #[serde_as(as = "Vec<serde_with::hex::Hex>")]
481    macs: Vec<[u8; 32]>,
482    /// A zero-knowledge proof using the Sprout circuit.
483    #[serde(with = "hex")]
484    proof: Vec<u8>,
485    /// The output notes ciphertexts.
486    #[serde_as(as = "Vec<serde_with::hex::Hex>")]
487    ciphertexts: Vec<Vec<u8>>,
488}
489
490/// A Sapling spend of a transaction.
491#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
492pub struct ShieldedSpend {
493    /// Value commitment to the input note.
494    #[serde(with = "hex")]
495    #[getter(skip)]
496    cv: ValueCommitment,
497    /// Merkle root of the Sapling note commitment tree.
498    #[serde(with = "hex")]
499    #[getter(copy)]
500    anchor: [u8; 32],
501    /// The nullifier of the input note.
502    #[serde(with = "hex")]
503    #[getter(copy)]
504    nullifier: [u8; 32],
505    /// The randomized public key for spendAuthSig.
506    #[serde(with = "hex")]
507    #[getter(copy)]
508    rk: [u8; 32],
509    /// A zero-knowledge proof using the Sapling Spend circuit.
510    #[serde(with = "hex")]
511    #[getter(copy)]
512    proof: [u8; 192],
513    /// A signature authorizing this Spend.
514    #[serde(rename = "spendAuthSig", with = "hex")]
515    #[getter(copy)]
516    spend_auth_sig: [u8; 64],
517}
518
519// We can't use `#[getter(copy)]` as upstream `sapling_crypto::note::ValueCommitment` is not `Copy`.
520impl ShieldedSpend {
521    /// The value commitment to the input note.
522    pub fn cv(&self) -> ValueCommitment {
523        self.cv.clone()
524    }
525}
526
527/// A Sapling output of a transaction.
528#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
529pub struct ShieldedOutput {
530    /// Value commitment to the input note.
531    #[serde(with = "hex")]
532    #[getter(skip)]
533    cv: ValueCommitment,
534    /// The u-coordinate of the note commitment for the output note.
535    #[serde(rename = "cmu", with = "hex")]
536    cm_u: [u8; 32],
537    /// A Jubjub public key.
538    #[serde(rename = "ephemeralKey", with = "hex")]
539    ephemeral_key: [u8; 32],
540    /// The output note encrypted to the recipient.
541    #[serde(rename = "encCiphertext", with = "arrayhex")]
542    enc_ciphertext: [u8; 580],
543    /// A ciphertext enabling the sender to recover the output note.
544    #[serde(rename = "outCiphertext", with = "hex")]
545    out_ciphertext: [u8; 80],
546    /// A zero-knowledge proof using the Sapling Output circuit.
547    #[serde(with = "hex")]
548    proof: [u8; 192],
549}
550
551// We can't use `#[getter(copy)]` as upstream `sapling_crypto::note::ValueCommitment` is not `Copy`.
552impl ShieldedOutput {
553    /// The value commitment to the output note.
554    pub fn cv(&self) -> ValueCommitment {
555        self.cv.clone()
556    }
557}
558
559/// Object with Orchard-specific information.
560#[serde_with::serde_as]
561#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
562pub struct Orchard {
563    /// Array of Orchard actions.
564    actions: Vec<OrchardAction>,
565    /// The net value of Orchard Actions in ZEC.
566    #[serde(rename = "valueBalance")]
567    value_balance: f64,
568    /// The net value of Orchard Actions in zatoshis.
569    #[serde(rename = "valueBalanceZat")]
570    value_balance_zat: i64,
571    /// The flags.
572    #[serde(skip_serializing_if = "Option::is_none")]
573    flags: Option<OrchardFlags>,
574    /// A root of the Orchard note commitment tree at some block height in the past
575    #[serde_as(as = "Option<serde_with::hex::Hex>")]
576    #[serde(skip_serializing_if = "Option::is_none")]
577    #[getter(copy)]
578    anchor: Option<[u8; 32]>,
579    /// Encoding of aggregated zk-SNARK proofs for Orchard Actions
580    #[serde_as(as = "Option<serde_with::hex::Hex>")]
581    #[serde(skip_serializing_if = "Option::is_none")]
582    proof: Option<Vec<u8>>,
583    /// An Orchard binding signature on the SIGHASH transaction hash
584    #[serde(rename = "bindingSig")]
585    #[serde(skip_serializing_if = "Option::is_none")]
586    #[serde_as(as = "Option<serde_with::hex::Hex>")]
587    #[getter(copy)]
588    binding_sig: Option<[u8; 64]>,
589}
590
591/// Object with Orchard-specific information.
592#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
593pub struct OrchardFlags {
594    /// Whether Orchard outputs are enabled.
595    #[serde(rename = "enableOutputs")]
596    enable_outputs: bool,
597    /// Whether Orchard spends are enabled.
598    #[serde(rename = "enableSpends")]
599    enable_spends: bool,
600}
601
602/// The Orchard action of a transaction.
603#[allow(clippy::too_many_arguments)]
604#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
605pub struct OrchardAction {
606    /// A value commitment to the net value of the input note minus the output note.
607    #[serde(with = "hex")]
608    cv: [u8; 32],
609    /// The nullifier of the input note.
610    #[serde(with = "hex")]
611    nullifier: [u8; 32],
612    /// The randomized validating key for spendAuthSig.
613    #[serde(with = "hex")]
614    rk: [u8; 32],
615    /// The x-coordinate of the note commitment for the output note.
616    #[serde(rename = "cmx", with = "hex")]
617    cm_x: [u8; 32],
618    /// An encoding of an ephemeral Pallas public key.
619    #[serde(rename = "ephemeralKey", with = "hex")]
620    ephemeral_key: [u8; 32],
621    /// The output note encrypted to the recipient.
622    #[serde(rename = "encCiphertext", with = "arrayhex")]
623    enc_ciphertext: [u8; 580],
624    /// A ciphertext enabling the sender to recover the output note.
625    #[serde(rename = "spendAuthSig", with = "hex")]
626    spend_auth_sig: [u8; 64],
627    /// A signature authorizing the spend in this Action.
628    #[serde(rename = "outCiphertext", with = "hex")]
629    out_ciphertext: [u8; 80],
630}
631
632impl Default for TransactionObject {
633    fn default() -> Self {
634        Self {
635            hex: SerializedTransaction::from(
636                [0u8; zebra_chain::transaction::MIN_TRANSPARENT_TX_SIZE as usize].to_vec(),
637            ),
638            height: Option::default(),
639            confirmations: Option::default(),
640            inputs: Vec::new(),
641            outputs: Vec::new(),
642            shielded_spends: Vec::new(),
643            shielded_outputs: Vec::new(),
644            joinsplits: Vec::new(),
645            orchard: None,
646            binding_sig: None,
647            joinsplit_pub_key: None,
648            joinsplit_sig: None,
649            value_balance: None,
650            value_balance_zat: None,
651            size: None,
652            time: None,
653            txid: transaction::Hash::from([0u8; 32]),
654            in_active_chain: None,
655            auth_digest: None,
656            overwintered: false,
657            version: 4,
658            version_group_id: None,
659            lock_time: 0,
660            expiry_height: None,
661            block_hash: None,
662            block_time: None,
663        }
664    }
665}
666
667impl TransactionObject {
668    /// Converts `tx` and `height` into a new `GetRawTransaction` in the `verbose` format.
669    #[allow(clippy::unwrap_in_result)]
670    #[allow(clippy::too_many_arguments)]
671    pub fn from_transaction(
672        tx: Arc<Transaction>,
673        height: Option<block::Height>,
674        confirmations: Option<u32>,
675        network: &Network,
676        block_time: Option<DateTime<Utc>>,
677        block_hash: Option<block::Hash>,
678        in_active_chain: Option<bool>,
679        txid: transaction::Hash,
680    ) -> Self {
681        let block_time = block_time.map(|bt| bt.timestamp());
682        Self {
683            hex: tx.clone().into(),
684            height: if in_active_chain.unwrap_or_default() {
685                height.map(|height| height.0 as i32)
686            } else if block_hash.is_some() {
687                // Side chain
688                Some(-1)
689            } else {
690                // Mempool
691                None
692            },
693            confirmations: if in_active_chain.unwrap_or_default() {
694                confirmations
695            } else if block_hash.is_some() {
696                // Side chain
697                Some(0)
698            } else {
699                // Mempool
700                None
701            },
702            inputs: tx
703                .inputs()
704                .iter()
705                .map(|input| match input {
706                    zebra_chain::transparent::Input::Coinbase { sequence, .. } => Input::Coinbase {
707                        coinbase: input
708                            .coinbase_script()
709                            .expect("we know it is a valid coinbase script"),
710                        sequence: *sequence,
711                    },
712                    zebra_chain::transparent::Input::PrevOut {
713                        sequence,
714                        unlock_script,
715                        outpoint,
716                    } => Input::NonCoinbase {
717                        txid: outpoint.hash.encode_hex(),
718                        vout: outpoint.index,
719                        script_sig: ScriptSig {
720                            // https://github.com/zcash/zcash/blob/v6.11.0/src/rpc/rawtransaction.cpp#L240
721                            asm: zcash_script::script::Code(unlock_script.as_raw_bytes().to_vec())
722                                .to_asm(true),
723                            hex: unlock_script.clone(),
724                        },
725                        sequence: *sequence,
726                        value: None,
727                        value_zat: None,
728                        address: None,
729                    },
730                })
731                .collect(),
732            outputs: tx
733                .outputs()
734                .iter()
735                .enumerate()
736                .map(|output| {
737                    // Parse the scriptPubKey to find destination addresses.
738                    let (addresses, req_sigs) = output
739                        .1
740                        .address(network)
741                        .map(|address| (vec![address.to_string()], 1))
742                        .unzip();
743
744                    Output {
745                        value: Zec::from(output.1.value).lossy_zec(),
746                        value_zat: output.1.value.zatoshis(),
747                        n: output.0 as u32,
748                        script_pub_key: ScriptPubKey {
749                            // https://github.com/zcash/zcash/blob/v6.11.0/src/rpc/rawtransaction.cpp#L271
750                            // https://github.com/zcash/zcash/blob/v6.11.0/src/rpc/rawtransaction.cpp#L45
751                            asm: zcash_script::script::Code(
752                                output.1.lock_script.as_raw_bytes().to_vec(),
753                            )
754                            .to_asm(false),
755                            hex: output.1.lock_script.clone(),
756                            req_sigs,
757                            r#type: zcash_script::script::Code(
758                                output.1.lock_script.as_raw_bytes().to_vec(),
759                            )
760                            .to_component()
761                            .ok()
762                            .and_then(|c| c.refine().ok())
763                            .and_then(|component| zcash_script::solver::standard(&component))
764                            .map(|kind| match kind {
765                                zcash_script::solver::ScriptKind::PubKeyHash { .. } => "pubkeyhash",
766                                zcash_script::solver::ScriptKind::ScriptHash { .. } => "scripthash",
767                                zcash_script::solver::ScriptKind::MultiSig { .. } => "multisig",
768                                zcash_script::solver::ScriptKind::NullData { .. } => "nulldata",
769                                zcash_script::solver::ScriptKind::PubKey { .. } => "pubkey",
770                            })
771                            .unwrap_or("nonstandard")
772                            .to_string(),
773                            addresses,
774                        },
775                    }
776                })
777                .collect(),
778            shielded_spends: tx
779                .sapling_spends_per_anchor()
780                .map(|spend| {
781                    let mut anchor = spend.per_spend_anchor.as_bytes();
782                    anchor.reverse();
783
784                    let mut nullifier = spend.nullifier.as_bytes();
785                    nullifier.reverse();
786
787                    let mut rk: [u8; 32] = spend.clone().rk.into();
788                    rk.reverse();
789
790                    let spend_auth_sig: [u8; 64] = spend.spend_auth_sig.into();
791
792                    ShieldedSpend {
793                        cv: spend.cv.clone(),
794                        anchor,
795                        nullifier,
796                        rk,
797                        proof: spend.zkproof.0,
798                        spend_auth_sig,
799                    }
800                })
801                .collect(),
802            shielded_outputs: tx
803                .sapling_outputs()
804                .map(|output| {
805                    let mut cm_u: [u8; 32] = output.cm_u.to_bytes();
806                    cm_u.reverse();
807                    let mut ephemeral_key: [u8; 32] = output.ephemeral_key.into();
808                    ephemeral_key.reverse();
809                    let enc_ciphertext: [u8; 580] = output.enc_ciphertext.into();
810                    let out_ciphertext: [u8; 80] = output.out_ciphertext.into();
811
812                    ShieldedOutput {
813                        cv: output.cv.clone(),
814                        cm_u,
815                        ephemeral_key,
816                        enc_ciphertext,
817                        out_ciphertext,
818                        proof: output.zkproof.0,
819                    }
820                })
821                .collect(),
822            joinsplits: tx
823                .sprout_joinsplits()
824                .map(|joinsplit| {
825                    let mut ephemeral_key_bytes: [u8; 32] = joinsplit.ephemeral_key.to_bytes();
826                    ephemeral_key_bytes.reverse();
827
828                    JoinSplit {
829                        old_public_value: Zec::from(joinsplit.vpub_old).lossy_zec(),
830                        old_public_value_zat: joinsplit.vpub_old.zatoshis(),
831                        new_public_value: Zec::from(joinsplit.vpub_new).lossy_zec(),
832                        new_public_value_zat: joinsplit.vpub_new.zatoshis(),
833                        anchor: joinsplit.anchor.bytes_in_display_order(),
834                        nullifiers: joinsplit
835                            .nullifiers
836                            .iter()
837                            .map(|n| n.bytes_in_display_order())
838                            .collect(),
839                        commitments: joinsplit
840                            .commitments
841                            .iter()
842                            .map(|c| c.bytes_in_display_order())
843                            .collect(),
844                        one_time_pubkey: ephemeral_key_bytes,
845                        random_seed: joinsplit.random_seed.bytes_in_display_order(),
846                        macs: joinsplit
847                            .vmacs
848                            .iter()
849                            .map(|m| m.bytes_in_display_order())
850                            .collect(),
851                        proof: joinsplit.zkproof.unwrap_or_default(),
852                        ciphertexts: joinsplit
853                            .enc_ciphertexts
854                            .iter()
855                            .map(|c| c.zcash_serialize_to_vec().unwrap_or_default())
856                            .collect(),
857                    }
858                })
859                .collect(),
860            value_balance: Some(Zec::from(tx.sapling_value_balance().sapling_amount()).lossy_zec()),
861            value_balance_zat: Some(tx.sapling_value_balance().sapling_amount().zatoshis()),
862            orchard: Some(Orchard {
863                actions: tx
864                    .orchard_actions()
865                    .collect::<Vec<_>>()
866                    .iter()
867                    .map(|action| {
868                        let spend_auth_sig: [u8; 64] = tx
869                            .orchard_shielded_data()
870                            .and_then(|shielded_data| {
871                                shielded_data
872                                    .actions
873                                    .iter()
874                                    .find(|authorized_action| authorized_action.action == **action)
875                                    .map(|authorized_action| {
876                                        authorized_action.spend_auth_sig.into()
877                                    })
878                            })
879                            .unwrap_or([0; 64]);
880
881                        let cv: [u8; 32] = action.cv.into();
882                        let nullifier: [u8; 32] = action.nullifier.into();
883                        let rk: [u8; 32] = action.rk.into();
884                        let cm_x: [u8; 32] = action.cm_x.into();
885                        let ephemeral_key: [u8; 32] = action.ephemeral_key.into();
886                        let enc_ciphertext: [u8; 580] = action.enc_ciphertext.into();
887                        let out_ciphertext: [u8; 80] = action.out_ciphertext.into();
888
889                        OrchardAction {
890                            cv,
891                            nullifier,
892                            rk,
893                            cm_x,
894                            ephemeral_key,
895                            enc_ciphertext,
896                            spend_auth_sig,
897                            out_ciphertext,
898                        }
899                    })
900                    .collect(),
901                value_balance: Zec::from(tx.orchard_value_balance().orchard_amount()).lossy_zec(),
902                value_balance_zat: tx.orchard_value_balance().orchard_amount().zatoshis(),
903                flags: tx.orchard_shielded_data().map(|data| {
904                    OrchardFlags::new(
905                        data.flags.contains(orchard::Flags::ENABLE_OUTPUTS),
906                        data.flags.contains(orchard::Flags::ENABLE_SPENDS),
907                    )
908                }),
909                anchor: tx
910                    .orchard_shielded_data()
911                    .map(|data| data.shared_anchor.bytes_in_display_order()),
912                proof: tx
913                    .orchard_shielded_data()
914                    .map(|data| data.proof.bytes_in_display_order()),
915                binding_sig: tx
916                    .orchard_shielded_data()
917                    .map(|data| data.binding_sig.into()),
918            }),
919            binding_sig: tx.sapling_binding_sig().map(|raw_sig| raw_sig.into()),
920            joinsplit_pub_key: tx.joinsplit_pub_key().map(|raw_key| {
921                // Display order is reversed in the RPC output.
922                let mut key: [u8; 32] = raw_key.into();
923                key.reverse();
924                key
925            }),
926            joinsplit_sig: tx.joinsplit_sig().map(|raw_sig| raw_sig.into()),
927            size: tx.as_bytes().len().try_into().ok(),
928            time: block_time,
929            txid,
930            in_active_chain,
931            auth_digest: tx.auth_digest(),
932            overwintered: tx.is_overwintered(),
933            version: tx.version(),
934            version_group_id: tx.version_group_id().map(|id| id.to_be_bytes().to_vec()),
935            lock_time: tx.raw_lock_time(),
936            // zcashd includes expiryheight only for Overwinter+ transactions.
937            // For those, expiry_height of 0 means "no expiry" per ZIP-203.
938            expiry_height: if tx.is_overwintered() {
939                Some(tx.expiry_height().unwrap_or(Height(0)))
940            } else {
941                None
942            },
943            block_hash,
944            block_time,
945        }
946    }
947}