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}