Skip to main content

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