Skip to content

Commit da69eb6

Browse files
0xOsiris0xKitsune
andauthored
Osiris/monotonic timestamps (#251)
* feat: implement monotonic unix timestamp for reliability * test: add more tests * chore: clp * fix: devnet * docs: fix comments --------- Co-authored-by: 0xKitsune <[email protected]>
1 parent 14c3454 commit da69eb6

File tree

4 files changed

+90
-19
lines changed

4 files changed

+90
-19
lines changed

scripts/ci/kurtosis-params.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ optimism_package:
2222
ethereum_package:
2323
participants:
2424
- el_type: geth
25-
cl_type: lighthouse
25+
cl_type: teku
2626
network_params:
2727
preset: minimal
2828
genesis_delay: 5

src/cli.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ impl Args {
9797

9898
// Handle commands if present
9999
if let Some(cmd) = self.command {
100-
let debug_addr = format!("http://{}", debug_addr);
100+
let debug_addr = format!("http://{debug_addr}");
101101
return match cmd {
102102
Commands::Debug { command } => match command {
103103
DebugCommands::SetExecutionMode { execution_mode } => {

src/health.rs

Lines changed: 86 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use crate::{Health, Probes, RpcClient};
1515
pub struct HealthHandle {
1616
pub probes: Arc<Probes>,
1717
pub builder_client: Arc<RpcClient>,
18-
pub health_check_interval: u64,
18+
pub health_check_interval: Duration,
1919
pub max_unsafe_interval: u64,
2020
}
2121

@@ -24,6 +24,8 @@ impl HealthHandle {
2424
/// the current time minus the max_unsafe_interval.
2525
pub fn spawn(self) -> JoinHandle<()> {
2626
tokio::spawn(async move {
27+
let mut timestamp = MonotonicTimestamp::new();
28+
2729
loop {
2830
let latest_unsafe = match self
2931
.builder_client
@@ -34,32 +36,70 @@ impl HealthHandle {
3436
Err(e) => {
3537
warn!(target: "rollup_boost::health", "Failed to get unsafe block from builder client: {} - updating health status", e);
3638
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;
4140
continue;
4241
}
4342
};
4443

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");
5249
self.probes.set_health(Health::PartialContent);
5350
} else {
5451
self.probes.set_health(Health::Healthy);
5552
}
5653

57-
sleep_until(Instant::now() + Duration::from_secs(self.health_check_interval)).await;
54+
sleep_until(Instant::now() + self.health_check_interval).await;
5855
}
5956
})
6057
}
6158
}
6259

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+
63103
#[cfg(test)]
64104
mod tests {
65105
use std::net::SocketAddr;
@@ -214,7 +254,7 @@ mod tests {
214254
let health_handle = HealthHandle {
215255
probes: probes.clone(),
216256
builder_client: builder_client.clone(),
217-
health_check_interval: 60,
257+
health_check_interval: Duration::from_secs(60),
218258
max_unsafe_interval: 5,
219259
};
220260

@@ -244,7 +284,7 @@ mod tests {
244284
let health_handle = HealthHandle {
245285
probes: probes.clone(),
246286
builder_client: builder_client.clone(),
247-
health_check_interval: 60,
287+
health_check_interval: Duration::from_secs(60),
248288
max_unsafe_interval: 5,
249289
};
250290

@@ -268,7 +308,7 @@ mod tests {
268308
let health_handle = HealthHandle {
269309
probes: probes.clone(),
270310
builder_client: builder_client.clone(),
271-
health_check_interval: 60,
311+
health_check_interval: Duration::from_secs(60),
272312
max_unsafe_interval: 5,
273313
};
274314

@@ -277,4 +317,34 @@ mod tests {
277317
assert!(matches!(probes.health(), Health::PartialContent));
278318
Ok(())
279319
}
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+
}
280350
}

src/server.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ use op_alloy_rpc_types_engine::{
2828
use opentelemetry::trace::SpanKind;
2929
use parking_lot::Mutex;
3030
use std::sync::Arc;
31+
use std::time::Duration;
3132
use tokio::task::JoinHandle;
3233
use tracing::{info, instrument};
3334

@@ -52,7 +53,7 @@ impl RollupBoostServer {
5253
let health_handle = HealthHandle {
5354
probes: probes.clone(),
5455
builder_client: Arc::new(builder_client.clone()),
55-
health_check_interval,
56+
health_check_interval: Duration::from_secs(health_check_interval),
5657
max_unsafe_interval,
5758
}
5859
.spawn();

0 commit comments

Comments
 (0)