Skip to content

Commit d4e3472

Browse files
committed
Add LSPS5 (bLIP-55) webhook notification support
Implement the bLIP-55 / LSPS5 webhook registration protocol on top of the multi-LSP liquidity module (src/liquidity/{client,service}). Client side, exposed via Node::liquidity().lsps5(): - set_webhook / list_webhooks / remove_webhook to manage webhook registrations with an LSP. - When no node_id is given, set_webhook and remove_webhook fan out to every LSPS5-capable LSP so a webhook can be configured once across all configured LSPs; set_webhook returns one result per LSP that accepted the registration and remove_webhook returns the LSPs it was removed from. Service side, enabled via Builder::enable_liquidity_provider_lsps5(): - Deliver outgoing webhook notifications over HTTP in response to LSPS5ServiceEvent::SendWebhookNotification. - Automatically send an onion-message-incoming notification when an intercepted onion message targets a client that is currently offline (wired from LdkEvent::OnionMessageIntercepted, gated on peer connectivity). Wires the feature through the UniFFI bindings and adds LSPS5-specific Error variants.
1 parent d9b8f7b commit d4e3472

12 files changed

Lines changed: 1268 additions & 35 deletions

File tree

bindings/ldk_node.udl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,10 @@ enum NodeError {
249249
"InvalidLnurl",
250250
"ChainSourceNotSupported",
251251
"InvalidPayerProof",
252+
"LiquiditySetWebhookFailed",
253+
"LiquidityRemoveWebhookFailed",
254+
"LiquidityListWebhooksFailed",
255+
"LiquidityNotifyWebhookFailed"
252256
};
253257

254258
typedef dictionary NodeStatus;

src/builder.rs

Lines changed: 42 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,8 @@ use crate::runtime::{Runtime, RuntimeSpawner};
8383
use crate::tx_broadcaster::TransactionBroadcaster;
8484
use crate::types::{
8585
AsyncPersister, ChainMonitor, ChannelManager, DynStore, DynStoreRef, DynStoreWrapper,
86-
GossipSync, Graph, HRNResolver, KeysManager, MessageRouter, OnionMessenger, PaymentStore,
87-
PeerManager, PendingPaymentStore,
86+
GossipSync, Graph, HRNResolver, KeysManager, LSPS5ServiceConfig, MessageRouter, OnionMessenger,
87+
PaymentStore, PeerManager, PendingPaymentStore,
8888
};
8989
use crate::wallet::persist::{read_address_pool, KVStoreWalletPersister};
9090
use crate::wallet::Wallet;
@@ -127,10 +127,12 @@ struct PathfindingScoresSyncConfig {
127127

128128
#[derive(Debug, Clone, Default)]
129129
struct LiquiditySourceConfig {
130-
// Acts for both LSPS1 and LSPS2 clients connecting to the given service.
130+
// Acts for LSPS1, LSPS2 and LSPS5 clients connecting to the given service.
131131
lsp_nodes: Vec<LspConfig>,
132132
// Act as an LSPS2 service.
133133
lsps2_service: Option<LSPS2ServiceConfig>,
134+
// Act as an LSPS5 service.
135+
lsps5_service: Option<LSPS5ServiceConfig>,
134136
}
135137

136138
#[derive(Clone)]
@@ -507,18 +509,26 @@ impl NodeBuilder {
507509
self
508510
}
509511

510-
/// Configures the [`Node`] instance to provide an [LSPS2] service, issuing just-in-time
511-
/// channels to clients.
512+
/// Configures the [`Node`] instance to provide [bLIP-52 / LSPS2] and/or [bLIP-55 / LSPS5]
513+
/// services to clients.
514+
///
515+
/// [bLIP-52 / LSPS2] issues just-in-time channels to clients, [bLIP-55 / LSPS5] allows clients
516+
/// to register webhooks for push notifications.
517+
///
518+
/// Passing `None` leaves the respective service disabled.
512519
///
513520
/// **Caution**: LSP service support is in **alpha** and is considered an experimental feature.
514521
///
515-
/// [LSPS2]: https://github.com/BitcoinAndLightningLayerSpecs/lsp/blob/main/LSPS2/README.md
522+
/// [bLIP-52 / LSPS2]: https://github.com/lightning/blips/blob/master/blip-0052.md
523+
/// [bLIP-55 / LSPS5]: https://github.com/lightning/blips/blob/master/blip-0055.md
516524
pub fn enable_liquidity_provider(
517-
&mut self, lsps2_service_config: LSPS2ServiceConfig,
525+
&mut self, lsps2_service_config: Option<LSPS2ServiceConfig>,
526+
lsps5_service_config: Option<LSPS5ServiceConfig>,
518527
) -> &mut Self {
519528
let liquidity_source_config =
520529
self.liquidity_source_config.get_or_insert(LiquiditySourceConfig::default());
521-
liquidity_source_config.lsps2_service = Some(lsps2_service_config);
530+
liquidity_source_config.lsps2_service = lsps2_service_config;
531+
liquidity_source_config.lsps5_service = lsps5_service_config;
522532
self
523533
}
524534

@@ -1112,14 +1122,26 @@ impl ArcedNodeBuilder {
11121122
);
11131123
}
11141124

1115-
/// Configures the [`Node`] instance to provide an [LSPS2] service, issuing just-in-time
1116-
/// channels to clients.
1125+
/// Configures the [`Node`] instance to provide [bLIP-52 / LSPS2] and/or [bLIP-55 / LSPS5]
1126+
/// services to clients.
1127+
///
1128+
/// [bLIP-52 / LSPS2] issues just-in-time channels to clients, [bLIP-55 / LSPS5] allows clients
1129+
/// to register webhooks for push notifications.
1130+
///
1131+
/// Passing `None` leaves the respective service disabled.
11171132
///
11181133
/// **Caution**: LSP service support is in **alpha** and is considered an experimental feature.
11191134
///
1120-
/// [LSPS2]: https://github.com/BitcoinAndLightningLayerSpecs/lsp/blob/main/LSPS2/README.md
1121-
pub fn enable_liquidity_provider(&self, lsps2_service_config: LSPS2ServiceConfig) {
1122-
self.inner.write().expect("lock").enable_liquidity_provider(lsps2_service_config);
1135+
/// [bLIP-52 / LSPS2]: https://github.com/lightning/blips/blob/master/blip-0052.md
1136+
/// [bLIP-55 / LSPS5]: https://github.com/lightning/blips/blob/master/blip-0055.md
1137+
pub fn enable_liquidity_provider(
1138+
&self, lsps2_service_config: Option<LSPS2ServiceConfig>,
1139+
lsps5_service_config: Option<LSPS5ServiceConfig>,
1140+
) {
1141+
self.inner
1142+
.write()
1143+
.expect("lock")
1144+
.enable_liquidity_provider(lsps2_service_config, lsps5_service_config);
11231145
}
11241146

11251147
/// Sets the used storage directory path.
@@ -2148,6 +2170,7 @@ fn build_with_store_internal(
21482170
Arc::clone(&tx_broadcaster),
21492171
Arc::clone(&kv_store),
21502172
Arc::clone(&config),
2173+
Arc::clone(&runtime),
21512174
Arc::clone(&logger),
21522175
);
21532176

@@ -2166,6 +2189,10 @@ fn build_with_store_internal(
21662189
lsc.lsps2_service.as_ref().map(|config| {
21672190
liquidity_source_builder.lsps2_service(promise_secret, config.clone())
21682191
});
2192+
2193+
lsc.lsps5_service
2194+
.as_ref()
2195+
.map(|config| liquidity_source_builder.lsps5_service(config.clone()));
21692196
}
21702197

21712198
let liquidity_source = runtime
@@ -2225,6 +2252,8 @@ fn build_with_store_internal(
22252252

22262253
liquidity_source.lsps2_service().set_peer_manager(Arc::downgrade(&peer_manager));
22272254

2255+
liquidity_source.lsps5_service().set_peer_manager(Arc::downgrade(&peer_manager));
2256+
22282257
let connection_manager = Arc::new(ConnectionManager::new(
22292258
Arc::clone(&peer_manager),
22302259
config.tor_config.clone(),

src/config.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,12 @@ pub(crate) const LIQUIDITY_DISCOVERY_RETRY_INITIAL_DELAY: Duration = Duration::f
152152
// thereafter until every configured LSP has been discovered.
153153
pub(crate) const LIQUIDITY_DISCOVERY_RETRY_MAX_DELAY: Duration = Duration::from_secs(60 * 60);
154154

155+
// The timeout after which we abort a LSPS5 webhook notification operation.
156+
pub(crate) const LSPS5_WEBHOOK_TIMEOUT_SECS: u64 = 30;
157+
158+
// The maximum size of a response body we'll accept when delivering an LSPS5 webhook notification.
159+
pub(crate) const LSPS5_WEBHOOK_MAX_RESPONSE_SIZE: usize = 64 * 1024;
160+
155161
#[derive(Debug, Clone)]
156162
#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
157163
/// Represents the configuration of an [`Node`] instance.

src/error.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,14 @@ pub enum Error {
143143
ChainSourceNotSupported,
144144
/// The provided payer proof is invalid.
145145
InvalidPayerProof,
146+
/// Failed to set a webhook with the LSP.
147+
LiquiditySetWebhookFailed,
148+
/// Failed to remove a webhook with the LSP.
149+
LiquidityRemoveWebhookFailed,
150+
/// Failed to list webhooks with the LSP.
151+
LiquidityListWebhooksFailed,
152+
/// Failed to send a webhook notification to a client.
153+
LiquidityNotifyWebhookFailed,
146154
}
147155

148156
impl fmt::Display for Error {
@@ -233,6 +241,18 @@ impl fmt::Display for Error {
233241
write!(f, "The configured chain source is not supported.")
234242
},
235243
Self::InvalidPayerProof => write!(f, "The provided payer proof is invalid."),
244+
Self::LiquiditySetWebhookFailed => {
245+
write!(f, "Failed to set a webhook with the LSP.")
246+
},
247+
Self::LiquidityRemoveWebhookFailed => {
248+
write!(f, "Failed to remove a webhook with the LSP.")
249+
},
250+
Self::LiquidityListWebhooksFailed => {
251+
write!(f, "Failed to list webhooks with the LSP.")
252+
},
253+
Self::LiquidityNotifyWebhookFailed => {
254+
write!(f, "Failed to send a webhook notification to a client.")
255+
},
236256
}
237257
}
238258
}

src/event.rs

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,12 @@ use lightning::events::bump_transaction::BumpTransactionEvent;
1919
#[cfg(not(feature = "uniffi"))]
2020
use lightning::events::PaidBolt12Invoice;
2121
use lightning::events::{
22-
ClosureReason, Event as LdkEvent, FundingInfo, HTLCLocator as LdkHtlcLocator,
23-
PaymentFailureReason, PaymentPurpose, ReplayEvent,
22+
ClosureReason, Event as LdkEvent, FundingInfo, HTLCHandlingFailureReason,
23+
HTLCHandlingFailureType, HTLCLocator as LdkHtlcLocator, PaymentFailureReason, PaymentPurpose,
24+
ReplayEvent,
2425
};
2526
use lightning::ln::channelmanager::{PaymentId, TrustedChannelFeatures};
27+
use lightning::ln::onion_utils::LocalHTLCFailureReason;
2628
use lightning::ln::types::ChannelId;
2729
use lightning::routing::gossip::NodeId;
2830
use lightning::sign::EntropySource;
@@ -1534,11 +1536,29 @@ where
15341536
prober.handle_background_probe_failed(&path, payment_id);
15351537
}
15361538
},
1537-
LdkEvent::HTLCHandlingFailed { failure_type, .. } => {
1539+
LdkEvent::HTLCHandlingFailed { failure_type, failure_reason, .. } => {
1540+
// Capture the client's node id before `failure_type` is consumed below. A forward
1541+
// that failed only because the next-hop peer was offline is our cue to wake an
1542+
// LSPS5 client. The HTLC is failed back as `temporary_channel_failure`, which is
1543+
// not permanent, so the sender can retry once the client is online.
1544+
let offline_node_id = match (&failure_type, &failure_reason) {
1545+
(
1546+
HTLCHandlingFailureType::Forward { node_id: Some(node_id), .. },
1547+
Some(HTLCHandlingFailureReason::Local {
1548+
reason: LocalHTLCFailureReason::PeerOffline,
1549+
}),
1550+
) => Some(*node_id),
1551+
_ => None,
1552+
};
1553+
15381554
self.liquidity_source
15391555
.lsps2_service()
15401556
.handle_htlc_handling_failed(failure_type)
15411557
.await;
1558+
1559+
if let Some(node_id) = offline_node_id {
1560+
self.liquidity_source.lsps5_service().notify_payment_incoming(node_id);
1561+
}
15421562
},
15431563
LdkEvent::SpendableOutputs { outputs, channel_id, counterparty_node_id } => {
15441564
match self
@@ -2022,6 +2042,8 @@ where
20222042
debug_assert!(false, "We currently don't handle BOLT12 invoices manually, so this event should never be emitted.");
20232043
},
20242044
LdkEvent::ConnectionNeeded { node_id, addresses } => {
2045+
self.liquidity_source.lsps5_service().notify_onion_message_incoming(node_id);
2046+
20252047
let spawn_logger = self.logger.clone();
20262048
let spawn_cm = Arc::clone(&self.connection_manager);
20272049
let future = async move {
@@ -2080,6 +2102,9 @@ where
20802102
"Onion message intercepted, but no onion message mailbox available"
20812103
);
20822104
}
2105+
self.liquidity_source
2106+
.lsps5_service()
2107+
.notify_onion_message_incoming(peer_node_id);
20832108
} else {
20842109
log_error!(self.logger, "Onion message intercepted for unknown SCID");
20852110
}

0 commit comments

Comments
 (0)