Skip to content

Commit

Permalink
Add LSPS1 API
Browse files Browse the repository at this point in the history
We add an `Lsps1Liquidity` API object, mirroring the approach we took
with the `payment` APIs.
  • Loading branch information
tnull committed Dec 4, 2024
1 parent eb3ee0b commit 5e8c263
Show file tree
Hide file tree
Showing 2 changed files with 148 additions and 5 deletions.
33 changes: 31 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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};
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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<Lsps1Liquidity> {
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<ChannelDetails> {
self.channel_manager.list_channels().into_iter().map(|c| c.into()).collect()
Expand Down
120 changes: 117 additions & 3 deletions src/liquidity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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<RwLock<Option<Arc<tokio::runtime::Runtime>>>>,
wallet: Arc<Wallet>,
connection_manager: Arc<ConnectionManager<Arc<FilesystemLogger>>>,
liquidity_source: Option<Arc<LiquiditySource<Arc<FilesystemLogger>>>>,
logger: Arc<FilesystemLogger>,
}

impl Lsps1Liquidity {
pub(crate) fn new(
runtime: Arc<RwLock<Option<Arc<tokio::runtime::Runtime>>>>, wallet: Arc<Wallet>,
connection_manager: Arc<ConnectionManager<Arc<FilesystemLogger>>>,
liquidity_source: Option<Arc<LiquiditySource<Arc<FilesystemLogger>>>>,
logger: Arc<FilesystemLogger>,
) -> 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<LSPS1OrderStatus, Error> {
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<LSPS1OrderStatus, Error> {
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)
}
}

0 comments on commit 5e8c263

Please sign in to comment.