extern crate alloc;
use alloc::string::String;
use binary_sv2::U256Owned;
use bitcoin::{hash_types::BlockHash, hashes::Hash, Target};
use core::{fmt::Write, ops::Div};
use primitive_types::U256 as U256Primitive;
pub fn u256_to_block_hash(v: U256Owned) -> BlockHash {
let hash = v.into_array();
let hash = Hash::from_slice(&hash).unwrap();
BlockHash::from_raw_hash(hash)
}
pub fn bytes_to_hex(bytes: &[u8]) -> String {
let mut s = String::with_capacity(bytes.len() * 2);
for &b in bytes {
write!(&mut s, "{b:02x}")
.expect("Writing hex bytes to pre-allocated string should never fail");
}
s
}
pub fn hash_rate_to_target(
hashrate: f64,
share_per_min: f64,
) -> Result<Target, HashRateToTargetError> {
if !hashrate.is_finite() || !share_per_min.is_finite() {
return Err(HashRateToTargetError::NonFiniteInput);
}
if share_per_min == 0.0 {
return Err(HashRateToTargetError::DivisionByZero);
}
if share_per_min.is_sign_negative() {
return Err(HashRateToTargetError::NegativeInput);
};
if hashrate.is_sign_negative() {
return Err(HashRateToTargetError::NegativeInput);
};
let shares_occurrency_frequence = 60_f64 / share_per_min;
let h_times_s = hashrate * shares_occurrency_frequence;
let h_times_s = h_times_s as u128;
let h_times_s_plus_one = h_times_s.saturating_add(1);
let h_times_s_plus_one = from_u128_to_u256(h_times_s_plus_one);
let denominator = h_times_s_plus_one;
let two_to_256_minus_one = [255_u8; 32];
let two_to_256_minus_one = U256Primitive::from_big_endian(two_to_256_minus_one.as_ref());
let mut h_times_s_array = [0u8; 32];
h_times_s_array[16..].copy_from_slice(&h_times_s.to_be_bytes());
let numerator = two_to_256_minus_one - U256Primitive::from_big_endian(h_times_s_array.as_ref());
let mut target_bytes = numerator.div(denominator).to_big_endian();
target_bytes.reverse();
Ok(Target::from_le_bytes(target_bytes))
}
pub fn from_u128_to_u256(input: u128) -> U256Primitive {
let input: [u8; 16] = input.to_be_bytes();
let mut be_bytes = [0_u8; 32];
for (i, b) in input.iter().enumerate() {
be_bytes[16 + i] = *b;
}
U256Primitive::from_big_endian(be_bytes.as_ref())
}
#[derive(Debug)]
#[non_exhaustive]
pub enum HashRateToTargetError {
DivisionByZero,
NegativeInput,
NonFiniteInput,
}
#[derive(Debug)]
pub enum InputError {
NegativeInput,
DivisionByZero,
ArithmeticOverflow,
}
pub fn hash_rate_from_target(target: U256Owned, share_per_min: f64) -> Result<f64, InputError> {
if share_per_min == 0.0 {
return Err(InputError::DivisionByZero);
}
if share_per_min.is_sign_negative() {
return Err(InputError::NegativeInput);
}
let mut target_arr: [u8; 32] = [0; 32];
let slice: &mut [u8] = &mut target_arr;
slice.copy_from_slice(target.as_bytes());
target_arr.reverse();
let target = U256Primitive::from_big_endian(target_arr.as_ref());
let max_target = [255_u8; 32];
let max_target = U256Primitive::from_big_endian(max_target.as_ref());
let target_minus_one = target
.checked_sub(U256Primitive::one())
.ok_or(InputError::ArithmeticOverflow)?;
let numerator = max_target - target_minus_one;
let shares_occurrency_frequence = 60_f64 / (share_per_min) * 100.0;
let shares_occurrency_frequence = shares_occurrency_frequence as u128;
if shares_occurrency_frequence == 0_u128 {
return Err(InputError::DivisionByZero);
}
let shares_occurrency_frequence = from_u128_to_u256(shares_occurrency_frequence);
let target_plus_one = U256Primitive::from_big_endian(target_arr.as_ref())
.checked_add(U256Primitive::one())
.ok_or(InputError::ArithmeticOverflow)?;
let denominator = target_plus_one
.checked_mul(shares_occurrency_frequence)
.and_then(|e| e.checked_div(U256Primitive::from(100)))
.ok_or(InputError::ArithmeticOverflow)?;
let result = numerator.div(denominator).low_u128();
Ok(result as f64)
}
#[cfg(test)]
mod tests {
use super::{
hash_rate_from_target, hash_rate_to_target, HashRateToTargetError, InputError, U256Owned,
};
#[test]
fn zero_target_is_rejected_not_panic() {
let zero: U256Owned = [0u8; 32].into();
assert!(matches!(
hash_rate_from_target(zero, 10.0),
Err(InputError::ArithmeticOverflow)
));
}
#[test]
fn huge_hashrate_does_not_overflow() {
assert!(hash_rate_to_target(f64::MAX, 1.0).is_ok());
}
#[test]
fn non_finite_hashrate_is_rejected() {
for h in [f64::NAN, f64::INFINITY, f64::NEG_INFINITY] {
assert!(matches!(
hash_rate_to_target(h, 1.0),
Err(HashRateToTargetError::NonFiniteInput)
));
}
}
#[test]
fn non_finite_share_per_min_is_rejected() {
for spm in [f64::NAN, f64::INFINITY, f64::NEG_INFINITY] {
assert!(matches!(
hash_rate_to_target(1_000.0, spm),
Err(HashRateToTargetError::NonFiniteInput)
));
}
}
}