1use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8use utoipa::ToSchema;
9
10#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
12pub struct ServerExtendedChannelInfo {
13 pub channel_id: u32,
14 pub user_identity: String,
15 pub nominal_hashrate: Option<f32>,
17 pub target_hex: String,
18 pub extranonce_prefix_hex: String,
19 pub full_extranonce_size: usize,
20 pub rollable_extranonce_size: u16,
21 pub version_rolling: bool,
22 pub shares_acknowledged: u32,
23 pub shares_submitted: u32,
24 pub shares_rejected: u32,
25 pub shares_rejected_by_reason: HashMap<String, u32>,
26 pub acknowledged_work_sum: u64,
28 pub validated_work_sum: f64,
30 pub best_diff: f64,
31 pub blocks_found: u32,
32}
33
34#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
36pub struct ServerStandardChannelInfo {
37 pub channel_id: u32,
38 pub user_identity: String,
39 pub nominal_hashrate: Option<f32>,
41 pub target_hex: String,
42 pub extranonce_prefix_hex: String,
43 pub shares_acknowledged: u32,
44 pub shares_submitted: u32,
45 pub shares_rejected: u32,
46 pub shares_rejected_by_reason: HashMap<String, u32>,
47 pub acknowledged_work_sum: u64,
49 pub validated_work_sum: f64,
51 pub best_diff: f64,
52 pub blocks_found: u32,
53}
54
55#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
57pub struct ServerInfo {
58 pub extended_channels: Vec<ServerExtendedChannelInfo>,
59 pub standard_channels: Vec<ServerStandardChannelInfo>,
60}
61
62impl ServerInfo {
63 pub fn total_channels(&self) -> usize {
65 self.extended_channels.len() + self.standard_channels.len()
66 }
67
68 pub fn total_hashrate(&self) -> f32 {
70 self.extended_channels
71 .iter()
72 .filter_map(|c| c.nominal_hashrate)
73 .sum::<f32>()
74 + self
75 .standard_channels
76 .iter()
77 .filter_map(|c| c.nominal_hashrate)
78 .sum::<f32>()
79 }
80}
81
82#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
84pub struct ServerSummary {
85 pub total_channels: usize,
86 pub extended_channels: usize,
87 pub standard_channels: usize,
88 pub total_hashrate: f32,
89}
90
91pub trait ServerMonitoring: Send + Sync {
93 fn get_server(&self) -> ServerInfo;
95
96 fn get_server_summary(&self) -> ServerSummary {
98 let server = self.get_server();
99
100 ServerSummary {
101 total_channels: server.total_channels(),
102 extended_channels: server.extended_channels.len(),
103 standard_channels: server.standard_channels.len(),
104 total_hashrate: server.total_hashrate(),
105 }
106 }
107}
108
109#[cfg(test)]
110mod tests {
111 use super::*;
112 use stratum_core::mining_sv2::ERROR_CODE_SUBMIT_SHARES_DUPLICATE_SHARE;
113
114 fn create_server_extended_channel_info(
117 channel_id: u32,
118 hashrate: Option<f32>,
119 ) -> ServerExtendedChannelInfo {
120 ServerExtendedChannelInfo {
121 channel_id,
122 user_identity: format!("pool-ext-{}", channel_id),
123 nominal_hashrate: hashrate,
124 target_hex: "00ff".into(),
125 extranonce_prefix_hex: "aa".into(),
126 full_extranonce_size: 16,
127 rollable_extranonce_size: 4,
128 version_rolling: true,
129 shares_acknowledged: 10,
130 shares_rejected: 0,
131 shares_rejected_by_reason: HashMap::new(),
132 acknowledged_work_sum: 100,
133 validated_work_sum: 100.0,
134 shares_submitted: 12,
135 best_diff: 50.0,
136 blocks_found: 0,
137 }
138 }
139
140 fn create_server_standard_channel_info(
141 channel_id: u32,
142 hashrate: Option<f32>,
143 ) -> ServerStandardChannelInfo {
144 ServerStandardChannelInfo {
145 channel_id,
146 user_identity: format!("pool-std-{}", channel_id),
147 nominal_hashrate: hashrate,
148 target_hex: "00ff".into(),
149 extranonce_prefix_hex: "bb".into(),
150 shares_acknowledged: 20,
151 shares_submitted: 22,
152 shares_rejected: 1,
153 shares_rejected_by_reason: HashMap::from([(
154 ERROR_CODE_SUBMIT_SHARES_DUPLICATE_SHARE.to_string(),
155 1,
156 )]),
157 acknowledged_work_sum: 200,
158 validated_work_sum: 200.0,
159 best_diff: 80.0,
160 blocks_found: 0,
161 }
162 }
163
164 #[test]
167 fn server_info_empty() {
168 let server = ServerInfo {
169 extended_channels: vec![],
170 standard_channels: vec![],
171 };
172 assert_eq!(server.total_channels(), 0);
173 assert_eq!(server.total_hashrate(), 0.0);
174 }
175
176 #[test]
177 fn server_info_aggregates_both_channel_types() {
178 let server = ServerInfo {
179 extended_channels: vec![create_server_extended_channel_info(1, Some(100.0))],
180 standard_channels: vec![
181 create_server_standard_channel_info(2, Some(50.0)),
182 create_server_standard_channel_info(3, Some(75.0)),
183 ],
184 };
185 assert_eq!(server.total_channels(), 3);
186 assert_eq!(server.total_hashrate(), 225.0);
187 }
188
189 #[test]
190 fn server_info_hashrate_skips_none_values() {
191 let server = ServerInfo {
192 extended_channels: vec![
193 create_server_extended_channel_info(1, Some(100.0)),
194 create_server_extended_channel_info(2, None),
195 ],
196 standard_channels: vec![
197 create_server_standard_channel_info(3, Some(50.0)),
198 create_server_standard_channel_info(4, None),
199 ],
200 };
201 assert_eq!(server.total_channels(), 4);
202 assert_eq!(server.total_hashrate(), 150.0);
203 }
204
205 struct MockServer(ServerInfo);
208 impl ServerMonitoring for MockServer {
209 fn get_server(&self) -> ServerInfo {
210 self.0.clone()
211 }
212 }
213
214 #[test]
215 fn server_monitoring_summary_empty() {
216 let monitor = MockServer(ServerInfo {
217 extended_channels: vec![],
218 standard_channels: vec![],
219 });
220 let summary = monitor.get_server_summary();
221
222 assert_eq!(summary.total_channels, 0);
223 assert_eq!(summary.extended_channels, 0);
224 assert_eq!(summary.standard_channels, 0);
225 assert_eq!(summary.total_hashrate, 0.0);
226 }
227
228 #[test]
229 fn server_monitoring_summary_aggregates_correctly() {
230 let monitor = MockServer(ServerInfo {
231 extended_channels: vec![
232 create_server_extended_channel_info(1, Some(100.0)),
233 create_server_extended_channel_info(2, Some(200.0)),
234 ],
235 standard_channels: vec![create_server_standard_channel_info(3, Some(50.0))],
236 });
237 let summary = monitor.get_server_summary();
238
239 assert_eq!(summary.total_channels, 3);
240 assert_eq!(summary.extended_channels, 2);
241 assert_eq!(summary.standard_channels, 1);
242 assert_eq!(summary.total_hashrate, 350.0);
243 }
244}