Add tests for testnet minimum difficulty blocks

This commit is contained in:
teor 2020-11-11 01:15:33 +10:00
parent 92dd16c114
commit 9baec96c18
1 changed files with 136 additions and 14 deletions

View File

@ -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::*;
@ -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());
@ -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
@ -285,7 +295,15 @@ 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();
@ -293,7 +311,7 @@ fn block_difficulty() -> Result<(), Report> {
canonical_compact);
}
/// SPANDOC: Check the work for block {?height}
/// SPANDOC: Check the work for block {?height, ?network}
{
let work = block
.header
@ -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]