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 hex::ToHex;
8
9use zebra_chain::{
10    amount::{self, Amount, NegativeOrZero, NonNegative},
11    block::{self, merkle::AUTH_DIGEST_PLACEHOLDER},
12    parameters::Network,
13    sapling::NotSmallOrderValueCommitment,
14    transaction::{self, SerializedTransaction, Transaction, UnminedTx, VerifiedUnminedTx},
15    transparent::Script,
16};
17use zebra_consensus::groth16::Description;
18use zebra_script::CachedFfiTransaction;
19use zebra_state::IntoDisk;
20
21use super::zec::Zec;
22
23/// Transaction data and fields needed to generate blocks using the `getblocktemplate` RPC.
24#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
25#[serde(bound = "FeeConstraint: amount::Constraint + Clone")]
26pub struct TransactionTemplate<FeeConstraint>
27where
28    FeeConstraint: amount::Constraint + Clone,
29{
30    /// The hex-encoded serialized data for this transaction.
31    #[serde(with = "hex")]
32    pub data: SerializedTransaction,
33
34    /// The transaction ID of this transaction.
35    #[serde(with = "hex")]
36    pub(crate) hash: transaction::Hash,
37
38    /// The authorizing data digest of a v5 transaction, or a placeholder for older versions.
39    #[serde(rename = "authdigest")]
40    #[serde(with = "hex")]
41    pub(crate) auth_digest: transaction::AuthDigest,
42
43    /// The transactions in this block template that this transaction depends upon.
44    /// These are 1-based indexes in the `transactions` list.
45    ///
46    /// Zebra's mempool does not support transaction dependencies, so this list is always empty.
47    ///
48    /// We use `u16` because 2 MB blocks are limited to around 39,000 transactions.
49    pub(crate) depends: Vec<u16>,
50
51    /// The fee for this transaction.
52    ///
53    /// Non-coinbase transactions must be `NonNegative`.
54    /// The Coinbase transaction `fee` is the negative sum of the fees of the transactions in
55    /// the block, so their fee must be `NegativeOrZero`.
56    pub(crate) fee: Amount<FeeConstraint>,
57
58    /// The number of transparent signature operations in this transaction.
59    pub(crate) sigops: u64,
60
61    /// Is this transaction required in the block?
62    ///
63    /// Coinbase transactions are required, all other transactions are not.
64    pub(crate) required: bool,
65}
66
67// Convert from a mempool transaction to a non-coinbase transaction template.
68impl From<&VerifiedUnminedTx> for TransactionTemplate<NonNegative> {
69    fn from(tx: &VerifiedUnminedTx) -> Self {
70        assert!(
71            !tx.transaction.transaction.is_coinbase(),
72            "unexpected coinbase transaction in mempool"
73        );
74
75        Self {
76            data: tx.transaction.transaction.as_ref().into(),
77            hash: tx.transaction.id.mined_id(),
78            auth_digest: tx
79                .transaction
80                .id
81                .auth_digest()
82                .unwrap_or(AUTH_DIGEST_PLACEHOLDER),
83
84            // Always empty, not supported by Zebra's mempool.
85            depends: Vec::new(),
86
87            fee: tx.miner_fee,
88
89            sigops: tx.legacy_sigop_count,
90
91            // Zebra does not require any transactions except the coinbase transaction.
92            required: false,
93        }
94    }
95}
96
97impl From<VerifiedUnminedTx> for TransactionTemplate<NonNegative> {
98    fn from(tx: VerifiedUnminedTx) -> Self {
99        Self::from(&tx)
100    }
101}
102
103impl TransactionTemplate<NegativeOrZero> {
104    /// Convert from a generated coinbase transaction into a coinbase transaction template.
105    ///
106    /// `miner_fee` is the total miner fees for the block, excluding newly created block rewards.
107    //
108    // TODO: use a different type for generated coinbase transactions?
109    pub fn from_coinbase(tx: &UnminedTx, miner_fee: Amount<NonNegative>) -> Self {
110        assert!(
111            tx.transaction.is_coinbase(),
112            "invalid generated coinbase transaction: \
113             must have exactly one input, which must be a coinbase input",
114        );
115
116        let miner_fee = (-miner_fee)
117            .constrain()
118            .expect("negating a NonNegative amount always results in a valid NegativeOrZero");
119
120        let legacy_sigop_count = CachedFfiTransaction::new(tx.transaction.clone(), Vec::new())
121            .legacy_sigop_count()
122            .expect(
123                "invalid generated coinbase transaction: \
124                 failure in zcash_script sigop count",
125            );
126
127        Self {
128            data: tx.transaction.as_ref().into(),
129            hash: tx.id.mined_id(),
130            auth_digest: tx.id.auth_digest().unwrap_or(AUTH_DIGEST_PLACEHOLDER),
131
132            // Always empty, coinbase transactions never have inputs.
133            depends: Vec::new(),
134
135            fee: miner_fee,
136
137            sigops: legacy_sigop_count,
138
139            // Zcash requires a coinbase transaction.
140            required: true,
141        }
142    }
143}
144
145/// A Transaction object as returned by `getrawtransaction` and `getblock` RPC
146/// requests.
147#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
148pub struct TransactionObject {
149    /// The raw transaction, encoded as hex bytes.
150    #[serde(with = "hex")]
151    pub hex: SerializedTransaction,
152    /// The height of the block in the best chain that contains the tx or `None` if the tx is in
153    /// the mempool.
154    #[serde(skip_serializing_if = "Option::is_none")]
155    pub height: Option<u32>,
156    /// The height diff between the block containing the tx and the best chain tip + 1 or `None`
157    /// if the tx is in the mempool.
158    #[serde(skip_serializing_if = "Option::is_none")]
159    pub confirmations: Option<u32>,
160
161    /// Transparent inputs of the transaction.
162    #[serde(rename = "vin", skip_serializing_if = "Option::is_none")]
163    pub inputs: Option<Vec<Input>>,
164
165    /// Transparent outputs of the transaction.
166    #[serde(rename = "vout", skip_serializing_if = "Option::is_none")]
167    pub outputs: Option<Vec<Output>>,
168
169    /// Sapling spends of the transaction.
170    #[serde(rename = "vShieldedSpend", skip_serializing_if = "Option::is_none")]
171    pub shielded_spends: Option<Vec<ShieldedSpend>>,
172
173    /// Sapling outputs of the transaction.
174    #[serde(rename = "vShieldedOutput", skip_serializing_if = "Option::is_none")]
175    pub shielded_outputs: Option<Vec<ShieldedOutput>>,
176
177    /// Orchard actions of the transaction.
178    #[serde(rename = "orchard", skip_serializing_if = "Option::is_none")]
179    pub orchard: Option<Orchard>,
180
181    /// The net value of Sapling Spends minus Outputs in ZEC
182    #[serde(rename = "valueBalance", skip_serializing_if = "Option::is_none")]
183    pub value_balance: Option<f64>,
184
185    /// The net value of Sapling Spends minus Outputs in zatoshis
186    #[serde(rename = "valueBalanceZat", skip_serializing_if = "Option::is_none")]
187    pub value_balance_zat: Option<i64>,
188
189    /// The size of the transaction in bytes.
190    #[serde(skip_serializing_if = "Option::is_none")]
191    pub size: Option<i64>,
192
193    /// The time the transaction was included in a block.
194    #[serde(skip_serializing_if = "Option::is_none")]
195    pub time: Option<i64>,
196    // TODO: some fields not yet supported
197}
198
199/// The transparent input of a transaction.
200#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
201#[serde(untagged)]
202pub enum Input {
203    /// A coinbase input.
204    Coinbase {
205        /// The coinbase scriptSig as hex.
206        #[serde(with = "hex")]
207        coinbase: Vec<u8>,
208        /// The script sequence number.
209        sequence: u32,
210    },
211    /// A non-coinbase input.
212    NonCoinbase {
213        /// The transaction id.
214        txid: String,
215        /// The vout index.
216        vout: u32,
217        /// The script.
218        #[serde(rename = "scriptSig")]
219        script_sig: ScriptSig,
220        /// The script sequence number.
221        sequence: u32,
222        /// The value of the output being spent in ZEC.
223        #[serde(skip_serializing_if = "Option::is_none")]
224        value: Option<f64>,
225        /// The value of the output being spent, in zats, named to match zcashd.
226        #[serde(rename = "valueSat", skip_serializing_if = "Option::is_none")]
227        value_zat: Option<i64>,
228        /// The address of the output being spent.
229        #[serde(skip_serializing_if = "Option::is_none")]
230        address: Option<String>,
231    },
232}
233
234/// The transparent output of a transaction.
235#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
236pub struct Output {
237    /// The value in ZEC.
238    value: f64,
239    /// The value in zats.
240    #[serde(rename = "valueZat")]
241    value_zat: i64,
242    /// index.
243    n: u32,
244    /// The scriptPubKey.
245    #[serde(rename = "scriptPubKey")]
246    script_pub_key: ScriptPubKey,
247}
248
249/// The scriptPubKey of a transaction output.
250#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
251pub struct ScriptPubKey {
252    /// the asm.
253    // #9330: The `asm` field is not currently populated.
254    asm: String,
255    /// the hex.
256    #[serde(with = "hex")]
257    hex: Script,
258    /// The required sigs.
259    #[serde(rename = "reqSigs")]
260    #[serde(default)]
261    #[serde(skip_serializing_if = "Option::is_none")]
262    req_sigs: Option<u32>,
263    /// The type, eg 'pubkeyhash'.
264    // #9330: The `type` field is not currently populated.
265    r#type: String,
266    /// The addresses.
267    #[serde(default)]
268    #[serde(skip_serializing_if = "Option::is_none")]
269    addresses: Option<Vec<String>>,
270}
271
272/// The scriptSig of a transaction input.
273#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
274pub struct ScriptSig {
275    /// The asm.
276    // #9330: The `asm` field is not currently populated.
277    asm: String,
278    /// The hex.
279    hex: Script,
280}
281
282/// A Sapling spend of a transaction.
283#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
284pub struct ShieldedSpend {
285    /// Value commitment to the input note.
286    #[serde(with = "hex")]
287    cv: NotSmallOrderValueCommitment,
288    /// Merkle root of the Sapling note commitment tree.
289    #[serde(with = "hex")]
290    anchor: [u8; 32],
291    /// The nullifier of the input note.
292    #[serde(with = "hex")]
293    nullifier: [u8; 32],
294    /// The randomized public key for spendAuthSig.
295    #[serde(with = "hex")]
296    rk: [u8; 32],
297    /// A zero-knowledge proof using the Sapling Spend circuit.
298    #[serde(with = "hex")]
299    proof: [u8; 192],
300    /// A signature authorizing this Spend.
301    #[serde(rename = "spendAuthSig", with = "hex")]
302    spend_auth_sig: [u8; 64],
303}
304
305/// A Sapling output of a transaction.
306#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
307pub struct ShieldedOutput {
308    /// Value commitment to the input note.
309    #[serde(with = "hex")]
310    cv: NotSmallOrderValueCommitment,
311    /// The u-coordinate of the note commitment for the output note.
312    #[serde(rename = "cmu", with = "hex")]
313    cm_u: [u8; 32],
314    /// A Jubjub public key.
315    #[serde(rename = "ephemeralKey", with = "hex")]
316    ephemeral_key: [u8; 32],
317    /// The output note encrypted to the recipient.
318    #[serde(rename = "encCiphertext", with = "arrayhex")]
319    enc_ciphertext: [u8; 580],
320    /// A ciphertext enabling the sender to recover the output note.
321    #[serde(rename = "outCiphertext", with = "hex")]
322    out_ciphertext: [u8; 80],
323    /// A zero-knowledge proof using the Sapling Output circuit.
324    #[serde(with = "hex")]
325    proof: [u8; 192],
326}
327
328/// Object with Orchard-specific information.
329#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
330pub struct Orchard {
331    /// Array of Orchard actions.
332    actions: Vec<OrchardAction>,
333    /// The net value of Orchard Actions in ZEC.
334    #[serde(rename = "valueBalance")]
335    value_balance: f64,
336    /// The net value of Orchard Actions in zatoshis.
337    #[serde(rename = "valueBalanceZat")]
338    value_balance_zat: i64,
339}
340
341/// The Orchard action of a transaction.
342#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
343pub struct OrchardAction {
344    /// A value commitment to the net value of the input note minus the output note.
345    #[serde(with = "hex")]
346    cv: [u8; 32],
347    /// The nullifier of the input note.
348    #[serde(with = "hex")]
349    nullifier: [u8; 32],
350    /// The randomized validating key for spendAuthSig.
351    #[serde(with = "hex")]
352    rk: [u8; 32],
353    /// The x-coordinate of the note commitment for the output note.
354    #[serde(rename = "cmx", with = "hex")]
355    cm_x: [u8; 32],
356    /// An encoding of an ephemeral Pallas public key.
357    #[serde(rename = "ephemeralKey", with = "hex")]
358    ephemeral_key: [u8; 32],
359    /// The output note encrypted to the recipient.
360    #[serde(rename = "encCiphertext", with = "arrayhex")]
361    enc_ciphertext: [u8; 580],
362    /// A ciphertext enabling the sender to recover the output note.
363    #[serde(rename = "spendAuthSig", with = "hex")]
364    spend_auth_sig: [u8; 64],
365    /// A signature authorizing the spend in this Action.
366    #[serde(rename = "outCiphertext", with = "hex")]
367    out_ciphertext: [u8; 80],
368}
369
370impl Default for TransactionObject {
371    fn default() -> Self {
372        Self {
373            hex: SerializedTransaction::from(
374                [0u8; zebra_chain::transaction::MIN_TRANSPARENT_TX_SIZE as usize].to_vec(),
375            ),
376            height: Option::default(),
377            confirmations: Option::default(),
378            inputs: None,
379            outputs: None,
380            shielded_spends: None,
381            shielded_outputs: None,
382            orchard: None,
383            value_balance: None,
384            value_balance_zat: None,
385            size: None,
386            time: None,
387        }
388    }
389}
390
391impl TransactionObject {
392    /// Converts `tx` and `height` into a new `GetRawTransaction` in the `verbose` format.
393    #[allow(clippy::unwrap_in_result)]
394    pub fn from_transaction(
395        tx: Arc<Transaction>,
396        height: Option<block::Height>,
397        confirmations: Option<u32>,
398        network: &Network,
399        block_time: Option<DateTime<Utc>>,
400    ) -> Self {
401        Self {
402            hex: tx.clone().into(),
403            height: height.map(|height| height.0),
404            confirmations,
405            inputs: Some(
406                tx.inputs()
407                    .iter()
408                    .map(|input| match input {
409                        zebra_chain::transparent::Input::Coinbase { sequence, .. } => {
410                            Input::Coinbase {
411                                coinbase: input
412                                    .coinbase_script()
413                                    .expect("we know it is a valid coinbase script"),
414                                sequence: *sequence,
415                            }
416                        }
417                        zebra_chain::transparent::Input::PrevOut {
418                            sequence,
419                            unlock_script,
420                            outpoint,
421                        } => Input::NonCoinbase {
422                            txid: outpoint.hash.encode_hex(),
423                            vout: outpoint.index,
424                            script_sig: ScriptSig {
425                                asm: "".to_string(),
426                                hex: unlock_script.clone(),
427                            },
428                            sequence: *sequence,
429                            value: None,
430                            value_zat: None,
431                            address: None,
432                        },
433                    })
434                    .collect(),
435            ),
436            outputs: Some(
437                tx.outputs()
438                    .iter()
439                    .enumerate()
440                    .map(|output| {
441                        // Parse the scriptPubKey to find destination addresses.
442                        let (addresses, req_sigs) = match output.1.address(network) {
443                            // TODO: For multisig scripts, this should populate `addresses`
444                            // with the pubkey IDs and `req_sigs` with the number of
445                            // signatures required to spend.
446
447                            // For other standard destinations, `addresses` is populated
448                            // with a single value and `req_sigs` is set to 1.
449                            Some(address) => Some((vec![address.to_string()], 1)),
450                            // For null-data or nonstandard outputs, both are omitted.
451                            None => None,
452                        }
453                        .unzip();
454
455                        Output {
456                            value: Zec::from(output.1.value).lossy_zec(),
457                            value_zat: output.1.value.zatoshis(),
458                            n: output.0 as u32,
459                            script_pub_key: ScriptPubKey {
460                                // TODO: Fill this out.
461                                asm: "".to_string(),
462                                hex: output.1.lock_script.clone(),
463                                req_sigs,
464                                // TODO: Fill this out.
465                                r#type: "".to_string(),
466                                addresses,
467                            },
468                        }
469                    })
470                    .collect(),
471            ),
472            shielded_spends: Some(
473                tx.sapling_spends_per_anchor()
474                    .map(|spend| {
475                        let mut anchor = spend.per_spend_anchor.as_bytes();
476                        anchor.reverse();
477
478                        let mut nullifier = spend.nullifier.as_bytes();
479                        nullifier.reverse();
480
481                        let mut rk: [u8; 32] = spend.clone().rk.into();
482                        rk.reverse();
483
484                        let spend_auth_sig: [u8; 64] = spend.spend_auth_sig.into();
485
486                        ShieldedSpend {
487                            cv: spend.cv,
488                            anchor,
489                            nullifier,
490                            rk,
491                            proof: spend.proof().0,
492                            spend_auth_sig,
493                        }
494                    })
495                    .collect(),
496            ),
497            shielded_outputs: Some(
498                tx.sapling_outputs()
499                    .map(|output| {
500                        let mut ephemeral_key: [u8; 32] = output.ephemeral_key.into();
501                        ephemeral_key.reverse();
502                        let enc_ciphertext: [u8; 580] = output.enc_ciphertext.into();
503                        let out_ciphertext: [u8; 80] = output.out_ciphertext.into();
504
505                        ShieldedOutput {
506                            cv: output.cv,
507                            cm_u: output.cm_u.to_bytes(),
508                            ephemeral_key,
509                            enc_ciphertext,
510                            out_ciphertext,
511                            proof: output.proof().0,
512                        }
513                    })
514                    .collect(),
515            ),
516            value_balance: Some(Zec::from(tx.sapling_value_balance().sapling_amount()).lossy_zec()),
517            value_balance_zat: Some(tx.sapling_value_balance().sapling_amount().zatoshis()),
518
519            orchard: if !tx.has_orchard_shielded_data() {
520                None
521            } else {
522                Some(Orchard {
523                    actions: tx
524                        .orchard_actions()
525                        .collect::<Vec<_>>()
526                        .iter()
527                        .map(|action| {
528                            let spend_auth_sig: [u8; 64] = tx
529                                .orchard_shielded_data()
530                                .and_then(|shielded_data| {
531                                    shielded_data
532                                        .actions
533                                        .iter()
534                                        .find(|authorized_action| {
535                                            authorized_action.action == **action
536                                        })
537                                        .map(|authorized_action| {
538                                            authorized_action.spend_auth_sig.into()
539                                        })
540                                })
541                                .unwrap_or([0; 64]);
542
543                            let cv: [u8; 32] = action.cv.into();
544                            let nullifier: [u8; 32] = action.nullifier.into();
545                            let rk: [u8; 32] = action.rk.into();
546                            let cm_x: [u8; 32] = action.cm_x.into();
547                            let ephemeral_key: [u8; 32] = action.ephemeral_key.into();
548                            let enc_ciphertext: [u8; 580] = action.enc_ciphertext.into();
549                            let out_ciphertext: [u8; 80] = action.out_ciphertext.into();
550
551                            OrchardAction {
552                                cv,
553                                nullifier,
554                                rk,
555                                cm_x,
556                                ephemeral_key,
557                                enc_ciphertext,
558                                spend_auth_sig,
559                                out_ciphertext,
560                            }
561                        })
562                        .collect(),
563                    value_balance: Zec::from(tx.orchard_value_balance().orchard_amount())
564                        .lossy_zec(),
565                    value_balance_zat: tx.orchard_value_balance().orchard_amount().zatoshis(),
566                })
567            },
568            size: tx.as_bytes().len().try_into().ok(),
569            time: block_time.map(|bt| bt.timestamp()),
570        }
571    }
572}