Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit dabada3

Browse files
committedMar 17, 2025
add webhook message support in serializer, events, and liquidity manager
- Update events.rs to include LSPS5 client and service event variants and From conversions. - Enhance lib.rs documentation to reference bLIP-55 / LSPS5 protocol support. - Modify manager.rs to initialize and route LSPS5 client and service handlers in LiquidityService. - Extend lsps0/ser.rs to support LSPS5 methods in LSPSMethod and LSPSMessage (de)serialization for SetWebhook, ListWebhooks, and RemoveWebhook. - Extend the match in TryFrom<LSPSMessage> to handle LSPSMessage::LSPS5 and explicitly return an error.
1 parent 905be83 commit dabada3

File tree

5 files changed

+242
-0
lines changed

5 files changed

+242
-0
lines changed
 

‎lightning-liquidity/src/events.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
use crate::lsps0;
1919
use crate::lsps1;
2020
use crate::lsps2;
21+
use crate::lsps5;
2122
use crate::prelude::{Vec, VecDeque};
2223
use crate::sync::{Arc, Mutex};
2324

@@ -116,6 +117,10 @@ pub enum LiquidityEvent {
116117
LSPS2Client(lsps2::event::LSPS2ClientEvent),
117118
/// An LSPS2 (JIT Channel) server event.
118119
LSPS2Service(lsps2::event::LSPS2ServiceEvent),
120+
/// An LSPS5 (Webhook) client event.
121+
LSPS5Client(lsps5::event::LSPS5ClientEvent),
122+
/// An LSPS5 (Webhook) server event.
123+
LSPS5Service(lsps5::event::LSPS5ServiceEvent),
119124
}
120125

121126
impl From<lsps0::event::LSPS0ClientEvent> for LiquidityEvent {
@@ -149,6 +154,18 @@ impl From<lsps2::event::LSPS2ServiceEvent> for LiquidityEvent {
149154
}
150155
}
151156

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

‎lightning-liquidity/src/lib.rs

Lines changed: 4 additions & 0 deletions
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)]
@@ -65,6 +68,7 @@ pub mod events;
6568
pub mod lsps0;
6669
pub mod lsps1;
6770
pub mod lsps2;
71+
pub mod lsps5;
6872
mod manager;
6973
pub mod message_queue;
7074
#[allow(dead_code)]

‎lightning-liquidity/src/lsps0/msgs.rs

Lines changed: 1 addition & 0 deletions
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

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ use crate::lsps1::msgs::{
1616
use crate::lsps2::msgs::{
1717
LSPS2Message, LSPS2Request, LSPS2Response, LSPS2_BUY_METHOD_NAME, LSPS2_GET_INFO_METHOD_NAME,
1818
};
19+
use crate::lsps5::msgs::{
20+
LSPS5Message, LSPS5Request, LSPS5Response, LSPS5_LIST_WEBHOOKS_METHOD_NAME,
21+
LSPS5_REMOVE_WEBHOOK_METHOD_NAME, LSPS5_SET_WEBHOOK_METHOD_NAME,
22+
};
1923
use crate::prelude::{HashMap, String};
2024

2125
use lightning::ln::msgs::LightningError;
@@ -55,6 +59,9 @@ pub(crate) enum LSPSMethod {
5559
LSPS1CreateOrder,
5660
LSPS2GetInfo,
5761
LSPS2Buy,
62+
LSPS5SetWebhook,
63+
LSPS5ListWebhooks,
64+
LSPS5RemoveWebhook,
5865
}
5966

6067
impl LSPSMethod {
@@ -66,6 +73,9 @@ impl LSPSMethod {
6673
Self::LSPS1GetOrder => LSPS1_GET_ORDER_METHOD_NAME,
6774
Self::LSPS2GetInfo => LSPS2_GET_INFO_METHOD_NAME,
6875
Self::LSPS2Buy => LSPS2_BUY_METHOD_NAME,
76+
Self::LSPS5SetWebhook => LSPS5_SET_WEBHOOK_METHOD_NAME,
77+
Self::LSPS5ListWebhooks => LSPS5_LIST_WEBHOOKS_METHOD_NAME,
78+
Self::LSPS5RemoveWebhook => LSPS5_REMOVE_WEBHOOK_METHOD_NAME,
6979
}
7080
}
7181
}
@@ -80,6 +90,10 @@ impl FromStr for LSPSMethod {
8090
LSPS1_GET_ORDER_METHOD_NAME => Ok(Self::LSPS1GetOrder),
8191
LSPS2_GET_INFO_METHOD_NAME => Ok(Self::LSPS2GetInfo),
8292
LSPS2_BUY_METHOD_NAME => Ok(Self::LSPS2Buy),
93+
// Add LSPS5 methods
94+
LSPS5_SET_WEBHOOK_METHOD_NAME => Ok(Self::LSPS5SetWebhook),
95+
LSPS5_LIST_WEBHOOKS_METHOD_NAME => Ok(Self::LSPS5ListWebhooks),
96+
LSPS5_REMOVE_WEBHOOK_METHOD_NAME => Ok(Self::LSPS5RemoveWebhook),
8397
_ => Err(&"Unknown method name"),
8498
}
8599
}
@@ -112,6 +126,17 @@ impl From<&LSPS2Request> for LSPSMethod {
112126
}
113127
}
114128

129+
// Add implementation for LSPS5Request
130+
impl From<&LSPS5Request> for LSPSMethod {
131+
fn from(value: &LSPS5Request) -> Self {
132+
match value {
133+
LSPS5Request::SetWebhook(_) => Self::LSPS5SetWebhook,
134+
LSPS5Request::ListWebhooks(_) => Self::LSPS5ListWebhooks,
135+
LSPS5Request::RemoveWebhook(_) => Self::LSPS5RemoveWebhook,
136+
}
137+
}
138+
}
139+
115140
impl<'de> Deserialize<'de> for LSPSMethod {
116141
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
117142
where
@@ -269,6 +294,8 @@ pub enum LSPSMessage {
269294
LSPS1(LSPS1Message),
270295
/// An LSPS2 message.
271296
LSPS2(LSPS2Message),
297+
/// An LSPS5 message.
298+
LSPS5(LSPS5Message),
272299
}
273300

274301
impl LSPSMessage {
@@ -296,6 +323,10 @@ impl LSPSMessage {
296323
LSPSMessage::LSPS2(LSPS2Message::Request(request_id, request)) => {
297324
Some((LSPSRequestId(request_id.0.clone()), request.into()))
298325
},
326+
// Add LSPS5
327+
LSPSMessage::LSPS5(LSPS5Message::Request(request_id, request)) => {
328+
Some((LSPSRequestId(request_id.0.clone()), request.into()))
329+
},
299330
_ => None,
300331
}
301332
}
@@ -412,6 +443,47 @@ impl Serialize for LSPSMessage {
412443
jsonrpc_object.serialize_field(JSONRPC_ID_FIELD_KEY, &serde_json::Value::Null)?;
413444
jsonrpc_object.serialize_field(JSONRPC_ERROR_FIELD_KEY, &error)?;
414445
},
446+
LSPSMessage::LSPS5(LSPS5Message::Request(request_id, request)) => {
447+
jsonrpc_object.serialize_field(JSONRPC_ID_FIELD_KEY, &request_id.0)?;
448+
jsonrpc_object
449+
.serialize_field(JSONRPC_METHOD_FIELD_KEY, &LSPSMethod::from(request))?;
450+
451+
match request {
452+
LSPS5Request::SetWebhook(params) => {
453+
jsonrpc_object.serialize_field(JSONRPC_PARAMS_FIELD_KEY, params)?
454+
},
455+
LSPS5Request::ListWebhooks(params) => {
456+
jsonrpc_object.serialize_field(JSONRPC_PARAMS_FIELD_KEY, params)?
457+
},
458+
LSPS5Request::RemoveWebhook(params) => {
459+
jsonrpc_object.serialize_field(JSONRPC_PARAMS_FIELD_KEY, params)?
460+
},
461+
}
462+
},
463+
LSPSMessage::LSPS5(LSPS5Message::Response(request_id, response)) => {
464+
jsonrpc_object.serialize_field(JSONRPC_ID_FIELD_KEY, &request_id.0)?;
465+
466+
match response {
467+
LSPS5Response::SetWebhook(result) => {
468+
jsonrpc_object.serialize_field(JSONRPC_RESULT_FIELD_KEY, result)?
469+
},
470+
LSPS5Response::SetWebhookError(error) => {
471+
jsonrpc_object.serialize_field(JSONRPC_ERROR_FIELD_KEY, error)?
472+
},
473+
LSPS5Response::ListWebhooks(result) => {
474+
jsonrpc_object.serialize_field(JSONRPC_RESULT_FIELD_KEY, result)?
475+
},
476+
LSPS5Response::ListWebhooksError(error) => {
477+
jsonrpc_object.serialize_field(JSONRPC_ERROR_FIELD_KEY, error)?
478+
},
479+
LSPS5Response::RemoveWebhook(result) => {
480+
jsonrpc_object.serialize_field(JSONRPC_RESULT_FIELD_KEY, result)?
481+
},
482+
LSPS5Response::RemoveWebhookError(error) => {
483+
jsonrpc_object.serialize_field(JSONRPC_ERROR_FIELD_KEY, error)?
484+
},
485+
}
486+
},
415487
}
416488

417489
jsonrpc_object.end()
@@ -525,6 +597,31 @@ impl<'de, 'a> Visitor<'de> for LSPSMessageVisitor<'a> {
525597
.map_err(de::Error::custom)?;
526598
Ok(LSPSMessage::LSPS2(LSPS2Message::Request(id, LSPS2Request::Buy(request))))
527599
},
600+
// Add LSPS5 methods
601+
LSPSMethod::LSPS5SetWebhook => {
602+
let request = serde_json::from_value(params.unwrap_or(json!({})))
603+
.map_err(de::Error::custom)?;
604+
Ok(LSPSMessage::LSPS5(LSPS5Message::Request(
605+
id,
606+
LSPS5Request::SetWebhook(request),
607+
)))
608+
},
609+
LSPSMethod::LSPS5ListWebhooks => {
610+
let request = serde_json::from_value(params.unwrap_or(json!({})))
611+
.map_err(de::Error::custom)?;
612+
Ok(LSPSMessage::LSPS5(LSPS5Message::Request(
613+
id,
614+
LSPS5Request::ListWebhooks(request),
615+
)))
616+
},
617+
LSPSMethod::LSPS5RemoveWebhook => {
618+
let request = serde_json::from_value(params.unwrap_or(json!({})))
619+
.map_err(de::Error::custom)?;
620+
Ok(LSPSMessage::LSPS5(LSPS5Message::Request(
621+
id,
622+
LSPS5Request::RemoveWebhook(request),
623+
)))
624+
},
528625
},
529626
None => match self.request_id_to_method_map.remove(&id) {
530627
Some(method) => match method {
@@ -630,6 +727,58 @@ impl<'de, 'a> Visitor<'de> for LSPSMessageVisitor<'a> {
630727
Err(de::Error::custom("Received invalid JSON-RPC object: one of method, result, or error required"))
631728
}
632729
},
730+
// Add LSPS5 methods
731+
LSPSMethod::LSPS5SetWebhook => {
732+
if let Some(error) = error {
733+
Ok(LSPSMessage::LSPS5(LSPS5Message::Response(
734+
id,
735+
LSPS5Response::SetWebhookError(error),
736+
)))
737+
} else if let Some(result) = result {
738+
let response =
739+
serde_json::from_value(result).map_err(de::Error::custom)?;
740+
Ok(LSPSMessage::LSPS5(LSPS5Message::Response(
741+
id,
742+
LSPS5Response::SetWebhook(response),
743+
)))
744+
} else {
745+
Err(de::Error::custom("Received invalid JSON-RPC object: one of method, result, or error required"))
746+
}
747+
},
748+
LSPSMethod::LSPS5ListWebhooks => {
749+
if let Some(error) = error {
750+
Ok(LSPSMessage::LSPS5(LSPS5Message::Response(
751+
id,
752+
LSPS5Response::ListWebhooksError(error),
753+
)))
754+
} else if let Some(result) = result {
755+
let response =
756+
serde_json::from_value(result).map_err(de::Error::custom)?;
757+
Ok(LSPSMessage::LSPS5(LSPS5Message::Response(
758+
id,
759+
LSPS5Response::ListWebhooks(response),
760+
)))
761+
} else {
762+
Err(de::Error::custom("Received invalid JSON-RPC object: one of method, result, or error required"))
763+
}
764+
},
765+
LSPSMethod::LSPS5RemoveWebhook => {
766+
if let Some(error) = error {
767+
Ok(LSPSMessage::LSPS5(LSPS5Message::Response(
768+
id,
769+
LSPS5Response::RemoveWebhookError(error),
770+
)))
771+
} else if let Some(result) = result {
772+
let response =
773+
serde_json::from_value(result).map_err(de::Error::custom)?;
774+
Ok(LSPSMessage::LSPS5(LSPS5Message::Response(
775+
id,
776+
LSPS5Response::RemoveWebhook(response),
777+
)))
778+
} else {
779+
Err(de::Error::custom("Received invalid JSON-RPC object: one of method, result, or error required"))
780+
}
781+
},
633782
},
634783
None => Err(de::Error::custom(format!(
635784
"Received response for unknown request id: {}",

‎lightning-liquidity/src/manager.rs

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ use crate::lsps0::ser::{
77
LSPS_MESSAGE_TYPE_ID,
88
};
99
use crate::lsps0::service::LSPS0ServiceHandler;
10+
use crate::lsps5::client::{LSPS5ClientConfig, LSPS5ClientHandler};
11+
use crate::lsps5::msgs::LSPS5Message;
12+
use crate::lsps5::service::{LSPS5ServiceConfig, LSPS5ServiceHandler};
1013
use crate::message_queue::{MessageQueue, ProcessMessagesCallback};
1114

1215
use crate::lsps1::client::{LSPS1ClientConfig, LSPS1ClientHandler};
@@ -48,6 +51,8 @@ pub struct LiquidityServiceConfig {
4851
/// Optional server-side configuration for JIT channels
4952
/// should you want to support them.
5053
pub lsps2_service_config: Option<LSPS2ServiceConfig>,
54+
/// Optional server-side configuration for LSPS5 webhook service.
55+
pub lsps5_service_config: Option<LSPS5ServiceConfig>,
5156
/// Controls whether the liquidity service should be advertised via setting the feature bit in
5257
/// node announcment and the init message.
5358
pub advertise_service: bool,
@@ -62,6 +67,8 @@ pub struct LiquidityClientConfig {
6267
pub lsps1_client_config: Option<LSPS1ClientConfig>,
6368
/// Optional client-side configuration for JIT channels.
6469
pub lsps2_client_config: Option<LSPS2ClientConfig>,
70+
/// Optional client-side configuration for LSPS5 webhook service.
71+
pub lsps5_client_config: Option<LSPS5ClientConfig>,
6572
}
6673

6774
/// The main interface into LSP functionality.
@@ -105,6 +112,8 @@ where
105112
lsps1_client_handler: Option<LSPS1ClientHandler<ES>>,
106113
lsps2_service_handler: Option<LSPS2ServiceHandler<CM>>,
107114
lsps2_client_handler: Option<LSPS2ClientHandler<ES>>,
115+
lsps5_service_handler: Option<LSPS5ServiceHandler>,
116+
lsps5_client_handler: Option<LSPS5ClientHandler<ES>>,
108117
service_config: Option<LiquidityServiceConfig>,
109118
_client_config: Option<LiquidityClientConfig>,
110119
best_block: RwLock<Option<BestBlock>>,
@@ -159,6 +168,32 @@ where {
159168
})
160169
});
161170

171+
let lsps5_client_handler = client_config.as_ref().and_then(|config| {
172+
config.lsps5_client_config.as_ref().map(|config| {
173+
LSPS5ClientHandler::new(
174+
entropy_source.clone(),
175+
Arc::clone(&pending_messages),
176+
Arc::clone(&pending_events),
177+
config.clone(),
178+
)
179+
})
180+
});
181+
182+
let lsps5_service_handler = service_config.as_ref().and_then(|config| {
183+
config.lsps5_service_config.as_ref().map(|config| {
184+
if let Some(number) =
185+
<LSPS5ServiceHandler as LSPSProtocolMessageHandler>::PROTOCOL_NUMBER
186+
{
187+
supported_protocols.push(number);
188+
}
189+
LSPS5ServiceHandler::new(
190+
Arc::clone(&pending_events),
191+
Arc::clone(&pending_messages),
192+
config.clone(),
193+
)
194+
})
195+
});
196+
162197
let lsps1_client_handler = client_config.as_ref().and_then(|config| {
163198
config.lsps1_client_config.as_ref().map(|config| {
164199
LSPS1ClientHandler::new(
@@ -213,6 +248,8 @@ where {
213248
lsps1_service_handler,
214249
lsps2_client_handler,
215250
lsps2_service_handler,
251+
lsps5_client_handler,
252+
lsps5_service_handler,
216253
service_config,
217254
_client_config: client_config,
218255
best_block: RwLock::new(chain_params.map(|chain_params| chain_params.best_block)),
@@ -260,6 +297,20 @@ where {
260297
self.lsps2_service_handler.as_ref()
261298
}
262299

300+
/// Returns a reference to the LSPS5 client-side handler.
301+
///
302+
/// The returned hendler allows to initiate the LSPS5 client-side flow. That is, it allows to
303+
pub fn lsps5_client_handler(&self) -> Option<&LSPS5ClientHandler<ES>> {
304+
self.lsps5_client_handler.as_ref()
305+
}
306+
307+
/// Returns a reference to the LSPS5 server-side handler.
308+
///
309+
/// The returned hendler allows to initiate the LSPS5 service-side flow.
310+
pub fn lsps5_service_handler(&self) -> Option<&LSPS5ServiceHandler> {
311+
self.lsps5_service_handler.as_ref()
312+
}
313+
263314
/// Allows to set a callback that will be called after new messages are pushed to the message
264315
/// queue.
265316
///
@@ -435,6 +486,26 @@ where {
435486
},
436487
}
437488
},
489+
LSPSMessage::LSPS5(msg @ LSPS5Message::Response(..)) => {
490+
match &self.lsps5_client_handler {
491+
Some(lsps5_client_handler) => {
492+
lsps5_client_handler.handle_message(msg, sender_node_id)?;
493+
},
494+
None => {
495+
return Err(LightningError { err: format!("Received LSPS5 response message without LSPS5 client handler configured. From node = {:?}", sender_node_id), action: ErrorAction::IgnoreAndLog(Level::Info)});
496+
},
497+
}
498+
},
499+
LSPSMessage::LSPS5(msg @ LSPS5Message::Request(..)) => {
500+
match &self.lsps5_service_handler {
501+
Some(lsps5_service_handler) => {
502+
lsps5_service_handler.handle_message(msg, sender_node_id)?;
503+
},
504+
None => {
505+
return Err(LightningError { err: format!("Received LSPS5 request message without LSPS5 service handler configured. From node = {:?}", sender_node_id), action: ErrorAction::IgnoreAndLog(Level::Info)});
506+
},
507+
}
508+
},
438509
}
439510
Ok(())
440511
}

0 commit comments

Comments
 (0)
Please sign in to comment.