feature: Add an ExpandedDifficulty type and conversion
Also add tests.
This commit is contained in:
parent
c4dec3fb36
commit
195948e5b1
|
|
@ -2644,7 +2644,9 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"serde-big-array",
|
"serde-big-array",
|
||||||
"sha2",
|
"sha2",
|
||||||
|
"spandoc",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
|
"tracing",
|
||||||
"x25519-dalek",
|
"x25519-dalek",
|
||||||
"zebra-test",
|
"zebra-test",
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -22,19 +22,23 @@ rand_core = "0.5.1"
|
||||||
ripemd160 = "0.8.0"
|
ripemd160 = "0.8.0"
|
||||||
secp256k1 = { version = "0.17.2", features = ["serde"] }
|
secp256k1 = { version = "0.17.2", features = ["serde"] }
|
||||||
serde = { version = "1", features = ["serde_derive", "rc"] }
|
serde = { version = "1", features = ["serde_derive", "rc"] }
|
||||||
|
serde-big-array = "0.3.0"
|
||||||
sha2 = { version = "0.8.2", features=["compress"] }
|
sha2 = { version = "0.8.2", features=["compress"] }
|
||||||
thiserror = "1"
|
thiserror = "1"
|
||||||
x25519-dalek = { version = "0.6", features = ["serde"] }
|
x25519-dalek = { version = "0.6", features = ["serde"] }
|
||||||
serde-big-array = "0.3.0"
|
|
||||||
# ZF deps
|
# ZF deps
|
||||||
ed25519-zebra = "1.0"
|
|
||||||
redjubjub = "0.2"
|
|
||||||
equihash = "0.1"
|
|
||||||
displaydoc = "0.1.7"
|
displaydoc = "0.1.7"
|
||||||
|
ed25519-zebra = "1.0"
|
||||||
|
equihash = "0.1"
|
||||||
|
redjubjub = "0.2"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
bincode = "1"
|
||||||
|
color-eyre = "0.5"
|
||||||
proptest = "0.10"
|
proptest = "0.10"
|
||||||
proptest-derive = "0.2.0"
|
proptest-derive = "0.2.0"
|
||||||
|
spandoc = "0.2"
|
||||||
|
tracing = "0.1.17"
|
||||||
|
|
||||||
zebra-test = { path = "../zebra-test/" }
|
zebra-test = { path = "../zebra-test/" }
|
||||||
color-eyre = "0.5"
|
|
||||||
bincode = "1"
|
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,236 @@ use proptest_derive::Arbitrary;
|
||||||
/// - calculating the 256-bit `ExpandedDifficulty` threshold, for comparison
|
/// - calculating the 256-bit `ExpandedDifficulty` threshold, for comparison
|
||||||
/// with the block header hash, and
|
/// with the block header hash, and
|
||||||
/// - calculating the block work.
|
/// - calculating the block work.
|
||||||
|
///
|
||||||
|
/// Details:
|
||||||
|
///
|
||||||
|
/// This is a floating-point encoding, with a 24-bit signed mantissa,
|
||||||
|
/// an 8-bit exponent, an offset of 3, and a radix of 256.
|
||||||
|
/// (IEEE 754 32-bit floating-point values use a separate sign bit, an implicit
|
||||||
|
/// leading mantissa bit, an offset of 127, and a radix of 2.)
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
|
||||||
#[cfg_attr(test, derive(Arbitrary))]
|
#[cfg_attr(test, derive(Arbitrary))]
|
||||||
pub struct CompactDifficulty(pub u32);
|
pub struct CompactDifficulty(pub u32);
|
||||||
|
|
||||||
|
/// A 256-bit expanded difficulty value.
|
||||||
|
///
|
||||||
|
/// Used as a target threshold for the difficulty of a `BlockHeaderHash`.
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
|
||||||
|
#[cfg_attr(test, derive(Arbitrary))]
|
||||||
|
pub struct ExpandedDifficulty([u8; 32]);
|
||||||
|
|
||||||
|
impl CompactDifficulty {
|
||||||
|
/// CompactDifficulty floating-point precision.
|
||||||
|
const PRECISION: u32 = 24;
|
||||||
|
|
||||||
|
/// CompactDifficulty sign bit, part of the signed mantissa.
|
||||||
|
const SIGN_BIT: u32 = 1 << (CompactDifficulty::PRECISION - 1);
|
||||||
|
|
||||||
|
/// CompactDifficulty unsigned mantissa mask.
|
||||||
|
///
|
||||||
|
/// Also the maximum unsigned mantissa value.
|
||||||
|
const U_MANT_MASK: u32 = CompactDifficulty::SIGN_BIT - 1;
|
||||||
|
|
||||||
|
/// CompactDifficulty exponent offset.
|
||||||
|
const OFFSET: i32 = 3;
|
||||||
|
|
||||||
|
/// Calculate the ExpandedDifficulty for a compact representation.
|
||||||
|
///
|
||||||
|
/// See `ToTarget()` in the Zcash Specification, and `CheckProofOfWork()` in
|
||||||
|
/// zcashd.
|
||||||
|
///
|
||||||
|
/// Returns None for negative, zero, and overflow values. (zcashd rejects
|
||||||
|
/// these values, before comparing the hash.)
|
||||||
|
pub fn to_expanded(&self) -> Option<ExpandedDifficulty> {
|
||||||
|
// The constants for this floating-point representation.
|
||||||
|
// Alias the struct constants here, so the code is easier to read.
|
||||||
|
const PRECISION: u32 = CompactDifficulty::PRECISION;
|
||||||
|
const SIGN_BIT: u32 = CompactDifficulty::SIGN_BIT;
|
||||||
|
const U_MANT_MASK: u32 = CompactDifficulty::U_MANT_MASK;
|
||||||
|
const OFFSET: i32 = CompactDifficulty::OFFSET;
|
||||||
|
|
||||||
|
// Negative values in this floating-point representation.
|
||||||
|
// 0 if (x & 2^23 == 2^23)
|
||||||
|
// zcashd rejects negative values without comparing the hash.
|
||||||
|
if self.0 & SIGN_BIT == SIGN_BIT {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The components of the result
|
||||||
|
// The fractional part of the number
|
||||||
|
// x & (2^23 - 1)
|
||||||
|
let mantissa = self.0 & U_MANT_MASK;
|
||||||
|
|
||||||
|
// The position of the number in the result, in bytes (rather than bits)
|
||||||
|
// 256^(floor(x/(2^24)) - 3)
|
||||||
|
// The i32 conversion is safe, because we've just divided self by 2^24.
|
||||||
|
let exponent = ((self.0 >> PRECISION) as i32) - OFFSET;
|
||||||
|
|
||||||
|
// Now put the mantissa in the right place in the result, based on
|
||||||
|
// the exponent.
|
||||||
|
let mut result = [0; 32];
|
||||||
|
for (i, b) in mantissa.to_le_bytes().iter().enumerate() {
|
||||||
|
// These conversions are safe, due to the size of the array, and the
|
||||||
|
// range checks before array access.
|
||||||
|
let position = exponent + i as i32;
|
||||||
|
if position >= 32 {
|
||||||
|
if *b != 0 {
|
||||||
|
// zcashd rejects overflow values, without comparing the
|
||||||
|
// hash
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
} else if position >= 0 {
|
||||||
|
// zcashd truncates fractional values
|
||||||
|
result[position as usize] = *b;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if result == [0; 32] {
|
||||||
|
// zcashd rejects zero values, without comparing the hash
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(ExpandedDifficulty(result))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
use color_eyre::eyre::Report;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use crate::block::{Block, BlockHeaderHash};
|
||||||
|
use crate::serialization::ZcashDeserialize;
|
||||||
|
|
||||||
|
// Alias the struct constants here, so the code is easier to read.
|
||||||
|
const PRECISION: u32 = CompactDifficulty::PRECISION;
|
||||||
|
const SIGN_BIT: u32 = CompactDifficulty::SIGN_BIT;
|
||||||
|
const U_MANT_MASK: u32 = CompactDifficulty::U_MANT_MASK;
|
||||||
|
const OFFSET: i32 = CompactDifficulty::OFFSET;
|
||||||
|
|
||||||
|
/// Test zero values for CompactDifficulty.
|
||||||
|
#[test]
|
||||||
|
fn compact_zero() {
|
||||||
|
zebra_test::init();
|
||||||
|
|
||||||
|
let natural_zero = CompactDifficulty(0);
|
||||||
|
assert_eq!(natural_zero.to_expanded(), None);
|
||||||
|
|
||||||
|
// Small value zeroes
|
||||||
|
let small_zero_1 = CompactDifficulty(1);
|
||||||
|
assert_eq!(small_zero_1.to_expanded(), None);
|
||||||
|
let small_zero_max = CompactDifficulty(U_MANT_MASK);
|
||||||
|
assert_eq!(small_zero_max.to_expanded(), None);
|
||||||
|
|
||||||
|
// Special-cased zeroes, negative in the floating-point representation
|
||||||
|
let sc_zero = CompactDifficulty(SIGN_BIT);
|
||||||
|
assert_eq!(sc_zero.to_expanded(), None);
|
||||||
|
let sc_zero_next = CompactDifficulty(SIGN_BIT + 1);
|
||||||
|
assert_eq!(sc_zero_next.to_expanded(), None);
|
||||||
|
let sc_zero_high = CompactDifficulty((1 << PRECISION) - 1);
|
||||||
|
assert_eq!(sc_zero_high.to_expanded(), None);
|
||||||
|
let sc_zero_max = CompactDifficulty(u32::MAX);
|
||||||
|
assert_eq!(sc_zero_max.to_expanded(), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Test extreme values for CompactDifficulty.
|
||||||
|
#[test]
|
||||||
|
fn compact_extremes() {
|
||||||
|
zebra_test::init();
|
||||||
|
|
||||||
|
// Values equal to one
|
||||||
|
let mut expanded_one = [0; 32];
|
||||||
|
expanded_one[0] = 1;
|
||||||
|
let expanded_one = Some(ExpandedDifficulty(expanded_one));
|
||||||
|
|
||||||
|
let one = CompactDifficulty(OFFSET as u32 * (1 << PRECISION) + 1);
|
||||||
|
assert_eq!(one.to_expanded(), expanded_one);
|
||||||
|
let another_one = CompactDifficulty((1 << PRECISION) + (1 << 16));
|
||||||
|
assert_eq!(another_one.to_expanded(), expanded_one);
|
||||||
|
|
||||||
|
// Maximum mantissa
|
||||||
|
let mut expanded_mant = [0; 32];
|
||||||
|
expanded_mant[0] = 0xff;
|
||||||
|
expanded_mant[1] = 0xff;
|
||||||
|
expanded_mant[2] = 0x7f;
|
||||||
|
let expanded_mant = Some(ExpandedDifficulty(expanded_mant));
|
||||||
|
|
||||||
|
let mant = CompactDifficulty(OFFSET as u32 * (1 << PRECISION) + U_MANT_MASK);
|
||||||
|
assert_eq!(mant.to_expanded(), expanded_mant);
|
||||||
|
|
||||||
|
// Maximum valid exponent
|
||||||
|
let mut expanded_exp = [0; 32];
|
||||||
|
expanded_exp[31] = 1;
|
||||||
|
let expanded_exp = Some(ExpandedDifficulty(expanded_exp));
|
||||||
|
|
||||||
|
let exp = CompactDifficulty((31 + OFFSET as u32) * (1 << PRECISION) + 1);
|
||||||
|
assert_eq!(exp.to_expanded(), expanded_exp);
|
||||||
|
|
||||||
|
// Maximum valid mantissa and exponent
|
||||||
|
let mut expanded_me = [0; 32];
|
||||||
|
expanded_me[29] = 0xff;
|
||||||
|
expanded_me[30] = 0xff;
|
||||||
|
expanded_me[31] = 0x7f;
|
||||||
|
let expanded_me = Some(ExpandedDifficulty(expanded_me));
|
||||||
|
|
||||||
|
let me = CompactDifficulty((31 + 1) * (1 << PRECISION) + U_MANT_MASK);
|
||||||
|
assert_eq!(me.to_expanded(), expanded_me);
|
||||||
|
|
||||||
|
// Maximum value, at least according to the spec
|
||||||
|
//
|
||||||
|
// According to ToTarget() in the spec, this value is
|
||||||
|
// `(2^23 - 1) * 256^253`, which is larger than the maximum expanded
|
||||||
|
// value. Therefore, a block can never pass with this threshold.
|
||||||
|
//
|
||||||
|
// zcashd rejects these blocks without comparing the hash.
|
||||||
|
let difficulty_max = CompactDifficulty(u32::MAX & !SIGN_BIT);
|
||||||
|
assert_eq!(difficulty_max.to_expanded(), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Test blocks using CompactDifficulty.
|
||||||
|
#[test]
|
||||||
|
#[spandoc::spandoc]
|
||||||
|
fn compact_blocks() -> Result<(), Report> {
|
||||||
|
zebra_test::init();
|
||||||
|
|
||||||
|
let mut blockchain = Vec::new();
|
||||||
|
for b in &[
|
||||||
|
&zebra_test::vectors::BLOCK_MAINNET_GENESIS_BYTES[..],
|
||||||
|
&zebra_test::vectors::BLOCK_MAINNET_1_BYTES[..],
|
||||||
|
&zebra_test::vectors::BLOCK_MAINNET_2_BYTES[..],
|
||||||
|
&zebra_test::vectors::BLOCK_MAINNET_3_BYTES[..],
|
||||||
|
&zebra_test::vectors::BLOCK_MAINNET_4_BYTES[..],
|
||||||
|
&zebra_test::vectors::BLOCK_MAINNET_5_BYTES[..],
|
||||||
|
&zebra_test::vectors::BLOCK_MAINNET_6_BYTES[..],
|
||||||
|
&zebra_test::vectors::BLOCK_MAINNET_7_BYTES[..],
|
||||||
|
&zebra_test::vectors::BLOCK_MAINNET_8_BYTES[..],
|
||||||
|
&zebra_test::vectors::BLOCK_MAINNET_9_BYTES[..],
|
||||||
|
&zebra_test::vectors::BLOCK_MAINNET_10_BYTES[..],
|
||||||
|
] {
|
||||||
|
let block = Arc::<Block>::zcash_deserialize(*b)?;
|
||||||
|
let hash: BlockHeaderHash = block.as_ref().into();
|
||||||
|
blockchain.push((block.clone(), block.coinbase_height().unwrap(), hash));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now verify each block
|
||||||
|
for (block, height, hash) in blockchain {
|
||||||
|
/// SPANDOC: Check the difficulty of a mainnet block {?height, ?hash}
|
||||||
|
let threshold = block
|
||||||
|
.header
|
||||||
|
.difficulty_threshold
|
||||||
|
.to_expanded()
|
||||||
|
.expect("Chain blocks have valid difficulty thresholds.");
|
||||||
|
|
||||||
|
// Check the difficulty of the block.
|
||||||
|
//
|
||||||
|
// Invert the "less than or equal" comparison, because we interpret
|
||||||
|
// these values in little-endian order.
|
||||||
|
// TODO: replace with PartialOrd implementation
|
||||||
|
assert!(hash.0 >= threshold.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue