zebra_chain/
transparent.rs

1//! Transparent-related (Bitcoin-inherited) functionality.
2
3use std::{collections::HashMap, fmt, iter};
4
5use crate::{
6    amount::{Amount, NonNegative},
7    block,
8    parameters::Network,
9    primitives::zcash_primitives,
10    transaction,
11};
12
13mod address;
14mod keys;
15mod opcodes;
16mod script;
17mod serialize;
18mod utxo;
19
20pub use address::Address;
21pub use script::Script;
22pub use serialize::{GENESIS_COINBASE_DATA, MAX_COINBASE_DATA_LEN, MAX_COINBASE_HEIGHT_DATA_LEN};
23pub use utxo::{
24    new_ordered_outputs, new_outputs, outputs_from_utxos, utxos_from_ordered_utxos,
25    CoinbaseSpendRestriction, OrderedUtxo, Utxo,
26};
27
28#[cfg(any(test, feature = "proptest-impl"))]
29pub use utxo::{
30    new_ordered_outputs_with_height, new_outputs_with_height, new_transaction_ordered_outputs,
31};
32
33#[cfg(any(test, feature = "proptest-impl"))]
34mod arbitrary;
35
36#[cfg(test)]
37mod tests;
38
39#[cfg(any(test, feature = "proptest-impl"))]
40use proptest_derive::Arbitrary;
41
42/// The maturity threshold for transparent coinbase outputs.
43///
44/// "A transaction MUST NOT spend a transparent output of a coinbase transaction
45/// from a block less than 100 blocks prior to the spend. Note that transparent
46/// outputs of coinbase transactions include Founders' Reward outputs and
47/// transparent Funding Stream outputs."
48/// [7.1](https://zips.z.cash/protocol/nu5.pdf#txnencodingandconsensus)
49//
50// TODO: change type to HeightDiff
51pub const MIN_TRANSPARENT_COINBASE_MATURITY: u32 = 100;
52
53/// Extra coinbase data that identifies some coinbase transactions generated by Zebra.
54/// <https://emojipedia.org/zebra/>
55//
56// # Note
57//
58// rust-analyzer will crash in some editors when moving over an actual Zebra emoji,
59// so we encode it here. This is a known issue in emacs-lsp and other lsp implementations:
60// - https://github.com/rust-lang/rust-analyzer/issues/9121
61// - https://github.com/emacs-lsp/lsp-mode/issues/2080
62// - https://github.com/rust-lang/rust-analyzer/issues/13709
63pub const EXTRA_ZEBRA_COINBASE_DATA: &str = "z\u{1F993}";
64
65/// Arbitrary data inserted by miners into a coinbase transaction.
66//
67// TODO: rename to ExtraCoinbaseData, because height is also part of the coinbase data?
68#[derive(Clone, Eq, PartialEq)]
69#[cfg_attr(
70    any(test, feature = "proptest-impl", feature = "elasticsearch"),
71    derive(Serialize)
72)]
73pub struct CoinbaseData(
74    /// Invariant: this vec, together with the coinbase height, must be less than
75    /// 100 bytes. We enforce this by only constructing CoinbaseData fields by
76    /// parsing blocks with 100-byte data fields, and checking newly created
77    /// CoinbaseData lengths in the transaction builder.
78    pub(super) Vec<u8>,
79);
80
81#[cfg(any(test, feature = "proptest-impl"))]
82impl CoinbaseData {
83    /// Create a new `CoinbaseData` containing `data`.
84    ///
85    /// Only for use in tests.
86    pub fn new(data: Vec<u8>) -> CoinbaseData {
87        CoinbaseData(data)
88    }
89}
90
91impl AsRef<[u8]> for CoinbaseData {
92    fn as_ref(&self) -> &[u8] {
93        self.0.as_ref()
94    }
95}
96
97impl std::fmt::Debug for CoinbaseData {
98    #[allow(clippy::unwrap_in_result)]
99    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
100        let escaped = String::from_utf8(
101            self.0
102                .iter()
103                .cloned()
104                .flat_map(std::ascii::escape_default)
105                .collect(),
106        )
107        .expect("ascii::escape_default produces utf8");
108        f.debug_tuple("CoinbaseData").field(&escaped).finish()
109    }
110}
111
112/// OutPoint
113///
114/// A particular transaction output reference.
115#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
116#[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary))]
117#[cfg_attr(
118    any(test, feature = "proptest-impl", feature = "elasticsearch"),
119    derive(Serialize)
120)]
121pub struct OutPoint {
122    /// References the transaction that contains the UTXO being spent.
123    ///
124    /// # Correctness
125    ///
126    /// Consensus-critical serialization uses
127    /// [`ZcashSerialize`](crate::serialization::ZcashSerialize).
128    /// [`serde`]-based hex serialization must only be used for testing.
129    #[cfg_attr(any(test, feature = "proptest-impl"), serde(with = "hex"))]
130    pub hash: transaction::Hash,
131
132    /// Identifies which UTXO from that transaction is referenced; the
133    /// first output is 0, etc.
134    pub index: u32,
135}
136
137impl OutPoint {
138    /// Returns a new [`OutPoint`] from an in-memory output `index`.
139    ///
140    /// # Panics
141    ///
142    /// If `index` doesn't fit in a [`u32`].
143    pub fn from_usize(hash: transaction::Hash, index: usize) -> OutPoint {
144        OutPoint {
145            hash,
146            index: index
147                .try_into()
148                .expect("valid in-memory output indexes fit in a u32"),
149        }
150    }
151}
152
153/// A transparent input to a transaction.
154#[derive(Clone, Debug, Eq, PartialEq)]
155#[cfg_attr(
156    any(test, feature = "proptest-impl", feature = "elasticsearch"),
157    derive(Serialize)
158)]
159pub enum Input {
160    /// A reference to an output of a previous transaction.
161    PrevOut {
162        /// The previous output transaction reference.
163        outpoint: OutPoint,
164        /// The script that authorizes spending `outpoint`.
165        unlock_script: Script,
166        /// The sequence number for the output.
167        sequence: u32,
168    },
169    /// New coins created by the block reward.
170    Coinbase {
171        /// The height of this block.
172        height: block::Height,
173        /// Free data inserted by miners after the block height.
174        data: CoinbaseData,
175        /// The sequence number for the output.
176        sequence: u32,
177    },
178}
179
180impl fmt::Display for Input {
181    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
182        match self {
183            Input::PrevOut {
184                outpoint,
185                unlock_script,
186                ..
187            } => {
188                let mut fmter = f.debug_struct("transparent::Input::PrevOut");
189
190                fmter.field("unlock_script_len", &unlock_script.as_raw_bytes().len());
191                fmter.field("outpoint", outpoint);
192
193                fmter.finish()
194            }
195            Input::Coinbase { height, data, .. } => {
196                let mut fmter = f.debug_struct("transparent::Input::Coinbase");
197
198                fmter.field("height", height);
199                fmter.field("data_len", &data.0.len());
200
201                fmter.finish()
202            }
203        }
204    }
205}
206
207impl Input {
208    /// Returns a new coinbase input for `height` with optional `data` and `sequence`.
209    ///
210    /// # Consensus
211    ///
212    /// The combined serialized size of `height` and `data` can be at most 100 bytes.
213    ///
214    /// > A coinbase transaction script MUST have length in {2 .. 100} bytes.
215    ///
216    /// <https://zips.z.cash/protocol/protocol.pdf#txnconsensus>
217    ///
218    /// # Panics
219    ///
220    /// If the coinbase data is greater than [`MAX_COINBASE_DATA_LEN`].
221    #[cfg(feature = "getblocktemplate-rpcs")]
222    pub fn new_coinbase(
223        height: block::Height,
224        data: Option<Vec<u8>>,
225        sequence: Option<u32>,
226    ) -> Input {
227        // "No extra coinbase data" is the default.
228        let data = data.unwrap_or_default();
229        let height_size = height.coinbase_zcash_serialized_size();
230
231        assert!(
232            data.len() + height_size <= MAX_COINBASE_DATA_LEN,
233            "invalid coinbase data: extra data {} bytes + height {height_size} bytes \
234             must be {} or less",
235            data.len(),
236            MAX_COINBASE_DATA_LEN,
237        );
238
239        Input::Coinbase {
240            height,
241            data: CoinbaseData(data),
242
243            // If the caller does not specify the sequence number,
244            // use a sequence number that activates the LockTime.
245            sequence: sequence.unwrap_or(0),
246        }
247    }
248
249    /// Returns the extra coinbase data in this input, if it is an [`Input::Coinbase`].
250    pub fn extra_coinbase_data(&self) -> Option<&CoinbaseData> {
251        match self {
252            Input::PrevOut { .. } => None,
253            Input::Coinbase { data, .. } => Some(data),
254        }
255    }
256
257    /// Returns the input's sequence number.
258    pub fn sequence(&self) -> u32 {
259        match self {
260            Input::PrevOut { sequence, .. } | Input::Coinbase { sequence, .. } => *sequence,
261        }
262    }
263
264    /// Sets the input's sequence number.
265    ///
266    /// Only for use in tests.
267    #[cfg(any(test, feature = "proptest-impl"))]
268    pub fn set_sequence(&mut self, new_sequence: u32) {
269        match self {
270            Input::PrevOut { sequence, .. } | Input::Coinbase { sequence, .. } => {
271                *sequence = new_sequence
272            }
273        }
274    }
275
276    /// If this is a [`Input::PrevOut`] input, returns this input's
277    /// [`OutPoint`]. Otherwise, returns `None`.
278    pub fn outpoint(&self) -> Option<OutPoint> {
279        if let Input::PrevOut { outpoint, .. } = self {
280            Some(*outpoint)
281        } else {
282            None
283        }
284    }
285
286    /// Set this input's [`OutPoint`].
287    ///
288    /// Should only be called on [`Input::PrevOut`] inputs.
289    ///
290    /// # Panics
291    ///
292    /// If `self` is a coinbase input.
293    #[cfg(any(test, feature = "proptest-impl"))]
294    pub fn set_outpoint(&mut self, new_outpoint: OutPoint) {
295        if let Input::PrevOut {
296            ref mut outpoint, ..
297        } = self
298        {
299            *outpoint = new_outpoint;
300        } else {
301            unreachable!("unexpected variant: Coinbase Inputs do not have OutPoints");
302        }
303    }
304
305    /// Get the value spent by this input, by looking up its [`OutPoint`] in `outputs`.
306    /// See [`Self::value`] for details.
307    ///
308    /// # Panics
309    ///
310    /// If the provided [`Output`]s don't have this input's [`OutPoint`].
311    pub(crate) fn value_from_outputs(
312        &self,
313        outputs: &HashMap<OutPoint, Output>,
314    ) -> Amount<NonNegative> {
315        match self {
316            Input::PrevOut { outpoint, .. } => {
317                outputs
318                    .get(outpoint)
319                    .unwrap_or_else(|| {
320                        panic!(
321                            "provided Outputs (length {:?}) don't have spent {:?}",
322                            outputs.len(),
323                            outpoint
324                        )
325                    })
326                    .value
327            }
328            Input::Coinbase { .. } => Amount::zero(),
329        }
330    }
331
332    /// Get the value spent by this input, by looking up its [`OutPoint`] in
333    /// [`Utxo`]s.
334    ///
335    /// This amount is added to the transaction value pool by this input.
336    ///
337    /// # Panics
338    ///
339    /// If the provided [`Utxo`]s don't have this input's [`OutPoint`].
340    pub fn value(&self, utxos: &HashMap<OutPoint, utxo::Utxo>) -> Amount<NonNegative> {
341        if let Some(outpoint) = self.outpoint() {
342            // look up the specific Output and convert it to the expected format
343            let output = utxos
344                .get(&outpoint)
345                .expect("provided Utxos don't have spent OutPoint")
346                .output
347                .clone();
348            self.value_from_outputs(&iter::once((outpoint, output)).collect())
349        } else {
350            // coinbase inputs don't need any UTXOs
351            self.value_from_outputs(&HashMap::new())
352        }
353    }
354
355    /// Get the value spent by this input, by looking up its [`OutPoint`] in
356    /// [`OrderedUtxo`]s.
357    ///
358    /// See [`Self::value`] for details.
359    ///
360    /// # Panics
361    ///
362    /// If the provided [`OrderedUtxo`]s don't have this input's [`OutPoint`].
363    pub fn value_from_ordered_utxos(
364        &self,
365        ordered_utxos: &HashMap<OutPoint, utxo::OrderedUtxo>,
366    ) -> Amount<NonNegative> {
367        if let Some(outpoint) = self.outpoint() {
368            // look up the specific Output and convert it to the expected format
369            let output = ordered_utxos
370                .get(&outpoint)
371                .expect("provided Utxos don't have spent OutPoint")
372                .utxo
373                .output
374                .clone();
375            self.value_from_outputs(&iter::once((outpoint, output)).collect())
376        } else {
377            // coinbase inputs don't need any UTXOs
378            self.value_from_outputs(&HashMap::new())
379        }
380    }
381}
382
383/// A transparent output from a transaction.
384///
385/// The most fundamental building block of a transaction is a
386/// transaction output -- the ZEC you own in your "wallet" is in
387/// fact a subset of unspent transaction outputs (or "UTXO"s) of the
388/// global UTXO set.
389///
390/// UTXOs are indivisible, discrete units of value which can only be
391/// consumed in their entirety. Thus, if I want to send you 1 ZEC and
392/// I only own one UTXO worth 2 ZEC, I would construct a transaction
393/// that spends my UTXO and sends 1 ZEC to you and 1 ZEC back to me
394/// (just like receiving change).
395#[derive(Clone, Debug, Eq, PartialEq, Hash)]
396#[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary, Deserialize))]
397#[cfg_attr(
398    any(test, feature = "proptest-impl", feature = "elasticsearch"),
399    derive(Serialize)
400)]
401pub struct Output {
402    /// Transaction value.
403    // At https://en.bitcoin.it/wiki/Protocol_documentation#tx, this is an i64.
404    pub value: Amount<NonNegative>,
405
406    /// The lock script defines the conditions under which this output can be spent.
407    pub lock_script: Script,
408}
409
410impl Output {
411    /// Returns a new coinbase output that pays `amount` using `lock_script`.
412    #[cfg(feature = "getblocktemplate-rpcs")]
413    pub fn new_coinbase(amount: Amount<NonNegative>, lock_script: Script) -> Output {
414        Output {
415            value: amount,
416            lock_script,
417        }
418    }
419
420    /// Get the value contained in this output.
421    /// This amount is subtracted from the transaction value pool by this output.
422    pub fn value(&self) -> Amount<NonNegative> {
423        self.value
424    }
425
426    /// Return the destination address from a transparent output.
427    ///
428    /// Returns None if the address type is not valid or unrecognized.
429    pub fn address(&self, network: &Network) -> Option<Address> {
430        zcash_primitives::transparent_output_address(self, network)
431    }
432}