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}