Zebra/zebra-state/src/tests.rs

121 lines
4.0 KiB
Rust

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<Block>;
fn set_work(self, work: u128) -> Arc<Block>;
}
impl FakeChainHelper for Arc<Block> {
fn make_fake_child(&self) -> Arc<Block> {
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<Block> {
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));
}
}