Skip to content

Commit 1eb19dc

Browse files
WIP commit to check CI
1 parent cb7ae43 commit 1eb19dc

File tree

11 files changed

+386
-789
lines changed

11 files changed

+386
-789
lines changed

lightning-liquidity/src/lsps0/ser.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ use lightning::util::ser::{LengthLimitedRead, LengthReadable, WithoutLength};
3535
use bitcoin::secp256k1::PublicKey;
3636

3737
use core::time::Duration;
38-
#[cfg(feature = "std")]
38+
#[cfg(feature = "time")]
3939
use std::time::{SystemTime, UNIX_EPOCH};
4040

4141
use serde::de::{self, MapAccess, Visitor};
@@ -229,7 +229,7 @@ impl LSPSDateTime {
229229
}
230230

231231
/// Returns if the given time is in the past.
232-
#[cfg(feature = "std")]
232+
#[cfg(feature = "time")]
233233
pub fn is_past(&self) -> bool {
234234
let now_seconds_since_epoch = SystemTime::now()
235235
.duration_since(UNIX_EPOCH)

lightning-liquidity/src/lsps2/utils.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,13 @@ pub fn is_valid_opening_fee_params(
2828
}
2929

3030
/// Determines if the given parameters are expired, or still valid.
31-
#[cfg_attr(not(feature = "std"), allow(unused_variables))]
31+
#[cfg_attr(not(feature = "time"), allow(unused_variables))]
3232
pub fn is_expired_opening_fee_params(fee_params: &LSPS2OpeningFeeParams) -> bool {
33-
#[cfg(feature = "std")]
33+
#[cfg(feature = "time")]
3434
{
3535
fee_params.valid_until.is_past()
3636
}
37-
#[cfg(not(feature = "std"))]
37+
#[cfg(not(feature = "time"))]
3838
{
3939
// TODO: We need to find a way to check expiry times in no-std builds.
4040
false

lightning-liquidity/src/lsps5/client.rs

Lines changed: 49 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -83,18 +83,18 @@ impl Default for LSPS5ClientConfig {
8383
}
8484
}
8585

86-
struct PeerState {
86+
struct PeerState<TP: TimeProvider + Send + Sync> {
8787
pending_set_webhook_requests:
8888
HashMap<LSPSRequestId, (LSPS5AppName, LSPS5WebhookUrl, LSPSDateTime)>,
8989
pending_list_webhooks_requests: HashMap<LSPSRequestId, LSPSDateTime>,
9090
pending_remove_webhook_requests: HashMap<LSPSRequestId, (LSPS5AppName, LSPSDateTime)>,
9191
last_cleanup: Option<LSPSDateTime>,
9292
max_age_secs: Duration,
93-
time_provider: Arc<dyn TimeProvider>,
93+
time_provider: Arc<TP>,
9494
}
9595

96-
impl PeerState {
97-
fn new(max_age_secs: Duration, time_provider: Arc<dyn TimeProvider>) -> Self {
96+
impl<TP: TimeProvider + Send + Sync> PeerState<TP> {
97+
fn new(max_age_secs: Duration, time_provider: Arc<TP>) -> Self {
9898
Self {
9999
pending_set_webhook_requests: new_hash_map(),
100100
pending_list_webhooks_requests: new_hash_map(),
@@ -109,27 +109,29 @@ impl PeerState {
109109
let now =
110110
LSPSDateTime::new_from_duration_since_epoch(self.time_provider.duration_since_epoch());
111111
// Only run cleanup once per minute to avoid excessive processing
112-
let minute = 60;
112+
const CLEANUP_INTERVAL: Duration = Duration::from_secs(60);
113113
if let Some(last_cleanup) = &self.last_cleanup {
114-
if now.abs_diff(last_cleanup.clone()) < minute {
114+
let time_since_last_cleanup = Duration::from_secs(now.abs_diff(last_cleanup.clone()));
115+
if time_since_last_cleanup < CLEANUP_INTERVAL {
115116
return;
116117
}
117118
}
118119

119120
self.last_cleanup = Some(now.clone());
120121

121122
self.pending_set_webhook_requests.retain(|_, (_, _, timestamp)| {
122-
timestamp.abs_diff(now.clone()) < self.max_age_secs.as_secs()
123+
Duration::from_secs(timestamp.abs_diff(now.clone())) < self.max_age_secs
124+
});
125+
self.pending_list_webhooks_requests.retain(|_, timestamp| {
126+
Duration::from_secs(timestamp.abs_diff(now.clone())) < self.max_age_secs
123127
});
124-
self.pending_list_webhooks_requests
125-
.retain(|_, timestamp| timestamp.abs_diff(now.clone()) < self.max_age_secs.as_secs());
126128
self.pending_remove_webhook_requests.retain(|_, (_, timestamp)| {
127-
timestamp.abs_diff(now.clone()) < self.max_age_secs.as_secs()
129+
Duration::from_secs(timestamp.abs_diff(now.clone())) < self.max_age_secs
128130
});
129131
}
130132
}
131133

132-
/// Clientside handler for the LSPS5 (bLIP-55) webhook registration protocol.
134+
/// Client-side handler for the LSPS5 (bLIP-55) webhook registration protocol.
133135
///
134136
/// `LSPS5ClientHandler` is the primary interface for LSP clients
135137
/// to register, list, and remove webhook endpoints with an LSP, and to parse
@@ -146,27 +148,27 @@ impl PeerState {
146148
/// [`lsps5.set_webhook`]: super::msgs::LSPS5Request::SetWebhook
147149
/// [`lsps5.list_webhooks`]: super::msgs::LSPS5Request::ListWebhooks
148150
/// [`lsps5.remove_webhook`]: super::msgs::LSPS5Request::RemoveWebhook
149-
pub struct LSPS5ClientHandler<ES: Deref>
151+
pub struct LSPS5ClientHandler<ES: Deref, TP: TimeProvider + Send + Sync>
150152
where
151153
ES::Target: EntropySource,
152154
{
153155
pending_messages: Arc<MessageQueue>,
154156
pending_events: Arc<EventQueue>,
155157
entropy_source: ES,
156-
per_peer_state: RwLock<HashMap<PublicKey, Mutex<PeerState>>>,
158+
per_peer_state: RwLock<HashMap<PublicKey, Mutex<PeerState<TP>>>>,
157159
config: LSPS5ClientConfig,
158-
time_provider: Arc<dyn TimeProvider>,
160+
time_provider: Arc<TP>,
159161
recent_signatures: Mutex<VecDeque<(String, LSPSDateTime)>>,
160162
}
161163

162-
impl<ES: Deref> LSPS5ClientHandler<ES>
164+
impl<ES: Deref, TP: TimeProvider + Send + Sync> LSPS5ClientHandler<ES, TP>
163165
where
164166
ES::Target: EntropySource,
165167
{
166168
/// Constructs an `LSPS5ClientHandler`.
167169
pub(crate) fn new(
168170
entropy_source: ES, pending_messages: Arc<MessageQueue>, pending_events: Arc<EventQueue>,
169-
config: LSPS5ClientConfig, time_provider: Arc<dyn TimeProvider>,
171+
config: LSPS5ClientConfig, time_provider: Arc<TP>,
170172
) -> Self {
171173
let max_signatures = config.signature_config.max_signatures.clone();
172174
Self {
@@ -182,7 +184,7 @@ where
182184

183185
fn with_peer_state<F, R>(&self, counterparty_node_id: PublicKey, f: F) -> R
184186
where
185-
F: FnOnce(&mut PeerState) -> R,
187+
F: FnOnce(&mut PeerState<TP>) -> R,
186188
{
187189
let mut outer_state_lock = self.per_peer_state.write().unwrap();
188190
let inner_state_lock = outer_state_lock.entry(counterparty_node_id).or_insert(Mutex::new(
@@ -347,7 +349,7 @@ where
347349
action: ErrorAction::IgnoreAndLog(Level::Error),
348350
});
349351
let event_queue_notifier = self.pending_events.notifier();
350-
let handle_response = |peer_state: &mut PeerState| {
352+
let handle_response = |peer_state: &mut PeerState<TP>| {
351353
if let Some((app_name, webhook_url, _)) =
352354
peer_state.pending_set_webhook_requests.remove(&request_id)
353355
{
@@ -449,13 +451,13 @@ where
449451
fn verify_notification_signature(
450452
&self, counterparty_node_id: PublicKey, signature_timestamp: &LSPSDateTime,
451453
signature: &str, notification: &WebhookNotification,
452-
) -> Result<bool, LSPS5ClientError> {
454+
) -> Result<(), LSPS5ClientError> {
453455
let now =
454456
LSPSDateTime::new_from_duration_since_epoch(self.time_provider.duration_since_epoch());
455457
let diff = signature_timestamp.abs_diff(now);
456-
let ten_minutes = 600;
457-
if diff > ten_minutes {
458-
return Err(LSPS5ClientError::InvalidTimestamp(signature_timestamp.to_rfc3339()));
458+
const MAX_TIMESTAMP_DRIFT_SECS: u64 = 600;
459+
if diff > MAX_TIMESTAMP_DRIFT_SECS {
460+
return Err(LSPS5ClientError::InvalidTimestamp);
459461
}
460462

461463
let message = format!(
@@ -465,7 +467,7 @@ where
465467
);
466468

467469
if message_signing::verify(message.as_bytes(), signature, &counterparty_node_id) {
468-
Ok(true)
470+
Ok(())
469471
} else {
470472
Err(LSPS5ClientError::InvalidSignature)
471473
}
@@ -490,17 +492,10 @@ where
490492

491493
recent_signatures.push_back((signature, now.clone()));
492494

493-
let retention_duration = self.config.signature_config.retention_minutes * 60;
494-
while let Some((_, time)) = recent_signatures.front() {
495-
if now.abs_diff(time.clone()) > retention_duration.as_secs() {
496-
recent_signatures.pop_front();
497-
} else {
498-
break;
499-
}
500-
}
501-
502-
while recent_signatures.len() > self.config.signature_config.max_signatures {
503-
recent_signatures.pop_front();
495+
let retention_secs = self.config.signature_config.retention_minutes.as_secs();
496+
recent_signatures.retain(|(_, ts)| now.abs_diff(ts.clone()) <= retention_secs);
497+
if recent_signatures.len() > self.config.signature_config.max_signatures {
498+
recent_signatures.truncate(self.config.signature_config.max_signatures);
504499
}
505500
}
506501

@@ -513,15 +508,15 @@ where
513508
/// configured retention window.
514509
/// 4. Reconstructs the exact string
515510
/// `"LSPS5: DO NOT SIGN THIS MESSAGE MANUALLY: LSP: At {timestamp} I notify {body}"`
516-
/// and verifies the zbase32 LN-style signature against the LSPs node ID.
511+
/// and verifies the zbase32 LN-style signature against the LSP's node ID.
517512
///
518513
/// # Parameters
519-
/// - `counterparty_node_id`: the LSPs public key, used to verify the signature.
514+
/// - `counterparty_node_id`: the LSP's public key, used to verify the signature.
520515
/// - `timestamp`: ISO8601 time when the LSP created the notification.
521516
/// - `signature`: the zbase32-encoded LN signature over timestamp+body.
522517
/// - `notification`: the [`WebhookNotification`] received from the LSP.
523518
///
524-
/// On success, emits [`LSPS5ClientEvent::WebhookNotificationReceived`].
519+
/// On success, returns the received [`WebhookNotification`].
525520
///
526521
/// Failure reasons include:
527522
/// - Timestamp too old (drift > 10 minutes)
@@ -532,40 +527,29 @@ where
532527
/// event, before taking action on the notification. This guarantees that only authentic,
533528
/// non-replayed notifications reach your application.
534529
///
535-
/// [`LSPS5ClientEvent::WebhookNotificationReceived`]: super::event::LSPS5ClientEvent::WebhookNotificationReceived
536530
/// [`LSPS5ServiceEvent::SendWebhookNotification`]: super::event::LSPS5ServiceEvent::SendWebhookNotification
537531
/// [`WebhookNotification`]: super::msgs::WebhookNotification
538532
pub fn parse_webhook_notification(
539533
&self, counterparty_node_id: PublicKey, timestamp: &LSPSDateTime, signature: &str,
540534
notification: &WebhookNotification,
541-
) -> Result<(), LSPS5ClientError> {
542-
match self.verify_notification_signature(
535+
) -> Result<WebhookNotification, LSPS5ClientError> {
536+
self.verify_notification_signature(
543537
counterparty_node_id,
544538
timestamp,
545539
signature,
546540
&notification,
547-
) {
548-
Ok(signature_valid) => {
549-
let event_queue_notifier = self.pending_events.notifier();
541+
)?;
550542

551-
self.check_signature_exists(signature)?;
543+
self.check_signature_exists(signature)?;
552544

553-
self.store_signature(signature.to_string());
545+
self.store_signature(signature.to_string());
554546

555-
event_queue_notifier.enqueue(LSPS5ClientEvent::WebhookNotificationReceived {
556-
counterparty_node_id,
557-
notification: notification.clone(),
558-
timestamp: timestamp.clone(),
559-
signature_valid,
560-
});
561-
Ok(())
562-
},
563-
Err(e) => Err(e),
564-
}
547+
Ok(notification.clone())
565548
}
566549
}
567550

568-
impl<ES: Deref> LSPSProtocolMessageHandler for LSPS5ClientHandler<ES>
551+
impl<ES: Deref, TP: TimeProvider + Send + Sync> LSPSProtocolMessageHandler
552+
for LSPS5ClientHandler<ES, TP>
569553
where
570554
ES::Target: EntropySource,
571555
{
@@ -592,8 +576,10 @@ mod tests {
592576
};
593577
use bitcoin::{key::Secp256k1, secp256k1::SecretKey};
594578

595-
fn setup_test_client() -> (
596-
LSPS5ClientHandler<Arc<TestEntropy>>,
579+
fn setup_test_client<TP: TimeProvider + Send + Sync + 'static>(
580+
time_provider: Arc<TP>,
581+
) -> (
582+
LSPS5ClientHandler<Arc<TestEntropy>, TP>,
597583
Arc<MessageQueue>,
598584
Arc<EventQueue>,
599585
PublicKey,
@@ -602,7 +588,6 @@ mod tests {
602588
let test_entropy_source = Arc::new(TestEntropy {});
603589
let message_queue = Arc::new(MessageQueue::new());
604590
let event_queue = Arc::new(EventQueue::new());
605-
let time_provider = Arc::new(DefaultTimeProvider);
606591
let client = LSPS5ClientHandler::new(
607592
test_entropy_source,
608593
message_queue.clone(),
@@ -622,7 +607,7 @@ mod tests {
622607

623608
#[test]
624609
fn test_per_peer_state_isolation() {
625-
let (client, _, _, peer_1, peer_2) = setup_test_client();
610+
let (client, _, _, peer_1, peer_2) = setup_test_client(Arc::new(DefaultTimeProvider));
626611

627612
let req_id_1 = client
628613
.set_webhook(peer_1, "test-app-1".to_string(), "https://example.com/hook1".to_string())
@@ -644,7 +629,7 @@ mod tests {
644629

645630
#[test]
646631
fn test_pending_request_tracking() {
647-
let (client, _, _, peer, _) = setup_test_client();
632+
let (client, _, _, peer, _) = setup_test_client(Arc::new(DefaultTimeProvider));
648633
const APP_NAME: &str = "test-app";
649634
const WEBHOOK_URL: &str = "https://example.com/hook";
650635
let lsps5_app_name = LSPS5AppName::from_string(APP_NAME.to_string()).unwrap();
@@ -677,7 +662,7 @@ mod tests {
677662

678663
#[test]
679664
fn test_handle_response_clears_pending_state() {
680-
let (client, _, _, peer, _) = setup_test_client();
665+
let (client, _, _, peer, _) = setup_test_client(Arc::new(DefaultTimeProvider));
681666

682667
let req_id = client
683668
.set_webhook(peer, "test-app".to_string(), "https://example.com/hook".to_string())
@@ -707,7 +692,7 @@ mod tests {
707692

708693
#[test]
709694
fn test_cleanup_expired_responses() {
710-
let (client, _, _, _, _) = setup_test_client();
695+
let (client, _, _, _, _) = setup_test_client(Arc::new(DefaultTimeProvider));
711696
let time_provider = &client.time_provider;
712697
const OLD_APP_NAME: &str = "test-app-old";
713698
const NEW_APP_NAME: &str = "test-app-new";
@@ -764,7 +749,7 @@ mod tests {
764749

765750
#[test]
766751
fn test_unknown_request_id_handling() {
767-
let (client, _message_queue, _, peer, _) = setup_test_client();
752+
let (client, _message_queue, _, peer, _) = setup_test_client(Arc::new(DefaultTimeProvider));
768753

769754
let _valid_req = client
770755
.set_webhook(peer, "test-app".to_string(), "https://example.com/hook".to_string())

0 commit comments

Comments
 (0)