1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
|
use serde::{Deserialize, Serialize};
use std::collections::VecDeque;
/// Tracks bandwidth metrics for a validator
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BandwidthMetrics {
/// Total bytes sent
pub bytes_sent: u64,
/// Total bytes received
pub bytes_received: u64,
/// Peak bandwidth (bytes/second)
pub peak_bandwidth: u64,
/// Average bandwidth (bytes/second)
pub avg_bandwidth: u64,
/// Measurement period (seconds)
pub measurement_period: u64,
}
impl BandwidthMetrics {
pub fn new() -> Self {
Self {
bytes_sent: 0,
bytes_received: 0,
peak_bandwidth: 0,
avg_bandwidth: 0,
measurement_period: 0,
}
}
/// Total bandwidth (sent + received)
pub fn total_bytes(&self) -> u64 {
self.bytes_sent + self.bytes_received
}
/// Calculate bandwidth score (normalized 0.0 - 1.0)
/// 1 Mbps = 125 KB/s = baseline score of 0.5
pub fn score(&self) -> f64 {
if self.measurement_period == 0 {
return 0.0;
}
let bytes_per_sec = self.total_bytes() / self.measurement_period;
// Baseline: 125 KB/s (1 Mbps) = 0.5 score
// 10 Mbps = 1.0 score
const MAX_BYTES_PER_SEC: u64 = 1_250_000; // 10 Mbps
let score = bytes_per_sec as f64 / MAX_BYTES_PER_SEC as f64;
score.clamp(0.0, 1.0)
}
/// Update metrics with new measurement
pub fn update(&mut self, bytes_sent: u64, bytes_received: u64, period_secs: u64) {
self.bytes_sent += bytes_sent;
self.bytes_received += bytes_received;
self.measurement_period += period_secs;
if period_secs > 0 {
let current_bandwidth = (bytes_sent + bytes_received) / period_secs;
self.peak_bandwidth = self.peak_bandwidth.max(current_bandwidth);
// Update rolling average
let total = self.total_bytes();
self.avg_bandwidth = total / self.measurement_period;
}
}
}
impl Default for BandwidthMetrics {
fn default() -> Self {
Self::new()
}
}
/// Tracks uptime metrics for a validator
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UptimeMetrics {
/// Total uptime in seconds
pub total_uptime: u64,
/// Total time window (seconds)
pub total_time: u64,
/// Number of disconnections
pub disconnections: u32,
/// Longest continuous uptime (seconds)
pub longest_uptime: u64,
/// Current uptime streak (seconds)
pub current_streak: u64,
/// Last seen timestamp
pub last_seen: i64,
}
impl UptimeMetrics {
pub fn new(current_time: i64) -> Self {
Self {
total_uptime: 0,
total_time: 0,
disconnections: 0,
longest_uptime: 0,
current_streak: 0,
last_seen: current_time,
}
}
/// Calculate uptime percentage
pub fn uptime_percentage(&self) -> f64 {
if self.total_time == 0 {
return 0.0;
}
(self.total_uptime as f64 / self.total_time as f64) * 100.0
}
/// Calculate uptime score (0.0 - 1.0)
/// Factors: uptime %, disconnection rate, streak length
pub fn score(&self) -> f64 {
let uptime_score = self.uptime_percentage() / 100.0;
// Penalty for frequent disconnections
let disconnection_penalty = if self.total_time > 0 {
let disconnects_per_day =
(self.disconnections as f64) / (self.total_time as f64 / 86400.0);
// More than 5 disconnects/day = penalty
if disconnects_per_day > 5.0 {
0.5
} else if disconnects_per_day > 2.0 {
0.8
} else {
1.0
}
} else {
1.0
};
// Bonus for long streaks
let streak_bonus = if self.current_streak > 7 * 86400 {
// > 7 days
1.2
} else if self.current_streak > 24 * 3600 {
// > 24 hours
1.1
} else {
1.0
};
(uptime_score * disconnection_penalty * streak_bonus).clamp(0.0, 1.0)
}
/// Record heartbeat (node is online)
pub fn record_heartbeat(&mut self, current_time: i64) {
let elapsed = (current_time - self.last_seen).max(0) as u64;
// If gap is small (< 5 min), consider continuous
if elapsed < 300 {
self.total_uptime += elapsed;
self.current_streak += elapsed;
if self.current_streak > self.longest_uptime {
self.longest_uptime = self.current_streak;
}
} else {
// Disconnection detected
self.disconnections += 1;
self.current_streak = 0;
}
self.total_time += elapsed;
self.last_seen = current_time;
}
/// Record explicit disconnection
pub fn record_disconnection(&mut self, current_time: i64) {
self.disconnections += 1;
self.current_streak = 0;
self.last_seen = current_time;
}
}
/// Tracks storage contribution metrics
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StorageMetrics {
/// Storage space provided (bytes)
pub storage_provided: u64,
/// Storage actually used (bytes)
pub storage_used: u64,
/// Number of blocks stored
pub blocks_stored: u64,
/// Number of transactions stored
pub transactions_stored: u64,
}
impl StorageMetrics {
pub fn new() -> Self {
Self {
storage_provided: 0,
storage_used: 0,
blocks_stored: 0,
transactions_stored: 0,
}
}
/// Calculate utilization percentage
pub fn utilization(&self) -> f64 {
if self.storage_provided == 0 {
return 0.0;
}
(self.storage_used as f64 / self.storage_provided as f64) * 100.0
}
/// Calculate storage score (0.0 - 1.0)
/// 10 GB provided = baseline 0.5
/// 100 GB provided = 1.0
pub fn score(&self) -> f64 {
const MAX_GB: u64 = 100;
let gb_provided = self.storage_provided / (1024 * 1024 * 1024);
let score = gb_provided as f64 / MAX_GB as f64;
score.clamp(0.0, 1.0)
}
/// Update storage metrics
pub fn update(&mut self, provided: u64, used: u64, blocks: u64, txs: u64) {
self.storage_provided = provided;
self.storage_used = used;
self.blocks_stored = blocks;
self.transactions_stored = txs;
}
}
impl Default for StorageMetrics {
fn default() -> Self {
Self::new()
}
}
/// Combined metrics tracker for a validator
#[derive(Debug)]
pub struct MetricsTracker {
pub bandwidth: BandwidthMetrics,
pub uptime: UptimeMetrics,
pub storage: StorageMetrics,
/// History of bandwidth measurements
bandwidth_history: VecDeque<BandwidthMetrics>,
/// Maximum history entries to keep
max_history: usize,
}
impl MetricsTracker {
pub fn new(current_time: i64) -> Self {
Self {
bandwidth: BandwidthMetrics::new(),
uptime: UptimeMetrics::new(current_time),
storage: StorageMetrics::new(),
bandwidth_history: VecDeque::new(),
max_history: 24, // 24 hours of hourly measurements
}
}
/// Record heartbeat
pub fn heartbeat(&mut self, current_time: i64) {
self.uptime.record_heartbeat(current_time);
}
/// Update bandwidth
pub fn update_bandwidth(&mut self, bytes_sent: u64, bytes_received: u64, period_secs: u64) {
self.bandwidth.update(bytes_sent, bytes_received, period_secs);
// Save to history
self.bandwidth_history.push_back(self.bandwidth.clone());
if self.bandwidth_history.len() > self.max_history {
self.bandwidth_history.pop_front();
}
}
/// Update storage
pub fn update_storage(&mut self, provided: u64, used: u64, blocks: u64, txs: u64) {
self.storage.update(provided, used, blocks, txs);
}
/// Get average bandwidth score over history
pub fn avg_bandwidth_score(&self) -> f64 {
if self.bandwidth_history.is_empty() {
return self.bandwidth.score();
}
let total: f64 = self.bandwidth_history.iter().map(|b| b.score()).sum();
total / self.bandwidth_history.len() as f64
}
/// Get combined metrics score
pub fn combined_score(&self) -> f64 {
let bandwidth_score = self.avg_bandwidth_score();
let uptime_score = self.uptime.score();
let storage_score = self.storage.score();
// Weighted average
bandwidth_score * 0.4 + uptime_score * 0.4 + storage_score * 0.2
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_bandwidth_metrics() {
let mut metrics = BandwidthMetrics::new();
// 1 MB sent + received over 10 seconds = 100 KB/s
metrics.update(500_000, 500_000, 10);
assert_eq!(metrics.total_bytes(), 1_000_000);
assert_eq!(metrics.avg_bandwidth, 100_000);
}
#[test]
fn test_uptime_metrics() {
let mut metrics = UptimeMetrics::new(0);
// Record heartbeats every minute for 1 hour
for i in 1..=60 {
metrics.record_heartbeat(i * 60);
}
assert!(metrics.uptime_percentage() > 99.0);
assert_eq!(metrics.disconnections, 0);
}
#[test]
fn test_uptime_with_disconnections() {
let mut metrics = UptimeMetrics::new(0);
metrics.record_heartbeat(60); // 1 min
metrics.record_heartbeat(120); // 2 min
metrics.record_heartbeat(600); // 10 min (gap = disconnection)
assert_eq!(metrics.disconnections, 1);
}
#[test]
fn test_storage_metrics() {
let mut metrics = StorageMetrics::new();
// 50 GB provided, 25 GB used
metrics.update(
50 * 1024 * 1024 * 1024,
25 * 1024 * 1024 * 1024,
1000,
50000,
);
assert_eq!(metrics.utilization(), 50.0);
assert!(metrics.score() > 0.0);
}
#[test]
fn test_metrics_tracker() {
let mut tracker = MetricsTracker::new(0);
tracker.heartbeat(60);
tracker.update_bandwidth(1_000_000, 1_000_000, 60);
tracker.update_storage(10 * 1024 * 1024 * 1024, 5 * 1024 * 1024 * 1024, 100, 1000);
let score = tracker.combined_score();
assert!(score > 0.0);
assert!(score <= 1.0);
}
}
|