Skip to content

Commit

Permalink
Add tests for testnet minimum difficulty blocks
Browse files Browse the repository at this point in the history
  • Loading branch information
teor2345 committed Nov 12, 2020
1 parent 7b8e76c commit e5a6eaf
Showing 1 changed file with 136 additions and 14 deletions.
150 changes: 136 additions & 14 deletions zebra-chain/src/work/difficulty/tests/vectors.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use color_eyre::eyre::eyre;
use color_eyre::eyre::Report;
use std::sync::Arc;

use crate::block::Block;
use crate::serialization::ZcashDeserialize;
use crate::{block::Block, parameters::NetworkUpgrade};

use super::super::*;

Expand Down Expand Up @@ -248,16 +248,21 @@ fn compact_bitcoin_test_vectors() {

/// Test blocks using CompactDifficulty.
#[test]
#[spandoc::spandoc]
fn block_difficulty() -> Result<(), Report> {
block_difficulty_for_network(Network::Mainnet)?;
block_difficulty_for_network(Network::Testnet)?;

Ok(())
}

#[spandoc::spandoc]
fn block_difficulty_for_network(network: Network) -> Result<(), Report> {
zebra_test::init();

let mut blockchain = Vec::new();
for b in zebra_test::vectors::BLOCKS.iter() {
let block = Arc::<Block>::zcash_deserialize(*b)?;
let hash = block.hash();
blockchain.push((block.clone(), block.coinbase_height().unwrap(), hash));
}
let block_iter = match network {
Network::Mainnet => zebra_test::vectors::MAINNET_BLOCKS.iter(),
Network::Testnet => zebra_test::vectors::TESTNET_BLOCKS.iter(),
};

let diff_zero = ExpandedDifficulty(U256::zero());
let diff_one = ExpandedDifficulty(U256::one());
Expand All @@ -268,15 +273,20 @@ fn block_difficulty() -> Result<(), Report> {

let mut cumulative_work = PartialCumulativeWork::default();
let mut previous_cumulative_work = PartialCumulativeWork::default();
for (block, height, hash) in blockchain {
/// SPANDOC: Calculate the threshold for block {?height}

for (&height, block) in block_iter {
let block =
Block::zcash_deserialize(&block[..]).expect("block test vector should deserialize");
let hash = block.hash();

/// SPANDOC: Calculate the threshold for block {?height, ?network}
let threshold = block
.header
.difficulty_threshold
.to_expanded()
.expect("Chain blocks have valid difficulty thresholds.");

/// SPANDOC: Check the difficulty for block {?height, ?threshold, ?hash}
/// SPANDOC: Check the difficulty for block {?height, ?network, ?threshold, ?hash}
{
assert!(hash <= threshold);
// also check the comparison operators work
Expand All @@ -285,15 +295,23 @@ fn block_difficulty() -> Result<(), Report> {
assert!(hash < diff_max);
}

/// SPANDOC: Check compact round-trip for block {?height}
/// SPANDOC: Check the PoWLimit for block {?height, ?network, ?threshold, ?hash}
{
// the consensus rule
assert!(threshold <= ExpandedDifficulty::target_difficulty_limit(network));
// check that ordering is transitive, we checked `hash <= threshold` above
assert!(hash <= ExpandedDifficulty::target_difficulty_limit(network));
}

/// SPANDOC: Check compact round-trip for block {?height, ?network}
{
let canonical_compact = threshold.to_compact();

assert_eq!(block.header.difficulty_threshold,
canonical_compact);
}

/// SPANDOC: Check the work for block {?height}
/// SPANDOC: Check the work for block {?height, ?network}
{
let work = block
.header
Expand Down Expand Up @@ -356,6 +374,110 @@ fn genesis_block_difficulty_for_network(network: Network) -> Result<(), Report>
Ok(())
}

/// Test that testnet minimum-difficulty blocks are valid
#[test]
#[spandoc::spandoc]
fn testnet_minimum_difficulty() -> Result<(), Report> {
const MINIMUM_DIFFICULTY_HEIGHTS: &[block::Height] = &[
// block time gaps greater than 15 minutes (pre-Blossom)
block::Height(299_188),
block::Height(299_189),
block::Height(299_202),
// block time gaps greater than 7.5 minutes (Blossom and later)
block::Height(584_000),
// these 3 blocks have gaps greater than 7.5 minutes and less than 15 minutes
block::Height(903_800),
block::Height(903_801),
block::Height(1_028_500),
];

for (&height, _block) in zebra_test::vectors::TESTNET_BLOCKS.iter() {
let height = block::Height(height);

/// SPANDOC: Do minimum difficulty checks for testnet block {?height}
if MINIMUM_DIFFICULTY_HEIGHTS.contains(&height) {
check_testnet_minimum_difficulty_block(height)?;
} else {
assert!(check_testnet_minimum_difficulty_block(height).is_err(),
"all testnet minimum difficulty block test vectors must be tested by the unit tests. Hint: add the failing block to MINIMUM_DIFFICULTY_HEIGHTS");
}
}

Ok(())
}

/// Check that the testnet block at `height` is a testnet minimum difficulty
/// block.
#[spandoc::spandoc]
fn check_testnet_minimum_difficulty_block(height: block::Height) -> Result<(), Report> {
let block = zebra_test::vectors::TESTNET_BLOCKS
.get(&height.0)
.expect("test vectors contain the specified minimum difficulty block height");
let block = Block::zcash_deserialize(&block[..]).expect("block test vector should deserialize");
let hash = block.hash();

/// SPANDOC: Check the testnet minimum difficulty start height {?height, ?hash}
if height < block::Height(299_188) {
Err(eyre!(
"the testnet minimum difficulty rule starts at block 299188"
))?;
}

/// SPANDOC: Make sure testnet minimum difficulty blocks have large time gaps {?height, ?hash}
{
let previous_block = zebra_test::vectors::TESTNET_BLOCKS.get(&(height.0 - 1));
if previous_block.is_none() {
Err(eyre!(
"test vectors should contain the previous block for each minimum difficulty block"
))?;
}

let previous_block = previous_block.unwrap();
let previous_block = Block::zcash_deserialize(&previous_block[..])
.expect("block test vector should deserialize");
let time_gap = block
.header
.time
.signed_duration_since(previous_block.header.time);

// zcashd requires a gap that's strictly greater than 6 times the target
// threshold, but ZIP-205 and ZIP-208 are ambiguous. See bug #1276.
match NetworkUpgrade::minimum_difficulty_spacing_for_height(Network::Testnet, height) {
None => Err(eyre!("the minimum difficulty rule is not active"))?,
Some(spacing) if (time_gap <= spacing) => Err(eyre!(
"minimum difficulty block times must be more than 6 target spacing intervals apart"
))?,
_ => {}
};
}

// At this point, the current block has passed all the consensus rules that allow
// minimum-difficulty blocks. So it is *allowed* to be a minimum-difficulty block, but not
// *required* to be one. But at the moment, all test vectors with large gaps are minimum-difficulty
// blocks.

/// SPANDOC: Calculate the threshold for testnet block {?height, ?hash}
let threshold = block
.header
.difficulty_threshold
.to_expanded()
.expect("Chain blocks have valid difficulty thresholds.");

/// SPANDOC: Check that the testnet minimum difficulty is the PoWLimit {?height, ?threshold, ?hash}
{
assert_eq!(threshold, ExpandedDifficulty::target_difficulty_limit(Network::Testnet),
"testnet minimum difficulty thresholds should be equal to the PoWLimit. Hint: Blocks with large gaps are allowed to have the minimum difficulty, but it's not required.");
// all blocks pass the minimum difficulty threshold, even if they aren't minimum
// difficulty blocks, because it's the lowest permitted difficulty
assert!(
hash <= ExpandedDifficulty::target_difficulty_limit(Network::Testnet),
"testnet minimum difficulty hashes must be less than the PoWLimit"
);
}

Ok(())
}

/// Test ExpandedDifficulty ordering
#[test]
#[spandoc::spandoc]
Expand Down

0 comments on commit e5a6eaf

Please sign in to comment.