247 lines
9.1 KiB
Rust
247 lines
9.1 KiB
Rust
//! Consensus check functions
|
|
|
|
use chrono::{DateTime, Utc};
|
|
|
|
use zebra_chain::{
|
|
block::{Block, Hash, Header, Height},
|
|
parameters::{Network, NetworkUpgrade},
|
|
transaction,
|
|
work::{difficulty::ExpandedDifficulty, equihash},
|
|
};
|
|
|
|
use crate::{error::*, parameters::SLOW_START_INTERVAL};
|
|
|
|
use super::subsidy;
|
|
|
|
/// Returns `Ok(())` if there is exactly one coinbase transaction in `Block`,
|
|
/// and that coinbase transaction is the first transaction in the block.
|
|
///
|
|
/// "The first (and only the first) transaction in a block is a coinbase
|
|
/// transaction, which collects and spends any miner subsidy and transaction
|
|
/// fees paid by transactions included in this block." [§3.10][3.10]
|
|
///
|
|
/// [3.10]: https://zips.z.cash/protocol/protocol.pdf#coinbasetransactions
|
|
pub fn coinbase_is_first(block: &Block) -> Result<(), BlockError> {
|
|
let first = block
|
|
.transactions
|
|
.get(0)
|
|
.ok_or(BlockError::NoTransactions)?;
|
|
let mut rest = block.transactions.iter().skip(1);
|
|
if !first.is_coinbase() {
|
|
return Err(TransactionError::CoinbasePosition)?;
|
|
}
|
|
if rest.any(|tx| tx.contains_coinbase_input()) {
|
|
return Err(TransactionError::CoinbaseAfterFirst)?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Returns `Ok(())` if `hash` passes:
|
|
/// - the target difficulty limit for `network` (PoWLimit), and
|
|
/// - the difficulty filter,
|
|
/// based on the fields in `header`.
|
|
///
|
|
/// If the block is invalid, returns an error containing `height` and `hash`.
|
|
pub fn difficulty_is_valid(
|
|
header: &Header,
|
|
network: Network,
|
|
height: &Height,
|
|
hash: &Hash,
|
|
) -> Result<(), BlockError> {
|
|
let difficulty_threshold = header
|
|
.difficulty_threshold
|
|
.to_expanded()
|
|
.ok_or(BlockError::InvalidDifficulty(*height, *hash))?;
|
|
|
|
// Note: the comparisons in this function are u256 integer comparisons, like
|
|
// zcashd and bitcoin. Greater values represent *less* work.
|
|
|
|
// The PowLimit check is part of `Threshold()` in the spec, but it doesn't
|
|
// actually depend on any previous blocks.
|
|
if difficulty_threshold > ExpandedDifficulty::target_difficulty_limit(network) {
|
|
Err(BlockError::TargetDifficultyLimit(
|
|
*height,
|
|
*hash,
|
|
difficulty_threshold,
|
|
network,
|
|
ExpandedDifficulty::target_difficulty_limit(network),
|
|
))?;
|
|
}
|
|
|
|
// The difficulty filter is also context-free.
|
|
//
|
|
// ZIP 205 and ZIP 208 incorrectly describe testnet minimum difficulty blocks
|
|
// as a change to the difficulty filter. But in `zcashd`, it is implemented
|
|
// as a change to the difficulty adjustment algorithm. So we don't need to
|
|
// do anything special for testnet here.
|
|
// For details, see https://github.com/zcash/zips/issues/416
|
|
if hash > &difficulty_threshold {
|
|
Err(BlockError::DifficultyFilter(
|
|
*height,
|
|
*hash,
|
|
difficulty_threshold,
|
|
network,
|
|
))?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Returns `Ok(())` if the `EquihashSolution` is valid for `header`
|
|
pub fn equihash_solution_is_valid(header: &Header) -> Result<(), equihash::Error> {
|
|
header.solution.check(&header)
|
|
}
|
|
|
|
/// Returns `Ok(())` if the block subsidy and miner fees in `block` are valid for `network`
|
|
///
|
|
/// [3.9]: https://zips.z.cash/protocol/protocol.pdf#subsidyconcepts
|
|
pub fn subsidy_is_valid(block: &Block, network: Network) -> Result<(), BlockError> {
|
|
let height = block.coinbase_height().ok_or(SubsidyError::NoCoinbase)?;
|
|
let coinbase = block.transactions.get(0).ok_or(SubsidyError::NoCoinbase)?;
|
|
|
|
let halving_div = subsidy::general::halving_divisor(height, network);
|
|
let canopy_activation_height = NetworkUpgrade::Canopy
|
|
.activation_height(network)
|
|
.expect("Canopy activation height is known");
|
|
|
|
// TODO: the sum of the coinbase transaction outputs must be less than or equal to the block subsidy plus transaction fees
|
|
|
|
// Check founders reward and funding streams
|
|
if height < SLOW_START_INTERVAL {
|
|
unreachable!(
|
|
"unsupported block height: callers should handle blocks below {:?}",
|
|
SLOW_START_INTERVAL
|
|
)
|
|
} else if halving_div.count_ones() != 1 {
|
|
unreachable!("invalid halving divisor: the halving divisor must be a non-zero power of two")
|
|
} else if height < canopy_activation_height {
|
|
// Founders rewards are paid up to Canopy activation, on both mainnet and testnet
|
|
let founders_reward = subsidy::founders_reward::founders_reward(height, network)
|
|
.expect("invalid Amount: founders reward should be valid");
|
|
let matching_values = subsidy::general::find_output_with_amount(coinbase, founders_reward);
|
|
|
|
// TODO: the exact founders reward value must be sent as a single output to the correct address
|
|
if !matching_values.is_empty() {
|
|
Ok(())
|
|
} else {
|
|
Err(SubsidyError::FoundersRewardNotFound)?
|
|
}
|
|
} else if halving_div < 4 {
|
|
// Funding streams are paid from Canopy activation to the second halving
|
|
// Note: Canopy activation is at the first halving on mainnet, but not on testnet
|
|
// ZIP-1014 only applies to mainnet, ZIP-214 contains the specific rules for testnet
|
|
tracing::trace!("funding stream block subsidy validation is not implemented");
|
|
// Return ok for now
|
|
Ok(())
|
|
} else {
|
|
// Future halving, with no founders reward or funding streams
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
/// Returns `Ok(())` if `header.time` is less than or equal to
|
|
/// 2 hours in the future, according to the node's local clock (`now`).
|
|
///
|
|
/// This is a non-deterministic rule, as clocks vary over time, and
|
|
/// between different nodes.
|
|
///
|
|
/// "In addition, a full validator MUST NOT accept blocks with nTime
|
|
/// more than two hours in the future according to its clock. This
|
|
/// is not strictly a consensus rule because it is nondeterministic,
|
|
/// and clock time varies between nodes. Also note that a block that
|
|
/// is rejected by this rule at a given point in time may later be
|
|
/// accepted." [§7.5][7.5]
|
|
///
|
|
/// [7.5]: https://zips.z.cash/protocol/protocol.pdf#blockheader
|
|
///
|
|
/// If the header time is invalid, returns an error containing `height` and `hash`.
|
|
pub fn time_is_valid_at(
|
|
header: &Header,
|
|
now: DateTime<Utc>,
|
|
height: &Height,
|
|
hash: &Hash,
|
|
) -> Result<(), zebra_chain::block::BlockTimeError> {
|
|
header.time_is_valid_at(now, height, hash)
|
|
}
|
|
|
|
/// Check Merkle root validity.
|
|
///
|
|
/// `transaction_hashes` is a precomputed list of transaction hashes.
|
|
///
|
|
/// # Consensus rules:
|
|
///
|
|
/// - The nConsensusBranchId field MUST match the consensus branch ID used for
|
|
/// SIGHASH transaction hashes, as specifed in [ZIP-244] ([7.1]).
|
|
/// - A SHA-256d hash in internal byte order. The merkle root is derived from the
|
|
/// hashes of all transactions included in this block, ensuring that none of
|
|
/// those transactions can be modified without modifying the header. [7.6]
|
|
///
|
|
/// # Panics
|
|
///
|
|
/// - If block does not have a coinbase transaction.
|
|
///
|
|
/// [ZIP-244]: https://zips.z.cash/zip-0244
|
|
/// [7.1]: https://zips.z.cash/protocol/nu5.pdf#txnencodingandconsensus
|
|
/// [7.6]: https://zips.z.cash/protocol/nu5.pdf#blockheader
|
|
pub fn merkle_root_validity(
|
|
network: Network,
|
|
block: &Block,
|
|
transaction_hashes: &[transaction::Hash],
|
|
) -> Result<(), BlockError> {
|
|
use transaction::Transaction;
|
|
let block_nu =
|
|
NetworkUpgrade::current(network, block.coinbase_height().expect("a valid height"));
|
|
|
|
if block
|
|
.transactions
|
|
.iter()
|
|
.filter_map(|trans| match *trans.as_ref() {
|
|
Transaction::V5 {
|
|
network_upgrade, ..
|
|
} => Some(network_upgrade),
|
|
Transaction::V1 { .. }
|
|
| Transaction::V2 { .. }
|
|
| Transaction::V3 { .. }
|
|
| Transaction::V4 { .. } => None,
|
|
})
|
|
.any(|trans_nu| trans_nu != block_nu)
|
|
{
|
|
return Err(BlockError::WrongTransactionConsensusBranchId);
|
|
}
|
|
|
|
let merkle_root = transaction_hashes.iter().cloned().collect();
|
|
|
|
if block.header.merkle_root != merkle_root {
|
|
return Err(BlockError::BadMerkleRoot {
|
|
actual: merkle_root,
|
|
expected: block.header.merkle_root,
|
|
});
|
|
}
|
|
|
|
// Bitcoin's transaction Merkle trees are malleable, allowing blocks with
|
|
// duplicate transactions to have the same Merkle root as blocks without
|
|
// duplicate transactions.
|
|
//
|
|
// Collecting into a HashSet deduplicates, so this checks that there are no
|
|
// duplicate transaction hashes, preventing Merkle root malleability.
|
|
//
|
|
// ## Full Block Validation
|
|
//
|
|
// Duplicate transactions should cause a block to be
|
|
// rejected, as duplicate transactions imply that the block contains a
|
|
// double-spend. As a defense-in-depth, however, we also check that there
|
|
// are no duplicate transaction hashes.
|
|
//
|
|
// ## Checkpoint Validation
|
|
//
|
|
// To prevent malleability (CVE-2012-2459), we also need to check
|
|
// whether the transaction hashes are unique.
|
|
use std::collections::HashSet;
|
|
if transaction_hashes.len() != transaction_hashes.iter().collect::<HashSet<_>>().len() {
|
|
return Err(BlockError::DuplicateTransaction);
|
|
}
|
|
|
|
Ok(())
|
|
}
|