Skip to main content

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::{group::ff::PrimeField, pallas};
6
7use crate::{
8    amount::{DeferredPoolBalanceChange, NegativeAllowed},
9    block::merkle::AuthDataRoot,
10    fmt::DisplayToDebug,
11    ironwood, orchard,
12    parameters::{Network, NetworkUpgrade},
13    sapling,
14    serialization::TrustedPreallocate,
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| {
85                let inputs = tx.inputs();
86                inputs.into_iter().next()
87            })
88            .and_then(|input| match input {
89                transparent::Input::Coinbase { height, .. } => Some(height),
90                _ => None,
91            })
92    }
93
94    /// Compute the hash of this block.
95    pub fn hash(&self) -> Hash {
96        Hash::from(self)
97    }
98
99    /// Get the parsed block [`Commitment`] for this block.
100    ///
101    /// The interpretation of the commitment depends on the
102    /// configured `network`, and this block's height.
103    ///
104    /// Returns an error if this block does not have a block height,
105    /// or if the commitment value is structurally invalid.
106    pub fn commitment(&self, network: &Network) -> Result<Commitment, CommitmentError> {
107        match self.coinbase_height() {
108            None => Err(CommitmentError::MissingBlockHeight {
109                block_hash: self.hash(),
110            }),
111            Some(height) => Commitment::from_bytes(*self.header.commitment_bytes, network, height),
112        }
113    }
114
115    /// Check if the `network_upgrade` fields from each transaction in the block matches
116    /// the network upgrade calculated from the `network` and block height.
117    ///
118    /// # Consensus
119    ///
120    /// > [NU5 onward] The nConsensusBranchId field MUST match the consensus branch ID used
121    /// > for SIGHASH transaction hashes, as specified in [ZIP-244].
122    ///
123    /// <https://zips.z.cash/protocol/protocol.pdf#txnconsensus>
124    ///
125    /// [ZIP-244]: https://zips.z.cash/zip-0244
126    #[allow(clippy::unwrap_in_result)]
127    pub fn check_transaction_network_upgrade_consistency(
128        &self,
129        network: &Network,
130    ) -> Result<(), error::BlockError> {
131        let block_nu =
132            NetworkUpgrade::current(network, self.coinbase_height().expect("a valid height"));
133
134        if self
135            .transactions
136            .iter()
137            .filter_map(|trans| trans.as_ref().network_upgrade())
138            .any(|trans_nu| trans_nu != block_nu)
139        {
140            return Err(error::BlockError::WrongTransactionConsensusBranchId);
141        }
142
143        Ok(())
144    }
145
146    /// Access the sprout nullifiers from all transactions in this block.
147    pub fn sprout_nullifiers(&self) -> impl Iterator<Item = sprout::Nullifier> + '_ {
148        self.transactions
149            .iter()
150            .flat_map(|transaction| transaction.sprout_nullifiers().collect::<Vec<_>>())
151    }
152
153    /// Access the sapling nullifiers from all transactions in this block.
154    pub fn sapling_nullifiers(&self) -> impl Iterator<Item = sapling::Nullifier> + '_ {
155        self.transactions
156            .iter()
157            .flat_map(|transaction| transaction.sapling_nullifiers().collect::<Vec<_>>())
158    }
159
160    /// Access the orchard nullifiers from all transactions in this block.
161    pub fn orchard_nullifiers(&self) -> impl Iterator<Item = orchard::Nullifier> + '_ {
162        self.transactions
163            .iter()
164            .flat_map(|transaction| transaction.orchard_nullifiers().collect::<Vec<_>>())
165    }
166
167    /// Access the ironwood nullifiers from all transactions in this block.
168    pub fn ironwood_nullifiers(&self) -> impl Iterator<Item = ironwood::Nullifier> + '_ {
169        self.transactions
170            .iter()
171            .flat_map(|transaction| transaction.ironwood_nullifiers().collect::<Vec<_>>())
172    }
173
174    /// Access the sprout note commitments from all transactions in this block.
175    pub fn sprout_note_commitments(
176        &self,
177    ) -> impl Iterator<Item = sprout::commitment::NoteCommitment> + '_ {
178        self.transactions
179            .iter()
180            .flat_map(|transaction| transaction.sprout_note_commitments().collect::<Vec<_>>())
181    }
182
183    /// Access the sapling note commitments from all transactions in this block.
184    pub fn sapling_note_commitments(
185        &self,
186    ) -> impl Iterator<Item = sapling_crypto::note::ExtractedNoteCommitment> + '_ {
187        self.transactions
188            .iter()
189            .flat_map(|transaction| transaction.sapling_note_commitments().collect::<Vec<_>>())
190    }
191
192    /// Access the orchard note commitments from all transactions in this block,
193    /// as `pallas::Base` values for the note commitment tree.
194    pub fn orchard_note_commitments(&self) -> impl Iterator<Item = pallas::Base> + '_ {
195        self.transactions.iter().flat_map(|transaction| {
196            transaction
197                .orchard_note_commitments()
198                .map(|cmx| {
199                    let bytes = cmx.to_bytes();
200                    pallas::Base::from_repr(bytes)
201                        .expect("orchard note commitment is a valid pallas::Base")
202                })
203                .collect::<Vec<_>>()
204        })
205    }
206
207    /// Access the ironwood note commitments from all transactions in this block,
208    /// as `pallas::Base` values for the note commitment tree.
209    pub fn ironwood_note_commitments(&self) -> impl Iterator<Item = pallas::Base> + '_ {
210        self.transactions.iter().flat_map(|transaction| {
211            transaction
212                .ironwood_note_commitments()
213                .map(|cmx| {
214                    let bytes = cmx.to_bytes();
215                    pallas::Base::from_repr(bytes)
216                        .expect("ironwood note commitment is a valid pallas::Base")
217                })
218                .collect::<Vec<_>>()
219        })
220    }
221
222    /// Count how many Sapling transactions exist in a block,
223    /// i.e. transactions "where either of vSpendsSapling or vOutputsSapling is non-empty"
224    /// <https://zips.z.cash/zip-0221#tree-node-specification>.
225    pub fn sapling_transactions_count(&self) -> u64 {
226        self.transactions
227            .iter()
228            .filter(|tx| tx.has_sapling_shielded_data())
229            .count()
230            .try_into()
231            .expect("number of transactions must fit u64")
232    }
233
234    /// Count how many Orchard transactions exist in a block,
235    /// i.e. transactions "where vActionsOrchard is non-empty."
236    /// <https://zips.z.cash/zip-0221#tree-node-specification>.
237    pub fn orchard_transactions_count(&self) -> u64 {
238        self.transactions
239            .iter()
240            .filter(|tx| tx.has_orchard_shielded_data())
241            .count()
242            .try_into()
243            .expect("number of transactions must fit u64")
244    }
245
246    /// Count how many Ironwood transactions exist in a block,
247    /// i.e. transactions where the Ironwood bundle is non-empty (NU6.3 onward).
248    pub fn ironwood_transactions_count(&self) -> u64 {
249        self.transactions
250            .iter()
251            .filter(|tx| tx.has_ironwood_shielded_data())
252            .count()
253            .try_into()
254            .expect("number of transactions must fit u64")
255    }
256
257    /// Returns the overall chain value pool change in this block---the negative sum of the
258    /// transaction value balances in this block.
259    ///
260    /// These are the changes in the transparent, Sprout, Sapling, Orchard, and
261    /// Deferred chain value pools, as a result of this block.
262    ///
263    /// Positive values are added to the corresponding chain value pool and negative values are
264    /// removed from the corresponding pool.
265    ///
266    /// <https://zebra.zfnd.org/dev/rfcs/0012-value-pools.html#definitions>
267    ///
268    /// The given `utxos` must contain the [`transparent::Utxo`]s of every input in this block,
269    /// including UTXOs created by earlier transactions in this block. It can also contain unrelated
270    /// UTXOs, which are ignored.
271    ///
272    /// Note that the chain value pool has the opposite sign to the transaction value pool.
273    pub fn chain_value_pool_change(
274        &self,
275        utxos: &HashMap<transparent::OutPoint, transparent::Utxo>,
276        deferred_pool_balance_change: DeferredPoolBalanceChange,
277    ) -> Result<ValueBalance<NegativeAllowed>, ValueBalanceError> {
278        // `Result<T, E>` implements `IntoIterator`, so a `flat_map(|t| t.value_balance(utxos))`
279        // would silently drop transactions whose value balance returns `Err`. Use `try_fold`
280        // to propagate the first error instead.
281        let tx_pool_sum = self
282            .transactions
283            .iter()
284            .try_fold(ValueBalance::<NegativeAllowed>::zero(), |acc, tx| {
285                acc + tx.value_balance(utxos)?
286            })?;
287
288        Ok(*tx_pool_sum
289            .neg()
290            .set_deferred_amount(deferred_pool_balance_change.value()))
291    }
292
293    /// Compute the root of the authorizing data Merkle tree,
294    /// as defined in [ZIP-244].
295    ///
296    /// [ZIP-244]: https://zips.z.cash/zip-0244
297    pub fn auth_data_root(&self) -> AuthDataRoot {
298        self.transactions.iter().collect::<AuthDataRoot>()
299    }
300}
301
302impl<'a> From<&'a Block> for Hash {
303    fn from(block: &'a Block) -> Hash {
304        block.header.as_ref().into()
305    }
306}
307
308/// The maximum number of `block::Hash` entries Zebra will preallocate for in
309/// a single peer-deserialized vector.
310///
311/// In the P2P protocol, `Vec<block::Hash>` appears as the `known_blocks` block
312/// locator in `getblocks` and `getheaders` messages. The Bitcoin/Zcash
313/// convention encodes locators with exponentially-spaced heights (1, 2, 3, …,
314/// 10, 20, 40, …, genesis), giving `~log2(N) + 10` entries for chain length N.
315/// For current Zcash chain heights (~3M blocks) a legitimate locator has ~32
316/// entries.
317///
318/// We cap at 101 to match Bitcoin Core's `MAX_LOCATOR_SZ` constant
319/// (`net_processing.cpp`), which zcashd inherits. This avoids any risk of
320/// rejecting legitimate locators sent by compatible nodes that follow the
321/// existing Bitcoin/Zcash protocol convention.
322///
323/// Without this cap, `Hash::max_allocation` was previously derived from
324/// `MAX_PROTOCOL_MESSAGE_LEN / 32 = 65,535`, which allowed a remote peer to
325/// force ~2 MiB heap preallocation per crafted `getblocks`/`getheaders` message
326/// before any payload was read. This is the same class as
327/// GHSA-xr93-pcq3-pxf8 (`addr_limit`), fixed for AddrV1/V2 in PR #10494.
328pub const MAX_BLOCK_LOCATOR_LENGTH: u64 = 101;
329
330impl TrustedPreallocate for Hash {
331    fn max_allocation() -> u64 {
332        MAX_BLOCK_LOCATOR_LENGTH
333    }
334}