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}