@@ -15,7 +15,7 @@ use crate::{Health, Probes, RpcClient};
15
15
pub struct HealthHandle {
16
16
pub probes : Arc < Probes > ,
17
17
pub builder_client : Arc < RpcClient > ,
18
- pub health_check_interval : u64 ,
18
+ pub health_check_interval : Duration ,
19
19
pub max_unsafe_interval : u64 ,
20
20
}
21
21
@@ -24,6 +24,8 @@ impl HealthHandle {
24
24
/// the current time minus the max_unsafe_interval.
25
25
pub fn spawn ( self ) -> JoinHandle < ( ) > {
26
26
tokio:: spawn ( async move {
27
+ let mut timestamp = MonotonicTimestamp :: new ( ) ;
28
+
27
29
loop {
28
30
let latest_unsafe = match self
29
31
. builder_client
@@ -34,32 +36,70 @@ impl HealthHandle {
34
36
Err ( e) => {
35
37
warn ! ( target: "rollup_boost::health" , "Failed to get unsafe block from builder client: {} - updating health status" , e) ;
36
38
self . probes . set_health ( Health :: PartialContent ) ;
37
- sleep_until (
38
- Instant :: now ( ) + Duration :: from_secs ( self . health_check_interval ) ,
39
- )
40
- . await ;
39
+ sleep_until ( Instant :: now ( ) + self . health_check_interval ) . await ;
41
40
continue ;
42
41
}
43
42
} ;
44
43
45
- let now = SystemTime :: now ( )
46
- . duration_since ( UNIX_EPOCH )
47
- . expect ( "Time went backwards" )
48
- . as_secs ( ) ;
49
-
50
- if now - latest_unsafe. header . timestamp > self . max_unsafe_interval {
51
- warn ! ( target: "rollup_boost::health" , "Unsafe block timestamp is too old ({} seconds - updating health status)" , now - latest_unsafe. header. timestamp) ;
44
+ let t = timestamp. tick ( ) ;
45
+ if t. saturating_sub ( latest_unsafe. header . timestamp )
46
+ . gt ( & self . max_unsafe_interval )
47
+ {
48
+ warn ! ( target: "rollup_boost::health" , curr_unix = %t, unsafe_unix = %latest_unsafe. header. timestamp, "Unsafe block timestamp is too old updating health status" ) ;
52
49
self . probes . set_health ( Health :: PartialContent ) ;
53
50
} else {
54
51
self . probes . set_health ( Health :: Healthy ) ;
55
52
}
56
53
57
- sleep_until ( Instant :: now ( ) + Duration :: from_secs ( self . health_check_interval ) ) . await ;
54
+ sleep_until ( Instant :: now ( ) + self . health_check_interval ) . await ;
58
55
}
59
56
} )
60
57
}
61
58
}
62
59
60
+ /// A monotonic wall-clock timestamp tracker that resists system clock changes.
61
+ ///
62
+ /// This struct provides a way to generate wall-clock-like timestamps that are
63
+ /// guaranteed to be monotonic (i.e., never go backward), even if the system
64
+ /// time is adjusted (e.g., via NTP, manual clock changes, or suspend/resume).
65
+ ///
66
+ /// - It tracks elapsed time using `Instant` to ensure monotonic progression.
67
+ /// - It produces a synthetic wall-clock timestamp that won't regress.
68
+ pub struct MonotonicTimestamp {
69
+ /// The last known UNIX timestamp in seconds.
70
+ pub last_unix : u64 ,
71
+
72
+ /// The last monotonic time reference.
73
+ pub last_instant : Instant ,
74
+ }
75
+
76
+ impl Default for MonotonicTimestamp {
77
+ fn default ( ) -> Self {
78
+ Self :: new ( )
79
+ }
80
+ }
81
+
82
+ impl MonotonicTimestamp {
83
+ pub fn new ( ) -> Self {
84
+ let last_unix = SystemTime :: now ( )
85
+ . duration_since ( UNIX_EPOCH )
86
+ . expect ( "Time went backwards" )
87
+ . as_secs ( ) ;
88
+ let last_instant = Instant :: now ( ) ;
89
+ Self {
90
+ last_unix,
91
+ last_instant,
92
+ }
93
+ }
94
+
95
+ fn tick ( & mut self ) -> u64 {
96
+ let elapsed = self . last_instant . elapsed ( ) . as_secs ( ) ;
97
+ self . last_unix += elapsed;
98
+ self . last_instant = Instant :: now ( ) ;
99
+ self . last_unix
100
+ }
101
+ }
102
+
63
103
#[ cfg( test) ]
64
104
mod tests {
65
105
use std:: net:: SocketAddr ;
@@ -214,7 +254,7 @@ mod tests {
214
254
let health_handle = HealthHandle {
215
255
probes : probes. clone ( ) ,
216
256
builder_client : builder_client. clone ( ) ,
217
- health_check_interval : 60 ,
257
+ health_check_interval : Duration :: from_secs ( 60 ) ,
218
258
max_unsafe_interval : 5 ,
219
259
} ;
220
260
@@ -244,7 +284,7 @@ mod tests {
244
284
let health_handle = HealthHandle {
245
285
probes : probes. clone ( ) ,
246
286
builder_client : builder_client. clone ( ) ,
247
- health_check_interval : 60 ,
287
+ health_check_interval : Duration :: from_secs ( 60 ) ,
248
288
max_unsafe_interval : 5 ,
249
289
} ;
250
290
@@ -268,7 +308,7 @@ mod tests {
268
308
let health_handle = HealthHandle {
269
309
probes : probes. clone ( ) ,
270
310
builder_client : builder_client. clone ( ) ,
271
- health_check_interval : 60 ,
311
+ health_check_interval : Duration :: from_secs ( 60 ) ,
272
312
max_unsafe_interval : 5 ,
273
313
} ;
274
314
@@ -277,4 +317,34 @@ mod tests {
277
317
assert ! ( matches!( probes. health( ) , Health :: PartialContent ) ) ;
278
318
Ok ( ( ) )
279
319
}
320
+
321
+ #[ tokio:: test]
322
+ async fn tick_advances_after_sleep ( ) {
323
+ let mut ts = MonotonicTimestamp :: new ( ) ;
324
+ let t1 = ts. tick ( ) ;
325
+ tokio:: time:: sleep ( Duration :: from_secs ( 1 ) ) . await ;
326
+ let t2 = ts. tick ( ) ;
327
+
328
+ assert ! ( t2 >= t1 + 1 , ) ;
329
+ }
330
+
331
+ #[ tokio:: test]
332
+ async fn tick_matches_system_clock ( ) {
333
+ let mut ts = MonotonicTimestamp :: new ( ) ;
334
+ let unix = SystemTime :: now ( )
335
+ . duration_since ( UNIX_EPOCH )
336
+ . expect ( "Time went backwards" )
337
+ . as_secs ( ) ;
338
+
339
+ assert_eq ! ( ts. last_unix, unix) ;
340
+
341
+ std:: thread:: sleep ( Duration :: from_secs ( 5 ) ) ;
342
+
343
+ let t1 = ts. tick ( ) ;
344
+ let unix = SystemTime :: now ( )
345
+ . duration_since ( UNIX_EPOCH )
346
+ . expect ( "Time went backwards" )
347
+ . as_secs ( ) ;
348
+ assert_eq ! ( t1, unix) ;
349
+ }
280
350
}
0 commit comments