From 30a1a4223a0fb20148eaa06ad3bc32d70d43b43a Mon Sep 17 00:00:00 2001 From: Harshit Verma Date: Mon, 22 Jul 2024 01:29:00 +0530 Subject: [PATCH] feat(lsp): be able to provide liquitidy to other nodes This commit allows our node to act as a server to provide liquidity to other nodes. --- Cargo.lock | 56 ++++++++++++ lampo-common/Cargo.toml | 2 +- lampo-common/src/lib.rs | 4 + lampo-common/src/logger.rs | 4 +- lampod/src/actions/handler.rs | 17 ++++ lampod/src/lib.rs | 3 +- lampod/src/ln/liquidity.rs | 146 +++++++++++++++++++++++++++++-- lampod/src/ln/message_handler.rs | 94 ++++++++++++++++++++ lampod/src/ln/mod.rs | 2 + lampod/src/ln/peer_manager.rs | 16 +++- 10 files changed, 329 insertions(+), 15 deletions(-) create mode 100644 lampod/src/ln/message_handler.rs diff --git a/Cargo.lock b/Cargo.lock index df8cdc1a..9687a290 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -32,6 +32,12 @@ version = "0.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0eb452e0d760aaeb2339812aef595b287a782b10ccb9a09ec9fefddf809f1ca" +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + [[package]] name = "android_log-sys" version = "0.2.0" @@ -50,6 +56,15 @@ dependencies = [ "once_cell", ] +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anstyle-query" version = "1.1.0" @@ -344,8 +359,11 @@ version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" dependencies = [ + "android-tzdata", + "iana-time-zone", "num-traits", "serde", + "windows-targets 0.52.5", ] [[package]] @@ -402,6 +420,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "core-foundation-sys" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" + [[package]] name = "crossbeam-channel" version = "0.5.13" @@ -656,6 +680,29 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3011d1213f159867b13cfd6ac92d2cd5f1345762c63be3554e84092d85a50bbd" +[[package]] +name = "iana-time-zone" +version = "0.1.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "idna" version = "0.5.0" @@ -1828,6 +1875,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.5", +] + [[package]] name = "windows-sys" version = "0.48.0" diff --git a/lampo-common/Cargo.toml b/lampo-common/Cargo.toml index b0735bcd..ce45f740 100644 --- a/lampo-common/Cargo.toml +++ b/lampo-common/Cargo.toml @@ -19,7 +19,7 @@ crossbeam-channel = "0.5.8" anyhow = "1.0.70" colored = "1.9" log = { version = "0.4", features = ["std"] } -chrono = { version = "0.4", features = ["std"], default-features = false } +chrono = { version = "0.4", features = ["std", "clock"], default-features = false } serde_json = "1.0" serde = "1.0" hex = "0.4.3" diff --git a/lampo-common/src/lib.rs b/lampo-common/src/lib.rs index 0a6d7d6e..08db1f20 100644 --- a/lampo-common/src/lib.rs +++ b/lampo-common/src/lib.rs @@ -48,3 +48,7 @@ pub mod btc_rpc { pub mempoolminfee: f32, } } + +pub mod chrono { + pub use chrono::*; +} diff --git a/lampo-common/src/logger.rs b/lampo-common/src/logger.rs index ebc10651..9575e5b6 100644 --- a/lampo-common/src/logger.rs +++ b/lampo-common/src/logger.rs @@ -8,7 +8,7 @@ use std::time::SystemTime; // FIXME: this is not async we should modify it use std::fs::File; -use chrono::prelude::*; +use crate::chrono::prelude::*; use colored::*; pub use log::{Level, Log, Metadata, Record, SetLoggerError}; @@ -53,7 +53,7 @@ impl Log for Logger { writeln!( stream, "{} {}", - DateTime::from(SystemTime::now()) + DateTime::::from(SystemTime::now()) .to_rfc3339_opts(SecondsFormat::Millis, true) .white(), message, diff --git a/lampod/src/actions/handler.rs b/lampod/src/actions/handler.rs index cf203bc3..c1985a7c 100644 --- a/lampod/src/actions/handler.rs +++ b/lampod/src/actions/handler.rs @@ -10,6 +10,8 @@ use lampo_common::event::{Emitter, Event, Subscriber}; use lampo_common::handler::Handler as EventHandler; use lampo_common::json; use lampo_common::ldk; +use lampo_common::ldk::ln::peer_handler::CustomMessageHandler; +use lampo_common::ldk::ln::wire::CustomMessageReader; use lampo_common::model::response::PaymentHop; use lampo_common::model::response::PaymentState; use lampo_common::types::ChannelState; @@ -19,6 +21,7 @@ use crate::chain::{LampoChainManager, WalletManager}; use crate::command::Command; use crate::handler::external_handler::ExternalHandler; use crate::ln::events::PeerEvents; +use crate::ln::liquidity::LampoLiquidityManager; use crate::ln::{LampoChannelManager, LampoInventoryManager, LampoPeerManager}; use crate::{async_run, LampoDaemon}; @@ -34,6 +37,7 @@ pub struct LampoHandler { wallet_manager: Arc, chain_manager: Arc, external_handlers: RefCell>>, + liquidity_manager: Option>, #[allow(dead_code)] emitter: Emitter, subscriber: Subscriber, @@ -53,6 +57,7 @@ impl LampoHandler { wallet_manager: lampod.wallet_manager(), chain_manager: lampod.onchain_manager(), external_handlers: RefCell::new(Vec::new()), + liquidity_manager: Some(lampod.liquidity()), emitter, subscriber, } @@ -142,6 +147,9 @@ impl Handler for LampoHandler { counterparty_node_id, channel_type, } => { + if self.liquidity_manager.is_some() { + self.liquidity_manager.as_ref().unwrap().channel_ready(user_channel_id, &channel_id, &counterparty_node_id)?; + } log::info!("channel ready with node `{counterparty_node_id}`, and channel type {channel_type}"); self.emit(Event::Lightning(LightningEvent::ChannelReady { counterparty_node_id, @@ -287,6 +295,15 @@ impl Handler for LampoHandler { self.emit(Event::Lightning(hop)); Ok(()) }, + ldk::events::Event::HTLCIntercepted { intercept_id, requested_next_hop_scid, payment_hash, inbound_amount_msat, expected_outbound_amount_msat } => { + log::info!("Intecepted an HTLC"); + if self.liquidity_manager.is_some() { + // TODO: Make a new function for self.liquidity_manager.as_ref().unwrap() + self.liquidity_manager.as_ref().unwrap().htlc_intercepted(requested_next_hop_scid, intercept_id, expected_outbound_amount_msat, payment_hash)?; + } + + Ok(()) + }, _ => Err(error::anyhow!("unexpected ldk event: {:?}", event)), } } diff --git a/lampod/src/lib.rs b/lampod/src/lib.rs index a64e4e0f..12253df7 100644 --- a/lampod/src/lib.rs +++ b/lampod/src/lib.rs @@ -211,7 +211,8 @@ impl LampoDaemon { pub fn init_peer_manager(&mut self) -> error::Result<()> { log::debug!(target: "lampo", "init peer manager ..."); - let mut peer_manager = LampoPeerManager::new(&self.conf, self.logger.clone()); + // TODO(Harshit): When we want to run as LSP. Configure this! + let mut peer_manager = LampoPeerManager::new(&self.conf, self.logger.clone(), None); peer_manager.init( self.onchain_manager(), self.wallet_manager.clone(), diff --git a/lampod/src/ln/liquidity.rs b/lampod/src/ln/liquidity.rs index 4d0f1cff..3ef16d03 100644 --- a/lampod/src/ln/liquidity.rs +++ b/lampod/src/ln/liquidity.rs @@ -3,6 +3,9 @@ use std::time::Duration; use lampo_common::bitcoin::hashes::sha256; use lampo_common::bitcoin::hashes::Hash; +use lampo_common::chrono; +use lampo_common::chrono::DateTime; +use lampo_common::chrono::Utc; use lampo_common::conf::LampoConf; use lampo_common::error; use lampo_common::keys::LampoKeysManager; @@ -11,8 +14,11 @@ use lampo_common::ldk::invoice::InvoiceBuilder; use lampo_common::ldk::invoice::RouteHint; use lampo_common::ldk::invoice::RouteHintHop; use lampo_common::ldk::invoice::RoutingFees; +use lampo_common::ldk::ln::channelmanager::InterceptId; use lampo_common::ldk::ln::channelmanager::MIN_FINAL_CLTV_EXPIRY_DELTA; use lampo_common::ldk::ln::msgs::SocketAddress; +use lampo_common::ldk::ln::ChannelId; +use lampo_common::ldk::ln::PaymentHash; use lampo_common::secp256k1::PublicKey; use lampo_common::secp256k1::Secp256k1; @@ -21,6 +27,7 @@ use lightning_liquidity::lsps0::ser::RequestId; use lightning_liquidity::lsps2::event::LSPS2ClientEvent; use lightning_liquidity::lsps2::event::LSPS2ServiceEvent; use lightning_liquidity::lsps2::msgs::OpeningFeeParams; +use lightning_liquidity::lsps2::msgs::RawOpeningFeeParams; use lightning_liquidity::LiquidityManager; use crate::chain::LampoChainManager; @@ -96,6 +103,46 @@ impl LampoLiquidityManager { } } + pub fn liquidity_manager(&self) -> Arc { + self.lampo_liquidity.clone() + } + + pub fn htlc_intercepted( + &self, + intercept_scid: u64, + intercept_id: InterceptId, + expected_outbound_amount_msat: u64, + payment_hash: PaymentHash, + ) -> error::Result<()> { + self.liquidity_manager() + .lsps2_service_handler() + .unwrap() + .htlc_intercepted( + intercept_scid, + intercept_id, + expected_outbound_amount_msat, + payment_hash, + ) + .map_err(|e| error::anyhow!("Error : {:?}", e))?; + + Ok(()) + } + + pub fn channel_ready( + &self, + user_channel_id: u128, + channel_id: &ChannelId, + counterparty_node_id: &PublicKey, + ) -> error::Result<()> { + self.lampo_liquidity + .lsps2_service_handler() + .unwrap() + .channel_ready(user_channel_id, channel_id, counterparty_node_id) + .map_err(|e| error::anyhow!("Error occured : {:?}", e))?; + + Ok(()) + } + pub fn get_events(&self) -> Vec { self.lampo_liquidity.get_and_clear_pending_events() } @@ -138,24 +185,107 @@ impl LampoLiquidityManager { Ok(()) } - Event::LSPS2Service(LSPS2ServiceEvent::BuyRequest { - request_id, - counterparty_node_id, - opening_fee_params, - payment_size_msat, - }) => todo!(), Event::LSPS2Service(LSPS2ServiceEvent::GetInfo { request_id, counterparty_node_id, token, - }) => todo!(), + }) => { + let service_handler = self.lampo_liquidity.lsps2_service_handler().unwrap(); + + let min_fee_msat = 0; + let proportional = 0; + let mut valid_until: DateTime = Utc::now(); + valid_until += Duration::from_secs_f64(600_f64); + let min_lifetime = 1008; + let max_client_to_self_delay = 144; + let min_payment_size_msat = 1000; + let max_payment_size_msat = 10_000_000_000; + + let opening_fee_params = RawOpeningFeeParams { + min_fee_msat, + proportional, + valid_until, + min_lifetime, + max_client_to_self_delay, + min_payment_size_msat, + max_payment_size_msat, + }; + + let opening_fee_params_menu = vec![opening_fee_params]; + + service_handler + .opening_fee_params_generated( + &counterparty_node_id, + request_id, + opening_fee_params_menu, + ) + .map_err(|e| error::anyhow!("Error : {:?}", e))?; + + Ok(()) + } + Event::LSPS2Service(LSPS2ServiceEvent::BuyRequest { + request_id, + counterparty_node_id, + opening_fee_params: _, + payment_size_msat: _, + }) => { + let user_channel_id = 0; + let scid = self + .channel_manager + .channeld + .as_ref() + .unwrap() + .get_intercept_scid(); + let cltv_expiry_delta = 72; + let client_trusts_lsp = true; + + let lsps2_service_handler = self.lampo_liquidity.lsps2_service_handler().unwrap(); + + lsps2_service_handler + .invoice_parameters_generated( + &counterparty_node_id, + request_id, + scid, + cltv_expiry_delta, + client_trusts_lsp, + user_channel_id, + ) + .map_err(|e| error::anyhow!("Error occured: {:?}", e))?; + + Ok(()) + } Event::LSPS2Service(LSPS2ServiceEvent::OpenChannel { their_network_key, amt_to_forward_msat, opening_fee_msat, user_channel_id, intercept_scid, - }) => todo!(), + }) => { + let channel_size_sats = (amt_to_forward_msat / 1000) * 4; + let mut config = self.lampo_conf.ldk_conf; + config + .channel_handshake_config + .max_inbound_htlc_value_in_flight_percent_of_channel = 100; + config.channel_config.forwarding_fee_base_msat = 0; + config.channel_config.forwarding_fee_proportional_millionths = 0; + + // TODO(Harshit): Make a different function to get channeld + self.channel_manager + .channeld + .as_ref() + .unwrap() + .create_channel( + their_network_key, + channel_size_sats, + 0, + user_channel_id, + None, + Some(config), + ) + .map_err(|e| error::anyhow!("Error occured: {:?}", e))?; + + Ok(()) + } } } diff --git a/lampod/src/ln/message_handler.rs b/lampod/src/ln/message_handler.rs new file mode 100644 index 00000000..ca4cb8ab --- /dev/null +++ b/lampod/src/ln/message_handler.rs @@ -0,0 +1,94 @@ +use crate::LampoLiquidityManager; +use std::ops::Deref; +use std::sync::Arc; + +use lampo_common::bitcoin::secp256k1::PublicKey; +use lampo_common::ldk::ln::features::{InitFeatures, NodeFeatures}; +use lampo_common::ldk::ln::peer_handler::CustomMessageHandler; +use lampo_common::ldk::ln::wire::CustomMessageReader; + +use lightning_liquidity::lsps0::ser::RawLSPSMessage; + +pub enum LampoCustomMessageHandler { + Ignoring, + Liquidity { + liquidity: Arc, + }, +} + +impl LampoCustomMessageHandler { + pub(crate) fn new_liquidity(liquidity: Arc) -> Self { + Self::Liquidity { liquidity } + } + + pub(crate) fn new_ignoring() -> Self { + Self::Ignoring + } +} + +impl CustomMessageReader for LampoCustomMessageHandler { + type CustomMessage = RawLSPSMessage; + + fn read( + &self, + message_type: u16, + buffer: &mut RD, + ) -> Result, lampo_common::ldk::ln::msgs::DecodeError> { + match self { + Self::Ignoring => Ok(None), + Self::Liquidity { liquidity, .. } => { + liquidity.liquidity_manager().read(message_type, buffer) + } + } + } +} + +impl CustomMessageHandler for LampoCustomMessageHandler { + fn handle_custom_message( + &self, + msg: Self::CustomMessage, + sender_node_id: &PublicKey, + ) -> Result<(), lampo_common::ldk::ln::msgs::LightningError> { + match self { + Self::Ignoring => Ok(()), + Self::Liquidity { liquidity, .. } => liquidity + .liquidity_manager() + .handle_custom_message(msg, sender_node_id), + } + } + + fn get_and_clear_pending_msg(&self) -> Vec<(PublicKey, Self::CustomMessage)> { + match self { + Self::Ignoring => Vec::new(), + Self::Liquidity { liquidity, .. } => { + liquidity.liquidity_manager().get_and_clear_pending_msg() + } + } + } + + fn provided_node_features(&self) -> NodeFeatures { + match self { + Self::Ignoring => NodeFeatures::empty(), + Self::Liquidity { liquidity, .. } => { + liquidity.liquidity_manager().provided_node_features() + } + } + } + + fn provided_init_features(&self, their_node_id: &PublicKey) -> InitFeatures { + match self { + Self::Ignoring => InitFeatures::empty(), + Self::Liquidity { liquidity, .. } => liquidity + .liquidity_manager() + .provided_init_features(their_node_id), + } + } +} + +impl Deref for LampoCustomMessageHandler { + type Target = Self; + + fn deref(&self) -> &Self::Target { + &self + } +} diff --git a/lampod/src/ln/mod.rs b/lampod/src/ln/mod.rs index 11615ee8..749cbafb 100644 --- a/lampod/src/ln/mod.rs +++ b/lampod/src/ln/mod.rs @@ -6,10 +6,12 @@ mod peer_manager; pub mod events; pub mod liquidity; +pub mod message_handler; pub mod peer_event; pub use channel_manager::LampoChannel; pub use channel_manager::LampoChannelManager; pub use inventory_manager::LampoInventoryManager; +pub use message_handler::LampoCustomMessageHandler; pub use offchain_manager::OffchainManager; pub use peer_manager::LampoPeerManager; diff --git a/lampod/src/ln/peer_manager.rs b/lampod/src/ln/peer_manager.rs index 13bbf1eb..bdca10a5 100644 --- a/lampod/src/ln/peer_manager.rs +++ b/lampod/src/ln/peer_manager.rs @@ -19,6 +19,8 @@ use lampo_common::ldk::routing::gossip::{NetworkGraph, P2PGossipSync}; use lampo_common::model::Connect; use lampo_common::types::NodeId; +use super::liquidity::{self, LampoLiquidityManager}; +use super::LampoCustomMessageHandler; use crate::async_run; use crate::chain::{LampoChainManager, WalletManager}; use crate::ln::LampoChannelManager; @@ -45,7 +47,7 @@ pub type SimpleArcPeerManager = PeerManager< Arc>, Arc, // Implement a custom messagehandler for liquidity See https://docs.rs/lightning-liquidity/0.1.0-alpha.4/lightning_liquidity/struct.LiquidityManager.html# - IgnoringMessageHandler, + LampoCustomMessageHandler, Arc, >; @@ -57,15 +59,21 @@ pub struct LampoPeerManager { channel_manager: Option>, conf: LampoConf, logger: Arc, + liquidity: Option>, } impl LampoPeerManager { - pub fn new(conf: &LampoConf, logger: Arc) -> LampoPeerManager { + pub fn new( + conf: &LampoConf, + logger: Arc, + liquidity: Option>, + ) -> LampoPeerManager { LampoPeerManager { peer_manager: None, conf: conf.to_owned(), logger, channel_manager: None, + liquidity, } } @@ -103,11 +111,13 @@ impl LampoPeerManager { self.logger.clone(), )); + // TODO: custom_message_handler should be LampoCustomMessageHandler::new_liquidity + // when we are acting as a client/server for liquidity let lightning_msg_handler = MessageHandler { chan_handler: channel_manager.channeld.clone().unwrap(), onion_message_handler: onion_messenger, route_handler: gossip_sync, - custom_message_handler: IgnoringMessageHandler {}, + custom_message_handler: LampoCustomMessageHandler::new_ignoring(), }; let peer_manager = InnerLampoPeerManager::new(