Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions bindings/ldk_node.udl
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,10 @@ enum NodeError {
"InvalidLnurl",
"ChainSourceNotSupported",
"InvalidPayerProof",
"LiquiditySetWebhookFailed",
"LiquidityRemoveWebhookFailed",
"LiquidityListWebhooksFailed",
"LiquidityNotifyWebhookFailed"
};

typedef dictionary NodeStatus;
Expand Down
55 changes: 42 additions & 13 deletions src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,8 @@ use crate::runtime::{Runtime, RuntimeSpawner};
use crate::tx_broadcaster::TransactionBroadcaster;
use crate::types::{
AsyncPersister, ChainMonitor, ChannelManager, DynStore, DynStoreRef, DynStoreWrapper,
GossipSync, Graph, HRNResolver, KeysManager, MessageRouter, OnionMessenger, PaymentStore,
PeerManager, PendingPaymentStore,
GossipSync, Graph, HRNResolver, KeysManager, LSPS5ServiceConfig, MessageRouter, OnionMessenger,
PaymentStore, PeerManager, PendingPaymentStore,
};
use crate::wallet::persist::{read_address_pool, KVStoreWalletPersister};
use crate::wallet::Wallet;
Expand Down Expand Up @@ -127,10 +127,12 @@ struct PathfindingScoresSyncConfig {

#[derive(Debug, Clone, Default)]
struct LiquiditySourceConfig {
// Acts for both LSPS1 and LSPS2 clients connecting to the given service.
// Acts for LSPS1, LSPS2 and LSPS5 clients connecting to the given service.
lsp_nodes: Vec<LspConfig>,
// Act as an LSPS2 service.
lsps2_service: Option<LSPS2ServiceConfig>,
// Act as an LSPS5 service.
lsps5_service: Option<LSPS5ServiceConfig>,
}

#[derive(Clone)]
Expand Down Expand Up @@ -507,18 +509,26 @@ impl NodeBuilder {
self
}

/// Configures the [`Node`] instance to provide an [LSPS2] service, issuing just-in-time
/// channels to clients.
/// Configures the [`Node`] instance to provide [bLIP-52 / LSPS2] and/or [bLIP-55 / LSPS5]
/// services to clients.
///
/// [bLIP-52 / LSPS2] issues just-in-time channels to clients, [bLIP-55 / LSPS5] allows clients
/// to register webhooks for push notifications.
///
/// Passing `None` leaves the respective service disabled.
///
/// **Caution**: LSP service support is in **alpha** and is considered an experimental feature.
///
/// [LSPS2]: https://github.com/BitcoinAndLightningLayerSpecs/lsp/blob/main/LSPS2/README.md
/// [bLIP-52 / LSPS2]: https://github.com/lightning/blips/blob/master/blip-0052.md
/// [bLIP-55 / LSPS5]: https://github.com/lightning/blips/blob/master/blip-0055.md
pub fn enable_liquidity_provider(
&mut self, lsps2_service_config: LSPS2ServiceConfig,
&mut self, lsps2_service_config: Option<LSPS2ServiceConfig>,
lsps5_service_config: Option<LSPS5ServiceConfig>,
) -> &mut Self {
let liquidity_source_config =
self.liquidity_source_config.get_or_insert(LiquiditySourceConfig::default());
liquidity_source_config.lsps2_service = Some(lsps2_service_config);
liquidity_source_config.lsps2_service = lsps2_service_config;
liquidity_source_config.lsps5_service = lsps5_service_config;
self
}

Expand Down Expand Up @@ -1112,14 +1122,26 @@ impl ArcedNodeBuilder {
);
}

/// Configures the [`Node`] instance to provide an [LSPS2] service, issuing just-in-time
/// channels to clients.
/// Configures the [`Node`] instance to provide [bLIP-52 / LSPS2] and/or [bLIP-55 / LSPS5]
/// services to clients.
///
/// [bLIP-52 / LSPS2] issues just-in-time channels to clients, [bLIP-55 / LSPS5] allows clients
/// to register webhooks for push notifications.
///
/// Passing `None` leaves the respective service disabled.
///
/// **Caution**: LSP service support is in **alpha** and is considered an experimental feature.
///
/// [LSPS2]: https://github.com/BitcoinAndLightningLayerSpecs/lsp/blob/main/LSPS2/README.md
pub fn enable_liquidity_provider(&self, lsps2_service_config: LSPS2ServiceConfig) {
self.inner.write().expect("lock").enable_liquidity_provider(lsps2_service_config);
/// [bLIP-52 / LSPS2]: https://github.com/lightning/blips/blob/master/blip-0052.md
/// [bLIP-55 / LSPS5]: https://github.com/lightning/blips/blob/master/blip-0055.md
pub fn enable_liquidity_provider(
&self, lsps2_service_config: Option<LSPS2ServiceConfig>,
lsps5_service_config: Option<LSPS5ServiceConfig>,
) {
self.inner
.write()
.expect("lock")
.enable_liquidity_provider(lsps2_service_config, lsps5_service_config);
}

/// Sets the used storage directory path.
Expand Down Expand Up @@ -2148,6 +2170,7 @@ fn build_with_store_internal(
Arc::clone(&tx_broadcaster),
Arc::clone(&kv_store),
Arc::clone(&config),
Arc::clone(&runtime),
Arc::clone(&logger),
);

Expand All @@ -2166,6 +2189,10 @@ fn build_with_store_internal(
lsc.lsps2_service.as_ref().map(|config| {
liquidity_source_builder.lsps2_service(promise_secret, config.clone())
});

lsc.lsps5_service
.as_ref()
.map(|config| liquidity_source_builder.lsps5_service(config.clone()));
}

let liquidity_source = runtime
Expand Down Expand Up @@ -2225,6 +2252,8 @@ fn build_with_store_internal(

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

liquidity_source.lsps5_service().set_peer_manager(Arc::downgrade(&peer_manager));

let connection_manager = Arc::new(ConnectionManager::new(
Arc::clone(&peer_manager),
config.tor_config.clone(),
Expand Down
6 changes: 6 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,12 @@ pub(crate) const LIQUIDITY_DISCOVERY_RETRY_INITIAL_DELAY: Duration = Duration::f
// thereafter until every configured LSP has been discovered.
pub(crate) const LIQUIDITY_DISCOVERY_RETRY_MAX_DELAY: Duration = Duration::from_secs(60 * 60);

// The timeout after which we abort a LSPS5 webhook notification operation.
pub(crate) const LSPS5_WEBHOOK_TIMEOUT_SECS: u64 = 30;

// The maximum size of a response body we'll accept when delivering an LSPS5 webhook notification.
pub(crate) const LSPS5_WEBHOOK_MAX_RESPONSE_SIZE: usize = 64 * 1024;

#[derive(Debug, Clone)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
/// Represents the configuration of an [`Node`] instance.
Expand Down
20 changes: 20 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,14 @@ pub enum Error {
ChainSourceNotSupported,
/// The provided payer proof is invalid.
InvalidPayerProof,
/// Failed to set a webhook with the LSP.
LiquiditySetWebhookFailed,
/// Failed to remove a webhook with the LSP.
LiquidityRemoveWebhookFailed,
/// Failed to list webhooks with the LSP.
LiquidityListWebhooksFailed,
/// Failed to send a webhook notification to a client.
LiquidityNotifyWebhookFailed,
}

impl fmt::Display for Error {
Expand Down Expand Up @@ -233,6 +241,18 @@ impl fmt::Display for Error {
write!(f, "The configured chain source is not supported.")
},
Self::InvalidPayerProof => write!(f, "The provided payer proof is invalid."),
Self::LiquiditySetWebhookFailed => {
write!(f, "Failed to set a webhook with the LSP.")
},
Self::LiquidityRemoveWebhookFailed => {
write!(f, "Failed to remove a webhook with the LSP.")
},
Self::LiquidityListWebhooksFailed => {
write!(f, "Failed to list webhooks with the LSP.")
},
Self::LiquidityNotifyWebhookFailed => {
write!(f, "Failed to send a webhook notification to a client.")
},
}
}
}
Expand Down
31 changes: 28 additions & 3 deletions src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,12 @@ use lightning::events::bump_transaction::BumpTransactionEvent;
#[cfg(not(feature = "uniffi"))]
use lightning::events::PaidBolt12Invoice;
use lightning::events::{
ClosureReason, Event as LdkEvent, FundingInfo, HTLCLocator as LdkHtlcLocator,
PaymentFailureReason, PaymentPurpose, ReplayEvent,
ClosureReason, Event as LdkEvent, FundingInfo, HTLCHandlingFailureReason,
HTLCHandlingFailureType, HTLCLocator as LdkHtlcLocator, PaymentFailureReason, PaymentPurpose,
ReplayEvent,
};
use lightning::ln::channelmanager::{PaymentId, TrustedChannelFeatures};
use lightning::ln::onion_utils::LocalHTLCFailureReason;
use lightning::ln::types::ChannelId;
use lightning::routing::gossip::NodeId;
use lightning::sign::EntropySource;
Expand Down Expand Up @@ -1534,11 +1536,29 @@ where
prober.handle_background_probe_failed(&path, payment_id);
}
},
LdkEvent::HTLCHandlingFailed { failure_type, .. } => {
LdkEvent::HTLCHandlingFailed { failure_type, failure_reason, .. } => {
// Capture the client's node id before `failure_type` is consumed below. A forward
// that failed only because the next-hop peer was offline is our cue to wake an
// LSPS5 client. The HTLC is failed back as `temporary_channel_failure`, which is
// not permanent, so the sender can retry once the client is online.
let offline_node_id = match (&failure_type, &failure_reason) {
(
HTLCHandlingFailureType::Forward { node_id: Some(node_id), .. },
Some(HTLCHandlingFailureReason::Local {
reason: LocalHTLCFailureReason::PeerOffline,
}),
) => Some(*node_id),
_ => None,
};

self.liquidity_source
.lsps2_service()
.handle_htlc_handling_failed(failure_type)
.await;

if let Some(node_id) = offline_node_id {
self.liquidity_source.lsps5_service().notify_payment_incoming(node_id);
}
},
LdkEvent::SpendableOutputs { outputs, channel_id, counterparty_node_id } => {
match self
Expand Down Expand Up @@ -2022,6 +2042,8 @@ where
debug_assert!(false, "We currently don't handle BOLT12 invoices manually, so this event should never be emitted.");
},
LdkEvent::ConnectionNeeded { node_id, addresses } => {
self.liquidity_source.lsps5_service().notify_onion_message_incoming(node_id);

let spawn_logger = self.logger.clone();
let spawn_cm = Arc::clone(&self.connection_manager);
let future = async move {
Expand Down Expand Up @@ -2080,6 +2102,9 @@ where
"Onion message intercepted, but no onion message mailbox available"
);
}
self.liquidity_source
.lsps5_service()
.notify_onion_message_incoming(peer_node_id);
} else {
log_error!(self.logger, "Onion message intercepted for unknown SCID");
}
Expand Down
Loading
Loading