diff --git a/README.md b/README.md index 7d7ecdfdb..5f978410a 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ LDK Node is a non-custodial Lightning node in library form. Its central goal is The primary abstraction of the library is the `Node`, which can be retrieved by setting up and configuring a `Builder` to your liking and calling `build()`. `Node` can then be controlled via commands such as `start`, `stop`, `connect_open_channel`, `send_payment`, etc.: ```rust -use ldk_node::Builder; +use ldk_node::{Builder, NetAddress}; use ldk_node::lightning_invoice::Invoice; use ldk_node::bitcoin::secp256k1::PublicKey; use std::str::FromStr; @@ -28,7 +28,7 @@ fn main() { node.sync_wallets().unwrap(); let node_id = PublicKey::from_str("NODE_ID").unwrap(); - let node_addr = "IP_ADDR:PORT".parse().unwrap(); + let node_addr = NetAddress::from_str("IP_ADDR:PORT").unwrap(); node.connect_open_channel(node_id, node_addr, 10000, None, false).unwrap(); let invoice = Invoice::from_str("INVOICE_STR").unwrap(); diff --git a/bindings/ldk_node.udl b/bindings/ldk_node.udl index d8627fb07..86507d2dd 100644 --- a/bindings/ldk_node.udl +++ b/bindings/ldk_node.udl @@ -36,11 +36,11 @@ interface Node { [Throws=NodeError] u64 total_onchain_balance_sats(); [Throws=NodeError] - void connect(PublicKey node_id, SocketAddr address, boolean permanently); + void connect(PublicKey node_id, NetAddress address, boolean permanently); [Throws=NodeError] void disconnect(PublicKey node_id); [Throws=NodeError] - void connect_open_channel(PublicKey node_id, SocketAddr address, u64 channel_amount_sats, u64? push_to_counterparty_msat, boolean announce_channel); + void connect_open_channel(PublicKey node_id, NetAddress address, u64 channel_amount_sats, u64? push_to_counterparty_msat, boolean announce_channel); [Throws=NodeError] void close_channel([ByRef]ChannelId channel_id, PublicKey counterparty_node_id); [Throws=NodeError] @@ -69,6 +69,7 @@ enum NodeError { "InvoiceCreationFailed", "PaymentFailed", "PeerInfoParseFailed", + "PeerInfoNotFound", "ChannelCreationFailed", "ChannelClosingFailed", "PersistenceFailed", @@ -129,6 +130,9 @@ typedef string Txid; [Custom] typedef string SocketAddr; +[Custom] +typedef string NetAddress; + [Custom] typedef string PublicKey; diff --git a/src/error.rs b/src/error.rs index 9a9a9c512..61f7aa53f 100644 --- a/src/error.rs +++ b/src/error.rs @@ -17,6 +17,8 @@ pub enum Error { PaymentFailed, /// A given peer info could not be parsed. PeerInfoParseFailed, + /// A given peer info could not be found. + PeerInfoNotFound, /// A channel could not be opened. ChannelCreationFailed, /// A channel could not be closed. @@ -65,6 +67,7 @@ impl fmt::Display for Error { Self::InvoiceCreationFailed => write!(f, "Failed to create invoice."), Self::PaymentFailed => write!(f, "Failed to send the given payment."), Self::PeerInfoParseFailed => write!(f, "Failed to parse the given peer information."), + Self::PeerInfoNotFound => write!(f, "Failed to resolve the given peer information."), Self::ChannelCreationFailed => write!(f, "Failed to create channel."), Self::ChannelClosingFailed => write!(f, "Failed to close channel."), Self::PersistenceFailed => write!(f, "Failed to persist data."), diff --git a/src/lib.rs b/src/lib.rs index dc2c1a256..75d277150 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,7 +26,7 @@ //! [`send_payment`], etc.: //! //! ```no_run -//! use ldk_node::Builder; +//! use ldk_node::{Builder, NetAddress}; //! use ldk_node::lightning_invoice::Invoice; //! use ldk_node::bitcoin::secp256k1::PublicKey; //! use std::str::FromStr; @@ -46,7 +46,7 @@ //! node.sync_wallets().unwrap(); //! //! let node_id = PublicKey::from_str("NODE_ID").unwrap(); -//! let node_addr = "IP_ADDR:PORT".parse().unwrap(); +//! let node_addr = NetAddress::from_str("IP_ADDR:PORT").unwrap(); //! node.connect_open_channel(node_id, node_addr, 10000, None, false).unwrap(); //! //! let invoice = Invoice::from_str("INVOICE_STR").unwrap(); @@ -95,6 +95,8 @@ pub use error::Error as NodeError; use error::Error; pub use event::Event; +pub use types::NetAddress; + use event::{EventHandler, EventQueue}; use io::fs_store::FilesystemStore; use io::{KVStore, CHANNEL_MANAGER_PERSISTENCE_KEY, CHANNEL_MANAGER_PERSISTENCE_NAMESPACE}; @@ -150,7 +152,7 @@ use rand::Rng; use std::convert::TryInto; use std::default::Default; use std::fs; -use std::net::SocketAddr; +use std::net::{SocketAddr, ToSocketAddrs}; use std::str::FromStr; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Mutex, RwLock}; @@ -758,7 +760,7 @@ impl Node { { if let Some(peer_info) = connect_peer_store.get_peer(&node_id) { let _ = do_connect_peer( - peer_info.pubkey, + peer_info.node_id, peer_info.address, Arc::clone(&connect_pm), Arc::clone(&connect_logger), @@ -910,7 +912,7 @@ impl Node { /// /// If `permanently` is set to `true`, we'll remember the peer and reconnect to it on restart. pub fn connect( - &self, node_id: PublicKey, address: SocketAddr, permanently: bool, + &self, node_id: PublicKey, address: NetAddress, permanently: bool, ) -> Result<(), Error> { let rt_lock = self.runtime.read().unwrap(); if rt_lock.is_none() { @@ -918,10 +920,10 @@ impl Node { } let runtime = rt_lock.as_ref().unwrap(); - let peer_info = PeerInfo { pubkey: node_id, address }; + let peer_info = PeerInfo { node_id, address }; - let con_peer_pubkey = peer_info.pubkey; - let con_peer_addr = peer_info.address; + let con_node_id = peer_info.node_id; + let con_addr = peer_info.address.clone(); let con_success = Arc::new(AtomicBool::new(false)); let con_success_cloned = Arc::clone(&con_success); let con_logger = Arc::clone(&self.logger); @@ -930,8 +932,7 @@ impl Node { tokio::task::block_in_place(move || { runtime.block_on(async move { let res = - connect_peer_if_necessary(con_peer_pubkey, con_peer_addr, con_pm, con_logger) - .await; + connect_peer_if_necessary(con_node_id, con_addr, con_pm, con_logger).await; con_success_cloned.store(res.is_ok(), Ordering::Release); }) }); @@ -940,7 +941,7 @@ impl Node { return Err(Error::ConnectionFailed); } - log_info!(self.logger, "Connected to peer {}@{}. ", peer_info.pubkey, peer_info.address,); + log_info!(self.logger, "Connected to peer {}@{}. ", peer_info.node_id, peer_info.address); if permanently { self.peer_store.add_peer(peer_info)?; @@ -982,7 +983,7 @@ impl Node { /// /// Returns a temporary channel id. pub fn connect_open_channel( - &self, node_id: PublicKey, address: SocketAddr, channel_amount_sats: u64, + &self, node_id: PublicKey, address: NetAddress, channel_amount_sats: u64, push_to_counterparty_msat: Option, announce_channel: bool, ) -> Result<(), Error> { let rt_lock = self.runtime.read().unwrap(); @@ -997,10 +998,10 @@ impl Node { return Err(Error::InsufficientFunds); } - let peer_info = PeerInfo { pubkey: node_id, address }; + let peer_info = PeerInfo { node_id, address }; - let con_peer_pubkey = peer_info.pubkey; - let con_peer_addr = peer_info.address; + let con_node_id = peer_info.node_id; + let con_addr = peer_info.address.clone(); let con_success = Arc::new(AtomicBool::new(false)); let con_success_cloned = Arc::clone(&con_success); let con_logger = Arc::clone(&self.logger); @@ -1009,8 +1010,7 @@ impl Node { tokio::task::block_in_place(move || { runtime.block_on(async move { let res = - connect_peer_if_necessary(con_peer_pubkey, con_peer_addr, con_pm, con_logger) - .await; + connect_peer_if_necessary(con_node_id, con_addr, con_pm, con_logger).await; con_success_cloned.store(res.is_ok(), Ordering::Release); }) }); @@ -1036,7 +1036,7 @@ impl Node { let user_channel_id: u128 = rand::thread_rng().gen::(); match self.channel_manager.create_channel( - peer_info.pubkey, + peer_info.node_id, channel_amount_sats, push_msat, user_channel_id, @@ -1046,7 +1046,7 @@ impl Node { log_info!( self.logger, "Initiated channel creation with peer {}. ", - peer_info.pubkey + peer_info.node_id ); self.peer_store.add_peer(peer_info)?; Ok(()) @@ -1447,44 +1447,52 @@ impl Drop for Node { } async fn connect_peer_if_necessary( - pubkey: PublicKey, peer_addr: SocketAddr, peer_manager: Arc, + node_id: PublicKey, addr: NetAddress, peer_manager: Arc, logger: Arc, ) -> Result<(), Error> { - for (node_pubkey, _addr) in peer_manager.get_peer_node_ids() { - if node_pubkey == pubkey { + for (pman_node_id, _pman_addr) in peer_manager.get_peer_node_ids() { + if node_id == pman_node_id { return Ok(()); } } - do_connect_peer(pubkey, peer_addr, peer_manager, logger).await + do_connect_peer(node_id, addr, peer_manager, logger).await } async fn do_connect_peer( - pubkey: PublicKey, peer_addr: SocketAddr, peer_manager: Arc, + node_id: PublicKey, addr: NetAddress, peer_manager: Arc, logger: Arc, ) -> Result<(), Error> { - log_info!(logger, "Connecting to peer: {}@{}", pubkey, peer_addr); - match lightning_net_tokio::connect_outbound(Arc::clone(&peer_manager), pubkey, peer_addr).await + log_info!(logger, "Connecting to peer: {}@{}", node_id, addr); + + let socket_addr = addr + .to_socket_addrs() + .map_err(|_| Error::PeerInfoNotFound)? + .next() + .ok_or(Error::ConnectionFailed)?; + + match lightning_net_tokio::connect_outbound(Arc::clone(&peer_manager), node_id, socket_addr) + .await { Some(connection_closed_future) => { let mut connection_closed_future = Box::pin(connection_closed_future); loop { match futures::poll!(&mut connection_closed_future) { std::task::Poll::Ready(_) => { - log_info!(logger, "Peer connection closed: {}@{}", pubkey, peer_addr); + log_info!(logger, "Peer connection closed: {}@{}", node_id, addr); return Err(Error::ConnectionFailed); } std::task::Poll::Pending => {} } // Avoid blocking the tokio context by sleeping a bit - match peer_manager.get_peer_node_ids().iter().find(|(id, _addr)| *id == pubkey) { + match peer_manager.get_peer_node_ids().iter().find(|(id, _addr)| *id == node_id) { Some(_) => return Ok(()), None => tokio::time::sleep(Duration::from_millis(10)).await, } } } None => { - log_error!(logger, "Failed to connect to peer: {}@{}", pubkey, peer_addr); + log_error!(logger, "Failed to connect to peer: {}@{}", node_id, addr); Err(Error::ConnectionFailed) } } diff --git a/src/peer_store.rs b/src/peer_store.rs index 7d772aba7..90cdae63c 100644 --- a/src/peer_store.rs +++ b/src/peer_store.rs @@ -2,14 +2,14 @@ use crate::io::{ KVStore, TransactionalWrite, PEER_INFO_PERSISTENCE_KEY, PEER_INFO_PERSISTENCE_NAMESPACE, }; use crate::logger::{log_error, Logger}; -use crate::Error; +use crate::{Error, NetAddress}; +use lightning::impl_writeable_tlv_based; use lightning::util::ser::{Readable, ReadableArgs, Writeable, Writer}; use bitcoin::secp256k1::PublicKey; use std::collections::HashMap; -use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; use std::ops::Deref; use std::sync::RwLock; @@ -36,14 +36,14 @@ where pub(crate) fn add_peer(&self, peer_info: PeerInfo) -> Result<(), Error> { let mut locked_peers = self.peers.write().unwrap(); - locked_peers.insert(peer_info.pubkey, peer_info); + locked_peers.insert(peer_info.node_id, peer_info); self.write_peers_and_commit(&*locked_peers) } - pub(crate) fn remove_peer(&self, peer_pubkey: &PublicKey) -> Result<(), Error> { + pub(crate) fn remove_peer(&self, node_id: &PublicKey) -> Result<(), Error> { let mut locked_peers = self.peers.write().unwrap(); - locked_peers.remove(peer_pubkey); + locked_peers.remove(node_id); self.write_peers_and_commit(&*locked_peers) } @@ -51,8 +51,8 @@ where self.peers.read().unwrap().values().cloned().collect() } - pub(crate) fn get_peer(&self, peer_pubkey: &PublicKey) -> Option { - self.peers.read().unwrap().get(peer_pubkey).cloned() + pub(crate) fn get_peer(&self, node_id: &PublicKey) -> Option { + self.peers.read().unwrap().get(node_id).cloned() } fn write_peers_and_commit( @@ -143,56 +143,14 @@ impl Writeable for PeerStoreSerWrapper<'_> { #[derive(Clone, Debug, PartialEq, Eq)] pub(crate) struct PeerInfo { - pub pubkey: PublicKey, - pub address: SocketAddr, + pub node_id: PublicKey, + pub address: NetAddress, } -impl Readable for PeerInfo { - fn read( - reader: &mut R, - ) -> Result { - let pubkey = Readable::read(reader)?; - - let ip_type: u8 = Readable::read(reader)?; - - let ip_addr = if ip_type == 0 { - let v4bytes: u32 = Readable::read(reader)?; - let v4addr = Ipv4Addr::from(v4bytes); - IpAddr::from(v4addr) - } else { - let v6bytes: u128 = Readable::read(reader)?; - let v6addr = Ipv6Addr::from(v6bytes); - IpAddr::from(v6addr) - }; - - let port: u16 = Readable::read(reader)?; - - let address = SocketAddr::new(ip_addr, port); - - Ok(PeerInfo { pubkey, address }) - } -} - -impl Writeable for PeerInfo { - fn write(&self, writer: &mut W) -> Result<(), lightning::io::Error> { - self.pubkey.write(writer)?; - - match self.address.ip() { - IpAddr::V4(v4addr) => { - 0u8.write(writer)?; - u32::from(v4addr).write(writer)?; - } - IpAddr::V6(v6addr) => { - 1u8.write(writer)?; - u128::from(v6addr).write(writer)?; - } - } - - self.address.port().write(writer)?; - - Ok(()) - } -} +impl_writeable_tlv_based!(PeerInfo, { + (0, node_id, required), + (2, address, required), +}); #[cfg(test)] mod tests { @@ -207,12 +165,12 @@ mod tests { let logger = Arc::new(TestLogger::new()); let peer_store = PeerStore::new(Arc::clone(&store), Arc::clone(&logger)); - let pubkey = PublicKey::from_str( + let node_id = PublicKey::from_str( "0276607124ebe6a6c9338517b6f485825b27c2dcc0b9fc2aa6a4c0df91194e5993", ) .unwrap(); - let address: SocketAddr = "127.0.0.1:9738".parse().unwrap(); - let expected_peer_info = PeerInfo { pubkey, address }; + let address = NetAddress::from_str("127.0.0.1:9738").unwrap(); + let expected_peer_info = PeerInfo { node_id, address }; peer_store.add_peer(expected_peer_info.clone()).unwrap(); assert!(store.get_and_clear_did_persist()); @@ -226,7 +184,7 @@ mod tests { let peers = deser_peer_store.list_peers(); assert_eq!(peers.len(), 1); assert_eq!(peers[0], expected_peer_info); - assert_eq!(deser_peer_store.get_peer(&pubkey), Some(expected_peer_info)); + assert_eq!(deser_peer_store.get_peer(&node_id), Some(expected_peer_info)); assert!(!store.get_and_clear_did_persist()); } } diff --git a/src/test/functional_tests.rs b/src/test/functional_tests.rs index ec2b41b64..f2beeaaac 100644 --- a/src/test/functional_tests.rs +++ b/src/test/functional_tests.rs @@ -39,7 +39,7 @@ fn channel_full_cycle() { node_a .connect_open_channel( node_b.node_id(), - node_b.listening_address().unwrap(), + node_b.listening_address().unwrap().into(), funding_amount_sat, Some(push_msat), true, @@ -241,7 +241,7 @@ fn channel_open_fails_when_funds_insufficient() { Err(Error::InsufficientFunds), node_a.connect_open_channel( node_b.node_id(), - node_b.listening_address().unwrap(), + node_b.listening_address().unwrap().into(), 120000, None, true diff --git a/src/types.rs b/src/types.rs index 8aa77dbbd..4df2b01d7 100644 --- a/src/types.rs +++ b/src/types.rs @@ -7,6 +7,7 @@ use crate::UniffiCustomTypeConverter; use lightning::chain::chainmonitor; use lightning::chain::keysinterface::InMemorySigner; +use lightning::ln::msgs::NetAddress as LdkNetAddress; use lightning::ln::peer_handler::IgnoringMessageHandler; use lightning::ln::{PaymentHash, PaymentPreimage, PaymentSecret}; use lightning::routing::gossip; @@ -14,7 +15,7 @@ use lightning::routing::gossip::P2PGossipSync; use lightning::routing::router::DefaultRouter; use lightning::routing::scoring::ProbabilisticScorer; use lightning::routing::utxo::UtxoLookup; -use lightning::util::ser::{Readable, Writeable, Writer}; +use lightning::util::ser::{Hostname, Readable, Writeable, Writer}; use lightning_invoice::{Invoice, SignedRawInvoice}; use lightning_net_tokio::SocketDescriptor; use lightning_transaction_sync::EsploraSyncClient; @@ -24,8 +25,10 @@ use bitcoin::hashes::Hash; use bitcoin::secp256k1::PublicKey; use bitcoin::{Address, Network, Txid}; +use core::convert::TryFrom; use std::convert::TryInto; -use std::net::SocketAddr; +use std::fmt::Display; +use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6, ToSocketAddrs}; use std::str::FromStr; use std::sync::{Arc, Mutex}; @@ -77,22 +80,6 @@ pub(crate) type OnionMessenger = lightning::onion_message::OnionMessenger< IgnoringMessageHandler, >; -impl UniffiCustomTypeConverter for SocketAddr { - type Builtin = String; - - fn into_custom(val: Self::Builtin) -> uniffi::Result { - if let Ok(addr) = SocketAddr::from_str(&val) { - return Ok(addr); - } - - Err(Error::InvalidPublicKey.into()) - } - - fn from_custom(obj: Self) -> Self::Builtin { - obj.to_string() - } -} - impl UniffiCustomTypeConverter for PublicKey { type Builtin = String; @@ -290,3 +277,161 @@ impl UniffiCustomTypeConverter for Txid { obj.to_string() } } + +impl UniffiCustomTypeConverter for SocketAddr { + type Builtin = String; + + fn into_custom(val: Self::Builtin) -> uniffi::Result { + if let Ok(addr) = SocketAddr::from_str(&val) { + return Ok(addr); + } + + Err(Error::InvalidPublicKey.into()) + } + + fn from_custom(obj: Self) -> Self::Builtin { + obj.to_string() + } +} + +/// The network address of a Lightning node. +/// +/// Currently only IPv4, IPv6, and DNS hostnames are supported. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct NetAddress(LdkNetAddress); + +impl Display for NetAddress { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self.0 { + LdkNetAddress::IPv4 { addr, port } => { + let ip_addr = Ipv4Addr::from(addr); + write!(f, "{}:{}", ip_addr, port) + } + LdkNetAddress::IPv6 { addr, port } => { + let ip_addr = Ipv6Addr::from(addr); + write!(f, "{}:{}", ip_addr, port) + } + LdkNetAddress::Hostname { ref hostname, port } => { + write!(f, "{}:{}", hostname.as_str(), port) + } + LdkNetAddress::OnionV2(o) => { + write!(f, "OnionV2 (unsupported): {:?}", o) + } + LdkNetAddress::OnionV3 { ed25519_pubkey, checksum, version, port } => write!( + f, + "OnionV3 (unsupported): {:?}/{:?}/{:?}/{:?}", + ed25519_pubkey, checksum, version, port + ), + } + } +} + +impl UniffiCustomTypeConverter for NetAddress { + type Builtin = String; + fn into_custom(val: Self::Builtin) -> uniffi::Result { + Ok(NetAddress::from_str(&val).unwrap()) + } + + fn from_custom(obj: Self) -> Self::Builtin { + obj.to_string() + } +} + +impl FromStr for NetAddress { + type Err = (); + + fn from_str(s: &str) -> Result { + match std::net::SocketAddr::from_str(s) { + Ok(addr) => { + let port: u16 = addr.port(); + match addr { + std::net::SocketAddr::V4(addr) => { + let addr = addr.ip().octets(); + return Ok(Self(LdkNetAddress::IPv4 { addr, port })); + } + std::net::SocketAddr::V6(addr) => { + let addr = addr.ip().octets(); + return Ok(Self(LdkNetAddress::IPv6 { addr, port })); + } + } + } + Err(_) => { + let trimmed_input = match s.rfind(":") { + Some(pos) => pos, + None => return Err(()), + }; + let host = &s[..trimmed_input]; + let port: u16 = match s[trimmed_input + 1..].parse() { + Ok(port) => port, + Err(_) => return Err(()), + }; + + if let Ok(hostname) = Hostname::try_from(host.to_string()) { + return Ok(Self(LdkNetAddress::Hostname { hostname, port })); + } + + Err(()) + } + } + } +} + +impl From for NetAddress { + fn from(value: SocketAddr) -> Self { + match value { + SocketAddr::V4(v4addr) => NetAddress::from(v4addr), + SocketAddr::V6(v6addr) => NetAddress::from(v6addr), + } + } +} + +impl From for NetAddress { + fn from(value: SocketAddrV4) -> Self { + Self(LdkNetAddress::IPv4 { addr: value.ip().octets(), port: value.port() }) + } +} + +impl From for NetAddress { + fn from(value: SocketAddrV6) -> Self { + Self(LdkNetAddress::IPv6 { addr: value.ip().octets(), port: value.port() }) + } +} + +impl ToSocketAddrs for NetAddress { + type Iter = std::option::IntoIter; + + fn to_socket_addrs(&self) -> std::io::Result { + match self.0 { + LdkNetAddress::IPv4 { addr, port } => { + let ip_addr = Ipv4Addr::from(addr); + (ip_addr, port).to_socket_addrs() + } + LdkNetAddress::IPv6 { addr, port } => { + let ip_addr = Ipv6Addr::from(addr); + (ip_addr, port).to_socket_addrs() + } + LdkNetAddress::Hostname { ref hostname, port } => { + Ok((hostname.as_str(), port).to_socket_addrs()?.next().into_iter()) + } + LdkNetAddress::OnionV2(..) => { + Err(std::io::Error::from(std::io::ErrorKind::Unsupported)) + } + LdkNetAddress::OnionV3 { .. } => { + Err(std::io::Error::from(std::io::ErrorKind::Unsupported)) + } + } + } +} + +impl Writeable for NetAddress { + fn write(&self, writer: &mut W) -> Result<(), std::io::Error> { + self.0.write(writer) + } +} + +impl Readable for NetAddress { + fn read(reader: &mut R) -> Result { + let addr: LdkNetAddress = Readable::read(reader)?; + Ok(Self(addr)) + } +}