use std::{convert::TryFrom, mem, sync::Arc}; use zebra_chain::{ block::{self, Block}, transaction::Transaction, transparent, work::difficulty::ExpandedDifficulty, work::difficulty::Work, }; use super::*; /// Helper trait for constructing "valid" looking chains of blocks pub trait FakeChainHelper { fn make_fake_child(&self) -> Arc; fn set_work(self, work: u128) -> Arc; } impl FakeChainHelper for Arc { fn make_fake_child(&self) -> Arc { let parent_hash = self.hash(); let mut child = Block::clone(self); let mut transactions = mem::take(&mut child.transactions); let mut tx = transactions.remove(0); let input = match Arc::make_mut(&mut tx) { Transaction::V1 { inputs, .. } => &mut inputs[0], Transaction::V2 { inputs, .. } => &mut inputs[0], Transaction::V3 { inputs, .. } => &mut inputs[0], Transaction::V4 { inputs, .. } => &mut inputs[0], }; match input { transparent::Input::Coinbase { height, .. } => height.0 += 1, _ => panic!("block must have a coinbase height to create a child"), } child.transactions.push(tx); child.header.previous_block_hash = parent_hash; Arc::new(child) } fn set_work(mut self, work: u128) -> Arc { use primitive_types::U256; let work: U256 = work.into(); // Work is calculated from expanded difficulty with the equation `work = 2^256 / (expanded + 1)` // By balancing the equation we get `expanded = (2^256 / work) - 1` // `2^256` is too large to represent, so we instead use the following equivalent equations // `expanded = (2^256 / work) - (work / work)` // `expanded = (2^256 - work) / work` // `expanded = ((2^256 - 1) - work + 1) / work` // `(2^256 - 1 - work)` is equivalent to `!work` when work is a U256 let expanded = (!work + 1) / work; let expanded = ExpandedDifficulty::from(expanded); let block = Arc::make_mut(&mut self); block.header.difficulty_threshold = expanded.into(); self } } /// Block heights, and the expected minimum block locator height static BLOCK_LOCATOR_CASES: &[(u32, u32)] = &[ (0, 0), (1, 0), (10, 0), (98, 0), (99, 0), (100, 1), (101, 2), (1000, 901), (10000, 9901), ]; /// Check that the block locator heights are sensible. #[test] fn test_block_locator_heights() { zebra_test::init(); for (height, min_height) in BLOCK_LOCATOR_CASES.iter().cloned() { let locator = util::block_locator_heights(block::Height(height)); assert!(!locator.is_empty(), "locators must not be empty"); if (height - min_height) > 1 { assert!( locator.len() > 2, "non-trivial locators must have some intermediate heights" ); } assert_eq!( locator[0], block::Height(height), "locators must start with the tip height" ); // Check that the locator is sorted, and that it has no duplicates // TODO: replace with dedup() and is_sorted_by() when sorting stabilises. assert!(locator.windows(2).all(|v| match v { [a, b] => a.0 > b.0, _ => unreachable!("windows returns exact sized slices"), })); let final_height = locator[locator.len() - 1]; assert_eq!( final_height, block::Height(min_height), "locators must end with the specified final height" ); assert!(height - final_height.0 <= constants::MAX_BLOCK_REORG_HEIGHT, format!("locator for {} must not be more than the maximum reorg height {} below the tip, but {} is {} blocks below the tip", height, constants::MAX_BLOCK_REORG_HEIGHT, final_height.0, height - final_height.0)); } }