Skip to main content

stratum_apps/monitoring/
sv1.rs

1//! SV1 client monitoring types
2//!
3//! These types are specific to SV1 protocol client connections.
4//! Used by Translator Proxy (tProxy) that accepts SV1 miner connections.
5
6use 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/// Information about a single SV1 client connection
14#[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/// Aggregate information about SV1 client connections
34#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
35pub struct Sv1ClientsSummary {
36    pub total_clients: usize,
37    pub total_hashrate: f32,
38}
39
40/// Trait for monitoring SV1 client connections
41pub trait Sv1ClientsMonitoring: Send + Sync {
42    /// Get all SV1 clients
43    fn get_sv1_clients(&self) -> Vec<Sv1ClientInfo>;
44
45    /// Get a single SV1 client by client_id
46    ///
47    /// Default implementation does O(n) scan. Override for O(1) lookup
48    /// if your implementation uses a HashMap internally.
49    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    /// Get summary of SV1 clients
56    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}