hashiverse_lib/tools/time_provider/time_provider.rs
1//! # `TimeProvider` trait — the clock seam
2//!
3//! A small trait every component takes as an `Arc<dyn TimeProvider>` instead of
4//! calling `SystemTime::now()` or `tokio::time::sleep` directly. `RealTimeProvider`
5//! delegates to wall-clock time; [`crate::tools::time_provider::manual_time_provider`]
6//! lets tests advance time by hand and makes every `sleep` return immediately, so
7//! deterministic integration tests can span simulated days in milliseconds of
8//! real wall time.
9
10use std::future::Future;
11use std::pin::Pin;
12use std::time::Duration;
13use chrono::{DateTime, TimeZone, Utc};
14use crate::tools::time::{DurationMillis, TimeMillis};
15
16/// A stubbable abstraction over wall-clock time and asynchronous sleeping.
17///
18/// Every component in the crate that needs to know "what time is it?" or "wake me in N
19/// milliseconds?" goes through a `TimeProvider` rather than touching `std::time` or
20/// `tokio::time` directly. That lets tests swap in a virtual clock that can be advanced
21/// programmatically — critical for exercising time-dependent logic (bucket rotation, PoW
22/// retry budgets, peer freshness, LRU eviction) without actually sleeping the test harness.
23///
24/// The production implementation is [`RealTimeProvider`]. In-memory integration tests use a
25/// controllable stub whose `sleep` resolves only when the virtual clock is advanced past the
26/// wake time. `TimeProvider` is carried on [`crate::tools::runtime_services::RuntimeServices`]
27/// so every layer — transport, client, server — shares a single clock.
28pub trait TimeProvider: Send + Sync {
29 /// Returns the current time in milliseconds since the UNIX epoch
30 fn current_time_millis(&self) -> TimeMillis;
31
32 fn current_time_str(&self) -> String {
33 self.current_time_millis().to_string()
34 }
35
36 /// Convert the current time to a `DateTime<Utc>`
37 fn current_datetime(&self) -> DateTime<Utc> {
38 let millis = self.current_time_millis();
39 Utc.timestamp_opt(millis.as_secs(), millis.part_nanos() as u32).unwrap()
40 }
41
42 fn sleep(&self, duration: Duration) -> Pin<Box<dyn Future<Output = ()> + Send>>;
43
44 fn sleep_millis(&self, millis: DurationMillis) -> Pin<Box<dyn Future<Output = ()> + Send>> {
45 self.sleep(Duration::from(millis))
46 }
47}
48
49/// Implementation of TimeProvider that uses the system clock
50#[derive(Default, Clone)]
51pub struct RealTimeProvider;
52
53impl TimeProvider for RealTimeProvider {
54 fn current_time_millis(&self) -> TimeMillis {
55 let now: DateTime<Utc> = Utc::now();
56 TimeMillis(now.timestamp_millis())
57 }
58
59 fn sleep(&self, duration: Duration) -> Pin<Box<dyn Future<Output = ()> + Send>> {
60 Box::pin(tokio::time::sleep(duration))
61 }
62}
63
64/// Implementation of TimeProvider that scales time by a given factor
65///
66/// This provider allows you to make time pass faster or slower than real time.
67/// For example, a scale factor of 2.0 means time passes twice as fast,
68/// and a scale factor of 0.5 means time passes at half speed.
69#[derive(Clone)]
70pub struct ScaledTimeProvider {
71 scale_factor: f64,
72 start_real_time: i64,
73}
74
75impl ScaledTimeProvider {
76 /// Create a new ScaledTimeProvider with the specified scale factor
77 ///
78 /// # Arguments
79 /// * `scale_factor` - How much faster (>1.0) or slower (<1.0) time should pass compared to real time
80 pub fn new(scale_factor: f64) -> Self {
81 Self {
82 scale_factor,
83 start_real_time: Utc::now().timestamp_millis(),
84 }
85 }
86}
87
88impl TimeProvider for ScaledTimeProvider {
89 fn current_time_millis(&self) -> TimeMillis {
90 // Calculate how much real time has passed since we started
91 let real_now = Utc::now().timestamp_millis();
92 let real_elapsed = real_now.saturating_sub(self.start_real_time);
93
94 // Scale the elapsed time and add it to our starting point
95 let scaled_elapsed = (real_elapsed as f64 * self.scale_factor) as i64;
96 TimeMillis(scaled_elapsed)
97 }
98
99 fn sleep(&self, duration: Duration) -> Pin<Box<dyn Future<Output = ()> + Send>> {
100 // Calculate the scaled duration and create the sleep future once
101 let scaled_duration = Duration::from_secs_f64(duration.as_secs_f64() / self.scale_factor);
102 Box::pin(tokio::time::sleep(scaled_duration))
103 }
104}
105
106