use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
use log::{debug, error, info, warn};
use lazy_static::lazy_static;
use serde::{Deserialize, Serialize};
use crate::core::metrics::PrometheusMetrics;
use crate::monitoring::metrics;
lazy_static! {
static ref BLOCKCHAIN_METRICS: Mutex<BlockchainMetrics> = Mutex::new(BlockchainMetrics::new());
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TimeSample {
pub timestamp: u64,
pub value: f64,
}
#[derive(Debug)]
pub struct BlockchainMetrics {
last_update: Instant,
metrics: PrometheusMetrics,
segwit_samples: Vec<TimeSample>,
taproot_samples: Vec<TimeSample>,
block_propagation_times: Vec<TimeSample>,
segwit_percentage: f64,
taproot_percentage: f64,
utxo_set_size: u64,
avg_fee_rate: f64,
error_rates: HashMap<String, f64>,
mempool_size: u64,
avg_block_size: u64,
block_height: u64,
bip_compliance: HashMap<String, bool>,
network_hashrate: f64,
}
impl BlockchainMetrics {
pub fn new() -> Self {
Self {
last_update: Instant::now(),
metrics: PrometheusMetrics::new(),
segwit_samples: Vec::new(),
taproot_samples: Vec::new(),
block_propagation_times: Vec::new(),
segwit_percentage: 0.0,
taproot_percentage: 0.0,
utxo_set_size: 0,
avg_fee_rate: 0.0,
error_rates: HashMap::new(),
mempool_size: 0,
avg_block_size: 0,
block_height: 0,
bip_compliance: HashMap::new(),
network_hashrate: 0.0,
}
}
pub fn update_segwit_percentage(&mut self, percentage: f64) {
self.segwit_percentage = percentage;
self.metrics.set_gauge("segwit_adoption_percentage", percentage);
let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs();
self.segwit_samples.push(TimeSample { timestamp: now, value: percentage });
if self.segwit_samples.len() > 144 {
self.segwit_samples.remove(0);
}
metrics::register_taproot_usage(percentage);
}
pub fn update_taproot_percentage(&mut self, percentage: f64) {
self.taproot_percentage = percentage;
self.metrics.set_gauge("taproot_adoption_percentage", percentage);
let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs();
self.taproot_samples.push(TimeSample { timestamp: now, value: percentage });
if self.taproot_samples.len() > 144 {
self.taproot_samples.remove(0);
}
}
pub fn update_utxo_set_size(&mut self, size: u64) {
self.utxo_set_size = size;
self.metrics.set_gauge("utxo_set_size", size as f64);
}
pub fn update_avg_fee_rate(&mut self, sats_per_vb: f64) {
self.avg_fee_rate = sats_per_vb;
self.metrics.set_gauge("avg_fee_rate_sats_per_vb", sats_per_vb);
}
pub fn update_error_rate(&mut self, category: &str, rate: f64) {
self.error_rates.insert(category.to_string(), rate);
self.metrics.set_gauge(&format!("error_rate_{}", category), rate);
}
pub fn update_mempool_size(&mut self, size_bytes: u64) {
self.mempool_size = size_bytes;
self.metrics.set_gauge("mempool_size_bytes", size_bytes as f64);
metrics::register_mempool_size((size_bytes / 1024) as usize); }
pub fn update_avg_block_size(&mut self, size_bytes: u64) {
self.avg_block_size = size_bytes;
self.metrics.set_gauge("avg_block_size_bytes", size_bytes as f64);
}
pub fn update_block_height(&mut self, height: u64) {
self.block_height = height;
self.metrics.set_gauge("block_height", height as f64);
}
pub fn update_network_hashrate(&mut self, eh_per_second: f64) {
self.network_hashrate = eh_per_second;
self.metrics.set_gauge("network_hashrate_eh_per_s", eh_per_second);
}
pub fn update_block_propagation_time(&mut self, block_hash: &str, milliseconds: u64) {
self.metrics.set_gauge("block_propagation_ms", milliseconds as f64);
let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs();
self.block_propagation_times.push(TimeSample { timestamp: now, value: milliseconds as f64 });
if self.block_propagation_times.len() > 100 {
self.block_propagation_times.remove(0);
}
metrics::register_block_propagation_time(milliseconds);
self.metrics.increment_counter("blocks_received", "hash", block_hash);
}
pub fn set_bip_compliance(&mut self, bip_number: &str, compliant: bool) {
self.bip_compliance.insert(bip_number.to_string(), compliant);
metrics::register_bip_compliance(bip_number, compliant);
}
pub fn as_json(&self) -> serde_json::Value {
let mut result = serde_json::json!({
"last_update_seconds_ago": self.last_update.elapsed().as_secs(),
"segwit_percentage": self.segwit_percentage,
"taproot_percentage": self.taproot_percentage,
"utxo_set_size": self.utxo_set_size,
"avg_fee_rate": self.avg_fee_rate,
"mempool_size": self.mempool_size,
"avg_block_size": self.avg_block_size,
"block_height": self.block_height,
"network_hashrate": self.network_hashrate,
});
let mut error_rates = serde_json::Map::new();
for (category, rate) in &self.error_rates {
error_rates.insert(category.clone(), serde_json::json!(rate));
}
result["error_rates"] = serde_json::json!(error_rates);
let mut bip_compliance = serde_json::Map::new();
for (bip, compliant) in &self.bip_compliance {
bip_compliance.insert(bip.clone(), serde_json::json!(compliant));
}
result["bip_compliance"] = serde_json::json!(bip_compliance);
result
}
pub fn get_historical_data(&self, metric_name: &str) -> Option<Vec<TimeSample>> {
match metric_name {
"segwit_percentage" => Some(self.segwit_samples.clone()),
"taproot_percentage" => Some(self.taproot_samples.clone()),
"block_propagation_times" => Some(self.block_propagation_times.clone()),
_ => None,
}
}
}
pub fn update_segwit_percentage(percentage: f64) {
let mut metrics = BLOCKCHAIN_METRICS.lock().unwrap();
metrics.update_segwit_percentage(percentage);
debug!("Updated SegWit adoption percentage to {:.2}%", percentage);
}
pub fn update_taproot_percentage(percentage: f64) {
let mut metrics = BLOCKCHAIN_METRICS.lock().unwrap();
metrics.update_taproot_percentage(percentage);
debug!("Updated Taproot adoption percentage to {:.2}%", percentage);
}
pub fn update_utxo_set_size(size: u64) {
let mut metrics = BLOCKCHAIN_METRICS.lock().unwrap();
metrics.update_utxo_set_size(size);
debug!("Updated UTXO set size to {}", size);
}
pub fn update_avg_fee_rate(sats_per_vb: f64) {
let mut metrics = BLOCKCHAIN_METRICS.lock().unwrap();
metrics.update_avg_fee_rate(sats_per_vb);
debug!("Updated average fee rate to {:.2} sats/vB", sats_per_vb);
}
pub fn update_error_rate(category: &str, rate: f64) {
let mut metrics = BLOCKCHAIN_METRICS.lock().unwrap();
metrics.update_error_rate(category, rate);
if rate > 0.05 { warn!("High error rate in {}: {:.2}%", category, rate * 100.0);
}
}
pub fn update_mempool_size(size_bytes: u64) {
let mut metrics = BLOCKCHAIN_METRICS.lock().unwrap();
metrics.update_mempool_size(size_bytes);
debug!("Updated mempool size to {} bytes", size_bytes);
}
pub fn update_avg_block_size(size_bytes: u64) {
let mut metrics = BLOCKCHAIN_METRICS.lock().unwrap();
metrics.update_avg_block_size(size_bytes);
debug!("Updated average block size to {} bytes", size_bytes);
}
pub fn update_block_height(height: u64) {
let mut metrics = BLOCKCHAIN_METRICS.lock().unwrap();
metrics.update_block_height(height);
debug!("Updated block height to {}", height);
}
pub fn update_network_hashrate(eh_per_second: f64) {
let mut metrics = BLOCKCHAIN_METRICS.lock().unwrap();
metrics.update_network_hashrate(eh_per_second);
debug!("Updated network hashrate to {:.2} EH/s", eh_per_second);
}
pub fn update_block_propagation_time(block_hash: &str, milliseconds: u64) {
let mut metrics = BLOCKCHAIN_METRICS.lock().unwrap();
metrics.update_block_propagation_time(block_hash, milliseconds);
debug!("Block {} propagation time: {}ms", block_hash, milliseconds);
}
pub fn set_bip_compliance(bip_number: &str, compliant: bool) {
let mut metrics = BLOCKCHAIN_METRICS.lock().unwrap();
metrics.set_bip_compliance(bip_number, compliant);
debug!("BIP-{} compliance status: {}", bip_number, compliant);
}
pub fn get_metrics_json() -> serde_json::Value {
let metrics = BLOCKCHAIN_METRICS.lock().unwrap();
metrics.as_json()
}
pub fn get_historical_data(metric_name: &str) -> Option<Vec<TimeSample>> {
let metrics = BLOCKCHAIN_METRICS.lock().unwrap();
metrics.get_historical_data(metric_name)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_blockchain_metrics() {
update_segwit_percentage(85.2);
update_taproot_percentage(12.8);
update_utxo_set_size(82_564_432);
update_avg_fee_rate(25.5);
update_error_rate("connection_failure", 0.02);
update_mempool_size(15_000_000);
update_avg_block_size(1_250_000);
update_block_height(750_432);
update_network_hashrate(305.7);
update_block_propagation_time("000000000000000000035c5f8c0294672f8456bc", 235);
set_bip_compliance("341", true);
let json = get_metrics_json();
assert_eq!(json["segwit_percentage"], 85.2);
assert_eq!(json["taproot_percentage"], 12.8);
assert_eq!(json["utxo_set_size"], 82_564_432);
assert_eq!(json["avg_fee_rate"], 25.5);
assert_eq!(json["error_rates"]["connection_failure"], 0.02);
assert_eq!(json["mempool_size"], 15_000_000);
assert_eq!(json["avg_block_size"], 1_250_000);
assert_eq!(json["block_height"], 750_432);
assert_eq!(json["network_hashrate"], 305.7);
assert_eq!(json["bip_compliance"]["341"], true);
}
}