Skip to content

Commit 2c870fb

Browse files
Integrate LSPS5 with liquidity manager
Fully integrates the LSPS5 webhook components into the lightning-liquidity framework, enabling usage through the LiquidityManager.
1 parent f135fee commit 2c870fb

File tree

11 files changed

+407
-31
lines changed

11 files changed

+407
-31
lines changed

fuzz/src/lsps_message.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ pub fn do_test(data: &[u8]) {
7878
));
7979

8080
let liquidity_manager = Arc::new(LiquidityManager::new(
81+
Arc::clone(&keys_manager),
8182
Arc::clone(&keys_manager),
8283
Arc::clone(&manager),
8384
None::<Arc<dyn Filter + Send + Sync>>,

lightning-background-processor/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ all-features = true
1414
rustdoc-args = ["--cfg", "docsrs"]
1515

1616
[features]
17-
std = ["lightning/std", "lightning-liquidity/std", "bitcoin-io/std", "bitcoin_hashes/std"]
18-
1917
default = ["std"]
18+
std = ["lightning/std", "lightning-liquidity/std", "bitcoin-io/std", "bitcoin_hashes/std", "lightning-liquidity/time"]
19+
2020

2121
[dependencies]
2222
bitcoin = { version = "0.32.2", default-features = false }

lightning-background-processor/src/lib.rs

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -735,6 +735,7 @@ use futures_util::{dummy_waker, OptionalSelector, Selector, SelectorOutput};
735735
/// # use lightning_background_processor::{process_events_async, GossipSync};
736736
/// # use core::future::Future;
737737
/// # use core::pin::Pin;
738+
/// # use lightning_liquidity::lsps5::service::TimeProvider;
738739
/// # struct Logger {}
739740
/// # impl lightning::util::logger::Logger for Logger {
740741
/// # fn log(&self, _record: lightning::util::logger::Record) {}
@@ -753,6 +754,15 @@ use futures_util::{dummy_waker, OptionalSelector, Selector, SelectorOutput};
753754
/// # fn remove(&self, primary_namespace: &str, secondary_namespace: &str, key: &str, lazy: bool) -> Pin<Box<dyn Future<Output = Result<(), io::Error>> + 'static + Send>> { todo!() }
754755
/// # fn list(&self, primary_namespace: &str, secondary_namespace: &str) -> Pin<Box<dyn Future<Output = Result<Vec<String>, io::Error>> + 'static + Send>> { todo!() }
755756
/// # }
757+
/// # use core::time::Duration;
758+
/// # struct DefaultTimeProvider;
759+
/// #
760+
/// # impl TimeProvider for DefaultTimeProvider {
761+
/// # fn duration_since_epoch(&self) -> Duration {
762+
/// # use std::time::{SystemTime, UNIX_EPOCH};
763+
/// # SystemTime::now().duration_since(UNIX_EPOCH).expect("system time before Unix epoch")
764+
/// # }
765+
/// # }
756766
/// # struct EventHandler {}
757767
/// # impl EventHandler {
758768
/// # async fn handle_event(&self, _: lightning::events::Event) -> Result<(), ReplayEvent> { Ok(()) }
@@ -768,7 +778,7 @@ use futures_util::{dummy_waker, OptionalSelector, Selector, SelectorOutput};
768778
/// # type P2PGossipSync<UL> = lightning::routing::gossip::P2PGossipSync<Arc<NetworkGraph>, Arc<UL>, Arc<Logger>>;
769779
/// # type ChannelManager<B, F, FE> = lightning::ln::channelmanager::SimpleArcChannelManager<ChainMonitor<B, F, FE>, B, FE, Logger>;
770780
/// # type OnionMessenger<B, F, FE> = lightning::onion_message::messenger::OnionMessenger<Arc<lightning::sign::KeysManager>, Arc<lightning::sign::KeysManager>, Arc<Logger>, Arc<ChannelManager<B, F, FE>>, Arc<lightning::onion_message::messenger::DefaultMessageRouter<Arc<NetworkGraph>, Arc<Logger>, Arc<lightning::sign::KeysManager>>>, Arc<ChannelManager<B, F, FE>>, lightning::ln::peer_handler::IgnoringMessageHandler, lightning::ln::peer_handler::IgnoringMessageHandler, lightning::ln::peer_handler::IgnoringMessageHandler>;
771-
/// # type LiquidityManager<B, F, FE> = lightning_liquidity::LiquidityManager<Arc<lightning::sign::KeysManager>, Arc<ChannelManager<B, F, FE>>, Arc<F>>;
781+
/// # type LiquidityManager<B, F, FE> = lightning_liquidity::LiquidityManager<Arc<lightning::sign::KeysManager>, Arc<lightning::sign::KeysManager>, Arc<ChannelManager<B, F, FE>>, Arc<F>, Arc<DefaultTimeProvider>>;
772782
/// # type Scorer = RwLock<lightning::routing::scoring::ProbabilisticScorer<Arc<NetworkGraph>, Arc<Logger>>>;
773783
/// # type PeerManager<B, F, FE, UL> = lightning::ln::peer_handler::SimpleArcPeerManager<SocketDescriptor, ChainMonitor<B, F, FE>, B, FE, Arc<UL>, Logger, F, StoreSync>;
774784
/// # type OutputSweeper<B, D, FE, F, O> = lightning::util::sweep::OutputSweeper<Arc<B>, Arc<D>, Arc<FE>, Arc<F>, Arc<Store>, Arc<Logger>, Arc<O>>;
@@ -1391,6 +1401,7 @@ mod tests {
13911401
use lightning::util::sweep::{OutputSpendStatus, OutputSweeperSync, PRUNE_DELAY_BLOCKS};
13921402
use lightning::util::test_utils;
13931403
use lightning::{get_event, get_event_msg};
1404+
use lightning_liquidity::lsps5::service::DefaultTimeProvider;
13941405
use lightning_liquidity::LiquidityManager;
13951406
use lightning_persister::fs_store::FilesystemStore;
13961407
use lightning_rapid_gossip_sync::RapidGossipSync;
@@ -1488,8 +1499,13 @@ mod tests {
14881499
IgnoringMessageHandler,
14891500
>;
14901501

1491-
type LM =
1492-
LiquidityManager<Arc<KeysManager>, Arc<ChannelManager>, Arc<dyn Filter + Sync + Send>>;
1502+
type LM = LiquidityManager<
1503+
Arc<KeysManager>,
1504+
Arc<KeysManager>,
1505+
Arc<ChannelManager>,
1506+
Arc<dyn Filter + Sync + Send>,
1507+
Arc<DefaultTimeProvider>,
1508+
>;
14931509

14941510
struct Node {
14951511
node: Arc<ChannelManager>,
@@ -1936,6 +1952,7 @@ mod tests {
19361952
Arc::clone(&keys_manager),
19371953
));
19381954
let liquidity_manager = Arc::new(LiquidityManager::new(
1955+
Arc::clone(&keys_manager),
19391956
Arc::clone(&keys_manager),
19401957
Arc::clone(&manager),
19411958
None,

lightning-liquidity/src/events/mod.rs

Lines changed: 17 additions & 0 deletions
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

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)]
@@ -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

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: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@ 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

2631
use lightning::ln::msgs::{DecodeError, LightningError};
@@ -61,6 +66,9 @@ pub(crate) enum LSPSMethod {
6166
LSPS1CreateOrder,
6267
LSPS2GetInfo,
6368
LSPS2Buy,
69+
LSPS5SetWebhook,
70+
LSPS5ListWebhooks,
71+
LSPS5RemoveWebhook,
6472
}
6573

6674
impl LSPSMethod {
@@ -72,6 +80,9 @@ impl LSPSMethod {
7280
Self::LSPS1GetOrder => LSPS1_GET_ORDER_METHOD_NAME,
7381
Self::LSPS2GetInfo => LSPS2_GET_INFO_METHOD_NAME,
7482
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,
7586
}
7687
}
7788
}
@@ -86,6 +97,9 @@ impl FromStr for LSPSMethod {
8697
LSPS1_GET_ORDER_METHOD_NAME => Ok(Self::LSPS1GetOrder),
8798
LSPS2_GET_INFO_METHOD_NAME => Ok(Self::LSPS2GetInfo),
8899
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),
89103
_ => Err(&"Unknown method name"),
90104
}
91105
}
@@ -118,6 +132,16 @@ impl From<&LSPS2Request> for LSPSMethod {
118132
}
119133
}
120134

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+
121145
impl<'de> Deserialize<'de> for LSPSMethod {
122146
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
123147
where
@@ -266,6 +290,8 @@ pub enum LSPSMessage {
266290
LSPS1(LSPS1Message),
267291
/// An LSPS2 message.
268292
LSPS2(LSPS2Message),
293+
/// An LSPS5 message.
294+
LSPS5(LSPS5Message),
269295
}
270296

271297
impl LSPSMessage {
@@ -293,6 +319,9 @@ impl LSPSMessage {
293319
LSPSMessage::LSPS2(LSPS2Message::Request(request_id, request)) => {
294320
Some((LSPSRequestId(request_id.0.clone()), request.into()))
295321
},
322+
LSPSMessage::LSPS5(LSPS5Message::Request(request_id, request)) => {
323+
Some((LSPSRequestId(request_id.0.clone()), request.into()))
324+
},
296325
_ => None,
297326
}
298327
}
@@ -409,6 +438,44 @@ impl Serialize for LSPSMessage {
409438
jsonrpc_object.serialize_field(JSONRPC_ID_FIELD_KEY, &serde_json::Value::Null)?;
410439
jsonrpc_object.serialize_field(JSONRPC_ERROR_FIELD_KEY, &error)?;
411440
},
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::RemoveWebhook(result) => {
472+
jsonrpc_object.serialize_field(JSONRPC_RESULT_FIELD_KEY, result)?
473+
},
474+
LSPS5Response::RemoveWebhookError(error) => {
475+
jsonrpc_object.serialize_field(JSONRPC_ERROR_FIELD_KEY, error)?
476+
},
477+
}
478+
},
412479
}
413480

414481
jsonrpc_object.end()
@@ -522,6 +589,30 @@ impl<'de, 'a> Visitor<'de> for LSPSMessageVisitor<'a> {
522589
.map_err(de::Error::custom)?;
523590
Ok(LSPSMessage::LSPS2(LSPS2Message::Request(id, LSPS2Request::Buy(request))))
524591
},
592+
LSPSMethod::LSPS5SetWebhook => {
593+
let request = serde_json::from_value(params.unwrap_or(json!({})))
594+
.map_err(de::Error::custom)?;
595+
Ok(LSPSMessage::LSPS5(LSPS5Message::Request(
596+
id,
597+
LSPS5Request::SetWebhook(request),
598+
)))
599+
},
600+
LSPSMethod::LSPS5ListWebhooks => {
601+
let request = serde_json::from_value(params.unwrap_or(json!({})))
602+
.map_err(de::Error::custom)?;
603+
Ok(LSPSMessage::LSPS5(LSPS5Message::Request(
604+
id,
605+
LSPS5Request::ListWebhooks(request),
606+
)))
607+
},
608+
LSPSMethod::LSPS5RemoveWebhook => {
609+
let request = serde_json::from_value(params.unwrap_or(json!({})))
610+
.map_err(de::Error::custom)?;
611+
Ok(LSPSMessage::LSPS5(LSPS5Message::Request(
612+
id,
613+
LSPS5Request::RemoveWebhook(request),
614+
)))
615+
},
525616
},
526617
None => match self.request_id_to_method_map.remove(&id) {
527618
Some(method) => match method {
@@ -627,6 +718,52 @@ impl<'de, 'a> Visitor<'de> for LSPSMessageVisitor<'a> {
627718
Err(de::Error::custom("Received invalid JSON-RPC object: one of method, result, or error required"))
628719
}
629720
},
721+
LSPSMethod::LSPS5SetWebhook => {
722+
if let Some(error) = error {
723+
Ok(LSPSMessage::LSPS5(LSPS5Message::Response(
724+
id,
725+
LSPS5Response::SetWebhookError(error.into()),
726+
)))
727+
} else if let Some(result) = result {
728+
let response =
729+
serde_json::from_value(result).map_err(de::Error::custom)?;
730+
Ok(LSPSMessage::LSPS5(LSPS5Message::Response(
731+
id,
732+
LSPS5Response::SetWebhook(response),
733+
)))
734+
} else {
735+
Err(de::Error::custom("Received invalid JSON-RPC object: one of method, result, or error required"))
736+
}
737+
},
738+
LSPSMethod::LSPS5ListWebhooks => {
739+
if let Some(result) = result {
740+
let response =
741+
serde_json::from_value(result).map_err(de::Error::custom)?;
742+
Ok(LSPSMessage::LSPS5(LSPS5Message::Response(
743+
id,
744+
LSPS5Response::ListWebhooks(response),
745+
)))
746+
} else {
747+
Err(de::Error::custom("Received invalid JSON-RPC object: one of method, result, or error required"))
748+
}
749+
},
750+
LSPSMethod::LSPS5RemoveWebhook => {
751+
if let Some(error) = error {
752+
Ok(LSPSMessage::LSPS5(LSPS5Message::Response(
753+
id,
754+
LSPS5Response::RemoveWebhookError(error.into()),
755+
)))
756+
} else if let Some(result) = result {
757+
let response =
758+
serde_json::from_value(result).map_err(de::Error::custom)?;
759+
Ok(LSPSMessage::LSPS5(LSPS5Message::Response(
760+
id,
761+
LSPS5Response::RemoveWebhook(response),
762+
)))
763+
} else {
764+
Err(de::Error::custom("Received invalid JSON-RPC object: one of method, result, or error required"))
765+
}
766+
},
630767
},
631768
None => Err(de::Error::custom(format!(
632769
"Received response for unknown request id: {}",

0 commit comments

Comments
 (0)