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