stratum_apps/monitoring/
sv1.rs1use std::net::IpAddr;
7
8#[cfg(feature = "asic-rs-telemetry")]
9pub use super::miner_telemetry::MinerTelemetry;
10use serde::{Deserialize, Serialize};
11use utoipa::ToSchema;
12
13#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
15pub struct Sv1ClientInfo {
16 pub client_id: usize,
17 pub channel_id: Option<u32>,
18 #[schema(value_type = String)]
19 pub connection_ip: IpAddr,
20 pub authorized_worker_name: String,
21 pub user_identity: String,
22 pub target_hex: String,
23 pub hashrate: Option<f32>,
24 pub stable_hashrate: bool,
25 pub extranonce1_hex: String,
26 pub extranonce2_len: usize,
27 pub version_rolling_mask: Option<String>,
28 pub version_rolling_min_bit: Option<String>,
29 #[cfg(feature = "asic-rs-telemetry")]
30 pub miner_telemetry: Option<MinerTelemetry>,
31}
32
33#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
35pub struct Sv1ClientsSummary {
36 pub total_clients: usize,
37 pub total_hashrate: f32,
38}
39
40pub trait Sv1ClientsMonitoring: Send + Sync {
42 fn get_sv1_clients(&self) -> Vec<Sv1ClientInfo>;
44
45 fn get_sv1_client_by_id(&self, client_id: usize) -> Option<Sv1ClientInfo> {
50 self.get_sv1_clients()
51 .into_iter()
52 .find(|c| c.client_id == client_id)
53 }
54
55 fn get_sv1_clients_summary(&self) -> Sv1ClientsSummary {
57 let clients = self.get_sv1_clients();
58
59 Sv1ClientsSummary {
60 total_clients: clients.len(),
61 total_hashrate: clients.iter().filter_map(|c| c.hashrate).sum(),
62 }
63 }
64}
65
66#[cfg(test)]
67mod tests {
68 use super::*;
69
70 fn create_sv1_client_info(id: usize, hashrate: Option<f32>) -> Sv1ClientInfo {
71 Sv1ClientInfo {
72 client_id: id,
73 channel_id: Some(id as u32),
74 connection_ip: format!("192.0.2.{}", id)
75 .parse()
76 .expect("test IP address must be valid"),
77 authorized_worker_name: format!("worker-{}", id),
78 user_identity: format!("miner-{}", id),
79 target_hex: "00ff".into(),
80 hashrate,
81 stable_hashrate: false,
82 extranonce1_hex: "aabb".into(),
83 extranonce2_len: 8,
84 version_rolling_mask: Some("ffffffff".into()),
85 version_rolling_min_bit: Some("00000000".into()),
86 #[cfg(feature = "asic-rs-telemetry")]
87 miner_telemetry: None,
88 }
89 }
90
91 struct MockSv1Clients(Vec<Sv1ClientInfo>);
92 impl Sv1ClientsMonitoring for MockSv1Clients {
93 fn get_sv1_clients(&self) -> Vec<Sv1ClientInfo> {
94 self.0.clone()
95 }
96 }
97
98 #[test]
99 fn sv1_get_client_by_id_found() {
100 let monitor = MockSv1Clients(vec![
101 create_sv1_client_info(1, Some(10.0)),
102 create_sv1_client_info(2, Some(20.0)),
103 ]);
104 let found = monitor.get_sv1_client_by_id(2);
105 assert!(found.is_some());
106 assert_eq!(found.unwrap().client_id, 2);
107 }
108
109 #[test]
110 fn sv1_get_client_by_id_not_found() {
111 let monitor = MockSv1Clients(vec![create_sv1_client_info(1, Some(10.0))]);
112 assert!(monitor.get_sv1_client_by_id(999).is_none());
113 }
114
115 #[test]
116 fn sv1_summary_empty() {
117 let monitor = MockSv1Clients(vec![]);
118 let summary = monitor.get_sv1_clients_summary();
119 assert_eq!(summary.total_clients, 0);
120 assert_eq!(summary.total_hashrate, 0.0);
121 }
122
123 #[test]
124 fn sv1_summary_skips_none_hashrate() {
125 let monitor = MockSv1Clients(vec![
126 create_sv1_client_info(1, Some(100.0)),
127 create_sv1_client_info(2, None),
128 create_sv1_client_info(3, Some(50.0)),
129 ]);
130 let summary = monitor.get_sv1_clients_summary();
131 assert_eq!(summary.total_clients, 3);
132 assert_eq!(summary.total_hashrate, 150.0);
133 }
134}