Skip to content

Commit 980b14c

Browse files
authored
Merge pull request #223 from tnull/2023-12-liquidity-mgmt-integration
Add support for liquidity mgmt. via `lightning-liquidity`
2 parents c5d8050 + 3c05b17 commit 980b14c

File tree

15 files changed

+987
-56
lines changed

15 files changed

+987
-56
lines changed

Cargo.toml

+3
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ lightning-persister = { version = "0.0.121" }
3535
lightning-background-processor = { version = "0.0.121", features = ["futures"] }
3636
lightning-rapid-gossip-sync = { version = "0.0.121" }
3737
lightning-transaction-sync = { version = "0.0.121", features = ["esplora-async-https", "time"] }
38+
lightning-liquidity = { version = "0.1.0-alpha", features = ["std"] }
3839

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

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

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

bindings/kotlin/ldk-node-jvm/lib/src/test/kotlin/org/lightningdevkit/ldknode/LibraryTest.kt

+3-3
Original file line numberDiff line numberDiff line change
@@ -217,8 +217,8 @@ class LibraryTest {
217217
assert(channelReadyEvent2 is Event.ChannelReady)
218218
node2.eventHandled()
219219

220-
val channelId = when (channelReadyEvent2) {
221-
is Event.ChannelReady -> channelReadyEvent2.channelId
220+
val userChannelId = when (channelReadyEvent2) {
221+
is Event.ChannelReady -> channelReadyEvent2.userChannelId
222222
else -> return
223223
}
224224

@@ -239,7 +239,7 @@ class LibraryTest {
239239
assert(node1.listPayments().size == 1)
240240
assert(node2.listPayments().size == 1)
241241

242-
node2.closeChannel(channelId, nodeId1)
242+
node2.closeChannel(userChannelId, nodeId1)
243243

244244
val channelClosedEvent1 = node1.waitNextEvent()
245245
println("Got event: $channelClosedEvent1")

bindings/ldk_node.udl

+23-9
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ interface Builder {
2828
void set_esplora_server(string esplora_server_url);
2929
void set_gossip_source_p2p();
3030
void set_gossip_source_rgs(string rgs_server_url);
31+
void set_liquidity_source_lsps2(SocketAddress address, PublicKey node_id, string? token);
3132
void set_storage_dir_path(string storage_dir_path);
3233
void set_network(Network network);
3334
[Throws=BuildError]
@@ -63,11 +64,11 @@ interface LDKNode {
6364
[Throws=NodeError]
6465
void disconnect(PublicKey node_id);
6566
[Throws=NodeError]
66-
void connect_open_channel(PublicKey node_id, SocketAddress address, u64 channel_amount_sats, u64? push_to_counterparty_msat, ChannelConfig? channel_config, boolean announce_channel);
67+
UserChannelId connect_open_channel(PublicKey node_id, SocketAddress address, u64 channel_amount_sats, u64? push_to_counterparty_msat, ChannelConfig? channel_config, boolean announce_channel);
6768
[Throws=NodeError]
68-
void close_channel([ByRef]ChannelId channel_id, PublicKey counterparty_node_id);
69+
void close_channel([ByRef]UserChannelId user_channel_id, PublicKey counterparty_node_id);
6970
[Throws=NodeError]
70-
void update_channel_config([ByRef]ChannelId channel_id, PublicKey counterparty_node_id, ChannelConfig channel_config);
71+
void update_channel_config([ByRef]UserChannelId user_channel_id, PublicKey counterparty_node_id, ChannelConfig channel_config);
7172
[Throws=NodeError]
7273
void sync_wallets();
7374
[Throws=NodeError]
@@ -86,6 +87,10 @@ interface LDKNode {
8687
Bolt11Invoice receive_payment(u64 amount_msat, [ByRef]string description, u32 expiry_secs);
8788
[Throws=NodeError]
8889
Bolt11Invoice receive_variable_amount_payment([ByRef]string description, u32 expiry_secs);
90+
[Throws=NodeError]
91+
Bolt11Invoice receive_payment_via_jit_channel(u64 amount_msat, [ByRef]string description, u32 expiry_secs, u64? max_lsp_fee_limit_msat);
92+
[Throws=NodeError]
93+
Bolt11Invoice receive_variable_amount_payment_via_jit_channel([ByRef]string description, u32 expiry_secs, u64? max_proportional_lsp_fee_limit_ppm_msat);
8994
PaymentDetails? payment([ByRef]PaymentHash payment_hash);
9095
[Throws=NodeError]
9196
void remove_payment([ByRef]PaymentHash payment_hash);
@@ -117,6 +122,7 @@ enum NodeError {
117122
"MessageSigningFailed",
118123
"TxSyncFailed",
119124
"GossipUpdateFailed",
125+
"LiquidityRequestFailed",
120126
"InvalidAddress",
121127
"InvalidSocketAddress",
122128
"InvalidPublicKey",
@@ -130,6 +136,8 @@ enum NodeError {
130136
"InvalidNetwork",
131137
"DuplicatePayment",
132138
"InsufficientFunds",
139+
"LiquiditySourceUnavailable",
140+
"LiquidityFeeTooHigh",
133141
};
134142

135143
[Error]
@@ -168,12 +176,9 @@ enum PaymentStatus {
168176
"Failed",
169177
};
170178

171-
[NonExhaustive]
172-
enum Network {
173-
"Bitcoin",
174-
"Testnet",
175-
"Signet",
176-
"Regtest",
179+
dictionary LSPFeeLimits {
180+
u64? max_total_opening_fee_msat;
181+
u64? max_proportional_opening_fee_ppm_msat;
177182
};
178183

179184
dictionary PaymentDetails {
@@ -183,6 +188,15 @@ dictionary PaymentDetails {
183188
u64? amount_msat;
184189
PaymentDirection direction;
185190
PaymentStatus status;
191+
LSPFeeLimits? lsp_fee_limits;
192+
};
193+
194+
[NonExhaustive]
195+
enum Network {
196+
"Bitcoin",
197+
"Testnet",
198+
"Signet",
199+
"Regtest",
186200
};
187201

188202
dictionary OutPoint {

bindings/python/src/ldk_node/test_ldk_node.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ def test_channel_full_cycle(self):
198198
print("EVENT:", payment_received_event_2)
199199
node_2.event_handled()
200200

201-
node_2.close_channel(channel_ready_event_2.channel_id, node_id_1)
201+
node_2.close_channel(channel_ready_event_2.user_channel_id, node_id_1)
202202

203203
channel_closed_event_1 = node_1.wait_next_event()
204204
assert isinstance(channel_closed_event_1, Event.CHANNEL_CLOSED)

src/builder.rs

+106-8
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ use crate::fee_estimator::OnchainFeeEstimator;
33
use crate::gossip::GossipSource;
44
use crate::io;
55
use crate::io::sqlite_store::SqliteStore;
6+
use crate::liquidity::LiquiditySource;
67
use crate::logger::{log_error, FilesystemLogger, Logger};
8+
use crate::message_handler::NodeCustomMessageHandler;
79
use crate::payment_store::PaymentStore;
810
use crate::peer_store::PeerStore;
911
use crate::sweep::OutputSweeper;
@@ -40,6 +42,9 @@ use lightning_persister::fs_store::FilesystemStore;
4042

4143
use lightning_transaction_sync::EsploraSyncClient;
4244

45+
use lightning_liquidity::lsps2::client::LSPS2ClientConfig;
46+
use lightning_liquidity::{LiquidityClientConfig, LiquidityManager};
47+
4348
#[cfg(any(vss, vss_test))]
4449
use crate::io::vss_store::VssStore;
4550
use bdk::bitcoin::secp256k1::Secp256k1;
@@ -49,6 +54,7 @@ use bdk::template::Bip84;
4954

5055
use bip39::Mnemonic;
5156

57+
use bitcoin::secp256k1::PublicKey;
5258
use bitcoin::{BlockHash, Network};
5359

5460
#[cfg(any(vss, vss_test))]
@@ -80,6 +86,18 @@ enum GossipSourceConfig {
8086
RapidGossipSync(String),
8187
}
8288

89+
#[derive(Debug, Clone)]
90+
struct LiquiditySourceConfig {
91+
// LSPS2 service's (address, node_id, token)
92+
lsps2_service: Option<(SocketAddress, PublicKey, Option<String>)>,
93+
}
94+
95+
impl Default for LiquiditySourceConfig {
96+
fn default() -> Self {
97+
Self { lsps2_service: None }
98+
}
99+
}
100+
83101
/// An error encountered during building a [`Node`].
84102
///
85103
/// [`Node`]: crate::Node
@@ -146,16 +164,14 @@ pub struct NodeBuilder {
146164
entropy_source_config: Option<EntropySourceConfig>,
147165
chain_data_source_config: Option<ChainDataSourceConfig>,
148166
gossip_source_config: Option<GossipSourceConfig>,
167+
liquidity_source_config: Option<LiquiditySourceConfig>,
149168
}
150169

151170
impl NodeBuilder {
152171
/// Creates a new builder instance with the default configuration.
153172
pub fn new() -> Self {
154173
let config = Config::default();
155-
let entropy_source_config = None;
156-
let chain_data_source_config = None;
157-
let gossip_source_config = None;
158-
Self { config, entropy_source_config, chain_data_source_config, gossip_source_config }
174+
Self::from_config(config)
159175
}
160176

161177
/// Creates a new builder instance from an [`Config`].
@@ -164,7 +180,14 @@ impl NodeBuilder {
164180
let entropy_source_config = None;
165181
let chain_data_source_config = None;
166182
let gossip_source_config = None;
167-
Self { config, entropy_source_config, chain_data_source_config, gossip_source_config }
183+
let liquidity_source_config = None;
184+
Self {
185+
config,
186+
entropy_source_config,
187+
chain_data_source_config,
188+
gossip_source_config,
189+
liquidity_source_config,
190+
}
168191
}
169192

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

244+
/// Configures the [`Node`] instance to source its inbound liquidity from the given
245+
/// [LSPS2](https://github.com/BitcoinAndLightningLayerSpecs/lsp/blob/main/LSPS2/README.md)
246+
/// service.
247+
///
248+
/// Will mark the LSP as trusted for 0-confirmation channels, see [`Config::trusted_peers_0conf`].
249+
///
250+
/// The given `token` will be used by the LSP to authenticate the user.
251+
pub fn set_liquidity_source_lsps2(
252+
&mut self, address: SocketAddress, node_id: PublicKey, token: Option<String>,
253+
) -> &mut Self {
254+
// Mark the LSP as trusted for 0conf
255+
self.config.trusted_peers_0conf.push(node_id.clone());
256+
257+
let liquidity_source_config =
258+
self.liquidity_source_config.get_or_insert(LiquiditySourceConfig::default());
259+
liquidity_source_config.lsps2_service = Some((address, node_id, token));
260+
self
261+
}
262+
221263
/// Sets the used storage directory path.
222264
pub fn set_storage_dir_path(&mut self, storage_dir_path: String) -> &mut Self {
223265
self.config.storage_dir_path = storage_dir_path;
@@ -318,6 +360,7 @@ impl NodeBuilder {
318360
config,
319361
self.chain_data_source_config.as_ref(),
320362
self.gossip_source_config.as_ref(),
363+
self.liquidity_source_config.as_ref(),
321364
seed_bytes,
322365
logger,
323366
vss_store,
@@ -340,6 +383,7 @@ impl NodeBuilder {
340383
config,
341384
self.chain_data_source_config.as_ref(),
342385
self.gossip_source_config.as_ref(),
386+
self.liquidity_source_config.as_ref(),
343387
seed_bytes,
344388
logger,
345389
kv_store,
@@ -413,6 +457,19 @@ impl ArcedNodeBuilder {
413457
self.inner.write().unwrap().set_gossip_source_rgs(rgs_server_url);
414458
}
415459

460+
/// Configures the [`Node`] instance to source its inbound liquidity from the given
461+
/// [LSPS2](https://github.com/BitcoinAndLightningLayerSpecs/lsp/blob/main/LSPS2/README.md)
462+
/// service.
463+
///
464+
/// Will mark the LSP as trusted for 0-confirmation channels, see [`Config::trusted_peers_0conf`].
465+
///
466+
/// The given `token` will be used by the LSP to authenticate the user.
467+
pub fn set_liquidity_source_lsps2(
468+
&self, address: SocketAddress, node_id: PublicKey, token: Option<String>,
469+
) {
470+
self.inner.write().unwrap().set_liquidity_source_lsps2(address, node_id, token);
471+
}
472+
416473
/// Sets the used storage directory path.
417474
pub fn set_storage_dir_path(&self, storage_dir_path: String) {
418475
self.inner.write().unwrap().set_storage_dir_path(storage_dir_path);
@@ -463,7 +520,8 @@ impl ArcedNodeBuilder {
463520
/// Builds a [`Node`] instance according to the options previously configured.
464521
fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
465522
config: Arc<Config>, chain_data_source_config: Option<&ChainDataSourceConfig>,
466-
gossip_source_config: Option<&GossipSourceConfig>, seed_bytes: [u8; 64],
523+
gossip_source_config: Option<&GossipSourceConfig>,
524+
liquidity_source_config: Option<&LiquiditySourceConfig>, seed_bytes: [u8; 64],
467525
logger: Arc<FilesystemLogger>, kv_store: Arc<K>,
468526
) -> Result<Node<K>, BuildError> {
469527
// Initialize the on-chain wallet and chain access
@@ -636,6 +694,12 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
636694
// generating the events otherwise.
637695
user_config.manually_accept_inbound_channels = true;
638696
}
697+
698+
if liquidity_source_config.is_some() {
699+
// Generally allow claiming underpaying HTLCs as the LSP will skim off some fee. We'll
700+
// check that they don't take too much before claiming.
701+
user_config.channel_config.accept_underpaying_htlcs = true;
702+
}
639703
let channel_manager = {
640704
if let Ok(res) = kv_store.read(
641705
CHANNEL_MANAGER_PERSISTENCE_PRIMARY_NAMESPACE,
@@ -746,20 +810,51 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
746810
}
747811
};
748812

813+
let liquidity_source = liquidity_source_config.as_ref().and_then(|lsc| {
814+
lsc.lsps2_service.as_ref().map(|(address, node_id, token)| {
815+
let lsps2_client_config = Some(LSPS2ClientConfig {});
816+
let liquidity_client_config = Some(LiquidityClientConfig { lsps2_client_config });
817+
let liquidity_manager = Arc::new(LiquidityManager::new(
818+
Arc::clone(&keys_manager),
819+
Arc::clone(&channel_manager),
820+
Some(Arc::clone(&tx_sync)),
821+
None,
822+
None,
823+
liquidity_client_config,
824+
));
825+
Arc::new(LiquiditySource::new_lsps2(
826+
address.clone(),
827+
*node_id,
828+
token.clone(),
829+
Arc::clone(&channel_manager),
830+
Arc::clone(&keys_manager),
831+
liquidity_manager,
832+
Arc::clone(&config),
833+
Arc::clone(&logger),
834+
))
835+
})
836+
});
837+
838+
let custom_message_handler = if let Some(liquidity_source) = liquidity_source.as_ref() {
839+
Arc::new(NodeCustomMessageHandler::new_liquidity(Arc::clone(&liquidity_source)))
840+
} else {
841+
Arc::new(NodeCustomMessageHandler::new_ignoring())
842+
};
843+
749844
let msg_handler = match gossip_source.as_gossip_sync() {
750845
GossipSync::P2P(p2p_gossip_sync) => MessageHandler {
751846
chan_handler: Arc::clone(&channel_manager),
752847
route_handler: Arc::clone(&p2p_gossip_sync)
753848
as Arc<dyn RoutingMessageHandler + Sync + Send>,
754849
onion_message_handler: onion_messenger,
755-
custom_message_handler: IgnoringMessageHandler {},
850+
custom_message_handler,
756851
},
757852
GossipSync::Rapid(_) => MessageHandler {
758853
chan_handler: Arc::clone(&channel_manager),
759854
route_handler: Arc::new(IgnoringMessageHandler {})
760855
as Arc<dyn RoutingMessageHandler + Sync + Send>,
761856
onion_message_handler: onion_messenger,
762-
custom_message_handler: IgnoringMessageHandler {},
857+
custom_message_handler,
763858
},
764859
GossipSync::None => {
765860
unreachable!("We must always have a gossip sync!");
@@ -782,6 +877,8 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
782877
Arc::clone(&keys_manager),
783878
));
784879

880+
liquidity_source.as_ref().map(|l| l.set_peer_manager(Arc::clone(&peer_manager)));
881+
785882
// Init payment info storage
786883
let payment_store = match io::utils::read_payments(Arc::clone(&kv_store), Arc::clone(&logger)) {
787884
Ok(payments) => {
@@ -853,6 +950,7 @@ fn build_with_store_internal<K: KVStore + Sync + Send + 'static>(
853950
keys_manager,
854951
network_graph,
855952
gossip_source,
953+
liquidity_source,
856954
kv_store,
857955
logger,
858956
_router: router,

0 commit comments

Comments
 (0)