Skip to content

Commit

Permalink
review comments
Browse files Browse the repository at this point in the history
  • Loading branch information
hansieodendaal committed Jun 24, 2023
1 parent 67e6a59 commit b95112d
Show file tree
Hide file tree
Showing 4 changed files with 137 additions and 209 deletions.
335 changes: 130 additions & 205 deletions base_layer/core/src/proof_of_work/difficulty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,10 @@ use num_format::{Locale, ToFormattedString};
use serde::{Deserialize, Serialize};
use tari_utilities::epoch_time::EpochTime;

use crate::proof_of_work::{error::DifficultyError, DifficultyAdjustmentError};
use crate::{
proof_of_work::{error::DifficultyError, DifficultyAdjustmentError},
U256,
};

/// Minimum difficulty, enforced in diff retargeting
/// avoids getting stuck when trying to increase difficulty subject to dampening
Expand All @@ -45,6 +48,11 @@ impl Difficulty {
Ok(Self(d))
}

/// Return the difficulty as a u64
pub fn as_u64(self) -> u64 {
self.0
}

/// Difficulty of MIN_DIFFICULTY
pub const fn min() -> Difficulty {
Difficulty(MIN_DIFFICULTY)
Expand All @@ -55,18 +63,41 @@ impl Difficulty {
Difficulty(u64::MAX)
}

/// Return the difficulty as a u64
pub fn as_u64(self) -> u64 {
self.0
/// Helper function to provide the difficulty of the hash assuming the hash is big_endian
pub fn big_endian_difficulty(hash: &[u8]) -> Result<Difficulty, DifficultyError> {
let scalar = U256::from_big_endian(hash); // Big endian so the hash has leading zeroes
Difficulty::u256_scalar_to_difficulty(scalar)
}

/// Helper function to provide the difficulty of the hash assuming the hash is little_endian
pub fn little_endian_difficulty(hash: &[u8]) -> Result<Difficulty, DifficultyError> {
let scalar = U256::from_little_endian(hash); // Little endian so the hash has trailing zeroes
Difficulty::u256_scalar_to_difficulty(scalar)
}

fn u256_scalar_to_difficulty(scalar: U256) -> Result<Difficulty, DifficultyError> {
let result = U256::MAX / scalar;
let result = result.min(u64::MAX.into());
Difficulty::from_u64(result.low_u64())
}
}

/// These traits should not be implemented for `Difficulty`:
/// - `Add<Self> for Difficulty` "`+` must not be used, use `checked_add(value)` instead; to prevent overflow
/// - `Sub<Self> for Difficulty` `-` must not be used, use `checked_sub(value)` instead; to prevent underflow
/// - `Mul for Difficulty` `*` must not be used at all; difficulties should only be added to or subtracted from
/// - `Div for Difficulty` `/` must not be used at all; difficulties should only be added to or subtracted from
/// - `From<u64> for Difficulty` `Difficulty::from<u64>` must not be used, use `from_u64(value)` instead; to prevent
/// assignment `< MIN_DIFFICULTY`

impl Default for Difficulty {
fn default() -> Self {
Difficulty::min()
}
}

/// This trait is used to add a type to `CheckedAdd`, which greatly simplifies usage in the code.
/// It is implemented for `Difficulty` and `u64`.
pub trait CheckedAdd<T> {
fn checked_add(&self, other: T) -> Option<Self>
where Self: Sized;
Expand All @@ -84,6 +115,8 @@ impl CheckedAdd<u64> for Difficulty {
}
}

/// This trait is used to add a type to `CheckedSub`, which greatly simplifies usage in the code.
/// It is implemented for `Difficulty` and `u64`.
pub trait CheckedSub<T> {
fn checked_sub(&self, other: T) -> Option<Self>
where Self: Sized;
Expand Down Expand Up @@ -121,57 +154,6 @@ impl fmt::Display for Difficulty {
}
}

#[cfg(any(test))]
use std::ops::{Add, Div, Mul, Sub};

// This trait should not be implemented for runtime, here only for testing
#[cfg(any(test))]
impl Add<Self> for Difficulty {
type Output = Self;

fn add(self, _rhs: Self) -> Self {
unimplemented!("Sub::sub<rhs> must not be used, use `.checked_add(value)` instead")
}
}

// This trait should not be implemented for runtime, here only for testing
#[cfg(any(test))]
impl Sub<Self> for Difficulty {
type Output = Self;

fn sub(self, _rhs: Self) -> Self {
unimplemented!("Sub::sub<rhs> must not be used, use `.checked_sub(value)` instead")
}
}

// This trait should not be implemented for runtime, here only for testing
#[cfg(any(test))]
impl Mul for Difficulty {
type Output = u64;

fn mul(self, _rhs: Self) -> Self::Output {
unimplemented!("Mul::mul<rhs> must not be used; difficulties should only be added to or subtracted from")
}
}

// This trait should not be implemented for runtime, here only for testing
#[cfg(any(test))]
impl Div for Difficulty {
type Output = u64;

fn div(self, _rhs: Self) -> Self::Output {
unimplemented!("Div::div<rhs> must not be used; difficulties should only be added to or subtracted from")
}
}

// This trait should not be implemented for runtime, here only for testing
#[cfg(any(test))]
impl From<u64> for Difficulty {
fn from(_value: u64) -> Self {
unimplemented!("Difficulty::from<u64> must not be used, use `Difficulty::from_u64(value)` instead")
}
}

/// General difficulty adjustment algorithm trait. The key method is `get_difficulty`, which returns the target
/// difficulty given a set of historical achieved difficulties; supplied through the `add` method.
pub trait DifficultyAdjustment {
Expand All @@ -187,157 +169,15 @@ pub trait DifficultyAdjustment {
fn get_difficulty(&self) -> Option<Difficulty>;
}

#[cfg(feature = "base_node")]
pub mod util {
use super::*;
use crate::U256;

/// This will provide the difficulty of the hash assuming the hash is big_endian
pub(crate) fn big_endian_difficulty(hash: &[u8]) -> Result<Difficulty, DifficultyError> {
let scalar = U256::from_big_endian(hash); // Big endian so the hash has leading zeroes
let result = U256::MAX / scalar;
let result = result.min(u64::MAX.into());
Difficulty::from_u64(result.low_u64())
}

/// This will provide the difficulty of the hash assuming the hash is little_endian
pub(crate) fn little_endian_difficulty(hash: &[u8]) -> Result<Difficulty, DifficultyError> {
let scalar = U256::from_little_endian(hash); // Little endian so the hash has trailing zeroes
let result = U256::MAX / scalar;
let result = result.min(u64::MAX.into());
Difficulty::from_u64(result.low_u64())
}

#[cfg(test)]
mod test {
use std::panic::catch_unwind;

use crate::{
proof_of_work::{
difficulty::{
util::{big_endian_difficulty, little_endian_difficulty},
CheckedAdd,
CheckedSub,
MIN_DIFFICULTY,
},
Difficulty,
},
U256,
};

#[test]
fn difficulty_converts_correctly_at_its_minimum() {
for d in 0..=MIN_DIFFICULTY + 1 {
if d < MIN_DIFFICULTY {
assert!(Difficulty::from_u64(d).is_err());
} else {
assert!(Difficulty::from_u64(d).is_ok());
}
}
assert_eq!(Difficulty::min().as_u64(), MIN_DIFFICULTY);
}

#[test]
fn addition_does_not_overflow() {
let d1 = Difficulty::from_u64(100).unwrap();
assert!(d1.checked_add(1).is_some());
let d2 = Difficulty::max();
assert!(d2.checked_add(1).is_none());
}

#[test]
fn it_blocks_unsupported_traits() {
let add_result = catch_unwind(|| {
let _ = Difficulty::min() + Difficulty::min();
});
assert!(add_result.is_err());
let sub_result = catch_unwind(|| {
let _ = Difficulty::max() - Difficulty::min();
});
assert!(sub_result.is_err());
let mul_result = catch_unwind(|| {
let _ = Difficulty::max() * Difficulty::min();
});
assert!(mul_result.is_err());
let div_result = catch_unwind(|| {
let _ = Difficulty::max() / Difficulty::min();
});
assert!(div_result.is_err());
let from_result = catch_unwind(|| {
let _ = Difficulty::from(10u64);
});
assert!(from_result.is_err());
}

#[test]
fn subtraction_does_not_underflow() {
let d1 = Difficulty::from_u64(100).unwrap();
assert!(d1.checked_sub(1).is_some());
let d2 = Difficulty::max();
assert!(d1.checked_sub(d2).is_none());
}

#[test]
fn be_high_target() {
let target: &[u8] = &[
0xff, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
];
let expected = Difficulty::min();
assert_eq!(big_endian_difficulty(target).unwrap(), expected);
}

#[test]
fn be_max_difficulty() {
let target = U256::MAX / U256::from(u64::MAX);
let mut bytes = [0u8; 32];
target.to_big_endian(&mut bytes);
assert_eq!(big_endian_difficulty(&bytes).unwrap(), Difficulty::max());
}

#[test]
fn be_stop_overflow() {
let target: u64 = 64;
let expected = u64::MAX;
assert_eq!(
big_endian_difficulty(&target.to_be_bytes()).unwrap(),
Difficulty::from_u64(expected).unwrap()
);
}

#[test]
fn le_high_target() {
let target: &[u8] = &[
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0xff,
];
let expected = Difficulty::min();
assert_eq!(little_endian_difficulty(target).unwrap(), expected);
}

#[test]
fn le_max_difficulty() {
let target = U256::MAX / U256::from(u64::MAX);
let mut bytes = [0u8; 32];
target.to_little_endian(&mut bytes);
assert_eq!(little_endian_difficulty(&bytes).unwrap(), Difficulty::max());
}

#[test]
fn le_stop_overflow() {
let target: u64 = 64;
let expected = u64::MAX;
assert_eq!(
little_endian_difficulty(&target.to_be_bytes()).unwrap(),
Difficulty::from_u64(expected).unwrap()
);
}
}
}

#[cfg(test)]
mod test {
use crate::proof_of_work::difficulty::{CheckedAdd, Difficulty};
use crate::{
proof_of_work::{
difficulty::{CheckedAdd, CheckedSub, MIN_DIFFICULTY},
Difficulty,
},
U256,
};

#[test]
fn add_difficulty() {
Expand All @@ -347,7 +187,7 @@ mod test {
);
assert_eq!(
Difficulty::default().checked_add(42).unwrap(),
Difficulty::from_u64(43).unwrap()
Difficulty::from_u64(MIN_DIFFICULTY + 42).unwrap()
);
assert_eq!(
Difficulty::from_u64(15).unwrap().checked_add(5).unwrap(),
Expand All @@ -360,4 +200,89 @@ mod test {
let d = Difficulty::from_u64(1_000_000).unwrap();
assert_eq!("1,000,000", format!("{}", d));
}

#[test]
fn difficulty_converts_correctly_at_its_limits() {
for d in 0..=MIN_DIFFICULTY + 1 {
if d < MIN_DIFFICULTY {
assert!(Difficulty::from_u64(d).is_err());
} else {
assert!(Difficulty::from_u64(d).is_ok());
}
}
assert_eq!(Difficulty::min().as_u64(), MIN_DIFFICULTY);
assert_eq!(Difficulty::max().as_u64(), u64::MAX);
}

#[test]
fn addition_does_not_overflow() {
let d1 = Difficulty::from_u64(100).unwrap();
assert!(d1.checked_add(1).is_some());
let d2 = Difficulty::max();
assert!(d2.checked_add(1).is_none());
}

#[test]
fn subtraction_does_not_underflow() {
let d1 = Difficulty::from_u64(100).unwrap();
assert!(d1.checked_sub(1).is_some());
let d2 = Difficulty::max();
assert!(d1.checked_sub(d2).is_none());
}

#[test]
fn be_high_target() {
let target: &[u8] = &[
0xff, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
];
let expected = Difficulty::min();
assert_eq!(Difficulty::big_endian_difficulty(target).unwrap(), expected);
}

#[test]
fn be_max_difficulty() {
let target = U256::MAX / U256::from(u64::MAX);
let mut bytes = [0u8; 32];
target.to_big_endian(&mut bytes);
assert_eq!(Difficulty::big_endian_difficulty(&bytes).unwrap(), Difficulty::max());
}

#[test]
fn be_stop_overflow() {
let target: u64 = 64;
let expected = u64::MAX;
assert_eq!(
Difficulty::big_endian_difficulty(&target.to_be_bytes()).unwrap(),
Difficulty::from_u64(expected).unwrap()
);
}

#[test]
fn le_high_target() {
let target: &[u8] = &[
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0xff,
];
let expected = Difficulty::min();
assert_eq!(Difficulty::little_endian_difficulty(target).unwrap(), expected);
}

#[test]
fn le_max_difficulty() {
let target = U256::MAX / U256::from(u64::MAX);
let mut bytes = [0u8; 32];
target.to_little_endian(&mut bytes);
assert_eq!(Difficulty::little_endian_difficulty(&bytes).unwrap(), Difficulty::max());
}

#[test]
fn le_stop_overflow() {
let target: u64 = 64;
let expected = u64::MAX;
assert_eq!(
Difficulty::little_endian_difficulty(&target.to_be_bytes()).unwrap(),
Difficulty::from_u64(expected).unwrap()
);
}
}
Loading

0 comments on commit b95112d

Please sign in to comment.