diff --git a/crates/bitcell-node/src/monitoring/mod.rs b/crates/bitcell-node/src/monitoring/mod.rs index 09d6532..97376ee 100644 --- a/crates/bitcell-node/src/monitoring/mod.rs +++ b/crates/bitcell-node/src/monitoring/mod.rs @@ -19,6 +19,8 @@ pub struct MetricsRegistry { peer_count: Arc, bytes_sent: Arc, bytes_received: Arc, + messages_sent: Arc, + messages_received: Arc, // Transaction pool metrics pending_txs: Arc, @@ -33,8 +35,8 @@ pub struct MetricsRegistry { // EBSL metrics active_miners: Arc, banned_miners: Arc, - #[allow(dead_code)] avg_trust_score: Arc, // Stored as fixed-point * 1000 + slashing_events: Arc, // DHT metrics dht_peer_count: Arc, @@ -48,6 +50,8 @@ impl MetricsRegistry { peer_count: Arc::new(AtomicUsize::new(0)), bytes_sent: Arc::new(AtomicU64::new(0)), bytes_received: Arc::new(AtomicU64::new(0)), + messages_sent: Arc::new(AtomicU64::new(0)), + messages_received: Arc::new(AtomicU64::new(0)), pending_txs: Arc::new(AtomicUsize::new(0)), total_txs_processed: Arc::new(AtomicU64::new(0)), proofs_generated: Arc::new(AtomicU64::new(0)), @@ -57,6 +61,7 @@ impl MetricsRegistry { active_miners: Arc::new(AtomicUsize::new(0)), banned_miners: Arc::new(AtomicUsize::new(0)), avg_trust_score: Arc::new(AtomicU64::new(0)), + slashing_events: Arc::new(AtomicU64::new(0)), dht_peer_count: Arc::new(AtomicUsize::new(0)), } } @@ -103,6 +108,22 @@ impl MetricsRegistry { self.bytes_received.load(Ordering::Relaxed) } + pub fn add_message_sent(&self) { + self.messages_sent.fetch_add(1, Ordering::Relaxed); + } + + pub fn add_message_received(&self) { + self.messages_received.fetch_add(1, Ordering::Relaxed); + } + + pub fn get_messages_sent(&self) -> u64 { + self.messages_sent.load(Ordering::Relaxed) + } + + pub fn get_messages_received(&self) -> u64 { + self.messages_received.load(Ordering::Relaxed) + } + // Transaction pool metrics pub fn set_pending_txs(&self, count: usize) { self.pending_txs.store(count, Ordering::Relaxed); @@ -162,6 +183,28 @@ impl MetricsRegistry { self.banned_miners.load(Ordering::Relaxed) } + pub fn set_average_trust_score(&self, score: f64) { + // Store as fixed-point * 1000 for atomic operations + // Trust scores are typically in range [0.0, 1.0], so this provides + // 3 decimal places of precision without overflow risk + let clamped_score = score.clamp(0.0, 1.0); + let fixed_point = (clamped_score * 1000.0) as u64; + self.avg_trust_score.store(fixed_point, Ordering::Relaxed); + } + + pub fn get_average_trust_score(&self) -> f64 { + let fixed_point = self.avg_trust_score.load(Ordering::Relaxed); + fixed_point as f64 / 1000.0 + } + + pub fn inc_slashing_events(&self) { + self.slashing_events.fetch_add(1, Ordering::Relaxed); + } + + pub fn get_slashing_events(&self) -> u64 { + self.slashing_events.load(Ordering::Relaxed) + } + // DHT metrics pub fn set_dht_peer_count(&self, count: usize) { self.dht_peer_count.store(count, Ordering::Relaxed); @@ -198,6 +241,14 @@ impl MetricsRegistry { # TYPE bitcell_bytes_received_total counter\n\ bitcell_bytes_received_total {}\n\ \n\ + # HELP bitcell_messages_sent_total Total messages sent\n\ + # TYPE bitcell_messages_sent_total counter\n\ + bitcell_messages_sent_total {}\n\ + \n\ + # HELP bitcell_messages_received_total Total messages received\n\ + # TYPE bitcell_messages_received_total counter\n\ + bitcell_messages_received_total {}\n\ + \n\ # HELP bitcell_pending_txs Number of pending transactions\n\ # TYPE bitcell_pending_txs gauge\n\ bitcell_pending_txs {}\n\ @@ -220,19 +271,31 @@ impl MetricsRegistry { \n\ # HELP bitcell_banned_miners Number of banned miners\n\ # TYPE bitcell_banned_miners gauge\n\ - bitcell_banned_miners {}\n", + bitcell_banned_miners {}\n\ + \n\ + # HELP bitcell_average_trust_score Average trust score of miners\n\ + # TYPE bitcell_average_trust_score gauge\n\ + bitcell_average_trust_score {}\n\ + \n\ + # HELP bitcell_slashing_events_total Total slashing events\n\ + # TYPE bitcell_slashing_events_total counter\n\ + bitcell_slashing_events_total {}\n", self.get_chain_height(), self.get_sync_progress(), self.get_peer_count(), self.get_dht_peer_count(), self.get_bytes_sent(), self.get_bytes_received(), + self.get_messages_sent(), + self.get_messages_received(), self.get_pending_txs(), self.get_total_txs_processed(), self.get_proofs_generated(), self.get_proofs_verified(), self.get_active_miners(), self.get_banned_miners(), + self.get_average_trust_score(), + self.get_slashing_events(), ) } } @@ -270,4 +333,50 @@ mod tests { assert!(export.contains("bitcell_chain_height 42")); assert!(export.contains("bitcell_peer_count 3")); } + + #[test] + fn test_new_metrics() { + let metrics = MetricsRegistry::new(); + + // Test message counters + metrics.add_message_sent(); + metrics.add_message_sent(); + metrics.add_message_sent(); + assert_eq!(metrics.get_messages_sent(), 3); + + metrics.add_message_received(); + assert_eq!(metrics.get_messages_received(), 1); + + // Test trust score + metrics.set_average_trust_score(0.85); + assert!((metrics.get_average_trust_score() - 0.85).abs() < 0.001); + + metrics.set_average_trust_score(0.923); + assert!((metrics.get_average_trust_score() - 0.923).abs() < 0.001); + + // Test slashing events + metrics.inc_slashing_events(); + metrics.inc_slashing_events(); + assert_eq!(metrics.get_slashing_events(), 2); + } + + #[test] + fn test_new_metrics_in_prometheus_export() { + let metrics = MetricsRegistry::new(); + + // Set new metrics + metrics.add_message_sent(); + metrics.add_message_sent(); + metrics.add_message_received(); + metrics.set_average_trust_score(0.875); + metrics.inc_slashing_events(); + + let export = metrics.export_prometheus(); + + // Verify new metrics are in export + assert!(export.contains("bitcell_messages_sent_total 2")); + assert!(export.contains("bitcell_messages_received_total 1")); + assert!(export.contains("bitcell_average_trust_score 0.875")); + assert!(export.contains("bitcell_slashing_events_total 1")); + } } diff --git a/crates/bitcell-node/src/network.rs b/crates/bitcell-node/src/network.rs index 7460b5d..d484ade 100644 --- a/crates/bitcell-node/src/network.rs +++ b/crates/bitcell-node/src/network.rs @@ -584,6 +584,10 @@ impl NetworkManager { } self.metrics.add_bytes_sent(block_size * peer_ids.len() as u64); + // Update message counter for each peer we sent to + for _ in &peer_ids { + self.metrics.add_message_sent(); + } // Broadcast via Gossipsub let dht_opt = { @@ -618,6 +622,10 @@ impl NetworkManager { } self.metrics.add_bytes_sent(tx_size * peer_ids.len() as u64); + // Update message counter for each peer we sent to + for _ in &peer_ids { + self.metrics.add_message_sent(); + } // Broadcast via Gossipsub let dht_opt = { @@ -648,6 +656,7 @@ impl NetworkManager { pub async fn handle_incoming_block(&self, block: Block) -> Result<()> { let block_size = bincode::serialize(&block).unwrap_or_default().len() as u64; self.metrics.add_bytes_received(block_size); + self.metrics.add_message_received(); // Forward to block processing channel let tx_opt = { @@ -665,6 +674,7 @@ impl NetworkManager { pub async fn handle_incoming_transaction(&self, tx: Transaction) -> Result<()> { let tx_size = bincode::serialize(&tx).unwrap_or_default().len() as u64; self.metrics.add_bytes_received(tx_size); + self.metrics.add_message_received(); // Forward to transaction processing channel let sender_opt = { diff --git a/crates/bitcell-node/src/tournament.rs b/crates/bitcell-node/src/tournament.rs index 0c327a0..5969804 100644 --- a/crates/bitcell-node/src/tournament.rs +++ b/crates/bitcell-node/src/tournament.rs @@ -15,6 +15,9 @@ const COMMIT_PHASE_SECS: u64 = 5; const REVEAL_PHASE_SECS: u64 = 5; const BATTLE_PHASE_SECS: u64 = 5; +/// Default trust score for new miners or when no miners exist +const DEFAULT_TRUST_SCORE: f64 = 0.85; + /// Tournament manager pub struct TournamentManager { /// Current tournament @@ -164,6 +167,11 @@ impl TournamentManager { // Add evidence with current block height let height = *self.current_height.read().unwrap(); counters.add_evidence(bitcell_ebsl::Evidence::new(evidence_type, 0, height)); + + // Track slashing events (negative evidence) + if evidence_type.is_negative() { + self.metrics.inc_slashing_events(); + } } // Drop write lock here // Update metrics (acquires read lock) @@ -208,19 +216,34 @@ impl TournamentManager { let mut active_count = 0; let mut banned_count = 0; + let mut total_trust_score = 0.0; + let mut miner_count = 0; for (_miner, counters) in evidence_map.iter() { let trust = TrustScore::from_evidence(counters, &self.ebsl_params); + let trust_value = trust.value(); + + total_trust_score += trust_value; + miner_count += 1; if trust.is_eligible(&self.ebsl_params) { active_count += 1; - } else if trust.value() < self.ebsl_params.t_kill { + } else if trust_value < self.ebsl_params.t_kill { banned_count += 1; } } self.metrics.set_active_miners(active_count); self.metrics.set_banned_miners(banned_count); + + // Calculate average trust score + if miner_count > 0 { + let avg_trust = total_trust_score / miner_count as f64; + self.metrics.set_average_trust_score(avg_trust); + } else { + // Use default trust score when no miners + self.metrics.set_average_trust_score(DEFAULT_TRUST_SCORE); + } } }