Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add LSPS1 client-side integration #418

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
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
75 changes: 74 additions & 1 deletion bindings/ldk_node.udl
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ interface Builder {
void set_chain_source_bitcoind_rpc(string rpc_host, u16 rpc_port, string rpc_user, string rpc_password);
void set_gossip_source_p2p();
void set_gossip_source_rgs(string rgs_server_url);
void set_liquidity_source_lsps2(SocketAddress address, PublicKey node_id, string? token);
void set_liquidity_source_lsps1(PublicKey node_id, SocketAddress address, string? token);
void set_liquidity_source_lsps2(PublicKey node_id, SocketAddress address, string? token);
void set_storage_dir_path(string storage_dir_path);
void set_network(Network network);
[Throws=BuildError]
Expand Down Expand Up @@ -78,6 +79,7 @@ interface Node {
SpontaneousPayment spontaneous_payment();
OnchainPayment onchain_payment();
UnifiedQrPayment unified_qr_payment();
Lsps1Liquidity lsps1_liquidity();
[Throws=NodeError]
void connect(PublicKey node_id, SocketAddress address, boolean persist);
[Throws=NodeError]
Expand Down Expand Up @@ -173,6 +175,13 @@ interface UnifiedQrPayment {
QrPaymentResult send([ByRef]string uri_str);
};

interface Lsps1Liquidity {
[Throws=NodeError]
LSPS1OrderStatus request_channel(u64 lsp_balance_sat, u64 client_balance_sat, u32 channel_expiry_blocks, boolean announce_channel);
[Throws=NodeError]
LSPS1OrderStatus check_order_status(OrderId order_id);
};

[Error]
enum NodeError {
"AlreadyRunning",
Expand Down Expand Up @@ -220,6 +229,8 @@ enum NodeError {
"InvalidUri",
"InvalidQuantity",
"InvalidNodeAlias",
"InvalidDateTime",
"InvalidFeeRate",
"DuplicatePayment",
"UnsupportedCurrency",
"InsufficientFunds",
Expand Down Expand Up @@ -372,6 +383,59 @@ dictionary CustomTlvRecord {
sequence<u8> value;
};

dictionary LSPS1OrderStatus {
OrderId order_id;
OrderParameters order_params;
PaymentInfo payment_options;
ChannelOrderInfo? channel_state;
};

dictionary OrderParameters {
u64 lsp_balance_sat;
u64 client_balance_sat;
u16 required_channel_confirmations;
u16 funding_confirms_within_blocks;
u32 channel_expiry_blocks;
string? token;
boolean announce_channel;
};

dictionary PaymentInfo {
Bolt11PaymentInfo? bolt11;
OnchainPaymentInfo? onchain;
};

dictionary Bolt11PaymentInfo {
PaymentState state;
DateTime expires_at;
u64 fee_total_sat;
u64 order_total_sat;
Bolt11Invoice invoice;
};

dictionary OnchainPaymentInfo {
PaymentState state;
DateTime expires_at;
u64 fee_total_sat;
u64 order_total_sat;
Address address;
u16? min_onchain_payment_confirmations;
FeeRate min_fee_for_0conf;
Address? refund_onchain_address;
};

dictionary ChannelOrderInfo {
DateTime funded_at;
OutPoint funding_outpoint;
DateTime expires_at;
};

enum PaymentState {
"ExpectPayment",
"Paid",
"Refunded",
};

[Enum]
interface MaxTotalRoutingFeeLimit {
None ();
Expand Down Expand Up @@ -624,3 +688,12 @@ typedef string UntrustedString;

[Custom]
typedef string NodeAlias;

[Custom]
typedef string OrderId;

[Custom]
typedef string DateTime;

[Custom]
typedef string FeeRate;
99 changes: 62 additions & 37 deletions src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use crate::gossip::GossipSource;
use crate::io::sqlite_store::SqliteStore;
use crate::io::utils::{read_node_metrics, write_node_metrics};
use crate::io::vss_store::VssStore;
use crate::liquidity::LiquiditySource;
use crate::liquidity::LiquiditySourceBuilder;
use crate::logger::{log_error, log_info, FilesystemLogger, Logger};
use crate::message_handler::NodeCustomMessageHandler;
use crate::payment::store::PaymentStore;
Expand Down Expand Up @@ -51,9 +51,6 @@ use lightning::util::sweep::OutputSweeper;

use lightning_persister::fs_store::FilesystemStore;

use lightning_liquidity::lsps2::client::LSPS2ClientConfig;
use lightning_liquidity::{LiquidityClientConfig, LiquidityManager};

use bdk_wallet::template::Bip84;
use bdk_wallet::KeychainKind;
use bdk_wallet::Wallet as BdkWallet;
Expand Down Expand Up @@ -96,13 +93,15 @@ enum GossipSourceConfig {

#[derive(Debug, Clone)]
struct LiquiditySourceConfig {
// LSPS2 service's (address, node_id, token)
lsps2_service: Option<(SocketAddress, PublicKey, Option<String>)>,
// LSPS1 service's (node_id, address, token)
lsps1_service: Option<(PublicKey, SocketAddress, Option<String>)>,
// LSPS2 service's (node_id, address, token)
lsps2_service: Option<(PublicKey, SocketAddress, Option<String>)>,
}

impl Default for LiquiditySourceConfig {
fn default() -> Self {
Self { lsps2_service: None }
Self { lsps1_service: None, lsps2_service: None }
}
}

Expand Down Expand Up @@ -273,22 +272,41 @@ impl NodeBuilder {
self
}

/// Configures the [`Node`] instance to source its inbound liquidity from the given
/// Configures the [`Node`] instance to source inbound liquidity from the given
/// [LSPS1](https://github.com/BitcoinAndLightningLayerSpecs/lsp/blob/main/LSPS1/README.md)
/// service.
///
/// Will mark the LSP as trusted for 0-confirmation channels, see [`Config::trusted_peers_0conf`].
///
/// The given `token` will be used by the LSP to authenticate the user.
pub fn set_liquidity_source_lsps1(
&mut self, node_id: PublicKey, address: SocketAddress, token: Option<String>,
) -> &mut Self {
// Mark the LSP as trusted for 0conf
self.config.trusted_peers_0conf.push(node_id.clone());

let liquidity_source_config =
self.liquidity_source_config.get_or_insert(LiquiditySourceConfig::default());
liquidity_source_config.lsps1_service = Some((node_id, address, token));
self
}

/// Configures the [`Node`] instance to source just-in-time inbound liquidity from the given
/// [LSPS2](https://github.com/BitcoinAndLightningLayerSpecs/lsp/blob/main/LSPS2/README.md)
/// service.
///
/// Will mark the LSP as trusted for 0-confirmation channels, see [`Config::trusted_peers_0conf`].
///
/// The given `token` will be used by the LSP to authenticate the user.
pub fn set_liquidity_source_lsps2(
&mut self, address: SocketAddress, node_id: PublicKey, token: Option<String>,
&mut self, node_id: PublicKey, address: SocketAddress, token: Option<String>,
) -> &mut Self {
// Mark the LSP as trusted for 0conf
self.config.trusted_peers_0conf.push(node_id.clone());

let liquidity_source_config =
self.liquidity_source_config.get_or_insert(LiquiditySourceConfig::default());
liquidity_source_config.lsps2_service = Some((address, node_id, token));
liquidity_source_config.lsps2_service = Some((node_id, address, token));
self
}

Expand Down Expand Up @@ -592,17 +610,30 @@ impl ArcedNodeBuilder {
self.inner.write().unwrap().set_gossip_source_rgs(rgs_server_url);
}

/// Configures the [`Node`] instance to source its inbound liquidity from the given
/// Configures the [`Node`] instance to source inbound liquidity from the given
/// [LSPS1](https://github.com/BitcoinAndLightningLayerSpecs/lsp/blob/main/LSPS1/README.md)
/// service.
///
/// Will mark the LSP as trusted for 0-confirmation channels, see [`Config::trusted_peers_0conf`].
///
/// The given `token` will be used by the LSP to authenticate the user.
pub fn set_liquidity_source_lsps1(
&self, node_id: PublicKey, address: SocketAddress, token: Option<String>,
) {
self.inner.write().unwrap().set_liquidity_source_lsps1(node_id, address, token);
}

/// Configures the [`Node`] instance to source just-in-time inbound liquidity from the given
/// [LSPS2](https://github.com/BitcoinAndLightningLayerSpecs/lsp/blob/main/LSPS2/README.md)
/// service.
///
/// Will mark the LSP as trusted for 0-confirmation channels, see [`Config::trusted_peers_0conf`].
///
/// The given `token` will be used by the LSP to authenticate the user.
pub fn set_liquidity_source_lsps2(
&self, address: SocketAddress, node_id: PublicKey, token: Option<String>,
&self, node_id: PublicKey, address: SocketAddress, token: Option<String>,
) {
self.inner.write().unwrap().set_liquidity_source_lsps2(address, node_id, token);
self.inner.write().unwrap().set_liquidity_source_lsps2(node_id, address, token);
}

/// Sets the used storage directory path.
Expand Down Expand Up @@ -1056,30 +1087,24 @@ fn build_with_store_internal(
},
};

let liquidity_source = liquidity_source_config.as_ref().and_then(|lsc| {
lsc.lsps2_service.as_ref().map(|(address, node_id, token)| {
let lsps2_client_config = Some(LSPS2ClientConfig {});
let liquidity_client_config =
Some(LiquidityClientConfig { lsps1_client_config: None, lsps2_client_config });
let liquidity_manager = Arc::new(LiquidityManager::new(
Arc::clone(&keys_manager),
Arc::clone(&channel_manager),
Some(Arc::clone(&chain_source)),
None,
None,
liquidity_client_config,
));
Arc::new(LiquiditySource::new_lsps2(
address.clone(),
*node_id,
token.clone(),
Arc::clone(&channel_manager),
Arc::clone(&keys_manager),
liquidity_manager,
Arc::clone(&config),
Arc::clone(&logger),
))
})
let liquidity_source = liquidity_source_config.as_ref().map(|lsc| {
let mut liquidity_source_builder = LiquiditySourceBuilder::new(
Arc::clone(&channel_manager),
Arc::clone(&keys_manager),
Arc::clone(&chain_source),
Arc::clone(&config),
Arc::clone(&logger),
);

lsc.lsps1_service.as_ref().map(|(node_id, address, token)| {
liquidity_source_builder.lsps1_service(*node_id, address.clone(), token.clone())
});

lsc.lsps2_service.as_ref().map(|(node_id, address, token)| {
liquidity_source_builder.lsps2_service(*node_id, address.clone(), token.clone())
});

Arc::new(liquidity_source_builder.build())
});

let custom_message_handler = if let Some(liquidity_source) = liquidity_source.as_ref() {
Expand Down
6 changes: 6 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,10 @@ pub enum Error {
InvalidQuantity,
/// The given node alias is invalid.
InvalidNodeAlias,
/// The given date time is invalid.
InvalidDateTime,
/// The given fee rate is invalid.
InvalidFeeRate,
/// A payment with the given hash has already been initiated.
DuplicatePayment,
/// The provided offer was denonminated in an unsupported currency.
Expand Down Expand Up @@ -172,6 +176,8 @@ impl fmt::Display for Error {
Self::InvalidUri => write!(f, "The given URI is invalid."),
Self::InvalidQuantity => write!(f, "The given quantity is invalid."),
Self::InvalidNodeAlias => write!(f, "The given node alias is invalid."),
Self::InvalidDateTime => write!(f, "The given date time is invalid."),
Self::InvalidFeeRate => write!(f, "The given fee rate is invalid."),
Self::DuplicatePayment => {
write!(f, "A payment with the given hash has already been initiated.")
},
Expand Down
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 lightning_types;
pub use vss_client;

Expand Down Expand Up @@ -130,7 +131,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 @@ -959,6 +960,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
Loading
Loading