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}