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            // The Mainnet and default Testnet disbursement lists are hardcoded and must be
265            // non-empty. Custom testnets and Regtest may configure no disbursements, in which
266            // case the NU6.1 activation block is not required to contain any disbursement
267            // outputs.
268            let must_have_disbursements =
269                matches!(net, Network::Mainnet) || net.is_default_testnet();
270            if lockbox_disbursements.is_empty() && must_have_disbursements {
271                Err(BlockError::Other(
272                    "missing lockbox disbursements for NU6.1 activation block".to_string(),
273                ))?;
274            }
275
276            deferred_pool_balance_change = lockbox_disbursements.into_iter().try_fold(
277                deferred_pool_balance_change,
278                |balance, (addr, expected_amount)| {
279                    if !has_amount(&addr, expected_amount) {
280                        Err(SubsidyError::OneTimeLockboxDisbursementNotFound)?;
281                    }
282
283                    balance
284                        .checked_sub(expected_amount)
285                        .ok_or(SubsidyError::Underflow)
286                },
287            )?;
288        };
289
290        // Check each funding stream output.
291        funding_streams.into_iter().try_for_each(
292            |(receiver, expected_amount)| -> Result<(), BlockError> {
293                let addr =
294                    funding_stream_address(height, net, receiver).ok_or(BlockError::Other(
295                        "A funding stream other than the deferred pool must have an address"
296                            .to_string(),
297                    ))?;
298
299                if !has_amount(addr, expected_amount) {
300                    Err(SubsidyError::FundingStreamNotFound)?;
301                }
302
303                Ok(())
304            },
305        )?;
306
307        Ok(DeferredPoolBalanceChange::new(deferred_pool_balance_change))
308    }
309}
310
311/// Returns `Ok(())` if the miner fees consensus rule is valid.
312///
313/// [7.1.2]: https://zips.z.cash/protocol/protocol.pdf#txnconsensus
314pub fn miner_fees_are_valid(
315    coinbase_tx: &Transaction,
316    height: Height,
317    block_miner_fees: Amount<NonNegative>,
318    expected_block_subsidy: Amount<NonNegative>,
319    expected_deferred_pool_balance_change: DeferredPoolBalanceChange,
320    network: &Network,
321) -> Result<(), BlockError> {
322    let transparent_value_balance = coinbase_tx
323        .outputs()
324        .iter()
325        .map(|output| output.value())
326        .sum::<Result<Amount<NonNegative>, AmountError>>()
327        .map_err(|_| SubsidyError::Overflow)?
328        .constrain()
329        .map_err(|e| BlockError::Other(format!("invalid transparent value balance: {e}")))?;
330    let sapling_value_balance = coinbase_tx.sapling_value_balance().sapling_amount();
331    let orchard_value_balance = coinbase_tx.orchard_value_balance().orchard_amount();
332    // [NU6.3 onward] The Ironwood pool is shielded too, so its value balance affects the coinbase
333    // output value exactly like Sapling and Orchard. This is zero for pre-v6 coinbase transactions
334    // (no Ironwood bundle), so it is a no-op before NU6.3.
335    let ironwood_value_balance = coinbase_tx.ironwood_value_balance().ironwood_amount();
336
337    // # Consensus
338    //
339    // > - define the total output value of its coinbase transaction to be the total value in zatoshi of its transparent
340    // >   outputs, minus vbalanceSapling, minus vbalanceOrchard, minus vbalanceIronwood, plus totalDeferredOutput(height);
341    // > – define the total input value of its coinbase transaction to be the value in zatoshi of the block subsidy,
342    // >   plus the transaction fees paid by transactions in the block.
343    //
344    // https://zips.z.cash/protocol/protocol.pdf#txnconsensus
345    //
346    // The expected lockbox funding stream output of the coinbase transaction is also subtracted
347    // from the block subsidy value plus the transaction fees paid by transactions in this block.
348    let total_output_value = (transparent_value_balance
349        - sapling_value_balance
350        - orchard_value_balance
351        - ironwood_value_balance
352        + expected_deferred_pool_balance_change.value())
353    .map_err(|_| SubsidyError::Overflow)?;
354
355    let total_input_value =
356        (expected_block_subsidy + block_miner_fees).map_err(|_| SubsidyError::Overflow)?;
357
358    // # Consensus
359    //
360    // > [Pre-NU6] The total output of a coinbase transaction MUST NOT be greater than its total
361    // input.
362    //
363    // > [NU6 onward] The total output of a coinbase transaction MUST be equal to its total input.
364    if if NetworkUpgrade::current(network, height) < NetworkUpgrade::Nu6 {
365        total_output_value > total_input_value
366    } else {
367        total_output_value != total_input_value
368    } {
369        Err(SubsidyError::InvalidMinerFees)?
370    };
371
372    Ok(())
373}
374
375/// Returns `Ok(())` if `header.time` is less than or equal to
376/// 2 hours in the future, according to the node's local clock (`now`).
377///
378/// This is a non-deterministic rule, as clocks vary over time, and
379/// between different nodes.
380///
381/// "In addition, a full validator MUST NOT accept blocks with nTime
382/// more than two hours in the future according to its clock. This
383/// is not strictly a consensus rule because it is nondeterministic,
384/// and clock time varies between nodes. Also note that a block that
385/// is rejected by this rule at a given point in time may later be
386/// accepted." [§7.5][7.5]
387///
388/// [7.5]: https://zips.z.cash/protocol/protocol.pdf#blockheader
389///
390/// If the header time is invalid, returns an error containing `height` and `hash`.
391pub fn time_is_valid_at(
392    header: &Header,
393    now: DateTime<Utc>,
394    height: &Height,
395    hash: &Hash,
396) -> Result<(), zebra_chain::block::BlockTimeError> {
397    header.time_is_valid_at(now, height, hash)
398}
399
400/// Check Merkle root validity.
401///
402/// `transaction_hashes` is a precomputed list of transaction hashes.
403///
404/// # Consensus rules:
405///
406/// - A SHA-256d hash in internal byte order. The merkle root is derived from the
407///   hashes of all transactions included in this block, ensuring that none of
408///   those transactions can be modified without modifying the header. [7.6]
409///
410/// # Panics
411///
412/// - If block does not have a coinbase transaction.
413///
414/// [ZIP-244]: https://zips.z.cash/zip-0244
415/// [7.1]: https://zips.z.cash/protocol/nu5.pdf#txnencodingandconsensus
416/// [7.6]: https://zips.z.cash/protocol/nu5.pdf#blockheader
417pub fn merkle_root_validity(
418    network: &Network,
419    block: &Block,
420    transaction_hashes: &[transaction::Hash],
421) -> Result<(), BlockError> {
422    // TODO: deduplicate zebra-chain and zebra-consensus errors (#2908)
423    block
424        .check_transaction_network_upgrade_consistency(network)
425        .map_err(|_| BlockError::WrongTransactionConsensusBranchId)?;
426
427    let merkle_root = transaction_hashes.iter().cloned().collect();
428
429    if block.header.merkle_root != merkle_root {
430        return Err(BlockError::BadMerkleRoot {
431            actual: merkle_root,
432            expected: block.header.merkle_root,
433        });
434    }
435
436    // Bitcoin's transaction Merkle trees are malleable, allowing blocks with
437    // duplicate transactions to have the same Merkle root as blocks without
438    // duplicate transactions.
439    //
440    // Collecting into a HashSet deduplicates, so this checks that there are no
441    // duplicate transaction hashes, preventing Merkle root malleability.
442    //
443    // ## Full Block Validation
444    //
445    // Duplicate transactions should cause a block to be
446    // rejected, as duplicate transactions imply that the block contains a
447    // double-spend. As a defense-in-depth, however, we also check that there
448    // are no duplicate transaction hashes.
449    //
450    // ## Checkpoint Validation
451    //
452    // To prevent malleability (CVE-2012-2459), we also need to check
453    // whether the transaction hashes are unique.
454    if transaction_hashes.len() != transaction_hashes.iter().collect::<HashSet<_>>().len() {
455        return Err(BlockError::DuplicateTransaction);
456    }
457
458    Ok(())
459}