Skip to content

Commit f3ec9a0

Browse files
Integrate LSPS5 with liquidity manager
Fully integrates the LSPS5 webhook components into the lightning-liquidity framework, enabling usage through the LiquidityManager. It includes - Registering LSPS5 events in the event system - Adding LSPS5 module to the main library exports - Updating LSPS0 serialization to handle LSPS5 messages - Adding LSPS5 configuration options to client and service config structures - Implementing message handling for LSPS5 requests and responses - Adding accessor methods for LSPS5 client and service handlers - LiquidityManager::new_with_custom_time_provider is created so it can be passed to the service and client handlers ::new With this change, LSPS5 webhook functionality can now be accessed through the standard LiquidityManager interface, following the same pattern as other LSPS protocols.
1 parent b24cece commit f3ec9a0

File tree

8 files changed

+286
-4
lines changed

8 files changed

+286
-4
lines changed

lightning-background-processor/src/lib.rs

+14-1
Original file line numberDiff line numberDiff line change
@@ -1142,6 +1142,7 @@ mod tests {
11421142
use lightning::util::sweep::{OutputSpendStatus, OutputSweeper, PRUNE_DELAY_BLOCKS};
11431143
use lightning::util::test_utils;
11441144
use lightning::{get_event, get_event_msg};
1145+
use lightning_liquidity::lsps5::service::TimeProvider;
11451146
use lightning_liquidity::LiquidityManager;
11461147
use lightning_persister::fs_store::FilesystemStore;
11471148
use lightning_rapid_gossip_sync::RapidGossipSync;
@@ -1578,6 +1579,16 @@ mod tests {
15781579
path.to_str().unwrap().to_string()
15791580
}
15801581

1582+
pub struct DefaultTimeProvider;
1583+
1584+
#[cfg(feature = "std")]
1585+
impl TimeProvider for DefaultTimeProvider {
1586+
fn duration_since_epoch(&self) -> Duration {
1587+
use std::time::{SystemTime, UNIX_EPOCH};
1588+
SystemTime::now().duration_since(UNIX_EPOCH).expect("system time before Unix epoch")
1589+
}
1590+
}
1591+
15811592
fn create_nodes(num_nodes: usize, persist_dir: &str) -> (String, Vec<Node>) {
15821593
let persist_temp_path = env::temp_dir().join(persist_dir);
15831594
let persist_dir = persist_temp_path.to_string_lossy().to_string();
@@ -1676,13 +1687,15 @@ mod tests {
16761687
logger.clone(),
16771688
keys_manager.clone(),
16781689
));
1679-
let liquidity_manager = Arc::new(LiquidityManager::new(
1690+
let time_provider = Arc::new(DefaultTimeProvider);
1691+
let liquidity_manager = Arc::new(LiquidityManager::new_with_custom_time_provider(
16801692
Arc::clone(&keys_manager),
16811693
Arc::clone(&manager),
16821694
None,
16831695
None,
16841696
None,
16851697
None,
1698+
time_provider,
16861699
));
16871700
let node = Node {
16881701
node: manager,

lightning-liquidity/src/events/event_queue.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use super::LiquidityEvent;
22
use crate::sync::{Arc, Mutex};
3+
34
use alloc::collections::VecDeque;
45
use alloc::vec::Vec;
56

lightning-liquidity/src/events/mod.rs

+17
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ pub use event_queue::MAX_EVENT_QUEUE_SIZE;
2323
use crate::lsps0;
2424
use crate::lsps1;
2525
use crate::lsps2;
26+
use crate::lsps5;
2627

2728
/// An event which you should probably take some action in response to.
2829
#[derive(Debug, Clone, PartialEq, Eq)]
@@ -38,6 +39,10 @@ pub enum LiquidityEvent {
3839
LSPS2Client(lsps2::event::LSPS2ClientEvent),
3940
/// An LSPS2 (JIT Channel) server event.
4041
LSPS2Service(lsps2::event::LSPS2ServiceEvent),
42+
/// An LSPS5 (Webhook) client event.
43+
LSPS5Client(lsps5::event::LSPS5ClientEvent),
44+
/// An LSPS5 (Webhook) server event.
45+
LSPS5Service(lsps5::event::LSPS5ServiceEvent),
4146
}
4247

4348
impl From<lsps0::event::LSPS0ClientEvent> for LiquidityEvent {
@@ -70,3 +75,15 @@ impl From<lsps2::event::LSPS2ServiceEvent> for LiquidityEvent {
7075
Self::LSPS2Service(event)
7176
}
7277
}
78+
79+
impl From<lsps5::event::LSPS5ClientEvent> for LiquidityEvent {
80+
fn from(event: lsps5::event::LSPS5ClientEvent) -> Self {
81+
Self::LSPS5Client(event)
82+
}
83+
}
84+
85+
impl From<lsps5::event::LSPS5ServiceEvent> for LiquidityEvent {
86+
fn from(event: lsps5::event::LSPS5ServiceEvent) -> Self {
87+
Self::LSPS5Service(event)
88+
}
89+
}

lightning-liquidity/src/lib.rs

+4
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
//! an LSP will open a "just-in-time" channel. This is useful for the initial on-boarding of
2424
//! clients as the channel opening fees are deducted from the incoming payment, i.e., no funds are
2525
//! required client-side to initiate this flow.
26+
//! - [bLIP-55 / LSPS5] defines a protocol for sending webhook notifications to clients. This is
27+
//! useful for notifying clients about incoming payments, channel expiries, etc.
2628
//!
2729
//! To get started, you'll want to setup a [`LiquidityManager`] and configure it to be the
2830
//! [`CustomMessageHandler`] of your LDK node. You can then for example call
@@ -37,6 +39,7 @@
3739
//! [bLIP-50 / LSPS0]: https://github.com/lightning/blips/blob/master/blip-0050.md
3840
//! [bLIP-51 / LSPS1]: https://github.com/lightning/blips/blob/master/blip-0051.md
3941
//! [bLIP-52 / LSPS2]: https://github.com/lightning/blips/blob/master/blip-0052.md
42+
//! [bLIP-55 / LSPS5]: https://github.com/lightning/blips/pull/55/files
4043
//! [`CustomMessageHandler`]: lightning::ln::peer_handler::CustomMessageHandler
4144
//! [`LiquidityManager::next_event`]: crate::LiquidityManager::next_event
4245
#![deny(missing_docs)]
@@ -59,6 +62,7 @@ pub mod events;
5962
pub mod lsps0;
6063
pub mod lsps1;
6164
pub mod lsps2;
65+
pub mod lsps5;
6266
mod manager;
6367
pub mod message_queue;
6468
#[allow(dead_code)]

lightning-liquidity/src/lsps0/msgs.rs

+1
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ impl TryFrom<LSPSMessage> for LSPS0Message {
8383
LSPSMessage::LSPS0(message) => Ok(message),
8484
LSPSMessage::LSPS1(_) => Err(()),
8585
LSPSMessage::LSPS2(_) => Err(()),
86+
LSPSMessage::LSPS5(_) => Err(()),
8687
}
8788
}
8889
}

lightning-liquidity/src/lsps0/ser.rs

+147-3
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,13 @@ use crate::lsps1::msgs::{
2121
use crate::lsps2::msgs::{
2222
LSPS2Message, LSPS2Request, LSPS2Response, LSPS2_BUY_METHOD_NAME, LSPS2_GET_INFO_METHOD_NAME,
2323
};
24+
use crate::lsps5::msgs::{
25+
LSPS5Message, LSPS5Request, LSPS5Response, LSPS5_LIST_WEBHOOKS_METHOD_NAME,
26+
LSPS5_REMOVE_WEBHOOK_METHOD_NAME, LSPS5_SET_WEBHOOK_METHOD_NAME,
27+
};
28+
2429
use crate::prelude::HashMap;
2530

26-
use chrono::DateTime;
2731
use lightning::ln::msgs::{DecodeError, LightningError};
2832
use lightning::ln::wire;
2933
use lightning::util::ser::{LengthLimitedRead, LengthReadable, WithoutLength};
@@ -62,6 +66,9 @@ pub(crate) enum LSPSMethod {
6266
LSPS1CreateOrder,
6367
LSPS2GetInfo,
6468
LSPS2Buy,
69+
LSPS5SetWebhook,
70+
LSPS5ListWebhooks,
71+
LSPS5RemoveWebhook,
6572
}
6673

6774
impl LSPSMethod {
@@ -73,6 +80,9 @@ impl LSPSMethod {
7380
Self::LSPS1GetOrder => LSPS1_GET_ORDER_METHOD_NAME,
7481
Self::LSPS2GetInfo => LSPS2_GET_INFO_METHOD_NAME,
7582
Self::LSPS2Buy => LSPS2_BUY_METHOD_NAME,
83+
Self::LSPS5SetWebhook => LSPS5_SET_WEBHOOK_METHOD_NAME,
84+
Self::LSPS5ListWebhooks => LSPS5_LIST_WEBHOOKS_METHOD_NAME,
85+
Self::LSPS5RemoveWebhook => LSPS5_REMOVE_WEBHOOK_METHOD_NAME,
7686
}
7787
}
7888
}
@@ -87,6 +97,9 @@ impl FromStr for LSPSMethod {
8797
LSPS1_GET_ORDER_METHOD_NAME => Ok(Self::LSPS1GetOrder),
8898
LSPS2_GET_INFO_METHOD_NAME => Ok(Self::LSPS2GetInfo),
8999
LSPS2_BUY_METHOD_NAME => Ok(Self::LSPS2Buy),
100+
LSPS5_SET_WEBHOOK_METHOD_NAME => Ok(Self::LSPS5SetWebhook),
101+
LSPS5_LIST_WEBHOOKS_METHOD_NAME => Ok(Self::LSPS5ListWebhooks),
102+
LSPS5_REMOVE_WEBHOOK_METHOD_NAME => Ok(Self::LSPS5RemoveWebhook),
90103
_ => Err(&"Unknown method name"),
91104
}
92105
}
@@ -119,6 +132,16 @@ impl From<&LSPS2Request> for LSPSMethod {
119132
}
120133
}
121134

135+
impl From<&LSPS5Request> for LSPSMethod {
136+
fn from(value: &LSPS5Request) -> Self {
137+
match value {
138+
LSPS5Request::SetWebhook(_) => Self::LSPS5SetWebhook,
139+
LSPS5Request::ListWebhooks(_) => Self::LSPS5ListWebhooks,
140+
LSPS5Request::RemoveWebhook(_) => Self::LSPS5RemoveWebhook,
141+
}
142+
}
143+
}
144+
122145
impl<'de> Deserialize<'de> for LSPSMethod {
123146
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
124147
where
@@ -218,13 +241,13 @@ impl LSPSDateTime {
218241
}
219242

220243
/// Returns the time in seconds since the unix epoch.
221-
pub fn abs_diff(&self, other: &Self) -> u64 {
244+
pub fn abs_diff(&self, other: Self) -> u64 {
222245
self.0.timestamp().abs_diff(other.0.timestamp())
223246
}
224247

225248
/// Returns the time in seconds since the unix epoch.
226249
pub fn new_from_duration_since_epoch(duration: Duration) -> Self {
227-
Self(DateTime::UNIX_EPOCH + duration)
250+
Self(chrono::DateTime::UNIX_EPOCH + duration)
228251
}
229252
}
230253

@@ -267,6 +290,8 @@ pub enum LSPSMessage {
267290
LSPS1(LSPS1Message),
268291
/// An LSPS2 message.
269292
LSPS2(LSPS2Message),
293+
/// An LSPS5 message.
294+
LSPS5(LSPS5Message),
270295
}
271296

272297
impl LSPSMessage {
@@ -294,6 +319,9 @@ impl LSPSMessage {
294319
LSPSMessage::LSPS2(LSPS2Message::Request(request_id, request)) => {
295320
Some((LSPSRequestId(request_id.0.clone()), request.into()))
296321
},
322+
LSPSMessage::LSPS5(LSPS5Message::Request(request_id, request)) => {
323+
Some((LSPSRequestId(request_id.0.clone()), request.into()))
324+
},
297325
_ => None,
298326
}
299327
}
@@ -410,6 +438,47 @@ impl Serialize for LSPSMessage {
410438
jsonrpc_object.serialize_field(JSONRPC_ID_FIELD_KEY, &serde_json::Value::Null)?;
411439
jsonrpc_object.serialize_field(JSONRPC_ERROR_FIELD_KEY, &error)?;
412440
},
441+
LSPSMessage::LSPS5(LSPS5Message::Request(request_id, request)) => {
442+
jsonrpc_object.serialize_field(JSONRPC_ID_FIELD_KEY, &request_id.0)?;
443+
jsonrpc_object
444+
.serialize_field(JSONRPC_METHOD_FIELD_KEY, &LSPSMethod::from(request))?;
445+
446+
match request {
447+
LSPS5Request::SetWebhook(params) => {
448+
jsonrpc_object.serialize_field(JSONRPC_PARAMS_FIELD_KEY, params)?
449+
},
450+
LSPS5Request::ListWebhooks(params) => {
451+
jsonrpc_object.serialize_field(JSONRPC_PARAMS_FIELD_KEY, params)?
452+
},
453+
LSPS5Request::RemoveWebhook(params) => {
454+
jsonrpc_object.serialize_field(JSONRPC_PARAMS_FIELD_KEY, params)?
455+
},
456+
}
457+
},
458+
LSPSMessage::LSPS5(LSPS5Message::Response(request_id, response)) => {
459+
jsonrpc_object.serialize_field(JSONRPC_ID_FIELD_KEY, &request_id.0)?;
460+
461+
match response {
462+
LSPS5Response::SetWebhook(result) => {
463+
jsonrpc_object.serialize_field(JSONRPC_RESULT_FIELD_KEY, result)?
464+
},
465+
LSPS5Response::SetWebhookError(error) => {
466+
jsonrpc_object.serialize_field(JSONRPC_ERROR_FIELD_KEY, error)?
467+
},
468+
LSPS5Response::ListWebhooks(result) => {
469+
jsonrpc_object.serialize_field(JSONRPC_RESULT_FIELD_KEY, result)?
470+
},
471+
LSPS5Response::ListWebhooksError(error) => {
472+
jsonrpc_object.serialize_field(JSONRPC_ERROR_FIELD_KEY, error)?
473+
},
474+
LSPS5Response::RemoveWebhook(result) => {
475+
jsonrpc_object.serialize_field(JSONRPC_RESULT_FIELD_KEY, result)?
476+
},
477+
LSPS5Response::RemoveWebhookError(error) => {
478+
jsonrpc_object.serialize_field(JSONRPC_ERROR_FIELD_KEY, error)?
479+
},
480+
}
481+
},
413482
}
414483

415484
jsonrpc_object.end()
@@ -523,6 +592,30 @@ impl<'de, 'a> Visitor<'de> for LSPSMessageVisitor<'a> {
523592
.map_err(de::Error::custom)?;
524593
Ok(LSPSMessage::LSPS2(LSPS2Message::Request(id, LSPS2Request::Buy(request))))
525594
},
595+
LSPSMethod::LSPS5SetWebhook => {
596+
let request = serde_json::from_value(params.unwrap_or(json!({})))
597+
.map_err(de::Error::custom)?;
598+
Ok(LSPSMessage::LSPS5(LSPS5Message::Request(
599+
id,
600+
LSPS5Request::SetWebhook(request),
601+
)))
602+
},
603+
LSPSMethod::LSPS5ListWebhooks => {
604+
let request = serde_json::from_value(params.unwrap_or(json!({})))
605+
.map_err(de::Error::custom)?;
606+
Ok(LSPSMessage::LSPS5(LSPS5Message::Request(
607+
id,
608+
LSPS5Request::ListWebhooks(request),
609+
)))
610+
},
611+
LSPSMethod::LSPS5RemoveWebhook => {
612+
let request = serde_json::from_value(params.unwrap_or(json!({})))
613+
.map_err(de::Error::custom)?;
614+
Ok(LSPSMessage::LSPS5(LSPS5Message::Request(
615+
id,
616+
LSPS5Request::RemoveWebhook(request),
617+
)))
618+
},
526619
},
527620
None => match self.request_id_to_method_map.remove(&id) {
528621
Some(method) => match method {
@@ -628,6 +721,57 @@ impl<'de, 'a> Visitor<'de> for LSPSMessageVisitor<'a> {
628721
Err(de::Error::custom("Received invalid JSON-RPC object: one of method, result, or error required"))
629722
}
630723
},
724+
LSPSMethod::LSPS5SetWebhook => {
725+
if let Some(error) = error {
726+
Ok(LSPSMessage::LSPS5(LSPS5Message::Response(
727+
id,
728+
LSPS5Response::SetWebhookError(error.into()),
729+
)))
730+
} else if let Some(result) = result {
731+
let response =
732+
serde_json::from_value(result).map_err(de::Error::custom)?;
733+
Ok(LSPSMessage::LSPS5(LSPS5Message::Response(
734+
id,
735+
LSPS5Response::SetWebhook(response),
736+
)))
737+
} else {
738+
Err(de::Error::custom("Received invalid JSON-RPC object: one of method, result, or error required"))
739+
}
740+
},
741+
LSPSMethod::LSPS5ListWebhooks => {
742+
if let Some(error) = error {
743+
Ok(LSPSMessage::LSPS5(LSPS5Message::Response(
744+
id,
745+
LSPS5Response::ListWebhooksError(error.into()),
746+
)))
747+
} else if let Some(result) = result {
748+
let response =
749+
serde_json::from_value(result).map_err(de::Error::custom)?;
750+
Ok(LSPSMessage::LSPS5(LSPS5Message::Response(
751+
id,
752+
LSPS5Response::ListWebhooks(response),
753+
)))
754+
} else {
755+
Err(de::Error::custom("Received invalid JSON-RPC object: one of method, result, or error required"))
756+
}
757+
},
758+
LSPSMethod::LSPS5RemoveWebhook => {
759+
if let Some(error) = error {
760+
Ok(LSPSMessage::LSPS5(LSPS5Message::Response(
761+
id,
762+
LSPS5Response::RemoveWebhookError(error.into()),
763+
)))
764+
} else if let Some(result) = result {
765+
let response =
766+
serde_json::from_value(result).map_err(de::Error::custom)?;
767+
Ok(LSPSMessage::LSPS5(LSPS5Message::Response(
768+
id,
769+
LSPS5Response::RemoveWebhook(response),
770+
)))
771+
} else {
772+
Err(de::Error::custom("Received invalid JSON-RPC object: one of method, result, or error required"))
773+
}
774+
},
631775
},
632776
None => Err(de::Error::custom(format!(
633777
"Received response for unknown request id: {}",

0 commit comments

Comments
 (0)