zebra_chain/
block.rs

1//! Blocks and block-related structures (heights, headers, etc.)
2
3use std::{collections::HashMap, fmt, ops::Neg, sync::Arc};
4
5use halo2::pasta::pallas;
6
7use crate::{
8    amount::{Amount, NegativeAllowed, NonNegative},
9    block::merkle::AuthDataRoot,
10    fmt::DisplayToDebug,
11    orchard,
12    parameters::{Network, NetworkUpgrade},
13    sapling,
14    serialization::{TrustedPreallocate, MAX_PROTOCOL_MESSAGE_LEN},
15    sprout,
16    transaction::Transaction,
17    transparent,
18    value_balance::{ValueBalance, ValueBalanceError},
19};
20
21mod commitment;
22mod error;
23mod hash;
24mod header;
25mod height;
26mod serialize;
27
28pub mod genesis;
29pub mod merkle;
30
31#[cfg(any(test, feature = "proptest-impl"))]
32pub mod arbitrary;
33#[cfg(any(test, feature = "bench", feature = "proptest-impl"))]
34pub mod tests;
35
36pub use commitment::{
37    ChainHistoryBlockTxAuthCommitmentHash, ChainHistoryMmrRootHash, Commitment, CommitmentError,
38};
39pub use hash::Hash;
40pub use header::{BlockTimeError, CountedHeader, Header, ZCASH_BLOCK_VERSION};
41pub use height::{Height, HeightDiff, TryIntoHeight};
42pub use serialize::{SerializedBlock, MAX_BLOCK_BYTES};
43
44#[cfg(any(test, feature = "proptest-impl"))]
45pub use arbitrary::LedgerState;
46
47/// A Zcash block, containing a header and a list of transactions.
48#[derive(Clone, Debug, Eq, PartialEq)]
49#[cfg_attr(
50    any(test, feature = "proptest-impl", feature = "elasticsearch"),
51    derive(Serialize)
52)]
53pub struct Block {
54    /// The block header, containing block metadata.
55    pub header: Arc<Header>,
56    /// The block transactions.
57    pub transactions: Vec<Arc<Transaction>>,
58}
59
60impl fmt::Display for Block {
61    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
62        let mut fmter = f.debug_struct("Block");
63
64        if let Some(height) = self.coinbase_height() {
65            fmter.field("height", &height);
66        }
67        fmter.field("transactions", &self.transactions.len());
68        fmter.field("hash", &DisplayToDebug(self.hash()));
69
70        fmter.finish()
71    }
72}
73
74impl Block {
75    /// Return the block height reported in the coinbase transaction, if any.
76    ///
77    /// Note
78    ///
79    /// Verified blocks have a valid height.
80    pub fn coinbase_height(&self) -> Option<Height> {
81        self.transactions
82            .first()
83            .and_then(|tx| tx.inputs().first())
84            .and_then(|input| match input {
85                transparent::Input::Coinbase { ref height, .. } => Some(*height),
86                _ => None,
87            })
88    }
89
90    /// Compute the hash of this block.
91    pub fn hash(&self) -> Hash {
92        Hash::from(self)
93    }
94
95    /// Get the parsed block [`Commitment`] for this block.
96    ///
97    /// The interpretation of the commitment depends on the
98    /// configured `network`, and this block's height.
99    ///
100    /// Returns an error if this block does not have a block height,
101    /// or if the commitment value is structurally invalid.
102    pub fn commitment(&self, network: &Network) -> Result<Commitment, CommitmentError> {
103        match self.coinbase_height() {
104            None => Err(CommitmentError::MissingBlockHeight {
105                block_hash: self.hash(),
106            }),
107            Some(height) => Commitment::from_bytes(*self.header.commitment_bytes, network, height),
108        }
109    }
110
111    /// Check if the `network_upgrade` fields from each transaction in the block matches
112    /// the network upgrade calculated from the `network` and block height.
113    ///
114    /// # Consensus
115    ///
116    /// > [NU5 onward] The nConsensusBranchId field MUST match the consensus branch ID used
117    /// > for SIGHASH transaction hashes, as specified in [ZIP-244].
118    ///
119    /// <https://zips.z.cash/protocol/protocol.pdf#txnconsensus>
120    ///
121    /// [ZIP-244]: https://zips.z.cash/zip-0244
122    #[allow(clippy::unwrap_in_result)]
123    pub fn check_transaction_network_upgrade_consistency(
124        &self,
125        network: &Network,
126    ) -> Result<(), error::BlockError> {
127        let block_nu =
128            NetworkUpgrade::current(network, self.coinbase_height().expect("a valid height"));
129
130        if self
131            .transactions
132            .iter()
133            .filter_map(|trans| trans.as_ref().network_upgrade())
134            .any(|trans_nu| trans_nu != block_nu)
135        {
136            return Err(error::BlockError::WrongTransactionConsensusBranchId);
137        }
138
139        Ok(())
140    }
141
142    /// Access the [`sprout::Nullifier`]s from all transactions in this block.
143    pub fn sprout_nullifiers(&self) -> impl Iterator<Item = &sprout::Nullifier> {
144        self.transactions
145            .iter()
146            .flat_map(|transaction| transaction.sprout_nullifiers())
147    }
148
149    /// Access the [`sapling::Nullifier`]s from all transactions in this block.
150    pub fn sapling_nullifiers(&self) -> impl Iterator<Item = &sapling::Nullifier> {
151        self.transactions
152            .iter()
153            .flat_map(|transaction| transaction.sapling_nullifiers())
154    }
155
156    /// Access the [`orchard::Nullifier`]s from all transactions in this block.
157    pub fn orchard_nullifiers(&self) -> impl Iterator<Item = &orchard::Nullifier> {
158        self.transactions
159            .iter()
160            .flat_map(|transaction| transaction.orchard_nullifiers())
161    }
162
163    /// Access the [`sprout::NoteCommitment`]s from all transactions in this block.
164    pub fn sprout_note_commitments(&self) -> impl Iterator<Item = &sprout::NoteCommitment> {
165        self.transactions
166            .iter()
167            .flat_map(|transaction| transaction.sprout_note_commitments())
168    }
169
170    /// Access the [sapling note commitments](jubjub::Fq) from all transactions in this block.
171    pub fn sapling_note_commitments(&self) -> impl Iterator<Item = &jubjub::Fq> {
172        self.transactions
173            .iter()
174            .flat_map(|transaction| transaction.sapling_note_commitments())
175    }
176
177    /// Access the [orchard note commitments](pallas::Base) from all transactions in this block.
178    pub fn orchard_note_commitments(&self) -> impl Iterator<Item = &pallas::Base> {
179        self.transactions
180            .iter()
181            .flat_map(|transaction| transaction.orchard_note_commitments())
182    }
183
184    /// Count how many Sapling transactions exist in a block,
185    /// i.e. transactions "where either of vSpendsSapling or vOutputsSapling is non-empty"
186    /// <https://zips.z.cash/zip-0221#tree-node-specification>.
187    pub fn sapling_transactions_count(&self) -> u64 {
188        self.transactions
189            .iter()
190            .filter(|tx| tx.has_sapling_shielded_data())
191            .count()
192            .try_into()
193            .expect("number of transactions must fit u64")
194    }
195
196    /// Count how many Orchard transactions exist in a block,
197    /// i.e. transactions "where vActionsOrchard is non-empty."
198    /// <https://zips.z.cash/zip-0221#tree-node-specification>.
199    pub fn orchard_transactions_count(&self) -> u64 {
200        self.transactions
201            .iter()
202            .filter(|tx| tx.has_orchard_shielded_data())
203            .count()
204            .try_into()
205            .expect("number of transactions must fit u64")
206    }
207
208    /// Returns the overall chain value pool change in this block---the negative sum of the
209    /// transaction value balances in this block.
210    ///
211    /// These are the changes in the transparent, Sprout, Sapling, Orchard, and
212    /// Deferred chain value pools, as a result of this block.
213    ///
214    /// Positive values are added to the corresponding chain value pool and negative values are
215    /// removed from the corresponding pool.
216    ///
217    /// <https://zebra.zfnd.org/dev/rfcs/0012-value-pools.html#definitions>
218    ///
219    /// The given `utxos` must contain the [`transparent::Utxo`]s of every input in this block,
220    /// including UTXOs created by earlier transactions in this block. It can also contain unrelated
221    /// UTXOs, which are ignored.
222    ///
223    /// Note that the chain value pool has the opposite sign to the transaction value pool.
224    pub fn chain_value_pool_change(
225        &self,
226        utxos: &HashMap<transparent::OutPoint, transparent::Utxo>,
227        deferred_balance: Option<Amount<NonNegative>>,
228    ) -> Result<ValueBalance<NegativeAllowed>, ValueBalanceError> {
229        Ok(*self
230            .transactions
231            .iter()
232            .flat_map(|t| t.value_balance(utxos))
233            .sum::<Result<ValueBalance<NegativeAllowed>, _>>()?
234            .neg()
235            .set_deferred_amount(
236                deferred_balance
237                    .unwrap_or(Amount::zero())
238                    .constrain::<NegativeAllowed>()
239                    .map_err(ValueBalanceError::Deferred)?,
240            ))
241    }
242
243    /// Compute the root of the authorizing data Merkle tree,
244    /// as defined in [ZIP-244].
245    ///
246    /// [ZIP-244]: https://zips.z.cash/zip-0244
247    pub fn auth_data_root(&self) -> AuthDataRoot {
248        self.transactions.iter().collect::<AuthDataRoot>()
249    }
250}
251
252impl<'a> From<&'a Block> for Hash {
253    fn from(block: &'a Block) -> Hash {
254        block.header.as_ref().into()
255    }
256}
257
258/// A serialized Block hash takes 32 bytes
259const BLOCK_HASH_SIZE: u64 = 32;
260
261/// The maximum number of hashes in a valid Zcash protocol message.
262impl TrustedPreallocate for Hash {
263    fn max_allocation() -> u64 {
264        // Every vector type requires a length field of at least one byte for de/serialization.
265        // Since a block::Hash takes 32 bytes, we can never receive more than (MAX_PROTOCOL_MESSAGE_LEN - 1) / 32 hashes in a single message
266        ((MAX_PROTOCOL_MESSAGE_LEN - 1) as u64) / BLOCK_HASH_SIZE
267    }
268}