//! Consensus critical contextual checks use std::borrow::Borrow; use zebra_chain::{ block::{self, Block}, parameters::Network, work::difficulty::CompactDifficulty, }; use crate::{PreparedBlock, ValidateContextError}; use super::check; pub mod difficulty; use difficulty::{AdjustedDifficulty, POW_AVERAGING_WINDOW, POW_MEDIAN_BLOCK_SPAN}; /// Check that `block` is contextually valid for `network`, based on the /// `finalized_tip_height` and `relevant_chain`. /// /// The relevant chain is an iterator over the ancestors of `block`, starting /// with its parent block. /// /// Panics if the finalized state is empty. /// /// Skips the difficulty adjustment check if the state contains less than 28 /// (`POW_AVERAGING_WINDOW + POW_MEDIAN_BLOCK_SPAN`) blocks. #[tracing::instrument( name = "contextual_validation", fields(?network), skip(prepared, network, finalized_tip_height, relevant_chain) )] pub(crate) fn block_is_contextually_valid( prepared: &PreparedBlock, network: Network, finalized_tip_height: Option, relevant_chain: C, ) -> Result<(), ValidateContextError> where C: IntoIterator, C::Item: Borrow, C::IntoIter: ExactSizeIterator, { let finalized_tip_height = finalized_tip_height .expect("finalized state must contain at least one block to do contextual validation"); check::block_is_not_orphaned(finalized_tip_height, prepared.height)?; // Peek at the first block let mut relevant_chain = relevant_chain.into_iter().peekable(); let parent_block = relevant_chain .peek() .expect("state must contain parent block to do contextual validation"); let parent_block = parent_block.borrow(); let parent_height = parent_block .coinbase_height() .expect("valid blocks have a coinbase height"); check::height_one_more_than_parent_height(parent_height, prepared.height)?; // Note: the difficulty check reads the first 28 blocks from the relevant // chain iterator. If you want to use those blocks in other checks, you'll // need to clone them here. if relevant_chain.len() >= POW_AVERAGING_WINDOW + POW_MEDIAN_BLOCK_SPAN { let relevant_data = relevant_chain.map(|block| { ( block.borrow().header.difficulty_threshold, block.borrow().header.time, ) }); // Reads the first 28 blocks from the iterator let expected_difficulty = AdjustedDifficulty::new_from_block(&prepared.block, network, relevant_data); check::difficulty_threshold_is_valid( prepared.block.header.difficulty_threshold, expected_difficulty, )?; } // TODO: other contextual validation design and implementation Ok(()) } /// Returns `ValidateContextError::OrphanedBlock` if the height of the given /// block is less than or equal to the finalized tip height. fn block_is_not_orphaned( finalized_tip_height: block::Height, height: block::Height, ) -> Result<(), ValidateContextError> { if height <= finalized_tip_height { Err(ValidateContextError::OrphanedBlock) } else { Ok(()) } } /// Returns `ValidateContextError::NonSequentialBlock` if the block height isn't /// equal to the parent_height+1. fn height_one_more_than_parent_height( parent_height: block::Height, height: block::Height, ) -> Result<(), ValidateContextError> { if parent_height + 1 != Some(height) { Err(ValidateContextError::NonSequentialBlock) } else { Ok(()) } } /// Validate the `difficulty_threshold` from a candidate block's header, based /// on an `expected_difficulty` for that block. /// /// Uses `expected_difficulty` to calculate the expected `ToCompact(Threshold())` /// value, then compares that value to the `difficulty_threshold`. /// Returns `Ok(())` if the values are equal. fn difficulty_threshold_is_valid( _difficulty_threshold: CompactDifficulty, _expected_difficulty: AdjustedDifficulty, ) -> Result<(), ValidateContextError> { Ok(()) } #[cfg(test)] mod tests { use std::sync::Arc; use zebra_chain::serialization::ZcashDeserializeInto; use super::*; #[test] fn test_orphan_consensus_check() { zebra_test::init(); let height = zebra_test::vectors::BLOCK_MAINNET_347499_BYTES .zcash_deserialize_into::>() .unwrap() .coinbase_height() .unwrap(); block_is_not_orphaned(block::Height(0), height).expect("tip is lower so it should be fine"); block_is_not_orphaned(block::Height(347498), height) .expect("tip is lower so it should be fine"); block_is_not_orphaned(block::Height(347499), height) .expect_err("tip is equal so it should error"); block_is_not_orphaned(block::Height(500000), height) .expect_err("tip is higher so it should error"); } #[test] fn test_sequential_height_check() { zebra_test::init(); let height = zebra_test::vectors::BLOCK_MAINNET_347499_BYTES .zcash_deserialize_into::>() .unwrap() .coinbase_height() .unwrap(); height_one_more_than_parent_height(block::Height(0), height) .expect_err("block is much lower, should panic"); height_one_more_than_parent_height(block::Height(347497), height) .expect_err("parent height is 2 less, should panic"); height_one_more_than_parent_height(block::Height(347498), height) .expect("parent height is 1 less, should be good"); height_one_more_than_parent_height(block::Height(347499), height) .expect_err("parent height is equal, should panic"); height_one_more_than_parent_height(block::Height(347500), height) .expect_err("parent height is way more, should panic"); height_one_more_than_parent_height(block::Height(500000), height) .expect_err("parent height is way more, should panic"); } }