Skip to content

Commit 01e21d8

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 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 c339a29 commit 01e21d8

File tree

6 files changed

+287
-3
lines changed

6 files changed

+287
-3
lines changed

lightning-liquidity/src/events.rs

+18
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@
1818
use crate::lsps0;
1919
use crate::lsps1;
2020
use crate::lsps2;
21+
use crate::lsps5;
2122
use crate::sync::{Arc, Mutex};
23+
2224
use alloc::collections::VecDeque;
2325
use alloc::vec::Vec;
2426

@@ -117,6 +119,10 @@ pub enum LiquidityEvent {
117119
LSPS2Client(lsps2::event::LSPS2ClientEvent),
118120
/// An LSPS2 (JIT Channel) server event.
119121
LSPS2Service(lsps2::event::LSPS2ServiceEvent),
122+
/// An LSPS5 (Webhook) client event.
123+
LSPS5Client(lsps5::event::LSPS5ClientEvent),
124+
/// An LSPS5 (Webhook) server event.
125+
LSPS5Service(lsps5::event::LSPS5ServiceEvent),
120126
}
121127

122128
impl From<lsps0::event::LSPS0ClientEvent> for LiquidityEvent {
@@ -150,6 +156,18 @@ impl From<lsps2::event::LSPS2ServiceEvent> for LiquidityEvent {
150156
}
151157
}
152158

159+
impl From<lsps5::event::LSPS5ClientEvent> for LiquidityEvent {
160+
fn from(event: lsps5::event::LSPS5ClientEvent) -> Self {
161+
Self::LSPS5Client(event)
162+
}
163+
}
164+
165+
impl From<lsps5::event::LSPS5ServiceEvent> for LiquidityEvent {
166+
fn from(event: lsps5::event::LSPS5ServiceEvent) -> Self {
167+
Self::LSPS5Service(event)
168+
}
169+
}
170+
153171
struct EventFuture {
154172
event_queue: Arc<Mutex<VecDeque<LiquidityEvent>>>,
155173
waker: Arc<Mutex<Option<Waker>>>,

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

+148-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,31 @@ 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+
// Add LSPS5 methods
596+
LSPSMethod::LSPS5SetWebhook => {
597+
let request = serde_json::from_value(params.unwrap_or(json!({})))
598+
.map_err(de::Error::custom)?;
599+
Ok(LSPSMessage::LSPS5(LSPS5Message::Request(
600+
id,
601+
LSPS5Request::SetWebhook(request),
602+
)))
603+
},
604+
LSPSMethod::LSPS5ListWebhooks => {
605+
let request = serde_json::from_value(params.unwrap_or(json!({})))
606+
.map_err(de::Error::custom)?;
607+
Ok(LSPSMessage::LSPS5(LSPS5Message::Request(
608+
id,
609+
LSPS5Request::ListWebhooks(request),
610+
)))
611+
},
612+
LSPSMethod::LSPS5RemoveWebhook => {
613+
let request = serde_json::from_value(params.unwrap_or(json!({})))
614+
.map_err(de::Error::custom)?;
615+
Ok(LSPSMessage::LSPS5(LSPS5Message::Request(
616+
id,
617+
LSPS5Request::RemoveWebhook(request),
618+
)))
619+
},
526620
},
527621
None => match self.request_id_to_method_map.remove(&id) {
528622
Some(method) => match method {
@@ -628,6 +722,57 @@ impl<'de, 'a> Visitor<'de> for LSPSMessageVisitor<'a> {
628722
Err(de::Error::custom("Received invalid JSON-RPC object: one of method, result, or error required"))
629723
}
630724
},
725+
LSPSMethod::LSPS5SetWebhook => {
726+
if let Some(error) = error {
727+
Ok(LSPSMessage::LSPS5(LSPS5Message::Response(
728+
id,
729+
LSPS5Response::SetWebhookError(error.into()),
730+
)))
731+
} else if let Some(result) = result {
732+
let response =
733+
serde_json::from_value(result).map_err(de::Error::custom)?;
734+
Ok(LSPSMessage::LSPS5(LSPS5Message::Response(
735+
id,
736+
LSPS5Response::SetWebhook(response),
737+
)))
738+
} else {
739+
Err(de::Error::custom("Received invalid JSON-RPC object: one of method, result, or error required"))
740+
}
741+
},
742+
LSPSMethod::LSPS5ListWebhooks => {
743+
if let Some(error) = error {
744+
Ok(LSPSMessage::LSPS5(LSPS5Message::Response(
745+
id,
746+
LSPS5Response::ListWebhooksError(error.into()),
747+
)))
748+
} else if let Some(result) = result {
749+
let response =
750+
serde_json::from_value(result).map_err(de::Error::custom)?;
751+
Ok(LSPSMessage::LSPS5(LSPS5Message::Response(
752+
id,
753+
LSPS5Response::ListWebhooks(response),
754+
)))
755+
} else {
756+
Err(de::Error::custom("Received invalid JSON-RPC object: one of method, result, or error required"))
757+
}
758+
},
759+
LSPSMethod::LSPS5RemoveWebhook => {
760+
if let Some(error) = error {
761+
Ok(LSPSMessage::LSPS5(LSPS5Message::Response(
762+
id,
763+
LSPS5Response::RemoveWebhookError(error.into()),
764+
)))
765+
} else if let Some(result) = result {
766+
let response =
767+
serde_json::from_value(result).map_err(de::Error::custom)?;
768+
Ok(LSPSMessage::LSPS5(LSPS5Message::Response(
769+
id,
770+
LSPS5Response::RemoveWebhook(response),
771+
)))
772+
} else {
773+
Err(de::Error::custom("Received invalid JSON-RPC object: one of method, result, or error required"))
774+
}
775+
},
631776
},
632777
None => Err(de::Error::custom(format!(
633778
"Received response for unknown request id: {}",

0 commit comments

Comments
 (0)