zebra_chain/transparent/
utxo.rs

1//! Unspent transparent output data structures and functions.
2
3use std::collections::HashMap;
4
5use crate::{
6    block::{self, Block, Height},
7    transaction::{self, Transaction},
8    transparent,
9};
10
11/// An unspent `transparent::Output`, with accompanying metadata.
12#[derive(Clone, Debug, PartialEq, Eq, Hash)]
13#[cfg_attr(
14    any(test, feature = "proptest-impl"),
15    derive(proptest_derive::Arbitrary, serde::Serialize)
16)]
17pub struct Utxo {
18    /// The output itself.
19    pub output: transparent::Output,
20
21    // TODO: replace the height and from_coinbase fields with OutputLocation,
22    //       and provide lookup/calculation methods for height and from_coinbase
23    //
24    /// The height at which the output was created.
25    pub height: block::Height,
26    /// Whether the output originated in a coinbase transaction.
27    pub from_coinbase: bool,
28}
29
30/// A [`Utxo`], and the index of its transaction within its block.
31///
32/// This extra index is used to check that spends come after outputs,
33/// when a new output and its spend are both in the same block.
34///
35/// The extra index is only used during block verification,
36/// so it does not need to be sent to the state.
37#[derive(Clone, Debug, PartialEq, Eq)]
38#[cfg_attr(
39    any(test, feature = "proptest-impl"),
40    derive(proptest_derive::Arbitrary)
41)]
42//
43// TODO: after modifying UTXO to contain an OutputLocation, replace this type with UTXO
44pub struct OrderedUtxo {
45    /// An unspent transaction output.
46    pub utxo: Utxo,
47    /// The index of the transaction that created the output, in the block at `height`.
48    ///
49    /// Used to make sure that transaction can only spend outputs
50    /// that were created earlier in the chain.
51    ///
52    /// Note: this is different from `OutPoint.index`,
53    /// which is the index of the output in its transaction.
54    pub tx_index_in_block: usize,
55}
56
57impl AsRef<Utxo> for OrderedUtxo {
58    fn as_ref(&self) -> &Utxo {
59        &self.utxo
60    }
61}
62
63impl Utxo {
64    /// Create a new UTXO from its fields.
65    pub fn new(output: transparent::Output, height: block::Height, from_coinbase: bool) -> Utxo {
66        Utxo {
67            output,
68            height,
69            from_coinbase,
70        }
71    }
72
73    /// Create a new UTXO from an output and its transaction location.
74    pub fn from_location(
75        output: transparent::Output,
76        height: block::Height,
77        tx_index_in_block: usize,
78    ) -> Utxo {
79        // Coinbase transactions are always the first transaction in their block,
80        // we check the other consensus rules separately.
81        let from_coinbase = tx_index_in_block == 0;
82
83        Utxo {
84            output,
85            height,
86            from_coinbase,
87        }
88    }
89}
90
91impl OrderedUtxo {
92    /// Create a new ordered UTXO from its fields.
93    pub fn new(
94        output: transparent::Output,
95        height: block::Height,
96        tx_index_in_block: usize,
97    ) -> OrderedUtxo {
98        // Coinbase transactions are always the first transaction in their block,
99        // we check the other consensus rules separately.
100        let from_coinbase = tx_index_in_block == 0;
101
102        OrderedUtxo {
103            utxo: Utxo::new(output, height, from_coinbase),
104            tx_index_in_block,
105        }
106    }
107
108    /// Create a new ordered UTXO from a UTXO and transaction index.
109    pub fn from_utxo(utxo: Utxo, tx_index_in_block: usize) -> OrderedUtxo {
110        OrderedUtxo {
111            utxo,
112            tx_index_in_block,
113        }
114    }
115}
116
117/// A restriction that must be checked before spending a transparent output of a
118/// coinbase transaction.
119///
120/// See the function `transparent_coinbase_spend` in `zebra-state` for the
121/// consensus rules.
122#[derive(Copy, Clone, Debug, PartialEq, Eq)]
123#[cfg_attr(
124    any(test, feature = "proptest-impl"),
125    derive(proptest_derive::Arbitrary)
126)]
127pub enum CoinbaseSpendRestriction {
128    /// The UTXO is spent in a transaction with one or more transparent outputs
129    /// on a network where coinbase outputs must not be spent by transactions
130    /// with transparent outputs.
131    DisallowCoinbaseSpend,
132
133    /// The UTXO is spent in a transaction which only has shielded outputs, or
134    /// transactions spending coinbase outputs may have transparent outputs on
135    /// this network.
136    CheckCoinbaseMaturity {
137        /// The height at which the UTXO is spent
138        spend_height: block::Height,
139    },
140}
141
142/// Compute an index of [`Utxo`]s, given an index of [`OrderedUtxo`]s.
143pub fn utxos_from_ordered_utxos(
144    ordered_utxos: HashMap<transparent::OutPoint, OrderedUtxo>,
145) -> HashMap<transparent::OutPoint, Utxo> {
146    ordered_utxos
147        .into_iter()
148        .map(|(outpoint, ordered_utxo)| (outpoint, ordered_utxo.utxo))
149        .collect()
150}
151
152/// Compute an index of [`transparent::Output`]s, given an index of [`Utxo`]s.
153pub fn outputs_from_utxos(
154    utxos: HashMap<transparent::OutPoint, Utxo>,
155) -> HashMap<transparent::OutPoint, transparent::Output> {
156    utxos
157        .into_iter()
158        .map(|(outpoint, utxo)| (outpoint, utxo.output))
159        .collect()
160}
161
162/// Compute an index of newly created [`Utxo`]s, given a block and a
163/// list of precomputed transaction hashes.
164pub fn new_outputs(
165    block: &Block,
166    transaction_hashes: &[transaction::Hash],
167) -> HashMap<transparent::OutPoint, Utxo> {
168    utxos_from_ordered_utxos(new_ordered_outputs(block, transaction_hashes))
169}
170
171/// Compute an index of newly created [`Utxo`]s, given a block and a
172/// list of precomputed transaction hashes.
173///
174/// This is a test-only function, prefer [`new_outputs`].
175#[cfg(any(test, feature = "proptest-impl"))]
176pub fn new_outputs_with_height(
177    block: &Block,
178    height: Height,
179    transaction_hashes: &[transaction::Hash],
180) -> HashMap<transparent::OutPoint, Utxo> {
181    utxos_from_ordered_utxos(new_ordered_outputs_with_height(
182        block,
183        height,
184        transaction_hashes,
185    ))
186}
187
188/// Compute an index of newly created [`OrderedUtxo`]s, given a block and a
189/// list of precomputed transaction hashes.
190pub fn new_ordered_outputs(
191    block: &Block,
192    transaction_hashes: &[transaction::Hash],
193) -> HashMap<transparent::OutPoint, OrderedUtxo> {
194    let height = block.coinbase_height().expect("block has coinbase height");
195
196    new_ordered_outputs_with_height(block, height, transaction_hashes)
197}
198
199/// Compute an index of newly created [`OrderedUtxo`]s, given a block and a
200/// list of precomputed transaction hashes.
201///
202/// This function is intended for use in this module, and in tests.
203/// Prefer [`new_ordered_outputs`].
204pub fn new_ordered_outputs_with_height(
205    block: &Block,
206    height: Height,
207    transaction_hashes: &[transaction::Hash],
208) -> HashMap<transparent::OutPoint, OrderedUtxo> {
209    let mut new_ordered_outputs = HashMap::new();
210
211    for (tx_index_in_block, (transaction, hash)) in block
212        .transactions
213        .iter()
214        .zip(transaction_hashes.iter().cloned())
215        .enumerate()
216    {
217        new_ordered_outputs.extend(new_transaction_ordered_outputs(
218            transaction,
219            hash,
220            tx_index_in_block,
221            height,
222        ));
223    }
224
225    new_ordered_outputs
226}
227
228/// Compute an index of newly created [`OrderedUtxo`]s, given a transaction,
229/// its precomputed transaction hash, the transaction's index in its block,
230/// and the block's height.
231///
232/// This function is only for use in this module, and in tests.
233pub fn new_transaction_ordered_outputs(
234    transaction: &Transaction,
235    hash: transaction::Hash,
236    tx_index_in_block: usize,
237    height: block::Height,
238) -> HashMap<transparent::OutPoint, OrderedUtxo> {
239    let mut new_ordered_outputs = HashMap::new();
240
241    for (output_index_in_transaction, output) in transaction.outputs().iter().cloned().enumerate() {
242        let output_index_in_transaction = output_index_in_transaction
243            .try_into()
244            .expect("unexpectedly large number of outputs");
245
246        new_ordered_outputs.insert(
247            transparent::OutPoint {
248                hash,
249                index: output_index_in_transaction,
250            },
251            OrderedUtxo::new(output, height, tx_index_in_block),
252        );
253    }
254
255    new_ordered_outputs
256}