zebra_consensus/block/
check.rs

1//! Consensus check functions
2
3use std::{collections::HashSet, sync::Arc};
4
5use chrono::{DateTime, Utc};
6
7use mset::MultiSet;
8use zebra_chain::{
9    amount::{
10        Amount, DeferredPoolBalanceChange, Error as AmountError, NegativeAllowed, NonNegative,
11    },
12    block::{Block, Hash, Header, Height},
13    parameters::{
14        subsidy::{FundingStreamReceiver, SubsidyError},
15        Network, NetworkUpgrade,
16    },
17    transaction::{self, Transaction},
18    transparent::Output,
19    work::{
20        difficulty::{ExpandedDifficulty, ParameterDifficulty as _},
21        equihash,
22    },
23};
24
25use crate::error::*;
26
27use super::subsidy;
28
29/// Checks if there is exactly one coinbase transaction in `Block`,
30/// and if that coinbase transaction is the first transaction in the block.
31/// Returns the coinbase transaction is successful.
32///
33/// > A transaction that has a single transparent input with a null prevout field,
34/// > is called a coinbase transaction. Every block has a single coinbase
35/// > transaction as the first transaction in the block.
36///
37/// <https://zips.z.cash/protocol/protocol.pdf#coinbasetransactions>
38pub fn coinbase_is_first(block: &Block) -> Result<Arc<transaction::Transaction>, BlockError> {
39    // # Consensus
40    //
41    // > A block MUST have at least one transaction
42    //
43    // <https://zips.z.cash/protocol/protocol.pdf#blockheader>
44    let first = block
45        .transactions
46        .first()
47        .ok_or(BlockError::NoTransactions)?;
48    // > The first transaction in a block MUST be a coinbase transaction,
49    // > and subsequent transactions MUST NOT be coinbase transactions.
50    //
51    // <https://zips.z.cash/protocol/protocol.pdf#blockheader>
52    //
53    // > A transaction that has a single transparent input with a null prevout
54    // > field, is called a coinbase transaction.
55    //
56    // <https://zips.z.cash/protocol/protocol.pdf#coinbasetransactions>
57    let mut rest = block.transactions.iter().skip(1);
58    if !first.is_coinbase() {
59        Err(TransactionError::CoinbasePosition)?;
60    }
61    // > A transparent input in a non-coinbase transaction MUST NOT have a null prevout
62    //
63    // <https://zips.z.cash/protocol/protocol.pdf#txnconsensus>
64    if !rest.all(|tx| tx.is_valid_non_coinbase()) {
65        Err(TransactionError::CoinbaseAfterFirst)?;
66    }
67
68    Ok(first.clone())
69}
70
71/// Returns `Ok(ExpandedDifficulty)` if the`difficulty_threshold` of `header` is at least as difficult as
72/// the target difficulty limit for `network` (PoWLimit)
73///
74/// If the header difficulty threshold is invalid, returns an error containing `height` and `hash`.
75pub fn difficulty_threshold_is_valid(
76    header: &Header,
77    network: &Network,
78    height: &Height,
79    hash: &Hash,
80) -> Result<ExpandedDifficulty, BlockError> {
81    let difficulty_threshold = header
82        .difficulty_threshold
83        .to_expanded()
84        .ok_or(BlockError::InvalidDifficulty(*height, *hash))?;
85
86    // Note: the comparison in this function is a u256 integer comparison, like
87    // zcashd and bitcoin. Greater values represent *less* work.
88
89    // The PowLimit check is part of `Threshold()` in the spec, but it doesn't
90    // actually depend on any previous blocks.
91    if difficulty_threshold > network.target_difficulty_limit() {
92        Err(BlockError::TargetDifficultyLimit(
93            *height,
94            *hash,
95            difficulty_threshold,
96            network.clone(),
97            network.target_difficulty_limit(),
98        ))?;
99    }
100
101    Ok(difficulty_threshold)
102}
103
104/// Returns `Ok(())` if `hash` passes:
105///   - the target difficulty limit for `network` (PoWLimit), and
106///   - the difficulty filter,
107///
108/// based on the fields in `header`.
109///
110/// If the block is invalid, returns an error containing `height` and `hash`.
111pub fn difficulty_is_valid(
112    header: &Header,
113    network: &Network,
114    height: &Height,
115    hash: &Hash,
116) -> Result<(), BlockError> {
117    let difficulty_threshold = difficulty_threshold_is_valid(header, network, height, hash)?;
118
119    // Note: the comparison in this function is a u256 integer comparison, like
120    // zcashd and bitcoin. Greater values represent *less* work.
121
122    // # Consensus
123    //
124    // > The block MUST pass the difficulty filter.
125    //
126    // https://zips.z.cash/protocol/protocol.pdf#blockheader
127    //
128    // The difficulty filter is also context-free.
129    if hash > &difficulty_threshold {
130        Err(BlockError::DifficultyFilter(
131            *height,
132            *hash,
133            difficulty_threshold,
134            network.clone(),
135        ))?;
136    }
137
138    Ok(())
139}
140
141/// Returns `Ok(())` if the `EquihashSolution` is valid for `header`
142pub fn equihash_solution_is_valid(header: &Header) -> Result<(), equihash::Error> {
143    // # Consensus
144    //
145    // > `solution` MUST represent a valid Equihash solution.
146    //
147    // https://zips.z.cash/protocol/protocol.pdf#blockheader
148    header.solution.check(header)
149}
150
151/// Returns `Ok()` with the deferred pool balance change of the coinbase transaction if
152/// the block subsidy in `block` is valid for `network`
153///
154/// [3.9]: https://zips.z.cash/protocol/protocol.pdf#subsidyconcepts
155pub fn subsidy_is_valid(
156    block: &Block,
157    network: &Network,
158    expected_block_subsidy: Amount<NonNegative>,
159) -> Result<DeferredPoolBalanceChange, BlockError> {
160    let height = block.coinbase_height().ok_or(SubsidyError::NoCoinbase)?;
161    let coinbase = block.transactions.first().ok_or(SubsidyError::NoCoinbase)?;
162
163    // Validate funding streams
164    let Some(halving_div) = zebra_chain::parameters::subsidy::halving_divisor(height, network)
165    else {
166        // Far future halving, with no founders reward or funding streams
167        return Ok(DeferredPoolBalanceChange::zero());
168    };
169
170    let canopy_activation_height = NetworkUpgrade::Canopy
171        .activation_height(network)
172        .expect("Canopy activation height is known");
173
174    let slow_start_interval = network.slow_start_interval();
175
176    if height < slow_start_interval {
177        unreachable!(
178            "unsupported block height: callers should handle blocks below {:?}",
179            slow_start_interval
180        )
181    } else if halving_div.count_ones() != 1 {
182        unreachable!("invalid halving divisor: the halving divisor must be a non-zero power of two")
183    } else if height < canopy_activation_height {
184        // Founders rewards are paid up to Canopy activation, on both mainnet and testnet.
185        // But we checkpoint in Canopy so founders reward does not apply for Zebra.
186        unreachable!("we cannot verify consensus rules before Canopy activation");
187    } else if halving_div < 8 {
188        let mut coinbase_outputs: MultiSet<Output> = coinbase.outputs().iter().cloned().collect();
189
190        // Funding streams are paid from Canopy activation to the second halving
191        // Note: Canopy activation is at the first halving on mainnet, but not on testnet
192        // ZIP-1014 only applies to mainnet, ZIP-214 contains the specific rules for testnet
193        // funding stream amount values
194        let mut funding_streams = zebra_chain::parameters::subsidy::funding_stream_values(
195            height,
196            network,
197            expected_block_subsidy,
198        )
199        // we always expect a funding stream hashmap response even if empty
200        .map_err(|err| BlockError::Other(err.to_string()))?;
201
202        let mut has_expected_output = |address, expected_amount| {
203            coinbase_outputs.remove(&Output::new_coinbase(
204                expected_amount,
205                subsidy::new_coinbase_script(address),
206            ))
207        };
208
209        // The deferred pool contribution is checked in `miner_fees_are_valid()`
210        // See [ZIP-1015](https://zips.z.cash/zip-1015) for more details.
211        let mut deferred_pool_balance_change = funding_streams
212            .remove(&FundingStreamReceiver::Deferred)
213            .unwrap_or_default()
214            .constrain::<NegativeAllowed>()
215            .expect("should be valid Amount");
216
217        // Checks the one-time lockbox disbursements in the NU6.1 activation block's coinbase transaction
218        // See [ZIP-271](https://zips.z.cash/zip-0271) and [ZIP-1016](https://zips.z.cash/zip-1016) for more details.
219        let expected_one_time_lockbox_disbursements = network.lockbox_disbursements(height);
220        for (address, expected_amount) in &expected_one_time_lockbox_disbursements {
221            if !has_expected_output(address, *expected_amount) {
222                Err(SubsidyError::OneTimeLockboxDisbursementNotFound)?;
223            }
224
225            deferred_pool_balance_change = deferred_pool_balance_change
226                .checked_sub(*expected_amount)
227                .expect("should be a valid Amount");
228        }
229
230        // # Consensus
231        //
232        // > [Canopy onward] The coinbase transaction at block height `height`
233        // > MUST contain at least one output per funding stream `fs` active at `height`,
234        // > that pays `fs.Value(height)` zatoshi in the prescribed way to the stream's
235        // > recipient address represented by `fs.AddressList[fs.AddressIndex(height)]
236        //
237        // https://zips.z.cash/protocol/protocol.pdf#fundingstreams
238        for (receiver, expected_amount) in funding_streams {
239            let address =
240                subsidy::funding_streams::funding_stream_address(height, network, receiver)
241                    // funding stream receivers other than the deferred pool must have an address
242                    .ok_or_else(|| {
243                        BlockError::Other(format!(
244                            "missing funding stream address at height {height:?}"
245                        ))
246                    })?;
247
248            if !has_expected_output(address, expected_amount) {
249                Err(SubsidyError::FundingStreamNotFound)?;
250            }
251        }
252
253        Ok(DeferredPoolBalanceChange::new(deferred_pool_balance_change))
254    } else {
255        // Future halving, with no founders reward or funding streams
256        Ok(DeferredPoolBalanceChange::zero())
257    }
258}
259
260/// Returns `Ok(())` if the miner fees consensus rule is valid.
261///
262/// [7.1.2]: https://zips.z.cash/protocol/protocol.pdf#txnconsensus
263pub fn miner_fees_are_valid(
264    coinbase_tx: &Transaction,
265    height: Height,
266    block_miner_fees: Amount<NonNegative>,
267    expected_block_subsidy: Amount<NonNegative>,
268    expected_deferred_pool_balance_change: DeferredPoolBalanceChange,
269    network: &Network,
270) -> Result<(), BlockError> {
271    let transparent_value_balance = coinbase_tx
272        .outputs()
273        .iter()
274        .map(|output| output.value())
275        .sum::<Result<Amount<NonNegative>, AmountError>>()
276        .map_err(|_| SubsidyError::SumOverflow)?
277        .constrain()
278        .expect("positive value always fit in `NegativeAllowed`");
279    let sapling_value_balance = coinbase_tx.sapling_value_balance().sapling_amount();
280    let orchard_value_balance = coinbase_tx.orchard_value_balance().orchard_amount();
281
282    // # Consensus
283    //
284    // > - define the total output value of its coinbase transaction to be the total value in zatoshi of its transparent
285    // >   outputs, minus vbalanceSapling, minus vbalanceOrchard, plus totalDeferredOutput(height);
286    // > – define the total input value of its coinbase transaction to be the value in zatoshi of the block subsidy,
287    // >   plus the transaction fees paid by transactions in the block.
288    //
289    // https://zips.z.cash/protocol/protocol.pdf#txnconsensus
290    //
291    // The expected lockbox funding stream output of the coinbase transaction is also subtracted
292    // from the block subsidy value plus the transaction fees paid by transactions in this block.
293    let total_output_value =
294        (transparent_value_balance - sapling_value_balance - orchard_value_balance
295            + expected_deferred_pool_balance_change.value())
296        .map_err(|_| SubsidyError::SumOverflow)?;
297
298    let total_input_value =
299        (expected_block_subsidy + block_miner_fees).map_err(|_| SubsidyError::SumOverflow)?;
300
301    // # Consensus
302    //
303    // > [Pre-NU6] The total output of a coinbase transaction MUST NOT be greater than its total
304    // input.
305    //
306    // > [NU6 onward] The total output of a coinbase transaction MUST be equal to its total input.
307    if if NetworkUpgrade::current(network, height) < NetworkUpgrade::Nu6 {
308        total_output_value > total_input_value
309    } else {
310        total_output_value != total_input_value
311    } {
312        Err(SubsidyError::InvalidMinerFees)?
313    };
314
315    Ok(())
316}
317
318/// Returns `Ok(())` if `header.time` is less than or equal to
319/// 2 hours in the future, according to the node's local clock (`now`).
320///
321/// This is a non-deterministic rule, as clocks vary over time, and
322/// between different nodes.
323///
324/// "In addition, a full validator MUST NOT accept blocks with nTime
325/// more than two hours in the future according to its clock. This
326/// is not strictly a consensus rule because it is nondeterministic,
327/// and clock time varies between nodes. Also note that a block that
328/// is rejected by this rule at a given point in time may later be
329/// accepted." [§7.5][7.5]
330///
331/// [7.5]: https://zips.z.cash/protocol/protocol.pdf#blockheader
332///
333/// If the header time is invalid, returns an error containing `height` and `hash`.
334pub fn time_is_valid_at(
335    header: &Header,
336    now: DateTime<Utc>,
337    height: &Height,
338    hash: &Hash,
339) -> Result<(), zebra_chain::block::BlockTimeError> {
340    header.time_is_valid_at(now, height, hash)
341}
342
343/// Check Merkle root validity.
344///
345/// `transaction_hashes` is a precomputed list of transaction hashes.
346///
347/// # Consensus rules:
348///
349/// - A SHA-256d hash in internal byte order. The merkle root is derived from the
350///   hashes of all transactions included in this block, ensuring that none of
351///   those transactions can be modified without modifying the header. [7.6]
352///
353/// # Panics
354///
355/// - If block does not have a coinbase transaction.
356///
357/// [ZIP-244]: https://zips.z.cash/zip-0244
358/// [7.1]: https://zips.z.cash/protocol/nu5.pdf#txnencodingandconsensus
359/// [7.6]: https://zips.z.cash/protocol/nu5.pdf#blockheader
360pub fn merkle_root_validity(
361    network: &Network,
362    block: &Block,
363    transaction_hashes: &[transaction::Hash],
364) -> Result<(), BlockError> {
365    // TODO: deduplicate zebra-chain and zebra-consensus errors (#2908)
366    block
367        .check_transaction_network_upgrade_consistency(network)
368        .map_err(|_| BlockError::WrongTransactionConsensusBranchId)?;
369
370    let merkle_root = transaction_hashes.iter().cloned().collect();
371
372    if block.header.merkle_root != merkle_root {
373        return Err(BlockError::BadMerkleRoot {
374            actual: merkle_root,
375            expected: block.header.merkle_root,
376        });
377    }
378
379    // Bitcoin's transaction Merkle trees are malleable, allowing blocks with
380    // duplicate transactions to have the same Merkle root as blocks without
381    // duplicate transactions.
382    //
383    // Collecting into a HashSet deduplicates, so this checks that there are no
384    // duplicate transaction hashes, preventing Merkle root malleability.
385    //
386    // ## Full Block Validation
387    //
388    // Duplicate transactions should cause a block to be
389    // rejected, as duplicate transactions imply that the block contains a
390    // double-spend. As a defense-in-depth, however, we also check that there
391    // are no duplicate transaction hashes.
392    //
393    // ## Checkpoint Validation
394    //
395    // To prevent malleability (CVE-2012-2459), we also need to check
396    // whether the transaction hashes are unique.
397    if transaction_hashes.len() != transaction_hashes.iter().collect::<HashSet<_>>().len() {
398        return Err(BlockError::DuplicateTransaction);
399    }
400
401    Ok(())
402}