feature: Make ExpandedDifficulty use U256 internally
Adds a dependency on the primitive-types crate. Also adds custom hex debug formatting for compact and expanded difficulties.
This commit is contained in:
parent
c04d1b7b8f
commit
b7fac7b3bc
|
@ -192,6 +192,16 @@ dependencies = [
|
|||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitvec"
|
||||
version = "0.17.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41262f11d771fd4a61aa3ce019fca363b4b6c282fca9da2a31186d3965a47a5c"
|
||||
dependencies = [
|
||||
"either",
|
||||
"radium",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "blake2b_simd"
|
||||
version = "0.5.10"
|
||||
|
@ -253,6 +263,12 @@ dependencies = [
|
|||
"sha2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "byte-slice-cast"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b0a5e3906bcbf133e33c1d4d95afc664ad37fbdb9f6568d8043e7ea8c27d93d3"
|
||||
|
||||
[[package]]
|
||||
name = "byte-tools"
|
||||
version = "0.3.1"
|
||||
|
@ -458,6 +474,12 @@ dependencies = [
|
|||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crunchy"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
|
||||
|
||||
[[package]]
|
||||
name = "ctor"
|
||||
version = "0.1.15"
|
||||
|
@ -600,6 +622,12 @@ dependencies = [
|
|||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3"
|
||||
|
||||
[[package]]
|
||||
name = "equihash"
|
||||
version = "0.1.0"
|
||||
|
@ -626,6 +654,18 @@ version = "0.1.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed"
|
||||
|
||||
[[package]]
|
||||
name = "fixed-hash"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "11498d382790b7a8f2fd211780bec78619bba81cdad3a283997c0c41f836759c"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"rand 0.7.3",
|
||||
"rustc-hex",
|
||||
"static_assertions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
version = "1.0.7"
|
||||
|
@ -966,6 +1006,15 @@ dependencies = [
|
|||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "impl-codec"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1be51a921b067b0eaca2fad532d9400041561aa922221cc65f95a85641c6bf53"
|
||||
dependencies = [
|
||||
"parity-scale-codec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indenter"
|
||||
version = "0.3.0"
|
||||
|
@ -1333,6 +1382,18 @@ version = "0.2.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c"
|
||||
|
||||
[[package]]
|
||||
name = "parity-scale-codec"
|
||||
version = "1.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34d38aeaffc032ec69faa476b3caaca8d4dd7f3f798137ff30359e5c7869ceb6"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"bitvec",
|
||||
"byte-slice-cast",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.10.2"
|
||||
|
@ -1421,6 +1482,17 @@ version = "0.2.8"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "237a5ed80e274dbc66f86bd59c1e25edc039660be53194b5fe0a482e0f2612ea"
|
||||
|
||||
[[package]]
|
||||
name = "primitive-types"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c55c21c64d0eaa4d7ed885d959ef2d62d9e488c27c0e02d9aa5ce6c877b7d5f8"
|
||||
dependencies = [
|
||||
"fixed-hash",
|
||||
"impl-codec",
|
||||
"uint",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error"
|
||||
version = "1.0.3"
|
||||
|
@ -1544,6 +1616,12 @@ dependencies = [
|
|||
"proc-macro2 1.0.19",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "radium"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "def50a86306165861203e7f84ecffbbdfdea79f0e51039b33de1e952358c47ac"
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.4.6"
|
||||
|
@ -1738,6 +1816,12 @@ version = "0.1.16"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783"
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hex"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6"
|
||||
|
||||
[[package]]
|
||||
name = "rusty-fork"
|
||||
version = "0.3.0"
|
||||
|
@ -1973,6 +2057,12 @@ dependencies = [
|
|||
"syn 1.0.35",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "static_assertions"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.8.0"
|
||||
|
@ -2481,6 +2571,18 @@ version = "1.12.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33"
|
||||
|
||||
[[package]]
|
||||
name = "uint"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "173cd16430c206dc1a430af8a89a0e9c076cf15cb42b4aedb10e8cc8fee73681"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"crunchy",
|
||||
"rustc-hex",
|
||||
"static_assertions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-segmentation"
|
||||
version = "1.6.0"
|
||||
|
@ -2635,6 +2737,7 @@ dependencies = [
|
|||
"hex",
|
||||
"jubjub",
|
||||
"lazy_static",
|
||||
"primitive-types",
|
||||
"proptest",
|
||||
"proptest-derive",
|
||||
"rand_core 0.5.1",
|
||||
|
|
|
@ -18,6 +18,7 @@ futures = "0.3"
|
|||
hex = "0.4"
|
||||
jubjub = "0.3.0"
|
||||
lazy_static = "1.4.0"
|
||||
primitive-types = "0.7.2"
|
||||
rand_core = "0.5.1"
|
||||
ripemd160 = "0.8.0"
|
||||
secp256k1 = { version = "0.17.2", features = ["serde"] }
|
||||
|
|
|
@ -10,6 +10,10 @@
|
|||
//! block's work value depends on the fixed threshold in the block header, not
|
||||
//! the actual work represented by the block header hash.
|
||||
|
||||
use primitive_types::U256;
|
||||
|
||||
#[cfg(test)]
|
||||
use proptest::prelude::*;
|
||||
#[cfg(test)]
|
||||
use proptest_derive::Arbitrary;
|
||||
|
||||
|
@ -41,10 +45,19 @@ use proptest_derive::Arbitrary;
|
|||
/// Without these consensus rules, some `ExpandedDifficulty` values would have
|
||||
/// multiple equivalent `CompactDifficulty` values, due to redundancy in the
|
||||
/// floating-point format.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
|
||||
#[cfg_attr(test, derive(Arbitrary))]
|
||||
pub struct CompactDifficulty(pub u32);
|
||||
|
||||
impl fmt::Debug for CompactDifficulty {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.debug_tuple("CompactDifficulty")
|
||||
// Use hex, because it's a float
|
||||
.field(&format_args!("{:#010x}", self.0))
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
/// A 256-bit unsigned "expanded difficulty" value.
|
||||
///
|
||||
/// Used as a target threshold for the difficulty of a `BlockHeaderHash`.
|
||||
|
@ -61,11 +74,27 @@ pub struct CompactDifficulty(pub u32);
|
|||
/// Therefore, consensus-critical code must perform the specified
|
||||
/// conversions to `CompactDifficulty`, even if the original
|
||||
/// `ExpandedDifficulty` values are known.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
|
||||
#[cfg_attr(test, derive(Arbitrary))]
|
||||
pub struct ExpandedDifficulty([u8; 32]);
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd)]
|
||||
pub struct ExpandedDifficulty(U256);
|
||||
|
||||
impl fmt::Debug for ExpandedDifficulty {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let mut buf = [0; 32];
|
||||
// Use the same byte order as BlockHeaderHash
|
||||
self.0.to_little_endian(&mut buf);
|
||||
f.debug_tuple("ExpandedDifficulty")
|
||||
.field(&hex::encode(&buf))
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl CompactDifficulty {
|
||||
/// CompactDifficulty exponent base.
|
||||
const BASE: u32 = 256;
|
||||
|
||||
/// CompactDifficulty exponent offset.
|
||||
const OFFSET: i32 = 3;
|
||||
|
||||
/// CompactDifficulty floating-point precision.
|
||||
const PRECISION: u32 = 24;
|
||||
|
||||
|
@ -75,10 +104,7 @@ impl CompactDifficulty {
|
|||
/// 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;
|
||||
const UNSIGNED_MANTISSA_MASK: u32 = CompactDifficulty::SIGN_BIT - 1;
|
||||
|
||||
/// Calculate the ExpandedDifficulty for a compact representation.
|
||||
///
|
||||
|
@ -90,10 +116,11 @@ impl CompactDifficulty {
|
|||
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 BASE: u32 = CompactDifficulty::BASE;
|
||||
const OFFSET: i32 = CompactDifficulty::OFFSET;
|
||||
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;
|
||||
const UNSIGNED_MANTISSA_MASK: u32 = CompactDifficulty::UNSIGNED_MANTISSA_MASK;
|
||||
|
||||
// Negative values in this floating-point representation.
|
||||
// 0 if (x & 2^23 == 2^23)
|
||||
|
@ -103,35 +130,46 @@ impl CompactDifficulty {
|
|||
}
|
||||
|
||||
// The components of the result
|
||||
// The fractional part of the number
|
||||
// The fractional part of the floating-point number
|
||||
// x & (2^23 - 1)
|
||||
let mantissa = self.0 & U_MANT_MASK;
|
||||
let mantissa = self.0 & UNSIGNED_MANTISSA_MASK;
|
||||
|
||||
// The position of the number in the result, in bytes (rather than bits)
|
||||
// The exponent for the multiplier in the floating-point number
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
// Normalise the mantissa and exponent before multiplying.
|
||||
//
|
||||
// zcashd rejects non-zero overflow values, but accepts overflows where
|
||||
// all the overflowing bits are zero. It also allows underflows.
|
||||
let (mantissa, exponent) = match (mantissa, exponent) {
|
||||
// Overflow: check for non-zero overflow bits
|
||||
//
|
||||
// If m is non-zero, overflow. If m is zero, invalid.
|
||||
(_, e) if (e >= 32) => return None,
|
||||
// If m is larger than the remaining bytes, overflow.
|
||||
// Otherwise, avoid overflows in base^exponent.
|
||||
(m, e) if (e == 31 && m > u8::MAX.into()) => return None,
|
||||
(m, e) if (e == 31 && m <= u8::MAX.into()) => (m << 16, e - 2),
|
||||
(m, e) if (e == 30 && m > u16::MAX.into()) => return None,
|
||||
(m, e) if (e == 30 && m <= u16::MAX.into()) => (m << 8, e - 1),
|
||||
|
||||
if result == [0; 32] {
|
||||
// Underflow: perform the right shift.
|
||||
// The abs is safe, because we've just divided by 2^24, and offset
|
||||
// is small.
|
||||
(m, e) if (e < 0) => (m >> ((e.abs() * 8) as u32), 0),
|
||||
(m, e) => (m, e),
|
||||
};
|
||||
|
||||
// Now calculate the result: mantissa*base^exponent
|
||||
// Earlier code should make sure all these values are in range.
|
||||
let mantissa: U256 = mantissa.into();
|
||||
let base: U256 = BASE.into();
|
||||
let exponent: U256 = exponent.into();
|
||||
let result = mantissa * base.pow(exponent);
|
||||
|
||||
if result == U256::zero() {
|
||||
// zcashd rejects zero values, without comparing the hash
|
||||
None
|
||||
} else {
|
||||
|
@ -140,6 +178,19 @@ impl CompactDifficulty {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
impl Arbitrary for ExpandedDifficulty {
|
||||
type Parameters = ();
|
||||
|
||||
fn arbitrary_with(_args: ()) -> Self::Strategy {
|
||||
(any::<[u8; 32]>())
|
||||
.prop_map(|v| ExpandedDifficulty(U256::from_little_endian(&v)))
|
||||
.boxed()
|
||||
}
|
||||
|
||||
type Strategy = BoxedStrategy<Self>;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
@ -153,9 +204,32 @@ mod tests {
|
|||
// 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 UNSIGNED_MANTISSA_MASK: u32 = CompactDifficulty::UNSIGNED_MANTISSA_MASK;
|
||||
const OFFSET: i32 = CompactDifficulty::OFFSET;
|
||||
|
||||
/// Test debug formatting.
|
||||
#[test]
|
||||
fn debug_format() {
|
||||
zebra_test::init();
|
||||
|
||||
assert_eq!(
|
||||
format!("{:?}", CompactDifficulty(0)),
|
||||
"CompactDifficulty(0x00000000)"
|
||||
);
|
||||
assert_eq!(
|
||||
format!("{:?}", CompactDifficulty(1)),
|
||||
"CompactDifficulty(0x00000001)"
|
||||
);
|
||||
assert_eq!(
|
||||
format!("{:?}", CompactDifficulty(u32::MAX)),
|
||||
"CompactDifficulty(0xffffffff)"
|
||||
);
|
||||
|
||||
assert_eq!(format!("{:?}", ExpandedDifficulty(U256::zero())), "ExpandedDifficulty(\"0000000000000000000000000000000000000000000000000000000000000000\")");
|
||||
assert_eq!(format!("{:?}", ExpandedDifficulty(U256::one())), "ExpandedDifficulty(\"0100000000000000000000000000000000000000000000000000000000000000\")");
|
||||
assert_eq!(format!("{:?}", ExpandedDifficulty(U256::MAX)), "ExpandedDifficulty(\"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\")");
|
||||
}
|
||||
|
||||
/// Test zero values for CompactDifficulty.
|
||||
#[test]
|
||||
fn compact_zero() {
|
||||
|
@ -167,7 +241,7 @@ mod tests {
|
|||
// 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);
|
||||
let small_zero_max = CompactDifficulty(UNSIGNED_MANTISSA_MASK);
|
||||
assert_eq!(small_zero_max.to_expanded(), None);
|
||||
|
||||
// Special-cased zeroes, negative in the floating-point representation
|
||||
|
@ -187,9 +261,7 @@ mod tests {
|
|||
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 expanded_one = Some(ExpandedDifficulty(U256::one()));
|
||||
|
||||
let one = CompactDifficulty(OFFSET as u32 * (1 << PRECISION) + 1);
|
||||
assert_eq!(one.to_expanded(), expanded_one);
|
||||
|
@ -197,31 +269,24 @@ mod tests {
|
|||
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 expanded_mant = Some(ExpandedDifficulty(UNSIGNED_MANTISSA_MASK.into()));
|
||||
|
||||
let mant = CompactDifficulty(OFFSET as u32 * (1 << PRECISION) + U_MANT_MASK);
|
||||
let mant = CompactDifficulty(OFFSET as u32 * (1 << PRECISION) + UNSIGNED_MANTISSA_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 exponent: U256 = (31 * 8).into();
|
||||
let expanded_exp = Some(ExpandedDifficulty(U256::from(2).pow(exponent)));
|
||||
|
||||
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 exponent: U256 = (29 * 8).into();
|
||||
let expanded_me = U256::from(UNSIGNED_MANTISSA_MASK) * U256::from(2).pow(exponent);
|
||||
let expanded_me = Some(ExpandedDifficulty(expanded_me));
|
||||
|
||||
let me = CompactDifficulty((31 + 1) * (1 << PRECISION) + U_MANT_MASK);
|
||||
let me = CompactDifficulty((31 + 1) * (1 << PRECISION) + UNSIGNED_MANTISSA_MASK);
|
||||
assert_eq!(me.to_expanded(), expanded_me);
|
||||
|
||||
// Maximum value, at least according to the spec
|
||||
|
|
Loading…
Reference in New Issue