From 5e8c2631c236a48a59053612d5ac0a09412ab024 Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Wed, 4 Dec 2024 14:02:26 +0100 Subject: [PATCH] Add LSPS1 API We add an `Lsps1Liquidity` API object, mirroring the approach we took with the `payment` APIs. --- src/lib.rs | 33 ++++++++++++- src/liquidity.rs | 120 +++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 148 insertions(+), 5 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 1e30c61c0..31f90da2e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -84,7 +84,7 @@ mod gossip; pub mod graph; mod hex_utils; pub mod io; -mod liquidity; +pub mod liquidity; mod logger; mod message_handler; pub mod payment; @@ -100,6 +100,7 @@ pub use bip39; pub use bitcoin; pub use lightning; pub use lightning_invoice; +pub use lightning_liquidity; pub use vss_client; pub use balance::{BalanceDetails, LightningBalance, PendingSweepBalance}; @@ -129,7 +130,7 @@ use event::{EventHandler, EventQueue}; use gossip::GossipSource; use graph::NetworkGraph; use io::utils::write_node_metrics; -use liquidity::LiquiditySource; +use liquidity::{LiquiditySource, Lsps1Liquidity}; use payment::store::PaymentStore; use payment::{ Bolt11Payment, Bolt12Payment, OnchainPayment, PaymentDetails, SpontaneousPayment, @@ -960,6 +961,34 @@ impl Node { )) } + /// Returns a liquidity handler allowing to request channels via the [LSPS1] protocol. + /// + /// [LSPS1]: https://github.com/BitcoinAndLightningLayerSpecs/lsp/tree/main/LSPS1 + #[cfg(not(feature = "uniffi"))] + pub fn lsps1_liquidity(&self) -> Lsps1Liquidity { + Lsps1Liquidity::new( + Arc::clone(&self.runtime), + Arc::clone(&self.wallet), + Arc::clone(&self.connection_manager), + self.liquidity_source.clone(), + Arc::clone(&self.logger), + ) + } + + /// Returns a liquidity handler allowing to request channels via the [LSPS1] protocol. + /// + /// [LSPS1]: https://github.com/BitcoinAndLightningLayerSpecs/lsp/tree/main/LSPS1 + #[cfg(feature = "uniffi")] + pub fn lsps1_liquidity(&self) -> Arc { + Arc::new(Lsps1Liquidity::new( + Arc::clone(&self.runtime), + Arc::clone(&self.wallet), + Arc::clone(&self.connection_manager), + self.liquidity_source.clone(), + Arc::clone(&self.logger), + )) + } + /// Retrieve a list of known channels. pub fn list_channels(&self) -> Vec { self.channel_manager.list_channels().into_iter().map(|c| c.into()).collect() diff --git a/src/liquidity.rs b/src/liquidity.rs index 92a4da617..a9987d892 100644 --- a/src/liquidity.rs +++ b/src/liquidity.rs @@ -5,9 +5,12 @@ // http://opensource.org/licenses/MIT>, at your option. You may not use this file except in // accordance with one or both of these licenses. +//! Objects related to liquidity management. + use crate::chain::ChainSource; -use crate::logger::{log_debug, log_error, log_info, Logger}; -use crate::types::{ChannelManager, KeysManager, LiquidityManager, PeerManager}; +use crate::connection::ConnectionManager; +use crate::logger::{log_debug, log_error, log_info, FilesystemLogger, Logger}; +use crate::types::{ChannelManager, KeysManager, LiquidityManager, PeerManager, Wallet}; use crate::{Config, Error}; use lightning::ln::channelmanager::MIN_FINAL_CLTV_EXPIRY_DELTA; @@ -34,7 +37,7 @@ use tokio::sync::oneshot; use std::collections::HashMap; use std::ops::Deref; -use std::sync::{Arc, Mutex}; +use std::sync::{Arc, Mutex, RwLock}; use std::time::Duration; const LIQUIDITY_REQUEST_TIMEOUT_SECS: u64 = 5; @@ -894,3 +897,114 @@ pub(crate) struct LSPS2BuyResponse { intercept_scid: u64, cltv_expiry_delta: u32, } + +/// A liquidity handler allowing to request channels via the [LSPS1] protocol. +/// +/// Should be retrieved by calling [`Node::lsps1_liquidity`]. +/// +/// [LSPS1]: https://github.com/BitcoinAndLightningLayerSpecs/lsp/tree/main/LSPS1 +/// [`Node::lsps1_liquidity`]: crate::Node::lsps1_liquidity +#[derive(Clone)] +pub struct Lsps1Liquidity { + runtime: Arc>>>, + wallet: Arc, + connection_manager: Arc>>, + liquidity_source: Option>>>, + logger: Arc, +} + +impl Lsps1Liquidity { + pub(crate) fn new( + runtime: Arc>>>, wallet: Arc, + connection_manager: Arc>>, + liquidity_source: Option>>>, + logger: Arc, + ) -> Self { + Self { runtime, wallet, connection_manager, liquidity_source, logger } + } + + /// Connects to the configured LSP and places an order for an inbound channel. + /// + /// The channel will be opened after one of the returned payment options has successfully been + /// paid. + pub fn request_channel( + &self, lsp_balance_sat: u64, client_balance_sat: u64, channel_expiry_blocks: u32, + announce_channel: bool, + ) -> Result { + let liquidity_source = + self.liquidity_source.as_ref().ok_or(Error::LiquiditySourceUnavailable)?; + + let (lsp_node_id, lsp_address) = liquidity_source + .get_lsps1_service_details() + .ok_or(Error::LiquiditySourceUnavailable)?; + + let rt_lock = self.runtime.read().unwrap(); + let runtime = rt_lock.as_ref().unwrap(); + + let con_node_id = lsp_node_id; + let con_addr = lsp_address.clone(); + let con_cm = Arc::clone(&self.connection_manager); + + // We need to use our main runtime here as a local runtime might not be around to poll + // connection futures going forward. + tokio::task::block_in_place(move || { + runtime.block_on(async move { + con_cm.connect_peer_if_necessary(con_node_id, con_addr).await + }) + })?; + + log_info!(self.logger, "Connected to LSP {}@{}. ", lsp_node_id, lsp_address); + + let refund_address = self.wallet.get_new_address()?; + + let liquidity_source = Arc::clone(&liquidity_source); + let response = tokio::task::block_in_place(move || { + runtime.block_on(async move { + liquidity_source + .lsps1_request_channel( + lsp_balance_sat, + client_balance_sat, + channel_expiry_blocks, + announce_channel, + refund_address, + ) + .await + }) + })?; + + Ok(response) + } + + /// Connects to the configured LSP and checks for the status of a previously-placed order. + pub fn check_order_status(&self, order_id: OrderId) -> Result { + let liquidity_source = + self.liquidity_source.as_ref().ok_or(Error::LiquiditySourceUnavailable)?; + + let (lsp_node_id, lsp_address) = liquidity_source + .get_lsps1_service_details() + .ok_or(Error::LiquiditySourceUnavailable)?; + + let rt_lock = self.runtime.read().unwrap(); + let runtime = rt_lock.as_ref().unwrap(); + + let con_node_id = lsp_node_id; + let con_addr = lsp_address.clone(); + let con_cm = Arc::clone(&self.connection_manager); + + // We need to use our main runtime here as a local runtime might not be around to poll + // connection futures going forward. + tokio::task::block_in_place(move || { + runtime.block_on(async move { + con_cm.connect_peer_if_necessary(con_node_id, con_addr).await + }) + })?; + + let liquidity_source = Arc::clone(&liquidity_source); + let response = tokio::task::block_in_place(move || { + runtime + .block_on(async move { liquidity_source.lsps1_check_order_status(order_id).await }) + })?; + + Ok(response) + } +}