Skip to content

Commit 26f3423

Browse files
fixup: get rid of std usage when using time
1 parent f08eafe commit 26f3423

File tree

5 files changed

+213
-94
lines changed

5 files changed

+213
-94
lines changed

lightning-liquidity/src/lsps5/client.rs

Lines changed: 62 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
//! Client implementation for LSPS5 webhook registration
1010
1111
use crate::events::EventQueue;
12-
use crate::lsps0::ser::{LSPSDateTime, LSPSMessage, LSPSProtocolMessageHandler, LSPSRequestId};
12+
use crate::lsps0::ser::{LSPSMessage, LSPSProtocolMessageHandler, LSPSRequestId};
1313
use crate::lsps5::event::LSPS5ClientEvent;
1414
use crate::lsps5::msgs::{
1515
LSPS5Message, LSPS5Request, LSPS5Response, ListWebhooksRequest, RemoveWebhookRequest,
@@ -24,39 +24,51 @@ use lightning::util::message_signing;
2424

2525
use crate::sync::{Arc, Mutex, RwLock};
2626
use core::ops::Deref;
27-
use core::str::FromStr;
2827

2928
use crate::prelude::{new_hash_map, HashMap, String};
3029

3130
use super::msgs::{Lsps5AppName, Lsps5WebhookUrl};
31+
use super::service::{DefaultTimeProvider, TimeProvider};
3232
use super::url_utils::Url;
33-
use chrono::Duration;
33+
use core::time::Duration;
3434
use lightning::sign::EntropySource;
3535
use lightning::util::logger::Level;
3636

3737
/// Default maximum age in seconds for cached responses (1 hour)
3838
pub const DEFAULT_RESPONSE_MAX_AGE_SECS: u64 = 3600;
3939

4040
/// Configuration options for LSPS5 client operations
41-
#[derive(Debug, Clone)]
41+
#[derive(Clone)]
4242
pub struct LSPS5ClientConfig {
4343
/// Maximum age in seconds for cached responses (default: 3600 - 1 hour)
4444
pub response_max_age_secs: u64,
45+
/// Time provider for LSPS5 service
46+
pub time_provider: Arc<dyn TimeProvider>,
4547
}
4648

4749
impl Default for LSPS5ClientConfig {
4850
fn default() -> Self {
49-
Self { response_max_age_secs: DEFAULT_RESPONSE_MAX_AGE_SECS }
51+
Self {
52+
response_max_age_secs: DEFAULT_RESPONSE_MAX_AGE_SECS,
53+
time_provider: Arc::new(DefaultTimeProvider),
54+
}
55+
}
56+
}
57+
58+
impl LSPS5ClientConfig {
59+
/// Set a custom time provider for the LSPS5 service
60+
pub fn with_time_provider(mut self, time_provider: impl TimeProvider + 'static) -> Self {
61+
self.time_provider = Arc::new(time_provider);
62+
self
5063
}
5164
}
5265

5366
struct PeerState {
54-
pending_set_webhook_requests:
55-
HashMap<LSPSRequestId, (Lsps5AppName, Lsps5WebhookUrl, LSPSDateTime)>, // RequestId -> (app_name, webhook_url, timestamp)
56-
pending_list_webhooks_requests: HashMap<LSPSRequestId, LSPSDateTime>, // RequestId -> timestamp
57-
pending_remove_webhook_requests: HashMap<LSPSRequestId, (Lsps5AppName, LSPSDateTime)>, // RequestId -> (app_name, timestamp)
67+
pending_set_webhook_requests: HashMap<LSPSRequestId, (Lsps5AppName, Lsps5WebhookUrl, Duration)>, // RequestId -> (app_name, webhook_url, timestamp)
68+
pending_list_webhooks_requests: HashMap<LSPSRequestId, Duration>, // RequestId -> timestamp
69+
pending_remove_webhook_requests: HashMap<LSPSRequestId, (Lsps5AppName, Duration)>, // RequestId -> (app_name, timestamp)
5870
// Last cleanup time for garbage collection
59-
last_cleanup: LSPSDateTime, // Seconds since epoch
71+
last_cleanup: Duration, // Seconds since epoch
6072
}
6173

6274
impl PeerState {
@@ -65,37 +77,34 @@ impl PeerState {
6577
pending_set_webhook_requests: new_hash_map(),
6678
pending_list_webhooks_requests: new_hash_map(),
6779
pending_remove_webhook_requests: new_hash_map(),
68-
last_cleanup: LSPSDateTime::now(),
80+
last_cleanup: Duration::from_secs(0),
6981
}
7082
}
7183

7284
/// Clean up expired responses based on max_age
73-
fn cleanup_expired_responses(&mut self, max_age_secs: u64) {
74-
let now = LSPSDateTime::now();
85+
fn cleanup_expired_responses(
86+
&mut self, max_age_secs: u64, time_provider: Arc<dyn TimeProvider>,
87+
) {
88+
let now = time_provider.now();
7589

7690
// Only run cleanup once per minute to avoid excessive processing
77-
if now.duration_since(&self.last_cleanup) < Duration::seconds(60) {
91+
if now.abs_diff(self.last_cleanup) < Duration::from_secs(60) {
7892
return;
7993
}
8094

8195
self.last_cleanup = now.clone();
8296

8397
// Calculate the cutoff time for expired requests
84-
let cutoff = now
85-
.checked_sub_signed(Duration::seconds(max_age_secs.try_into().unwrap()))
86-
.expect("Cutoff time must be computed");
98+
let cutoff = now.abs_diff(Duration::from_secs(max_age_secs.try_into().unwrap()));
8799

88100
// Remove expired set_webhook requests
89-
self.pending_set_webhook_requests
90-
.retain(|_, (_, _, timestamp)| timestamp.timestamp() > cutoff.timestamp());
101+
self.pending_set_webhook_requests.retain(|_, (_, _, timestamp)| *timestamp > cutoff);
91102

92103
// Remove expired list_webhooks requests
93-
self.pending_list_webhooks_requests
94-
.retain(|_, timestamp| timestamp.timestamp() > cutoff.timestamp());
104+
self.pending_list_webhooks_requests.retain(|_, timestamp| *timestamp > cutoff);
95105

96106
// Remove expired remove_webhook requests
97-
self.pending_remove_webhook_requests
98-
.retain(|_, (_, timestamp)| timestamp.timestamp() > cutoff.timestamp());
107+
self.pending_remove_webhook_requests.retain(|_, (_, timestamp)| *timestamp > cutoff);
99108
}
100109
}
101110

@@ -114,6 +123,8 @@ where
114123
per_peer_state: RwLock<HashMap<PublicKey, Mutex<PeerState>>>,
115124
/// Client configuration
116125
config: LSPS5ClientConfig,
126+
/// Time provider for LSPS5 service
127+
time_provider: Arc<dyn TimeProvider>,
117128
}
118129

119130
impl<ES: Deref> LSPS5ClientHandler<ES>
@@ -126,12 +137,14 @@ where
126137
entropy_source: ES, pending_messages: Arc<MessageQueue>, pending_events: Arc<EventQueue>,
127138
config: LSPS5ClientConfig,
128139
) -> Self {
140+
let time_provider = config.time_provider.clone();
129141
Self {
130142
pending_messages,
131143
pending_events,
132144
entropy_source,
133145
per_peer_state: RwLock::new(new_hash_map()),
134146
config,
147+
time_provider,
135148
}
136149
}
137150

@@ -146,7 +159,10 @@ where
146159
let mut peer_state_lock = inner_state_lock.lock().unwrap();
147160

148161
// Clean up expired responses using configured max age
149-
peer_state_lock.cleanup_expired_responses(self.config.response_max_age_secs);
162+
peer_state_lock.cleanup_expired_responses(
163+
self.config.response_max_age_secs,
164+
self.time_provider.clone(),
165+
);
150166

151167
// Execute the provided function with the locked peer state
152168
f(&mut *peer_state_lock)
@@ -208,7 +224,7 @@ where
208224
self.with_peer_state(counterparty_node_id, |peer_state| {
209225
peer_state.pending_set_webhook_requests.insert(
210226
request_id.clone(),
211-
(app_name.clone(), webhook.clone(), LSPSDateTime::now()),
227+
(app_name.clone(), webhook.clone(), self.time_provider.now()),
212228
);
213229
});
214230

@@ -242,7 +258,7 @@ where
242258
self.with_peer_state(counterparty_node_id, |peer_state| {
243259
peer_state
244260
.pending_list_webhooks_requests
245-
.insert(request_id.clone(), LSPSDateTime::now());
261+
.insert(request_id.clone(), self.time_provider.now());
246262
});
247263

248264
// Create the request
@@ -284,7 +300,7 @@ where
284300
self.with_peer_state(counterparty_node_id, |peer_state| {
285301
peer_state
286302
.pending_remove_webhook_requests
287-
.insert(request_id.clone(), (app_name.clone(), LSPSDateTime::now()));
303+
.insert(request_id.clone(), (app_name.clone(), self.time_provider.now()));
288304
});
289305

290306
let request = LSPS5Request::RemoveWebhook(RemoveWebhookRequest { app_name });
@@ -501,20 +517,20 @@ where
501517
/// * On error: LightningError with error description
502518
pub fn verify_notification_signature(
503519
counterparty_node_id: PublicKey, timestamp: &str, signature: &str,
504-
notification: &WebhookNotification,
520+
notification: &WebhookNotification, time_provider: Arc<dyn TimeProvider>,
505521
) -> Result<bool, LightningError> {
506522
// Check timestamp format
507-
match LSPSDateTime::from_str(timestamp) {
523+
match time_provider.from_rfc3339(timestamp) {
508524
Ok(timestamp_dt) => {
509525
// Check timestamp is within 10 minutes of current time
510-
let now = LSPSDateTime::now();
511-
let diff = (timestamp_dt.timestamp() - now.timestamp()).abs();
512-
if diff > 600 {
526+
let now = time_provider.now();
527+
let diff = now.abs_diff(timestamp_dt);
528+
if diff > Duration::from_secs(600) {
513529
// 10 minutes
514530
return Err(LightningError {
515531
err: format!(
516-
"Timestamp too far from current time: {} (diff: {} seconds)",
517-
timestamp, diff
532+
"Timestamp too far from current time: {:?} (diff: {:?} seconds)",
533+
now, diff
518534
),
519535
action: ErrorAction::IgnoreAndLog(Level::Error),
520536
});
@@ -571,6 +587,7 @@ where
571587
/// * On error: LightningError with error description
572588
pub fn parse_webhook_notification(
573589
counterparty_node_id: PublicKey, timestamp: &str, signature: &str, notification_json: &str,
590+
time_provider: Arc<dyn TimeProvider>,
574591
) -> Result<WebhookNotification, LightningError> {
575592
// Parse the notification JSON
576593
let notification: WebhookNotification = match serde_json::from_str(notification_json) {
@@ -588,6 +605,7 @@ where
588605
timestamp,
589606
signature,
590607
&notification,
608+
time_provider,
591609
) {
592610
Ok(_) => Ok(notification),
593611
Err(e) => Err(e),
@@ -750,17 +768,20 @@ mod tests {
750768

751769
#[test]
752770
fn test_cleanup_expired_responses() {
771+
// use DefaultTimeProvider
772+
let (client, _, _, _, _) = setup_test_client();
773+
let time_provider = client.time_provider;
753774
const OLD_APP_NAME: &str = "test-app-old";
754775
const NEW_APP_NAME: &str = "test-app-new";
755776
const WEBHOOK_URL: &str = "https://example.com/hook";
756777
let lsps5_old_app_name = Lsps5AppName::new(OLD_APP_NAME.to_string()).unwrap();
757778
let lsps5_new_app_name = Lsps5AppName::new(NEW_APP_NAME.to_string()).unwrap();
758779
let lsps5_webhook_url = Lsps5WebhookUrl::new(WEBHOOK_URL.to_string()).unwrap();
759780
// The current time for setting request timestamps
760-
let now = LSPSDateTime::now();
781+
let now = time_provider.now();
761782
// Create a mock PeerState with a very old cleanup time
762783
let mut peer_state = PeerState::new();
763-
peer_state.last_cleanup = now.checked_sub_signed(Duration::seconds(120)).unwrap();
784+
peer_state.last_cleanup = now.abs_diff(Duration::from_secs(120));
764785

765786
// Add some test requests with different timestamps
766787
let old_request_id = LSPSRequestId("test:request:old".to_string());
@@ -772,30 +793,26 @@ mod tests {
772793
(
773794
lsps5_old_app_name,
774795
lsps5_webhook_url.clone(),
775-
now.checked_sub_signed(Duration::seconds(7200)).unwrap(),
796+
now.abs_diff(Duration::from_secs(7200)),
776797
), // 2 hours old
777798
);
778799

779800
// Add a recent request (should be kept)
780801
peer_state.pending_set_webhook_requests.insert(
781802
new_request_id.clone(),
782-
(
783-
lsps5_new_app_name,
784-
lsps5_webhook_url,
785-
now.checked_sub_signed(Duration::seconds(600)).unwrap(),
786-
), // 10 minutes old
803+
(lsps5_new_app_name, lsps5_webhook_url, now.abs_diff(Duration::from_secs(600))), // 10 minutes old
787804
);
788805

789806
// Run cleanup with 30 minutes (1800 seconds) max age
790-
peer_state.cleanup_expired_responses(1800);
807+
peer_state.cleanup_expired_responses(1800, time_provider.clone());
791808

792809
// Verify old request is removed and new request is kept
793810
assert!(!peer_state.pending_set_webhook_requests.contains_key(&old_request_id));
794811
assert!(peer_state.pending_set_webhook_requests.contains_key(&new_request_id));
795812

796813
// Verify last_cleanup was updated within the last 10 seconds
797-
let cleanup_age = LSPSDateTime::now().duration_since(&peer_state.last_cleanup);
798-
assert!(cleanup_age < Duration::seconds(10));
814+
let cleanup_age = time_provider.clone().now().abs_diff(peer_state.last_cleanup);
815+
assert!(cleanup_age < Duration::from_secs(10));
799816
}
800817

801818
#[test]

0 commit comments

Comments
 (0)