Skip to content

Commit

Permalink
Merge pull request #223 from tnull/2023-12-liquidity-mgmt-integration
Browse files Browse the repository at this point in the history
Add support for liquidity mgmt. via `lightning-liquidity`
  • Loading branch information
tnull committed Feb 21, 2024
2 parents c5d8050 + 3c05b17 commit 980b14c
Show file tree
Hide file tree
Showing 15 changed files with 987 additions and 56 deletions.
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ lightning-persister = { version = "0.0.121" }
lightning-background-processor = { version = "0.0.121", features = ["futures"] }
lightning-rapid-gossip-sync = { version = "0.0.121" }
lightning-transaction-sync = { version = "0.0.121", features = ["esplora-async-https", "time"] }
lightning-liquidity = { version = "0.1.0-alpha", features = ["std"] }

#lightning = { git = "https://github.com/lightningdevkit/rust-lightning", branch="main", features = ["std"] }
#lightning-invoice = { git = "https://github.com/lightningdevkit/rust-lightning", branch="main" }
Expand All @@ -43,6 +44,7 @@ lightning-transaction-sync = { version = "0.0.121", features = ["esplora-async-h
#lightning-background-processor = { git = "https://github.com/lightningdevkit/rust-lightning", branch="main", features = ["futures"] }
#lightning-rapid-gossip-sync = { git = "https://github.com/lightningdevkit/rust-lightning", branch="main" }
#lightning-transaction-sync = { git = "https://github.com/lightningdevkit/rust-lightning", branch="main", features = ["esplora-async"] }
#lightning-liquidity = { git = "https://github.com/lightningdevkit/lightning-liquidity", branch="main", features = ["std"] }

#lightning = { path = "../rust-lightning/lightning", features = ["std"] }
#lightning-invoice = { path = "../rust-lightning/lightning-invoice" }
Expand All @@ -51,6 +53,7 @@ lightning-transaction-sync = { version = "0.0.121", features = ["esplora-async-h
#lightning-background-processor = { path = "../rust-lightning/lightning-background-processor", features = ["futures"] }
#lightning-rapid-gossip-sync = { path = "../rust-lightning/lightning-rapid-gossip-sync" }
#lightning-transaction-sync = { path = "../rust-lightning/lightning-transaction-sync", features = ["esplora-async"] }
#lightning-liquidity = { path = "../lightning-liquidity", features = ["std"] }

bdk = { version = "0.29.0", default-features = false, features = ["std", "async-interface", "use-esplora-async", "sqlite-bundled", "keys-bip39"]}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -217,8 +217,8 @@ class LibraryTest {
assert(channelReadyEvent2 is Event.ChannelReady)
node2.eventHandled()

val channelId = when (channelReadyEvent2) {
is Event.ChannelReady -> channelReadyEvent2.channelId
val userChannelId = when (channelReadyEvent2) {
is Event.ChannelReady -> channelReadyEvent2.userChannelId
else -> return
}

Expand All @@ -239,7 +239,7 @@ class LibraryTest {
assert(node1.listPayments().size == 1)
assert(node2.listPayments().size == 1)

node2.closeChannel(channelId, nodeId1)
node2.closeChannel(userChannelId, nodeId1)

val channelClosedEvent1 = node1.waitNextEvent()
println("Got event: $channelClosedEvent1")
Expand Down
32 changes: 23 additions & 9 deletions bindings/ldk_node.udl
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ interface Builder {
void set_esplora_server(string esplora_server_url);
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_storage_dir_path(string storage_dir_path);
void set_network(Network network);
[Throws=BuildError]
Expand Down Expand Up @@ -63,11 +64,11 @@ interface LDKNode {
[Throws=NodeError]
void disconnect(PublicKey node_id);
[Throws=NodeError]
void connect_open_channel(PublicKey node_id, SocketAddress address, u64 channel_amount_sats, u64? push_to_counterparty_msat, ChannelConfig? channel_config, boolean announce_channel);
UserChannelId connect_open_channel(PublicKey node_id, SocketAddress address, u64 channel_amount_sats, u64? push_to_counterparty_msat, ChannelConfig? channel_config, boolean announce_channel);
[Throws=NodeError]
void close_channel([ByRef]ChannelId channel_id, PublicKey counterparty_node_id);
void close_channel([ByRef]UserChannelId user_channel_id, PublicKey counterparty_node_id);
[Throws=NodeError]
void update_channel_config([ByRef]ChannelId channel_id, PublicKey counterparty_node_id, ChannelConfig channel_config);
void update_channel_config([ByRef]UserChannelId user_channel_id, PublicKey counterparty_node_id, ChannelConfig channel_config);
[Throws=NodeError]
void sync_wallets();
[Throws=NodeError]
Expand All @@ -86,6 +87,10 @@ interface LDKNode {
Bolt11Invoice receive_payment(u64 amount_msat, [ByRef]string description, u32 expiry_secs);
[Throws=NodeError]
Bolt11Invoice receive_variable_amount_payment([ByRef]string description, u32 expiry_secs);
[Throws=NodeError]
Bolt11Invoice receive_payment_via_jit_channel(u64 amount_msat, [ByRef]string description, u32 expiry_secs, u64? max_lsp_fee_limit_msat);
[Throws=NodeError]
Bolt11Invoice receive_variable_amount_payment_via_jit_channel([ByRef]string description, u32 expiry_secs, u64? max_proportional_lsp_fee_limit_ppm_msat);
PaymentDetails? payment([ByRef]PaymentHash payment_hash);
[Throws=NodeError]
void remove_payment([ByRef]PaymentHash payment_hash);
Expand Down Expand Up @@ -117,6 +122,7 @@ enum NodeError {
"MessageSigningFailed",
"TxSyncFailed",
"GossipUpdateFailed",
"LiquidityRequestFailed",
"InvalidAddress",
"InvalidSocketAddress",
"InvalidPublicKey",
Expand All @@ -130,6 +136,8 @@ enum NodeError {
"InvalidNetwork",
"DuplicatePayment",
"InsufficientFunds",
"LiquiditySourceUnavailable",
"LiquidityFeeTooHigh",
};

[Error]
Expand Down Expand Up @@ -168,12 +176,9 @@ enum PaymentStatus {
"Failed",
};

[NonExhaustive]
enum Network {
"Bitcoin",
"Testnet",
"Signet",
"Regtest",
dictionary LSPFeeLimits {
u64? max_total_opening_fee_msat;
u64? max_proportional_opening_fee_ppm_msat;
};

dictionary PaymentDetails {
Expand All @@ -183,6 +188,15 @@ dictionary PaymentDetails {
u64? amount_msat;
PaymentDirection direction;
PaymentStatus status;
LSPFeeLimits? lsp_fee_limits;
};

[NonExhaustive]
enum Network {
"Bitcoin",
"Testnet",
"Signet",
"Regtest",
};

dictionary OutPoint {
Expand Down
2 changes: 1 addition & 1 deletion bindings/python/src/ldk_node/test_ldk_node.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ def test_channel_full_cycle(self):
print("EVENT:", payment_received_event_2)
node_2.event_handled()

node_2.close_channel(channel_ready_event_2.channel_id, node_id_1)
node_2.close_channel(channel_ready_event_2.user_channel_id, node_id_1)

channel_closed_event_1 = node_1.wait_next_event()
assert isinstance(channel_closed_event_1, Event.CHANNEL_CLOSED)
Expand Down
114 changes: 106 additions & 8 deletions src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ use crate::fee_estimator::OnchainFeeEstimator;
use crate::gossip::GossipSource;
use crate::io;
use crate::io::sqlite_store::SqliteStore;
use crate::liquidity::LiquiditySource;
use crate::logger::{log_error, FilesystemLogger, Logger};
use crate::message_handler::NodeCustomMessageHandler;
use crate::payment_store::PaymentStore;
use crate::peer_store::PeerStore;
use crate::sweep::OutputSweeper;
Expand Down Expand Up @@ -40,6 +42,9 @@ use lightning_persister::fs_store::FilesystemStore;

use lightning_transaction_sync::EsploraSyncClient;

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

#[cfg(any(vss, vss_test))]
use crate::io::vss_store::VssStore;
use bdk::bitcoin::secp256k1::Secp256k1;
Expand All @@ -49,6 +54,7 @@ use bdk::template::Bip84;

use bip39::Mnemonic;

use bitcoin::secp256k1::PublicKey;
use bitcoin::{BlockHash, Network};

#[cfg(any(vss, vss_test))]
Expand Down Expand Up @@ -80,6 +86,18 @@ enum GossipSourceConfig {
RapidGossipSync(String),
}

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

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

/// An error encountered during building a [`Node`].
///
/// [`Node`]: crate::Node
Expand Down Expand Up @@ -146,16 +164,14 @@ pub struct NodeBuilder {
entropy_source_config: Option<EntropySourceConfig>,
chain_data_source_config: Option<ChainDataSourceConfig>,
gossip_source_config: Option<GossipSourceConfig>,
liquidity_source_config: Option<LiquiditySourceConfig>,
}

impl NodeBuilder {
/// Creates a new builder instance with the default configuration.
pub fn new() -> Self {
let config = Config::default();
let entropy_source_config = None;
let chain_data_source_config = None;
let gossip_source_config = None;
Self { config, entropy_source_config, chain_data_source_config, gossip_source_config }
Self::from_config(config)
}

/// Creates a new builder instance from an [`Config`].
Expand All @@ -164,7 +180,14 @@ impl NodeBuilder {
let entropy_source_config = None;
let chain_data_source_config = None;
let gossip_source_config = None;
Self { config, entropy_source_config, chain_data_source_config, gossip_source_config }
let liquidity_source_config = None;
Self {
config,
entropy_source_config,
chain_data_source_config,
gossip_source_config,
liquidity_source_config,
}
}

/// Configures the [`Node`] instance to source its wallet entropy from a seed file on disk.
Expand Down Expand Up @@ -218,6 +241,25 @@ impl NodeBuilder {
self
}

/// Configures the [`Node`] instance to source its 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 {
// 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));
self
}

/// Sets the used storage directory path.
pub fn set_storage_dir_path(&mut self, storage_dir_path: String) -> &mut Self {
self.config.storage_dir_path = storage_dir_path;
Expand Down Expand Up @@ -318,6 +360,7 @@ impl NodeBuilder {
config,
self.chain_data_source_config.as_ref(),
self.gossip_source_config.as_ref(),
self.liquidity_source_config.as_ref(),
seed_bytes,
logger,
vss_store,
Expand All @@ -340,6 +383,7 @@ impl NodeBuilder {
config,
self.chain_data_source_config.as_ref(),
self.gossip_source_config.as_ref(),
self.liquidity_source_config.as_ref(),
seed_bytes,
logger,
kv_store,
Expand Down Expand Up @@ -413,6 +457,19 @@ 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
/// [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.inner.write().unwrap().set_liquidity_source_lsps2(address, node_id, token);
}

/// Sets the used storage directory path.
pub fn set_storage_dir_path(&self, storage_dir_path: String) {
self.inner.write().unwrap().set_storage_dir_path(storage_dir_path);
Expand Down Expand Up @@ -463,7 +520,8 @@ impl ArcedNodeBuilder {
/// Builds a [`Node`] instance according to the options previously configured.
fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
config: Arc<Config>, chain_data_source_config: Option<&ChainDataSourceConfig>,
gossip_source_config: Option<&GossipSourceConfig>, seed_bytes: [u8; 64],
gossip_source_config: Option<&GossipSourceConfig>,
liquidity_source_config: Option<&LiquiditySourceConfig>, seed_bytes: [u8; 64],
logger: Arc<FilesystemLogger>, kv_store: Arc<K>,
) -> Result<Node<K>, BuildError> {
// Initialize the on-chain wallet and chain access
Expand Down Expand Up @@ -636,6 +694,12 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
// generating the events otherwise.
user_config.manually_accept_inbound_channels = true;
}

if liquidity_source_config.is_some() {
// Generally allow claiming underpaying HTLCs as the LSP will skim off some fee. We'll
// check that they don't take too much before claiming.
user_config.channel_config.accept_underpaying_htlcs = true;
}
let channel_manager = {
if let Ok(res) = kv_store.read(
CHANNEL_MANAGER_PERSISTENCE_PRIMARY_NAMESPACE,
Expand Down Expand Up @@ -746,20 +810,51 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
}
};

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 { lsps2_client_config });
let liquidity_manager = Arc::new(LiquidityManager::new(
Arc::clone(&keys_manager),
Arc::clone(&channel_manager),
Some(Arc::clone(&tx_sync)),
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 custom_message_handler = if let Some(liquidity_source) = liquidity_source.as_ref() {
Arc::new(NodeCustomMessageHandler::new_liquidity(Arc::clone(&liquidity_source)))
} else {
Arc::new(NodeCustomMessageHandler::new_ignoring())
};

let msg_handler = match gossip_source.as_gossip_sync() {
GossipSync::P2P(p2p_gossip_sync) => MessageHandler {
chan_handler: Arc::clone(&channel_manager),
route_handler: Arc::clone(&p2p_gossip_sync)
as Arc<dyn RoutingMessageHandler + Sync + Send>,
onion_message_handler: onion_messenger,
custom_message_handler: IgnoringMessageHandler {},
custom_message_handler,
},
GossipSync::Rapid(_) => MessageHandler {
chan_handler: Arc::clone(&channel_manager),
route_handler: Arc::new(IgnoringMessageHandler {})
as Arc<dyn RoutingMessageHandler + Sync + Send>,
onion_message_handler: onion_messenger,
custom_message_handler: IgnoringMessageHandler {},
custom_message_handler,
},
GossipSync::None => {
unreachable!("We must always have a gossip sync!");
Expand All @@ -782,6 +877,8 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
Arc::clone(&keys_manager),
));

liquidity_source.as_ref().map(|l| l.set_peer_manager(Arc::clone(&peer_manager)));

// Init payment info storage
let payment_store = match io::utils::read_payments(Arc::clone(&kv_store), Arc::clone(&logger)) {
Ok(payments) => {
Expand Down Expand Up @@ -853,6 +950,7 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
keys_manager,
network_graph,
gossip_source,
liquidity_source,
kv_store,
logger,
_router: router,
Expand Down
Loading

0 comments on commit 980b14c

Please sign in to comment.