use once_cell::sync::Lazy;
use primitive_types::U512;
use tari_common_types::types::FixedHash;
use tari_metrics::{Gauge, IntCounter, IntCounterVec, IntGauge, IntGaugeVec};
use tari_utilities::hex::Hex;
pub fn tip_height() -> &'static IntGauge {
static METER: Lazy<IntGauge> = Lazy::new(|| {
tari_metrics::register_int_gauge("base_node::blockchain::tip_height", "The current tip height").unwrap()
});
&METER
}
pub fn target_difficulty_sha() -> &'static IntGauge {
static METER: Lazy<IntGauge> = Lazy::new(|| {
tari_metrics::register_int_gauge(
"base_node::blockchain::target_difficulty_sha",
"The current miner target difficulty for the sha3 PoW algo",
)
.unwrap()
});
&METER
}
pub fn target_difficulty_monero_randomx() -> &'static IntGauge {
static METER: Lazy<IntGauge> = Lazy::new(|| {
tari_metrics::register_int_gauge(
"base_node::blockchain::target_difficulty_monero",
"The current miner target difficulty for the monero PoW algo",
)
.unwrap()
});
&METER
}
pub fn target_difficulty_tari_randomx() -> &'static IntGauge {
static METER: Lazy<IntGauge> = Lazy::new(|| {
tari_metrics::register_int_gauge(
"base_node::blockchain::target_difficulty_tari_rx",
"The current miner target difficulty for the tari rx PoW algo",
)
.unwrap()
});
&METER
}
pub fn target_difficulty_cuckaroo() -> &'static IntGauge {
static METER: Lazy<IntGauge> = Lazy::new(|| {
tari_metrics::register_int_gauge(
"base_node::blockchain::target_difficulty_cuckaroo",
"The current miner target difficulty for the cuckaroo PoW algo",
)
.unwrap()
});
&METER
}
pub fn target_difficulty() -> &'static IntGauge {
static METER: Lazy<IntGauge> = Lazy::new(|| {
tari_metrics::register_int_gauge("base_node::blockchain::target_diff", "target_difficulty at height").unwrap()
});
&METER
}
pub fn accumulated_difficulty_indicator() -> &'static IntGauge {
static METER: Lazy<IntGauge> = Lazy::new(|| {
tari_metrics::register_int_gauge(
"base_node::blockchain::acc_diff_indicator",
"log2(accumulated_difficulty at height) * 1000",
)
.unwrap()
});
&METER
}
pub fn target_difficulty_indicator() -> &'static IntGauge {
static METER: Lazy<IntGauge> = Lazy::new(|| {
tari_metrics::register_int_gauge(
"base_node::blockchain::target_diff_indicator",
"log2(target_difficulty at height) * 1000",
)
.unwrap()
});
&METER
}
pub fn difficulty_indicator_height() -> &'static IntGauge {
static METER: Lazy<IntGauge> = Lazy::new(|| {
tari_metrics::register_int_gauge(
"base_node::blockchain::diff_indicator_height",
"block height associated with difficulty indicators",
)
.unwrap()
});
&METER
}
pub fn accumulated_difficulty_exp2() -> &'static IntGauge {
static METER: Lazy<IntGauge> = Lazy::new(|| {
tari_metrics::register_int_gauge(
"base_node::blockchain::acc_diff_exp2",
"floor(log2(total_accumulated_difficulty)) at height [reconstruction: (acc_diff_sig53 / 2^52) * \
2^acc_diff_exp2]",
)
.unwrap()
});
&METER
}
pub fn accumulated_difficulty_sig53() -> &'static IntGauge {
static METER: Lazy<IntGauge> = Lazy::new(|| {
tari_metrics::register_int_gauge(
"base_node::blockchain::acc_diff_sig53",
"Top 53 bits of total_accumulated_difficulty at height [reconstruction: (acc_diff_sig53 / 2^52) * \
2^acc_diff_exp2]",
)
.unwrap()
});
&METER
}
pub fn accumulated_difficulty_as_f64() -> &'static Gauge {
static METER: Lazy<Gauge> = Lazy::new(|| {
tari_metrics::register_gauge(
"base_node::blockchain::acc_diff_as_f64",
"Approximate total_accumulated_difficulty at height as an f64 [approximation: (acc_diff_sig53 / 2^52) * \
2^acc_diff_exp2]",
)
.unwrap()
});
&METER
}
pub fn reorg(fork_height: u64, num_added: usize, num_removed: usize) -> IntGauge {
static METER: Lazy<IntGaugeVec> = Lazy::new(|| {
tari_metrics::register_int_gauge_vec("base_node::blockchain::reorgs", "Reorg stats", &[
"fork_height",
"num_added",
"num_removed",
])
.unwrap()
});
METER.with_label_values(&[
&fork_height.to_string(),
&num_added.to_string(),
&num_removed.to_string(),
])
}
pub fn reorg_blocks_added() -> &'static IntGauge {
static METER: Lazy<IntGauge> = Lazy::new(|| {
tari_metrics::register_int_gauge(
"base_node::blockchain::reorg_blocks_added_total",
"Total number of blocks added due to chain reorgs",
)
.unwrap()
});
&METER
}
pub fn reorg_blocks_removed() -> &'static IntGauge {
static METER: Lazy<IntGauge> = Lazy::new(|| {
tari_metrics::register_int_gauge(
"base_node::blockchain::reorg_blocks_removed_total",
"Total number of blocks removed due to chain reorgs",
)
.unwrap()
});
&METER
}
pub fn compact_block_tx_misses(height: u64) -> IntGauge {
static METER: Lazy<IntGaugeVec> = Lazy::new(|| {
tari_metrics::register_int_gauge_vec(
"base_node::blockchain::compact_block_unknown_transactions",
"Number of unknown transactions from the incoming compact block",
&["height"],
)
.unwrap()
});
METER.with_label_values(&[&height.to_string()])
}
pub fn compact_block_full_misses(height: u64) -> IntCounter {
static METER: Lazy<IntCounterVec> = Lazy::new(|| {
tari_metrics::register_int_counter_vec(
"base_node::blockchain::compact_block_miss",
"Number of full blocks that had to be requested",
&["height"],
)
.unwrap()
});
METER.with_label_values(&[&height.to_string()])
}
pub fn compact_block_mmr_mismatch(height: u64) -> IntCounter {
static METER: Lazy<IntCounterVec> = Lazy::new(|| {
tari_metrics::register_int_counter_vec(
"base_node::blockchain::compact_block_mmr_mismatch",
"Number of full blocks that had to be requested because of MMR mismatch",
&["height"],
)
.unwrap()
});
METER.with_label_values(&[&height.to_string()])
}
pub fn orphaned_blocks() -> IntCounter {
static METER: Lazy<IntCounter> = Lazy::new(|| {
tari_metrics::register_int_counter(
"base_node::blockchain::orphaned_blocks",
"Number of valid orphan blocks accepted by the base node",
)
.unwrap()
});
METER.clone()
}
pub fn rejected_blocks(height: u64, hash: &FixedHash) -> IntCounter {
static METER: Lazy<IntCounterVec> = Lazy::new(|| {
tari_metrics::register_int_counter_vec(
"base_node::blockchain::rejected_blocks",
"Number of block rejected by the base node",
&["height", "block_hash"],
)
.unwrap()
});
METER.with_label_values(&[&height.to_string(), &hash.to_hex()])
}
pub fn active_sync_peers() -> &'static IntGauge {
static METER: Lazy<IntGauge> = Lazy::new(|| {
tari_metrics::register_int_gauge(
"base_node::sync::active_peers",
"Number of active peers syncing from this node",
)
.unwrap()
});
&METER
}
pub fn utxo_set_size() -> &'static IntGauge {
static METER: Lazy<IntGauge> = Lazy::new(|| {
tari_metrics::register_int_gauge(
"base_node::blockchain::utxo_set_size",
"The number of UTXOs in the current UTXO set",
)
.unwrap()
});
&METER
}
const INV_2P52: f64 = 1.0 / ((1u64 << 52) as f64);
#[allow(clippy::cast_possible_truncation)]
pub fn log2_u512(value_u512: &U512) -> Option<f64> {
if value_u512.is_zero() {
return None;
}
let (total_bits, sig53) = u512_into_parts(value_u512);
let x = (sig53 as f64) * INV_2P52;
let frac = x.log2();
let mut res = (f64::from(total_bits) - 1.0) + frac;
if sig53 > (1u64 << 52) {
res = next_down(res);
}
Some(res)
}
#[allow(clippy::cast_possible_truncation)]
fn u512_into_parts(value_u512: &U512) -> (u32, u64) {
let total_bits: u32 = value_u512.bits() as u32;
let sig53: u64 = if total_bits > 53 {
(value_u512 >> (total_bits - 53)).as_u64()
} else {
(value_u512 << (53 - total_bits)).as_u64()
};
debug_assert!(((1u64 << 52)..(1u64 << 53)).contains(&sig53));
(total_bits, sig53)
}
#[allow(clippy::cast_possible_truncation)]
pub fn u512_exp2_sig53(value: &U512) -> Option<(i64, i64)> {
if value.is_zero() {
return None;
}
let (total_bits, sig53) = u512_into_parts(value);
Some((i64::from(total_bits) - 1, i64::try_from(sig53).unwrap_or(i64::MAX)))
}
#[allow(clippy::cast_possible_truncation)]
pub fn approximate_u512_with_f64(value: &U512) -> Option<f64> {
if value.is_zero() {
return None;
}
let (exp2, sig53) = u512_exp2_sig53(value).unwrap();
const TWO_P52: f64 = 4503599627370496.0; let two_pow_exp2 = f64::from_bits(((exp2 + 1023) as u64) << 52);
Some((sig53 as f64 / TWO_P52) * two_pow_exp2)
}
#[inline]
fn next_down(x: f64) -> f64 {
f64::from_bits(x.to_bits().saturating_sub(1))
}
#[inline]
#[allow(clippy::cast_possible_truncation)]
pub fn log2_u128(value_u128: u128) -> Option<f64> {
if value_u128 == 0 {
return None;
}
let total_bits: u32 = 128 - value_u128.leading_zeros();
let sig53: u64 = if total_bits > 53 {
(value_u128 >> (total_bits - 53)) as u64
} else {
(value_u128 << (53 - total_bits)) as u64
};
debug_assert!(((1u64 << 52)..(1u64 << 53)).contains(&sig53));
let x = (sig53 as f64) * INV_2P52;
let frac = x.log2();
let mut res = (f64::from(total_bits) - 1.0) + frac;
if sig53 > (1u64 << 52) {
res = next_down(res);
}
Some(res)
}
#[inline]
#[allow(clippy::cast_possible_truncation)]
pub fn milli_bits(x: f64) -> i64 {
(x * 1000.0).round() as i64
}
#[cfg(test)]
mod tests {
use primitive_types::U512;
use super::*;
#[test]
fn test_log2_u512() {
assert_eq!(log2_u512(&U512::from(1u64)), Some(0.0));
assert_eq!(log2_u512(&(U512::from(2u64))), Some(1.0));
assert_eq!(log2_u512(&(U512::from(8u64))), Some(3.0));
assert_eq!(log2_u512(&(U512::from(1024u64))), Some(10.0));
let value = U512::from(1u64) << 200;
assert_eq!(log2_u512(&value), Some(200.0));
let value = (U512::from(1u64) << 200) - U512::from(1u64);
assert!(log2_u512(&value).unwrap() < 200.0);
assert!(log2_u512(&U512::from(u128::MAX)).unwrap() < 128.0);
let u512_max = U512::MAX;
let log2_u512_max = log2_u512(&u512_max).unwrap();
assert!(log2_u512_max < 512.0);
assert!(log2_u512_max > 511.0);
assert!(log2_u512(&U512::from(0u64)).is_none());
}
#[test]
fn test_log2_u128() {
assert_eq!(log2_u128(1), Some(0.0));
assert_eq!(log2_u128(2), Some(1.0));
assert_eq!(log2_u128(8), Some(3.0));
assert_eq!(log2_u128(1024), Some(10.0));
let value = 1u128 << 100;
assert_eq!(log2_u128(value), Some(100.0));
let value = (1u128 << 100) - 1;
assert!(log2_u128(value).unwrap() < 100.0);
assert!(log2_u128(u128::MAX).unwrap() < 128.0);
assert!(log2_u128(0).is_none());
}
#[test]
fn millibit_correctly_handles_small_difficulty_growth() {
let acc_diff_v1 = U512::from_dec_str("3872628503165662556508806093911347954645375156922").unwrap();
let target_diff_v1: u128 = 33_208_643_413_617_919;
let acc_diff_v1_mbits = milli_bits(log2_u512(&acc_diff_v1).unwrap());
let target_diff_v1_mbits = milli_bits(log2_u128(target_diff_v1).unwrap());
let acc_diff_v2 = acc_diff_v1 + acc_diff_v1 / U512::from(667u64);
let target_diff_v2 = target_diff_v1 + (target_diff_v1 / 667);
let acc_diff_v2_mbits = milli_bits(log2_u512(&acc_diff_v2).unwrap());
let target_diff_v2_mbits = milli_bits(log2_u128(target_diff_v2).unwrap());
assert!(acc_diff_v2_mbits > acc_diff_v1_mbits);
assert!(target_diff_v2_mbits > target_diff_v1_mbits);
}
#[test]
fn exp2_sig53_power_of_two_and_neighbors() {
let v = U512::from(1u64) << 200;
let (e, s) = u512_exp2_sig53(&v).unwrap();
assert_eq!(e, 200);
assert_eq!(s, 1i64 << 52, "exact power of two => normalized sig53 == 2^52");
let v = (U512::from(1u64) << 200) - U512::from(1u64);
let (e, s) = u512_exp2_sig53(&v).unwrap();
assert_eq!(e, 199, "floor(log2(2^200-1)) = 199");
assert!(((1i64 << 52)..(1i64 << 53)).contains(&s));
let v = (U512::from(1u64) << 200) + U512::from(1u64);
let (e, s) = u512_exp2_sig53(&v).unwrap();
assert_eq!(e, 200);
assert!(((1i64 << 52)..(1i64 << 53)).contains(&s));
}
#[test]
fn it_can_reconstruct_u512_from_exp2_sig53_parts_as_f64() {
use primitive_types::U512;
let original_u512 = U512::from_dec_str("3872628503165662556508806093911347954645375156922").unwrap();
let approximate_u512 = approximate_u512_with_f64(&original_u512).unwrap();
let expected_bits = log2_u512(&original_u512).unwrap();
let approx_bits = approximate_u512.log2();
assert!(
(approx_bits - expected_bits).abs() < 1e-9,
"reconstructed log2 mismatch: approx={}, expected={}",
approx_bits,
expected_bits
);
let relative_err = (approximate_u512.log2() - log2_u512(&original_u512).unwrap()).abs() /
log2_u512(&original_u512).unwrap().abs();
assert!(relative_err < 1e-12);
}
}