zebra_consensus/block/
check.rs

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