use std::{collections::HashSet, sync::Arc};
use chrono::{DateTime, Utc};
use zebra_chain::{
amount::{Amount, DeferredPoolBalanceChange, NonNegative},
block::{Block, Hash, Header, Height},
parameters::{
subsidy::{self, CoinbaseTransactionError},
Network, NetworkUpgrade,
},
transaction::{self, Transaction},
work::{
difficulty::{ExpandedDifficulty, ParameterDifficulty as _},
equihash,
},
};
use crate::{block::ShieldedActionCounts, error::*};
pub fn coinbase_is_first(block: &Block) -> Result<Arc<transaction::Transaction>, BlockError> {
let first = block
.transactions
.first()
.ok_or(BlockError::NoTransactions)?;
let mut rest = block.transactions.iter().skip(1);
if !first.is_coinbase() {
Err(TransactionError::CoinbasePosition)?;
}
if !rest.all(|tx| tx.is_valid_non_coinbase()) {
Err(TransactionError::CoinbaseAfterFirst)?;
}
Ok(first.clone())
}
pub fn difficulty_threshold_is_valid(
header: &Header,
network: &Network,
height: &Height,
hash: &Hash,
) -> Result<ExpandedDifficulty, BlockError> {
let difficulty_threshold = header
.difficulty_threshold
.to_expanded()
.ok_or(BlockError::InvalidDifficulty(*height, *hash))?;
if difficulty_threshold > network.target_difficulty_limit() {
Err(BlockError::TargetDifficultyLimit(
*height,
*hash,
difficulty_threshold,
network.clone(),
network.target_difficulty_limit(),
))?;
}
Ok(difficulty_threshold)
}
pub fn difficulty_is_valid(
header: &Header,
network: &Network,
height: &Height,
hash: &Hash,
) -> Result<(), BlockError> {
let difficulty_threshold = difficulty_threshold_is_valid(header, network, height, hash)?;
if hash > &difficulty_threshold {
Err(BlockError::DifficultyFilter(
*height,
*hash,
difficulty_threshold,
network.clone(),
))?;
}
Ok(())
}
pub fn equihash_solution_is_valid(header: &Header) -> Result<(), equihash::Error> {
header.solution.check(header)
}
pub fn subsidy_is_valid(
block: &Block,
net: &Network,
expected_block_subsidy: Amount<NonNegative>,
) -> Result<DeferredPoolBalanceChange, BlockError> {
subsidy::subsidy_is_valid(block, net, expected_block_subsidy).map_err(subsidy_block_error)
}
pub fn miner_fees_are_valid(
coinbase_tx: &Transaction,
height: Height,
block_miner_fees: Amount<NonNegative>,
expected_block_subsidy: Amount<NonNegative>,
expected_deferred_pool_balance_change: DeferredPoolBalanceChange,
network: &Network,
) -> Result<(), BlockError> {
subsidy::miner_fees_are_valid(
coinbase_tx,
height,
block_miner_fees,
expected_block_subsidy,
expected_deferred_pool_balance_change,
network,
)
.map_err(subsidy_block_error)
}
fn subsidy_block_error(error: CoinbaseTransactionError) -> BlockError {
match error {
CoinbaseTransactionError::Subsidy(error) => error.into(),
error => BlockError::Other(error.to_string()),
}
}
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)
}
pub fn merkle_root_validity(
network: &Network,
block: &Block,
transaction_hashes: &[transaction::Hash],
) -> Result<(), BlockError> {
block
.check_transaction_network_upgrade_consistency(network)
.map_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,
});
}
if transaction_hashes.len() != transaction_hashes.iter().collect::<HashSet<_>>().len() {
return Err(BlockError::DuplicateTransaction);
}
Ok(())
}
pub fn shielded_action_limits_are_valid(
block: &Block,
network: &Network,
height: Height,
hash: Hash,
) -> Result<(), BlockError> {
if NetworkUpgrade::current(network, height) < NetworkUpgrade::Nu7 {
return Ok(());
}
let counts = block
.transactions
.iter()
.map(|transaction| ShieldedActionCounts::from_transaction(transaction))
.fold(ShieldedActionCounts::default(), |acc, counts| {
acc.saturating_add(counts)
});
if let Some((pool, count, limit)) = counts.exceeded_limit() {
Err(BlockError::TooManyShieldedActions {
height,
hash,
pool,
count,
limit,
})?;
}
Ok(())
}